Skip to content

hub-client: UI/UX modernization Phase 2 — keyboard and assistive-tech support - #609

Merged
shikokuchuo merged 7 commits into
hub-client-uiux-phase1from
hub-client-uiux-phase2
Aug 26, 2026
Merged

hub-client: UI/UX modernization Phase 2 — keyboard and assistive-tech support#609
shikokuchuo merged 7 commits into
hub-client-uiux-phase1from
hub-client-uiux-phase2

Conversation

@shikokuchuo

Copy link
Copy Markdown
Member

Stacked on #607. Review and merge that PR first.

What this PR does

Phase 2 of the hub-client UI/UX modernization: full keyboard access and assistive-technology support.

  • The file tree is now keyboard-operable. Tab moves into the tree once. Arrow keys, Home, and End move the focus. Enter opens a file. Shift+F10 opens the actions menu for a row. Search results work the same way.
  • Every interactive control shows a visible focus ring for keyboard users.
  • Windows High Contrast mode keeps visible borders on buttons, menus, dialogs, and notifications.
  • The About tab has a new Keyboard Shortcuts reference.
  • Copy is now consistent: sentence case, one name per concept, correct ellipsis characters. For example, the sidebar button "Upload asset" is now "Add asset", the same name as the dialog that it opens.
  • All user-facing copy in the app chrome lives in one module, src/strings.ts.

Bug fixes

  • A dialog opened from a menu now returns the focus to the menu button when the dialog closes. This focus chain was broken before this PR.
  • New accessibility scans found low-contrast menu hint text in the dark theme. A new theme token fixes the contrast.

Testing

  • New tests: 15 keyboard tests, 4 high-contrast tests, 4 accessibility scans. All suites pass: 1251 unit tests and 79 visual tests.
  • The visual baselines in CI are stale after this PR. The recreate-all-snapshots workflow run on this branch adds the new baselines as a bot commit.

@posit-snyk-bot

posit-snyk-bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

shikokuchuo and others added 7 commits August 26, 2026 18:12
…tics

File tree (FileSidebar) is now an APG treeview: role=tree/treeitem/group
with aria-level/aria-expanded/aria-selected, roving tabindex (one tab
stop for the whole tree), ArrowUp/Down/Home/End navigation, ArrowRight/
Left expand/collapse/parent, type-ahead with repeat-key cycling, Enter/
Space activation, and Shift+F10/Menu-key context menu anchored to the
row so focus returns to it. Search results become a listbox with the
same navigation; ArrowDown from the search input moves into the results
and Escape clears back to the input. Row kebab buttons leave the tab
order (tabIndex=-1) — the keyboard path is Shift+F10 on the row.

- Outline chevrons gain aria-expanded and per-symbol accessible names
  ('Collapse Introduction'); outline rows/chevrons get the token focus
  ring, replacing a bare outline:none (lint exception burned down).
- Sidebar section headers get aria-controls wired to role=region
  content panels with aria-labelledby, plus a focus-visible ring.
- Focus rings: token --focus-ring on tree rows, search results, folder
  headers, section headers (inset offset so scroll containers don't
  clip them); file-row kebab now also appears on row focus-within.
- Menu: focus return on close is synchronous again, but happens BEFORE
  the closing state updates commit — a dialog opened from a menu item
  now captures the trigger (not the doomed menu item) as its restore
  target during render, so Escape from the dialog returns focus to the
  trigger. Fixes the pre-existing projects-home-dialogs failure.
- DevHarness sidebar route is stateful (tracks selection, records the
  last action in an offscreen testid) and gains a search fixture, so
  keyboard specs can assert activation; e2e/sidebar-keyboard.visual
  .spec.ts covers all of the above (15 specs).
- baseline-screens now screenshots the surface's container element for
  component routes instead of the full page: a sidebar-wide change
  measured 0.86% of full-page pixels, silently under the 1% tolerance.
  Baselines regenerated locally (gitignored); the sidebar baseline now
  includes the search box, matching real chrome.

Phase 2 of the UI/UX modernization plan.
- Burn down the lint:css outline:none exceptions to zero: the rule is
  now enforced repo-wide. Every suppressed outline gains the token
  --focus-ring on :focus-visible (NewFileDialog, ProjectSelector,
  ProjectSetSetup, ReplayDrawer, debug page); .qh-input gains the ring
  (border-color-only focus was too subtle); the rename input and the
  two ProjectSelector halo shadows standardize on the one token ring.
- Forced colors (Windows High Contrast): .qh-icon-btn, .qh-dialog,
  .qh-tooltip, and the toasts get system-color borders (ButtonBorder/
  CanvasText) so they keep visible boundaries when the platform strips
  backgrounds and shadows. The replay waveform scrubber rings the
  waveform on keyboard focus. Covered by e2e/forced-colors.visual.spec
  .ts (Playwright forcedColors emulation).
- New e2e/gallery-states-a11y.visual.spec.ts scans the open menu and
  visible tooltip with axe-core in both themes, strict zero-tolerance
  for serious/critical (scoped via .include so the page's baselined
  contrast issues don't leak in). This is the Phase 2 extension of the
  axe coverage to every gallery surface.
- That scan caught a real one: menu shortcut hints used --text-muted,
  3.6:1 on the dark menu background (WCAG 1.4.3 fail). New semantic
  token --menu-hint-text: --text-muted in light (5.2:1),
  --text-secondary in dark (5.6:1).
- Replay waveform slider gains aria-label='Replay position'.

Phase 2 of the UI/UX modernization plan.
…cript

- src/utils/keyboardShortcuts.ts: the single source of truth for
  keyboard interactions (global handlers + the tree/listbox/menu/dialog
  patterns), rendered by the new Keyboard Shortcuts section in the
  sidebar's About tab. AboutTab.test.tsx pins the reference to render
  every group and entry so docs can't drift from the registry.
- hub-client/screen-reader-smoke.md: the manual VoiceOver pass (header,
  file tree, outline, dialogs, notifications) and the Windows High
  Contrast pass, alongside the automated forced-colors spec that
  references it.

Phase 2 of the UI/UX modernization plan.
… focus chain

src/strings.ts is the single source of truth for app-chrome copy
(header, file tree, outline, sections, notifications, dialogs, tabs,
view toggle, replay), with the copy conventions recorded in the module
header: Unicode … (never three dots), sentence case, … only on menu
items that open dialogs, one verb per concept. Migrated the chrome
components; ProjectsHome/ProjectSelector/ProjectSetSetup keep local
strings (noted in the module).

Copy fixes from the audit: 'Upload asset' → 'Add asset' (matches the
dialog it opens), 'Open in New Tab'/'Copy Link'/'Browse Files' →
sentence case, 'Share Project' → 'Share project', 'Loading...' and
friends → '…'. Test assertions updated to match.

Two focus-chain fixes this surfaced:
- Menu now handles Enter/Space explicitly (preventDefault + click the
  focused item). The browser's default activation click is dispatched
  after the keydown handler — with the synchronous focus return it
  landed on the re-focused trigger and re-opened the menu.
- ModalDialog's focus restore defers past the commit via a microtask
  and skips while the dialog is still connected: StrictMode's
  mount-time double-effect was running the unmount cleanup while the
  dialog stayed mounted, stealing focus from the autoFocus child to
  the restore target. The menu→dialog→trigger focus chain now works
  end to end (projects-home-dialogs spec green).

Baselines regenerated (gitignored, local-only): dialog-share (title
now sentence case) and dialog-new-file (the focused filename input
shows the new .qh-input focus ring — the intended WCAG 2.4.7 change).

Phase 2 of the UI/UX modernization plan.
The shortcuts reference used global --text-primary/--bg-input tokens,
but the About tab lives in the editor-token scope where those resolve
differently — the kbd chips rendered dark-on-dark in dark mode. The
section now uses the same editor-scoped tokens as the rest of AboutTab
(kbd chips match the sidebar search input styling).

The About tab had no dev-harness route, so no visual or axe coverage
caught this. Added the #/dev/about-tab route (sidebar width, in the
editor chrome scope) and registered it in baseline-screens and
baseline-a11y.

The axe scan also surfaced a pre-existing issue: .section-label's
--editor-text-muted is 4.24:1 on the dark sidebar (WCAG 1.4.3 needs
4.5:1). The new group titles use --editor-text-dim instead; the shared
label pattern is baselined as-is, with the token fix deferred to the
Phase 5 visual gate.
@shikokuchuo
shikokuchuo force-pushed the hub-client-uiux-phase2 branch from ef3ebba to 07b041d Compare August 26, 2026 17:13
@shikokuchuo
shikokuchuo merged commit 7a73982 into main Aug 26, 2026
12 of 18 checks passed
@shikokuchuo
shikokuchuo deleted the hub-client-uiux-phase2 branch August 26, 2026 19:39
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.

2 participants