Skip to content

feat(console): tabs with tablist semantics - #2878

Merged
freeqaz merged 17 commits into
mainfrom
ux-c7
Oct 18, 2026
Merged

freeqaz merged 17 commits into
mainfrom
ux-c7

Conversation

@freeqaz

@freeqaz freeqaz commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

UX plan C7. The Agent version tabs (.agent-tabs) were a nav of plain buttons: no tablist/tab/tabpanel semantics, each tab its own Tab stop (six or seven stops before the panel), no arrow keys, and on a phone the strip clipped with no sign that it scrolls (audit 2.9; design language "Tabs").

Change

agents/detail.mjs, in the tab-strip builder and the two places that already update the strip:

  • The strip is role="tablist" (same accessible name, "Agent configuration views"). Each tab is a role="tab" button with aria-selected, an id and aria-controls pointing at the tab content, which is now role="tabpanel" labelled by the selected tab. Ids carry a per-render crypto.randomUUID() (precedent: slack-approvers.mjs).
  • One Tab stop: the selected tab has tabindex=0, the others -1. Left/Right (wrapping) and Home/End move focus between enabled tabs without opening one; Enter or Space opens the focused tab, which still goes through the same click handler (draft guard, URL tab=, in-place switch). Once focus has left the list, the Tab stop goes back to the selected tab, so Shift+Tab comes back to it (a window blur keeps the arrowed-to stop). When Back/Forward selects another tab while focus is in the strip, focus follows the selection. A draft navigation block that disables tabs moves the stop to the selected one.
  • When a tab opens (click, Enter, Back/Forward), aria-selected, the Tab stop and the panel's label follow the URL's tab=. aria-current="page" is no longer set on tabs; aria-selected replaces it.
  • A narrow strip fades the edge that has more tabs past it (data-more-before/data-more-after, updated on scroll, after a tab switch and by the strip's existing ResizeObserver); a tab focused with the arrows scrolls clear of the fade (scroll-padding-inline). The strip's reveal keeps a focused tab in view (else the selected one), so a resize never scrolls keyboard focus off-screen.

console.css: the selected style keys on [aria-selected="true"] (same look; 1440 Storybook frame is pixel-identical); the edge fade is a mask built from --space-8 and an opaque token (a mask reads only alpha), with no new literals.

Docs and Storybook: the draft scenario (scenarios.mjs, Pages/Agent detail › First version) gains the keyboard steps. A new scripts/console-storybook/agent-tabs-keyboard-workflow.md walks the tablist (one Tab stop, arrows, Enter, Back, phone fade). focus-and-disabled-workflow.md step 2 now arrows through the tabs instead of tabbing through each one. docs/flows/platform-console.md section 7 notes the single Tab stop and arrow keys. That is word-neutral: 2495 to 2498 on the real counter, cap 2500. It goes in section 7, not the tab-reveal paragraph, because #906 rewrites that paragraph and editing it would add a conflict hunk.

Tests: 18 browser files and tests/integration/postgres-github-sign-in.test.mjs addressed the tabs as getByRole("button", { name: "Logs" }) etc. or via aria-current; they now use getByRole("tab", …) / aria-selected. Mechanical, no behaviour change.

Verification

  • New test in tests/browser/console-agent-detail-2.test.mjs (already in a browser suite), "Agent tabs are a tablist with arrow keys, one Tab stop and the URL's selection":
    • the tablist exposes six tabs, aria-selected and the single Tab stop match tab=plugins, and every tab controls the panel;
    • Right, Left (wrapping), End and Home move focus and the Tab stop only, while selection and URL stay put;
    • Tab leaves the list, and Shift+Tab returns to the selected tab;
    • Enter opens Channels: the URL becomes tab=channels, and selection, Tab stop and panel label follow;
    • Back returns aria-selected, the panel label, the Tab stop and focus to Plugins;
    • at 390 px, End + Enter opens Workspace files and Back returns to Configuration with focus on it and in view; an arrowed-to unselected tab stays in view through a resize to 360;
    • at 390 px the strip fades its right edge from first paint, and only its left edge once scrolled to the end; at 1440 it is not masked.
  • Fails on main (no tablist). It also kills six mutants: no focus-out reset, no Tab-stop update on activation, no focus-follow on history, a reveal that ignores the focused tab, and no fade update from the ResizeObserver. Pinned 20/20 at 2 CPUs after each round of fixes. The existing "phone-width routes, resizing and history keep the selected Agent tab visible" test also passes 20/20.
  • All 24 tests/browser/console*.test.mjs files passed: 605/605 before the main merge, and 611/612 on the review-fix product code, then 615/615, 616/616, 617/617 and 629/629 after merging main (refactor(console): one focus ring and one disabled style #2876; feat(console): responsive table rows for the Agents list #2877 and fix(console): Create Agent harness outcome copy and workspace missing-file copy #2875; feat(console): dialog and drawer pattern #2879; fix(console): offer Deploy again only after the Configuration read succeeds #2883-test(console): cover overlapping legacy token aliases #2885). The one failure was the new test's own Back expectation, which then changed to match the kept focus stop and passes 20/20. The PostgreSQL sign-in integration test change is a one-line role swap; it was not run locally because it needs the postgres profile.
  • Internal review: changes needed, then fixed. It caught the integration test's stale button query. It also flagged focus-out on window blur, the stop during an in-place switch, the stop on a tab disabled by the navigation block, the fade refresh after a switch, and the fade over a focused edge tab. All are fixed here. Two optional test gaps are not covered: arrows during the navigation block, and the revision view's tab set.
  • ClawSweeper (needs changes, 3 items), all addressed:
    • Focused tab off-screen after Back/resize: focus now follows a history selection, and the reveal keeps a focused tab in view. Both are tested.
    • Storybook scenario: steps added, plus the keyboard workflow file.
    • Flow doc: the section 7 note, as above.
  • prettier and eslint clean on touched files.

Media (current head; media commit e8e313e99893ebe07fc235df316cc2e2e3f53590, removed in the next commit):

Tab strip at 390, main vs this PR, scrolled to the start and to the end

Phone keyboard and history walkthrough (webm, 390 then 360). The steps and the state checked after each:

  • Right, Right, Right, End, Home, End: focus is on Workspace files, Configuration is still selected, and the focused tab is in view.
  • Enter: Workspace files opens (tab=workspace).
  • Back: Configuration is selected again, and focus follows it, in view.
  • End, then resize to 360: Workspace files keeps focus and stays in view.
  • Tab, then Shift+Tab: focus returns to the selected Configuration tab.

The Storybook "Pages/Agent detail › First version" frame at 1440 is sha256-identical before and after; at 390 only the edge fade differs.

Open PRs (3-way merge, conflict files/hunks with origin/main vs with this head):

PR with origin/main with this PR new hunks
#115 4 files / 9 hunks (css 0, suite json 0) 4 files / 9 hunks (css 0, suite json 0) none
#1619 8 files / 17 hunks (css 1, suite json 0) 8 files / 17 hunks (css 1, suite json 0) none
#2253 2 files / 3 hunks (css 0, suite json 0) 2 files / 3 hunks (css 0, suite json 0) none
#2255 3 files / 5 hunks (css 0, suite json 0) 3 files / 5 hunks (css 0, suite json 0) none
#2258 1 files / 3 hunks (css 0, suite json 0) 1 files / 3 hunks (css 0, suite json 0) none
#2268 9 files / 18 hunks (css 0, suite json 0) 9 files / 18 hunks (css 0, suite json 0) none
#2409 6 files / 15 hunks (css 0, suite json 0) 6 files / 15 hunks (css 0, suite json 0) none
#2445 4 files / 4 hunks (css 0, suite json 0) 4 files / 4 hunks (css 0, suite json 0) none
#2487 11 files / 18 hunks (css 0, suite json 0) 11 files / 18 hunks (css 0, suite json 0) none
#2502 3 files / 6 hunks (css 0, suite json 0) 3 files / 6 hunks (css 0, suite json 0) none
#2505 1 files / 2 hunks (css 0, suite json 0) 1 files / 2 hunks (css 0, suite json 0) none
#2518 6 files / 11 hunks (css 0, suite json 0) 6 files / 11 hunks (css 0, suite json 0) none
#2524 3 files / 4 hunks (css 0, suite json 0) 3 files / 4 hunks (css 0, suite json 0) none
#2533 27 files / 64 hunks (css 0, suite json 0) 27 files / 64 hunks (css 0, suite json 0) none
#2543 27 files / 68 hunks (css 0, suite json 0) 27 files / 68 hunks (css 0, suite json 0) none
#2596 0 files / 0 hunks (css 0, suite json 0) 0 files / 0 hunks (css 0, suite json 0) none
#2599 19 files / 33 hunks (css 0, suite json 0) 19 files / 33 hunks (css 0, suite json 0) none
#2826 0 files / 0 hunks (css 0, suite json 0) 0 files / 0 hunks (css 0, suite json 0) none
#2833 0 files / 0 hunks (css 0, suite json 0) 0 files / 0 hunks (css 0, suite json 0) none
#2860 0 files / 0 hunks (css 0, suite json 0) 0 files / 0 hunks (css 0, suite json 0) none
#2883 0 files / 0 hunks (css 0, suite json 0) 0 files / 0 hunks (css 0, suite json 0) none
#878 2 files / 2 hunks (css 0, suite json 0) 2 files / 2 hunks (css 0, suite json 0) none
#906 2 files / 4 hunks (css 0, suite json 0) 2 files / 4 hunks (css 0, suite json 0) none

No new conflict hunk. #2877 (C5) has landed and is merged in. One semantic follow-up: #2445 adds a "Policy tester" tab through the same builder and clicks it with getByRole("button", { name: "Policy tester" }) (2 lines). After this lands, those two lines need "tab". #2599 ("Policy denials") and #2518 ("Network") add tabs too, but their tests don't address them by role.

Risks

Low to medium. Keyboard and screen-reader behaviour of the tab strip changes on purpose; pointer behaviour is unchanged. Tests or tools outside this repo that find these tabs by button role or aria-current need the tab role / aria-selected.

The Agent version tabs are a tablist: role=tab buttons with
aria-selected and aria-controls, a tabpanel, one Tab stop on the selected
tab, Left/Right/Home/End to move focus, Enter or Space to open. An edge
with more tabs past it fades on a narrow screen. Tests address the tabs
by their tab role. UX plan C7.
Left/Right/Home/End move focus without opening a tab, Tab leaves the
list and Shift+Tab returns to the selected tab, Enter opens the focused
tab, aria-selected follows tab= (Back included), and the phone strip
fades the edge with more tabs from its first paint.
Tab names held in variables, the logs strip lookup (closest nav) and two
aria-current checks now use the tab role and aria-selected.
@clawsweeper

clawsweeper Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

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

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@freeqaz
freeqaz requested a review from a team as a code owner October 18, 2026 21:57
The sign-in integration test finds the Configuration tab by its tab role.
The focus-out reset checks where focus went once it moved, so a window
blur keeps the arrowed-to stop; a tab switch keeps the stop on a tab
that has focus; a draft navigation block moves the stop off tabs it
disables; the fade refreshes after a switch and a focused tab scrolls
clear of the faded edge. The test checks the panel label and the stop
after Back.
@clawsweeper clawsweeper Bot added the clawsweeper:intake:feature Preliminary submission classification; full source review pending. label Oct 18, 2026
@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed clawsweeper:intake:feature Preliminary submission classification; full source review pending. labels Oct 18, 2026
@clawsweeper

clawsweeper Bot commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge.

What this changes

This PR adds accessible tab semantics, keyboard navigation, and overflow fades to Agent configuration tabs.

Example: With Plugins selected, press Right and then Enter.

  • Before: Right does not navigate between the tab buttons.
  • After: Right focuses Channels without changing selection; Enter opens Channels.

Review scores

Measure Result What it means
Overall readiness 🦪 silver shellfish (2/6) Useful, narrowly owned accessibility work with meaningful browser coverage, an offscreen-focus defect, and incomplete final UI evidence.
Proof confidence 🦪 silver shellfish (2/6) Real behavior proof is not required for maintainer- or bot-authored pull requests.
Patch quality 🦐 gold shrimp (3/6) 3 actionable review findings remain.

Product

Kind: Feature · Worth it: Yes · Fix scope: Complete
User problem: Keyboard users must traverse every Agent tab separately, assistive technology receives no tab semantics, and phone users lack an overflow cue.
Reason: The collaborator-authored direction improves accessibility through the existing navigation owner without adding configuration or changing authorization.

Merge readiness

⛔ Needs changes before merge - 3 items remain

Keep open: this maintainer-directed accessibility improvement is useful, but narrow-screen navigation can leave keyboard focus offscreen.

Priority: P2
Reviewed head: 20200716824029a6d26ef347426e338bd04cadeb

Before merge

  • Keep the focused tab visible when selection changes (P2) - At 390 px, open Configuration, activate Workspace files, then navigate Back while Workspace files retains focus. This code deliberately preserves that focused tab and its Tab stop, but revealSelectedTab immediately scrolls left to Configuration, leaving keyboard focus offscreen. Resizing an overflowing strip after arrowing to an unselected tab has the same problem. Make horizontal reveal prioritize a focused tab while focus remains in the list, and cover this combined focus/history/overflow scenario.
  • Add the changed keyboard workflow to the existing Storybook scenario (P3) - This adds a new interaction model, but no scripts/console-storybook file changes. The existing draft scenario only instructs reviewers to resize the preparation steps; it does not expose the arrow-key, activation, focus-return, or tab-overflow workflow. AGENTS.md explicitly requires matching scenario and workflow updates for console changes. Extend the existing Agent detail scenarios with those actions and expected results.
  • Document the new focus and selection transitions in the owning flow (P3) - The PR introduces separate focused and selected tab state, focus-out reset behavior, and keyboard navigation, but leaves the owning flow unchanged. AGENTS.md and local-dev require a source-backed flow update for new runtime paths and state transitions. Extend docs/flows/platform-console.md beside its existing tab-navigation explanation with the keyboard, activation, navigation-block, and history transitions.

Findings

  • [P2] Keep the focused tab visible when selection changes — apps/controller/src/console/agents/detail.mjs:3038-3046
  • [P3] Add the changed keyboard workflow to the existing Storybook scenario — apps/controller/src/console/agents/detail.mjs:1448-1456
  • [P3] Document the new focus and selection transitions in the owning flow — apps/controller/src/console/agents/detail.mjs:1467-1476

Tests

  • Missing end-to-end proof: Supply current-revision UI media showing keyboard activation, Back/Forward, and narrow-strip focus visibility; the existing regression checks focus and overflow separately.
Agent review details

How this fits together

The console's Agent detail renderer translates tab clicks, keyboard input, and URL history into selected content while preserving the surrounding page and draft guards.

flowchart TD
  A[Keyboard or pointer input] --> B[Agent tab strip]
  B --> C[Existing navigation guard]
  C --> D[URL tab selection]
  E[Browser history] --> D
  D --> F[Tab content renderer]
  F --> B
Loading

Technical review

Best possible solution:

Keep the existing tab owner, make horizontal reveal honor keyboard focus, and complete the required Storybook and flow updates.

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

At 390 px, open Configuration, activate Workspace files, and navigate Back while focus remains on Workspace files; renderTab keeps that focus but scrolls Configuration into view, hiding the focused control.

Is this the best way to solve the issue?

Extending the existing builder is appropriate; its horizontal reveal logic must account for the newly separate focused and selected tabs.

Full review comments:

  • [P2] Keep the focused tab visible when selection changes — apps/controller/src/console/agents/detail.mjs:3038-3046
    At 390 px, open Configuration, activate Workspace files, then navigate Back while Workspace files retains focus. This code deliberately preserves that focused tab and its Tab stop, but revealSelectedTab immediately scrolls left to Configuration, leaving keyboard focus offscreen. Resizing an overflowing strip after arrowing to an unselected tab has the same problem. Make horizontal reveal prioritize a focused tab while focus remains in the list, and cover this combined focus/history/overflow scenario.
    Confidence: 0.96
  • [P3] Add the changed keyboard workflow to the existing Storybook scenario — apps/controller/src/console/agents/detail.mjs:1448-1456
    This adds a new interaction model, but no scripts/console-storybook file changes. The existing draft scenario only instructs reviewers to resize the preparation steps; it does not expose the arrow-key, activation, focus-return, or tab-overflow workflow. AGENTS.md explicitly requires matching scenario and workflow updates for console changes. Extend the existing Agent detail scenarios with those actions and expected results.
    Confidence: 0.99
  • [P3] Document the new focus and selection transitions in the owning flow — apps/controller/src/console/agents/detail.mjs:1467-1476
    The PR introduces separate focused and selected tab state, focus-out reset behavior, and keyboard navigation, but leaves the owning flow unchanged. AGENTS.md and local-dev require a source-backed flow update for new runtime paths and state transitions. Extend docs/flows/platform-console.md beside its existing tab-navigation explanation with the keyboard, activation, navigation-block, and history transitions.
    Confidence: 0.97

Overall correctness: patch is incorrect
Overall confidence: 0.94

AGENTS.md: found and applied where relevant.

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

Provenance checked

  • Agent tab construction and semantics changes intended behavior with a stated reason (835b1cc: feat: complete console Agent setup with credentials and workspace files #58 split Agent setup into focused console views; the current PR explicitly explains replacing the original plain-button semantics.)
  • In-place tab navigation keeps the original intent (5129b17: Preserve surrounding panels, URL history, drafts, and late-response protection while switching content.)
  • Horizontal tab reveal keeps the original intent (05e2093: Keep the selected tab visible across viewport changes, history navigation, and retained-view restoration.)

Testing

Proof path: in-process harness.

Security

None.

Evidence

Acceptance criteria:

  • node --test tests/browser/console-agent-detail-2.test.mjs tests/browser/console-agent-logs.test.mjs
  • pnpm lint
  • pnpm format:check
  • pnpm check:workspace
  • pnpm docs:check

What I checked:

Likely related people:

  • Free Wortley: Raw commit 354e94b adds apps/controller/src/console/console.css:561 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: high; commits: 354e94b12a4b; files: apps/controller/src/console/console.css)

Review metrics

Metric Value Why it matters
Production and test changes Production +84/-8; tests +250/-121 The implementation remains in two existing files; test growth combines one keyboard regression with necessary selector updates.

Labels

Label changes:

  • add P2: Useful accessibility work with a bounded keyboard-focus defect on overflowing tab strips.
  • add rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🦐 gold shrimp.
  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action.

Label justifications:

  • P2: Useful accessibility work with a bounded keyboard-focus defect on overflowing tab strips.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action.

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 18, 2026, 6:35 PM ET / 22:35 UTC.

A tab focused with the arrows stays in view when Back moves the
selection elsewhere or the strip resizes. The draft Storybook scenario
gains the keyboard steps, and the console flow states the tablist
behaviour (word-neutral). The test covers Back and resize at 390/360.
…agraph

#906 rewrites the tab-reveal paragraph, so editing it there would add a
conflict hunk. The focus section names the single Tab stop and arrow
keys instead (word count 2495 to 2498).
Back or Forward that selects another tab while focus is in the strip
moves focus to the selected tab, so the revealed tab and the focused
one agree and the existing phone-width history test keeps the selected
tab visible. A tab focused with the arrows still stays in view through
a resize.
A workflow for the tablist (one Tab stop, arrows, Enter, Back, phone
fade), and the focus-ring walkthrough now arrows through the tabs
instead of tabbing through each one.
@freeqaz
freeqaz temporarily deployed to integration-qa-pr October 18, 2026 23:43 — with GitHub Actions Inactive
@freeqaz
freeqaz temporarily deployed to integration-qa-pr October 18, 2026 23:43 — with GitHub Actions Inactive
@freeqaz
freeqaz merged commit 4e45875 into main Oct 18, 2026
44 of 46 checks passed
@freeqaz
freeqaz deleted the ux-c7 branch October 18, 2026 23:57

This branch was previously deployed

1 inactive deployment
integration-qa-pr — bc4ef729 Deployed Oct 18, 2026 by freeqaz via QA advisory (compose) #2386
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. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant