Repository navigation
Make the file list speak to VoiceOver: cursor, marked files, position, and file kind #437
Description
Problem. The file list probably says nothing to VoiceOver as you move through it. The role="application" container in DualPaneExplorer.svelte holds DOM focus at all times (key-dispatch.ts handleFocusGuard pulls focus back to it), but aria-activedescendant sits on the listbox (BriefList.svelte, FullList.svelte), which never gets focus. ARIA only honors aria-activedescendant on the focused element. On top of that:
aria-selectedtracks the cursor, so marked (multi-selected) files aren't exposed at all. Noaria-multiselectable, no second state.- No
aria-setsize/aria-posinseton the virtualized rows, so a screen reader can't say "12 of 3,400". - File icons are
alt="", so nothing tells a folder from a file, a symlink, or the..row. - Full view isn't a grid: no column headers tied to cells, and no
aria-sort(the sort caret isaria-hidden). - Both panes' listboxes are named "File list"; the pane regions don't carry the path or which one is active.
- The volume chooser trigger is a bare
spanwithonclick(VolumeBreadcrumb.svelte), not focusable, noaria-haspopup. Tabispane.switch, and the focus guard bounces focus off every chrome button, so Full Keyboard Access and VoiceOver users can't land on the toolbar, breadcrumb, or tabs.
Impact. The core of the app is likely unusable without sight. This is the biggest gap in the accessibility audit (docs/notes/accessibility-audit-2026-10.md, WCAG 4.1.2, 1.3.1, 1.1.1; Section 508 502.3.1, 502.3.3, 502.3.12; EN 301 549 11.5.2.5, 11.5.2.6, 11.5.2.13) and it blocks "Without vision" (508 302.1, EN 4.2.1). Pending a VoiceOver check to confirm; the code says it can't work as wired.
Solution.
- Put
aria-activedescendanton the element that actually holds focus (the container), or let the listbox take focus and drop the guard for it. - Expose marked state separately from the cursor:
aria-multiselectable="true"plusaria-selectedfor marked rows, with the cursor carried by activedescendant. - Add
aria-setsize/aria-posinsetfrom the listing's total count. - Put the kind ("folder", "symlink", "parent folder") into each row's accessible name.
- Full view:
role="grid"withcolumnheader/gridcellandaria-sort, or keep listbox and add the column names into the row name. - Name each listbox after its side and path; mark the active pane.
- Make the volume chooser trigger a real button with
aria-haspopup="menu". - Decide how screen reader and Full Keyboard Access users reach the pane chrome (for example a rotor-friendly landmark plus a "focus toolbar" command), without breaking
Tabfor pane switching. - Verify with VoiceOver (David's guide: vault,
projects/Cmdr/2026-10-14 Cmdr VoiceOver test guide.md).
Size. L. Related: #267 (menus under VoiceOver).
Activity
docs(a11y): criterion-by-criterion accessibility audit, the evidence …
feat(website): an accessibility conformance report at /trust/accessib…
vdavid commentedon Oct 15, 2026
Code is on local
main(not pushed yet), perdocs/specs/a11y-file-list-focus-plan.md: focus lives on the focused pane's list with a deferredaria-activedescendantand per-list unique row ids (630beb349,a63b0eec7,f6251a522); menus and submenus name their cursor row and submenus take focus (63b7caf8b), the volume chip is a real menu button (fdf494651); rows expose marked state, position, kind, and a per-pane location announcement (194300367,24cc4b61b,865ba111b); pane controls reachable with ⌃F5 without changing Tab (58612c351). A VoiceOver spike on prototype code (2026-10-15) heard rows, positions, marking, pane switches, and submenus speak where they were silent before. Left open until the shipped code is heard with VoiceOver (the plan's "Remaining" list).fix(a11y): file rows name their symlink and sync badges, and unnamed …
docs(a11y): the conformance report shows 1.3.5 and 3.1.2 as supported…
docs(specs): plan for making the file list and menus speak to VoiceOv…
test(a11y): pin today's keyboard and focus behavior so the screen-rea…
feat(a11y): focus sits on the focused pane's file list, so VoiceOver …
feat(a11y): focus comes back to the focused pane's list after every d…
fix(a11y): an unclaimed arrow or Page key no longer scrolls a focused…
feat(a11y): every listed entry says whether it's a macOS package, so …
feat(a11y): the file list says what it holds: each row's name, kind, …
feat(a11y): keyboard and VoiceOver users reach each pane's tabs, loca…
docs(a11y): the conformance report and audit record the #438 gaps clo…
docs(website): an accessibility guide at /trust/accessibility-guide t…
docs(a11y): the conformance report and accessibility guide describe t…
docs(a11y): the audit cites the 2026-10-15 VoiceOver spike as evidenc…
vdavid commentedon Oct 15, 2026
Done on local
main(not pushed yet) and heard with real VoiceOver on 2026-10-15 (macOS 27.0, VoiceOver 10): the cursor row, position, kind, marking, and pane switches speak ("subfolder, folder (1 of 7)", "b.txt selected (3 of 7)"). The last silent case was WebKit ignoring focus on a not-yet-laid-out list (d94bb0767). Records:94a33b172. The remaining by-ear checks are below; they belong to the VoiceOver guide run, not to this issue.VoiceOver checks still owed (after the 2026-10-15 runs)
Heard passing on 2026-10-15 (macOS 27.0, VoiceOver 10, dev builds at
1e669900candf58df3754): V1 browse, V2 switch panes, V3 mark files, V10 in-app menus (volume switcher with submenus, Favorites, archive Enter popup, drive-index badge menu), ⌃F5 into pane chrome, sort and tab clicks keeping focus on the list, the command palette, and the silent-list fix. Full log:docs/notes/accessibility-audit-2026-10.md§ VoiceOver listening runs.Re-listen: fixed after a failed listen, not heard since
615a87390).2ce9db14a).1c9e0acbb).966f0af0b)...row in Full view says "Parent folder, (this folder) 36,860" (2d18f0be9).fa4f15189).ed7435a72).a3143f2be,2e8e38025).Never run
vo-long, Brief and Full (no-op keys leavescrollTopunchanged; ↓ × 30 and Page Down keep the cursor in view).Tab, launch, and entering a folder? (AX posts the row 5–15 ms first; the phrase poll can't tell.)Minor, AX only, not heard: opening the drive-index badge menu with Space posts a brief focus on the left list first.
docs(a11y): the conformance report and audit record the VoiceOver lis…
docs(a11y): David's VoiceOver sign-off (V1–V12 heard passing, 2026-10…
docs(a11y): every partial row in the conformance report links an open…