Sweep `--el-text-faint` off ACTIVE informational text — every site the scanner rules a violation, and its faint arm turned on repo-wide
Carved out of MOTIR-2459 on 2026-08-08, which was sized 5 points / 90 minutes for "build the guard AND fix every violation" before anyone had the defect count. The count is now measured and it is a backlog, so 2459 keeps the SCANNER and this card carries the --el-text-faint sweep it enables.
Repo: motir-core. One PR.
What the scanner measured, on origin/main at 0d5279da
Over app/**, components/**, lib/** and packages/design-system/src/** (1703 files), text-(--el-text-faint) lands on 230 sites:
| verdict | sites | what it means |
|---|---|---|
| decorative | 81 | aria-hidden, a labelled role="img", or a labelled control whose content is glyphs only — legitimate |
| disabled | 17 | a disabled / aria-disabled element, or the disabled branch of a ternary — WCAG 1.4.3 exempts it |
| violation | 122 | active informational text at 2.37–2.61:1 |
| unattributable | 10 | a bare class constant the scanner cannot attach to an element, so nothing can show it is either of the above |
132 defects across 82 files. The card this was carved from cited 262 occurrences / ~184 "text-ish" and warned the heuristic was untrustworthy; it was — the honest split is above, and the 10 unattributable constants are a shape neither number contained.
Why the shipped a11y sweeps are green with 132 defects on main
Crossed the violation files against the routes tests/e2e/shell-a11y*.spec.ts actually visits: 108 of the 134 files carrying either defect sit on routes the sweeps never load at all — the whole app/(public) tree (docs, explore, p/[identifier]), (onboarding), /backlog, /triage, /settings/account, /settings/organization, components/planning. The 26 that DO sit on a swept route survive because the sweep loads them EMPTY (a fresh user, a fresh project), so the faint-carrying branches — a "Showing N of M" tail, a populated row's timestamp, an open menu's section label — never render. A green sweep is evidence about the fixture, not about the ink.
The fix, and why one substitution covers the sweep
--el-text-secondary clears AA on all four measured surfaces in both themes (6.18–6.80:1), so it is the only replacement that cannot be wrong without knowing the surface — and motir-core/CLAUDE.md § The token map already says to reach for it when in doubt. So the sweep is: every violation takes --el-text-secondary, except where the element is genuinely a glyph, which is fixed by MARKING it (aria-hidden, a labelled role="img") rather than by re-inking it. The 10 unattributable constants are fixed by moving the class onto the element that renders it, or by marking that element.
Acceptance criteria
scanSource(from MOTIR-2459) reports zeroviolationand zerounattributablefaint findings across the scanned tree — asserted as a derivation over the scanned file set, never against a frozen count of 132, so the assertion survives the tree growing.- A new
tests/theme/inkContrastLint.test.tsruns that scan repo-wide and FAILS the build on any faint violation, in the mould oftests/theme/swapLayerLint.test.ts— and it asserts the scanned file set is real and non-empty first (notes.html#195), naming at least one file per scanned root. - No allowlist, no exclusion, no per-file exemption. Every violation is fixed by moving the ink or by marking the element decorative; an element that is genuinely a glyph gains
aria-hidden(or a labelledrole="img") so the guard can see what a reader already assumed. - The guard's muted arm stays off in this PR — it is a separate sweep with a separate blind spot, and turning it on here would make the diff unreviewable.
- Every string the sweep re-inks keeps its existing size / weight / tracking; this changes the ink only.
pnpm lint,pnpm typecheck,pnpm prettierandnext buildpass, and every component test that asserts on a re-inked class is updated in the same PR.- The PR body states the final counts by verdict and names the routes the a11y sweeps do NOT cover, so the next reader inherits the coverage gap rather than re-deriving it.
Context refs
tests/theme/inkContrastScan.ts— the scanner this card consumes (MOTIR-2459).tests/theme/swapLayerLint.test.ts— the repo-wide token-lint mould to follow.motir-core/CLAUDE.md§ The token map — the measured contrast table and the two-legitimate-jobs rule.tests/e2e/shell-a11y.spec.ts—SHELL_ROUTES, the swept set.- MOTIR-2455 — the incident that measured the tokens.