Status: deployed, integration-tested, and accepted in the reference web client
Telex implements durable message reactions as progressive IRCv3 client-tag state. A reaction is owned by an authenticated account and attached to one retained message. It is not a chat message, does not move a read or delivery cursor, and does not extend the parent's retention lifetime.
A client must negotiate message-tags and send TAGMSG with the retained
parent's canonical msgid:
@+reply=<parent-msgid>;+draft/react=π TAGMSG #room
@+reply=<parent-msgid>;+draft/unreact=π TAGMSG #room
The target may be a joined channel or an accessible direct-message target. The parent ID must belong to that exact conversation and still retain its message body. Reaction values contain 1β64 UTF-8 bytes and no control characters. One account may hold at most 16 distinct values on a message, and one message may hold at most 240 account/value rows.
Adding existing state or removing absent state is an idempotent no-op. A real
change is relayed as TAGMSG with a server-assigned msgid and, when
negotiated, time and account. Channel membership and direct-message policy
are checked through the ordinary messaging boundaries. With
standard-replies, invalid requests receive FAIL TAGMSG; otherwise Telex
uses a generic protocol error.
CHATHISTORY continues to count and page only retained PRIVMSG and NOTICE
events. Immediately after each returned parent, Telex emits its current
reaction rows as +draft/react TAGMSG entries inside the same history batch.
Those snapshot rows do not consume the requested message limit and do not
advance account read markers or installation delivery checkpoints.
If reactions make a multi-message response exceed the bounded connection
output budget, Telex returns CHATHISTORY LIMIT_EXCEEDED so the client can
retry with a smaller message limit; every valid one-message snapshot fits.
The separate reaction table stores only current state. Removing a reaction does not leave a tombstone in chat history. Pruning a parent first deletes its reactions, and a content reset removes them through the parent conversation. Consequently a reaction cannot preserve attribution or content beyond the retained message it annotates.
Telex sends reaction frames only to clients that negotiated message-tags.
It does not manufacture fallback PRIVMSG, NOTICE, status-buffer text, or
numerics for recipients. Clients that understand tags but not reactions can
silently ignore the unknown client-only tags and TAGMSG; clients without
message tags receive the ordinary message only. Bounded legacy JOIN replay
likewise remains message-only.
HexDroid uses an older, internally consistent dialect: it negotiates
draft/message-reactions, sends the parent as +draft/reply, and removes a
reaction with +draft/react-removed. Telex advertises that name as an explicit
compatibility marker, requires it to be negotiated together with
message-tags, and accepts those aliases only on a connection that negotiated
the marker. If canonical and compatibility tags are both present, their values
must match. Telex still stores one canonical reaction state and emits canonical
+reply plus +draft/react or +draft/unreact; recipients that negotiated the
compatibility marker also receive +draft/react-removed on removal. Other
clients never receive that extra alias. This compatibility path is deployed and
has passed live HexDroid add/remove acceptance.
The reference web client renders compact counts, shows the reacting accounts,
and currently offers thirteen quick choices: π, β€οΈ, π, π, π,
π€, π³οΈβπ, π, π€, π, ππΏ, ππΏ, and π©. This is only a
shortcut palette, not a reaction allowlist; the wire and storage model continue
to accept any valid bounded reaction value. Reaction changes are stored beside
their parent in the browser's bounded IndexedDB transcript and are refreshed
from the server's current history snapshot after reconnect.