Skip to content

fix(a11y): /dashboard/cli-code failed the project's own bar, unaudited - #71

Merged
LMPrado-DZ23 merged 1 commit into
release/v3.8.55from
fix/cli-code-a11y
Sep 20, 2026
Merged

LMPrado-DZ23 merged 1 commit into
release/v3.8.55from
fix/cli-code-a11y

Conversation

@LMPrado-DZ23

Copy link
Copy Markdown
Owner

The product audit drove the shipped production build with axe-core 4.13.0 and found this page carrying four blocking nodes, in both themes and at every width it measured — against a project bar of zero critical/serious:

theme width violations
dark 1280 select-name critical ×2, color-contrast serious ×2
light 1280 select-name critical ×2, color-contrast serious ×2
light 375 select-name critical ×2

passes 25, violations 2, incomplete 0 — real failures, not gradient "incomplete" noise.

select-name (critical)

Both filter selects had no accessible name at all: no id, no aria-label, no aria-labelledby, not wrapped — and the two visible <label>s had no htmlFor. Each select's first option is "All", so a screen reader announced two unnamed combo boxes both saying "All".

Bound each label to its select. The labels are visible here, so this is the htmlFor/id pair rather than the visually-hidden FilterSelect the logs page uses — that component exists to supply a name the UI does not show, which is not this case.

color-contrast (serious) — two different defects

light 2.92  #e17100 on #f9f4ee  14px  .text-amber-600   ← "no active providers" banner
light 3.74  #b83242 on #ebc5ca  10px  bg-primary/20 text-primary  (pill)
dark  3.25  #e54d5e on #502933  10px  same pill
dark  4.14  #e54d5e on #2b2028  12px  uppercase tracking-wider text-primary

The 2.92:1 node is the worst thing here. It is the amber banner that tells a brand-new user with nothing configured what to do next — the most load-bearing text on the page for a first run, and the least readable. amber-600 → amber-800 takes it to 6.3:1.

The other three are brand coral used as text on a tinted surface — the same failure mode #55 fixed. globals.css already ships --color-primary-on-tint for exactly this, with the reasoning written down in Sidebar.tsx ("measured 4.43:1 in dark theme… the theme-aware shade"). These call sites used plain text-primary.

The gate's coverage was the real finding

tests/e2e/a11y.spec.ts audited seven paths — and neither of this release's two new surfaces was among them. A11Y_VIEWPORTS started at tablet (768), so 375 was never swept, which is why a violation that reproduces at phone width could ship.

Both pages and the phone width are in it now, and the test title no longer claims a range it does not cover (768–1440px → 375–1440px).

Tests

tests/unit/cli-code-page-a11y.test.tsx is the fast half, so a regression fails in the unit suite instead of waiting for the nightly axe job. It renders the page in the no-provider state — the one that shows the amber banner — and asserts zero axe violations, plus a direct check that every <select> has a name by one of the four legitimate mechanisms.

Proven failable rather than assumed: removing the two htmlFor attributes takes it from 2 pass to 2 fail.

Gate Result
cli-code-page-a11y + workspaces-page-a11y 3 pass / 0 fail
typecheck:core exit 0, 0 errors
eslint --max-warnings=0 exit 0
prettier --check clean

jsdom has no layout, so contrast is "incomplete" in the unit test — that half is covered by the e2e sweep this PR extends.

Not fixed here: the light-theme primary button falls to 3.96:1 over the violet end of --grad-brand, which axe reports as incomplete because it is a gradient. That is a brand-token decision across every primary button in the product, not a cli-code defect, and it deserves its own change.

🤖 Generated with Claude Code

The product audit drove the shipped production build and found this page
carrying four blocking axe nodes, in BOTH themes and at every width it
measured — against a project bar of zero critical/serious:

  select-name    critical x2   both filter selects, 1280 and 375
  color-contrast serious  x2   dark 4.14 / 3.25, light 3.74 / 2.92

The selects had no accessible name at all: no id, no aria-label, and the
two VISIBLE <label>s had no htmlFor. Each one's first option is "All", so
a screen reader announced two unnamed combo boxes both saying "All".
Bound each label to its select — the labels are visible here, so this is
the htmlFor/id pair rather than the visually-hidden FilterSelect the logs
page uses.

The contrast nodes are two separate defects:

  - the "no active providers" banner at 2.92:1 in light — the one line a
    first-run user with nothing configured most needs to read, and the
    least readable text on the page. amber-600 -> amber-800 (6.3:1).
  - brand coral used as TEXT on a tinted surface, the same failure mode
    #55 fixed. globals.css already ships --color-primary-on-tint for
    exactly this, with the reasoning written down in Sidebar.tsx; these
    two call sites used plain text-primary.

The gate's coverage was the real finding. tests/e2e/a11y.spec.ts audited
seven paths, NEITHER of this release's two new surfaces among them, and
A11Y_VIEWPORTS started at 768 so 375 was never swept. Both pages and the
phone width are in it now, and the test title no longer claims a range it
does not cover.

tests/unit/cli-code-page-a11y.test.tsx is the fast half, so a regression
fails in the unit suite instead of waiting for the nightly axe job.
Proven failable: removing the two htmlFor attributes takes it from
2 pass to 2 fail.

  cli-code + workspaces a11y     3 pass / 0 fail
  typecheck:core exit 0, 0 errors; eslint exit 0; prettier clean

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 20, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 584098c3-6079-4752-bd9a-7c60b344e437


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@LMPrado-DZ23
LMPrado-DZ23 merged commit fc5c4a1 into release/v3.8.55 Sep 20, 2026
15 checks passed
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