Skip to content

Make the GUI keyboard-accessible and fix result-grid ARIA (H-3, B-20..B-22, C-13) - #171

Merged
fstubner merged 1 commit into
mainfrom
fix/gui-accessibility
Aug 14, 2026
Merged

fstubner merged 1 commit into
mainfrom
fix/gui-accessibility

Conversation

@fstubner

Copy link
Copy Markdown
Owner

H-3 — the workspace tabs could not be used by keyboard at all

Plain <div>s with an onClick: no role, no tabIndex, and no tab-switching shortcut anywhere in the app. Now a proper tablist with a roving tabindex — arrows move and wrap, Home/End jump to the ends, Delete closes, Enter/Space activate (a div doesn't activate on Enter the way a button does). Arrow selection also moves focus, which the roving tabindex otherwise strands on the old tab.

Also fixed

# Bug
B-20 role="grid" sat on the scroll container whose only child was a <table> — that breaks row ownership, so AT saw a grid with no rows. Role, focus and key handling move onto the table (aria-activedescendant must sit on the focused element). Headers gain aria-sort, previously conveyed only by a caret glyph.
B-21 Toasts were never announced — no role, no aria-live anywhere in the GUI, so operation-complete and operation-failed notices were silent. The live region now renders unconditionally: screen readers only announce changes within a region that already existed.
B-22 Native context menu suppressed document-wide including text inputs, while the app's own menu only covers result surfaces — right-clicking any form field yielded nothing, losing the only mouse-driven cut/copy/paste.
C-13 Detail-pane "tabs" had no tab semantics — announced as unrelated buttons with no current-item indication.

Verified live, not just in tests

Driven through the running GUI via browser tooling:

tablistPresent: true   gridOnTable: true   liveRegion: true
gridActiveDescendant: "result-row-0"
ariaSortValues: ["ascending","none","none","none","none","none"]

ArrowLeft: selectedIdx 2 -> 1, tabIndexes ["-1","0","-1"],
           focusedIsSelected: true

clickSelectsTab: true          wheelMovedScroll: true
contextMenuAllowedInInput: true  contextMenuSuppressedOnBody: true

The last two lines matter because I extracted drag-to-scroll mechanically — click-select and wheel-scroll are confirmed still working, not assumed.

Note on the file-size gate

TabStrip crossed the cap. I started shaving comments to squeeze under it, realised that was the wrong fix, and extracted keyboard handling and drag-to-scroll into their own modules instead.

80 tests pass, up from 72 — 8 new ones covering tablist semantics, roving tabindex, wrap-around, Home/End, Delete and Enter/Space.

H-3: the workspace tabs were plain divs with an onClick -- no role, no
tabIndex, and no tab-switching shortcut anywhere in the app -- so they
could not be reached or operated by keyboard at all. They are now a
tablist with a roving tabindex: arrows move and wrap, Home/End jump to
the ends, Delete closes, Enter/Space activate (a div does not activate on
Enter the way a button does). Selecting via arrow also moves focus, which
the roving tabindex otherwise strands on the old tab.

B-20: `role="grid"` sat on the scroll container whose only child was a
table, which breaks row ownership -- assistive tech saw a grid with no
rows. The role moves onto the table, along with focus and key handling,
since aria-activedescendant must sit on the focused element. Sortable
headers gain aria-sort, which was previously conveyed only by a caret
glyph, and rows gain ids for aria-activedescendant.

B-21: toasts were never announced -- no role, no aria-live anywhere in
the GUI, so operation-complete and operation-failed notices were silent.
The live region is now rendered unconditionally, because screen readers
only announce changes within a region that already existed; inserting an
element that itself carries aria-live is unreliable. `.toast` is
position:absolute and a static wrapper creates no containing block, so
this does not move it.

B-22: the native context menu was suppressed document-wide, including in
text inputs, while the app's own menu only covers result surfaces -- so
right-clicking any form field yielded nothing and users lost their only
mouse-driven cut/copy/paste affordance. Editable targets now keep the
native menu.

C-13: the detail-pane tabs looked and behaved like tabs but carried no
tab semantics, so they were announced as unrelated buttons with no
indication which was current. Now a proper tablist/tabpanel pair.

TabStrip crossed the file-size cap, so keyboard handling and
drag-to-scroll move to their own modules rather than shaving comments to
squeeze under it.

Verified live in the running GUI, not just in tests: arrow navigation
moves selection and focus together, the roving tabindex follows, click
select and wheel scroll still work after the drag extraction, and the
context menu is allowed in inputs while still suppressed elsewhere.

80 tests pass, up from 72.
@fstubner
fstubner merged commit cdb2bdf into main Aug 14, 2026
15 of 16 checks passed
@fstubner
fstubner deleted the fix/gui-accessibility branch August 14, 2026 22:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant