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.md answered 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