Skip to content

fix: accessibility pass — keyboard reachability and dialog semantics - #285

Open
NovakPAai wants to merge 1 commit into
mainfrom
claude/novak-accessibility-pass
Open

fix: accessibility pass — keyboard reachability and dialog semantics#285
NovakPAai wants to merge 1 commit into
mainfrom
claude/novak-accessibility-pass

Conversation

@NovakPAai

Copy link
Copy Markdown
Collaborator

Summary

Follow-up on a UX audit run across Overview, Projects, Sessions, and Workspace: keyboard/screen-reader support was inconsistent throughout — some controls had real focus/ARIA semantics, most didn't. This closes the accessibility gaps found (see conversation history for the full audit).

  • Overview: .ov-card gets a visible :focus-visible outline (it was already a real <button>, just missing the ring — every other button class in the app has one).
  • Calendar: day cells are now real <button>s (were unfocusable <div>s) with aria-pressed reflecting selection; the popup is a proper role="dialog" aria-modal with Escape-to-close and focus returned to the date button on close.
  • Add Project modal: addProjectSwitchTab() now updates aria-selected/roving tabindex when switching tabs — it previously only toggled the visual .active class, so a screen reader kept announcing the first tab as selected forever. Added Left/Right/Home/End arrow-key navigation matching the existing Projects/History tablist pattern one function away.
  • Session cards (.card / .list-row / .qa-item): were plain <div>s with an onclick and nested buttons — entirely unreachable by Tab, only mouse or the undiscoverable j/k shortcuts worked. Added tabindex, a descriptive aria-label, :focus-visible styling, and a shared onCardKeydown handler (Enter/Space triggers the card's own click, guarded so it doesn't also double-fire for a nested button/checkbox's own key handling).
  • Detail panel: role="dialog" aria-modal, wired into the existing _installModalFocusTrap/_uninstallModalFocusTrap helper (same one Add Project / Projects Settings already use) for Tab-trapping and focus-return on close. Also hardened _uninstallModalFocusTrap to skip focus-return when the captured element has since been detached from the DOM (a background poll/re-render can replace it while a modal is open) instead of silently stranding focus on the modal's own now-hidden close button.
  • Workspace: aria-label on the pane close button, tab close button, and the three launch/layout <select> menus that only had a title before. Split-pane resize handles (.ws-resizer) are now real ARIA separators — tabindex, role="separator", aria-orientation, and arrow-key resize (mirrors the pointer-drag math without triggering a full handle rebuild mid-interaction, which would destroy and unfocus the handle the user is actively adjusting).

Test plan

  • node --test "test/**/*.test.js" — 257 passed, 1 skipped (win32-only)
  • Verified live in a browser (Playwright): Tab reaches a session card, Enter opens the detail dialog with focus auto-landed on the close button; Escape closes it and returns focus to the originating card; calendar popup opens as a dialog, Escape closes and returns focus to the date button; Add Project tabs correctly sync aria-selected/tabindex on click and via arrow-key navigation

🤖 Generated with Claude Code

Closes the gaps found in a UX audit across Overview, Projects, Sessions,
and Workspace: real keyboard focus and screen-reader semantics were
inconsistent — some controls had them, most didn't.

- Overview: .ov-card gets a visible :focus-visible outline (it was
  already a real <button>, just missing the ring).
- Calendar: day cells are now real <button>s (were unfocusable divs) with
  aria-pressed reflecting selection; the popup is a proper role="dialog"
  aria-modal with Escape-to-close and focus returned to the date button
  on close (toggleCalendar/closeCalendar/onCalendarPopupKeydown).
- Add Project modal: addProjectSwitchTab() now updates aria-selected and
  roving tabindex when switching tabs (previously only toggled the
  visual .active class, so a screen reader kept announcing the first tab
  as selected forever); added Left/Right/Home/End arrow-key navigation
  matching the existing Projects/History tablist pattern.
- Session cards (.card / .list-row / .qa-item): were plain divs with an
  onclick and nested buttons — unreachable by Tab entirely. Added
  tabindex, a descriptive aria-label, :focus-visible styling, and a
  shared onCardKeydown handler (Enter/Space triggers the card's own
  click, guarded so it doesn't also fire for a nested button/checkbox's
  own key handling).
- Detail panel: role="dialog" aria-modal, wired into the existing
  _installModalFocusTrap/_uninstallModalFocusTrap helper (same one Add
  Project / Projects Settings use) for Tab-trapping and focus-return on
  close. Hardened _uninstallModalFocusTrap to skip focus-return when the
  captured element has since been detached from the DOM (a background
  poll/re-render can replace it while the modal is open) rather than
  silently stranding focus on the modal's own now-hidden close button.
- Workspace: aria-label on the pane close button, tab close button, and
  the three launch/layout <select> menus that only had a title before.
  Split-pane resize handles (.ws-resizer) are now real ARIA separators —
  tabindex, role="separator", aria-orientation, and arrow-key resize
  (mirrors the pointer-drag math without triggering a full handle
  rebuild mid-interaction, which would destroy and unfocus the handle
  the user is actively adjusting).
vakovalskii added a commit that referenced this pull request Aug 2, 2026
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@NovakPAai
NovakPAai requested a review from vakovalskii August 2, 2026 19:43
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