Texting · WyvernChat
Three regex scripts and one lorebook entry turn plain shorthand into WhatsApp-style bubbles: green on the right, grey on the left, read ticks, contact header, the lot. The AI never sees a single line of HTML.
Three layers, and each one only does one job.
me| poor baby. Cheap, and easy to get right.The important part is that layer three is display only. The model writes shorthand and reads shorthand back in its own history. It never sees the HTML, never has to produce it, and never wastes tokens on it. All the visual weight lives in your browser.
Asking a model to output styled HTML directly works for about four messages before it starts dropping closing tags and inventing colours. Shorthand in, HTML out, is far more stable, and a shorthand line costs maybe eight tokens against several hundred for the equivalent markup.
Three line types, each one a keyword, a pipe, and the content.
head| S | siofra | online them| who knew being programmer could be tiring tbh me| oh no did you have to sit in a comfy chair for eight hours me| poor baby
| Line | Becomes |
|---|---|
| head| | The contact bar at the top. Three fields: avatar letter, contact name, status line |
| me| | A green bubble on the right, with blue read ticks |
| them| | A grey bubble on the left |
me and them are from the character's point of view, not yours. me| is the character talking, and it renders green on the right. them| is you. Everyone wires this backwards on the first attempt, stares at it for a minute, and then gets it. (When you write your own messages the perspective flips to yours. See writing your own.)
This is what makes the model produce the shorthand at all. Without it, the regex has nothing to catch. Do NOT forget to enable the lorebook entry in your chat itself.
| Field | Value |
|---|---|
| Entry name | Texting With Regex |
| Priority | 1 |
| Position in context | After Character |
| Entry type | No specific type |
| Lua callback | No callback |
Key logic is AND ANY, so any one of these firing switches texting mode on.
texting, texted, text me, shoot me a text, sms, dm me, dmed, notification, over text
Around 311 tokens. It defines the format and, just as importantly, forbids everything else.
TEXTING MODE
Active when {{char}} and {{user}} are talking over text, not in person. When active your entire reply is ONE texting block in the shorthand below and nothing else: no prose, no narration, no *actions*. In-person scenes are written normally.
Format:
head| LETTER | CONTACT | status
them| {{user}}'s incoming message
me| {{char}}'s reply
me| another reply
Every line starts with its keyword followed immediately by a pipe. Always open with a head| line. Use only head|, them|, and me| lines. Never write any other line type.
LETTER = one letter for the avatar circle, usually the first letter of the contact name.
CONTACT = how {{char}} has {{user}} saved in their phone. Fit it to the relationship and {{char}}'s feelings: real name, pet name, a heart, an inside joke, an insult.
status = "online", "typing...", or "last seen today 9:14 PM".
them| = {{user}}'s texts (left side). me| = {{char}}'s texts (right side).
Voice: short, casual, lowercase if it suits {{char}}, emojis and abbreviations in character. Split longer thoughts into separate me| lines instead of one wall of text. No markdown (* _ #).
Only enter texting mode for pure phone scenes. If the phone is one beat inside a physical scene, drop a single block, then return to prose.
Five things in there are load-bearing, and it's worth knowing which:
| Line | What it prevents |
|---|---|
| nothing else | Prose wrapped around the bubbles. Without an explicit ban, models narrate |
| only head|, them|, me| | Invented prefixes like you|, which no regex is watching for |
| always open with head| | A conversation with no contact bar, floating in space |
| no markdown (* _ #) | Asterisks rendering as italics inside the bubbles and wrecking the illusion |
| split into separate me| lines | One giant paragraph in a single bubble, which reads nothing like texting |
CONTACT is characterisation. The entry tells the model to decide how the character has you saved in their phone (real name, pet name, a heart, an inside joke, an insult) and to fit it to how they currently feel about you. It's one word in the header bar, it costs nothing, and it tells you more about the relationship than a paragraph would. Watch it change over a long chat.
The last line matters too: the entry only claims pure phone scenes. If someone checks their phone mid-conversation, the model drops a single block and goes back to prose. That's what keeps texting mode from swallowing the whole roleplay the moment one of the keys fires.
Regex scripts live in Wyvern's settings under their own section. Each one is a find-and-replace that runs over messages as they're displayed.
Repeat three times, once per script below. When you're done the list looks like this:
Each pattern is anchored to a different keyword (^head|, ^me|, ^them|), so they can't collide or double-match. Run them in any order. That stops being true the moment you add a script whose pattern overlaps another's.
Copy each pair into a script of its own. The colours are WhatsApp's real dark-mode palette, so it looks right rather than approximately right.
Three capture groups: ([^|\n]*) twice, then (.*). The first two stop dead at the next pipe, which is what keeps the three fields from bleeding into each other. The third takes everything left on the line.
/^head\|\s*([^|\n]*)\|\s*([^|\n]*)\|\s*(.*)$/gm
<div style="clear:both;width:100%;max-width:360px;margin:16px auto 0;background:#1f2c34;border-radius:16px 16px 0 0;padding:9px 12px;display:flex;align-items:center;gap:10px;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;box-sizing:border-box"><span style="color:#aebac1;font-size:26px;line-height:1">‹</span><span style="width:38px;height:38px;border-radius:50%;flex:0 0 38px;background:linear-gradient(135deg,#6a4c93,#b5179e);display:flex;align-items:center;justify-content:center;font-weight:600;color:#fff;font-size:16px;text-transform:uppercase">$1</span><span style="flex:1;min-width:0"><span style="display:block;font-size:15px;font-weight:600;color:#e9edef;line-height:1.25">$2</span><span style="display:block;font-size:12px;color:#8696a0;line-height:1.25">$3</span></span><span style="color:#aebac1;font-size:20px;line-height:1">⋮</span></div>
$1 is the avatar letter, dropped into a 38px circle with a purple gradient. $2 is the name, $3 the status line. The back chevron and the three-dot menu are HTML entities, not images.
/^me\|\s*(.*)$/gm
<div style="clear:both;width:100%;max-width:360px;margin:0 auto;background:#0b141a;padding:3px 9px;display:flex;justify-content:flex-end;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;box-sizing:border-box"><span style="position:relative;display:inline-block;max-width:76%;background:#005c4b;color:#e9edef;padding:6px 9px 7px;border-radius:9px;border-top-right-radius:2px;font-size:14.5px;line-height:1.38">$1<span style="font-size:10.5px;color:#53bdeb;margin-left:6px;font-weight:700;letter-spacing:-2px">✓✓</span><span style="position:absolute;top:0;right:-7px;border-top:8px solid #005c4b;border-right:8px solid transparent"></span></span></div>
/^them\|\s*(.*)$/gm
<div style="clear:both;width:100%;max-width:360px;margin:0 auto;background:#0b141a;padding:3px 9px;display:flex;justify-content:flex-start;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;box-sizing:border-box"><span style="position:relative;display:inline-block;max-width:76%;background:#202c33;color:#e9edef;padding:6px 9px 7px;border-radius:9px;border-top-left-radius:2px;font-size:14.5px;line-height:1.38">$1<span style="position:absolute;top:0;left:-7px;border-top:8px solid #202c33;border-left:8px solid transparent"></span></span></div>
The two bubble scripts are near-identical twins. Outgoing justifies right, uses #005c4b, squares off its top-right corner and carries the read ticks. Incoming justifies left, uses #202c33, squares off its top-left corner and has no ticks, because you don't get read receipts on your own screen for messages you received.
Identical on all three scripts.
| Toggle | Why |
|---|---|
| Markdown only | On. Display only, never sent to the AI. This is the toggle the entire approach rests on. It keeps the HTML out of context |
| Prompt only | Off. The opposite of what you want. It would send the HTML to the model and show you nothing |
| Affects user input | On. So your own messages get bubbles too, not just the character's |
| Affects AI response | On. The main event |
| Re-apply when a message is edited | On. Fix a typo and the bubble re-renders instead of collapsing back to raw shorthand |
With it on, the model's history still reads me| poor baby. It stays fluent in its own shorthand, context stays small, and the styling costs nothing. Turn it off and you'll be feeding several hundred tokens of inline CSS back into the model every single turn.
| Hex | What it is |
|---|---|
| #0b141a | Chat background behind the bubbles |
| #1f2c34 | Contact header bar |
| #005c4b | Outgoing bubble green |
| #202c33 | Incoming bubble grey |
| #e9edef | Message text |
| #8696a0 | Status line, secondary text |
| #53bdeb | Read ticks |
Change a bubble colour in two places or you'll get a mismatched tail: once in the background, once in the tail's border-top.
That little triangle beside each bubble is an empty span with two borders: one solid in the bubble colour, one transparent. A border corner is a diagonal, so you get a right triangle for free. No images, no clip-path, nothing to sanitize away.
The gradient lives in linear-gradient(135deg,#6a4c93,#b5179e). Swap both stops to recolour it per character.
max-width:360px appears in all three scripts and sets the phone width. Change it in all three or the header will stop lining up with the bubbles.
Inline style attributes are the safe bet for anything a regex injects into Wyvern, and they're what's known to survive. A <style> block or a class hook may or may not make it through, and debugging that is a worse afternoon than just writing it inline. Hence one enormous unbroken line per script instead of anything readable.
Because Affects user input is on, everything you type runs through the same three scripts. Write the shorthand yourself and your messages come out as bubbles too, instead of sitting there as plain grey text ruining the effect.
head| (avatar letter) | (contact name) | online them| (bot's last message) me| your msg1 me| your msg2 me| your msg3 me| your msg4
head| m | minjae 🍜 | online them| im practically half dead over here me| funny u mention it me| in fact, I did get a blister me| carpal tunnel syndrome is a real bitch yknow me| also im not a clown or entertainment
Each message renders as its own little phone screen, belonging to whoever wrote it. So me| always means "the person typing this message" and always goes green on the right. In the character's replies that's the character. In yours, it's you. Same two keywords, opposite owners, and it looks right on screen either way.
The them| line at the top repeats whatever the character just said to you. It isn't required, but without it your bubbles open on an empty screen with no context above them. Echoing one line stitches your message onto the end of theirs so the thread reads continuously as you scroll.
The character decides how they have you saved. You decide how you have them saved, and it's the same free characterisation in reverse. minjae 🍜 tells you something a plain name wouldn't. Emoji work fine in that field.
Everything else carries over from the lorebook rules: no markdown, and split a long thought across several me| lines instead of cramming it into one. Four short bubbles read like texting. One long one reads like an essay someone sent you at 2am.
Check the flags on the end of your pattern. You need /gm. The m makes ^ and $ match at every line break instead of only at the very start and end of the message. Since every one of these lines sits mid-message, dropping it doesn't give you "fewer matches", it gives you zero. Missing g is subtler: you'll get exactly one bubble per message and the rest stay as text.
Missing g. See above.
There's a pipe character inside the contact name. The header pattern splits on pipes, so a name like sio|fra shifts every field one place left and the status ends up wherever's left over. Pipes are fine inside message bodies, since me| wait|no seriously works because that pattern grabs everything after the first pipe, but keep them out of head| lines.
Lorebook problem, not regex. The entry has to say the reply is the texting block and nothing else, meaning no narration and no asterisk actions. Models will happily wrap beautiful bubbles in two paragraphs of scene-setting unless told not to.
Things like you| or user| instead of them|. The format block in the lorebook needs to show the exact literal prefixes. If it keeps drifting, add a line naming the three allowed prefixes explicitly.
Check Markdown only is on. If the HTML went to the model instead of the screen, this is what you'd see.
None of the nine keys fired. Add whatever phrasing you actually use. The key list is the only thing standing between a normal scene and a texting one.