Don't call a turn finished with a message still waiting behind it

A message written into the tail of a turn is read the moment that turn's
`result` lands: the session reports idle and is running again in the same
breath. The phone that sent it got "finished" in between -- seconds before
anything it asked for had been done, which is the notification arriving to
say the opposite of what is happening.

`notification_for` now takes how many messages the session has been given
and not started reading, and a turn ending with any of them waiting is not
an ending. The count is kept in `pump`, from the recorded events, because
that is the one place that sees all of them in transcript order: a
`messageQueued` up, and the `userMessage` that resolves it or a
`messageDropped` down. Asking the driver instead would answer about the
moment the question was asked rather than the moment the status was
written, which is the same class of mistake as reading a session's status
to decide what a queue contains.

It deliberately does not suppress *awaiting input*. A question is worth
interrupting somebody for whatever is queued behind it -- the queue is
precisely what will not move until it is answered.

Tested both halves: the decision on the number, and the number itself,
where an echo turn that reads its queued message before going idle still
announces its finish. That last is the case a suppression written slightly
wrong silences, and it is the common one.
This commit is contained in:
iris committed 2026-08-31 22:43:24 -04:00
1 parent bfaf5e6f38
commit a1eedd7a78
2 files changed
+118 -15

No files matched your search

+13
View File
@@ -925,6 +925,19 @@ level: the session on screen is registered by the one composable that draws
one, and "the app is up" *is* the banner queue being collected, since it
collects only while it is on screen.
**What counts as finished** is decided in `notification_for`, and since
2026-08-31 it takes the number of messages the session has been given and
not started reading. With one waiting, a turn ending is not the work
ending: a message written into the tail of a turn is read the moment that
turn's `result` lands, so the session goes idle and immediately runs again
-- and the phone that sent it was told its work had finished, seconds
before any of it was done. The count is kept in `pump` from the recorded
events (`MessageQueued` up, the `UserMessage` that resolves it or a
`MessageDropped` down), because that is the one place that sees every event
in transcript order. It does not suppress *awaiting input*: a question is
worth saying whatever is queued behind it, and the queue is exactly what
will not move until it is answered.
The alternative considered and rejected was giving the app its own
connection to `/notifications` while it is in front. That is a second stream
per device saying the same thing, and it puts the "which of these two shows