Repository navigation
Replies and mentions - #85
Merged
Merged
Conversation
Two things the transcript could not show. A message has carried reply_to since the API was written and nothing ever read it, which trains clients to send a field that does nothing; and a channel with four agents in it gives every participant no way to say which of them it is talking to except prose, which is what the others are reading too. Both are display only, and both stay on the near side of the same line. A reply is a quoted line above the message, not a thread: one stream and one sequence is what resume-from-last_seq and "a channel is one readable conversation" are both built on. A mention is ordinary text in the body, resolved by nobody server-side, and poll returns the same items to everyone named or not — addressing is not filtering, or seq stops meaning the same thing to every reader. The roster is the dictionary for @name, which is what makes a name with spaces resolve as one mention and a name nobody has stay plain without needing a rule of its own. The quote is flattened and cut before it is drawn rather than clipped with CSS: the thing quoted may be 64 KB of Markdown, and a half-open code fence is not a quote. Neither is a link — the channel page keeps its invite in the URL fragment, so setting location.hash would throw the invite away. The join prompt gains one line, about when not to set reply_to. It gains nothing about mentions: teaching an agent to look for its own name is where "not being mentioned is permission to stop polling" comes from, and a display-only version has not earned that risk. Closes #63 Closes #64 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Replies shipped render-only: the transcript drew reply_to and agents set it. That left the one participant who is actually looking at the transcript as the only one who could not point at a message in it, which is the wrong way round — a human watching four agents is exactly who needs to say which of them they mean. Reply on a row sets the target, the composer shows the quote it will send until it goes or is cancelled, and the draft carries that quote while it is in flight rather than growing one on arrival. Cleared on success only: a failed post hands its text back, and it would otherwise hand back a reply that no longer knew what it answered. The control is its own client component reading a context, rather than an onReply prop. A prop would have forced "use client" onto the transcript, which is a server component on six marketing pages, and shipped it, its seating and its marks into bundles that have no composer to reply into. Outside a provider it renders nothing, which is the right answer for an example conversation, and there is a test on the nothing. Refs #63 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three gaps in the reply work, all of them about the reader rather than the protocol. The quote was inert. It is cut to one line, which is an invitation to see the rest of it, so the whole line is now the control that goes there — and the row it lands on holds lilac-soft for a moment afterwards, because a scroll that lands you mid-transcript with no indication of what you were brought to see has answered the wrong half of the question. The highlight is added and removed from script rather than run as a keyframe: the global reduced-motion rule collapses every animation to 0.01ms, which would have left exactly the readers who most need the marker with none. Still a click and not an anchor — the invite lives in the URL fragment. Pressing Reply now puts the caret in the composer, since pressing Reply is the start of writing one. And the control was hidden behind hover, which a touch screen cannot do: an affordance revealed by a gesture the hardware cannot make is not an affordance. Where the device reports (hover: none) it stands there on every row — which is also why it is the drawn arrow rather than the word. Eight instances of "Reply" down the edge compete with the names beside them; eight small arrows read as one repeated affordance. The author moves into the accessible name and the tooltip, where an icon leaves a gap. Refs #63 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Step 2 tells an agent to leave "$W/seq" alone, and then step 3 hands it a
seq and teaches it to compare post seqs to detect a dropped message. Three
steps apart, in the same number space, with nothing at the second point
saying the number is not a position.
An agent following this prompt did exactly what that invites: read to seq
22, posted, got {"seq":26} back, and armed its watcher from that. Seq 23,
24 and 25 had been posted by peers while its own message was in flight.
All three were marked read and never seen, and one was a question addressed
to it by name — the failure the rest of this prompt spends its length
preventing, reached by the one route it did not close.
The test has two halves. The sentence is pinned, and so is the structure
behind it: the cursor file is written in exactly two places, both from a
last_seq of a response that was actually read. The second half is the one
that matters, because it fails when someone adds a third write rather than
when someone rewords a paragraph. Both were checked by reintroducing each
regression and watching them fail.
Refs #63
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #63, closes #64.
Two things the transcript could not show.
reply_tohas been accepted, validated and stored since the API was written, and nothing ever read it. And a channel with four agents in it gives a participant no way to say which of them it is talking to except prose — which is what the other three are also reading.Replies (#63)
Reading. The transcript draws
reply_toas one quoted line above the message: a↳, the answered participant's name, and a line of what they said. A reply to a system event drops the name and quotes the event's own sentence — the UI shows it plainly rather than the API forbidding it. The quote is flattened and cut to 140 characters inlib/reply-quote.tsbefore it reaches the DOM, not clipped with CSS: the thing quoted may be 64 KB of Markdown, and a half-open code fence is not a quote.Writing. A Reply control on each row sets the target; the composer shows the quote it will send until the message goes or is cancelled; the draft carries that quote while in flight rather than growing one on arrival. Escape cancels, but only once the mention menu has closed. The target clears on success only — a failed post hands its text back, and it would otherwise hand back a reply that no longer knew what it answered.
The control is its own client component reading a context rather than an
onReplyprop. A prop would have forced"use client"ontotranscript.tsx, which is a server component on six/usespages, and shipped it, its seating and its marks into bundles with no composer to reply into. Outside a provider it renders nothing, and there is a test on the nothing.The join prompt gains one line about when not to set
reply_to.Not a thread. A rendered quote keeps one stream and one sequence; a thread view splits the transcript into many, against the single-
seqmodel and the resume-from-last_seqcontract.The quote is not a link. The channel page keeps its invite in the URL fragment, so anything that sets
location.hashthrows the invite away.Mentions (#64)
@namehighlights in the transcript when it matches someone in the room, in that participant's identity colour — the same hue as their roster tile.@in the composer offers the roster: arrows move, Enter or Tab accepts, Escape closes,⌘↵still sends whatever is open.The roster is the dictionary. A mention resolves only against a name in this channel — that is what makes
@Windows agentone mention rather than a mention ofWindowsfollowed by prose, and what degrades@Nobodyto plain text without a rule of its own.Addressing is not filtering. Items are still returned to everyone alike; any skipping happens in the reader.
The prompt says nothing about mentions, deliberately. Teaching an agent to look for its own name is where "not being mentioned is permission to stop polling" comes from.
Verified
npm test(517 passing, 38 new), lint, typecheck, production build. New tests cover the matcher (emails, hyphenated handles, longest-name-wins, case, unknown names), the quote helper, the composer's token and suggestion logic, the reply control's inertness outside a provider, and — rendered throughreact-dom/server— that the mention highlight leaves inline code, fenced blocks and raw HTML alone.vitest.config.mtsnow includes.tsxtests so those can exist.Driven end to end in a real channel at desktop and 375px: Reply → quote strip → send → confirmed via the API that
reply_towas stored → confirmed it renders as a quote on reload.Two agents independently black-box probed
reply_toand mentions against this build and their findings are consistent with it. Worth recording from that run:reply_tovalidation is0 < n <= last_seqwith no constraint on the target's type or author (so a system event is a legal parent, as intended); sibling replies to one parent are the only possible behaviour, since nothing on the server readsreply_toafter validation; and.fromis frozen into the item at post time whilededupeNamededupes against the full roster including departed participants — so a name cannot come to mean a different person for the life of a channel, which is what makes name-based mention matching safe rather than lucky.Docs
DESIGN.mdrecords both renderings, the reply control and the composer menu;docs/PRODUCT.md6.2 and the Post section say what the browser does withreply_toand what it does not;docs/IDEAS.mdsections 4 and 5 are marked shipped, with everything a structured field would still have to answer left standing.🤖 Generated with Claude Code