Checks
The instruments
Twenty one of them, in design/kit/checks/, and each one exists because a class of defect got through everything else. They all drive a real browser, because every defect they were written for has the same shape: the value is never wrong in any file, and it resolves wrong. A reader sees correct text, a search sees a correct declaration, and only a rendering sees what happened.
There are no screenshots in this system, and these are why. A picture of a component is a second copy: it has to be retaken, it goes stale without saying so, and the first one taken on this stand documented a focus ring it did not contain, because a focus set from a script does not match :focus-visible. What the refactors of stage 08 and 09 promise is proved by rendering the tree twice and comparing it element by element, which says which element moved rather than how many pixels changed.
What each one finds, and what it says today
contrast.mjsstage 07A contrast ratio for every text node on every page, in both themes and at both viewports, with the ground composited layer by layer down to the body. Reading the first three numbers of an rgba() and calling it solid reports a panel item at 3.58 that is really 4.87, which is a false failure and can as easily be a false pass.
It found the defect that no reader, no search and no reviewer can see. At stage 06, a light documentation palette inherited by two dark pages, where the opening paragraph ran at 1.42:1 and every file path at 1.14:1 for two stages. At stage 07, 1018 failures on 128 pages made by four values. At stage 08, a stand class repainting the first product component placed inside it. At stage 09, a grid track sized by its content pushing a page sideways at 1440.
86 pages
0 failures
0 overflow
112 exemptions, declared
rules.mjsstage 09The thirteen usage rules, as functions. A prohibition written only in prose is a prohibition nobody runs. Each rule is measured at both viewports on what renders rather than on what is in the markup, and each may answer "not applicable": a screen with no dialog does not pass the dialog rule, it is not asked. Counting those as passes is how a suite reports green while testing nothing.
Two of the thirteen only became correct when they stopped reading the file and started reading the box. Two primary actions in a document are one primary action on a screen when the second is the viewport twin of the first. And an overlay pinned to the viewport covers a pane that sits below the fold, because the pane passes under it the moment anybody scrolls.
R13 arrived at stage 13 and it forbids two things at once: an expansion that has a head must be a details and not a div wearing a chevron, and no head holds a link, because a link inside a summary cannot be clicked. The second half is why the six handover notes keep no head, and it is what makes the first half structural: a filling with no head loses the mark by construction. 39 held, 93 not applicable, 0 broken.
132 renderings
0 broken
R11 was 17 and closed at stage 12
refactor.mjsstage 09The tree as it stood before the first edit against the tree as it stands now, walked element by element in document order at both viewports. The baseline is a copy of design/ taken outside the repository, so no image and no duplicate tree is committed.
It reports the tree shape before the boxes. A pattern class is added to an element that already exists, so the two subtrees must have the same element count in the same order, and a count that does not line up is a bigger finding than any box that moved.
51 screens
0 shape changes
0 boxes moved
compose.mjsstage 09Every composition in the grey corpus, counted as host{child child} with runs of the same child collapsed, so a queue of eighteen rows and a queue of three are one composition. It reads the DOM and not the file, because two of the corpus's biggest compositions live in no html file at all: the whole global navigation and both shell zones are written by a script at load.
The same pass read backwards gives the prohibitions: for every class, the maximum instances found on any one screen. A maximum of one is a candidate for a rule and never the rule itself, because the counter cannot tell a deliberate constraint from a component that has not yet had a second reason.
62 grey pages
68 on two screens
54 on three
4 became patterns
coverage.mjsstage 08Every selector of the deleted kit.css, against every selector the system declares, through a map of what was renamed and a list of what was deliberately dropped with its reason. It is what made deleting a 367 selector stylesheet a measurement rather than a hope.
Its own failure mode is a row that can never fire, and stage 09 found one: an earlier rename rewrote the prefix a later row was matching on, so the row sat there looking like cover. A declared list with nothing in it fails this system's rules as loudly as an undeclared case.
0 declared nowhere
geometry.mjsstage 08The box of each component on its own stand page against the same component on the screen it stands on. It exists because the stand can drift from the product silently: the page looks like documentation and the screen looks like a screen, and nobody compares them.
It was written after a font shorthand quietly reset a line height and moved every chip by 4.94px, and then it found the product's own defect: three chip heights in one scope bar, because the element was deciding the height.
0 unexplained
themes.mjsstage 08The dark theme as a stress test of the neighbourhood rather than of a component: fills that merge, two roles that arrive at one value, a role that clears its threshold in one theme and fails in the other, and a pair that looks copied rather than measured.
It printed FAIL fifteen times on its first run and meant nothing. It assumed a token resolves to rgb() and they resolve to hex, so every ratio was NaN. An instrument that has never been wrong out loud is an instrument nobody has checked.
2 ramp collapses, declared
icons.mjsstage 08Every icon applied as a CSS mask, character for character against design/system/icons.js, which is the one source the set has. A glyph pasted into a stylesheet is a second copy, and a second copy of a path is one nobody will ever diff by eye.
0 stroke findings
migrate.mjsstage 08The migrated screen against the version in the last commit, element by element. It is the instrument that sees a cascade defect, where both stylesheets are correct, both declarations are correct, and what is wrong is which of them wins. It found three: a component beating a viewport utility because the system loads later, a media query written where a class selector belonged, and a narrow only rule hoisted to the top level.
862 elements moved
0 unexplained
pixels.mjs and attribute.mjsstage 08A per pixel difference computed in a browser canvas, and a check that every visible difference is attributable to a named row of docs/tokens-audit.md. They answer how much and where, where the two comparisons above answer which element, and the pair is what makes "we did change some things" stop eating the whole comparison.
superseded for stage 09 by
refactor.mjs, which needs no imagessyntax.mjsstage 09Two counts and nothing else: comment openers against terminators, and braces. The cheapest instrument here and the last one written, because it took a self inflicted wound to see the need for it.
An edit to a comment in tokens.css dropped its terminator. The comment swallowed two declarations and the closing brace of the semantic block, so everything after it became nested inside that block, and two media queries then read :root as a descendant of the root, which nothing can be. Both of the system's responsive primitives went dead: the pane stopped narrowing below 1560 and the queue row stopped collapsing to one column at 360.
Every other instrument on this page passed while it was broken. The contrast sweep, because nothing about colour changed. The overflow check, because a row that is suddenly 646px wide sits inside a scroll container and clips. The usage rules, because not one of them is about a track. The baseline comparison, because it hides the panel and reads the product's boxes, and at 1440 they were unchanged.
0 unterminated
0 unclosed
sweep.mjsstage 10Every width from 320 to 2560, in steps of 40 and in tens within 80 of the point, on every screen. At each one: horizontal overflow, any element past the page box, how many navigation carriers are visible at once, and the longest run of prose counted in characters.
The width is measured, not intended. A scrollbar turns a requested 360 into an actual 345 and a whole row of results is then taken at a width nobody asked about, so every reading carries the document's own clientWidth and a disagreement is an error of measurement rather than a result.
It found the defect the stage exists for on its first run. The split had an arithmetic minimum nobody had added up, so from 910 to 1200 the product rendered a split whose row did not fit its own column, and had done since stage 04. Three screenshots at three widths prove three widths; this reads the ones between them.
Three of its own findings were not defects, and each was a defect of the finder: it asserted one navigation carrier on screens that carry no shell, then on a console that is out and has nowhere to go, then counted a row of buttons as an 86 character line. An instrument gets the same verification as the product.
52 screens
49 widths
0 findings from 360 up
24 below it, declared
motion.mjsstage 11Every element, twice, in two viewports. Once normally and once with the browser emulating prefers-reduced-motion: reduce. A screenshot shows a frame rather than a movement, so the only way to read motion is to ask the browser what it resolved.
The sign under reduce is "more than 1ms", not "zero". A transition of exactly zero and a transition that was never declared read identically in computed style, so a suite demanding zero cannot tell an element that obeyed from an element nobody asked.
It found three defects of its own before it found any of the product's. It could not see the one animation in the project, which lives on a pseudo element and which querySelectorAll('*') does not return: the queue's filling bar read as motionless, and read as obeying reduce for the same reason. It answered "did anything stop existing" by comparing what MOVES, so the one animation failed that check by obeying it. And it called a two property transition a drift.
4 roles, one value each
0 above 1ms under reduce
0 stopped existing
screens.mjsstage 12Is a screen file only markup? The rollout builds ten pages with subagents, and the one failure mode that survives every other instrument in this folder is an agent writing appearance into the file it was told to write. Twenty screens each carrying their own media query look right in a browser, pass contrast, pass the usage rules, and are twenty products.
It reads the file, deliberately, and that is the whole difference from the rest of this folder. Everything else here asks the browser what it resolved, because the browser is the only witness to what renders. This one asks what is WRITTEN, because the defect is a line of authorship: a rule that never fires is still a rule in the wrong file, and it will fire the day somebody renames a class.
The tenth sign is the one that needs a parser. Nine of them are words to search for. The tenth, a class no stylesheet declares, is what catches the agent who drew instead of stopping, so the file reads every sheet in design/system/ and strips the comments first: this project’s stylesheets carry more prose than rules, and a class name quoted in a comment would otherwise declare itself.
Its first run reported 59 hex values and 51 unknown classes, and almost all of it was the instrument. The hex was ▾, the triangle on every filter chip, read as a colour on 23 pages. Three of the classes are named in the files that dropped them and are declared exemptions now. One was real: qbanner, which the stage 08 rename map removed from five screens and left on the sixth, wearing a name whose declaration had moved to the parent.
10 signs
0 of them found
5 exempt, declared
coverage-map.mjsstage 12The map against the disk, and both ways round. The coverage map is a declared list, so the idle control applies to it in both directions: a row can be green over a page that does not open, and a page can exist that no row mentions. Reading the table catches neither, because the table is the thing being checked.
It opens the pages rather than testing for a file. A half assembled page is a file too. Every record declared in colour is loaded in a browser, and a page that throws, 404s a stylesheet, renders no h1 or renders no panel is not covered whatever the registry says.
It does not require the map to be all green. What the stage promises is that nothing was lost, not that everything was done. A screen deliberately out of scope stays on the map as its own state with a reason and is counted. A green row over an unfinished screen and a vanished row for something we chose not to build are the same lie told two ways.
62 opened
0 green over nothing
0 in nobody’s count
census.mjsstage 12Every control in the product, snapshot first. Twenty three properties are taken from every node and nothing is keyed during the walk, because keying while walking decides that two controls are the same before it has looked at the properties by which they differ. The collapse happens afterwards, on the full set, and each table names what it collapsed by.
Section 6 of the census said “Reserved” for four stages, and the honest reading of that is not that the measurement was skipped: it is that the walk was a script written from scratch each time somebody remembered to run it, so there was nothing to point the section at. It also ran twice over the 52 coloured screens, which was the sample; this is the first time it has seen the product.
It found the most invisible defect of the stage on its first run. Twelve textareas on the escalate family had been rendering as raw browser controls, monospace at 13.3px with the user agent’s grey border, inside a dark dialog, since stage 09. Contrast passed them because the browser’s own colours happen to clear the floor, the usage rules do not look at fields, and a class that is missing is not a forbidden sign, so the file reading instrument could not see it either.
Its first list of divergences was 18 rows and half of them were the instrument, folding a selected row in beside an unselected one. The form key ignores state and viewport classes now, and the list is 16.
1610 product controls
28 classes worn
0 controls with no form
focus.mjsstage 13A real Tab walk through the real tab sequence, in both themes, on every screen. At each stop: the element, its component, whether a ring actually renders, and what that ring measures against the ground behind the control. Every other instrument here could prove that --color-focus is declared, which is not the question anyone is asking.
Its first finding was its own. It measured the ring against the control’s own fill instead of the ground behind it, and reported every filled control in the light theme as a failure. An outline is drawn outside the border box; that correction is in the file’s header rather than quietly applied.
Then it found a real one. The focus ring on the record rail measures 1.93:1 in the shipped dark theme, on five pages: the rail is the only full width inverted surface in the product, and the ring’s pair is computed against the page ground, so on an inverted surface the pair inverts with it. Not fixed here, and a row in backlog.md.
2 themes
0 with no ring
1 below 3:1
zoom.mjsstage 13Two different things, and confusing them is the reason this file exists. Browser zoom at 200%, where every CSS pixel doubles and the layout must reach its narrow arrangement exactly as it does when the window is dragged. And separately the reader’s own font size at 200%, which is what a breakpoint in rem is FOR: a person reading at twice the default sits at a desk width with a phone’s worth of text in a line.
The width sweep proves the layout holds when the WINDOW changes. This asks the question WCAG 1.4.4 actually asks, which is what happens when the PERSON changes size.
Its first run reported every screen in the product, because it counted the identity strip and the navigation inside it as two carriers. The stage 10 sweep had the right selector all along and this one had written its own.
66 pages
0 failures
route.mjsstage 13Does the handoff lead anywhere. Every link in the README and on the handoff page followed to a file on disk; the click distance from the root to any artefact of the handoff; the sign ]( in the rendered html, which is a markdown link that survived into a page as its own source code; and the handoff page read at a measured 360.
A dead route is not findable by reading. A link to a file that moved at stage 08 looks exactly like a link to one that did not.
Its first run reported every artefact as unreachable, because it seeded the roadmap registry after walking the tree instead of before it. The finding was the instrument’s, and the walk order is now a comment in its header.
13 internal links
0 dead
0 beyond two clicks
diverge.mjsstage 13The grey corpus against the coloured one, both rendered, at 1440 and at a measured 360, rooted at .wf-screen against .screen with the documentation panel excluded. Six measures per pair: the zone signature, the element count, the tag only sequence aligned by LCS, the heading tree, the visible control count and the text runs. Classes are ignored in the tag diff, because the stage 08 rename map changes a name on every coloured page and changes no tree.
It exists because a claim asserted twice and never re-run went false in silence. “Every coloured page holds a structural diff of exactly zero against its grey original” was measured at stage 06, re-asserted at stage 07, and then stood in seven files in the present tense for two stages. Nothing in this folder compared the two corpora: census, compose, screens and rules each take a corpus as an argument and read one at a time, and migrate and refactor compare design/ against a design/ baseline.
A divergence is not automatically a lag. The grey is frozen, so every difference was made on the coloured side, and it is ahead, deliberate or drifted. Ahead: the stage 12 heading ruling, on 24 pages. Deliberate: escalate-from-expired drops Accept, because an expired snapshot no longer offers it. Only the third is a defect, and this file does not tell them apart.
62 pairs
45 diverged
0 zones moved
The rule they were all written under
An instrument that has never found anything is not a clean result. It is an untested instrument.
That sentence is in CLAUDE.md because it was paid for: a search meant to catch a rule declared twice compared inline against inline and never inline against the shared sheet, and reported clean for two steps while ten rules were duplicated. Every instrument here has a paragraph in its own header naming the defect it failed to catch on its first run, and three of them were corrected during stage 09 alone: one counted a contents page as a screen, one measured a rule other than the one it was named after, and one compared two rectangles at a scroll position instead of across a column. The twelfth exists because a defect got past all eleven, the thirteenth because three widths cannot see what happens between them, and the fourteenth because a screenshot cannot see a movement at all. The fourteenth then found three defects in itself before it found one in the product, which is the same lesson in a smaller box. The fifteenth is the only one that reads a file rather than a browser, and it is the only one whose subject is not the product but the people building it. The sixteenth checks a document, because the coverage map is the one claim in this case study that cannot be verified by looking at it. It found nothing on the product on its first run, and one defect of its own before that: it read the registry by slicing from the first occurrence of the global’s NAME, which in a file that opens with three paragraphs about that name means handing prose to a JavaScript parser. And the seventeenth is the oldest of the three: the census walk existed at stage 08 and was never a file, so the section that reported it said “Reserved” for four stages. Writing it down is what let it run over the whole product instead of the sample, and it found on its first run what four stages of instruments had not.
The full log of what each pass found, what was fixed, and what was withdrawn on verification is the critique log. How the last ten screens were built, by whom, and what they had to ask is the rollout journal. What the system knows it does not do yet is the backlog, and every row of it names who closes it.