fix(web-client): persist in-flight chat sends so reloads don't drop replies#1856
fix(web-client): persist in-flight chat sends so reloads don't drop replies#1856john-the-dev wants to merge 3 commits into
Conversation
…eplies
The voice-disconnected text path posts to the task bridge and polls /result
for the late reply (core pickup is 10-32s). The poll lived in an in-page
setInterval closure that died on page reload, so a refresh during the wait
dropped the reply forever and read as "no response".
Persist {task_id, text} to localStorage on send and resume polling on page
load, so a reload re-attaches and renders the reply (/result/<id> serves from
results/archive too, so the reply survives the bridge archiving the file).
Also show a "working…" placeholder bubble immediately on send so the wait
reads as in-progress, not failure.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
bassilkhilo-ag2
left a comment
There was a problem hiding this comment.
✅ Correct fix for the reload-drops-reply bug. 30-min cutoff on loadPendingTasks() prevents zombie polls. DOMPurify + marked used on render — no raw innerHTML from agent results. addPendingChatSend/removePendingChatSend dedup by task_id correctly. Approve.
bassilkhilo-ag2
left a comment
There was a problem hiding this comment.
Correct approach. The PENDING_KEY localStorage entry with a 30-minute cutoff gives the page exactly what it needs to resume polling after a reload — task IDs that were mid-flight. The dedup guard in appendPendingMessage() prevents double-rendering when the page loads and also the poll restores the same bubble. forgetPendingTask() cleans both the localStorage entry and the interval. The cutoff filter on loadPendingTasks() prevents stale entries from tasks that completed or timed out before the reload. ✓
bassilkhilo-ag2
left a comment
There was a problem hiding this comment.
Good persistence design. localStorage-backed pending tasks with 30-min TTL and polling-to-recover-on-reload. Dedup via data-pending-task prevents double spinners. forgetPendingTask clears the interval to prevent memory leaks. Clean UX improvement. LGTM.
The dashboard send-path poll capped at 5 min and, on give-up, deleted the persisted entry and left a dead "(No response yet…)" line in the transcript. Long agent tasks (PR creation, research) routinely exceed that, so the reply landed in the Tasks tab but never in chat — even a reload couldn't recover it because the localStorage entry was already gone. Raise the ceiling to 30 min, back the poll cadence off after a 2-min fast window, GC persisted sends older than 30 min on load, and on the hard ceiling keep the persisted entry (a reload re-attaches and still renders the late reply, since /result serves archived results indefinitely). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
@cla-assistant check |
Summary
When voice is disconnected, web-chat text routes through the task bridge and polls
GET /result/<id>for the late reply. Two failure modes left the reply in the Tasks tab but never in the chat transcript:(No response yet…)line — even a reload couldn't recover it.Both are fixed here. Separate from PR #1855 (
/answerfor pending questions).What changed
localStorageand resume polling on page load./chatdo not claim each other's in-flight replies./chatpage./resultserves archived results indefinitely).Test plan
npm run typechecknpx tsx --test tests/web-ui-chat-pending-recovery.test.ts— 3 passinggit diff --check/and/chat): mock/task, keep/result/<id>pending, reload, confirm user text + working placeholder recover, flip result to completed, confirm assistant reply renders and pending key clearsNotes
No backend changes.
GET /result/<id>already serves archived results, so this is front-end recovery for the late reply path.