fix(chat): pending dialog survives mid-window transcript writes and refetch churn #90
No reviewers
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab!90
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "lab/20260710-0155"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #89
Two independent bugs behind the operator report ("question dialog disappears but is still in Claude Code" / "what I type as the answer just disappears"):
Server: mtime staleness guard hid genuinely pending dialogs
claudecode.PendingDialogsuppressed the dialog spool whenever the transcript's mtime was newer than the spool's — premised on compat §5's "the transcript is byte-frozen while a dialog is pending". Live evidence (grill transcriptd4be520a, 2026-07-10): a message queued while the picker was up appendedqueue-operation+attachmententries mid-window, after which the server servedpending_dialog: nullfor a still-open picker (card gone in the chat, composer unlocked over a focused picker).The rotation staleness issue #34 actually cared about is now keyed to the spool payload's own session identity (
session_id,transcript_path-stem fallback) compared to the current transcript's filename stem: exact for /clear//rewind (suppresses regardless of which file is newer on disk), immune to mid-window writes. A payload with no session identity degrades to the old mtime backstop. compat §5/§9 record the corrected ground truth.Client: dialog answers wiped by refetch churn
Every messages response is a fresh JSON parse, so the same pending dialog arrived as a new object each SSE tick.
DialogPanel's andMultiQuestionForm's reset effects used Solid'son(...)without equality gating — any refetch (sibling-runrun.changed, resync, the needs-input marker) wiped in-progress picks and half-typed Other text; the identity churn also recreated the option rows'<input>(focus loss). NowpendingDialogFieldkeeps its object identity while the sametool_idstays pending, and both resets key on an equality-gatedcreateMemo.Tests
All red on the old code, green with the fix:
TestPendingDialog_survivesTranscriptWritesDuringPending— same-session transcript write during the pending window keeps the dialog servedTestPendingDialog_staleAfterTranscriptRotation— rotated-out session suppressed regardless of mtime order (hardened both directions)TestPendingDialog_legacyPayloadFallsBackToMtime— identity-less payload keeps the old backstopGates:
go test ./...,go vet,tsc --noEmit, eslint, prettier, 560 web tests — all green.🤖 Generated with Claude Code
https://claude.ai/code/session_01GTGfgrhqMrfNotfZ6dypsS