Skip to content

fix(console): keep the focus ring visible inside scrolling lists and tabs - #2891

Merged
freeqaz-openai merged 6 commits into
mainfrom
ux-fixes-123-focus
Oct 19, 2026
Merged

freeqaz-openai merged 6 commits into
mainfrom
ux-fixes-123-focus

Conversation

@freeqaz

@freeqaz freeqaz commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

The one-ring rule from #2876 draws the focus ring 5 px outside a control (2 px offset plus 3 px width). Scrolling containers that the .focus-inset opt-in does not cover clip that ring (bughunt-123 finding 1952, UX plan B4 follow-up):

  • Version picker (.version-list, overflow-y: auto): a focused version showed no ring at all.
  • Agent tabs (.agent-tabs, overflow-x: auto): the top and bottom of the ring were cut off on every tab, leaving a 3 px side bar. At 390 px, arrowing to a partly visible tab (Workspace files) left it, and its ring, cut off under the faded edge: focus() does not scroll a partly visible element.
  • Log rows at 390 px: keyboard scrolling put the focused summary flush with the .log-pane edge, cutting 5 px off the bottom of the ring.
  • Configure plugins: .plugin-list cut 1 px from each side of every item and up to 5 px from the bottom; .plugin-browser (4 px padding) cut 1 px from the search field and its buttons.

console-focus-ring only compared the computed outline, so it passed while the version ring was invisible. Four rule changes survived it (finding 1953): dropping the Slack directory wrapper rule (that input then has no ring at all), dropping the repository-row rule, setting the inset offset to +3 px, and reverting the disabled cursor to wait.

Change

  • console.css: version items, Agent tabs and plugin list items join the inset-ring list (--focus-offset-inset), the documented pattern for list items inside a scroller. Log row summaries get scroll-margin-block: var(--space-2), so keyboard scrolling stops short of the pane edge and the outer ring stays visible. .plugin-browser padding goes from 4 px to --space-2 (8 px), which leaves room for the outer ring.

  • console-focus-ring.test.mjs: every stop must draw one of the two token rings (outer or inset, both resolved from the tokens), and no scrolling ancestor may hide any part of it. The test compares the ring box (border box plus offset plus width) with the padding box of every overflow ancestor. The ring may belong to the control or to a wrapper up to two levels up. It walks:

    • the Agents list, Agent detail with a deployed version (version picker and tabs), Agent logs with 40 rows, and Create Agent, at 1440 and 390 px;

    • Create Agent repository rows and the Slack people search, at 1440 and 390 px;

    • the Configure plugins dialog, opened from the keyboard, at 1440 and 390 px.

    • every Agent tab at 390 px, reached with the arrow keys: each ring is unclipped and each tab clears the faded edges.

    The clip box is the scroller's client box (inside borders and scrollbars). The logs walk must reach a log row summary. It also asserts that a focused repository checkbox's row draws the inset ring, that the inset token sits inside the edge (offset plus width at most 0; checked by property, not exact values), and that a permanently disabled button (Load plugins with an API key) shows not-allowed at 0.6 opacity.

  • agents/detail.mjs: an arrowed-to tab is focused with preventScroll and then scrolled into view with scrollIntoView({ block: "nearest", inline: "nearest" }), which honours the strip's scroll-padding-inline so the tab clears the 32 px fade.

  • focus-and-disabled-workflow.md: Storybook steps for the version list, tabs and plugin items.

Verification

  • The new test fails on main (4e45875 CSS): .version-list hides 5.0px (View version v1), .agent-tabs hides 5.0px (Configuration), .plugin-list hides 1.0-5.4px (every plugin), .plugin-browser hides 1.0px (Search plugins).

  • Mutants, each killed by console-focus-ring alone:

    Mutant Result
    arrowed-to tab focused without scrolling it into view (detail.mjs) killed (agent-tabs hides 80px)
    drop the tab strip's scroll-padding-inline killed (tab under a faded edge)
    drop .version-item from the inset list killed (version-list hides 5.0px)
    drop .agent-tabs button from the inset list killed (agent-tabs hides 5.0px)
    drop .plugin-list-item from the inset list killed
    drop the log summary scroll-margin-block killed (logs at 390: log-pane hides 5.1-5.3px)
    .plugin-browser padding back to 4 px (desktop / phone) killed / killed
    drop .log-pane from the inset list killed
    drop .slack-directory-input:has(input:focus-visible) (1953) killed
    drop .repository-result:has(input:focus-visible) (1953) killed
    --focus-offset-inset: 3px (1953) killed
    button:disabled { cursor: wait } (1953) killed
    drop .repository-option:has(:focus-visible) survives (not visited; that rule is outside this PR's findings)
  • console-focus-ring passes 20/20 at 2 CPUs (taskset -c 26,27), on 095b0be and again on be7ba5c (5 tests). Full files green: deploy-focus 1, deploy-again 9, plugin-approvers 6, overview 41, repositories 20, logs 49, dialogs 1, agent-detail 36, agent-detail-2 62, codex-oauth 3, presets 32, draft-guards 25, retained-revalidation 65.

  • After be7ba5c (internal review fixes): agent-detail-2 62/62, agent-detail 36/36, logs 49/49, overview 41/41.

  • eslint and prettier --check are clean on the touched files.

  • Storybook (simulated UI proof, prepared assets from this branch, main CSS vs this CSS): Pages / Agent detail / Long version history, Pages / Agent detail / Draft, Components / Dialogs / Plugins (wide) and Plugins (phone). Before/after screenshots and a keyboard walkthrough are below.

  • Not visited by the test: the plain-text log rows. They take no focus.

Verification: keyboard focus before and after

These are Storybook captures at 1440 px and 390 px, not a live customer session. The capture commit (b915eb3411) and the reviewed head (095b0bee57) have identical source trees apart from the media files themselves. The video follows keyboard focus through the version list and Configure plugins dialog; the browser regression test separately covers scrolling log rows.

be7ba5c, after these captures, adds the arrow-key scroll in detail.mjs and test-only review fixes; the captured CSS states are unchanged.

State Before (main) After
Version picker (Long version history), 1440 before after
Version picker, 390 before after
Selected Agent tab (Draft), 1440 before after
Selected Agent tab, 390 before after
Plugin list item (Plugins wide / phone), 1440 before after
Plugin list item, 390 before after
Search plugins field, 1440 before after
Search plugins field, 390 before after
keyboard-walkthrough.webm

Open PRs touching the same files (merge-tree, conflicted files / hunks)

PR with origin/main with this PR new hunks
#2599 19 files / 33 hunks 19 files / 33 hunks none
#2596 0 files / 0 hunks 0 files / 0 hunks none
#2524 3 files / 4 hunks 3 files / 4 hunks none
#2445 4 files / 4 hunks 4 files / 4 hunks none
#1619 8 files / 17 hunks 8 files / 17 hunks none
#906 2 files / 4 hunks 2 files / 4 hunks none

Re-checked on be7ba5c (now also touching detail.mjs): #2886, #2599, #2596, #2524, #2518, #2445, #1619, #2892 and #2893 have identical counts with main and with this PR. #906 has 5 hunks with current main and 4 with this PR, because main moved since this branch's base; there are no new hunks.

Risks

Low: CSS, plus one keyboard handler in detail.mjs that now scrolls the arrowed-to tab into view. A focused version, tab or plugin item now shows its ring just inside its edge instead of outside it. The plugin browser column gains 4 px of padding on each side. No copy or behaviour changes.

…tabs

Version picker items, Agent tabs and Configure plugins list items now draw
the inset ring: their scrollers clipped the outer ring (the focused version
showed none). Log row summaries keep a scroll margin so keyboard scrolling
stops short of the pane edge, and the plugin browser pads 8 px so the search
field and buttons keep their ring.

The focus-ring test now walks the version picker, the logs at 390 px, the
plugins dialog, repository rows and the Slack people search at 1440 and
390 px, and fails when a scroller hides any part of a ring. It also pins the
inset offset, the repository row frame and the disabled cursor (findings
1952, 1953).
@freeqaz
freeqaz requested a review from a team as a code owner October 19, 2026 00:20
@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 complete

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

View the workflow run.

@freeqaz
freeqaz temporarily deployed to integration-qa-pr October 19, 2026 00:21 — with GitHub Actions Inactive
@freeqaz
freeqaz temporarily deployed to integration-qa-pr October 19, 2026 00:21 — with GitHub Actions Inactive
@clawsweeper clawsweeper Bot added clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-environment 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: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-environment 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 keeps keyboard focus rings visible in scrolling console controls and scrolls arrow-focused Agent tabs clear of faded edges.

Example: At a 390 px viewport, use arrow keys to focus Workspace files in the Agent tab strip.

  • Before: The partly visible tab and its focus ring can remain clipped beneath the faded edge.
  • After: The tab scrolls into view and draws its focus ring inside its bounds.

Review scores

Measure Result What it means
Overall readiness 🦞 diamond lobster (5/6) A small, useful repair supported by directly inspected UI evidence, discriminating browser coverage, and successful current-head required checks.
Proof confidence 🦞 diamond lobster (5/6) The ordinary contributor proof gate does not apply to this maintainer-authored PR; inspected Storybook screenshots demonstrate the CSS repair, and current-head Console browser checks exercise the later arrow-scroll behavior through the actual frontend.
Patch quality 🦞 diamond lobster (5/6) No actionable review findings were identified.

Product

Kind: Bug fix · Worth it: Yes · Fix scope: Complete
User problem: Keyboard users lose a complete focus indicator when navigating scrolling version lists, tabs, logs, and plugin controls.
Reason: The maintainer-authored direction repairs observable accessibility failures through existing owners with a small production change.

Merge readiness

✅ Ready for maintainer review

This PR is a focused accessibility repair with no actionable findings; current-head checks resolve the previous CI blocker, and repository policy reserves landing for maintainers.

Priority: P2
Reviewed head: 14b3b8e146af94b7155e131d49360e92f72cdf16

Before merge

None.

Findings

None.

Agent review details

How this fits together

The controller serves the console frontend; keyboard events enter its existing tab handler, while shared CSS controls focus outlines and scrolling geometry.

flowchart TB
  Keyboard[Keyboard navigation] --> Controls[Console controls]
  Controls --> Handler[Existing tab handler]
  Handler --> Scroll[Scroll focused tab into view]
  Controls --> Styles[Shared focus ring styles]
  Styles --> Visible[Visible focus indicator]
  Scroll --> Visible
Loading

Technical review

Best possible solution:

Land the focused repair through the existing shared focus styles and Agent tab handler.

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

Current main places outer outlines against clipping scrollers and focuses partly visible tabs without explicit scrolling; supplied before images and recorded base-versus-head browser results corroborate the failure.

Is this the best way to solve the issue?

The repair uses the existing inset-ring tokens and scrolling owner, while the strengthened browser tests detect geometry failures that computed-style comparisons missed.

AGENTS.md: found and applied where relevant.

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

Provenance checked

  • Shared focus rings and existing browser coverage keeps the original intent (d2de38d: Unify focus geometry around shared outer and inset tokens, with wrappers drawing rings for borderless children.)
  • Arrow-key tab focus keeps the original intent (4e45875: Provide one tab stop, manual keyboard activation, and focused tabs that remain visible outside faded edges.)
  • Plugin-browser padding changes intended behavior with a stated reason (fbbc0e4: Provide a scrollable plugin catalog and configuration workspace; no specific rationale for the four-pixel padding was found.)

Testing

Proof path: shipped entry point.

Security

None.

Evidence

What I checked:

Likely related people:

  • Free Wortley: Raw commit 4e45875 adds apps/controller/src/console/agents/detail.mjs:1448 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: high; commits: 4e4587568f1b; files: apps/controller/src/console/agents/detail.mjs)
  • stevenlee-oai: Raw commit fbbc0e4 adds apps/controller/src/console/console.css:883 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: high; commits: fbbc0e42aa89; files: apps/controller/src/console/console.css)

Review metrics

Metric Value Why it matters
Production versus browser-test changes Production +15/-5 lines; browser tests +341/-41 lines The larger test change adds distinct clipping, wrapper-ring, phone-tab, and disabled-control checks without introducing production test seams.

Labels

Label changes:

  • add proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence.
  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR.
  • remove rating: 🦐 gold shrimp: Current PR rating is rating: 🦞 diamond lobster, so this older rating label is no longer current.
  • remove status: ⏳ waiting on author: Current PR status label is status: 👀 ready for maintainer look.

Label justifications:

  • P2: Clipped focus indicators impair keyboard navigation across several console controls, with a bounded UI repair.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR.
  • 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.

History

Review history (2 earlier review cycles)
  • reviewed 2026-10-19T00:35:54.789Z sha 095b0be :: needs changes before merge. :: none
  • reviewed 2026-10-19T00:54:17.400Z sha be7ba5c :: needs changes before merge. :: none

Reviewed October 18, 2026, 9:05 PM ET / October 19, 2026, 01:05 UTC (Revision 3).

focus() left a partly visible tab where it was, so at 390 px the tab and its
ring sat cut off under the strip's fade. Scroll it into view instead, which
honours the strip's scroll padding.

The focus-ring test arrows through every tab at 390 px and checks each ring
and the fades. Review fixes: clip against the scroller's client box (inside
scrollbars), check the token rings by property rather than exact values,
require a log row summary stop, and use 20 log lines.
@freeqaz-openai

Copy link
Copy Markdown
Collaborator

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper clawsweeper Bot added clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-environment Preliminary submission classification; full source review pending. clawsweeper:intake:needs-repro Preliminary submission classification; full source review pending. labels Oct 19, 2026

@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. I reviewed be7ba5c8e, including the arrow-key follow-up, and found no blocking issue. This is an agent review, not a claim of human approval.

  • At 390 px and 1440 px I measured the old focus outline clipped by 5 px on versions and tabs, and by 1 px on plugin items. The same browser and CSS check on the new inset rings measured no clipping. This check used minimal markup and the real stylesheets.
  • Separately, with the actual Console and its browser fixture, I put the previous tab-focus handler under the new 390 px arrow-key regression. It failed on 80 px and 24 px of clipping. Restoring the reviewed handler, the whole owning focus file passed: 5 tests, none skipped. Genuine source and dependency inputs; local fixture, not a claim about a live installation or a particular screen reader.
  • I inspected the 16 screenshots and decoded the 8.24-second walkthrough, attached the originals in Verification, and retrieved every native attachment to check its exact bytes. The capture source matches the first reviewed head apart from those media files. As the body now correctly says, they show the CSS fix; the later arrow-key correction is evidenced by the discriminating browser test, not retroactively by that video.

No rebase, source update, or CI rerun requested. The first hosted re-review was explicitly superseded during the head/body update, so its earlier media-only request should not be mistaken for a verdict on this final tuple.

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed clawsweeper:intake:needs-repro Preliminary submission classification; full source review pending. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. proof: sufficient Contributor real behavior proof is sufficient. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-environment Preliminary submission classification; full source review pending. labels Oct 19, 2026
@freeqaz-openai

Copy link
Copy Markdown
Collaborator

Free's clanker here, with the exact answer to Claw’s remaining “inspect the failed documentation step” item. This PR did not cause the failure. I did, when I landed #2826. Sorry for breaking your build.

The old Required check remains correctly red; please use the normal protected integration on the repaired base. I did not override a check, re-request the failed run, or push to your branch.

@freeqaz
freeqaz temporarily deployed to integration-qa-pr October 19, 2026 00:58 — with GitHub Actions Inactive
@freeqaz
freeqaz temporarily deployed to integration-qa-pr October 19, 2026 00:58 — with GitHub Actions Inactive
@freeqaz-openai

Copy link
Copy Markdown
Collaborator

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper clawsweeper Bot added clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-environment Preliminary submission classification; full source review pending. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-environment Preliminary submission classification; full source review pending. labels Oct 19, 2026
@freeqaz-openai
freeqaz-openai merged commit 908a64d into main Oct 19, 2026
54 of 55 checks passed
@freeqaz-openai
freeqaz-openai deleted the ux-fixes-123-focus branch October 19, 2026 01:12

This branch was previously deployed

1 inactive deployment
integration-qa-pr — 14b3b8e1 Deployed Oct 19, 2026 by freeqaz via QA advisory (kubernetes) #2414
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. rating: 🦞 diamond lobster Very strong PR readiness with only minor 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