Skip to content

fix(dev-fleet): arrow-key navigation in row-actions menu (#5851) - #6226

Merged
dwu96 merged 1 commit into
mainfrom
fix/devfleet-menu-arrow-nav-5851
Aug 27, 2026
Merged

fix(dev-fleet): arrow-key navigation in row-actions menu (#5851)#6226
dwu96 merged 1 commit into
mainfrom
fix/devfleet-menu-arrow-nav-5851

Conversation

@chenmingwei23

Copy link
Copy Markdown
Contributor

What is the problem?

The Dev Fleet row-actions dropdown (MenuBtn in website/src/pages/DevFleetPage.tsx) announces role="menu", and that role promises ArrowDown/ArrowUp item navigation. PR #3525 added focus entry, Tab/Shift-Tab containment, and focus return, so the menu is usable via Tab -- but arrow keys did nothing.

Why this issue matters to the user

Screen-reader users hear "menu" and reach for arrow keys first; when arrows do nothing the menu appears broken even though Tab works. Keyboard users get the same dead keys, and an unhandled arrow scrolls the page behind the open menu.

How our fix solves it

The announced role and the handled keys now agree. In MenuBtn's open-effect key handler, ArrowDown/ArrowUp move focus through the same focusableItems() list the existing Tab containment uses (disabled items are already filtered out), wrapping at both ends; Home/End jump to the boundary items; preventDefault stops the page scrolling behind the menu. Three guards keep the new branch honest:

  • Modified arrows (Cmd/Ctrl/Alt) pass through untouched -- they are OS/browser shortcuts the menu pattern assigns no behavior to.
  • The shared IME latch (useDocumentImeLatch.claimKey) runs before preventDefault() and any focus move, so an arrow that belongs to a composition (candidate navigation) never moves menu focus -- the same claim contract as the existing Tab/Escape branches. The menu takes the stricter side than useListKeyboardNav (which lets composition arrows through) because the menu holds no editable target; the code comment records the divergence.
  • When focus is outside the item list (reachable: the fleet page polls, so a focused item can unmount mid-open), ArrowDown enters at the first item and ArrowUp at the last.

Tab containment, Escape, focus return, and outside-click behavior are unchanged. Scope note: this delivers arrow-key navigation; full menu-pattern semantics (menuitem child roles, roving tabindex) are a pre-existing gap in the shared Clickable component and are out of scope here.

What tests we did

  • 5 new tests in website/src/test/DevFleetPage.test.tsx: arrow cycling with wrap at both ends, Home/End jumps, IME-composition decline (focus stays put), outside-the-list entry (Down to first, Up to last), and modified-arrow passthrough.
  • Mutation-verified: deleting the arrow branch reds the cycling tests; deleting only the claimKey line reds the decline test.
  • Gates: npx tsc -b clean; full frontend suite 24646 tests passed; eslint warning count identical to base for both touched files.
  • Pre-push review: two model-pinned reviewers (GPT + Opus lanes), both NO BLOCKING; all five advisories folded into this single commit or noted above.

Any other suggestions on the work

The IME ratchet (scanUnguardedTabFocusTraps) only recognizes Tab-shaped traps, so an unguarded arrow branch that moves focus is invisible to it -- widening the predicate to the choose-class key set is the cause-level fix and belongs to the existing ratchet-widening issue #5475.

Closes #5851

role="menu" promises ArrowDown/ArrowUp item navigation per the WAI-ARIA
menu pattern, but the row-actions dropdown only handled Tab containment.
Handle ArrowDown/ArrowUp over the same focusableItems() list (wrap at
both ends) plus Home/End, with preventDefault to stop page scroll,
modified arrows (Cmd/Ctrl/Alt) passed through as OS shortcuts, and the
shared IME latch claim so composition-owned arrows never move menu
focus. Tab containment and focus return are unchanged.

Closes #5851
@chenmingwei23
chenmingwei23 requested a review from a team August 27, 2026 05:36
@chenmingwei23 chenmingwei23 added the no-screenshots PR has no visual delta; screenshot gate exempt label Aug 27, 2026
@chenmingwei23
chenmingwei23 requested a review from a team as a code owner August 27, 2026 05:36
@chenmingwei23
chenmingwei23 requested a review from buluoray August 27, 2026 05:36
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Aug 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — ✅ PASS

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

UX-Verdict: PASS

Arrow/Home/End navigation delivers the behavior role="menu" already promised to assistive tech; wrap, entry, disabled-skip, and modifier pass-through all match the existing Tab/Escape conventions.

[UX-REVIEWED] ec4db96

@github-actions

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

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

Design-Verdict: PASS

Small, correctly-scoped fix that makes the announced role="menu" contract true, reusing the existing focus list and IME-claim pattern already proven in this component.

Suggestions

[DESIGN-REVIEWED] ec4db96

@github-actions

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of ec4db9624152f2075565dc1c9c343632bad81239 and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] ec4db96

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

@github-actions

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — 🟡 CONCERNS

Premise-level review of ec4db9624152f2075565dc1c9c343632bad81239 — 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 evidence gathered. The fix itself is derived (WAI-ARIA role="menu" keyboard contract, reported defect #5851), it introduces no new public surface, and it reuses the existing IME latch rather than hand-rolling one. The two depth findings: five sibling role="menu" surfaces remain arrow-dead, and the description justifies divergence against useListKeyboardNav while never accounting for useListboxKeyboard, the closer existing mechanism (which I verified is meaningfully different: it closes on Tab where #2533 requires containment, doesn't wrap, and lacks the document-level IME latch).

First-Principles-Verdict: CONCERNS

The arrow fix is derived and honest, but it is the codebase's fourth keyboard-nav spelling and five counted role="menu" siblings stay arrow-dead.

What this change ships

Intent: make the Dev Fleet row-actions menu answer the arrow keys its role="menu" announcement promises — a FIX.

  1. Arrow keys move through the row-actions menu, wrapping at the ends — justified (reported defect Dev Fleet row-actions menu: role="menu" promises arrow-key navigation but only Tab is handled #5851, ARIA role contract)
  2. Home/End jump to first/last item — rides along, declared; same role contract names the harm
  3. Arrows no longer scroll the page behind the open menu — justified
  4. Cmd/Ctrl/Alt+arrow still reach the OS/browser — justified
  5. An arrow owned by an IME composition leaves menu focus alone — justified; reuses the existing useDocumentImeLatch, no new mechanism
  6. Arrow with focus outside the list enters at first/last item — justified, named reachable cause (polling unmount)

Watch

[FIRST-PRINCIPLES-REVIEWED] ec4db96

@github-actions

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

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

Review details

No findings.

[OPUS-REVIEWED] ec4db96

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

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

@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 Aug 27, 2026
@chenmingwei23

Copy link
Copy Markdown
Contributor Author

Dispositions for the First Principles CONCERNS (advisory, head ec4db96):

1. Five sibling role="menu" surfaces remain arrow-dead -- accepted and deferred. Filed #6231 covering the five named surfaces (AgentPillBar.tsx, MicSourceMenu.tsx, mochi ContextMenu.tsx, mochi PackEditor.tsx, PierreWorkspaceTreeImpl.tsx). The review is right that #5475 widens only the ratchet predicate, not the menus, so #6231 carries the behavior half explicitly and names the shared-primitive direction (extend useListboxKeyboard or extract a shared menu-keyboard hook) so the sibling fix lands there instead of a fifth inline spelling. Folding five more surfaces into this converged single-commit PR would be the scope growth this repo's review process exists to prevent.

2. useListboxKeyboard not accounted for in the description -- acknowledged; the inline branch stands. The review's own verification settles the reuse question: useListboxKeyboard closes on Tab where #2533 requires containment, does not wrap, and lacks the document-level IME latch -- three load-bearing differences, so adopting it here would have regressed the #3525 containment contract. That comparison is now recorded in #6231 as the input to the shared-primitive design, which is the right place for the consolidation decision.

@dwu96 dwu96 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tier 1 auto-approve: fix (2 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, SAST annotations clean, security checklist all-NO, AI reviewers green. Category: role="menu" already promised WAI-ARIA arrow-key item navigation but the keydown handler only bound Escape/Tab, so screen-reader users had no arrow path through the row-actions menu; the fix adds ArrowUp/ArrowDown/Home/End with wrap, IME-composition and modified-key claims, plus five regression tests.

@bolichen97 bolichen97 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.

Tier 1 auto-approve: fix (2 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, SAST annotations clean, security checklist all-NO, AI reviewers green. Category: fix — arrow-key item navigation in the row-actions menu (WAI-ARIA menu pattern) for #5851, test-covered, no runtime/behavior change outside the menu keyboard handler.

@dwu96
dwu96 merged commit 7902b2a into main Aug 27, 2026
67 of 70 checks passed
@dwu96
dwu96 deleted the fix/devfleet-menu-arrow-nav-5851 branch August 27, 2026 06:04
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Aug 27, 2026

@bolichen97 bolichen97 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.

Tier 1 auto-approve: fix (2 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, SAST annotations clean (Semgrep success/0, CodeQL success + 0 PR-scoped alerts), security checklist all-NO, AI reviewers green. Category: fix(dev-fleet) adds WAI-ARIA arrow-key/Home/End navigation to the existing row-actions menu keydown handler with tests, clear root cause (#5851: role=menu promised arrow navigation it lacked).

dwu96 pushed a commit that referenced this pull request Aug 27, 2026
…nu surfaces

Fixes #6231

Five containers declare role="menu" — which promises WAI-ARIA arrow-key
item navigation — and none implemented it. Extract MenuBtn's proven
roving-focus keydown logic (DevFleetPage, #6226) into a shared
useMenuKeyboard hook (arrows wrap at both ends, Home/End, Tab containment
per #2533, document IME latch), refactor MenuBtn onto it (its tests pass
unchanged — the extraction's behaviour-preservation proof), and wire the
five surfaces: AgentPillBar (menu semantics on the actual controls, with
host-driven focus repair), MicSourceMenu, mochi ContextMenu (guarded
useLayoutEffect unmount focus restore), mochi PackEditor SlotPopover
(closeToOpener for Escape and every command), and PierreWorkspaceTreeImpl
(single-item semantic no-op, wired for contract consistency).

Zero i18n changes; no visible label, ordering, or click behaviour changed.
bolichen97 pushed a commit that referenced this pull request Aug 28, 2026
…nu surfaces (#6267)

* fix: honour the role="menu" arrow-key contract on all five sibling menu surfaces

Fixes #6231

Five containers declare role="menu" — which promises WAI-ARIA arrow-key
item navigation — and none implemented it. Extract MenuBtn's proven
roving-focus keydown logic (DevFleetPage, #6226) into a shared
useMenuKeyboard hook (arrows wrap at both ends, Home/End, Tab containment
per #2533, document IME latch), refactor MenuBtn onto it (its tests pass
unchanged — the extraction's behaviour-preservation proof), and wire the
five surfaces: AgentPillBar (menu semantics on the actual controls, with
host-driven focus repair), MicSourceMenu, mochi ContextMenu (guarded
useLayoutEffect unmount focus restore), mochi PackEditor SlotPopover
(closeToOpener for Escape and every command), and PierreWorkspaceTreeImpl
(single-item semantic no-op, wired for contract consistency).

Zero i18n changes; no visible label, ordering, or click behaviour changed.

* chore: inclusive-language gate — black-box -> closed-box in a test comment

---------

Co-authored-by: Di Wu <diwm@dev-dsk-diwm-2b-abc02530.us-west-2.amazon.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-screenshots PR has no visual delta; screenshot gate exempt

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dev Fleet row-actions menu: role="menu" promises arrow-key navigation but only Tab is handled

3 participants