fix(a11y): ta bort alfa-tonad textfärg — 82 kontrastöverträdelser (UI-09)
CI (SIAX Cloud) / integration (push) Skipped
masterplan-lock / check (push) Successful in 7s
Security Scan (SIAX Cloud) / security (push) Successful in 20s
CI (SIAX Cloud) / security (push) Successful in 47s
masterplan-lock / check (pull_request) Successful in 10s
CI (SIAX Cloud) / quality (push) Successful in 1m11s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 32s
CI (SIAX Cloud) / security (pull_request) Successful in 42s
CI (SIAX Cloud) / integration (pull_request) Successful in 56s
CI (SIAX Cloud) / quality (pull_request) Successful in 59s
CI (SIAX Cloud) / integration (push) Skipped
masterplan-lock / check (push) Successful in 7s
Security Scan (SIAX Cloud) / security (push) Successful in 20s
CI (SIAX Cloud) / security (push) Successful in 47s
masterplan-lock / check (pull_request) Successful in 10s
CI (SIAX Cloud) / quality (push) Successful in 1m11s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 32s
CI (SIAX Cloud) / security (pull_request) Successful in 42s
CI (SIAX Cloud) / integration (pull_request) Successful in 56s
CI (SIAX Cloud) / quality (pull_request) Successful in 59s
axe-core 4.10.2 mot 0pt.app 2026-09-26: en enda regel, color-contrast, impact=serious, 82 noder. Estatets största enskilda a11y-defekt. Alla 82 har samma form: en Tailwind-alfamodifierare på en textfärg. Uppmätt WCAG 2.2-kontrast mot bägge temabakgrunderna i styles/globals.css: text-muted-foreground 4.73:1 ljust 7.63:1 mörkt GODKÄNT text-muted-foreground/70 2.71:1 4.20:1 faller text-muted-foreground/60 2.29:1 3.37:1 faller text-muted-foreground/50 1.96:1 2.67:1 faller text-muted-foreground/40 1.69:1 2.11:1 faller text-foreground/20 1.57:1 1.74:1 faller Den omodifierade token klarar AA i bägge teman, så åtgärden är helt enkelt att aldrig tona den. Elva textställen bytte till text-muted-foreground. Kopieringsknappen i app/portal går från 1.57:1 till 4.73:1 i viloläge och 19.79:1 vid hover — den föll även 1.4.11 för icke-textkomponenter. Toastens stängknapp klarade redan 1.4.11 (3.71:1) men blir 4.73:1. Kvar med alfa är endast den dekorativa masthead-siffran i app/bestall, som redan bär aria-hidden + select-none + pointer-events-none. Den är undantagen både av WCAG och av axe, och guarden nedan tillåter den på just den grunden. scripts/ci/check-text-contrast.mjs är en blockerande källkodsgrind i quality-jobbet. Den är avsiktligt inte en axe-körning: den fullständiga axe-jobb som UI-09 också begär kräver build + serve i CI och spåras separat. Tills det finns stoppar den här grinden regressionen vid den form som orsakade defekten. Refs: UI-09 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Executable
+74
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Blocking guard for finding UI-09 (measured 2026-09-26): 0pt.app carried 82
|
||||
* `color-contrast` violations at axe-core 4.10.2 impact=serious — the estate's
|
||||
* single largest a11y defect. Every one of them came from the same shape: a
|
||||
* Tailwind alpha modifier on a muted text colour.
|
||||
*
|
||||
* Measured WCAG 2.2 contrast against the two theme backgrounds in
|
||||
* styles/globals.css (--background: oklch(1 0 0) light / oklch(0.145 0 0) dark,
|
||||
* --muted-foreground: oklch(0.556 0 0) / oklch(0.708 0 0)):
|
||||
*
|
||||
* text-muted-foreground 4.73:1 light 7.63:1 dark PASS (AA needs 4.5)
|
||||
* text-muted-foreground/70 2.71:1 4.20:1 FAIL
|
||||
* text-muted-foreground/60 2.29:1 3.37:1 FAIL
|
||||
* text-muted-foreground/50 1.96:1 2.67:1 FAIL
|
||||
* text-muted-foreground/40 1.69:1 2.11:1 FAIL
|
||||
* text-muted-foreground/10 1.13:1 1.13:1 FAIL
|
||||
*
|
||||
* The unmodified token passes in both themes, so the fix is simply never to
|
||||
* fade it. Purely decorative text is exempt — but it has to SAY so: the element
|
||||
* must carry `aria-hidden`, which is also what makes axe skip it. The single
|
||||
* remaining case (app/bestall/page.tsx, the oversized "01" masthead numeral) is
|
||||
* `aria-hidden` + `select-none` + `pointer-events-none` and is allowed by that
|
||||
* rule.
|
||||
*
|
||||
* This is a source-level guard, not a browser run. The full axe-core job that
|
||||
* UI-09 also asks for needs a build + serve in CI and is tracked separately;
|
||||
* until it exists this stops the regression at the shape that caused it.
|
||||
*/
|
||||
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
||||
import { join, relative } from 'node:path';
|
||||
|
||||
const ROOT = new URL('../..', import.meta.url).pathname.replace(/\/$/, '');
|
||||
const SKIP = new Set(['node_modules', '.next', '.git', 'dist', 'build', 'coverage', '.turbo']);
|
||||
const EXT = /\.(tsx|ts|jsx|js|css)$/;
|
||||
/** Alpha-modified colour utilities on TEXT. Background/border alphas are fine. */
|
||||
const PATTERN = /\btext-(muted-foreground|foreground|primary|secondary|accent-foreground|card-foreground|popover-foreground)\/\d+/g;
|
||||
|
||||
function walk(dir, out = []) {
|
||||
for (const name of readdirSync(dir)) {
|
||||
if (SKIP.has(name)) continue;
|
||||
const p = join(dir, name);
|
||||
if (statSync(p).isDirectory()) walk(p, out);
|
||||
else if (EXT.test(name)) out.push(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const violations = [];
|
||||
for (const file of walk(ROOT)) {
|
||||
const src = readFileSync(file, 'utf8');
|
||||
const lines = src.split('\n');
|
||||
lines.forEach((line, i) => {
|
||||
for (const m of line.matchAll(PATTERN)) {
|
||||
// Decorative text is exempt when the element declares itself hidden to AT.
|
||||
// The attribute may sit on an earlier line of the same JSX element.
|
||||
const window = lines.slice(Math.max(0, i - 6), i + 1).join('\n');
|
||||
if (/\baria-hidden\b/.test(window)) continue;
|
||||
violations.push({ file: relative(ROOT, file), line: i + 1, token: m[0], text: line.trim() });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (violations.length) {
|
||||
console.error(`\n✗ ${violations.length} alpha-faded text colour(s) found — WCAG 2.2 AA 1.4.3 (finding UI-09).\n`);
|
||||
for (const v of violations) {
|
||||
console.error(` ${v.file}:${v.line} ${v.token}`);
|
||||
console.error(` ${v.text.slice(0, 160)}`);
|
||||
}
|
||||
console.error('\n Use the unmodified token (text-muted-foreground = 4.73:1 light / 7.63:1 dark).');
|
||||
console.error(' If the text really is decoration, put aria-hidden on the element.\n');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('✓ no alpha-faded text colours — WCAG 2.2 AA 1.4.3 contrast shape is clean (UI-09)');
|
||||
Reference in New Issue
Block a user