Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions a11y-audits/8-13-26/wave-4-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
**Program:** [program-plan.md](./program-plan.md)
**Critical / serious:** [resolution-plan.md](./resolution-plan.md) (A1–A11, D1–D2 on `main`)
**Standard:** WCAG 2.2 AA
**Status:** Product calls P8–P15 confirmed. Issues filed. A14 on `main` (PR #68). Executing Wave 4b A19.
**Status:** Product calls P8–P15 confirmed. Issues filed. A19 on `main` (PR #69). Executing Wave 4b A21.

In scope: **A12–A20, D3**, and leftover axe **A21**.
Out of this plan: A1–A11, D1, D2. Do not reopen them. Do not retune A7 Learn-Practice choice tokens. Question editor stays out (internal-only).
Expand Down Expand Up @@ -35,6 +35,7 @@ Out of this plan: A1–A11, D1, D2. Do not reopen them. Do not retune A7 Learn-P
| A20 | #66 | Designed focus ring on FIB blanks and toolbar tools |
| A12 | #67 | Activity `h2`; authored heading or P10 type name |
| A14 | #68 | Sort instructions include the keyboard path |
| A19 | #69 | KaTeX exposes MathML; visual layer aria-hidden |
| D3 | DS #33 → app #61 | Divider line token (Neutral-800), ≥3:1 vs panes |

Unrelated merges on the same timeline: clipboard in iframes (#43), Sort heading font (#45). Not audit IDs.
Expand Down Expand Up @@ -263,7 +264,7 @@ Fill issue/PR numbers when filing. Never write “this PR”.
| A16 | #53 | 4a | #62 | Closed (PR #62) |
| A17 | #54 | 4a | #60 | Closed (PR #60) |
| A18 | #55 | 4a | #63 | Closed (PR #63) |
| A19 | #56 | 4b | | Open |
| A19 | #56 | 4b | #69 | Closed (PR #69) |
| A20 | #57 | 4a | #66 | Closed (PR #66) |
| A21 | #58 | 4b | | Open |
| D3 | [DS #32](https://github.com/CodeSignal/learn_bespoke-design-system/issues/32) | 4 ∥ | #61 | Closed (PR #61) |
Expand All @@ -284,4 +285,4 @@ Critical/serious rows stay in [resolution-plan.md](./resolution-plan.md). The do

## Next step

A14 is on `main` (PR #68). Next: Wave 4b A19 (`fix/a11y-katex-mathml`, #56). Do not put #68 on the A19 row.
A19 is on `main` (PR #69). Next: Wave 4b A21 (`fix/a11y-sort-dropzone-contrast`, #58). Do not put #69 on the A21 row.
2 changes: 1 addition & 1 deletion public/modules/sort.css
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@

.categorization-dropzone.empty::before {
content: "Drop items here";
color: var(--Colors-Text-Body-Lighter);
color: var(--Colors-Text-Body-Default);
font-size: var(--Fonts-Body-Default-xxs, 13px);
align-self: center;
margin: auto;
Expand Down
42 changes: 42 additions & 0 deletions test/a11y-characterization.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,48 @@ test('A19: KaTeX exposes MathML beside an aria-hidden visual layer', () => {
assert.doesNotMatch(src, /aria-label/, 'P12 exposes MathML; do not invent a spoken label');
});

test('A21: Sort empty dropzone placeholder meets 4.5:1 in light and keeps Drop items here', () => {
const sortCss = read('public/modules/sort.css');
const before = sortCss.match(/\.categorization-dropzone\.empty::before\s*\{[^}]+\}/);
assert.ok(before, 'empty dropzone ::before rule exists');
assert.match(before[0], /content:\s*"Drop items here"/);
const colorM = before[0].match(/color:\s*var\((--Colors-Text-Body-[A-Za-z]+)\)/);
assert.ok(colorM, 'placeholder sets a Body color token');
assert.equal(
colorM[1],
'--Colors-Text-Body-Default',
'13px placeholder must not use Body-Lighter'
);

const colorsCss = read('public/design-system/colors/colors.css');
const light = colorsCss.slice(0, colorsCss.indexOf('@media (prefers-color-scheme: dark)'));
const resolve = (prop) => {
const m = light.match(new RegExp(`${prop}:\\s*var\\((--Colors-Base-[A-Za-z0-9-]+)\\)`));
assert.ok(m, `light ${prop} aliases a base token`);
return m[1];
};
const hex = baseColorHex();
const fgToken = resolve(colorM[1]);
const bgToken = resolve('--Colors-Backgrounds-Main-Top');
const fg = hex[fgToken];
const bg = hex[bgToken];
assert.ok(fg && bg, `unresolved ${fgToken} or ${bgToken}`);
const ratio = contrastRatio(hexToRgb(fg), hexToRgb(bg));
assert.ok(
ratio >= 4.5,
`placeholder ${colorM[1]} (${fg}) on Main-Top (${bg}) is ${ratio.toFixed(2)}:1, need 4.5:1`
);

const instructions = sortCss.match(
/\.categorization-instructions-text\s*\{[^}]*color:\s*var\((--Colors-Text-Body-[A-Za-z]+)\)/
);
assert.equal(
instructions && instructions[1],
'--Colors-Text-Body-Default',
'A8 instruction token stays Body-Default'
);
});

test('A14: Sort instructions include the keyboard path', () => {
const sortJs = read('public/modules/sort.js');
assert.match(
Expand Down
Loading