fix(chat): gate batch comment submit on gateway connection - #8348
fix(chat): gate batch comment submit on gateway connection#8348lucasmokwa wants to merge 1 commit into
Conversation
The file panel's Submit All and the artifact panel's Submit fired while the dashboard was disconnected, even though the chat send path silently refuses messages in that state. Worst case (file panel) the pending comments were composed, cleared, and dropped — silent loss. Mirror ChatInput's Send gating: plumb connected from ChatPage into both panels, disable the submit buttons with the shared offlineProps affordance, and guard the submit handlers so pending comments are never cleared on a refused send. ChatPage.submitComments also bails before dispatching switchSlot so an offline submit can't switch the active session with nothing sent.
Design Review (Fable 5, fork) — ✅ PASSDesign-level review of All consumers verified: the only Design-Verdict: PASS Real silent-data-loss bug, fixed at both the UI affordance and the handler, using the codebase's established Watch
Suggestions
[DESIGN-REVIEWED] 787ae4d |
UX Review (Fable 5, fork) — ✅ PASSUX-level review of This is a tight, pattern-conformant fix. The new offline gating exactly mirrors the composer's established UX-Verdict: PASS Closes a silent comment-loss path with the composer's exact offline affordance — disabled state, translated tooltip, and preserved drafts all match established patterns. [UX-REVIEWED] 787ae4d |
First Principles Review (Fable 5, fork) — ✅ PASSPremise-level review of All checks are done. Every entry point passing First-Principles-Verdict: PASS A reported silent-data-loss defect, fixed with the repo's own documented offline-gating mechanism, with no riders and complete entry-point coverage. What this change shipsIntent: stop a user's pending review comments from being silently destroyed when they submit while the gateway is down. This is a FIX.
The change reuses Watch
[FIRST-PRINCIPLES-REVIEWED] 787ae4d |
Opus 4.8 Review (fork) — ✅ no blocking findingsReviewed Review detailsThe discovery pass found no candidates, and my review of the diff confirms it: this is a defensive offline-gating change that threads a No findings. [OPUS-REVIEWED] 787ae4d |
GPT 5.6 Review (fork) — ✅ no blocking findingsReviewed Review detailsNo findings. |
Problem / Motivation
The chat composer is fully gated on the gateway connection — Send/Optimize disable with
!connected,offlineProps()sets the offline tooltip/aria state, andChatPage.send()bails on!connected. The batch comment submit affordances have none of this wiring:CommentList, CommentOverlay.tsx): nodisabledprop at all. Clicking while offline composes the batch, unconditionally clears the pending comments (setComments([])in MarkdownPanel'ssubmitAllComments), andsend()silently drops the message — the user's comments are destroyed with no error.SubmitBar, ArtifactPanel.tsx):disabledbound only to the 400mssubmittingspinner flag. Clicking while offline flashes the fake spinner and drops the batch.ChatPage.submitCommentsalso dispatchesswitchSlot(target)beforesend()bails, so an offline submit can switch the active session with nothing sent.Why it matters
Silent data loss of user-authored content: a user who wrote several inline review comments and clicks Submit All during a gateway drop loses all of them with no error and no recovery path. This is exactly the offline draft-loss class the composer's
send()guard already protects against (its comment names it) — these three entry points bypass that protection's UI half.What changed (motivation → approach → change)
Symptom: batch submits fire while disconnected. Root cause: the
connectedflag never reaches the panels' submit affordances, and the submit handlers compose-and-clear before the refused send. Change: mirror ChatInput's Send gating end to end —connected(optional prop, defaulttrue) from ChatPage (useConnected()) → SidePanel → TabBody/FileTabBody → MarkdownPanel/ArtifactPanel → CommentList/SubmitBar.disabled={!connected}+ the sharedofflineProps(connected, 'submit comments', …)affordance (same tooltip/aria pattern as the composer).submitAllComments/submitToChat/submitCommentsonconnectedas the behavioral backstop, so pending comments are never composed + cleared on a refused send, and an offline submit can no longer switch the active session.Prop-with-default (rather than calling
useConnected()in the panels) follows the existing convention — presentational panels are rendered in tests without a Redux provider, and non-chat embeddings (noonSubmitComments) hide the affordance anyway.Tests
website/src/test/CommentListOffline.test.tsx(new, 4 tests): offline disables Submit All with the aria/title affordance; offline click does not fireonSubmitAll; connected default and connected=true carry no offline affordance and submit normally.website/src/test/ArtifactPanelCoverage.test.tsx("offline gating (submit-to-chat)" describe, 3 tests): offline disables Submit with the affordance; offline click does not fireonSubmitComments; omittedconnecteddefaults to enabled and submits.Manual verification
Rendered the real
CommentListin both states via a throwaway Vite harness (not committed) and verified in the live DOM: connected buttondisabled=false, aria-disabled=false; disconnected buttondisabled=true, aria-disabled=true, title="Gateway offline — reconnect to submit comments", aria-label="Submit All disabled — gateway offline". Screenshot below is from that run.Screenshots / video
File panel comment list, connected (unchanged) vs disconnected (Submit All disabled + offline tooltip/aria):
The artifact panel's Submit button gets the identical
SendBtn+offlinePropstreatment (asserted by the ArtifactPanelCoverage tests above).Related Issues
no linked issue: found and fixed while porting an internal fix; no tracking issue exists for it.
Pattern harvest
Rule candidate: review-prompt
Pattern: an action path that clears user-authored state (pending comments, drafts) before an unconfirmed send must carry the same
connectedgating as the composer — buttondisabled+offlineProps+ handler bail, all three.Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)