Commit ae18e3e
authored
feat(a11y): WCAG 2.2 AA scan harness, Accessible theme, and name/role fixes (#404)
* test(a11y): add automated WCAG 2.2 AA scan harness
Adds an axe-core + Playwright accessibility scan under e2e/a11y/ that reuses
the existing e2e auth/seed/server wiring (no parallel setup, no app changes).
It scans every app route as an authenticated user against seeded data, splits
WCAG A/AA findings from best-practice, scans cheap interactive states
(dialog/menu), and aggregates a deduped report grouped by WCAG success
criterion for VPAT-style review.
- routes.ts: manifest of all app routes with runtime-resolved seeded IDs
- fixtures.setup.ts: seeds a richly-populated project + triggers ES reindex
- scan.spec.ts: parameterized axe scan, per-route JSON output
- aggregate.ts: deduped report.md/report.json grouped by success criterion
- run.ts: a11y:scan runner (single-route, strict mode, exit-code propagation)
- adds a11y:scan / a11y:report npm scripts, @axe-core/playwright dev dep,
gitignore entries for results/fixtures, README, and a report-mode CI draft
Report mode by default; A11Y_STRICT=on (or CI=strict) fails on serious/critical.
* fix(a11y): resolve top shared-component WCAG violations
Addresses the most widespread findings from the automated accessibility scan,
fixing them at the shared-component root so the improvement applies across
every page that uses them:
- DataTable column-resize handle: add role="separator" + aria-orientation so
its aria-label is valid ARIA (WCAG 4.1.2 aria-prohibited-attr) — clears 227
elements across 26 routes.
- BreadcrumbComponent: make the tooltip trigger asChild so the folder link is
no longer a focusable <a> nested inside a <button> (WCAG 4.1.2
nested-interactive); drop the invalid type="button" on the anchor.
- Test run / milestone / session summaries: give the icon-only sort toggle an
aria-label from the existing sort-by translation keys (WCAG 4.1.2
aria-command-name) — clears 6 routes.
Reuses existing i18n keys; no new strings. Verified via e2e/a11y scan:
aria-prohibited-attr 26->0, aria-command-name 6->0, nested-interactive 5->4.
* test(a11y): seed a case-owned dataset so the dataset-detail route scans
The model create policy's Zod validator rejects a null `ownerCase` relation, so
a shared (case-less) dataset can't be created via the API. Connect an in-project
ownerCase instead — this satisfies the policy and lets the scan cover the
settings/datasets/[dataSetId] detail route (was previously recorded as skipped).
* fix(a11y): give icon-only controls accessible names
Adds accessible names to the highest-volume unnamed controls surfaced by the
accessibility scan, fixing them at shared-component sources:
- Project switcher (SelectTrigger): aria-label — present on ~33 routes.
- TipTap toolbar (bold/italic/underline/strikethrough/code/heading) buttons.
- Test-case panel collapse toggles.
- Report-type Select triggers.
- Slider primitive now forwards aria-label/aria-labelledby onto the focusable
thumb (role="slider"); the five admin Security sliders pass their existing
labels through.
Reuses existing translation keys where possible; adds common.aria.selectProject,
common.aria.togglePanel, and common.editor.{bold,italic,underline,strikethrough,
code,heading} to en-US only (Crowdin manages other locales). Verified via the
e2e/a11y scan: button-name failing elements 813->712, aria-input-field-name 5->0.
* fix(a11y): name more icon controls and fix issue-title trigger semantics
Continues labelling unnamed controls and fixes two structural ARIA issues:
- TipTap toolbar: bullet/numbered list, blockquote, and table buttons get
aria-labels (new common.editor keys).
- UserNameCell: the name tooltip trigger is now asChild, so it no longer
renders a <button> nested inside the profile <Link> (nested-interactive).
- Issues columns: the title popover trigger is a <button> instead of a <div>,
so aria-expanded is valid (aria-allowed-attr).
- BreadcrumbComponent: folder links get an explicit aria-label fallback.
Verified via e2e/a11y scan: button-name 712->672 elements, aria-allowed-attr
13->7, target-size 108->82 (UserNameCell), with no new violations.
* feat(theme): add opt-in Accessible (WCAG 2.2 AA) theme
Adds a selectable "Accessible" theme alongside the existing five. It pairs a
high-contrast, light-based palette with a small scoped override layer that
enforces the presentation success criteria app-wide — without touching any
component or the other themes (the rules only apply under .accessible):
- 1.4.3 Contrast: neutralizes text-muted-foreground/* and text-primary/*
opacity modifiers (the main cause of sub-4.5:1 text) and uses a darker
muted token (~8:1).
- 1.4.11 Non-text Contrast: darker borders/inputs (>=3:1).
- 2.4.7 Focus Visible: a consistent high-contrast focus ring.
- 2.5.8 Target Size: 24px minimum hit area for controls and links.
Wired through next-themes (themes list), the Theme enum, the user-menu picker
(persists like the other themes), and i18n. Scanning with this theme active
drops color-contrast from 58 routes to 13 and target-size from 26 to 15; the
remainder is data-driven badge colors and exempt disabled controls.
* test(a11y): let the scan force a theme (A11Y_THEME)
Adds A11Y_THEME so the scan can measure a specific theme regardless of the
seeded user preference (e.g. A11Y_THEME=accessible). The forced theme class is
applied before axe runs and recorded in the report header.
* fix(a11y): make the avatar tooltip non-interactive
Avatar rendered its tooltip trigger as a nameless <button> (the color-swatch
cursor-default control) that also nested inside link/row cells. Switching the
trigger to asChild uses the avatar itself (img alt / initials) as the trigger,
removing the button — which clears button-name and nested-interactive for every
avatar across the app.
* fix(a11y): give row-action menus accessible names (button-name)
The icon-only "..." DropdownMenu triggers in the users, test-run, session,
milestone, comment, and folder-tree row menus had no accessible name. Adds an
aria-label (the existing "Actions" string) to each trigger. aria-label only —
no visual change in any theme.
* fix(a11y): name admin toggles, selects, and edit buttons (button-name)
Adds aria-labels to controls that had no accessible name: the user active
toggle, the project review-workflow switch, the result-editing-policy select,
and the app-config / template edit buttons. aria-label only — no visual change.
* fix(a11y): name edit/delete icon buttons in admin tables (button-name)
Adds aria-labels (existing Edit/Delete strings) to the icon-only edit and delete
buttons (enabled and disabled variants) in the app-config, template, and status
admin tables. aria-label only — no visual change.
* fix(a11y): name edit/delete buttons in config, llm, projects, roles tables (button-name)
* fix(a11y): name edit/delete buttons in remaining admin tables (button-name)
* fix(a11y): name edit/delete buttons in workflow/group/milestone/prompt tables (button-name)
* fix(a11y): name the project sidebar toggle and case back button (button-name)
* fix(a11y): label enabled/status toggle switches in admin tables (button-name)
* fix(a11y): name result-expand, remove-parent-folder, requires-review controls (button-name)
* fix(a11y): name the shared column-filter operator selects (button-name)
* docs(a11y): add WCAG 2.2 AA conformance report (ACR / VPAT draft)
* fix(a11y): add accessible names for various UI elements in multiple languages
This commit enhances accessibility by adding aria-labels for project selection, panel toggles, folder management, and various text formatting options across multiple language files. These changes ensure that users relying on assistive technologies can better navigate and interact with the application.
* fix(a11y): show Accessible theme icon in profile and onboarding pickers
The Accessible theme option appeared in the profile Preferences page and the
initial-preferences onboarding dialog but rendered without an icon, since both
getThemeIcon/getThemeColor switches lacked an Accessible case. Add the
Accessibility icon (matching the user menu) and clear the accessible class in
the onboarding theme-preview fallback.
* fix(a11y): escape backslashes in report Markdown cells
The report cell escaper handled the pipe delimiter but not the backslash
itself, so a backslash in axe output could corrupt table rendering. Escape
backslashes first, then pipes, and normalize CRLF newlines.
* test(a11y): update Avatar test for the asChild tooltip trigger
The avatar tooltip now uses asChild, so the avatar element is the trigger
itself rather than being wrapped in a nameless button. Assert the accessible
contract (no button ancestor) instead of the old wrapper-button DOM shape.
* chore(a11y): satisfy lint and prettier on the scan harness
Drop the unused baseURL test arg (no-unused-vars) and apply Prettier
formatting to the harness files flagged by format:check.
* docs(a11y): document the opt-in Accessible theme
Add the Accessible theme to the user-menu and user-profile theme lists, a
new Accessibility section in the user menu guide explaining what the theme
does (contrast, target size, focus ring) and that it is opt-in and scoped,
and an Accessible theme entry in the feature list.1 parent 2f2f261 commit ae18e3e
88 files changed
Lines changed: 2883 additions & 66 deletions
File tree
- docs/docs
- user-guide
- testplanit
- app/[locale]
- admin
- app-config
- code-repositories
- configurations
- fields
- groups
- issues
- llm
- milestones
- projects
- prompts
- quickscripts
- roles
- security
- statuses
- tags
- users
- workflows
- issues
- projects
- milestones/[projectId]
- repository/[projectId]
- [caseId]
- runs/[projectId]
- sessions/[projectId]
- users/profile/[userId]
- components
- comments
- onboarding
- reports
- tables
- tiptap
- ui
- e2e/a11y
- lib/openapi
- messages
- prisma
- styles
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
174 | 174 | | |
175 | 175 | | |
176 | 176 | | |
| 177 | + | |
177 | 178 | | |
178 | 179 | | |
179 | 180 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
| 17 | + | |
| 18 | + | |
18 | 19 | | |
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
22 | 23 | | |
23 | 24 | | |
24 | 25 | | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
63 | 63 | | |
64 | 64 | | |
65 | 65 | | |
66 | | - | |
| 66 | + | |
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
64 | 70 | | |
65 | 71 | | |
66 | 72 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
84 | 84 | | |
85 | 85 | | |
86 | 86 | | |
| 87 | + | |
87 | 88 | | |
88 | 89 | | |
89 | 90 | | |
| |||
92 | 93 | | |
93 | 94 | | |
94 | 95 | | |
| 96 | + | |
95 | 97 | | |
96 | 98 | | |
97 | 99 | | |
| |||
Lines changed: 2 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
168 | 168 | | |
169 | 169 | | |
170 | 170 | | |
| 171 | + | |
171 | 172 | | |
172 | 173 | | |
173 | 174 | | |
| |||
176 | 177 | | |
177 | 178 | | |
178 | 179 | | |
| 180 | + | |
179 | 181 | | |
180 | 182 | | |
181 | 183 | | |
| |||
Lines changed: 2 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
135 | 135 | | |
136 | 136 | | |
137 | 137 | | |
| 138 | + | |
138 | 139 | | |
139 | 140 | | |
140 | 141 | | |
141 | 142 | | |
142 | 143 | | |
143 | 144 | | |
144 | 145 | | |
| 146 | + | |
145 | 147 | | |
146 | 148 | | |
147 | 149 | | |
| |||
Lines changed: 2 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
329 | 329 | | |
330 | 330 | | |
331 | 331 | | |
| 332 | + | |
332 | 333 | | |
333 | 334 | | |
334 | 335 | | |
335 | 336 | | |
336 | 337 | | |
337 | 338 | | |
338 | 339 | | |
| 340 | + | |
339 | 341 | | |
340 | 342 | | |
341 | 343 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
| 99 | + | |
99 | 100 | | |
100 | 101 | | |
101 | 102 | | |
| |||
114 | 115 | | |
115 | 116 | | |
116 | 117 | | |
| 118 | + | |
117 | 119 | | |
118 | 120 | | |
119 | 121 | | |
| |||
132 | 134 | | |
133 | 135 | | |
134 | 136 | | |
| 137 | + | |
135 | 138 | | |
136 | 139 | | |
137 | 140 | | |
| |||
155 | 158 | | |
156 | 159 | | |
157 | 160 | | |
| 161 | + | |
158 | 162 | | |
159 | 163 | | |
160 | 164 | | |
| |||
163 | 167 | | |
164 | 168 | | |
165 | 169 | | |
| 170 | + | |
166 | 171 | | |
167 | 172 | | |
168 | 173 | | |
| |||
0 commit comments