fix(chat): use the menu keyboard contract for busy send - #7251
Conversation
Route the role=menu picker through the shared menu hook so arrow navigation wraps and Tab remains contained. Keep Escape dismissal and trigger focus restoration owned by the component.
Design Review (Fable 5, fork) — ✅ PASSDesign-level review of The patch is small and I've read both hooks and the host component. The picker declares Design-Verdict: PASS Real role/behavior mismatch fixed by adopting the repo's one existing [DESIGN-REVIEWED] 38f02cd |
UX Review (Fable 5, fork) — ✅ PASSUX-level review of The diff is a keyboard-contract swap for the busy-send mode picker (listbox hook → shared menu hook). I've compared the old and new behavior against the repo's shared UX-Verdict: PASS Keyboard behavior now matches the product's shared menu contract — wrapping arrows, contained Tab, Escape-to-trigger — with no visible or copy changes. [UX-REVIEWED] 38f02cd |
First Principles Review (Fable 5, fork) — 🟡 CONCERNSPremise-level review of All evidence gathered. The fix adopts the repo's established shared menu contract (7 existing First-Principles-Verdict: CONCERNS Right fix at cause level, but MarkdownPanel's What this change shipsIntent: make the busy-send picker's keyboard behavior match the menu it announces to assistive technology. This is a FIX.
Every item routes through the existing Watch
[FIRST-PRINCIPLES-REVIEWED] 38f02cd |
Opus 4.8 Review (fork) — ✅ no blocking findingsReviewed Review detailsI've examined the diff, both hooks, the full component, and the applicable frontend rules. Let me verify one interaction point — whether the removed The analysis holds:
No defect groundable in the changed lines. The candidate list itself found no candidates, and nothing survives Step 2. No findings. [OPUS-REVIEWED] 38f02cd |
GPT 5.6 Review (fork) — ✅ no blocking findingsReviewed Review detailsFINDING -- website/src/components/BusySendButton.tsx:136 -- |
iamwhatever
left a comment
There was a problem hiding this comment.
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: the portaled busy-send picker advertises role="menu" but was wired to useListboxKeyboard; swapped to the shared useMenuKeyboard contract (arrow wrap, Home/End, Tab containment) with Escape kept host-owned so closing restores the caret trigger. CodeQL is not applicable on this fork PR (default-setup emits no check-run); SAST coverage is Semgrep only, latest run success with 0 annotations.
Problem / Motivation
The busy-send picker declares
role="menu", but it is wired through thelistbox keyboard hook. That gives it listbox behavior: ArrowUp clamps at the
first row and Tab closes the picker. Other menu surfaces use the shared menu
contract, where arrows wrap and Tab stays within the open menu.
Why it matters
Keyboard and assistive-technology users are told this is a menu, then receive a
different navigation model. At the first row ArrowUp appears broken, and Tab
dismisses the picker instead of cycling through its two choices.
What changed (motivation → approach → change)
The picker now uses the existing
useMenuKeyboardhook. That supplies therepository's one
role="menu"contract: focus enters on open, arrows wrap,Home/End select the boundaries, Tab and Shift-Tab remain contained, and the
document-level IME latch protects navigation keys.
Escape remains owned by
BusySendButton, because this host must close theportal and restore focus to its caret trigger. Selection, portal positioning,
outside-click dismissal, and the persisted busy-send mode are unchanged.
Tests
BusySendButton.menuKeyboard.test.tsxfor ArrowUp wrap, bidirectionalTab containment while the menu remains open, and Escape focus restoration.
closed the menu).
tests, and the shared menu-hook tests.
tsc -bwas attempted with the reused local dependency tree; that tree lacksthe
yamlpackage and reports only untouchedSkillForm.tsxdiagnostics.Authoritative CI will run against the lockfile-complete install.
Manual verification
N/A — real-DOM focus assertions exercise every changed keyboard transition,
including the portaled menu and trigger focus restoration.
Screenshots / video
No pixel or layout changes. The delta is keyboard focus behavior inside the
existing two-row picker, which a still image cannot demonstrate; the focused
DOM tests record the transitions directly.
Related Issues
Follow-up to the accepted-and-deferred
BusySendButtonmenu adoption noted bythe First Principles review on merged PR #6547.
Pattern harvest
Rule candidate: review-prompt
Pattern: a
role="menu"surface must useuseMenuKeyboardor document why itshost-specific keyboard contract differs.
Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)Contribution License Agreement