Repository navigation
fix(console): keep the versions list one tab stop - #3023
Conversation
Show older versions moves out of the Versions toolbar so the arrows stay on versions and Tab reaches it next; it keeps focus while loading and hands it to the first older version. A rail rebuild (a poll landing an outcome) keeps focus on the same control. Tests assert both and a Queued latest outcome (finding 2115, #3006).
|
🦞👀 Pull request received. I will update this pull request when review starts. ClawSweeper review completeClawSweeper finished reviewing this revision. The review result is being finalized. |
|
Codex review: needs maintainer review before merge. What this changesThis PR separates pagination from the Versions toolbar and preserves visible keyboard focus during loading and list rebuilds. Example: Open v1 while the first history page lists only v3, then load the final page containing v2 and v1.
Review scores
ProductKind: Bug fix · Worth it: Yes · Fix scope: Complete Merge readiness✅ Ready for maintainer review Keep this PR open: the focused accessibility repair is useful, and the previously reported findings are addressed without a new concrete correctness defect. Priority: P2 Before mergeNone. FindingsNone. Tests
Agent review detailsHow this fits togetherThe console Agent detail page combines revision summaries and deployment polling into version controls; keyboard input selects versions or requests older pages. flowchart LR
A[Revision API] --> C[Agent detail page]
B[Deployment polling] --> C
C --> D[Versions toolbar]
C --> E[Older versions button]
D --> F[Keyboard focus]
E --> F
Technical reviewBest possible solution: Land the focused repair after the required visual walkthrough demonstrates the final keyboard behavior. Do we have a high-confidence way to reproduce the issue? Main places an independently tabbable pagination button inside the roving toolbar and replaces focused controls without restoration; source inspection and the supplied browser comparisons establish both paths, without a reviewer-run reproduction. Is this the best way to solve the issue? The repair uses the existing renderer and roving-focus owner, preserves the intended keyboard contract, and addresses the earlier scrolling, Storybook, and page-boundary findings. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning medium; reviewed against 084d0f73f19f. Provenance checked
TestingProof path: in-process harness. SecurityNone. EvidenceWhat I checked:
Likely related people:
LabelsLabel changes:
Label justifications:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale6/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. WorkflowClawSweeper edits this one comment on every review. Comment HistoryReview history (2 earlier review cycles)
Reviewed October 20, 2026, 5:51 AM ET / 09:51 UTC (Revision 3). |
Review follow-ups for the Versions list (finding 2115): - a rebuild keeps the list's scroll position and scrolls the restored version into view - focus falls back to the last version (older pages) or the list's Tab stop (a version no longer listed) instead of dropping to the page - after older versions load, focus goes to the first one below those shown before, not a newer one a poll added meanwhile - the loading button looks disabled and busy; aria-disabled buttons get no hover highlight - Show older versions' bottom margin uses the spacing scale Tests assert the rebuild (old node disconnected), the kept scroll position, and a held older page: focus stays on the busy button and a second press sends no second request.
…ybook Long version history now loads each older page in 1.5 s and walks the keyboard through it: focus stays on the busy Show older versions, then moves to the first loaded version, in view. New version in progress, then current checks focus stays on v6 across the poll's rebuild. The focus workflow says the same. The paging test asserts the newly focused v1 is visible inside an overflowing list (finding 2115).
|
One keyboard edge case to fix before merge, reproduced on The separately rendered current version is being counted as the end of the previous page. I tested a small fix that records only the versions actually listed before the fetch, uses that boundary to place focus, and keeps one helper for the roving tab stop. The same browser assertion got no version focus on the base, v1 on this head, and the expected v2 after the fix. All three focused controller-backed browser cases passed locally; I did not rerun hosted CI. The earlier scrolling fix is worth keeping: on this head I also verified that a newly focused version actually scrolls into view in a short list viewport. |
|
@clawsweeper re-review |
|
🦞👀 Re-review progress:
|
…load A viewed version on no loaded page shows on its own below the list, and it counted as the end of the earlier pages, so loading the page that held it focused it again instead of the first newly listed version. The boundary now comes from the revisions the list actually showed. New test opens v1 with only v3 listed and asserts Show older versions hands visible focus to v2 (finding 2115).
|
Free's clanker here. Thanks for the reproduction. Fixed in e598a28: the end of the earlier pages now comes from the revisions the list actually showed. The viewed version that shows on its own below the list no longer counts, so focus goes to the newly listed v2. The new browser case, "Show older versions hands focus to the first version it lists", opens v1 while only v3 is listed, then presses Enter on Show older versions. It asserts that focus lands on v2 and that v2 is visible in an overflowing list. Results for the focused version: |
|
@clawsweeper re-review |
|
🦞👀 Re-review progress:
|
|
Verified the fix in I also tried three pages: focus stays on Show older versions while another page remains, and Tab can leave it. No further change requested for this finding. |
Brings in the 9 commits landed on main since the last sync (#3023..#3042). One textual conflict in docs/reference/controller.md: the worker.error entry keeps both main's claim-lost causes (#3034) and this branch's repository credential codes, followed by main's worker.claim-renewal-warning entry. Main added no migrations, so 0060-0068 keep their numbers.
Problem
Finding 2115 (bughunt on #3006): the Versions list was not quite one Tab stop.
role="toolbar"list after the roving versions and kepttabIndex 0. A paged list had two Tab stops, and Up/Down/Home/End never reached the button.renderVersions). A reader arrowing through the versions lost focus to<body>.Change
apps/controller/src/console/agents/detail.mjs,console.css:max-height), so it stays visible on a long page of versions. CSS keeps the same spacing: the list's 8 px gap above the button and the list's 22 px bottom margin below it. The before and after screenshots match.aria-disabledinstead ofdisabled, so focus stays on the pressed button, and the click handler already ignores a press while loading. When the last page loads and the button goes away, focus moves to the first version that just loaded.renderVersionsreplaces the list, focus goes back to the matching new control. A version gets the roving Tab stop, so the next Tab goes back to the same version.aria-busygives it the busy cursor. Buttons witharia-disabled="true"no longer get the hover highlight. The other one in the console is Refresh deployment while it waits; it only loses the hover.var(--space-6)(24 px) instead of 22 px. That is 2 px, not visible.Verification
Tests are in
tests/browser/console-agent-overview.test.mjs. Both are existing tests, extended:Queued/info) and its accessible name; this kills a surviving mutant that dropped QueuedResults:
Assertions added after internal review:
aria-disabled="true", and that a second Enter sends no second request (counted after the page's fetches settle).disabledwhile loadingClawSweeper follow-ups (verdict on
015e66eb9):scrollIntoViewfails it.focus-and-disabled-workflow.mdstep 3 says the same.Final-page boundary (freeqaz-openai's report and ClawSweeper's verdict on
35bcf3a41): the version you are viewing, when it is on no loaded page, shows on its own below the list. It was counted as the end of the earlier pages, so after Show older versions loaded the page holding it, focus went back to it. The boundary now comes from the revisions the list actually showed. The new test "Show older versions hands focus to the first version it lists" opens v1 with only v3 listed and asserts that visible focus goes to v2 in an overflowing list. Its focused version isnullon main,View version v1on35bcf3a41, andView version v2with the fix.Fails on main, passes with the fix: with origin/main's
detail.mjsandconsole.csschecked out under the new tests, both tests fail (actual: 1, expected: 0: the button is inside the toolbar;actual: null, expected: 'v2': focus dropped after the poll). Both pass with the fix.Pin: the two tests passed 20/20 at 2 CPUs (
taskset -c 6-7), 2 tests per run, on the first head with 1-min load under 40. On0b7cf124fthey passed 20/20 with load at 43 to 53. On35bcf3a41, which merges main again (fix(console): finish an agent share before leaving its page #3020, fix(console): stop a member's create from repeating refused reads #3021, Fix deletion recovery after an Agent status read #3022), they passed 20/20 at 2 CPUs. On the current heade598a2813, the three Versions tests passed 20/20 at 2 CPUs, 3 tests per run. Both of those pins checked load under 40 before every run.Full touched files green:
console-agent-overview,console-focus-ringandconsole-button-hierarchy: 63/63 before the latest main merge, 65/65 on35bcf3a41, 66/66 one598a2813.Mutation: 17 mutants of the Versions list code run against those three files; 16 are killed. Since bughunt-135 (10/12), these are newly killed:
queued_unmarkeddisabledwhile loadingdisabled_in_arrowssurvives. It is near-equivalent: versions are disabled only while a draft edit blocks navigation, and then all of them are.prettier check (verified it can fail) and eslint
--max-warnings 0pass on the touched files.Merge-tree, comparing conflicted files and conflict hunks against open PRs that touch these files (main vs this head):
This PR adds no new conflict hunk.
Screenshots: Versions list with older pages (paged), Tab from the newest version, before and after.
The layout is unchanged; the button is now outside
role="toolbar", after the scrolling list.Risks
Low. This is client-only console focus, scroll and DOM order. No API, permission or copy changes. The one global CSS change: a button with
aria-disabled="true"gets no hover highlight. Today that affects only Refresh deployment while it waits, and the Agents back button after it is pressed. Focus is restored only when it was inside the Versions list and the rebuild dropped it, so focus elsewhere on the page is never taken.