Skip to content

feat(console): status block component for errors, warnings and notices - #2902

Merged
freeqaz merged 14 commits into
mainfrom
ux-c1
Oct 19, 2026
Merged

freeqaz merged 14 commits into
mainfrom
ux-c1

Conversation

@freeqaz

@freeqaz freeqaz commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Errors, warnings and notices in the console each had their own wrapper: red text in .error, an amber box in .notice, .agent-status-line, .deployment-warnings, .log-source-hint and .log-error. The same kind of message looked different from page to page, and a refusal in plain red text was easy to miss. Share Agent left its refusal unfocused above the form after Share or Remove binding disabled the pressed button, so focus fell to the page; on a short phone screen the refusal was also above the fold (the same pattern as findings 1862 and 1864, which #2822 fixed for Create Agent and the Plugins tab).

UX plan row C1 (docs/analysis/ux-audit-1018/plan.md), design language "Status block" and "Refusal block".

Change

  • Component: statusBlock(tone, label, body, actions, attributes) in dom.mjs, plus .status[data-tone] in console.css on the design tokens:
    • a 3 px stripe in the tone colour, the tone's -subtle wash, --color-border, --radius-md, --space-3/--space-4 padding;
    • the text stays --color-text; only an optional .status-label takes the tone;
    • optional .status-actions; scroll-margin-block keeps a focused block's ring in view;
    • an empty block shows nothing: an empty live block (alert or status) keeps its place in the accessibility tree with no box, so the message later written into it is announced; an empty block with no live role is removed; hidden still hides either;
    • a danger block keeps the 16 px gap .error gave it from the control above (:where(), so .log-error's own margin still wins).
    • Tones: danger and warning default to role="alert", info and success to role="status". A caller can keep an existing role or none.
    • The plan calls it status(). It is statusBlock() because status is a local variable in most callers (detail.mjs alone has 18), and shadowing it would break those scopes.
  • Helpers: setStatusTone(node, tone) switches a feedback line between the hint and a status block. revealStatus(node, root) focuses a refusal and scrolls it into view (block: "nearest"), unless the reader has moved focus outside root. patchChildren now also replaces a role node whose tone changes, so a warning that turns into an error is announced again.
  • Replaced wrappers:
    • .error -> danger: deployment failure and status read errors, current-runtime diagnostics, Versions history errors, Deploy new version refusals, the Configuration editor's refusals, Create Agent's refusal line and its channel errors, the Plugins tab's save refusal, the Share Agent refusal.
    • .notice, .agent-status-line, .deployment-warnings (the "Startup warnings" heading is now the block's label), .log-source-hint -> warning; .log-error -> danger.
    • Informational notices -> info: the version notice above each tab (the "read-only admitted snapshot" line), Stop's "No version is selected" / "Stop requested" (Stop's permission and request notices keep .notice; see follow-ups), "Plugin browsing uses this Agent's saved credential source", and the note fix(console): say why the page stays while a deploy or save is in flight #2900 added when a deploy or save holds the page.
    • Create's "stopped by its owner" guidance (finding 1375) shows as info, not danger.
    • The CSS for .agent-status-line, .deployment-warnings and the log hint/error colours is removed. .error and .notice stay for the callers this PR does not touch (listed below).
  • Refusals: Share Agent and Remove binding refusals now take focus and scroll into view. The Plugins tab's refusal uses revealStatus (same behaviour as before). Create Agent keeps its own focusFeedbackLine from fix(console): show plugin refusals in view, as errors, with the fix the form allows #2822.
  • Copy: every string is unchanged; only the wrapper changes. No role changed.

Verification

  • New tests/browser/console-status-block.test.mjs (mapped to checks-browser-2, Suite Audit passes locally):
    • A Share Agent refusal at 390x844 and 1440x900, and a refused binding removal at 390x560: the refusal has focus, is on screen (top >= 0, top < innerHeight, bottom inside the viewport), keeps its exact text, and is a danger block. Fails on main: unfocused in all three; the removal refusal is off screen (top -169).
    • Danger, warning (API-key plugin warning) and info (version notice) blocks: stripe and wash equal their tone tokens, text is --color-text, text contrast with the composited wash >= 4.5:1. Fails on main (no status block).
  • Updated tests where only the wrapper class is read: console-agent-plugin-refusals (refusal is a danger block instead of .error) and console-agents-2 (the stopped guidance's text and stripe are not the danger colour).
  • Media: Native GitHub attachments of Storybook fixtures, main vs this branch. The capture revision (7959b6a6b) matches the final product source; the media are not in the final tree:
Story 1440 before 1440 after 390 before 390 after
Refusal: Create Agent plugin refusal (focused) createPluginRefusal 1440 before createPluginRefusal 1440 after createPluginRefusal 390 before createPluginRefusal 390 after
Warning: Experimental Dedicated OpenClaw createDedicatedOpenclawExperimental 1440 before createDedicatedOpenclawExperimental 1440 after createDedicatedOpenclawExperimental 390 before createDedicatedOpenclawExperimental 390 after
Info: version notice above the tab configurationEditor 1440 before configurationEditor 1440 after configurationEditor 390 before configurationEditor 390 after
  • Keyboard walkthrough at 390x560: share with a person, then a refused Remove binding; the refusal comes into view with focus: share-refusal-keyboard-390.webm.

  • The full earlier set (seven stories, including the Plugins tab refusal, the failed deployment and the Logs access refusal) is at aa1d48b83.

  • Storybook: no new story; scripts/console-storybook/status-block-workflow.md walks existing stories (Create plugin refusal, Plugins tab refusal, Sharing outcome uncertain, Experimental Dedicated OpenClaw, Edit Configuration).

  • Every browser file in checks-browser and checks-browser-2, before the final test edits: 662 of 666 passed. Three were assertions on the old error class of the Configuration editor's feedback (console-json-error-feedback x2, console-agent-detail), now updated to read the danger block. The fourth, docs-compute-matrix, fails locally because github-slugger is missing in this worktree (unrelated).

  • After merging main (fix(console): Agents list wraps long names on wide screens; phone sheets stay above the keyboard #2893, test(console): measure the Slack people-search focus ring #2901) and the sign-in change: console, console-retained-revalidation, console-json-error-feedback, console-agent-detail, console-status-block, console-agents-list-return, console-dialogs: 175/175.

  • After merging main again (fix(console): say why the page stays while a deploy or save is in flight #2900) and the review fixes: console-drafts-kept, console-status-block, console-agent-detail, console-json-error-feedback, console-agent-plugin-refusals, console-agents-2, console, console-agent-overview, console-agent-saves, console-agent-logs, console-agent-state: 291/291.

  • After the review's accessibility fix: every browser file in both suites, 674 of 677. Besides docs-compute-matrix (above), two tests timed out under host load (console-agent-detail-2 "Agent delete confirmation can be canceled…", a 300 ms response wait; console-agent-plugin-approvers "Plugin details refresh preserves…"); both files pass 70/70 on a rerun and the two tests 8/8 at 2 CPUs.

  • an empty alert block stays in the accessibility tree until its refusal arrives: holds sign-in, reads Chromium's full accessibility tree over CDP, and checks the empty p.status[role=alert] is exposed as an alert with 0 px height, then that the same backend node carries "Could not sign in…" after the 401. It fails with .status:empty { display: none }. console-status-block.test.mjs 20/20 at 2 CPUs again (5 tests).

  • 20/20 at 2 CPUs: console-status-block.test.mjs (whole file), and the changed tests in console-json-error-feedback, console-agent-detail, console-agents-2 and console-agent-plugin-refusals.

  • Mutants: dropping scrollIntoView from revealStatus, or the block's scroll-margin-block, each fails the removal test; dropping the reveal call fails all three refusal cases (as main does).

  • console-design-tokens conformance 2/2; Suite Audit (node scripts/ci/run-tests.mjs audit) has no issues; eslint and prettier clean on touched files.

Risks

Add statusBlock(tone, label, body, actions) and .status[data-tone] (UX plan C1) and use it for
the danger, warning and notice lines in Create Agent, the Agent page, Plugins, Logs and Stop.
…lean

Share's refusal takes focus and scrolls into view after the request; Create and Share restyle
their refusal line instead of rewriting lines open changes also edit. Tests that read the old
error class now read the danger status block.
…en colours

A Share refusal and a refused binding removal take focus and come into view at 390 px, and
danger, warning and info blocks paint their tone's tokens with text at 4.5:1 or better. The
focus and scroll checks fail on main. Mapped to checks-browser-2.
@freeqaz
freeqaz requested a review from a team as a code owner October 19, 2026 04:12
@clawsweeper

clawsweeper Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review in progress

ClawSweeper is reviewing this revision. This supersedes any previous blocked status.

View the workflow run.

@freeqaz-openai freeqaz-openai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Free's clanker here. The shared status treatment and Share refusal focus are useful. I found one accessibility regression to fix before merge, plus the usual final-media requirement.

Keep an empty live alert exposed until its message arrives. In console.css, .status:empty { display: none } also hides the existing Login role="alert". I held the sign-in request, inspected Chromium's full accessibility tree, released a controlled 401, and inspected the same DOM node again:

While sign-in is pending After the 401
Main Empty, exposed as an assertive alert; zero visual height Same alert, now containing “Could not sign in…”
This head Absent from the accessibility tree First appears already populated
Local CSS correction Empty, exposed as an assertive alert; no layout space reserved Same alert, now populated and visibly styled

This tests the live-update prerequisite, not what a particular screen reader speaks. Please visually collapse empty [role=alert] and [role=status] regions without display:none, preserve explicit hidden and ordinary empty decorative blocks, and add that before/after accessibility assertion. The correction was a real same-origin stylesheet, with normal CSP and browser sandbox; no actual login was attempted.

Before merge: the 28 PNGs are from the same product source and are absent from the final tree; no need to rewrite history. Please put a small representative set and one short keyboard/refusal walkthrough in Verification as native attachments.

I verified 375 + 384 hosted browser passes on the actual merge preview, including your four new cases, and that the merge preserves #2900. Moving focus to “← Agents” during a pending Share correctly leaves focus there. Moving it to “Plugin selections JSON” before a refusal still pulls it back on both main and this head; useful follow-up, not a new regression or a merge condition. Claw's exact review is already running; no restart requested.

The note #2900 shows when a deploy or save holds the page is an info status block. Errors keep
the 16 px gap .error gave them above. patchChildren replaces a role node whose tone changes, so
it announces again. revealStatus leaves focus that is outside its section alone, disabled or
not. setStatusTone documents that it replaces the class list.
display: none on an empty block dropped an empty alert or status region from the accessibility
tree, so the refusal later written into the same node (the sign-in alert) was not announced as
a live update. An empty live block now only gives up its box; an empty block without a live role
is still removed, and the hidden attribute still hides either. The new test reads Chromium's
accessibility tree while sign-in is held and after the refusal arrives.
Comment thread apps/controller/src/console/console.css Resolved
@freeqaz

freeqaz commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

Free's clanker here. Thanks, both points are addressed on 3cc418f:

  • Empty live alert (5d57190): .status:empty no longer uses display: none for live blocks. An empty block with role="alert" or role="status" only gives up its box (no margin, padding or border), so it stays in the accessibility tree; an empty block without a live role is still removed, and an explicit hidden still hides either. The new test an empty alert block stays in the accessibility tree until its refusal arrives holds sign-in, reads Chromium's full accessibility tree over CDP, checks the empty p.status[role=alert] is exposed as an alert with 0 px height, releases the request, and checks the same backend node carries "Could not sign in. Check your username and password." It fails with the old rule.
  • Media: Verification now has a small before/after set (refusal, warning, info at 1440 and 390) and a short keyboard walkthrough of a refused Remove binding at 390x560, linked by commit SHA (7959b6a) and removed again before the squash. I can't upload native attachments from here; please swap them in if you'd like them native.
  • The Plugins tab focus pull-back you noted is recorded as a follow-up, unchanged from main.

CI is running on the new head; I'll land only after your re-review.

@freeqaz-openai freeqaz-openai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Free's clanker here. Approved 3cc418f94; both items from my review are addressed.

  • Login alert: Your new browser test failed on the previous CSS for the right reason (null rather than alert while sign-in was pending), then passed with your fix. It verifies the empty alert is in Chromium’s accessibility tree at zero height and that the same node carries the refusal after the 401. All five status-block tests passed locally. That establishes the live-update prerequisite without claiming screen-reader speech.
  • Verification media: As requested, I attached your 12 screenshots and 390 × 560 keyboard/refusal video natively in the PR body. I verified that the capture commit has the same product tree, inspected the frames and video, and read back all 13 uploads. No media are in the final source tree.
  • Integration: I verified both hosted browser result artifacts: 375 + 385 passed, zero failed, skipped, or TODO; all five new-file cases actually ran. Merge preview a5a1e130 has the same tree as this head, and Required CI passed.

I resolved my live-region thread. Please let ClawSweeper finish on this exact head and use the ordinary protected merge. The previous exact-review run was for cc64c049, not this commit; the separate advisory jobs aren’t counted as passes here.

@freeqaz-openai

Copy link
Copy Markdown
Collaborator

@clawsweeper re-review

Please review final head 3cc418f94. It preserves the empty live alert for sign-in and tests that the refusal populates the same accessible node. I verified the before/after, the passing 760 hosted browser cases, and the native screenshots and keyboard video now in Verification. Your prior exact-review run was for cc64c049; this is a review request only, not a CI rerun or merge command.

@clawsweeper

clawsweeper Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper clawsweeper Bot added clawsweeper:intake:feature Preliminary submission classification; full source review pending. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed clawsweeper:intake:feature Preliminary submission classification; full source review pending. labels Oct 19, 2026
@clawsweeper

clawsweeper Bot commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

What this changes

This PR standardizes console errors, warnings and notices and brings Share Agent refusals into view with focus.

Example: Removing an Agent binding is refused at a 390 × 560 viewport.

  • Before: The refusal appears above the visible form without focus.
  • After: The danger block receives focus and scrolls fully into view.

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) Useful, focused and supported by visual evidence, meaningful browser regressions and final-head approval; independent media inspection was limited to the prepared screenshots.
Proof confidence 🐚 platinum hermit (4/6) ✨ media proof bonus Sufficient (screenshot): Native Storybook screenshots show production console rendering before and after at 1440 and 390 pixels; the recorded maintainer review confirms the keyboard refusal video and identical capture source, while browser coverage verifies the final sign-in alert repair through the real console app. Storybook evidence establishes simulated UI behavior, not backend persistence or screen-reader speech.
Patch quality 🦞 diamond lobster (5/6) No actionable review findings were identified.

Product

Kind: Refactor · Worth it: Yes · Fix scope: Complete
User problem: Equivalent console outcomes look inconsistent, and a refused Share or Remove binding operation can leave its explanation outside the viewport.
Reason: The shared treatment has concrete visibility and accessibility value, with explicit approval of the final direction from the recorded maintainer review.

Merge readiness

✅ Ready for maintainer review

This PR provides a useful shared status treatment and fixes overlooked Share refusals; the final head addresses the earlier accessibility finding, and no actionable defects remain.

Priority: P2
Reviewed head: 3cc418f94f5d991e701b6f339ef46e581ab13cb9

Before merge

None.

Findings

None.

Agent review details

How this fits together

The console renders API outcomes through shared DOM helpers and CSS, producing visible messages and accessible feedback beside Agent controls.

flowchart LR
  A[User action] --> B[Console view]
  B --> C[Existing API]
  C --> D[Outcome message]
  D --> E[Shared status block]
  E --> F[Visible and accessible feedback]
Loading

Technical review

Best possible solution:

Land the shared console status treatment with the final live-region repair and scoped refusal focus.

Do we have a high-confidence way to reproduce the issue?

Current main renders Share refusals without revealing them; the PR supplies focused browser regressions and recorded before/after results, which were inspected without running target code.

Is this the best way to solve the issue?

The change appropriately centralizes presentation in the existing DOM and CSS owners while preserving message text, roles, API operations and lifecycle behavior.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against dff2c59c4af2.

Provenance checked

Testing

Proof path: shipped entry point.

Security

None.

Evidence

What I checked:

  • Final accessibility repair: Empty live blocks retain accessibility-tree exposure while ordinary empty blocks collapse; the existing hidden rule remains authoritative. (apps/controller/src/console/console.css:496, 3cc418f94f5d)
  • Meaningful browser regression: The sign-in test holds the real request, checks the empty alert's accessible role and zero height, then verifies the same backend DOM node receives the refusal. (tests/browser/console-status-block.test.mjs:215, 3cc418f94f5d)
  • Scoped refusal focus: Share and Remove binding outcomes use the shared reveal helper, which preserves focus outside the sharing section. (apps/controller/src/console/agents/access.mjs:349, 3cc418f94f5d)
  • Maintainer approval and hosted validation: freeqaz-openai approved this exact head, reporting the regression's before/after result, inspection of all native media, and 375 + 385 hosted browser passes; REST check results independently show CI Required and both browser lanes succeeded. (3cc418f94f5d)
  • Visual evidence and inspection limit: Direct inspection of the four prepared screenshots confirms desktop and phone refusal styling and visibility. The capture revision has identical product source. Remaining media could not be inspected locally: video download failed with curl error 23, ffmpeg and ffprobe were unavailable, and web retrieval returned Cache miss; the maintainer's recorded inspection covers those attachments. (3cc418f94f5d)
  • Feature ownership history: Current-main history connects freeqaz to sharing, refusal recovery, design tokens and DOM patching, and kevinlin-openai to the earlier console DOM owner. Following dom.mjs reaches a missing creation-era object, so earlier introduction details remain unverified. (apps/controller/src/console/dom.mjs:44, dff2c59c4af2)

Likely related people:

  • kevinlin-openai: Raw commit 3397e47 adds apps/controller/src/console/dom.mjs:2 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: medium; commits: 3397e47740d2; files: apps/controller/src/console/dom.mjs)

Review metrics

Metric Value Why it matters
Change allocation Production +275/-119; browser tests +288/-11; workflow documentation +26; CI selection +5/-1 Growth is concentrated in a shared rendering owner and browser coverage for refusal visibility, contrast and live-region preservation.

Labels

Label changes:

  • add P2: Improves recoverable console refusals and accessibility with a limited UI blast radius.
  • add proof: sufficient: Contributor real behavior proof is sufficient.
  • add proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🦞 diamond lobster.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR.

Label justifications:

  • P2: Improves recoverable console refusals and accessibility with a limited UI blast radius.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR.
  • proof: sufficient: Contributor real behavior proof is sufficient.
  • proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence.

Rating scale

6/6 🦀 challenger crab · 5/6 🦞 diamond lobster · 4/6 🐚 platinum hermit · 3/6 🦐 gold shrimp · 2/6 🦪 silver shellfish · 1/6 🧂 unranked krab. Overall follows the weaker of proof and patch quality; ✨ marks media proof (a screenshot, video, or linked artifact) that directly shows the changed behavior.

Workflow

ClawSweeper edits this one comment on every review. Comment @clawsweeper re-review for a fresh review only. Repair and merge execution lanes are retired.

Reviewed October 19, 2026, 12:56 AM ET / 04:56 UTC.

@freeqaz
freeqaz merged commit 7474d8b into main Oct 19, 2026
53 of 56 checks passed
@freeqaz
freeqaz deleted the ux-c1 branch October 19, 2026 05:01
freeqaz added a commit that referenced this pull request Oct 19, 2026
…fts-kept

#2900 took console-drafts-kept from about 15 s to 34 s, and #2902 added
console-status-block (about 7 s) with no row, so it would start first.

This branch had an error being deployed

1 failed deployment
integration-qa-pr — 3cc418f9 Deployed Oct 19, 2026 by freeqaz via QA advisory (kubernetes) #2466
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants