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:
1 parent
778b2e3b04
commit
82401cd887
12 files changed
+439
-48
No files matched your search
@@ -247,6 +247,35 @@ turn. Claude's dialect: a `user` message on stdin mid-stream; pi's: `steer`.
|
||||
- Images in: base64 image content blocks in the stream-json user message.
|
||||
- Working directory, host, and model are spawn-screen fields.
|
||||
|
||||
### Taking a queued message back (decided 2026-08-31)
|
||||
|
||||
A message sent into a running turn is drawn as a bubble waiting below the
|
||||
transcript, and tapping it asks the server to drop it before the session
|
||||
reads it — `POST /sessions/{id}/unqueue {messageId}`, answered by
|
||||
`Driver::unqueue` and recorded as `Event::MessageDropped` so that every
|
||||
device watching loses the bubble and a reconnect does not replay it back.
|
||||
|
||||
The answer has **three** states rather than a yes/no, and that is the whole
|
||||
of the design: `Dropped`, `AlreadySent`, and `Unknown`. The reason is that
|
||||
the Claude driver can only ever give the middle one. It writes a steer into
|
||||
the CLI's stdin the instant it arrives — that is what makes a steer reach
|
||||
the model at the next tool boundary instead of at the end of the turn, and
|
||||
it was measured (see `Queue`'s doc comment) — so the line is gone before the
|
||||
phone could ask for it back. What waits in `awaiting` is the *announcement*,
|
||||
not the message.
|
||||
|
||||
Holding the write until a boundary was considered and rejected on 2026-08-31:
|
||||
it would make the drop real everywhere, but it costs a steer one model call,
|
||||
which is the latency the immediate write was introduced to remove. So the
|
||||
refusal is the honest answer and it is reported where the reader pressed —
|
||||
on the bubble itself, not in the screen's error row, which is under the
|
||||
header a screen away. What a tap buys on a Claude session is therefore
|
||||
knowing that the session has already been told; on a driver that really does
|
||||
hold a queue (echo today) the message goes.
|
||||
|
||||
`Unknown` is not "we could not find out": a driver that is gone reported
|
||||
everything it was holding when it closed, so there is nothing waiting.
|
||||
|
||||
### Session processes outlive the backend (decided 2026-08-29)
|
||||
|
||||
A session's process is **left running when the backend stops, and adopted
|
||||
@@ -659,6 +688,7 @@ GET /sessions list (id, provider, host, title, model, st
|
||||
POST /sessions spawn {provider, host, model, cwd, permission_mode, title}
|
||||
GET /sessions/:id/events?after=N SSE: transcript replay from N, then live
|
||||
POST /sessions/:id/message {text, attachment_ids}
|
||||
POST /sessions/:id/unqueue {message_id} take back one not read yet
|
||||
POST /sessions/:id/answer {question_id, answer} (questions and permissions)
|
||||
POST /sessions/:id/interrupt stop the running turn; the process stays
|
||||
POST /sessions/:id/stop end the process; the session and transcript stay
|
||||
|
||||
Reference in new issue
Block a user