Skip to content

Make the file list speak to VoiceOver: cursor, marked files, position, and file kind #437

Closed

Description

@vdavid

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-selected tracks the cursor, so marked (multi-selected) files aren't exposed at all. No aria-multiselectable, no second state.
  • No aria-setsize / aria-posinset on 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 is aria-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 span with onclick (VolumeBreadcrumb.svelte), not focusable, no aria-haspopup.
  • Tab is pane.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.

  1. Put aria-activedescendant on the element that actually holds focus (the container), or let the listbox take focus and drop the guard for it.
  2. Expose marked state separately from the cursor: aria-multiselectable="true" plus aria-selected for marked rows, with the cursor carried by activedescendant.
  3. Add aria-setsize / aria-posinset from the listing's total count.
  4. Put the kind ("folder", "symlink", "parent folder") into each row's accessible name.
  5. Full view: role="grid" with columnheader / gridcell and aria-sort, or keep listbox and add the column names into the row name.
  6. Name each listbox after its side and path; mark the active pane.
  7. Make the volume chooser trigger a real button with aria-haspopup="menu".
  8. 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 Tab for pane switching.
  9. 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

  1. vdavid commented on Oct 15, 2026

    @vdavid
    OwnerAuthor

    Code is on local main (not pushed yet), per docs/specs/a11y-file-list-focus-plan.md: focus lives on the focused pane's list with a deferred aria-activedescendant and 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).

  2. vdavid commented on Oct 15, 2026

    @vdavid
    OwnerAuthor

    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 1e669900c and f58df3754): 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

    1. Click a toast's text, then ↓: pane keys still work (615a87390).
    2. The Space tip toast reads "…configurable in Settings > Keyboard shortcuts", link text included (2ce9db14a).
    3. A search with zero results says "No files match these criteria" (1c9e0acbb).
    4. Settings sidebar: every row says its position, not just the first ("Zoom and density, 3 of 15") (966f0af0b).
    5. The .. row in Full view says "Parent folder, (this folder) 36,860" (2d18f0be9).
    6. A real click on the unfocused pane's EMPTY list space switches panes and the next ↓ speaks (fa4f15189).
    7. Escape on the quick filter says "Filter cleared, 8 names" (ed7435a72).
    8. Full view with the Git column on: a modified file says "Modified in git", in the UI language (a3143f2be, 2e8e38025).

    Never run

    1. The file viewer's right-click menu (native): ↓ reads each item's name (it opened as "menu (2 items)", names not caught).
    2. C1: VO-→ from the list to the volume chip reads "…, choose a volume, menu pop up button".
    3. C3: with "keyboard focus follows VoiceOver cursor" on, the VO cursor onto a path segment keeps focus there, and VO-Space navigates.
    4. Enter on a path segment from ⌃F5, and a real click on one: navigates, and VoiceOver lands back on the list.
    5. Harness step 6: the real-key scroll check in vo-long, Brief and Full (no-op keys leave scrollTop unchanged; ↓ × 30 and Page Down keep the cursor in view).
    6. V4: a slow copy (network or a non-cloning volume) so the progress value is heard, plus the copy dialog's title on open.
    7. Launch with focus in the LEFT pane: the cursor row, then "Left pane, {folder}".
    8. By ear: does the pane announcer ("Right pane, …") cut off the cursor row on Tab, launch, and entering a folder? (AX posts the row 5–15 ms first; the phrase poll can't tell.)
    9. V5 rename (field name, an invalid name's error), the license key error, V7 a control's value and a change, V8 delete confirmation, V11 an error pane and a text file in the viewer, V12 reading order in a dialog and in Settings.

    Minor, AX only, not heard: opening the drive-index badge menu with Space posts a brief focus on the left list first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions