fix(desktop): stop resurrecting completed stream bubbles - #70209
Conversation
Related: #70173 deduplicates an interim stream bubble and its final completion; this patch separately prevents an already-completed local stream projection from being re-appended during history reconciliation. |
b2917fc to
af3cf98
Compare
|
Thanks for the focused regression fix. Current main still has the reported predicate: The proposed change makes the explicit flag authoritative and adds the missing completed-stream case. The PR is currently conflicting with main, but its two-file change is localized and should be mechanically salvageable without broad rework. Automated hermes-sweeper review. |
af3cf98 to
ac2d436
Compare
|
Superseded by #77644, with credit — the duplicate settled bubble is real and it's fixed there. Your diagnosis of the mechanism holds; I took a narrower fix than the predicate change. Keying The conflict with main is moot now; no rebase needed on your side. |
…ative transcript Third residue class behind NousResearch#70209: when the renderer kept both the committed user row and its optimistic duplicate, the duplicate's role-ordinal shifts past the committed user onto a LATER user (e.g. a background process notice) — newest-user check and ordinal pairing both miss it, and it gets re-appended after the transcript. Gate: a live optimistic user row whose exact text already exists anywhere in the authoritative transcript is dropped. 84/84 focused tests (RED→GREEN verified).
…eady carries A still-pending assistant stream row (id `assistant-stream-*`) whose reply the authoritative transcript already committed used to fall through to `preserved.push` when ordinal pairing missed it — the commit shifted the row's ordinal under compaction/history rewrites, so `nextByRoleOrdinal` returned nothing and the local copy was appended to the tail, rendering the same answer twice (reported as A B C D E C D tail duplication). The #70209 guard only covers SETTLED local rows (`pending !== true`); pending rows were unprotected. Match pending rows against SETTLED authoritative rows before appending: - identical answer text -> authoritative already carries it - authoritative extends local text -> authoritative is the settled final version of the still-streaming local copy - local extends authoritative text -> replace the committed row with the richer local body instead of appending Live projection shells (still-pending candidates) never match, so the traces-only local row keeps replacing the empty shell.
Summary
pendingflag as the authority when reconciling local assistant projectionsassistant-stream-*bubble from being re-appended after authoritative history has caught upWhy
preserveLocalPendingTurnMessagescurrently considers any assistant message whose ID starts withassistant-stream-to be pending, even after terminal stream paths have explicitly setpending: false.If such a completed local bubble has no same-ordinal assistant in the refreshed authoritative transcript, the reconciliation step appends it again. This can resurrect an already completed assistant bubble alongside the persisted response.
All live stream constructors set
pending: true, while completion, interim sealing, interruption, and error paths setpending: false; the explicit state is therefore the correct authority. This is a separate reconciliation invariant from the upstream stream/final merge work in #70173.Related: #70108
Verification
origin/mainand passes with this patchnpm exec vitest run -- --project ui src/app/session/hooks/use-session-actions/utils.test.ts— 33 passednpm exec vitest run -- --project ui src/app/session/hooks/use-session-actions.test.tsx— 33 passednpm run typecheck— passednpm exec eslint -- src/app/session/hooks/use-session-actions/utils.ts src/app/session/hooks/use-session-actions/utils.test.ts— passednpm exec prettier -- --check src/app/session/hooks/use-session-actions/utils.ts src/app/session/hooks/use-session-actions/utils.test.ts— passed