The proof
This page is not part of the system. It is the evidence ABOUT it: what the stage promised, what it actually moved, who found what, and the three places the numbers are still open. A system that cannot be audited is a preference with a stylesheet.
The pixel comparison, and why it has no photographs
The obvious form of this page is a wall of before-and-after screenshots, and this project decided against exactly that on 2026-08-11. 120 state snapshots came out of this kit that day, framed as measurements and honest about it, on one argument: a value moves and every picture of it is quietly stale until somebody re-shoots it, and a stale picture is worse than no picture, because it looks checked. A page of proof built from the one artefact the project has already rejected would be the least trustworthy page in the folder. So the comparison here is the thing a screenshot never carried: the element count, the shape of every difference, and the named row that explains it.
The raw material is on disk and re-runnable: screens/pixel-before.json, screens/pixel-after.json, screens/pixel-brand.json, screens/ledger.txt and screens/brand-ledger.txt, each beside the script that produced it.
Round one, step 8: the sample moves onto the system
| page-viewports compared element by element | 52 of 56 |
| elements compared | 3588 |
| distinct declared shapes of difference | 83 |
| explained by: consolidated drift (step 3) | 1 shape, 10 occurrences |
| explained by: the review of the foundations (step 4) | 69 shapes, 4074 occurrences |
| explained by: moved onto system classes (step 6) | 11 shapes, 69 occurrences |
| explained by: the founder's review of the components | 2 shapes, 20 occurrences |
| UNEXPLAINED | 0 shapes, 0 occurrences |
The proportions are the story. The stage that was not allowed to change the look moved 4074 values, and every one of them is two scales the founder adopted by one rule each: nearest step, ties to the larger, applied mechanically. Nobody decided value by value, which is what makes the result auditable rather than arguable.
Round two, 2026-08-12: the brand
| elements matched across the 28 coloured screens | 3774 |
| added | 168, which is exactly 3 per page-viewport on all 56 |
| dropped | 0 |
| moved | 69: 56 the brand's own slot, 7 the Free chip, 6 the Pro chip |
| other elements that changed a declared property | 0 |
The chips moved because margin-left: auto in chip.css absorbs the slot's 25.235px, and for no other reason. The rows were re-aligned by an LCS on (tag, text) rather than by document order, because the lockup inserts three elements per screen and a fingerprint keyed by position cannot survive an insertion.
Round three, 2026-08-12: the rail from 240 to 220
| did not move at all | 281: the ten .app.flow screens, which replace the grid with a flex column, plus everything inside the rail |
| slid left, width unchanged | 301 at -20px, 167 at -10px |
| slid left and took the width | 216, 214 and 129 in three shapes |
| changed height | 2, and both are one improvement |
| horizontal overflow anywhere | 0 |
The two height changes are on subscription-detail-unrecognized, where the line "All we have is how it appears on your statement" stopped wrapping. A block 21px shorter and the statement descriptor 21px higher. That is what the width was for.
Nothing on this page is unpaired. All 28 coloured screens were compared in all three rounds. There is no screen without a comparison and therefore no row here reading "unverified".
The critique, by class, and who found what
Two instruments, taken independently and merged afterwards, which is this project's rule rather than a flourish. Codex read the source in read-only and owned everything falsifiable in a file; this pass owned what only a browser can answer. The column that matters is the last one.
| Class | Found | Who | State |
|---|---|---|---|
| Frozen folder modified | 1 | Codex | fixed: the system script came off three grey pages, the structural markup stayed, and CLAUDE.md gained the rule that was missing |
| Registry against reality | 2 rows, 13 axes | Codex | fixed in both the inventory and _nav.js |
| Stale count | 1 | Codex | fixed: 55 and 18 became 57 and 20 |
| Declared list with no idle control | 2 + 4 one-offs | Codex, and separately Claude | fixed: the census re-counted, the one-offs given a status column, two of them found CONSUMED by the renaming map rather than waiting |
| Shell desync | 1 | Codex | fixed: four destination icons in shell.html carry their own class again |
| Primitive with no consumer | 1 | Codex | KEPT with the reason at the value: a scale is declared whole, and an unused step is not an unused role |
| Icon coverage | 1 | Codex | fixed: the brand mark is accounted for on the icons page and pointed at its own |
| Two roles collapsed in the dark theme | 2 | Codex | closed 2026-08-12, the sixth named source. One was real and mirrored: --bg-callout now reads --canvas-dark, which is what the light theme has always said, and its separation from the recess went from 1.00:1 to 1.22:1. The other is identical BY REQUIREMENT and left alone with the reason at the token |
| One token doing two jobs | 3 | Codex | closed 2026-08-12. --control-accent, --bg-chip-accent and --text-chip-accent, each keeping the value it split from, in both themes. Zero pixels |
| Literal geometry | ~28 files raised, 18 real | Codex | closed 2026-08-12. 11 were widths and positions, exempt by this system's own rule. 16 of the remaining 18 were one thing: the focus ring, written out in 17 files, now the primitive --ring, one name for the width and the offset because they must move together. Two literals stay, both already owed --tap-rail since stage 07 |
| Role surface | 2 | Codex | open, and one is contested: a mask painted through background-color IS how ink is drawn |
| Token comments without origin | 31 | Codex | open |
| Old path still referenced | 5 | Codex | dropped at verification: all five describe the migration chain in the past tense, and that chain is what the rule requires to be recorded |
| A role that has never rendered | 1 | Claude | open: --bg-trial and --text-trial paint .chip.trial, whose only wearer is the grey landing |
The landing, and .landing | 2 | Claude | open, and the largest: 48 of 57 component files carry a .landing selector that no page loading the system wears. Re-derived at the backlog pass: 44 of the 48 have never rendered anywhere at all |
| The page against the screen | 45 raised, 1 survived | Claude | 44 dropped at verification, listed with reasons |
| The format of a component page | 0 | Claude | 57 pages, six blocks each, in order |
| State snapshots | 0 | Claude | the class is empty by the founder's decision of 2026-08-11 |
| Accessibility, the measurable half | 0 | impeccable | see below |
The overlap between the two instruments was ZERO, and that is the finding this table exists to carry. Codex returned 24 rows and this pass returned 4, and not one of them was the same row. A browser pass cannot see a contradiction between two files; a source pass cannot see a computed value. The rule that says "two instruments" is not redundancy, and the proof is that the single most serious finding of the step, a violated freeze, was invisible to the instrument that runs every session: the standing check looks for UNCOMMITTED changes in wireframes/, and this change had been committed three steps earlier.
What was dropped, and why that number is healthy. Of 45 computed-style disagreements this pass raised, 44 did not survive re-reading: 25 were min-height: auto against 0px, which is whether an element is a flex item and not a paint; two caught the skeleton mid-pulse; the rest compared a host-scoped class against a different host. Seven page-format warnings matched the word :hover INSIDE a comment that says no hover is declared. An instrument that returns the same answer at every input is not measuring the input.
The token audit, was and became
| Number | |
|---|---|
names declared in :root | 105 |
| primitive, raw values | 71 |
semantic, roles read through var() | 34 |
semantic roles with a pair in [data-theme="dark"] | 34 of 34 |
| roles present in only one theme | 0, verified name by name by the second instrument |
| semantic roles with no consumer | 0 |
| semantic roles with exactly one consumer | 8, each judged: seven are real roles, the eighth has never rendered |
| primitives with no consumer | 1, --space-56, kept as a step of a whole scale |
| levels | two, not three |
--color-focus, the token an entire keyboard depends on, measured in both themes rather than assumed: 6.23:1 on surface and 5.56:1 on canvas in the light theme, 6.82:1 and 7.51:1 in the dark. The non-text threshold is 3:1. Had the pair been left for later, the dark ring would have been the light petrol at 1.9:1: present in the file, invisible on the screen.
Consolidation, in numbers
| rows before the consolidation of step 2 | 68 |
| components after it | 57, as 20 atoms, 23 molecules and 14 organisms |
| of those, arrived from a DECISION rather than from the grey corpus | 2, the brand mark and the brand wordmark |
| modifiers deleted for having no wearer | 2, .btn.inverse and .btn.compact |
component files, each with a page, a registry row, an inventory line and an @import in its own level group | 57 of 57, verified by the second instrument |
The counting rule has a blind spot and it is named rather than hidden. This inventory reads what the product already draws, so it can only ever find what has been drawn. A brand, a splash screen, a first-run tour and an error page that nobody wireframed are invisible to it by construction. The brand is the proof: it occurs zero times in the grey corpus and no amount of reading it would ever have produced the mark.
The control census, both measurements
| pages walked, in two viewports | 28 |
| control records | 594 |
| distinct control forms, as class strings | 25 |
| families after resolving modifiers | 24 |
| pages that yielded no control at all | 1, verified rather than assumed |
This reads as the opposite of the pixel comparison, and the pair is the point. The comparison says nothing changed by accident. The census says the set of forms was closed on purpose. One is a promise about the past and the other is a promise about the future, and neither is worth much without the other.
Six forms were tracked across three corpora until they came clean. Re-counted at step 9 and the answer changed: two rows still credited the frozen stand with 2 inverse and 1 .is-disabled that had been deleted underneath them. A census is a measurement and a measurement has a date; those two carried the number and not the date.
The dark theme, and the three instruments that caught it
The theme is not one check, and the three numbers below were produced by three different instruments looking at three different things. That is why they are reported separately rather than summed.
| Instrument | What it can see | Found |
|---|---|---|
| the stress test on the full showcase, step 7 | NEIGHBOURHOOD: two surfaces that separate in one theme and merge in the other | the chrome's own ink at 1.09:1 on its own panel, invisible because every row inside happened to declare a colour of its own |
| the second pass over the screens, step 8 | a component that reads a primitive directly and so never hears the theme | 0 escapees in the components |
| the source pass, step 9 | a role declared in one theme and not the other, name by name | 0 in either direction, across all 31, and separately 2 collisions: three fills that are distinct in the light theme and identical in the dark, and one more pair the same shape |
The two collisions are open and they are here rather than in a backlog file, because this is the page that claims the theme works. Three roles that a person can tell apart in daylight and cannot tell apart at night are a defect the light theme can never show you.
The dry run: the rollout that has not happened yet
The rollout moved to stage 12, and the main test of the system's completeness moved with it. This is the cheap replacement: take the densest screens the system has never been shown, and check on paper whether it could build them. Nothing was built.
| Still-grey screen | Class tokens | In the system | Through the renaming map | Not covered |
|---|---|---|---|---|
index, the landing | 60 | 18 | 3 | 36 |
cancel-guide | 30 | 21 | 6 | 0 |
alerts | 29 | 22 | 4 | 0 |
data-privacy | 29 | 20 | 5 | 1, a declared one-off |
connections | 25 | 16 | 6 | 0 |
Four of the five need nothing. The system covers the densest screens it has never seen, which is the answer this test was built to give and the good one. All 27 still-grey pages were ranked by distinct class tokens and these are the top five.
The whole gap is one page, and the re-run corrected two things this page claimed. It is 36 uncovered, not 37: .stage-flush is a modifier of the reviewer chrome class .stage and counts as chrome. And not all of them are the lp- namespace: 24 of the 36 carry the prefix and 12 do not, so retiring the lp- twins closes two thirds of the gap rather than all of it. All 36 are landing-only, so "the gap is one page" survives. It is not undeclared: the inventory names it twice and ends with the sentence "the kit takes the two-host pattern as the rule and retires the lp- twins". That retirement has not happened, and the number on the other side of it is 48 of 57 component files carrying a .landing selector that the COLOURED corpus never wears (0 of 29). The grey landing does wear it, at wireframes/index.html:14, and that page loads _wf.css rather than the system. Half of forty-eight files is written for a page that does not exist.
The accessibility half, measured on the screens
images with no alt | 0 |
| inputs with no label | 0 |
| breaks in the heading hierarchy | 0 |
pages with no main landmark | 0 |
pages with no lang | 0 |
| duplicate ids | 0 |
onclick on something that is not a control | 0 |
:focus where :focus-visible was meant | 0 |
| tap targets under the floor | 0 in the product. 35 flags were the reviewer's own chrome, 6 were inline links inside a sentence, which WCAG 2.5.8 exempts by name, and 1 was a 20px checkbox inside a 44px label that is the actual target |
28 coloured screens, both accepted viewports, both themes. The eleven ARIA findings raised at the molecule round are not counted here: they were moved to stage 12 by decision, and they are in docs/backlog.md.
Stage 09, the whole of it: three patterns, one screen the system had never seen, two instruments
This section was added on 2026-08-13 and it is the second round of proof on this page, not a second edition of the first. Stage 08 proved that a sample of screens could be moved onto the system without moving a pixel. Stage 09 asked two harder questions and both have a number: can a composition be lifted out of the components without the screens noticing, and can the system build a screen nobody built for it.
Was and became, step 1: eight coloured pages move onto three patterns
| Measured | Result |
|---|---|
| page-viewports compared, 360 and 1280 | 56 |
| element count identical on every one | yes, 56 of 56 |
| elements compared | 3998 |
| elements with any change | 137 |
| changes that are the margin changing owner, interruption | 24 occurrences on 12 page-viewports: marB 24 → 0 on the announcement, marT 0 → 24 on what follows it. Same gap, different owner |
| changes that are the margin changing owner, act-foot | 14 occurrences on 12 page-viewports, the same shape at 16px |
| changes that are the named decision, the 16px foot gap at every width | 5 occurrences on 4 page-viewports, plus 45 on the one page already taller than its pane, where the 16px goes into the chart instead of off the bottom |
| changes that are the skeleton pulse caught at another frame | 56 occurrences on 3 loading pages, opacity 1 against 0.9999. No declared value involved |
| unexplained | 0 |
| at 360, the narrow end | no geometry moved at all. Under 900 the screen is normal flow, adjacent margins collapse, and 24 with 0 collapses to 24 the same way 0 with 24 does |
Re-runnable, beside the script that made it: screens/ledger09.cjs, screens/ledger09.txt, screens/pixel09-before.json, screens/pixel09-after.json.
Step 5: the screen the system had never seen
The self-sufficiency test has no before, so it cannot have a pair, and the number that replaces the pair is what was NOT added. Alerts and its three states were built on 2026-08-13 out of design/system/ alone: 44 distinct system classes across four pages, 0 new CSS files, 0 new tokens, 0 new component variants, 0 new lines of microcopy, 0 style attributes and 0 page-level style blocks. Structure and copy are character-identical to the grey originals, which Voice owns and the wireframes freeze.
| What the build changed in the numbers of this system | Was | Became |
|---|---|---|
coloured pages in design/ | 28 | 32 |
| coloured screens | 7 | 8 |
| components with no coloured render at all | 9 | 7 |
.alert, the largest hole in the sample | 0 coloured pages | 2 pages, 12 items, which is every item the grey has |
<summary>, the disclosure form of the panel | 0 coloured pages | 1, and it is the only one in the product |
| hosts on the interruption's axis | 3 | 4, and the fourth cost no CSS |
| the interruption applied to the new column | 24px gap with the class and 24px without it, at both viewports. Applying it moved nothing and only moved who owns it | |
Step 6: the critique, by class, and who found what
Two instruments again, taken independently and merged afterwards. Codex read the source read-only and owned everything falsifiable in a file; a browser and scripted pass owned what only a running page can answer. Twelve findings held, two were dropped at verification, and the eleven that look like eleven defects are one defect with eleven addresses.
| Class | Found | Who | State |
|---|---|---|---|
| A count that lies about the coloured corpus | 21 components, ~40 places | Codex found 3, Claude found the rest by recount | fixed. The corpus grew 28 to 32 pages and 7 to 8 screens in one commit, and every hand-written footprint went stale with it. The structural half is an open row in docs/backlog.md: nothing recomputes those numbers |
| Documentation that lies about the system | 15 occurrences, 12 files | Codex | fixed: ten pages and two CSS files said another component "is not built yet" when all 57 are built. Each now links the page that exists |
| A comment that names a line number in another file | 1 | Codex | fixed, and it closed the open half of a stage 08 backlog row: chip.css was still describing a repair as pending that had landed |
| The IA node id of a new page | 1 | Codex | fixed: the error state was labelled 3.8.2, and ia/docs/sitemap.md gives 3.8.2 to loading and 3.8.3 to the error. My error, caught in the source |
| A state written with a value | 2 | Codex and Claude, independently | fixed: outline-offset: -2px in two focus rules now reads calc(var(--ring) * -1). Same pixel, derived instead of typed |
| A callout anchor that is not marked up | 5 of 257 | Claude | fixed in the stand's own stylesheet, once, rather than five times in markup. Deliberately not by wrapping prose in code |
| Malformed markup, found by the pass that checked the repair | 3 | Claude | fixed, and all three predate this stage: a callout closed with </td> and a doubled </section> on Select, a callout list closed with </p> on the Concept stand |
| A state token with no pair in the other theme | 0 | Codex and Claude, counted separately | 34 of 34 paired. Both instruments walked :root against [data-theme="dark"] name by name and neither found a one-sided token. --color-focus measures 6.23:1 light and 6.82:1 dark against a 3:1 floor |
| A component that renders differently on its page than on a screen | 0 | Claude | 16 components measured on the stand and in the product, 16 computed properties each: 0 differences. Four apparent ones were min-height and display, both host-derived, and vanished with the hosts matched |
| A state that moves the layout | 0 | Claude | every :hover, :active, :focus-visible and :disabled in 60 files, checked against the geometric properties |
| A usage rule with no source, and a component it names with no Limits | 0 both ways | Codex | 11 rules, 11 sources filled, 11 components named and all 11 carrying the sub-item, and no Limits item without a rule behind it |
| The format of a page | 0 | Codex | 57 component pages carry all six required ids; 3 pattern pages carry the six with where in place of states |
A pattern with a style of its own, or an @import out of order | 0 | Codex | no colour, font or radius in patterns/; all three imported after all 57 components; the registry group after Organisms |
wireframes/ changed by a character | 0 | Codex, twice | the frozen folder is byte-identical to HEAD |
| Accessibility, the measurable half | 0 | impeccable | the four new pages carry the same clean result as the 28: no unlabelled image, no unlabelled input, no heading break, no duplicate id |
Two findings were dropped at verification, and the reason is on the record so neither returns. range-picker.css:114 writes font-weight: 600 inside a disabled-and-pressed rule, which reads as a state written with a value: it is not, because this system has no weight scale at all. Weight is a literal in every one of the 57 files, by the rule that colour goes through a role and geometry through a primitive and weight is neither, so tokenising one occurrence would have opened a third level to serve one line. The detector's em-dash-overuse, raised on 34 files, counts -- in running text, and the running text of this project is made of custom property names: the colour page holds 378 double hyphens and zero em dashes.
The overlap between the instruments was four rows out of twelve this time, against zero at stage 08, and the change is not noise. At stage 08 Codex read files and the browser read pixels, so they could not meet. At stage 09 both were reading the same thing, a set of claims in prose, and prose is falsifiable from either side. Where they met they agreed; the eight rows they did not share are still split the old way, with the source findings on one side and the running page on the other.
The numbers this stage leaves behind
| components in the system | 57, unchanged: stage 09 added none, which is the point of it |
| components with declared interactive states | 21 |
| components that are not interactive, checked rather than assumed | 36, each with the sweep that found no anchor, no button and no target inside it |
| patterns extracted | 3: the interruption, the action foot, the list column |
| candidates waiting for a third screen | 4, named on the patterns page with what each is waiting for |
| usage rules, each with its source filled | 11 |
| patterns with no coloured render | 0, and the one host with none is said out loud: the dialog sheet, on three grey pages, proved at the rollout |
| backlog | 60 rows, 23 closed, 37 open. The self-sufficiency test added 4 and the verification pass added 2, which is the honest shape: a test that returns an empty backlog was run as a formality |
Stage 10, Responsive: the promise is asymmetric, and both halves are measured
Added 2026-08-13, and it is the first round of proof on this page whose promise is not "nothing moved". Stages 08 and 09 owed zero difference at both viewports. A stage about width cannot owe that: at 360 the mobile layout is the base of a mobile-first product and it must not move at all, and on a wide screen the whole point is that things move. So the promise splits, and the second half is enforced differently: every desktop difference must be explained by a named row, and an unexplained one is exactly as much a defect as any difference at 360.
The narrow end: 32 pages, zero difference, box and ink
| Measured at a real 360 | Result |
|---|---|
| coloured pages compared against the pre-stage baseline | 32, every one |
| how a page is compared | the position and size of every element inside the shell, plus the rectangle the text itself paints, which catches a re-wrap that leaves the box alone |
| pages identical on the box | 32 of 32 |
| pages identical on the ink | 32 of 32 |
| horizontal scroll anywhere | none |
| the baseline | git archive HEAD into a second served copy, so the two versions render side by side from the same browser on the same origin |
| where the instrument lives | screens/width10-harness.html, and its output as run on 2026-08-13 in screens/width10.txt. No screenshots, on the decision of 2026-08-11 recorded at the head of this page: a picture of a measurement goes stale silently and then looks checked. Open the harness over a local server and press Run |
It failed once, and that is why it is worth running. The first pass lit up on six pages. Two of the four reading-measure fixes had been written as a comment appended after an already-closed comment block, which left prose in the stylesheet and a stray */ after it; the browser dropped what followed. The 360 check caught a broken stylesheet that the eye read as a normal page. A comment-balance check now runs over all 65 stylesheets and is clean.
The wide end: 32 pages changed, and every change has a row
| Measured at 1440 | Result |
|---|---|
| pages whose geometry changed | 32, and 688 element boxes among them |
| pages where the ink moved, meaning a line actually re-wrapped | 19, and 277 text rectangles |
| causes, all named on the width page | four reading measures folded into one token; four prose blocks that had no measure at all; two dead caps repaired; two grids that stopped declaring a count; the groups column set; the detail screen's columns |
| unexplained | 0 |
| the biggest single move | the muted line under the chart on History and Trends: 1300px to 459px, from 150 characters a line to 53 |
The instrument: a sweep, because a defect lives between the points
| The width sweep | Result |
|---|---|
| widths per page | 50: 320 to 1600 in steps of 40, with 10px steps around both points, around the container threshold and around the two widths where the column set changes its count |
| total measurements | 1600 |
| document-level horizontal scroll | none at any stop, and that check alone would have found nothing: the pane is its own scroller past the tablet point, so a layout that does not fit scrolls inside it while the document stays clean |
| elements leaving the viewport | 18 stops before the fix, all on the six Subscription Detail pages at 900, 910 and 920. 0 after |
| the carrier of top-level navigation | exactly one at every stop on every app-frame screen, four links, never two. Confirmed in the markup: 22 screens carry one .tabbar, the 10 flow screens carry none |
| the worst reading measure found | 150ch (History and Trends at 1600), then 82.8, 78.9, 72, 69, 67.8 |
| the worst reading measure now | 60.7ch, the prose card, which is a named 560px decision and is written up in docs/backlog.md rather than quietly changed. Everything else is 53ch or below |
Two instruments, and what each one was blind to
| Found by | What | Verdict |
|---|---|---|
| Claude, in a browser | five defects: the 78.9ch card, the unmeasured muted line at 150ch, the unmeasured wash paragraph and promise item, the 900-to-927 valley, and a sixth element needing the > .screen > binding | all five confirmed and fixed. None of them is visible in the source: every one needed a rendered page at a specific width |
| Codex, read-only in the source | ten findings across ten mechanical classes | 7 confirmed (a literal repeating a token, four inventory rows, a stale register claim, a comment contradicting its own rule), 2 dropped at verification with the reason written into the code, 1 resolved by the stage closing |
| Claude, by grep, after Codex | eight stale claims naming 1340, the number this stage removed, in the kit pages, the shell template, the review chrome and DESIGN.md | all eight repaired. This is the class Codex was asked for and did not return, and it is the reason the two instruments are run and merged rather than trusted one at a time |
| The a11y pass | the shell template declared <main class="app"> around <main class="screen">, a main inside a main, while all 32 built screens use a div | repaired in the template. Nobody would have seen it until stage 12 built twenty screens from it, each with two landmarks |
| The a11y pass, not this stage's material | the project sidebar's section links read 2.65:1 on white at 13px, and the dimmed roadmap rows 3.37:1. Light theme only; the dark theme is clean | reported, not fixed. It is /_nav.css, the project registry chrome, and it is on every stage page of the project: a colour decision for the founder rather than a stage-10 edit |