← guides

Texting · WyvernChat

Text messages that
look like text messages.

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.

A chat rendered as a WhatsApp-style conversation: a contact header reading siofra online, grey incoming bubbles on the left and green outgoing bubbles with blue double ticks on the right.
What you're building. Every bubble here started life as one short line of plain text.

Contents
  1. How it works
  2. The shorthand
  3. 1 · The lorebook entry
  4. 2 · Creating a regex script
  5. 3 · The three scripts
  6. The toggles, explained
  7. Making it yours
  8. Writing your own
  9. When it breaks

Overview

How it works

Three layers, and each one only does one job.

  1. A lorebook entry teaches the model a tiny shorthand and tells it when to use it.
  2. The model writes plain lines like me| poor baby. Cheap, and easy to get right.
  3. Three regex scripts catch those lines on their way to your screen and swap them for styled HTML.

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.

Why this is the good way round

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.

Overview

The shorthand

Three line types, each one a keyword, a pipe, and the content.

what the model writes
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
LineBecomes
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
Read this bit twice

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.)

Step one

1 / 3

The lorebook entry

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.

Entry settings

FieldValue
Entry nameTexting With Regex
Priority1
Position in contextAfter Character
Entry typeNo specific type
Lua callbackNo callback
The lorebook entry editor showing name Texting With Regex, priority 1, position After Character, no entry type and no Lua callback.
After Character puts the entry late in context, where it sticks out instead of getting buried under the character card.

Keys

Key logic is AND ANY, so any one of these firing switches texting mode on.

keys
texting, texted, text me, shoot me a text, sms, dm me, dmed, notification, over text
The lorebook keys panel showing nine keys as chips and key logic set to AND ANY.
Nine keys, all the natural ways a scene turns into a text conversation.

Content

Around 311 tokens. It defines the format and, just as importantly, forbids everything else.

entry content
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:

LineWhat it prevents
nothing elseProse 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| linesOne giant paragraph in a single bubble, which reads nothing like texting
The nicest touch in the whole thing

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.

Step two

2 / 3

Creating a regex script

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.

  1. Open the regex section in "AI Connections" and create a new script.
  2. Give it a Script Name. Purely for you. Mine are grouped under a folder called Texting and script names can be seen in screenshots below.
  3. Paste the Find Regex from Step 3.
  4. Paste the Replace With part from Step 3.
  5. Leave Trim Out empty, and Substitute Regex on "Don't substitute".
  6. Leave Min Depth and Max Depth blank so it applies to the whole chat.
  7. Set the toggles as shown in the toggles section, which matters more than it looks.
The regex script editor showing Script Name, Find Regex, Replace With, Trim Out, Substitute Regex, Min Depth and Max Depth fields.
One script's editor. The Replace With field is a single unbroken line of HTML. It will look alarming, and that's correct.

Repeat three times, once per script below. When you're done the list looks like this:

Three enabled regex scripts named sms head, sms out and sms in, grouped under a Texting folder.
All three enabled. Grouping them keeps the list navigable once you have a dozen scripts.
Order doesn't matter here

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.

Step three

3 / 3

The three scripts

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.

sms head, the contact bar

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.

find regex
/^head\|\s*([^|\n]*)\|\s*([^|\n]*)\|\s*(.*)$/gm
replace with
<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">&#8249;</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">&#8942;</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.

sms out, the character's messages

find regex
/^me\|\s*(.*)$/gm
replace with
<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">&#10003;&#10003;</span><span style="position:absolute;top:0;right:-7px;border-top:8px solid #005c4b;border-right:8px solid transparent"></span></span></div>

sms in, your messages

find regex
/^them\|\s*(.*)$/gm
replace with
<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.

Settings

The toggles, explained

Identical on all three scripts.

Regex script toggles: Markdown only, Affects user input and Re-apply when a message is edited are checked on the left; Prompt only is unchecked and Affects AI response is checked on the right.
Four on, one off.
ToggleWhy
Markdown onlyOn. Display only, never sent to the AI. This is the toggle the entire approach rests on. It keeps the HTML out of context
Prompt onlyOff. The opposite of what you want. It would send the HTML to the model and show you nothing
Affects user inputOn. So your own messages get bubbles too, not just the character's
Affects AI responseOn. The main event
Re-apply when a message is editedOn. Fix a typo and the bubble re-renders instead of collapsing back to raw shorthand
Markdown only is the whole trick

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.

Customising

Making it yours

The colours

HexWhat it is
#0b141aChat background behind the bubbles
#1f2c34Contact header bar
#005c4bOutgoing bubble green
#202c33Incoming bubble grey
#e9edefMessage text
#8696a0Status line, secondary text
#53bdebRead 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.

The tail

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 avatar

The gradient lives in linear-gradient(135deg,#6a4c93,#b5179e). Swap both stops to recolour it per character.

Width

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.

Why it's all inline styles

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.

Your side

Writing your own messages

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.

The pattern

template
head| (avatar letter) | (contact name) | online
them| (bot's last message)
me| your msg1
me| your msg2
me| your msg3
me| your msg4

A real one

example
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
The perspective flips, and that's correct

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.

Why you echo their last line

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 contact name is yours to play with

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.

Troubleshooting

When it breaks

Nothing renders, you just see raw shorthand

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.

Only the first bubble renders

Missing g. See above.

The contact header comes out scrambled

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.

The model writes prose around the bubbles

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.

The model invents its own prefixes

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.

Bubbles appear but the styling is stripped

Check Markdown only is on. If the HTML went to the model instead of the screen, this is what you'd see.

Texting mode never activates

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.