Select any of the transcript, and take a queued message back

Two things a reader could not do to what is on screen.

**Selection.** Nothing in the transcript was selectable at all, so a
command, a path or an error message could be read and not copied. One
`SelectionContainer` around the whole list rather than one per row: a
transcript is one body of text to a reader, and a selection has to be able
to run from a reply into the tool output under it. Per row it also could
not, and whatever was drawn without a container would have been silently
unselectable -- a state nothing on screen reports. Rows keep their tap
handlers; checked on the emulator that expanding a tool call, scrolling and
flinging are all unaffected, since a selection is a long press.

**Taking a message back.** A message sent into a running turn sits as a
bubble waiting to be read, and there was no way to change your mind: it is
tappable now, and the server answers `POST /sessions/{id}/unqueue`.

The answer has three states, and the middle one is the point. Claude's
driver writes a steer into the CLI's stdin the instant it arrives -- that
is what makes it reach the model at the next tool boundary rather than at
the end of the turn, and it was measured -- so the line is already gone and
`AlreadySent` is the only honest answer it can give. Holding the write
until a boundary would make the drop real and cost a steer one model call,
which is the latency the immediate write exists to remove; rejected on that
trade, with the reasoning in PLAN.md. The refusal is drawn on the bubble
that was pressed rather than in the error row under the header, a screen
away from it.

Where a driver really does hold its queue -- echo today -- the message goes
for good, and it goes as an `Event::MessageDropped` rather than as a return
value: every device watching the session loses the bubble, and a phone that
reconnects and replays the `messageQueued` does not put back one that was
cancelled with nothing left to resolve it.
This commit is contained in:
iris committed 2026-08-31 22:20:47 -04:00
1 parent 778b2e3b04
commit 82401cd887
12 files changed
+439 -48

No files matched your search

+14
View File
@@ -245,6 +245,20 @@ first if a remote spawn ever mangles an argument.
on the same transcript. What that costs is that the rows below slide up
under the reader's finger, so a row that has just moved ignores taps for
half a second (`SETTLE_MS`).
- **All transcript text is selectable, from one `SelectionContainer` around
the whole list** (`TranscriptList.kt`). Not per row: a transcript is one
body of text to a reader, so a selection has to be able to run from a
reply into the tool output under it -- and a container per row leaves
whatever was drawn without one silently unselectable, which nothing on
screen reports. Rows keep their tap handlers; selection is a long press.
- **A queued message can be tapped to take it back**, which is
`POST /sessions/{id}/unqueue` and a `messageDropped` event -- see PLAN.md's
"Taking a queued message back". On a **Claude** session it always refuses,
and that is correct rather than broken: the driver writes a steer into the
CLI the moment it arrives, so what the bubble is waiting for is the CLI
*reading* it, not this server sending it. The refusal is drawn on the
bubble. The echo driver really does hold its queue, so that is the rig for
the case where the drop succeeds.
- **Deleting a session offers to take the machine's own transcript with
it.** `DELETE /sessions/{id}?deleteForeign=true`, behind a switch in the
confirmation, and only where the driver keeps a record of its own