Handoff
Stage 13, the last row. What is being handed over, where to start reading it, how it behaves, what each screen is made of, and what is still open - each of them counted by an instrument rather than remembered.
Handed over on 2026-08-23, and brought level with the live product on 2026-09-30. The figures on this page are the handoff's and keep that corpus. The product built from this package is live at tendd.co; what it changed since, why, and what this repository did about it is the live product, since v1.0.
Start here: three doors and the entry they hang off
Everything below is reachable from these four addresses, and the same four are written in README.md in the same pass, because a README is the page a person opens on GitHub and sending them one click further for the address of the product would be the route into nowhere this stage exists to fix.
tendd.co
The product in colourEvery screen, 17 of them plus five public documents, across 73 pages since the sync of 2026-09-30 (58 at handoff). Opens on the landing, node 1.1.sergiodesign4u-dot.github.io/Tendd-Fintech/design/index.html
The design systemThe stand that shows it: why it is like this, a page per component, the foundations and the growth rule.sergiodesign4u-dot.github.io/Tendd-Fintech/design/kit/why.html
The repositoryThe whole thing, including everything no page shows: the decision log, the instruments, the raw measurements.github.com/sergiodesign4u-dot/Tendd-Fintech
The project entryThe roadmap: thirteen stages, each with its own account. This is where the sidebar on every page points back to.sergiodesign4u-dot.github.io/Tendd-Fintech/
| If you want | Open |
|---|---|
| Every screen, in colour, with its states | design/overview.html |
| How the product behaves, flow by flow | handoff/docs/behaviour.md |
| What any one screen is assembled from | handoff/docs/map.md |
| Where accessibility lives and how to re-check it | handoff/docs/a11y.md |
| What a stranger could not work out on their own | handoff/docs/onboarding-gaps.md |
| The rules this repository is built under | CLAUDE.md, loaded into every session |
| Why any single value is what it is | docs/decisions.md, newest on top, never edited |
| The visual language as a document | DESIGN.md, generated from the shipped code |
| Where every figure on a screen comes from | docs/bank-connection.md |
| To lift the system into another project | design/system/ plus its own CLAUDE.md |
| To review this as an outside critic | AGENTS.md |
Eight markdown files have had no visible place at one point or another, and this
section is what closed them. Four were closed in August: AGENTS.md and
DESIGN-artifacts.md, the draft the design document grew
out of, and research/docs/product-model.md
and research/docs/quality-check.md, the last
of which was named by nothing at all, not even by another document. The five documents of
this stage joined them the day each was written. It is worth saying out loud rather than
quietly enjoying: handoff13.cjs counts "md files named by at least one html",
this is an html, and the naming is the sentence you are reading. An artefact with no
visible place does not exist for the person who decides, and three of these four fell
out of the count again the moment this page was rewritten, which is the whole argument for
running the census rather than remembering it.
handoff/docs/map.md counted as shown by eleven pages, every one of which was
naming ia/docs/sitemap.md. Corrected on 2026-08-23 to a boundary match: a
preceding slash is fine, a preceding letter is not. The instrument whose whole job is
finding an artefact nobody can reach had been unable to see one for five days.The package boundary: what this is, and what it is not
This is a clickable product, not an application. There is no data layer, no API, no router, no persistence and no session. Nothing here talks to a bank, charges a card or remembers anything between two page loads. A reviewer can walk every flow end to end and a developer can read every rule, and neither of those is running software.
Every STATE is its own file. 17 screens carry 39 states between them and that is 58
html files: home.html, home-loading.html,
home-empty.html, home-error.html, home-few.html,
home-one.html are one screen. A real build collapses those 57 files back into
17 screens with states, and nothing in this repository says who owns that translation
- it is the first decision a receiver makes and it is named here because it was written down
nowhere.
| What is real and ships as it stands | What is not here at all |
|---|---|
| The design system. css that is production code, lifted and measured. See the lift test below | A backend. No database, no auth, no Plaid, no Stripe, no telemetry. The stack in CLAUDE.md is a hypothesis and nothing here depends on it |
The copy. Every string on every screen is final and owned by voice/docs/microcopy.md, which is where a new one comes from | State. A filter does not filter, a form does not submit, a switch does not persist. The one exception is the trend chart's cursor |
| The structure. One IA node per screen, one flow per journey, and the behaviour spec that joins them | A router. Navigation is an anchor from one file to another. What that becomes is the receiver's decision |
| The behaviour. Steps, states, edge cases and validation, every row naming its source | Anything out of MVP. Budgeting, investments, a native app, bill negotiation, bill pay, a household view. Named in CLAUDE.md |
Two scripts exist and they are not the same kind of thing.
design/system/behaviour.js belongs to the system, is the only javascript it
owns, travels with it, and draws the trend chart's cursor: 7 of the 57 screens load it.
design/story.js belonged to the landing and drove its scroll choreography:
1 screen loaded it. Everything else on every screen is html and css.
Updated 2026-09-30: the landing is now Light 2 and its motion is the product's own,
bundled into design/landing.js (three.js and Lenis, generated, with its rebuild
command in its banner), plus design/auth.js for the sign-in dialog; story.js is
gone. See the live product, since v1.0.
Which theme is the main one
Light is the main one, dark is complete, and since 2026-08-23 a person can choose.
The light palette is the one written on :root; the dark palette is written on
[data-theme="dark"] and overrides it role by role. Every semantic role in the
system exists in both or it does not exist, which is a rule of stage 08 and is checked
rather than promised.
The switch you see on a screen is not the one this is about. That one belongs to
design/_screen.css, the reviewer's chrome, together with the panel that names
the screen and its IA node. Chrome is not shipped, and the map of the product deliberately
does not include it. The PRODUCT's control is a switch in a group of its own on Settings,
How Tendd looks, above the rows that leave the screen.
prefers-color-scheme anywhere: a capability that was fully paid for and a
feature that had never been specified. Three readings were available and the founder chose
the third: follow the machine, stay light-only and say so, or give a person the choice.
The two new lines went through Voice before they went on a screen, the block went into
IA node 6.16, and the structure went into the frozen grey twin with the decision written
into that file, which is the only way this project allows the structure contract to
change after Voice.And the product still does not follow the machine.
prefers-color-scheme stays at zero occurrences in base.css, in
tokens.css and in both chrome stylesheets, deliberately: a person who has
chosen a theme has said what they want, and a money app that changes appearance because
the sun went down is doing something its user did not ask for.
What a real build still owes is one line in a data model, and it is written in behaviour.md rather than guessed here: where the choice lives. On the account, so it follows a person to a second device, or in the browser, so it is per-device. The switch in this package draws the state and does not keep it, exactly like the four alert switches above it, because this is a clickable product with no runtime and no persistence.
The route through the repository
Thirteen stages, one folder each, and every one of them opens on a page rather than on a folder listing. The order below is the order the work happened in, which is also the order that makes it readable: each stage's material is the previous stage's answer.
| Folder | What is in it | Where to start |
|---|---|---|
index.html | The entry page. It renders the roadmap from _nav.js, which is the single registry of the project: a stage is switched on in one place and nowhere else | index.html |
research/ | Who this is for and what they are trying to do: the market, four personas, the jobs, and the journey before and after | research.html |
ia/ | The structure: a sitemap, the flows, one page per node with the block bank, the seven global elements and one accessibility contract | structure.html |
wireframes/ | The grey structure contract, frozen since Voice, and the answer to why every screen exists twice. One grey page per state, real copy, no colour. It is read-only and it is not a source of truth: after the rollout the coloured screen is the product, and this folder is process history | overview.html |
voice/ | The voice, and the line inventory that owns every interface string in the product | voice.html |
design/ | The product in colour. The folder opens on the product home; the map of all 58 pages is the hub beside it | index.html, overview.html |
design/system/ | The code of the design system, liftable whole and carrying its own CLAUDE.md: tokens at two levels, the base, the components, the patterns | CLAUDE.md |
design/kit/ | The stand that shows it: a page per component, the foundations, the architecture and the backlog | why.html |
handoff/ | This page and its five documents. It adds what no other page answers and it repeats nothing | you are here |
| at the root | CLAUDE.md the rules, docs/decisions.md the log, DESIGN.md the language read out of the code, AGENTS.md the entry for an outside critic, README.md the index | README.md |
The route is checked and not asserted.
design/kit/screens/route13.cjs resolves every href and
src in all 245 pages against the file that writes it, every markdown link in
all 72 documents, and then walks the project in a real browser from the entry page: 3522
links, 0 dead; 55 markdown links, 0 dead; 0 pages carrying a markdown link pasted into
html. A browser is not optional there, because the roadmap on every page is drawn by
_nav.js at runtime and a static read of the entry page sees none of it.
How it behaves
A screen shows a state. What it does not show is what leads to that state, what happens at the edges, and what a field will refuse. That is handoff/docs/behaviour.md, and the thing that makes it a specification rather than a plausible description is the source column: every row names the file it was read from, and a row with no source is not written into the spec at all. It goes to a list addressed to a person.
The roll-call closes at five. Flows in ia/docs/flows.md = described 5 +
deliberately not 0. The five sources are the screen file, the flow, the IA node, the figures
document - approved as a source on 2026-08-23, having been used and marked until then -
and the grey wireframe - and the last of them is used zero times in all five
flows, which is the honest version of "the grey is not a source of truth": it was
available and it was never needed.
Nine states cannot be reached by clicking, and every one of them is right. The click graph reports them, and the spec answers each with the real trigger a build will fire: a bank connection that goes stale, a merchant with no cancellation guide, a person who has never had an account, a renewal that fails on a card that was working. The largest single reason is that the canonical person in this package is on Free, so the two screens that only a paying account ever sees have nothing to link them.
What each screen is made of
handoff/docs/map.md is read off the code by
design/kit/screens/map13.cjs, which serves the repository to a browser, walks
all 57 screens, and writes its raw output beside itself. It is in three parts and the third
is the working one.
| Part | Rows | What it answers |
|---|---|---|
| A. Screen to zones and components | 57 | What is on this screen, how many regions its markup declares, and how many of them the line inventory addresses |
| B. Component to the tokens it reads | 73 | Every var() inside that component's own css, and its reach in screens |
| C. Token to the screens it stands on | 163 | If I change this value, what moves. Derived by INVERTING A and B rather than by walking the code a second time |
The inversion is two knees deep and that is the whole of why it works. A component reads colour only through a semantic role, so a flat inversion would name no primitive at all and the entire primitive level would report as unused. The chain followed is component to semantic role to primitive, in both themes, since a dark role points at a dark primitive.
The roll-call closes and the idle control is a number rather than an impression.
Screens named by the registry 57 = mapped 57, with nothing on disk that the registry does
not name and nothing named that is not on disk. Zero components stand on no screen,
asked here for the third time and against the whole product rather than a sample. Three
tokens are reached by no chain, and all three are accounted for: two are the width
points, which a container query cannot read through a var(), so the token holds
the decision and the literal in the query is its application; the third was declared idle
and kept, with the date, in the file that declares it.
Accessibility
handoff/docs/a11y.md introduces nothing. Accessibility was built at stages 08 to 11; what was missing was not the work but the PROOF. So every row carries four things and the third is the one that matters: item, where it is in the code, how to check it, and a status of exactly two words. "Confirmed" means a run on 2026-08-23 with the instrument named in the row, never a memory of stage 08 doing it, and a row for which no instrument can be named never gets the word.
Two rows were real failures when this stage measured them, and both closed the same day. Nine screens scrolled sideways at twice the default text size, all on a phone and none at desktop width; and the trend chart, made focusable at runtime and not a native control, wore the browser's own focus ring. They went to the backlog as debts, because the product was accepted at stage 12 and an edit would have voided every pixel comparison it stands on - and then the founder lifted the freeze. Nine became 0 of 114 and the ring is the system's, with zero boxes moved at 360 or at 1440.
rem
reads the viewport in units of the reader's text, so one threshold releases a label
that may not fold, and it releases it under zoom and nowhere in the product's own sweep.
It is the first time that refusal has paid for itself.main landmark and 0 do now.And the instrument had to be made deterministic before any of its numbers could be believed. Its first two full runs reported catastrophic contrast failures, sixteen in one and twenty-eight in the next, on different screens each time, and none of them reproduced in isolation. The cause is in the system: every interactive element transitions its colours over the state duration, so asserting a theme starts a cross-fade across the whole page and a sample taken inside it compares one theme's ink against a surface halfway to the other. The contrast and focus passes now run with reduced motion emulated, which makes the product's own reduce block zero every duration; the motion pass needs a context of its own, since measuring the baseline inside a reduced one would report the reduce block as the product's motion, which is a check that always passes and proves nothing. Two consecutive runs now return byte-identical output, and that determinism is part of what "confirmed" means.
How to add a feature
There is a ready prompt for it: handoff/docs/one-shot.md. Copy it, put your feature in the one blank it leaves, and hand it to whoever is building - a person, or a model in a fresh session. It leads to all three documents above, carries the growth rules word for word from the files that own them, and repeats no value from any of them.
It was written to be tested, and it was tested twice on 2026-08-23 by two readers with no memory of this project, on two different features. Each was given the prompt and nothing else, and each was sized before the run rather than after it - four states and three - because a reader who does not stumble on a feature of two states has proved nothing. Both built something the instruments accepted: zero style violations, zero contrast or focus failures across the grown corpus in both themes, zero prose past the reading measure at fifty-eight widths. Neither screen is in the product, and the reason is in "deliberately not done" below.
The second list is not shorter, and saying so is the point of running the exam twice. Thirteen readings became twelve, which is no improvement worth claiming. What did change is WHICH readings: the second reader named five paragraphs of the prompt that did real work and said what each one saved it, and only one item on its list repeated a difficulty from the first. That one is the paragraph the first run's own fix created - the test for whether a feature is a new screen or a state of an existing one - and the second reader had to break the tie itself because the rule and the cautionary tale beside it pointed at two different tables. It is a three-row table now instead of a sentence. A prompt that is measurably better and still produces a list that long is a prompt that is working and is not finished, and the honest form of that is a number rather than a paragraph of comfort.
What the exams found about the product, rather than about the writing, is two rows and both are in the backlog: a rewrite that the line inventory's own log records as done and that was applied by halves, still carrying the word two documents agreed to drop; and a shipped button label that breaks the voice's own rule about how a button addresses a person, in three places at once. Neither had been seen before, because no instrument compares a line in the inventory against the rules of the voice, and nobody had read the two side by side.
The census: what is on disk
Counted by group rather than by folder, because a receiver's first question is not "how
many files" but "how much of this is the product, how much is the argument for it, and how
much is scaffolding I can ignore". Every figure comes from
design/kit/screens/handoff13.cjs, whose corpus is git ls-files - what
a receiver actually gets - with a walk of the tree kept as the fallback for a copy that is
not a checkout. Neither is a list somebody typed.
| Group | Files | Lines | What it is |
|---|---|---|---|
| Product screens, colour | 58 | 8225 | What ships. 17 screens, 39 states, every one with a grey twin |
| Product screens, grey | 58 | 5951 | The structure contract, frozen since Voice |
| The design system | 77 | 20371 | 76 css and 1 js. The thing that is meant to travel |
| The stand | 87 | 20329 | design/kit/: a page per component, per pattern, per foundation |
| Stage pages | 36 | 18422 | One account per stage, this page included - which makes this the one row that moves when the page does, so it is the census's checked-in run and not a live figure |
| Markdown | 72 | 26172 | 2 loaded into every session, 70 read on demand |
| Instruments | 21 | 4424 | .cjs in design/kit/screens/, each re-runnable |
| Everything else | 162 | - | Assets, the registries, the fingerprint json a stage left behind |
Two numbers are worth reading together. The product is 8225 lines of html and the argument for it is 26172 lines of markdown, more than three times as much. That ratio is the pipeline's shape rather than an accident: every value in the system carries the decision that put it there, and a decision with no ground is the thing this repository spends its words refusing.
.gitignore and none of them ever handed to anybody. A census of a receiver's
package that counts files the receiver never gets is wrong in the one direction that
flatters it, and only a clone could have found it. The corpus is now
git ls-files, with the tree walk kept as the fallback for a copy that is not a
checkout.These figures were each one line per file too high until the critique of this stage. The census counted lines with a split on the newline, which returns an extra element for the empty string after a file's final newline, so every group total was inflated by the number of files in it. The read-only pass found it from the outside, by flagging a line count on this page against the census's own and calling the page wrong; the page was right. A census that is off by one on every file is worse than no census, because the error is invisible at any single row.
The lift test
The design system's central claim is that design/system/ is liftable
whole. It had been asserted in three places and measured in none, so on 2026-08-18 it was
measured: the folder was copied into an empty directory and one screen the system had never
seen was assembled there out of its classes alone - the shell, the app bar, the brand lockup,
the big total, the interactive trend chart, the segmented range, a prose card, a divided
list, the trust block and the tab bar.
Every url() inside the folder resolves inside it, assets/
included - which is why those four washes are part of the lift rather than decoration. The
chart's cursor was built by behaviour.js, the only javascript the system owns,
and it works in the empty folder exactly as it works in the product.
And one dependency it cannot provide
base.css asks for Inter by name and nothing in the folder provides it:
no @font-face, no file, no import. The lift test passed without noticing,
because the machine it ran on has Inter installed, which is exactly the shape of failure
a handoff exists to prevent. It is written down now in the two places a person looks -
design/system/CLAUDE.md under "What this folder does NOT carry", and at the
declaration in base.css, which is the line somebody greps for.
base.css is deliberate and safe, so a
page with no Inter is readable and not broken - it is simply not Tendd.The run surface
The census asked what a receiver needs before they can open anything, and in its first run
the answer was almost nothing existed: no package.json, one file in the
whole repository telling a person how to run something, and every instrument resolving
playwright from a global npm root - one of them from an absolute path inside one machine's
npx cache, which was the least liftable line here.
python3 -m http.server 8080 # nothing is built; every page is static html
npm install # playwright, the only dependency there is
npm run check # the rollout ledger and the quality sweep
npm run check:route # every link, and the two-click promise of this page
npm run census # this page's numbers, recomputed
Every instrument resolves playwright local first and global after, so it runs for a
receiver who typed npm install and still runs on a machine that never had one.
node_modules/ is ignored; nothing in this repository is built from it and
nothing is published.
| Instrument | What it answers | Clean? |
|---|---|---|
rollout12.cjs | Pairing, the ban on a screen carrying a style of its own, links, the registry against the code, and the template a new screen is copied from | 0 violations |
quality13.cjs | Six checks over every screen the registry names, by two widths by two themes: overflow, a control's label, a tap target, a flush distance, an empty box | clean |
route13.cjs | Every link in every page and every document, a markdown link pasted into html, the click depth from the entry page measured in a browser, and the three doors agreeing across their two places | the route holds |
map13.cjs | Screen to zone to component to token, taken off the rendered product, with the reverse list produced by inversion | roll-call closed |
a11y13.cjs | Contrast in both themes, a real tab pass, motion under reduce, text at twice its size, and the 129 pages around the product that nothing else reads | clean |
aria13.cjs | Six semantics questions over every page in both corpora: a group with no name, a label ARIA discards, a wait that does not declare itself, a control with no accessible name, a heading level skipped | 0 of 6 |
walk13.cjs | The click graph: every link inside the app across the 57 screens. Which screens leave a reviewer no way forward, which states cannot be reached by clicking, and what the chrome declares against what it stands on | 0 unanswered |
width12.cjs | The width sweep: every page across 58 widths | on demand |
edges13.cjs | Every right edge on every app screen at the widest stop, and the check behind the usage rule that says one screen keeps one measure across its states | 0 split |
pages13.cjs | Every page that is not a product screen, at the three narrowest stops | 0 scroll sideways |
design13.cjs | DESIGN.md against tokens.css: every type step the document names, resolved and compared | 11 of 11 agree |
plans13.cjs | Where the product parts company by plan, and the four ways the "one product, not two" decision could stop being true | 4 checks clean |
fp12.cjs | The box fingerprint, before and after any change that should move nothing | on demand |
inert12.cjs | "Checked rather than assumed", re-run on the whole product | on demand |
handoff13.cjs | This page's census | on demand |
The remaining six are the ledgers and pixel proofs of stages 08 to 12 and are kept for the same reason their stage pages are: a claim with no instrument behind it is a claim, and the instrument is how the next person disagrees with it.
What is open, counted rather than remembered
Read off design/kit/docs/backlog.md by a script that classifies each row by
what it says about itself, so the number cannot drift from the file.
The one row addressed to this stage is the largest structural debt in the repository:
a per-component coloured footprint is written by hand in forty places and nothing recomputes
it, which is why twenty-one component pages went stale the day the corpus grew. It has an
instrument, counts.cjs, and what it does not have is the discipline of being
run - which is the honest state of it and is why it is still open.
The count of the founder's own rows moved from twenty to five between two runs, and the reason is not drift. The founder took twenty-one decisions on 2026-08-20 and the rows that record them still carry the word in their prose. The census now prints two readings rather than one - rows where the founder is the OWNER, and rows that mention the founder anywhere - because a single number would have made a closed decision look like an open one.
Who decides after the handover
Two lists in this package are addressed to a person and not to a builder, and without an addressee the first row of either stops the work. They are not defects. Each one is a place where the code is consistent, the documentation is silent, and somebody has to say which way it goes.
Four things that were so on purpose and written down nowhere, and the day they stopped being
From the onboarding log, where a clean-context reader was sent through the repository twice and every finding was verified line by line against the files before it was believed. All four were put to the founder on 2026-08-23 and all four were answered the same day, which is why this table has a third column that says what happened rather than what is owed.
| The question | What was actually true | Decided |
|---|---|---|
| Which theme is the main one, and is dark a product feature? | Light is the main one, dark was complete, and the product had no control, no string and no prefers-color-scheme anywhere | A control in Settings. Two lines through Voice, a block in IA node 6.16, a switch in a group of its own, and the structure mirrored into the frozen grey with the decision written into that file. The product still does not follow the machine, deliberately |
| How many destinations does the tab bar have? | Four, since the Save tab was retired. Seventeen surfaces outside the decision log still said five or named Save | Dated, not corrected. They keep the corpus they were written on, which is what the live-count rule provides for a claim that is not recounted, and docs/decisions.md is the naming. Two were corrected because this stage owned them |
| Two screens carry a class the system does not define | The ledger had reported it since those screens landed, and no backlog row had ever named it | Neither option in the row was the answer. The class was already declared, under the name the renaming map gives it in colour: the two newest screens had copied the grey markup and skipped the rename, so that line had been rendering unstyled on both |
| Is the stand's shell really the file a new screen is copied from? | The registry said so, and the file named three destinations against the product's four, put a back control on a destination against U18, and closed a div with a </main> | Fixed, and given an instrument. The ledger has a check 8 now that reads the template against the product, because the reason it rotted is that nothing here had ever read it |
Seven behaviours no file stated, and the day they were stated
From the behaviour spec, which refuses to write a row it cannot source. All seven were put to the founder on 2026-08-23 and all seven were answered, and the list is kept exactly as it was written rather than deleted: a spec that quietly removes its own open list is one that cannot be trusted with the next.
| What was undecided | Decided |
|---|---|
| What happens to a subscription after the person reports it cancelled, which changes the count on the main screen and is therefore what the product IS | It stays in the list, marked, and leaves the monthly total at once, then disappears on its own once the next charge date passes with no charge, silently. Not gone immediately, because Tendd knows a person SAID so and not that it worked; not still in the total, because the total is what a person is choosing to pay for. State 2.6.7, four lines through Voice, one screen in both editions, and nothing added to the design system |
| When the alerts dot appears and when it clears | It appears on an alert that was not there last time and clears when the Alerts screen is opened, read or not. No number, ever. The node had refused the whole family without mentioning the one that shipped, and now says why: a count is a demand and a dot is an offer |
| What an empty or malformed email does, what is valid in the four Add Subscription fields, and whether asking for another sign-in link has a limit | One minimum contract for all three forms, in the spec and on no screen. The three rules about WHEN matter more than the field list: the primary action is inactive until the form can be submitted, an error appears after a person has tried and never while they are typing, and it is one line under its own field. The resend is answered rather than dropped |
| Whether the figures document is an approved source | Yes. It is the only file that states triggers, and refusing it would have stripped the source from ten cells to keep a rule about which files count |
| A third exit the flows do not draw | The drawing was behind the product. "Do this later" into the empty list is in Flow A now, with the reason the screen already carried |
What is deliberately not done
So that a decision is not read as a hole. Everything here was available and was refused, and each refusal has a reason that survives being written down.
| Not done | Why |
|---|---|
| Nothing in the product was fixed WHILE THIS STAGE RAN | The product was accepted at stage 12 against a pixel comparison of the whole corpus, so every hole this stage found became a row in the backlog with its measurement rather than an edit. The founder lifted the freeze at the close and eleven of those rows were paid in one round, each verified by the same fingerprint the freeze existed to protect: zero boxes moved at 360, and the only two screens that moved at 1440 are the two whose unstyled line was corrected |
| No skip link | It is a new product string, and a string belongs to Voice before it belongs to accessibility. Named at stage 12 as one of three things left, and still true. What it would skip TO now exists: seven stage pages had no main landmark and have one since 2026-08-23 |
| No focus trap in a dialog | Every state in this package is its own file, so a dialog is a page and there is nothing to trap focus inside. A real build owes a trap, a restore on close and an escape key. That is a boundary rather than a gap, and it is counted as one |
| No screen reader journey test | The semantics instrument proves that names and roles exist; it cannot prove that a journey reads well. That needs a person with a screen reader, and its absence is named rather than assumed away |
| No deploy in this stage | GitHub Pages has served this repository since stage 01. What this stage owes is a version tag, three live addresses, and the proof that a fresh clone works with nothing explained out loud |
| No new screen, no new component, no new token | A handoff documents what was built. The single exception the stage allows is an examination screen built by somebody else from the documentation alone, and its fate is decided out loud rather than left in the product |
The critique, on two instruments
Run 2026-08-18 the way CLAUDE.md requires it: Codex read-only on the
source and Claude in a browser on the render, taken independently and merged
afterwards, with a column saying who found what. 22 findings, 22 closed, none dropped at
verification - and the two most valuable ones were each produced by checking the other
instrument's work rather than by either instrument alone. The closing round of this stage
adds its own and lands in this section.
| Was | Became | Found by |
|---|---|---|
| Six places in the navigation node still described a different number of tabs, including a decision line calling the extra one "exactly the density and alarm this segment avoids" and an accessibility contract promising a landmark with a count that no longer matched | All six agree with the code. The refusal is kept and dated rather than deleted, with the condition that removed it and the measurement that replaced it | Codex |
DESIGN.md described a tab bar the product no longer had, and its Do-block still forbade petrol inside a screen's content after a decision in the same document had granted it a fourth job | Both corrected. A normative rule that contradicts its own document is worse than a stale figure | Codex |
tokens.css justified the plotted line with a count of one element on one screen | Two on two. The sentence was measured correctly in the morning and the founder gave a second screen its own plot in the afternoon: the count moved under a claim that was never re-run. The ARGUMENT survives | Codex |
| An instrument carried an absolute path into one machine's home, on the same day the census had named exactly that defect in another one | Resolved from the file's own location, like every other instrument. A third file with the same defect survived both sweeps and was found at step 5 of this stage | Codex |
| This page said "four markdown files are named by no page" while being an html page that names all four | The census reads zero, and the paragraph says why out loud: naming them here is what closed it | Codex |
Two figures in README.md had drifted, and a sentence credited every instrument with serving the repository to a browser when two of them never open one | Recounted, and the two that never touch a browser are named | Codex |
| The file that reports "zero em dashes found in any file" contained one, inside the sentence naming the character it had searched for | Named by code point and never by drawing it. The result it reports is true again | Codex |
| A component file said petrol has three jobs, written the same day a decision made it four | Four - and the finding was a thread: pulling it found 24 more places saying three, in nine system files, twelve kit pages, DESIGN.md and a concept page. All 25 corrected | Codex found one, Claude swept the rest |
| A stand page asserted "the line is not petrol, and that is the decision worth keeping" two sections above the one explaining that it now is | The refusal is quoted as what it was, with a link to the day it was lifted. A page arguing with itself is the worst kind of stale | Claude |
| Ten of the pages that are not product screens scrolled sideways at the narrowest stop - and nothing had ever swept them. The width sweep covers the product screens and reports zero, truthfully | 0 of 127. Four causes, each with a solved form already in the product. pages13.cjs is checked in beside the others | Claude |
| A brand page logged three console errors from an svg attribute that cannot take the value it was given | The attribute is gone and the aspect ratio says what it was for. The drawing is unchanged | Claude |
| THE ONE THAT CAME FROM DISAGREEING. Codex flagged a line count on this page against the census's own and called the page wrong. The page was right | The census counted lines with a split on the newline, which returns an extra element after a file's final newline: every line figure it had ever printed was inflated by one per file. Fixed at the function, every figure on this page recounted. A census off by one on every file is worse than no census, because the error is invisible at any single row | Codex found the disagreement, Claude found which side was wrong |
Nothing was dropped at verification, which is unusual and is worth one sentence rather than a celebration: the read-only pass was given a scope of files changed on one day, and a narrow scope is what makes a critic precise. The one finding that could have been dropped turned out to be the most valuable in the run, because the instrument was the wrong one.
Round 2, 2026-08-23: the closing pass over this stage's own material
The same two instruments, the scope narrowed to what stage 13 produced. Eleven findings, ten closed, one filed, and four dropped at verification inside a single row - every one re-read in the file it names before it was believed. Seven came from the read-only pass, three from the browser and the design detector, and the split is exactly what the two instruments are for: the sharpest source finding is a rule this package could not obey, and the sharpest render finding is a contrast failure no source read could ever have produced.
| Was | Became | Found by |
|---|---|---|
THE ONE THAT MATTERS MOST: this package broke its own rule, and the rule was the thing that was wrong. CLAUDE.md said a css fragment or a pixel value anywhere inside handoff/ is a defect. This page carries a style block full of them, because a stage account has a look, exactly as the twelve accounts before it do | The rule now binds handoff/docs/, where it was always aimed, and names the two things that are not copies: a MEASURED figure, and a stage page's own chrome, which reaches no screen. A rule that the file it governs cannot obey is not a strict rule, it is a wrong one | Codex |
| The prompt for adding a feature said "no pixel number anywhere above" forty lines after printing one | The number is a measurement and says so: the band of sideways scroll that survived two accepted stages, which is the whole argument for sweeping a width rather than sampling its ends. Caught by the same pass, in the same class it exists to warn about | Codex |
| Four instruments took their product corpus from a typed list of what to leave out, and one of them promised in its own header that nothing in it is typed by hand | A screen is a screen if design/_nav.js names it. All four read the registry now, and the map prints what is on disk and unregistered rather than assuming it. Every figure they produce is byte-identical, which is the proof this corrected the method and not the measurement | Codex |
| Three route figures on this page contradicted the output checked in beside the instrument, because the corpus grew after they were written | Read off the run. The page had been carrying the shape of the very defect it spends a paragraph describing | Codex |
| This page said the census derives its corpus from the tree, five days after the clean clone changed it to what git carries | Named correctly, with the tree walk described as what it now is: the fallback for a copy that is not a checkout | Codex |
| The page said "four documents" twice and the stage has five since the prompt was written | Five, in both places | Codex |
| Two headers still described a corpus of 55 screens and 110 pages, inside the two instruments whose whole argument is that a claim counted off a corpus is recounted when the corpus grows | Neither carries a number any more: both name the registry they read. A comment that cannot go stale is better than a comment that was corrected twice | Codex |
| The hamburger on this page said "All screens" and opened the roadmap. The label is generated by the coloured screens' chrome, where it is exactly right, and it reached the two stage accounts that load the same stylesheet | Corrected on this page in its own style block, because one line in the shared stylesheet would move the render of all 57 accepted screens. design/rollout.html carries the same wrong word, is frozen html, and is a backlog row | Claude |
THE ONE THE SOURCE COULD NOT SEE: the project's own sidebar fails WCAG AA, on every page that shows section links. `.nav-section` in _nav.css is drawn in the muted grey the registry reserves for a stage that has not started, at 13px. Measured against the panel: 2.65:1, where 4.5:1 is the floor. Only the CURRENT link escapes it, because that one is petrol | Not fixed, and filed. It is one token and it moves no box, but it repaints the sidebar of twenty pages, and a colour change at the closing ritual belongs to a founder's walk rather than to a builder's last commit. What made it invisible for the whole project is that no instrument has ever swept a stage page: the accessibility run and the semantics run both take the product and its grey twin and stop | Claude |
This page had a nav, a footer and no main, because the eleven stage accounts before it have none either. A page with landmarks and no main gives a screen reader nothing to skip TO | A real <main> here. The other accounts are frozen and carry a row | Claude |
| Five type and radius values on this page sit off the documented ramp, reported by the design detector | Dropped at verification, four of five, with the reason. Every one of them is character-identical to design/rollout.html, which is the account this page answers to: a stage account is chrome, it reads the semantic colour roles and owns its own geometry, and eleven accounts before it do the same. The fifth was mine and real - one invented half-pixel step that existed nowhere else in the house set - and it is gone. The typeface flag is a locked decision from Concept | detector, Claude verified |
Was unclear, became
This stage produced no screen and no component. What it produced is a package that answers questions it could not answer before, and the honest way to show that is the pair: what a stranger actually asked, and where the answer lives now. Every row on the left was said out loud by a reader with no memory of this project, in one of the three clean-context runs, and none of them was imagined by the person who built it.
| Was unclear | Became |
|---|---|
| "What IS this? Seventeen screens exist as fifty-eight files and nothing says what a real build does with that." | The package boundary, on this page: no data layer, no API, no router, no persistence; every state is its own file; and the translation from 57 files back to 17 screens with states is named as the first decision a receiver makes rather than left as a surprise |
| "Which theme is the main one, and is dark a feature or a capability?" | A section of its own, and the answer is measured: light is the main one, every role is paired, the switch is reviewer chrome, the product has no control and no string, and prefers-color-scheme appears zero times anywhere. Recorded in docs/decisions.md as a state with the founder's call still open |
| "How does the product behave? A screen shows a state and never what led to it." | behaviour.md: five flows, five described, steps, states, edge cases and validation, every row naming the file it was read from, and seven rows that had no source moved to a list addressed to a person rather than guessed |
| "If I change this value, what moves?" | map.md, taken off the rendered product: 57 screens to 73 components to 163 tokens, with the reverse list produced by inverting the same data two knees deep rather than by walking the code twice |
| "Is accessibility done, or is it claimed?" | a11y.md: fifteen rows, each with how to check it, and a status of two words where "confirmed" means a run today with a named instrument. Eleven confirmed, two debts written to the backlog, one boundary. Nothing was fixed here |
| "How do I add something without breaking it?" | one-shot.md, a prompt rather than a checklist, tested twice by strangers on two features and rewritten thirteen times by what they got wrong |
| "Who do I ask? Everything open seems to be addressed to nobody." | Two lists with an addressee: four things true on purpose and written down nowhere, and seven behaviours no file decides. Plus the rule for who owns what: a string is Voice's, a bending rule is the founder's, a defect with an instrument behind it is a builder's |
| "Where does the route start, and does it go anywhere?" | Four addresses in two places written in one pass, a folder-by-folder table, and route13.cjs: every link in every page and every document, resolved, plus the click depth from the entry page measured in a real browser |
| "How much of this do I actually receive?" | A census whose corpus is what git carries, because the clean clone found it counting 80 files nobody is ever handed |
The contract of this stage, checked
The list a handoff has to satisfy, as a table rather than as a paragraph, so that a disagreement lands on a row. Every "yes" here is a run or a file, and where the answer is partly no it says so.
| The contract asks | Answer | Where it is proved |
|---|---|---|
| The audit was done by a stranger, not by the builder imagining one | yes | Three clean-context runs, each with the forbidden paths listed BY PATH, each returning a reading journal checked line by line against that list. onboarding-gaps.md |
| Both of the stranger's lists verified row by row | yes | Four verdicts per row, and the rows that came back "so intentionally and nowhere written" became entries in docs/decisions.md rather than disappearing quietly |
| Every behaviour row names its source, and a row with none goes to a list for a person | yes | Five source kinds declared, seven NOT DECIDED rows, roll-call closed at five flows |
| The map is read off the code, and its reverse list is produced by inversion | yes | map13.cjs, and the idle control closes with numbers: 0 components on no screen, 3 tokens no chain reaches and all three accounted for |
| Every accessibility row has a way to check it, and no hole was fixed here | yes | 15 rows, 11 confirmed by a run today, 2 debts and 1 boundary. Two rows went to the backlog with their measurements |
| The README leads rather than reports, and there is exactly one status table | yes | Rewritten as an index; the other owner of the same truth is /_nav.js and the census checks the two against each other |
| Two clicks from the entry page to any artefact of the handoff | yes | route13.cjs check 4, walked in a browser because the roadmap is drawn at runtime: 6 of 6 artefacts, none further than two |
| The clean clone works with nothing explained out loud | yes | Cloned into an empty directory, instruments run from inside it: 0 absolute paths, playwright resolved globally, 239 pages opened, 0 dead links, 0 of 129 pages scrolling sideways |
| The exam was passed twice, by different readers on different features | yes | Two builds, both accepted by every instrument, both deleted |
| The second list is shorter than the first | no | Thirteen became twelve, and that is not an improvement. What changed is which readings, and one item repeated a difficulty the first run's own fix had created. Said here rather than smoothed: a prompt this much better that still produces a list this long is working and is not finished |
| The fate of the exam screens was decided out loud | yes | Branch B for both, reverted by git, with the reason in docs/decisions.md |
| The product outside that fork is unchanged while the stage ran | yes | 57 paired with 57, 0 orphans either way, 0 style violations. The founder then lifted the freeze and eleven backlog rows were paid, which is a decision taken out loud at the close and not a stage that edited what it was documenting. The round is in docs/decisions.md and every change carries the fingerprint that proves what moved |
| The critique ran on two instruments, with a column saying who found what | yes | Two rounds, 22 findings and 8, all closed. The read-only pass owns the source, the browser pass owns the render, and round 2's single browser finding is a generated label that no source read could see |
| Nothing in the documents copies a value instead of naming it | yes | Checked mechanically: 0 hex, 0 css fragments, 0 product strings in handoff/docs/. The rule itself was corrected in round 2, because as first written this page could not obey it |
| Every file of the package has a named reader among the three | yes | The new developer reads the prompt and the boundary; you-in-a-year reads the decisions and the open lists; a model in a new session reads the map and the behaviour spec, which are the two written to be read by something that cannot see the screen |