Repository navigation
fix(desktop): role-aware tail-first stamping (re-land of #364) - #398
Conversation
|
| Filename | Overview |
|---|---|
| apps/desktop/src/app/chat/hooks/use-session-changes.ts | Replaces the linear role-blind stamp walk with a role-aware tail-first algorithm; the 3+-id extras loop can stamp a zombie with a duplicate committed ID when extractCommittedMessageIds returns overlapping array+scalar ids |
| apps/desktop/src/app/chat/hooks/use-session-changes.test.ts | Adds two new describe blocks (9 tests) covering the zombie mis-stamp and edge-frame scenarios; the 3+-id test uses distinct ids without a zombie row, leaving the duplicate-id + zombie combination untested |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[stampOptimisticTranscriptRows called] --> B{committedIds.length?}
B -- 0 --> C[Return unchanged messages]
B -- 1 --> D[Lone-id path: prefer assistant tail, else any]
B -- 2 --> E[2-id positional path]
B -- 3+ --> F[3+-id path]
E --> E1["claim(ids[0], 'user')"]
E1 --> E2["claim(ids[1], 'assistant')"]
E2 --> G[Build final messages via assignment map]
F --> F1["claim(ids[0], 'user')"]
F1 --> F2["claim(ids[1], 'assistant')"]
F2 --> F3["for each extra id: claim assistant else any"]
F3 -- "duplicate id + unclaimed zombie" --> F4["Zombie stamped with duplicate committed id — unsweepable"]
F3 -- "all stampable rows claimed" --> G
D --> D1["claim(id, 'assistant')"]
D1 -- fail --> D2["claim(id, null)"]
D1 -- success --> G
D2 --> G
G --> H[Return messages + stampedIds]
H --> I[Poll: dropZombieOptimisticRows sweeps remaining optimistic rows]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[stampOptimisticTranscriptRows called] --> B{committedIds.length?}
B -- 0 --> C[Return unchanged messages]
B -- 1 --> D[Lone-id path: prefer assistant tail, else any]
B -- 2 --> E[2-id positional path]
B -- 3+ --> F[3+-id path]
E --> E1["claim(ids[0], 'user')"]
E1 --> E2["claim(ids[1], 'assistant')"]
E2 --> G[Build final messages via assignment map]
F --> F1["claim(ids[0], 'user')"]
F1 --> F2["claim(ids[1], 'assistant')"]
F2 --> F3["for each extra id: claim assistant else any"]
F3 -- "duplicate id + unclaimed zombie" --> F4["Zombie stamped with duplicate committed id — unsweepable"]
F3 -- "all stampable rows claimed" --> G
D --> D1["claim(id, 'assistant')"]
D1 -- fail --> D2["claim(id, null)"]
D1 -- success --> G
D2 --> G
G --> H[Return messages + stampedIds]
H --> I[Poll: dropZombieOptimisticRows sweeps remaining optimistic rows]
Reviews (2): Last reviewed commit: "fix(desktop): 3+-id frames keep the posi..." | Re-trigger Greptile
… ids onto stale zombies (re-land of #364) Re-lands #364 (author: Kyzcreig; branch was 705 commits behind) onto current main. CDP-attach caught the mechanism live 2026-07-15: a stale optimistic assistant row (completion frame severed by a backend restart) absorbed the fresh turn user_id via the old top-down role-blind walk — the zombie wore a committed id (invisible to the #361 sweep) and painted as a permanent duplicate. stampOptimisticTranscriptRows now assigns role-aware and tail-first. Both LIVE Greptile #364 findings adjudicated: (1) silent user-id drop when no user-role stampable row exists is DELIBERATE and now documented + test-pinned — cross-role fallback would re-create the exact mis-stamp this fixes; the poll reconciles the committed row safely. (2) 3+-id frames (array + scalar fields both populated) now documented + test-pinned: assistant-preferring tail-first walk, extras left for the poll, no crash. RED-proven: swapping back the fork/main top-down impl fails 5/36 (the 3 zombie vectors + both edge-frame tests). Hook suite 36 green; full desktop suite delta vs clean fork/main baseline = zero new failures (17 pre-existing reds on both, 1 local-env flake passes 3/3 in isolation); tsc errors identical to baseline (7, all pre-existing). Co-authored-by: Kyzcreig <9063726+Kyzcreig@users.noreply.github.com>
… role-aware (Greptile #398)
f15ab4d to
e0702d6
Compare
What
Re-lands #364 (role-aware tail-first stamping) onto current
main. The original branch fell 705 commits behind; its hook file conflicted with the livesync Phase-1 work landed since.The live mechanism (CDP-attach, 2026-07-15): a stale optimistic assistant row — its completion frame severed by a backend restart — absorbed the fresh turn's
user_idvia the old top-down role-blind walk. The zombie then wore a committed id (invisible to the #361 sweep) and painted its stale text as a permanent duplicate.Both LIVE Greptile findings adjudicated (one fixed-by-documentation, one fixed-by-hardening)
message_idsarray + scalar fields both populated) — now handled by the same assistant-preferring tail-first walk: no crash, extras left for the poll, each id/row claimed at most once. Test-pinned.Verification
tsc --noEmit: identical 7 pre-existing errors on both trees.#364 will be closed as superseded with credit (Co-authored-by).