Context
Decomposition leaf of #14619 (FM cockpit accessibility pass), created per @neo-gpt-emmy's gate-1 / gate-5 review disposition on PR #15094. #14619 is a 4-axis accessibility leaf (focus-order/keyboard, contrast, reduced-motion, token/contract-docs); PR #15094 delivers ONLY the keyboard/focus-order axis, so it must Resolve a scoped leaf rather than close the whole multi-axis epic. #14619 stays open for its remaining axes.
Live latest-open sweep: checked the latest 20 open issues at 2026-07-12T22:47Z; no equivalent keyboard-a11y leaf found. A2A in-flight claim sweep (latest 30, all read-states): no overlapping [lane-claim].
The Problem
The FM fleet cockpit's AgentCards were keyboard-inoperable in a WCAG-invalid way: each card carried role=button while CONTAINING native lifecycle buttons (start/stop/restart) — an invalid nested-interactive topology — and the roving tabindex + role were written to the vdom but did not reliably flush to the mounted DOM (cards were not actually focusable; a mounted whitebox-e2e caught this class of bug the .vdom unit assertions could not see). Screen-reader + keyboard users could not operate the roster correctly.
The Architectural Reality
apps/agentos/view/fleet/AgentCard.mjs — the card (a Neo.container.Base), its role, the drill affordance, the lifecycle control cluster.
apps/agentos/view/fleet/FleetGrid.mjs — the ranked card region + its keyboard model.
apps/agentos/view/fleet/AgentCardController.mjs — the drill (agentSelect) handler.
src/main/DomEvents.mjs — the shared main-thread neo-selection arrow-key scroll-suppression rule.
- Neo Button (
src/button/Base.mjs) renders a NATIVE <button type="button">, so native Enter/Space activation is a browser guarantee, not a hand-rolled key handler.
The Fix (implemented — branch agent/14619-fm-focus-order, commit 8c8932f62)
Per the reviewer disposition (the FM roster is a ranked responsive LIST, not a 2D data matrix, so a full ARIA grid composite is the wrong contract):
- AgentCard → a NON-interactive
role=listitem; the card-name becomes a dedicated NATIVE drill <button> (accessible name = the resident name); lifecycle toggle/restart stay as sibling native Buttons — every control a real element in ordinary Tab order (drill → toggle → restart). No card-level tabIndex.
- FleetGrid → ordinary Tab replaces roving-tabindex; an OPTIONAL Up/Down efficiency shortcut jumps focus between drill Buttons ONLY (large-roster convenience without an outer roving tab stop or a hidden interaction mode); focus survives a roster rebuild, restored to the resident's EXACT semantic child, never the card root or a different agent.
- DomEvents → reverts the shared Space scroll-suppression (native Buttons own Enter/Space); arrow-scroll suppression scoped to the drill Buttons via
neo-selection.
Contract Ledger
N/A — no human/agent/external-consumed surface introduced or changed (the drill Button is an internal app-view component; no public method, config, or MCP tool).
Decision Record impact
none — no ADR interaction.
Acceptance Criteria
Out of Scope
Contrast, reduced-motion, and token/contract-docs axes of #14619 (they remain #14619's open scope). The large-roster long-Tab UX (a fully-specified layout-grid / filter / search with AT evidence) is a future graduation, not this leaf.
Related
- Parent epic: #14619 (FM cockpit accessibility pass).
- Delivered by PR #15094.
- Supersedes the roving-tabindex approach reviewed in PR #15094 review cycles 1–2 (@neo-gpt + @neo-gpt-emmy dispositions).
Origin Session ID: d99146da-0478-4f23-bc16-dff04f5d650c
Retrieval Hint: "FM cockpit keyboard a11y native listitem drill Button gate-1 disposition"
Context
Decomposition leaf of #14619 (FM cockpit accessibility pass), created per @neo-gpt-emmy's gate-1 / gate-5 review disposition on PR #15094. #14619 is a 4-axis accessibility leaf (focus-order/keyboard, contrast, reduced-motion, token/contract-docs); PR #15094 delivers ONLY the keyboard/focus-order axis, so it must Resolve a scoped leaf rather than close the whole multi-axis epic. #14619 stays open for its remaining axes.
Live latest-open sweep: checked the latest 20 open issues at 2026-07-12T22:47Z; no equivalent keyboard-a11y leaf found. A2A in-flight claim sweep (latest 30, all read-states): no overlapping
[lane-claim].The Problem
The FM fleet cockpit's AgentCards were keyboard-inoperable in a WCAG-invalid way: each card carried
role=buttonwhile CONTAINING native lifecycle buttons (start/stop/restart) — an invalid nested-interactive topology — and the roving tabindex + role were written to the vdom but did not reliably flush to the mounted DOM (cards were not actually focusable; a mounted whitebox-e2e caught this class of bug the.vdomunit assertions could not see). Screen-reader + keyboard users could not operate the roster correctly.The Architectural Reality
apps/agentos/view/fleet/AgentCard.mjs— the card (aNeo.container.Base), itsrole, the drill affordance, the lifecycle control cluster.apps/agentos/view/fleet/FleetGrid.mjs— the ranked card region + its keyboard model.apps/agentos/view/fleet/AgentCardController.mjs— the drill (agentSelect) handler.src/main/DomEvents.mjs— the shared main-threadneo-selectionarrow-key scroll-suppression rule.src/button/Base.mjs) renders a NATIVE<button type="button">, so native Enter/Space activation is a browser guarantee, not a hand-rolled key handler.The Fix (implemented — branch
agent/14619-fm-focus-order, commit 8c8932f62)Per the reviewer disposition (the FM roster is a ranked responsive LIST, not a 2D data matrix, so a full ARIA grid composite is the wrong contract):
role=listitem; thecard-namebecomes a dedicated NATIVE drill<button>(accessible name = the resident name); lifecycle toggle/restart stay as sibling native Buttons — every control a real element in ordinary Tab order (drill → toggle → restart). No card-level tabIndex.neo-selection.Contract Ledger
N/A — no human/agent/external-consumed surface introduced or changed (the drill Button is an internal app-view component; no public method, config, or MCP tool).
Decision Record impact
none — no ADR interaction.
Acceptance Criteria
role=listitem(no cardrole=button, no card tabIndex) with a dedicated native drill<button>announced by the resident name.FleetGridKeyboardA11y.spec.mjs) proving topology, native drill activation, drill-only jump, scroll stability, and semantic-child restoration on the real DOM.Out of Scope
Contrast, reduced-motion, and token/contract-docs axes of #14619 (they remain #14619's open scope). The large-roster long-Tab UX (a fully-specified layout-grid / filter / search with AT evidence) is a future graduation, not this leaf.
Related
Origin Session ID: d99146da-0478-4f23-bc16-dff04f5d650c Retrieval Hint: "FM cockpit keyboard a11y native listitem drill Button gate-1 disposition"