Skip to content

fix(pages): route error states through ErrorNotice (batch pages-rest-1) - #8816

Merged
iamwhatever merged 2 commits into
mainfrom
fix/error-notice-pages-rest-1
Sep 6, 2026
Merged

fix(pages): route error states through ErrorNotice (batch pages-rest-1)#8816
iamwhatever merged 2 commits into
mainfrom
fix/error-notice-pages-rest-1

Conversation

@CrysisDeu

@CrysisDeu CrysisDeu commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Problem / Motivation

The dashboard error-state audit (research/error-states-audit.md) found 320 error surfaces bypassing the shared ErrorNotice and 197 mutations/queries that fail silently — every one a violation of the blocking AUTOSDE rule errors-use-error-notice. The report recommends fixing by surface in batches of ≤25 files. This PR is §5.4, batch pages-rest-1: the 13 top-level pages AgentsPage, AppDetailPage, AppPage, ArtifactDeployPage, ArtifactDetailPage, ArtifactPopoutFrame, ArtifactsPage, ChannelPage, ChatPage, ChatSidebar, ContextBreakdownPanel, DevFleetPage, HooksPage (48 violation rows + 45 silent-failure rows).

No linked issue: driven by the audit report.

Why it matters

This is an AI agent app: an error the user cannot fix is usually one the agent can, so a hand-written red div is a dead end — it throws away the structured context (route, endpoint, HTTP status, backend code) that ErrorNotice recovers from the error journal and offers to the agent. Several of these sites were worse than a dead end: a rejected getApp rendered as "App Not Found", a rejected file read opened a file tab whose content was the error string (so it looked saveable), alert() dialogs carried nothing to the agent, and ~30 Dev Fleet failures lived only in a 7-second toast.

What changed (motivation → approach → change)

Every site follows the report's per-row recommended_action. askAgent is on wherever the surface holds no unsaved draft (load/list failures, status panels, actions whose inputs are already persisted). It is off with a {/* No hand-off: <draft> */} comment next to an unsaved value: the ArtifactDeployPage new-profile form (npName/npAccount/npRole, conditional on showNewProfile), the ArtifactDetailPage editor buffer (editedContent) and comment draft, the ChannelPage unsent message text, the ChatSidebar bulk-model selection, the open HookForm (!creating && !editing) and the ArtifactsPage folder-name inputs.

ChatPage follows the reasoning sibling PR #8730 established and I verified in code: the composer draft is persisted per slot on every keystroke and slot switch, and an in-chat hand-off (subscribeChatHandoff) opens a fresh slot without navigating away, so the hand-off is on for the pane-level notices, with that reasoning as a comment on the notice stack.

Hand-written surfaces migrated → ErrorNotice

File Before After
AgentsPage role="alert" div for a refused delete; text-warn div on the Skills tab only notices (askAgent); the config-load failure now also shows on the overview so it is seen without opening Skills
AppDetailPage load error dropped into the "App not found" header subtitle; bg-danger/10 div with a directly mounted AskAgentButton; bespoke Fix with AI button on the install-failed card a real 404 renders "not found", any other rejection renders Failed to load app + notice + Retry; page banner is ErrorNotice askAgent onDismiss; install-failed headline is an inline notice whose hand-off resolves a journal entry (recordError) carrying the redacted log tail the old button pasted by hand
ArtifactDeployPage accent Card mixing success text with backend {error} bodies; color: var(--danger) span; literal #dc2626 div success notice kept for success only; failures → ErrorNotice (No hand-off while the new-profile form is open); verify-unreachable and pending-confirmation failures → notices
ArtifactDetailPage hand-written load-failure Card; border-danger/40 div for publication.last_error; two text-danger spans in UpstreamSyncBanner notices; a failed snapshot fetch (versionQuery.error) is now a load failure with Back to Live, not "Not found."
ArtifactsPage bg-danger/10 banner with an "Error" heading ErrorNotice onDismiss; the pure client-side pick hints (unsupported type / too large / empty) split into a role="status" hint, not the error surface
ChannelPage custom error Modal (AlertTriangle text-warn, OK button); two native alert()s one in-page ErrorNotice askAgent onDismiss (testId="channel-error"); inputs were already submitted, so a blocking modal bought nothing
ChatPage alert() for fork / plan-from-here / apply-plan; hand-written upload, sid and pin banners; unresumableResume notice with no askAgent decision; toast-only createSlot and pinModelToAgent failures; 'Unable to read file' / 'Error reading file' written as the file tab's content one pane-level actionError notice (testId="action-error") plus upload-error / sid-error / pin-error notices above the composer; the 20-file / 50 MB client hints stay role="status"; pinStatus keeps only answers (pin limit, not found); read failures report through the notice and leave the tab cold (retry on next click) instead of masquerading as file text; askAgent + reasoning on unresumableResume
ChatSidebar text-muted preview failure; text-danger spans for cleanup / bulk-model errors; role="status" seed box inline notices (retry buttons kept as separate affordances). The icon-only source-link retry chip (+N ↻, failure named in its title/aria-label) is left as is, the report's second option for that row: a text notice cannot sit on the session row's chip line without a third control (max-two-buttons-per-row) or a second line (session-row-fixed-height), and errors-use-error-notice does not accept a row budget as a reason to omit the hand-off — three blocking rules with no compliant intersection, so the grandfathered chip stays untouched by this diff.
DevFleetPage var(--danger) spans (rebase, sync, provision, worktree detail, prune rows); role="alert" divs (fleet discovery, gateway restart); EmptyState subtitle={error} for backend-unavailable; page-local notify(msg, {type:'error'}) ToastHost as the only report for ~30 catch sites notices throughout; notify(type:'error') now lands in an in-page actionError ErrorNotice (testId="devfleet-action-error") instead of the transient toast — every existing site gains a persistent, selectable surface without touching them; success/info keep the toast
HooksPage bg-danger/10 banner with "Error" heading; last_error only in an InfoTip tooltip; text-danger div + role="alert" box for test failures; provider-hooks failure dressed as EmptyState notices (hooks-error, hook-test-error); last_error opens as an expandable row under the hook; provider failure = notice + Retry

Silent failures surfaced

  • AgentsPage (11): SubagentInfo.error rendered per failed run; spawn-list / sessions-context / sessions-usage (.catch(()=>null) removed) / agents-installed (+Retry) / mcp-tools / kirocrew-agents+default-agent (explains why delete is unavailable, +Retry) / agent-detail queries render isError; patchModelMut, spawnClearMut, spawnDeleteMut, setDefaultMut gain onError.
  • AppDetailPage / AppPage: .catch(() => setX(null|[])) swallows replaced by an error state; getApp only treats status === 404 as "not installed" (isNotFoundError, duck-typed so mocked clients keep working); registry/system failures are held and shown while an installed app still renders.
  • ArtifactDeployPage: jget/jsend (and the two raw fetches) now throw on !res.ok carrying the backend error/detail, so 4xx/5xx bodies become query errors; config/profiles/sites/webapp/pending queries render isError (+Retry); sitesResp.profile_errors rendered; every profile/recall/destroy/verify/loadPolicy mutation gains onError.
  • ArtifactDetailPage: versions / events / comments / publish-providers / upstream-status isError rendered; the seven comment mutations' onMutErr now records the message (commentActionError) instead of only invalidating.
  • ArtifactsPage: allTags / providers / sessionDocs isError (+Retry); pinMut, createFolderMut, updateFolderMut errors join the banner chain; RemoteArtifactsSection renders a notice + Retry instead of return null.
  • ChannelPage: channelsList, channelPresets, channelGet, channelDismissAgent, channelUpdateAgent, channelClose swallows surfaced; channelPost failure now keeps the text in the composer and reports it (No hand-off: the unsent message).
  • ChatPage: steerMutation (console-only), renameSlot .catch(()=>{}), generateTitle, pin/unpin .catches → actionError / pinError.
  • ChatSidebar: chat-folders / tag-columns isError (+Retry); column, folder and the five create-chat mutations gain onErrorboard-error / folder-action-error / new-chat-error notices.
  • ContextBreakdownPanel: context-trace isError rendered. DevFleetPage: disk query isError rendered inline.

Deliberately not done

  • website/AUTOSDE.yaml untouched (settings-1 owns the rule text). Rule-gap variants found here, for that PR: (askAgent-off comment form) an omission whose reason is a ROW BUDGET (max-two-buttons-per-row / session-row-fixed-height) rather than a draft — ChatSidebar source-link chip line — has no sanctioned comment shape; (4.5) native alert() — ChannelPage ×2, ChatPage ×3; (4.2) toast/notification as the only report — ChatPage restage/createSlot/pinModel, DevFleetPage ToastHost; (4.6/4.7) .catch(() => setX(null|[])) and empty catch {} presenting failure as empty/not-found — AgentsPage, AppDetailPage, AppPage, ChannelPage ×7; (4.8) res.ok never checked — ArtifactDeployPage jget/jsend + two raw fetches; (4.4) tooltip-only last_error — HooksPage InfoTip; error string used as content (file-tab text, PageHeader subtitle) — ChatPage, AppDetailPage; bespoke hand-off button / direct AskAgentButton mount outside a boundary — AppDetailPage; failure visible only as a status Badge — AgentsPage subagents; if (error) return null hiding a section — ArtifactsPage; role="status" holding a failure — ChatSidebar seed, ChatPage pins; success and failure sharing one non-alert card — ArtifactDeployPage.
  • ChatPage overlap with fix(chat): route error states through ErrorNotice (batch chat-1) #8730 (chat-1): that PR adds a ~13-line wiring touch to ChatPage.tsx (the /side slash failure routed into the existing refusedPress notice, plus askAgent on that notice). This PR does not touch those four spots; a small overlap/trivial rebase is expected whichever lands second.
  • Rendering the file-read failure inside the file tab would need readError on PanelTab (hooks/usePanelTabs.ts) and a notice in FileTabBody (pages/chat/SidePanel.tsx) — outside this batch; the ChatPage side (query keeps ok:false, no error-as-content) is done and the failure reports through the pane notice.
  • Review rounds 1–2 (squashed into the single commit, then rebased onto main over chat-2 fix(chat): route error states through ErrorNotice (batch chat-2) #8743, apps-2 fix(apps): route error states through ErrorNotice (batch apps-2) #8792, settings-1/2 fix(settings): route error states through ErrorNotice (batch settings-1) #8729/fix(settings): route error states through ErrorNotice (batch settings-2) #8749 and the cloud-drive publish feat(publish): personal cloud drive as an opt-in publish destination #7199; the catalog conflicts were resolved by key-level replay of this PR's 36 added / 16 removed keys onto main's catalogs): Focus Cue Gate on the ChatPage title input (focus-visible: accent underline added); GPT F1 — the ChatSidebar source-link failure notice stays on the chip line (zero-basis flex-1, truncated, full text in title) so the session row gains no stacked line; GPT F2 — the DevFleet rebase failure notice is its own row-spanning block, not a third control in the action row; UX — DevFleet's page-level action error scrolls into view when set, AppPage's failure branch has Retry + Back to Apps; First Principles — the five file-local error→text helpers are replaced by utils/thunkError.errMessage (+ per-site fallback), and TrustAppModal.isNotFound is replaced by the shared isNotFoundError.
  • Review round 3 (second commit): GPT max-two-buttons-per-row at five error rows — HooksPage banner (Retry on a failed read, dismiss on a failed action, never both), ChatSidebar source-link chip line (no hand-off on this notice: the line already holds the link chips and the +N retry and may not grow a line — a row-budget reason, not a draft; flagged below as a rule-gap for the askAgent-off comment form), ChatSidebar cleanup dialog (notice above the Cancel/Archive pair), DevFleet sync and provision failures (notice on its own basis-full line above Log / dismiss). UX: ChatPage clears the pane-level action error on slot switch. Declared rider: the focus-visible:border-b focus-visible:border-accent classes on ChatPage's title-rename Input are required by the (blocking) Focus Cue Gate, which fires because this diff touches that line's onBlur (the renameSlot failure path).
  • Review round 4 (folded into the second commit): First Principles BLOCK — ArtifactDeployPage's local failFrom replaced by the shared toApiError factory (api/apiError.ts), so a non-2xx reply is an ApiError carrying the status and the backend's error/detail text; the interim request_failed_status key is retired from all catalogs. GPT F2 taken — DevFleet keeps the latest action failure in module state (_lastActionError), seeds the page notice from it on mount and clears both on dismiss, so a background Pull+Build/provision failure that lands while the page is unmounted is still on the page when the user returns (test seam __resetDevFleetNoticesForTests). GPT F1 (hand-off on the ChatSidebar chip-line notice) NOT taken — see the rule-gap note; the adjudicator downgraded both findings as disproportionate. Frontend Tests round: translator-context orphan for the retired channelPage.ok, zh-CN "turn" = 轮次, HooksPage integration test on the hooks-error notice, the pin-jump source contract on setPinLoadError.
  • Review round 5 (single commit, rebased onto main over chat-core feat(chat-core): route ChatPage's send and steer through the transport #8689, apps-1/apps-3 fix(apps): route error states through ErrorNotice (batch apps-1) #8760/fix(apps): route error states through ErrorNotice (batch apps-3) #8810 and test(settings): point the RemoteCrewPanel diagnosis helper at the ErrorNotice #8828): GPT's upheld F1 resolved by REMOVING the chip-line notice (see the ChatSidebar row above) rather than choosing which blocking rule to break. chat-core feat(chat-core): route ChatPage's send and steer through the transport #8689 rewrote steerMutation around a receipt that never rejects and renders its own error row, so this PR's onError there is dropped and the could_not_steer key retired. Catalog conflicts resolved by key-level replay again (32 added / 16 removed keys onto main's catalogs).
  • Inherited red (not this PR): RemoteCrewPanel.test.tsx › "agent hand-off from the diagnosis note" (4 cases) fails on every merge ref that includes both fix(settings): route error states through ErrorNotice (batch settings-1) #8729 and fix(settings): route error states through ErrorNotice (batch settings-2) #8749 — the test helper looks for a role="status" box that fix(settings): route error states through ErrorNotice (batch settings-2) #8749 replaced with ErrorNotice. Unblocker: test(settings): point the RemoteCrewPanel diagnosis helper at the ErrorNotice #8828 (one test helper line). Frontend Tests here will stay red until it lands or this branch is rebased over it.
  • Review round 6 (second commit, UX CONCERNS): ChannelPage no longer removes a channel whose close was refused and puts an agent row back when its dismiss / listen change is refused, so the notice and the row agree; a failed presets read no longer papers over a failed channel-list read. DevFleet's DISK card shows "—" (not "…") on a failed read. HooksPage's expanded last_error row spans the scrolling columns with a sticky spacer under Actions. Evidence: ChannelPage + AgentsPage scenes and the HooksPage failed-hook row / failed Test are now in the frames. ChatPage and ChatSidebar are not in the harness: both need a live slot with transcript state to render their composer-adjacent notices, which the fixture-fetch harness cannot stage; their notices are pinned by the updated suites instead.
  • Review round 7 (UX BLOCK, folded into the second commit): the HooksPage last_error row is a sticky-left block exactly as wide as the table's scroller, so it reads in full at any horizontal scroll instead of painting under the sticky Actions column; the harness scene is wide enough for the Status column and its expander to be in frame. Failure-as-empty tension removed: ChannelPage withholds its onboarding copy while the list read failed; AgentsPage names a page-wide transport failure once (the roster notice + Retry) and keeps the identically-failed cards quiet while still withholding their empty states; DevFleet treats a rejected fleet read as the same unknown as a discovery error ( everywhere, no "Worktrees (0)" / "0 rows"). "Ask the agent" wording is a shared-component change — flagged for the components batch.
  • Review round 8 (folded into the second commit): GPT F1 — the ChannelPage rollbacks restore only the state THAT request set (a.listenMode === mode / a.state === 'done' guards), so two overlapping changes where the first fails and the second succeeds keep the server-confirmed value; GPT F2 (page-layout-pattern) — every control this batch added to a page file is a Btn, no raw <button> remains in the diff. Two suites updated to the round-7 contracts (failed list read → notice, not empty state; measured scroller ref).
  • Review round 9 (folded into the second commit): GPT — AppDetailPage.load() now drops the previous record (setApp(null)) before resolving the new name, so an in-session navigation from a loaded app to a genuinely missing one renders the not-found page instead of the stale app under the new URL. (The loading branch already replaced the page for the duration of a load, so nothing else changes.)
  • Review round 10 (UX CONCERNS, folded into the second commit): a failed hook Test is reported once — by the titled hook-test-error notice beside the row; testMut leaves the page-top mutError chain, since a second bare copy read as a page-wide outage. The AppPage and AgentsPage load-failure notices carry a what-failed title (Failed to load app / Failed to load agents, 2 new keys) instead of the bare transport message. The HooksPage capture scene now shows the provider-hooks notice + Retry. "Ask the agent" pre-click disclosure is a shared-component change — components batch.
  • Review round 11 (GPT, folded into the second commit): a refused ChannelPage dismiss / listen change now reconciles the row from channelGet (the pattern clear-context already uses) instead of a hand-rolled rollback — a rollback cannot tell the optimistic value from one a channel_agent_left socket event or a newer request wrote, so it could resurrect a removed agent. If the re-read fails too the row is left as is; the notice already names the failure.
  • Review round 12 (First Principles, folded into the second commit): the ChatPage pin FAILURE notice no longer shares the pre-existing 8-second timer with the pin STATUS line ("not in this history", "pin limit reached") — only the answer times out; a failure stays until dismissed or handed off. Also declaring: the rename Input gained focus-visible:border-b focus-visible:border-accent because the Focus Cue gate flags a focusable control whose focus ring is suppressed (focus:!shadow-none) with no replacement cue — it is the gate's fix, not a design change.
  • Inherited red refactor: rename project KiroClaw to KiroCrew #2 (not this PR — touches no backend file): test_members_dm_thread.py::TestDenialAuditOffload::test_no_members_sel_audit_is_offloaded fails on every merge ref containing both fix(sel): warm singleton at startup; drop per-site audit hops (#8608) #8741 (inverted the members AST guard) and feat: four-layer member system prompt for crew DM threads #7235 (added two to_thread-wrapped audits). Unblocker: fix(members): enqueue the rules read/write audits inline, not via to_thread #8844.
  • ManageAgentsFooter (components/AgentDropdownList.tsx) still renders the default-agent failure as a hand-written role="alert" div and only accepts error?: boolean — a components-batch item. App.tsx's agentSwitchNotice surface likewise.
  • Copy-to-clipboard glyphs (ArtifactDetailPage) kept as icon state, per the report and the fix(chat): route error states through ErrorNotice (batch chat-1) #8730 precedent (browser API, no journal context).
  • Polling-loop rejections (DevFleetPage), config-read queries and best-effort side effects (ChatPage slack link / breadcrumb / voice) left as acceptable degrade, per the report.

i18n: 34 new keys in en.manual.json (net after review rounds: 32 of the originals retired/kept plus appPage.retry, appPage.back_to_apps, appPage.failed_to_load_app, agentsPage.failed_to_load_agents) + 11 translations (zh-CN, hi, es, fr, bn, pt, ru, de, ja, ko, it), en-XA regenerated. Wherever possible the notice receives the same string the site already had so the journal message-match keeps working. 16 retired keys (fix_with_ai, dismiss_error, app_not_found_2, error/add_failed/update_failed/remove_failed, unable_to_read_file, error_reading_file, dismiss_provision_status, …) removed from all 13 catalogs so the deadKeys ratchet stays at baseline.

Tests

Per the owner's standing instruction no test suite was run locally; verification is on CI. Existing suites updated to the new shape:

  • ChatPageMoreCoverage: fork / plan-from-here / apply-plan now assert the action-error ErrorNotice (role alert, real reason, alertSpy not called).
  • AppDetailPageCoverage: load failure asserts Failed to load app + notice + Retry (not "App Not Found"); registry/system failure asserts the app renders and the notice names registry offline; install-failed hand-off asserts the staged prompt (consumeChatHandoff) carries the log tail and the endpoint; Dismiss label. AppDetailPageAutoAction / Stars / ArtLocalFallback / TrustAppModal: "not installed" rejections now carry status: 404.
  • ArtifactDeployPageCoverage: failure text is the backend reason verbatim / Request failed (NNN).
  • ChannelPageCoverage + ChannelPage.clearContext: in-page channel-error notice instead of a modal / window.alert.
  • DevFleetPage + DevFleetPageCoverage: dismiss via the provision-error-<name> notice.
  • HooksPage.testFailure: hook-test-error / hooks-error notices.

Local gates run clean: tsc -b, eslint on every touched file, the full i18n-check.mjs chain with I18N_BASE_REF=origin/main (added-lines 0, source-strings 0 badly shaped, key-refs 0 dangling, pseudolocale in sync), check-phantom-classes.mjs, check-theme-colors.mjs; no non-inclusive terms in the diff.

Manual verification

Capture harness website/capture/error-notice-pages-rest-1.{html,tsx} + website/scripts/capture-error-notice-pages-rest-1.mjs mounts the real HooksPage, AppPage, ChannelPage, AgentsPage and DevFleetPage with fetch stubbed to reject — except the hooks list, which resolves with one hook whose last run failed so the runner can open its last_error row and run a failing Test (both notices are in the after frame). The runner asserts the after frames render one "Ask the agent" per scene and none of the legacy markup, and that the before frame (same harness against a pristine origin/main worktree) renders zero hand-offs.

Screenshots / video

Before (base) → after (this PR), dark:

before — "Error" banner, "App Not Found" for a rejected fetch, EmptyState for a failed discovery

after — ErrorNotice with hand-off and Retry on every failure

After, light theme

after — light

Related Issues

Batch pages-rest-1 of the error-state sweep (research/error-states-audit.md §5.4). Sibling batches: settings-1 (owns the AUTOSDE rule text), chat-1 (#8730 — shares the ChatPage /side touch noted above), chat-2, pages-rest-2, components.

Pattern harvest

Rule candidate: semgrep
Pattern: a fetch helper (jget/jsend-style) that returns r.json() without checking r.ok, and a useQuery/raw-promise .catch(() => setX(null | [])) — both convert a transport failure into an empty / not-found state (audit §4.6–4.8). Recorded here for the settings-1 rule-text PR.

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title
  • Existing tests pass and new tests added for new functionality (CI; assertions updated to the new notice shape as listed above)

@CrysisDeu
CrysisDeu requested a review from a team September 5, 2026 21:03
@CrysisDeu
CrysisDeu requested a review from a team as a code owner September 5, 2026 21:03
@CrysisDeu
CrysisDeu requested a review from Zedmor September 5, 2026 21:03
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 5, 2026
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

Design-level review of 6f025646028418a7fdb902da5c20a9860c612dfd — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

The temp-screenshots/ and capture-harness conventions are established by sibling batches at the base commit, so those are repo-normal. The diff matches the description's extensive accounting; the DevFleet module-state notice, the grandfathered ChatSidebar chip, and the cross-PR overlaps are all disclosed with reasoning that holds up against the code.

Design-Verdict: PASS

A documented audit batch executed per-row against a blocking rule, with every deviation, overlap, and grandfathered surface disclosed and justified in the diff.

[DESIGN-REVIEWED] 6f02564

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — 🟡 CONCERNS

UX-level review of 6f025646028418a7fdb902da5c20a9860c612dfd — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All evidence reconciled. The blind reader correctly identified every primary control the PR adds (Retry, Ask the agent, dismiss, Back to Apps, the last-error chevron) and would dare to use them; the confusions it did report, plus the screenshot coverage, drive the verdict below.

UX-Verdict: CONCERNS

Every migrated notice names what failed and offers a way out, and the blind reader used them all correctly — but 8 of 13 migrated pages were never screenshotted.

Watch

  • Two red strips under one hook row are indistinguishable: the expanded last_error notice renders untitled ("npm ERR! code ELIFECYCLE…") directly above the titled "Test failed for fmt" notice, and the blind reader said "the row seems to report two different failures at once and I can't tell which is which." Anyone with a failing hook who presses Test hits this, every time. Smallest fix: give the expanded ErrorNotice in HooksPage.tsx a title such as "Last run failed".

Evidence gaps

  • No screenshot exists for 8 touched pages: ChatPage (the new action-error, upload-error, sid-error, pin-error notices), ChatSidebar, AppDetailPage (install-failed notice), ArtifactDeployPage ("Some profiles could not be listed"), ArtifactDetailPage ("Back to Live" load failure), ArtifactsPage, ArtifactPopoutFrame, ContextBreakdownPanel — an after-dark capture of each would close it.
  • The new HooksPage page-level "Failed to load hooks" notice with Retry appears in no after screenshot (only the retired banner in before-dark.png).

Suggestions

  • AgentsPage quiet mode leaves "Context Window Usage" and "Subagents" as bare empty boxes during a roster outage (reader: "no idea what it is; nothing to dare"); render one muted line, e.g. "Unavailable while agents can't load", instead of blank.

[UX-REVIEWED] 6f02564

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — 🟡 CONCERNS

Premise-level review of 6f025646028418a7fdb902da5c20a9860c612dfd — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All hunks are read, the mandating rule is verified in website/AUTOSDE.yaml:526 (blocking errors-use-error-notice), the reused mechanisms (ErrorNotice's existing askAgent/testId/report props, findReport/recordError, the capture-harness convention with 406 existing capture scripts) all pre-exist, and isNotFoundError consolidates a private duplicate out of TrustAppModal into three consumers. The only thing riding along across ~7,900 lines is a one-class focus style on the chat title input.

First-Principles-Verdict: CONCERNS

Every substantive item is mandated by the blocking errors-use-error-notice rule; one cosmetic focus-style change rides along in ChatPage's rename hunk.

What this change ships

Intent: bring 13 top-level pages into compliance with the blocking AUTOSDE rule errors-use-error-notice and surface their silent failures — a FIX (website/AUTOSDE.yaml:526, restated in website/AGENTS.md, so the requirement is derived, not relitigable).

  1. Hand-written error divs/spans/modals on 13 pages now render through shared ErrorNotice — justified
  2. Native alert()s (ChannelPage ×2, ChatPage fork/plan/apply) become in-page notices — justified
  3. Non-404 app-load failures stop rendering as "App Not Found" (isNotFoundError, 3 consumers, replaces TrustAppModal's private copy) — justified
  4. Failed chat file reads stop opening a tab whose "content" is the error string — justified
  5. ~45 swallowed .catch/onError-less mutations now show error states with Retry — justified, declared
  6. Deploy-page jget/jsend throw on non-2xx so backend error bodies become errors, not data — mechanism-level, declared
  7. DevFleet notify(type:'error') rerouted from 7s toast to persistent notice at the one seam all ~30 sites share — cause-level, declared
  8. Hooks last_error moves from tooltip to expandable row — declared, named harm (hidden failure text)
  9. Client-side pick/upload validation split into role="status" hints — derived from the rule's own error/hint distinction
  10. Chat title input gains a focus-visible underline — rides along
    Capture harness + screenshots follow the established repo convention (not riders). The 320-surface backlog is handled by recorded sibling batches (apps-1/2/3, settings-1/2, chat-2, pages-rest-2 capture files already exist), so batch scope is accepted-and-deferred, not a point patch.

Watch

The description was truncated at 8000 bytes by the workflow, so "undeclared" could not be fully established for item 10; it is nonetheless unrelated to error surfacing.

Subtractions

Drop focus-visible:border-b focus-visible:border-accent from the session-title Input in website/src/pages/ChatPage.tsx (the rename-failure hunk) — it is a styling change with no error-surface role; land it in its own change if wanted.

[FIRST-PRINCIPLES-REVIEWED] 6f02564

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings (all downgraded on adjudication)

GPT 5.6 flagged blocking issues on 6f025646028418a7fdb902da5c20a9860c612dfd; Opus 4.8 adjudication downgraded every one of them to advisory. Adjudication downgraded all 1 blocking finding(s) to advisory: the remedy each one requires is disproportionate to the harm it prevents. Read them as advice, not as merge conditions.

This comment is updated in place on each push.

Review details

BLOCKING -- website/src/pages/ChannelPage.tsx:695 -- Failed reconciliation leaves rejected optimistic state visible
} catch { /* the failure notice is already showing; nothing better to say */ }
Gateway outage -> mutation and reconciliation reject -> the row retains its uncommitted done/listen state.
Anchor: errors-use-error-notice
Fix: Roll back the optimistic field and surface the reconciliation failure through ErrorNotice.
[GPT-REVIEWED] 6f02564
[BLOCK-MERGE-DOWNGRADED] 6f02564

Adjudication (Opus 4.8) — is blocking on each finding proportionate?

The error IS already surfaced through ErrorNotice via fail() (line 706/715 → setError → the page's single ErrorNotice). The anchor rule errors-use-error-notice is therefore satisfied; the finding's real complaint is the transient row-state inconsistency on a double failure.

LOW — requires both the mutation reject (line 704/713 catch) AND the reconciliation re-read reject (line 693-695 swallowed catch); consequence is a visible, self-correcting UI inconsistency: the error notice is shown via fail(), the server never received the change (mutation failed, so no data corruption), and the row corrects on the next successful channelGet/reload or socket event (channel_agent_left, line 631-634).
Recovery: named at line 693-694 (re-read replaces row) and via socket handlers; notice visible at line 706/715.
Real fix (hand-rolled rollback) is the exact mechanism the author documented as unsafe at lines 685-690 — it could resurrect a genuinely removed agent or clobber a later successful write — so the remedy adds a higher-harm correctness hazard plus permanent complexity to cure a transient visual glitch.

[ADJUDICATION] 6f02564 total=1 uphold=0 downgrade=1
DOWNGRADE F1 website/src/pages/ChannelPage.tsx:695 reason=disproportionate-remedy
[GPT-ADJUDICATED] 6f02564

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 6f025646028418a7fdb902da5c20a9860c612dfd: <one-sentence reason>

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed 6f025646028418a7fdb902da5c20a9860c612dfd — this comment is updated in place on each push.

Review details

No findings that block; one advisory.

FINDING — website/src/pages/AppDetailPage.tsx:872 — for an installed app, api.system() failing independently sets sideFailure ??= e, and the unconditional if (sideFailure) setError(...) then renders a page-level error banner over the fully-working installed app (the old api.system().catch(() => ({ hostname: '' })) degraded this silently), with text describing the hostname probe, not the catalog → Fix: hold the hostname-probe failure in a separate variable that does not feed sideFailure/setError, or restore the silent '' hostname fallback.

[OPUS-REVIEWED] 6f02564

Verdict parsed from the review's SHA-scoped output markers for commit 6f025646028418a7fdb902da5c20a9860c612dfd.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 6f025646028418a7fdb902da5c20a9860c612dfd: <one-sentence reason>

@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 5, 2026
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Round-1 dispositions (head bf762b832):

GPT 5.6 — both blocking findings fixed in code.

  • F1 ChatSidebar.tsx (session-row-fixed-height): the source-link failure notice no longer wraps under the chips. It sits on the chip line as a zero-basis flex-1 min-w-0 item, its text truncates ([&>span]:truncate) with the full sentence in title, and the +N ↻ chip beside it remains the retry. No new stacked line.
  • F2 DevFleetPage.tsx (max-two-buttons-per-row): the rebase failure notice (hand-off + dismiss) is out of rowButtons and renders as its own row-spanning block under the row (renderRebaseFailure, same placement as the sync-log <pre>). The success chip stays where it was.

UX Review — Watch fixed; both suggestions taken.

  • DevFleet feedback locus: the page-level action error now scrollIntoViews when set, so a failure from a row deep in the list is brought on screen (what the viewport-anchored toast used to do). Errors still go to the notification center as before.
  • AppPage error branch: Retry (re-runs the load) + Back to Apps added.
  • ChatSidebar source-links notice: with F1 it now sits directly beside the +N ↻ retry chip, so "Click to retry" reads against the control it names; the notice text is the string the site already had.

First Principles — both subtractions taken.

  • The five file-local error→text helpers (errText ×2, errMsg, boardErrMsg, newChatErrMsg) are gone; every site imports errMessage from utils/thunkError with a per-site || <fallback> (AgentsPage keeps a one-line nullable adapter failText so <ErrorNotice message> still renders nothing for "no failure").
  • TrustAppModal.isNotFound deleted; it imports the shared isNotFoundError, so one predicate owns "absent, not failed".

Focus Cue Gate: the ChatPage title-rename Input (touched by this diff) gains focus-visible:border-b focus-visible:border-accent.

Design Review — PASS, nothing to answer.

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention merge conflict Branch has merge conflicts with its base — author must resolve before merge and removed readiness: checking Automated validation is still running labels Sep 5, 2026
@CrysisDeu
CrysisDeu force-pushed the fix/error-notice-pages-rest-1 branch from bf762b8 to 6c64881 Compare September 5, 2026 21:58
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 5, 2026
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Round-2 dispositions (head 6c64881b1, squashed with round 1 into the second commit):

Frontend Tests (first real run, 28 red across 4 shards) — 24 are this PR's, fixed; 4 are inherited from main.

@CrysisDeu
CrysisDeu force-pushed the fix/error-notice-pages-rest-1 branch from 6c64881 to ab8a1c9 Compare September 5, 2026 22:10
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running and removed merge conflict Branch has merge conflicts with its base — author must resolve before merge readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention labels Sep 5, 2026
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Round-3 dispositions (head f22384da6):

GPT 5.6 — blocking finding fixed at all five rows (max-two-buttons-per-row):

  • HooksPage.tsx banner: a failed read offers Retry with no dismiss (dismissing a query error would only hide a list that is still missing); a failed action offers dismiss with no Retry — never three.
  • ChatSidebar.tsx source-link chip line: the notice drops its hand-off. The line already holds the link chips and the +N retry control and may not grow a second line (session-row-fixed-height), so the constraint is the row budget; the comment on the notice says so. Nothing is at risk from a hand-off here, so this is a rule-gap for the askAgent-off comment form — recorded in the PR body for the settings-1 rule-text PR.
  • ChatSidebar.tsx cleanup dialog: the notice moves to its own line above the Cancel / Archive pair.
  • DevFleetPage.tsx sync and provision failures: the notice takes its own basis-full line; Log / dismiss stay a two-control row beneath it.

UX Review — suggestion taken: ChatPage clears actionError in the slot-switch effect alongside uploadError/uploadHint.

Design Review — both suggestions acknowledged, not taken here: (1) DevFleet's single latest-error slot is a documented trade-off — burst failures still reach the notification center, and a stacked list is a follow-up for the components batch; (2) folding the fallback into utils/thunkError.errMessage changes a shared helper whose documented contract is to return '' for a message-less rejection so every caller keeps its own fallback — out of scope for a pages batch, noted for the rule-text PR.

First Principles — rider declared: the two focus-visible:* classes on the title-rename Input are required by the blocking Focus Cue Gate, which fires because this diff touches that line (the renameSlot failure now reports through the notice). Added to the PR body under round 3.

Frontend Tests: the 4 remaining reds on ab8a1c92e were the inherited RemoteCrewPanel cases; their unblocker #8828 merged at 22:23, so this run's merge ref includes the fix.

@github-actions github-actions Bot removed the readiness: checking Automated validation is still running label Sep 5, 2026
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 6, 2026
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Round-9 disposition (head cc890a7af, folded into the second commit):

GPT 5.6 — the one fenced finding fixed. AppDetailPage.load() never cleared app at the start of a load, so navigating in-session from a loaded app to a genuinely non-existent one left the old record rendered under the new URL (both lookups report absence, nothing calls setApp). load() now does setApp(null) up front; the loading branch already replaced the page for the duration of a load, so the post-install reload path is unchanged.

Everything else on adcf8d63e was green: all four frontend shards, all backend shards (the inherited members-audit red is gone with #8844), E2E, desktop builds, Design / UX / First Principles PASS.

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Sep 6, 2026
@CrysisDeu
CrysisDeu force-pushed the fix/error-notice-pages-rest-1 branch from cc890a7 to eb5ebe2 Compare September 6, 2026 02:14
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: passed Eligible automated validation passed for the current revision labels Sep 6, 2026
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Round-10 dispositions (head eb5ebe216, folded into the second commit) — everything on cc890a7af was green (58/58; GPT no blocking findings, Design / First Principles PASS); this round takes the UX CONCERNS Watch items:

  • Two notices for one failed Test — fixed. testMut.error leaves the page-top mutError chain; the titled hook-test-error notice beside the row is the only report. HooksPage.testFailure now pins exactly one occurrence of the message and no hooks-error banner.
  • Bare error.message on AppPage / AgentsPage — fixed. Both load-failure notices carry a what-failed title (Failed to load app, Failed to load agents; 2 new keys × 13 catalogs, en-XA regenerated), matching their siblings.
  • "Ask the agent" pre-click disclosure — agreed it is now the dominant error control; it is AskAgentButton's copy, so a components-batch change. Noted in the body.
  • Evidence gaps — the HooksPage scene is taller, so the provider-hooks notice + Retry are in the frame. The remaining pages' notices are pinned by their updated suites; ChatPage / ChatSidebar need a live slot the fixture-fetch harness cannot stage.

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 6, 2026
@CrysisDeu
CrysisDeu force-pushed the fix/error-notice-pages-rest-1 branch from eb5ebe2 to ee3ee8f Compare September 6, 2026 02:44
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 6, 2026
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Round-11 disposition (head ee3ee8feb, folded into the second commit):

GPT 5.6 — the one fenced finding fixed by taking its first suggested remedy. A refused dismiss / listen change no longer rolls the row back by hand; it re-reads the channel (reconcileChannelapi.channelGetmapChannel, the same path clear-context already uses) and lets the server's answer replace the row. A hand-rolled rollback could not distinguish the optimistic done from the one channel_agent_left writes, so a server-side removal with a lost response would have resurrected the agent; the server is now the only source for the row after a refusal. If the re-read fails too, the row is left as is — the notice already names the failure.

Everything else on eb5ebe216 was green (56/58 with only GPT + Readiness red; Design / UX / First Principles PASS or advisory).

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Sep 6, 2026
@bolichen97
bolichen97 enabled auto-merge (squash) September 6, 2026 03:11
@CrysisDeu
CrysisDeu force-pushed the fix/error-notice-pages-rest-1 branch from ee3ee8f to ccaf01c Compare September 6, 2026 03:13
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Round-12 dispositions (ee3ee8feb was all green — 58/58, GPT + Opus no blocking findings, Design PASS; UX and First Principles advisory CONCERNS, both answered here):

First Principles

  • Pin failure auto-vanishes — taken. The 8-second timer was pre-existing on main and covered both kinds; it now covers only the STATUS line ("not in this history", "pin limit reached"). A pin/unpin/load failure stays until dismissed or handed off.
  • Rename Input focus-visible styling undeclared — declared in the body. It is the Focus Cue gate's fix: the control suppresses its focus ring (focus:!shadow-none) and the gate requires a replacement cue on any focusable control this diff touches. Not a design change.
  • Inline errMessage(e) || fallback in ArtifactDeployPage / ChatSidebar — agreed, but declined for this batch: a page-local reader is a 15-site mechanical edit on two files that are otherwise settled after eleven review rounds; it goes with the components-batch follow-up that also moves the "Ask the agent" wording.

UX

  • "Ask the agent" reads as a guess / "agent" is overloaded — agreed it is now higher-stakes; it is AskAgentButton copy and lands in the components batch.
  • Evidence gaps — ChatPage / ChatSidebar need a live slot the fixture-fetch harness cannot stage; the AppDetailPage and Artifact* notices are pinned by their updated suites (AppDetailPageCoverage, ArtifactDeployPageCoverage, ArtifactDetailPage*). Adding those scenes to the harness is a follow-up, not a merge blocker.

@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: passed Eligible automated validation passed for the current revision readiness: checking Automated validation is still running labels Sep 6, 2026
UX Review:
- ChannelPage: a refused close keeps the channel in the list; a refused
  dismiss / listen change puts the agent row back, restoring only the state
  THAT request set so a newer, successful change is never overwritten by a
  stale rollback; a failed presets read no longer papers over a failed
  channel-list read; the "No channels yet" / "Create a channel" onboarding
  copy is withheld while the list read failed.
- AgentsPage: a transport failure that takes every read down is named once
  (the roster notice with Retry); the cards whose failure reads identically
  stay quiet but still withhold their "no active sessions" / "no subagents"
  empty states. A card failing for a different reason still names it.
- DevFleetPage: a rejected fleet read is the same unknown as a discovery
  error (counts show "—", not "Worktrees (0)" / "0 rows"); the DISK card
  shows "—" when /disk fails.
- HooksPage: the expanded last_error row is a sticky-left block exactly as
  wide as the scroller, so it reads in full at any horizontal scroll instead
  of painting under the sticky Actions column.

GPT (page-layout-pattern): every control this batch added to a page file
renders through the Btn primitive, not a raw <button>.

Capture harness: ChannelPage and AgentsPage scenes; HooksPage mounts a hook
whose last run failed and the runner opens its last_error row and runs a
failing Test; scene width fits the hooks table so the Status column and its
expander are in frame.

Tests: ChannelPageCoverage pins the reverted optimistic state, the kept
channel on a refused close, and the notice (not the empty state) on a failed
list read; HooksPage.stickyActions pins the measured scroller ref.

GPT (round 9): AppDetailPage.load() drops the previous record before resolving
the new name, so an in-session navigation to a genuinely missing app renders
the not-found page instead of the stale app under the new URL.

UX (round 10): a failed hook Test is reported once, by the titled notice
beside the row (testMut leaves the page-top mutError chain); the AppPage and
AgentsPage load-failure notices carry a what-failed title instead of the bare
transport message; the HooksPage capture scene is tall enough to show the
provider-hooks notice and its Retry.

GPT (round 11): a refused dismiss / listen change reconciles the row from
channelGet instead of a hand-rolled rollback, which could not tell the
optimistic value from one a socket event or a newer request wrote.

First Principles (round 12): the pin FAILURE notice no longer shares the
8-second timer with the pin STATUS line; only the answer times out.
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Final head 6f0256460 — 58/58 green (the one red on this head was test_snapshot.py::…::test_a_FRESH_gateway_still_orders_the_copy_against_a_delivery, a timing assertion in a backend test this PR does not touch; it passed on rerun). GPT + Opus no blocking findings, Design PASS; UX / First Principles advisory:

  • Opus advisory — AppDetailPage api.system() failure now shows a page-level notice over a working installed app. Accepted as intended: the sweep's premise is that a swallowed probe failure is a defect (the hostname feeds the install target the page shows), and the notice is dismissible with a hand-off. If a quieter surface is wanted for side probes, that is a page-level decision for the follow-up, not a rule violation.
  • First Principles — focus-visible styling on the rename Input. Declared in the body (the lane saw a description truncated at 8000 bytes): it is the Focus Cue gate's required replacement cue for a control that suppresses its focus ring, not a cosmetic ride-along. Dropping it re-reds the gate.
  • UX — 8 of 13 pages not screenshotted. Unchanged from round 12: chat surfaces need a live slot the fixture-fetch harness cannot stage; the rest are pinned by their updated suites.

Ready for human review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants