fix(components): route error states through ErrorNotice (batch components-2) - #8867
Conversation
Design Review (Fable 5) — ✅ PASSDesign-level review of The diff matches the description on every sampled claim: Design-Verdict: PASS Eighth batch of an established sweep; the required- [DESIGN-REVIEWED] 217509a |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
First Principles Review (Fable 5) — ✅ PASSPremise-level review of I have everything I need. The mandate ( First-Principles-Verdict: PASS Every surface converted is mandated by the blocking What this change shipsIntent: make every error in 22 shared components visible and recoverable through the shared notice, per a blocking repo rule — a FIX.
Subtractions
[FIRST-PRINCIPLES-REVIEWED] 217509a |
UX Review (Fable 5) — 🟡 CONCERNSUX-level review of Reconciliation is complete. All surfaces this PR touches appear in both screenshots (dark + light, before/after); the blind reader identified every control correctly and dared to use them all — "Ask the agent" was read correctly but self-rated "a guess". The one substantive issue: three newly-visible sites use UX-Verdict: CONCERNS Errors now surface everywhere and read correctly cold, but three newly-visible sites show only the raw server line, hiding what actually failed. Watch
Evidence gaps
[UX-REVIEWED] 217509a |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsNo findings. FINDING — website/src/components/OAuthRelayAffordance.tsx:182 — the invalid-address branch sets [OPUS-REVIEWED] 217509a Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
ea27120 to
35c7873
Compare
35c7873 to
152317f
Compare
152317f to
b53e219
Compare
b53e219 to
7ed1fa2
Compare
…ents-2) Batch components-2 of the dashboard error-state sweep (audit report §5.5): every hand-written error surface in these 22 shared components renders through the shared ErrorNotice, with the askAgent decision stated at each site, and the silent query/mutation/raw-promise failures the report listed now render. Components: FilePathMenu (revealOrOpen gains an onError route; the menu and the Office card render the failure in place instead of alert()), FilePickerMenu, FileRenderers, FolderConfigModal, FollowUpCard, GitPanel, InstanceTabBar, InstancesViewport, IssuePanel, KasLoginGate, KiroPrerequisiteGate, LinkedSurfacesSection, LogEntry, MarkdownPanel (the four alert() mutation handlers and downloadFile report into one panel-level notice; the three background reads now surface), MarkdownRenderer, McpBrowserModal, McpCustomServerModal, MigrationCheck (documented exclusion), MobileConnectModal, OAuthRelayAffordance, OnboardingFlow, PendingQuestionCard. ErrorNotice gains an onHandoff pass-through so a notice inside an overlay can dismiss it once the hand-off proceeds. Fifteen new keys (13 catalogs + en-XA). Tests aligned to the new shape (testId / role=alert); capture harness with before/after screenshots.
7ed1fa2 to
217509a
Compare
|
UX Review disposition (CONCERNS on
|
|
UX Review disposition (CONCERNS on
|
|
UX Review disposition (CONCERNS on
|
|
UX Review disposition (CONCERNS on
|
Summary
Batch components-2 of the dashboard error-state sweep (audit report
research/error-states-audit.md, §5.5 "Batch components-2", 22 files underwebsite/src/components/): every hand-written error surface renders through the sharedErrorNotice, per the blocking AUTOSDE ruleerrors-use-error-notice, and every silent failure the report listed for these components (auseQueryerror nobody read, a mutation whoseonErroronly logged or raised a blockingalert(), a raw.catchthat kept the card up with no message) now renders. Reference implementation: the merged sibling batches (#8729, #8743, #8749, #8760, #8792, #8810, #8843).no linked issue: driven by the audit report (
research/error-states-audit.md, section 5.5).Each site passes the SAME string it already rendered, so
ErrorNoticerecovers the structured context by message match. Every notice carries atestIdso tests target it unambiguously. TheaskAgentdecision is stated at every site: on where the hand-off loses nothing, off with aNo hand-off: <draft>comment next to an unsaved draft.Sites
askAgent on (reads, lists, actions on persisted items; no draft on screen)
FilePathMenu.tsx—revealOrOpennow takes a requiredonErrorand has noalert()path at all (review round 1, GPT F1);useCopyAckholdsrevealErrorand the context menu renders it as a dismissable inline notice. A smalluseRevealFailure()hook gives every other caller the same state, and all six callers outside this batch's file list were migrated in place rather than left on a fallback:MarkdownRenderer(link + chip: notice beside the element),ChatPageDirChip,FilesHomePanel,FolderPanel,FileViewer,DeckViewer— each renders<ErrorNotice variant="inline" askAgent onDismiss>where the click happened (none holds a draft; transcript chips per the report critic).OverflowMenu'sonErroris required too (the standalone-mountalertfallback is gone).FileRenderers.tsx—JsonViewer's parse failure →ErrorNotice title='Invalid JSON'above the raw preview; the Office card renders the reveal failure fromuseCopyAckin place.FollowUpCard.tsx— worktree-create failure (per the report critic: the card holds no draft, the host composer draft is persisted per slot, and the hand-off opens a fresh slot).GitPanel.tsx— silent fixed: a failedgit-status/git-logread used to render an empty panel indistinguishable from a clean repo; both now render at the top of the body.InstanceTabBar.tsx— silent fixed: only a 403 was interpreted; any otherlistInstancesfailure hid the bar. A failed read now keeps the bar up with an inline notice. The tunnel-error tooltip stays an indicator (comment added): the full error is theErrorNoticeinInstancesViewport's panel, which this strip sits on top of, so repeating it would show the same failure twice.InstancesViewport.tsx— the error panel's message block →ErrorNotice report={panelReport} askAgent onHandoff={→ Local}(the standaloneAskAgentButtonis folded into it; the report keeps the prompt bound to THIS crew and the ladder); the pane-load-timeout line is a notice too. Silent fixed: the Retry's ownconnectMutationrejection reached onlypaneLog; it now feedspanelError.IssuePanel.tsx— the full-height load card (title + message, Retry beneath; the login-required branch keeps the command as remedy text), thejira_no_credentialssetup card (the value is a query error code, so it renders as one; Open-in-Jira link kept), and the stale-revalidationrole="status"bar → inline notice.LinkedSurfacesSection.tsx— the four mutations' bell-feed notification was the ONLY report of a write that did not persist; each failure now also renders in place under its row (failRow, cleared on the next click / dismiss). Silent fixed: a failedchannel-targetsread now says so instead of showing no offers.LogEntry.tsx— silent fixed: a failedcronRunDetailrendered an empty<pre>.McpBrowserModal.tsx— row install failure (literaltext-red-400), detail-pane install failure, and the 409conflict(a rejected request with no overwrite path here, unlikeSkillBrowserModal's — literaltext-amber-400,role="status") all →ErrorNotice. Silent fixed: a failed search fell through to "No servers found"; a failed detail read left Install disabled forever with no message.MobileConnectModal.tsx— probe failure (Try again kept), QR mint failure, link mint failure, and both clipboard failures. All five passonHandoff={onClose}(round 2, GPT): the modal is a fixed full-screen overlay, so the hand-off closes it or the fresh chat stays hidden underneath.MarkdownPanel.tsx(conditional) — see below.No hand-off (an unsaved draft shares the screen)
FolderConfigModal.tsx— save failure (hand-builtrole="alert"box) and the tags-vocabulary failure (Retry kept): the folder name / color / project dir / agent / tags form is unsaved.FilePickerMenu.tsx— silent fixed: a settled search error showed the ordinary "No matching files" copy; it now renders its own notice above the (placeholder) results. The Enter-release gate is unchanged (test updated). The composer draft the picker is completing an @-mention inside is unsaved.KasLoginGate.tsx— chooser start failure (Company SSO start-URL field); the sign-in-problem detail; the status-unavailable detail. Silent fixed: asettleCancelthat returnedunknownleft the waiting screen up with buttons re-enabled and nothing said; both waiting screens now rendercancel_unsettled. All areNo hand-off: the gate stands between the user and the chat the hand-off navigates to, and without a signed-in account there is no agent to hand to (see rule-gap below).cancel_unsettledreworded per the UX lane (rounds 2–3) to drop the word “gateway” and end with the remedy: “It is still active — press the same option again to retry.”KiroPrerequisiteGate.tsx— the status-check failure (message split from the retry hint), the CLI update failure and both repair failures (red<pre>under an uppercase label →ErrorNotice titlewith the verbatim body; the block variant iswhitespace-pre-wrap).No hand-off: kiro-cli — the agent runtime — is the thing that failed. Per the UX lane, each of the three now carries a plain-language next step under the verbatim output (attempt_failed_remedy, interpolating the screen's own button name — “Update Kiro CLI” / “Check again” — per UX round 3: fix the named cause, press that button, reinstall if it keeps failing).MarkdownPanel.tsx— the save failure (always off: the buffer holds the edits that were not persisted); the fouralert()mutation handlers (add-to-knowledge, promote, snapshot, save-as-artifact),downloadFileand the overflow's open/reveal now report into one panel-levelactionErrornotice withaskAgent={!dirty}. Silent fixed: theknowledge-configread resolved tonullon!r.ok(rendered as "not added"); it and theartifact-by-source-path/file-diffquery rejections now render, alsoaskAgent={!dirty}.OverflowMenutakesonError(optional only for a standalone mount, where it falls back to the legacy dialog so nothing is dropped).MarkdownRenderer.tsx— Mermaid render failure: the source is no longer painted red; an inline notice above it names the failure.No hand-off: the renderer is embedded in hosts that hold unsaved drafts (file-panel editor buffer, composer preview) and cannot tell which.McpCustomServerModal.tsx— spec load failure and submit failure (literaltext-amber-400role="alert"spans): the spec JSON textarea is unsaved. The client-side parse hint stays plain validation text (excluded by the report).OAuthRelayAffordance.tsx— relay failure: the pasted return-address input is unsaved. The client-side invalid-address check is split into its ownhintstate and rendered as plain muted text (GPT F2), so validation never dresses as a request error.OnboardingFlow.tsx— profile save failure (style={{ color: 'var(--danger)' }}<p role="alert">): the wizard answers are unsaved.PendingQuestionCard.tsx— silent fixed: the retryable (non-404) answer and dismiss failures kept the card with no message; a notice under the card now says why it is still there. The selected answers / custom text are unsaved. The failure is keyed by the request'slockKeyand rendered only while that identity is still the card on screen (GPT round 4): a rejection for card A landing after card B replaced it in the slot must not paint A's failure under B. The same scoping applies touseRevealFailure(subject)(reset when the path it was raised for changes) andMarkdownPanel'sactionError(reset onfilePath).Documented exclusion
MigrationCheck.tsx— a best-effort probe mounted on every app route; a failed['apps']read is reported where that data is the page's subject (AppsPage / AppDetailPage), and repeating it above every app page would show the same failure twice. Comment added.Shared component change
ErrorNoticegains two optional props.onHandoffpasses through toAskAgentButton(ignored whenaskAgentis off) — needed byInstancesViewportandMobileConnectModal, whose overlays sit over the chat the hand-off navigates to.messageClassNamestyles themessagespan only (UX round 3):IssuePanel,KiroPrerequisiteGateandJsonViewerpassfont-monothere so verbatim tool/server output stays monospaced while the plain-languagetitlekeeps the UI font and reads as a separate clause. The block variant wraps the message in a span only when the prop is given, so every existing consumer's DOM is unchanged.Deviations from the report's recommended_action (author decisions)
InstanceTabBartunnel error: kept as an indicator (report: "low priority; consider leaving as indicator and documenting") because the same text renders as theErrorNoticedirectly beneath the strip inInstancesViewport's panel; the list-load silent failure IS fixed.FollowUpCard, transcript surfaces: report suggested aNo hand-off: transcriptcomment; per its own critic that names no real draft, soaskAgentis on.KasLoginGate/KiroPrerequisiteGate: report suggestedaskAgenton for the gate screens; kept off — the gate hides the chat the hand-off would open, and the failure is the account / runtime the agent needs (rule-gap below).IssuePaneljira_no_credentials: migrated (report offered either); the Jira link stays as the remedy.MarkdownRendererMermaid: report allowed a transcriptNo hand-offcomment; the comment here names the real drafts the shared renderer can sit beside.revealOrOpen: the six callers outside this batch's file list WERE migrated (round 1) — a shared helper with a blocking-dialog fallback is itself the violation, and a requiredonErroris the only shape that cannot regress. Theerrorreturn field from the first push is dropped (First Principles: zero consumers;onErroris the delivery path).Rule-gap variants seen in this batch (NOT editing
website/AUTOSDE.yaml— #8729 owns that)askAgentnor a draft-namingNo hand-offcomment describes it (KasLoginGate, KiroPrerequisiteGate)alert()raised from inside a shared hook / helper, so the same failure has no host to render in (revealOrOpen, MarkdownPanel's mutation hooks)<pre>(LogEntry), an empty panel (GitPanel), a hidden bar (InstanceTabBar), "not added" (MarkdownPanel knowledge query returningnullon!r.ok), a disabled button forever (McpBrowserModal detail)role="status"in a literal palette colour (McpBrowserModalconflict)settleCancel)text-muted/--dangerinline style rather than a danger class (IssuePanel, OnboardingFlow), and literal Tailwind palette colours (text-red-400,text-amber-400)Pattern harvest
Rule candidate:
errors-use-error-noticeshould name two shapes this batch found the current text misses — (1) a shared helper or hook that raises the failure itself (alert()insiderevealOrOpen,onError: alertinsideMarkdownPanel's mutation hooks), where no single call site owns a surface and the fix is a requiredonError/render callback plus a small shared failure-state hook; and (2) a gate component whose hand-off target (the chat) is hidden behind the gate itself and whose failure IS the account or runtime the agent depends on, where neitheraskAgentnor a draft-namingNo hand-offcomment describes the decision. The rule-text edit itself is owned by #8729.i18n
Sixteen new keys in
en.manual.json+ all 11 catalogs (hand-translated) +en-XAregenerated:components.filePickerMenu.search_failed,components.gitPanel.{status_failed,log_failed},components.instanceTabBar.instances_load_failed,components.kasLogin.cancel_unsettled,components.logEntry.trace_load_failed,components.markdownPanel.{knowledge_status_failed,artifact_status_failed,diff_status_failed},components.markdownRenderer.mermaid_render_failed,components.mcpBrowserModal.{detail_load_failed,search_failed},components.linkedSurfacesSection.targets_load_failed,components.pendingQuestionCard.{answer_failed,dismiss_failed},components.kiroPrerequisiteGate.attempt_failed_remedy. Catalog diffs are key-level inserts validated with a duplicate-key-rejecting parser;gitPanel,logEntryandpendingQuestionCardare new blocks inen.manual.json(they only existed in the generateden.json).Tests
Aligned to the new shape (no test deleted):
FilePickerMenu.cov80.test.tsx(error branch asserts the notice, not "No matching files"),KiroPrerequisiteGate.test.tsx(message and retry hint are now two elements),MarkdownPanelCoverage.test.tsx/MarkdownPanelMoreCoverage.test.tsx(panel failures assertmarkdown-panel-action-errorand thatalertwas NOT called),MarkdownRenderer.mermaidEnlarge.test.tsx,PendingQuestionCard.test.tsx,LinkedSurfacesSection.cov80.test.tsx(in-place notice beside the notification record),apiRevealPath.test.ts(newonErrorroute). Round 1: standaloneOverflowMenumounts pass anonErrorspy and assert on it (alertnever called);FileExplorerPageCoverage,PptxDeckViewerCov80,FileRenderers.testassert the in-place notice bytestId;McpOAuthBanner.relay.testasserts the validation hint is NOT an alert;apiRevealPath.testis rewritten to the required-onErrorcontract.Verification
tsc -bclean,eslinton every touched file clean,I18N_BASE_REF=origin/main npm run i18n:check19/19 PASS, no non-inclusive terms in the diff.Screenshot Evidence
Capture harness:
website/capture/error-notice-components-2.{html,tsx}+website/scripts/capture-error-notice-components-2.mjs(Vite + Playwright, self-checking across BOTH rows: 24role="alert"notices and 15 hand-offs in the AFTER columns, Retry beside the IssuePanel notice, 0 hand-offs in the BEFORE columns). The KiroPrerequisiteGate cell now also shows theattempt_failed_remedyline and the Check-again button beneath the notice (UX round 2). Row 2 (added per the UX lane) shows the 13 surfaces that only appear once a menu, modal or gate is open: FilePickerMenu, JsonViewer, InstanceTabBar, InstancesViewport, KasLoginGate, LinkedSurfacesSection, LogEntry, MarkdownPanel, MarkdownRenderer, McpBrowserModal search, McpCustomServerModal, OAuthRelayAffordance, OnboardingFlow.Before (origin/main) vs after (this branch), dark:
Light: