Verification Checkpoints
Checkpoint 1 — Div soup exposes nothing, and costs more
npm run a11y-tree
Pass:
- div soup shows 0 landmarks, 0 headings, 0 interactive, 0 focusable
- semantic shows ≥ 4 landmarks, ≥ 2 headings, ≥ 3 interactive, ≥ 3 focusable
- div soup markup is larger than semantic (≈ 1.3–1.4×)
Fail — div soup shows landmarks: your markup accidentally used semantic elements, or Chrome inferred a role. Check the variant string.
The checkpoint is the byte column. If you cannot state that div soup costs more markup for less function, the experiment has not landed.
Checkpoint 2 — ARIA restores the tree but not behaviour
Pass: the ARIA variant matches semantic on landmarks/headings/roles, uses ≈ 1.6–1.8× the markup,
and reaches 3 focusable elements only because tabindex was declared explicitly.
Say out loud what is still missing versus the semantic version: Enter/Space activation, disabled
semantics, form participation, and automatic name computation.
Checkpoint 3 — Native dialog passes behaviours a naive custom one does not
npm run dialog
Pass:
<dialog>: yes on all seven rows- naive custom: NO on focus-moves-in, focus-trapped, background-inert, Escape, focus-restored
- careful custom: yes on everything except
::backdrop - naive custom JS ≥ native JS bytes
Fail — naive custom passes focus trap: the test is not pressing enough Tabs, or the background buttons were removed. Focus must be able to escape for the check to mean anything.
Checkpoint 4 — Constraint validation works with zero validation JS
npm run forms
Pass: every NO row reports the correct specific ValidityState flag (typeMismatch,
rangeUnderflow, patternMismatch), submission is blocked while invalid and allowed once valid,
and :user-invalid is supported.
Checkpoint 5 — The dirty-value trap reproduces
Pass:
el.value = 'short' (programmatic) -> valid=true tooShort=false
user types 'short' (real keys) -> valid=false tooShort=true
This is the highest-transfer checkpoint in the module. You must be able to state the
consequence: a test that sets .value directly passes while the real form rejects the same input.
Then verify you understand it by breaking it deliberately — swap page.type() for page.fill() and
watch the trap disappear.
Checkpoint 6 — You replaced something
Not a script. Take one component in a codebase you own — a modal, dropdown, accordion, tooltip, or tab set — and either:
- replace it with the native primitive, measuring JS bytes before and after and confirming the behaviour table, or
- write the justification for keeping it, naming which native behaviours you are now responsible for maintaining and what you tested to confirm they work.
Both are passing outcomes. The failure is having no answer.
Module completion
- Checkpoints 1–6
-
steps/05-principal-review.mdanswered in writing - The behaviour table from experiment 2 reproduced for one control you actually ship
- Learning log updated — particularly the dirty-value flag, which changes how you write tests