fix(desktop): preserve active stream across hydration - #71877
Conversation
|
Thanks for the focused race fix. The premise remains present on current The proposed reconciliation composes the existing live-tail contract in The targeted hunks remain applicable against current main despite unrelated Desktop movement since the PR base. Automated hermes-sweeper review. |
|
Author-status audit — corrected 2026-08-05: GitHub now reports this head as mergeable, current-head CI is green, and there are no unresolved inline review threads. The earlier conflict classification was a stale mergeability result, not a branch defect. Restoring the PR to ready for review. No branch rewrite is required. |
|
Independent current-
The merged tree was discarded after verification and the worktree returned clean to — 🤖 Bot comment · LLM: gpt-5.6-sol · not written by a human |
What does this PR do?
Prevents a lagging Desktop transcript hydration from splitting one live assistant turn into two bubbles.
The post-turn hydrator can finish after the user has already started the next turn. It replaced the renderer's message array with the older stored snapshot while leaving the active
streamIdunchanged. The nextmessage.deltacould no longer find that id and created a second assistant row, producing the intermittent byte-for-byte duplicate described in #38319 and #71857.This PR closes the race at both seams:
streamIdorphaned, the next delta rebinds the newest pending assistant to that id instead of appending a second bubble.The fallback is deliberately narrow. It runs only when a prior stream id exists and a non-hidden pending assistant is present. A genuinely new stream still creates a new assistant message, and completed historical assistants are never reused by text equality.
Related Issue
Fixes #38319
Fixes #71857
Related but not duplicated by #68478: that broader session-reliability PR explicitly leaves the streaming-versus-hydration duplicate-response race out of scope.
Type of Change
Changes Made
reconcileStoredSessionMessages()as the shared stored-history reconciliation seam;streamIdby rebinding the newest pending assistant;How to Test
cd apps/desktop ../../node_modules/.bin/vitest run --project ui \ src/app/session/hooks/use-message-stream/hydration-stream-race.test.tsx \ src/app/session/hooks/use-session-actions/utils.test.ts npm run typecheckThen run ESLint on the five changed TypeScript files and
git diff --check.Expected focused result: 40 passed.
The stream regression was run against the pre-fix behavior first and failed with two assistant rows instead of one. The utility regression likewise failed because the shared reconciliation seam did not exist.
Checklist
Code
pytest tests/ -qand all tests pass — N/A for this Desktop-only TypeScript change; the focused UI tests and complete Desktop typecheck passDocumentation & Housekeeping
cli-config.yaml.example— N/A; no config keys changedCONTRIBUTING.md/AGENTS.md— N/A; no contributor workflow or architecture changedVerification