# Backlog - what is open

Everything decided but not done, and everything not yet decided. One row per item, with the stage
that owns it and where it came from.

**This file is not loaded into a session.** `CLAUDE.md` holds the rules; [`decisions.md`](./decisions.md)
holds the record of what was done. This holds what is not.

Unlike `decisions.md`, this file is edited: a row is struck when the item closes, with the date and
the entry in `decisions.md` that closed it.

**AND A STRUCK ROW KEEPS ITS HEADER'S CELLS, decided 2026-08-20 by rendering this file rather than
reading it.** A row with FEWER cells than the header is padded with blanks by the renderer and loses
nothing; a row with MORE **has its last cell dropped in silence**, which is the opposite of what
backlog 223 was filed on. So the shape is: the Item cell carries the strike and the closure, Source
and Note keep whatever they had, and a cell with nothing left in it **stays as an empty cell rather
than being folded away**. **A row that quotes another row escapes the quote's pipes**, because a
quoted row is prose about a row and not a row: six of the eleven that were losing text were doing
exactly that. Measured over every markdown file here, code spans stripped: **342 tables, 2,708 body
rows, 0 disagreeing with their header** in either direction.

**Open: 0, counted from the rows below on 2026-08-21**, and it was 2 earlier the same day. It was 0 on 2026-08-20, and every row this
file had ever carried was struck; **230 was filed the next day by re-running a measurement the tree
had outgrown**, which is what the paragraph below this one said an empty backlog is a reason to do. That is a statement about the
BACKLOG and not about the product: `PRODUCT.md` still carries `Jurisdiction: [?]`, the four legal
pages are a draft nobody with counsel has read, and there are 0 lines of product code. **An empty
backlog means nothing measured today is open, which is a reason to go and measure something else.**

**AND IT WAS 0 UNTIL 2026-08-23, WHEN THE HANDOFF RUN FILED THREE.** 232, 233 and 234 are below
under *Accessibility, found by the Handoff run*. **The same run produced three more findings and
filed none of them**, because all three were the instrument rather than the page, and a finding that
was never true is not an open item: they are written up in `handoff/docs/a11y.md` with the control
that caught each one. Open is **0**, counted from the rows on 2026-08-23. **Thirteen rows closed that day in three passes**, and the last of them closed by disproving its own premise: a repository is not made of the files somebody remembers being large. Five of the thirteen needed no edit at all: the filter toggle's target was its label, the modal screens were already inert with focus inside and Escape live, two thirds of a contrast count was not text, the numerator nobody could define was defined in a legend every one of those files already carries, and the 278 MB of history was an unrepacked `.git` that `git gc` halved without touching a hash. **The last four are the only ones found by somebody USING the package rather than measuring it**, and the clean-clone test is the only one that reads what a person receives rather than what is already on the disk.


**The week of 2026-08-19 and 2026-08-20 filed eleven rows and struck five of them within a day, and
the pattern is worth keeping**: 217 was filed as "delete a word" and was really claim against
mechanism; 219 was filed as a classification problem and was really a HORIZON problem; 221 was filed
as an unmade placement and was really a routing convention nobody had measured; 220 and 222 were
declared states that no tree had drawn. **Four of the five were half wrong in the row and right about
there being something there**, which is what a backlog is for.

**AND THE COUNT HAD DRIFTED AGAIN, WHICH IS ITEM 74 ARRIVING A SECOND TIME.** The header said 45 and
a row-by-row count says 47, and every section heading but one was stale with it: "Design defects
deferred" claimed 13 open and 6 closed against 11 and 10, "Found by the component pages" claimed 14
open against 11. **Nothing was renumbered and no row moved**; only the counts were re-read, from the
rows, on 2026-08-10. A count in a heading is a fact written twice, so it drifts exactly the way this
file's own rule says a fact written twice will, and the answer is the same as everywhere else here:
it is checked by being counted, not by being carried forward.)

**The count was 47 and it was wrong, which is why 74 existed.** A row-by-row read found 44 items
where the header said 47, because three numbers each named two different things. **Every number in
this file is unique from 2026-08-09 and the highest is 90**, so a count is now something a reader can
check rather than a figure carried forward. 20 is deliberately absent and its row explains why; 16
is not missing, it is 16a to 16d; and 29 is the one collision item 74 deliberately left standing,
because one of its two rows closed on 2026-08-03 and renumbering a closed row rewrites a record
nobody will act on.

**Four section headers below carried counts that had drifted**, which is item 74's defect in the
one place it was not looked for. Recounted 2026-08-09 and only the header of the section this
day's work touched was corrected; the other three were then named in this paragraph instead, with
their counts written out.

**AND THOSE FOUR COUNTS WENT STALE IN THEIR TURN, WHICH IS THE THIRD TIME AND IS ROW 128.**
Re-read row by row on 2026-08-12, this paragraph said *Design defects deferred* held 21 rows and 12
open against 21 and 1, *Component boundaries* 6 and 4 against 10 and 1, *Accessibility and keyboard*
5 and 4 against 5 and 0, and *Found by the audit* 7 and 3 against 7 and 1: **four for four wrong.**
The fix in 2026-08-09 was to move the copy from the headings into a paragraph, and a copy in a
paragraph drifts exactly the way a copy in a heading does, because what drifts is the copying and not
the place. **So the numbers are not restated here.** They were recounted into the section headers on
2026-08-12, those drifted once more, and on 2026-08-13 the headers stopped counting at all: see the
paragraph under the rule below. The only total this file holds is **Open**, at the top.

The Owner column carries the **new** stage numbers (the project renumbered from thirteen stages to
twelve on 2026-08-02, and an owner is a pointer at work not yet done). The Source column keeps the
number the record was written under, because it cites an entry in [`decisions.md`](./decisions.md)
whose header holds the old-to-new key.

---

**THE SECTION HEADINGS BELOW NO LONGER COUNT ANYTHING, and that is row 128 closing on the first of
the two answers it named for itself.** They carried a count for as long as this file has existed and
it drifted on 2026-08-09, on 2026-08-10, on 2026-08-12 and again on 2026-08-13, which is four passes
and never once a reader who caught it before the pass that had to take it anyway. The last one is
the shape of all four: seven of the eight headings were right and *Component boundaries* said 6
closed over 10 closed rows, the SAME section that was wrong the time before. **A count in a heading
is not a fact about the rows, it is a copy of one**, and this file's own rule says a fact written
twice will drift; four passes say the second copy is the one nobody re-reads. The answer that was
tried twice, moving the copy into a paragraph, moved WHERE it is written twice and not that it is.

**So there is one number in this file now: Open, at the top.** It is the only one that never drifted,
because it is the one every pass has to edit to do its work. Everything else a reader wants is under
a heading and can be counted from the rows, which is the second answer 128 named and is what this
line asks for. The rows themselves stay numbered, unique and permanent: a number that names a thing
is not a copy of anything.

## Unread surfaces

Not a defect list. No entry here is a known bug; each is a place where a bug would be invisible,
because nothing has ever read it. Written 2026-07-28 after nine audit passes over Stage 09, which
yielded 34, 14, 24, 11, 10, 8, 15, 9 and 20 findings - a sequence that does not decay, which is the
argument that yield tracked unread surface rather than effort. Measurements are from that date.

| # | Surface | Size | Owner |
|---|---|---|---|
| ~~1~~ | ~~The 28 course pages' own content~~ | 203 KB inline css | **Closed as NEVER**, 2026-08-02: the course frame, not the product, and the one part a reader touches is already out in `course-chrome.css` |
| ~~2~~ | ~~`wireframes/` inline css~~ | 34 `<style>` bodies over 104 pages, largest 52 KB | **Closed as NEVER**, 2026-08-02: the grey tree is frozen and the generators that could act on a finding are the ones `CLAUDE.md` forbids running, so every finding would arrive with "not fixed, by rule" attached |
| ~~3~~ | ~~The page scripts as code~~ | 15 distinct bodies in 810 blocks; every sweep reads their output, never the code | **Closed 2026-08-15, and the row's own numbers were stale: it is 21 distinct bodies in 1,137 blocks over 211 documents, 1,091 distinct source lines.** Read three ways rather than one. **What they WRITE**: three classes, `open`, `scrolled` and `sel`, and every one of the three is read by a stylesheet in `components/`, so 0 are orphans, which was the defect this row existed to look for. **What they REFERENCE**: a first pass reported 12 unguarded `getElementById` calls on ids the document does not carry, and every one was the instrument: the guard is `if (btn)` on a sibling variable and the reader only looked 260 characters ahead for a guard naming the same variable. **What they DO**: every document of both trees loaded in Chromium 151 and WebKit 26.5, with a thrown error injected first to prove the listener catches one, gives **0 page errors over 106 painted and 105 grey documents in both engines**. A null dereference cannot hide from that. The row asked for the code to be read once because nothing ever had; it has been, and what it found was a stale count and a bad detector rather than a defect |
| ~~4~~ | ~~What a screen reader is told on change~~ | ~~`aria-live` / `role="status"` on 9 screens of 105~~ | **Re-measured and replaced by item 24, 2026-08-02.** The count was right and the conclusion drawn from it was not: both trees carry the same 9 live regions and the toast already announces itself. What is missing is the form-error axis |
| ~~5~~ | ~~Page weight, font swap, layout shift~~ **MEASURED 2026-08-10**, `decisions.md` same date, which is what this row asked for: it is not a defect list, it is the last surface here nobody had read. 106 painted screens, 390 and 1280, both themes, 424 renders per pass, 16 passes, against a FROZEN copy of the tree because the live one was being edited throughout and the control caught it moving (+7,431 bytes on every one of the 424 renders, all of it stylesheet). **WEIGHT: 1,367,334 bytes and 58 requests per screen on a cold cache, and 48.4 per cent of it is the same bytes on every screen.** Identical at both widths and in both themes, 0 of 106 documents vary. Split: image **53.0**, stylesheet 37.5, font 4.5, document 3.7, script 1.4. Worst `event-feed.html` at 2,903,176. Shared and cached after the first document: 575,840 of stylesheet over 50 files, 65,080 of font, 20,807 of `icons.js`. **Only 4 of the 8 declared faces are ever fetched** and the four `latin-ext` files were requested 0 times in 424 renders, which is `unicode-range` working and not a defect. **SWAP: `font-display:swap` on all 8, and the behaviour is FOUT and never FOIT**, proven by ink painted at 60 to 90ms while `fonts.status` was still loading. The swap **moves a median 70 per cent of the laid-out text elements**, 3,488 of 6,492, typically by 1.0px and by up to 58.5px, and the document height moves 20 to 21px on the feed and the detail. **CLS: structural layout shift is 0.0000 and every shift that exists is the font.** 616 of 616 entries landed at or after the 1400ms the delayed font arrives; the sum before it is exactly 0. With webfonts aborted, 0 of 424 renders shift, frozen AND with animation live, so **the entrance animation contributes exactly 0**, which is worth knowing because it was the prime suspect. Theme has no effect anywhere: 390 dark and 390 light both sum to 0.4793. **The warm-cache control was NOT 0** and the cause was found before the number was reported, a race between applying a cached font and first paint, nondeterministic and never to be quoted as a score. What is left of the row is three defects and they are rows 99 and 100. | | |

Accessibility was checked before that table was written, because a large hole there would have
changed the answer: **0 buttons without an accessible name** across 105 screens, every `<img>` with
`alt`, native `<dialog>` supplying `aria-modal` and inerting the page, tab strips as radio groups
that arrow keys already drive. The one real gap is the announcement, and a toast is a state, so
Stage 09 owns it.

---

## Product research not done

Carried since the project brief; none of it has been answered.

### Accessibility, found by the Handoff run

Three rows from Handoff step 4, 2026-08-23. **Each was produced by a probe that had first passed its
own control**, and the same run threw away three findings that were the instrument: a gradient ground
that computes to transparent, an off-canvas skip link drawn in the browser's own blue, and a probe
that opened every header disclosure at once and was closed again by the page's own script. Those
three are written up in `handoff/docs/a11y.md` rather than filed, because a finding that was never
true is not an open item.

None of the three below is fixed in the Handoff stage: a product edit after the tree was accepted
cancels every comparison the acceptance stood on. Each carries the instrument that finds it again.

| # | Item | Source | Note |
|---|---|---|---|
| ~~232~~ | ~~**Primary navigation has no landmark label at and above the desk rung.** Below it the bottom bar carries a `nav` with a primary label. Above it the bar goes, the header takes over, and the header is not a labelled navigation landmark: a reader moving by landmark finds categories, the footer columns and legal, and no primary. **Every destination is reachable** - the run confirmed that, once it stopped fighting the page's own disclosure script - so this is a missing label and not a missing route. **How to check:** list every `nav` and its accessible name at each rung and one pixel either side, and assert one is primary. The fix is a label on every painted screen, which is why it is a row and not an edit~~ **FIXED 2026-08-23.** The account destinations lived in a `role="menu"` dropdown that was no landmark at all, so a reader navigating by landmark found Categories, the footer columns and Legal and no primary at any width above the desk rung. It is `<nav aria-label="Primary (account menu)">` now on **149 documents**, 83 painted, 64 grey and 2 kit, and the six links lost a `role="menuitem"` they never earned: `role="menu"` promises arrow-key semantics this product does not implement, so it was a claim to assistive technology rather than a description. Verified on both engines at 390, 640, 760, 900 and 1280: the landmark is present at every width with its six destinations, and 0 `role="menu"` remain in the avatar dropdown. | Handoff step 4, 2026-08-23 | `handoff/docs/a11y.md` row 7. Measured over 17 widths at three browser default sizes, both engines |
| ~~233~~ | ~~**Four controls measure under the touch floor with the coarse-pointer branch asserted on**: the filter toggle on the feed, and three icon controls on the event detail. The floor itself applies correctly - the branch was asserted in the page before the measurement, which is the reading that a headless browser silently turns off. The four hidden tab inputs on the event detail are NOT in this list: they are not the target, their labels are, and the clipped skip link is not a target either. **How to check:** emulate touch, assert `(pointer:coarse)` matches inside the page, then measure every interactive box; the floor is one rule in `base.css` and a `max()`, so the fix is naming these four rather than adding a rule~~ **MEASURED AND CLOSED AS NOT A DEFECT 2026-08-23, and the row had counted the wrong element twice.** The filter toggle is `<input type="checkbox">` at 1x1 by design and **its label is 44x44**: the target a finger hits is the label, and both this row and the audit that agreed with it measured the input. The three icon tiles on the event detail are genuinely **28x28**, and they are one of the four exclusions `base.css` declares by name with its reason written beside them. **28 clears WCAG 2.5.8 at AA, which asks 24**; 44 is the AAA figure, and whether these four should take it is the question that file already points at. Nothing resized. | Handoff step 4, 2026-08-23 | `handoff/docs/a11y.md` row 9 |
| ~~234~~ | ~~**Focus placement and escape were never measured on the 19 screens that open with a modal dialog.** Those screens are the feed with an overlay over it, and the document behind a modal is inert - which the run confirmed by finding the skip link correctly unreachable on exactly those 19 and correct on the other 100. What was not measured is the half a keyboard user meets FIRST: whether focus lands inside the dialog on load, and whether Escape returns it. **How to check:** TAB once from load and assert the first stop is inside the dialog; press Escape and assert focus returns to the page. **This row is a gap in the measurement, not a known defect**, and it is filed because a point that cannot be verified looks like finished work and is not~~ **MEASURED AND CLOSED 2026-08-23, and the answer is that it was already right.** The row said 19 screens; there are **24**. All 24 open with `showModal()`, on both engines: focus lands **inside** the dialog on load on 24 of 24, **no element behind can take focus** on any of them so the page is inert, focus **wraps back into the dialog** after 40 tabs on 24 of 24 in Chromium and 23 in WebKit, and **Escape closes on 24 of 24**. **The probe manufactured a defect twice before it got there**: it read `:modal` as false on the first pass and corrected on the second, and it read Tab reaching `BODY` as a leak when that is the browser chrome round-trip, not the page behind. The test that decides it is whether an element behind can TAKE focus, and the control proves focus works when nothing is modal. | Handoff step 4, 2026-08-23 | `handoff/docs/a11y.md` row 11 |

---

## Counts inside pages, found by the Handoff route pass

Two rows from Handoff step 5, 2026-08-23. Both are the same defect as the six prose files repaired
that day, in the two places the Handoff stage may not touch: a stand page and the product tree.
**The stage repairs documentation and never a page**, because a product edit after the tree was
accepted cancels every comparison the acceptance stood on.

| # | Item | Source | Note |
|---|---|---|---|
| ~~235~~ | ~~**`ui-kit/overview.html` argues against itself in one screenful.** Its panel COMPUTES the kit's tally from the registry and prints it live; three inches below, its own body prose still types a smaller page count and a pair of tree sizes that are several documents behind the disk. Read as source the two look like statements of the same rank; rendered, one is a computation and one is a fossil, and a reader with no context said so before any instrument here did. **How to check:** open the page and read the panel against the paragraph. The fix is to delete the typed figures or make them read from the same registry the panel already reads~~ **FIXED 2026-08-23.** Two typed page counts deleted in favour of the panel that already computes them, and two dated readings kept and marked as readings: the 115-document sweep and the 41 screens of the 106 both carry `2026-08-08` now. **A tally typed into prose beside a tally that is computed is a fossil beside a reading, and in the source the two look the same rank**, which is the whole of what a reader with no context saw before any instrument here did. | Handoff step 5, 2026-08-23 | The page is a stand page and out of scope for the Handoff stage. `handoff/docs/onboarding-gaps.md`, LIST B row on stale counts |
| ~~236~~ | ~~**The grey tree's only self-check is switched off, on every document.** That tree links no stylesheet, so each file carries its rules inline and each region states its own numbers, `SHARED (N of M, R rules)`. The point of the denominator is that a file which has LOST a shared block contradicts its own header. Measured 2026-08-23: every grey document declares itself one of a tree several documents smaller than the tree on disk, and the shared-region markers carry three different denominators between them, none of which is the count. **A denominator that is behind the tree cannot catch a reduced copy**, which is the whole and only job it has. **How to check:** count the documents, then read the denominators; they must agree. The fix is a sweep over every grey document, which is a product edit~~ **FIXED 2026-08-23, and the definition was written all along.** The legend at the head of every inline stylesheet says `these R rules stand in N of the M documents`, which is exactly what a measurement finds; what nothing did was recompute it. **2,349 markers over 30 regions in 119 documents were recounted in one pass and every N now equals the number of documents carrying its region**, against a set that had been written for a tree of 114 with three regions stranded on 116 and 117 by later passes that turned one marker and not the set. The legend's own denominator was one of the stale ones. **A stale numerator makes every document contradict its header equally, which is the same as none of them doing it** - the marker's whole purpose is that a document which has LOST a block stands out. `wireframes/CLAUDE.md` carries the rule now: recount the set when the tree changes size, in one pass, and never turn a single marker by hand. | Handoff step 5, 2026-08-23 | `handoff/docs/onboarding-gaps.md`, LIST B. The mechanism is described in `wireframes/_conventions.md`  **AND THE NUMERATOR HAS NO DEFINITION, 2026-08-23.** `wireframes/CLAUDE.md` says the counts are recomputed whenever a screen is added and never says what `N` counts. A second reader with no context recomputed them on the only obvious reading, the number of documents carrying that region, and got **123 for a region whose header says 56** - so that is not the definition and no file holds one. They reverted all 123 documents to their exact HEAD values before doing anything else and disclosed it. **A count nobody can recompute is a count that can only ever be copied**, which is what a self-check written into 119 inline stylesheets was supposed to stop. |


## What a stranger building a feature found, and none of it was the feature

Four rows from Handoff step 7, 2026-08-23. A subagent with no context was given one written prompt
and a reserved node and told to build. **The screen it built is reverted and is not on this branch**;
these four are what it walked past on the way, every one of them pre-existing, and every one
re-measured against the reverted tree before it was filed. **A reader who has never seen the
repository walks a path nobody who built it walks any more**, which is the whole argument for the
examination and is why these were found by a build and not by a sweep.

| # | Item | Source | Note |
|---|---|---|---|
| ~~239~~ | ~~**Twelve documents carry a screen-tree panel that names a smaller set than the rest of their tree does, and the two trees disagree about which.** Grey: 111 of 119 documents name 119 screens, and eight name 118. Four of the eight have no row for `event-detail-bet-ready.html` and four have none for `event-detail-recurring-multi.html`. Painted: 116 of 120 name 122 rows and four name 121, all four missing `event-detail-recurring-multi.html`. **The disagreement is the evidence**: `event-detail-bet-ready` is absent from four GREY panels and present in all 120 painted ones, so a document is reachable from one tree and not from the other. All twelve are documents that were created by copying a document written before the row existed, and the copy brought the panel of its own day with it. **Nothing here can see this**: an off-canvas panel adds no height, no sideways scroll, no duplicate id and no page error, and 908 renders once passed the same class of defect. **How to check:** collect the set of hrefs inside `.wf-tree` in every grey document and inside `#rmSidebar` in every painted one, and assert one set per tree and the same set across the two.~~ **FIXED 2026-08-23.** The twelve missing rows were inserted at the position their siblings carry: four grey panels gained `event-detail-bet-ready`, four grey and four painted gained `event-detail-recurring-multi`. Re-read as a set afterwards: **one panel of 119 rows in all 119 grey documents and one of 122 in all 120 painted**, 0 differing in either tree, and the two rows the painted panel carries and the grey one does not are `overview.html` and `research.html`, which are route out of the tree rather than screens in it. | Handoff step 7, the examination build, 2026-08-23, re-measured on the reverted tree | The two rows the trees legitimately differ on are `overview.html` and `research.html`, which the painted panel carries as route out of the tree and the grey panel does not. That is a difference in what the two panels are FOR, and it is not this row  **FOUND TWICE, INDEPENDENTLY, 2026-08-23**: a second reader with no context, building a different feature from the same prompt, ran the same set read and reported the same twelve documents. Two readers who never met each other reaching one number is the strongest evidence this row can carry. |
| ~~240~~ | ~~**Four documents have no `h1` at all, and `handoff/docs/a11y.md` had already told you which row was hiding it.** `event-detail-loading.html` and `event-detail-logged-out-loading.html`, in both trees. Every one of the other 235 documents has exactly one. The loading state replaces the question with a skeleton and the heading went out with it, which is the same shape as a busy region marked on the wrong element: **a state may show LESS than the success state, and a document with no heading at all is not less, it is a document a screen reader cannot enter.** The row in `a11y.md` that covers this says one `h1` per screen with no level skipped, marks it confirmed **as part of the earlier structural passes**, and states plainly that it was **not re-run**. This is what that not-re-run was hiding, and the row now carries the measurement instead. **How to check:** count `<h1` in every document of both trees; the answer is 1 everywhere or the row is not closed.~~ **FIXED 2026-08-23.** The loading state's screen-reader line was a `<p class="sk-status" role="status">` and the heading had gone out with the question it replaced. It is `<div role="status"><h1 class="sk-status">` now in all four documents: **one string, both roles**, no new class and no new rule, because `.sk-status` was already the visually hidden face. **239 of 239 documents in both trees carry exactly one level-one heading**, measured after. | Handoff step 7, the examination build, 2026-08-23, confirmed independently on the reverted tree | It is a product edit in both trees, so it is filed rather than made: this stage documents what is |
| ~~241~~ | ~~**The copy inventory still says `Portfolio`, and that is a word the voice contract bans by name.** `voice/docs/microcopy.md` carries `Portfolio` on 11 rows, the Wallet section among them: `Portfolio total`, `Portfolio = Cash + In-play`, and `Deposit` as a button label. The shipped screens carry `Balance`, `Cash`, `In-play` and `Add funds`, and **`Portfolio` stands in 0 of the 120 painted documents and 0 of the 119 grey ones**. The sweep of 2026-08-20 that took that word off 296 placements swept the TREES; **the inventory that is supposed to be the source of truth for every string was not in it**, so the file a person is told to read before writing copy is the one place the banned word survives. The same section still says `position` for the reader's own bet. **How to check:** grep the inventory for the lexicon's banned list and cross it with the render of the screens the section names.~~ **FIXED 2026-08-23.** Nine inventory rows turned to the strings the screens actually ship, taken from the render rather than from memory: `Portfolio` and `Portfolio total` to **`Balance`**, the bottom-nav slot to **`Profile`**, the swap control's accessible name to **`Swap balance (showing Balance)`**, the explanatory line to **`Balance = Cash + In-play. In-play is locked in open bets until they resolve.`** and both `Deposit` buttons to **`Add funds`**. The two remaining mentions are the file's own before-and-after records of the 2026-08-20 sweep and they stay: a record is true as of its date. | Handoff step 7, the examination build, writing a copy section beside it, 2026-08-23 | This is the rule about a set measured over the set, one layer below where it was last applied: the voice pass measured every rendered word and did not measure the document that lists them |
| ~~242~~ | ~~**All four hand-written renderings in `ia/` are behind the markdown they render, by two to eight days.** `ia/sitemap.html` last changed 2026-08-19 against a source last changed 2026-08-21; `flows.html` and `seo.html` are 2026-08-13 against 2026-08-21; `system.html` is 2026-08-13 against 2026-08-16. That folder's rule is that the HTML renders the markdown and **the markdown wins**, so a rendering that is merely thinner is tolerated by design. What is not tolerated is a rendering that says the OPPOSITE of its source, and nothing measures the difference between the two cases. **How to check:** for each pair, read the rendering against the markdown and classify each divergence as thinner or contradictory; only the second class is a defect.~~ **FIXED 2026-08-23, and the check found one real contradiction among four renderings.** `ia/flows.html` said **four key flows and drew four** while `ia/docs/flows.md` had drawn **five** since FJ1 was added on 2026-08-18: the job the whole browse layer exists for was missing from the rendering entirely. Its diagram is in, from the source, and both engines render five. Everything else the four pages diverge on is THINNER than its markdown, which that folder's rule tolerates by design, and a scan for numbers the rendering claims and the source no longer carries returned nothing on any of the four. **Each page now states what it is**: a reading of a named markdown file, dated, with the markdown winning and the two failure classes named, so the next reader knows which divergences are a defect. | Handoff step 7, the examination build, 2026-08-23 | The same shape as the two registries that render the stage status: a copy nobody re-reads goes stale, and the one a reader SEES is the one to turn first |
| ~~243~~ | ~~**A shipped sentence tells a reader how long their money takes to arrive and no file anywhere states a duration.** `win-payout-pending.html` in both trees says the payout will arrive in a few minutes, and `voice/docs/microcopy.md` carries the row, so the string is registered and the CLAIM is not: no duration is stated in `PRODUCT.md`, `ia/docs/sitemap.md`, `docs/launch-catalog.md` or the build plan. **It is the shape of `behaviour.md` N9 arriving in already-shipped copy rather than in a feature nobody built**: a promise about the clock with nothing behind it, which is exactly the class that produced the deadline notification nobody could make true. **How to check:** grep the product documents for a settlement duration; the answer is that there is none.~~ **DECIDED AND FIXED 2026-08-23.** A payout confirms in **under a minute**, and the bound is the rail rather than an average: Base is an L2 with a block time of about two seconds and a payout is one transfer the resolver signs, so under a minute is a ceiling that stays true on a bad day. **The same shape as the notification horizon, which is bounded by the shortest period the catalog opens.** `PRODUCT.md` carries it. **And the first launch has no rail at all** - release one is points with no chain, where a payout is a balance write and is immediate - so the pending state is the real-money behaviour and does not ship in release one. The string names the confirmation instead of a chain, so it is true in both, in both trees and in the inventory. | Handoff step 7, examination run 2, 2026-08-23 | Narrow on purpose. The wider question of what the first release can claim about a chain was filed and settled as row 217 on 2026-08-19 and **is not re-opened here**: a sweep that re-opens a settled judgement every time is a sweep nobody runs twice. This is the duration only |
| ~~244~~ | ~~**The course documents fail the text contrast criterion on 301 elements, and the two this stage wrote were among them until the sweep of 2026-08-23.** Read as pages on both engines: **14 documents, 5,038 text elements, 301 under threshold**, worst **2.59**. The shared palette's `--muted` was **3.36** on the page ground and **3.15** on the plate, and it is now a value that clears 4.5 on both; that one token closed seven documents including `index.html` and `handoff/handoff.html`. **What remains is 301 greys written as literals inside seven individual documents, past their own token**, at four values between 2.59 and 4.31, and the heaviest are the two IA renderings that backlog 242 already names for a different reason. **A palette is a promise a document can decline**, which is the same shape as a screen file inventing what belongs in the system, one layer out in the course chrome. **How to check:** render each course document, walk every element carrying a text node, compare its computed colour against its first painted ancestor ground, with a planted low-contrast paragraph proving the probe fires on each document.~~ **FIXED 2026-08-23, and three of the four causes were different things wearing one number.** From **301 to 0** over 14 documents and 4,867 text elements on both engines, with a planted low-contrast paragraph proving the probe fired on every document. **38 declarations raised** past the threshold; **238 were not text at all** and are marked decorative, a hyphen in an empty matrix cell and a funnel arrow, both of which said what an empty cell already says; **six external reference links rendered in the browser's own blue** because that document never styled a link, at 2.05, and a link with no rule is a link wearing the user agent's stylesheet; and the last four were **mermaid class definitions naming their own colour**, one of them a deferred-node grey at 2.43 on its own fill. **An override written for those was inert and was deleted rather than kept**, because mermaid scopes its rules by id and a class selector never wins: a rule that looks like it does something is worse than no rule. | Handoff step 8, the critique, 2026-08-23 | The course chrome is not the product and its documents link neither `components/index.css` nor `base.css`, so no product sweep has ever read them and no product rule reaches them. **Both engines returned the identical 301**, which is what a colour arithmetic defect looks like as opposed to a rendering one |

---

## What only a clone shows, found by the Handoff clean-clone test

Two rows from Handoff step 6, 2026-08-23. Neither is visible to any instrument in this repository,
because all of them open a file that is already on the disk. **These came from raising the package
from a temporary folder as a stranger would**, which is the one test that reads what a person
actually receives rather than what the author already has.

| # | Item | Source | Note |
|---|---|---|---|
| ~~237~~ | ~~**A person handed this repository downloads roughly 278 MB of history to receive roughly 60 MB of package, and every megabyte of the difference is images that were deliberately deleted.** `.git` measures 278 MB against a working tree of about 60 MB. The two heaviest blobs ever committed are `ui-kit/screens/kit-before.png` at 8.9 MB and `kit-after.png` at 8.6 MB, and `.gitignore` already carries the reason they went: they are the pixel proof of a sentence `ui-kit/screens/README.md` already states, and they regenerate in ninety seconds. **A deleted file is not a smaller clone**, which is the one thing the deletion did not buy, and nothing in this repository has ever measured the difference. The fix is a history rewrite and that is not a documentation decision: it changes every commit hash, and the tag this stage is about to cut would name a commit that afterwards does not exist. **So it is filed rather than done, and it is filed BEFORE the tag on purpose**: a rewrite is cheap now and expensive the day somebody has cloned it.~~ **CLOSED 2026-08-23, AND THE ROW WAS WRONG ABOUT WHAT THE HISTORY IS MADE OF.** It said every megabyte of the difference is images that were deliberately deleted. Measured over every blob in every commit: **1,774 MB of blob bytes, of which HTML is 80.3 per cent and markdown 11.5, and images and fonts together are 5.6** (99.5 MB). `wireframes/` alone is **43.4 per cent** and `ui-visual/` 31.8. The heaviest single path in the whole history is **`docs/decisions.md` at 110 MB**, which is the append-only record this project least wants to lose, and the grey tree's documents are 11 to 12 MB each because every one carries a copy of the shared stylesheet and every sweep rewrites all 119. **The weight is the method, not a mistake, and the two heaviest images the row named are 17 MB of 1,774.** **AND THE 278 MB WAS AN UNREPACKED `.git`, WHICH IS THE WHOLE FIX.** `git gc` took it to **115 MB with every hash, tag and branch untouched**, `git fsck` clean; and a clone from the published remote was already costing **129 MB** rather than 278, because GitHub packs its own. **A history rewrite is refused and the refusal is the decision**: it would change every commit hash and invalidate a tag that is already published, to remove 5.6 per cent of the bytes, and the largest thing it could remove is the record. **The row was filed on the author's local disk state and read as a property of the package**, which is the same shape as every other count in this repository that was typed rather than measured. | Handoff step 6, the clean-clone test, 2026-08-23 | The clone is otherwise exactly right: 685 files, 0 content files lost to `.gitignore`, 0 submodules, no LFS, no `package.json`, `Makefile`, `Gemfile` or `requirements.txt`, 0 absolute paths to the author's machine, and 0 references to `localhost`. **330 documents walked from `index.html` over 52,335 links on two engines: 0 page errors and 2 dead links, both `one-shot.md`, which step 7 writes.** |
| ~~238~~ | ~~**One tracked file has a name in another language, it is the third-heaviest blob in the history, and nothing references it.** the one file under `concept/brand-toolkit/images/`, whose name begins `ChatGPT Image 17` and carries a month and a year in Cyrillic, 2.4 MB, referenced by **0** documents in any tree. It is also the only path in the package that is not ASCII, which is the classic way a clone stops matching its index on a filesystem that normalises unicode differently from the one it was written on: it clones correctly here because it was written here. **The rule that files here are written in English was stated about generated documents and never measured over the tracked set**, which is this repository's own "a rule stated over a set is measured over the set" arriving one layer below the prose. Deleting it, renaming it, or keeping it with a reason are all fine; leaving it undecided is what is not.~~ **FIXED 2026-08-23 by reading the file rather than deleting it.** It is the **B, grounded vault** brand plate: graphite ground, one brass accent, green and red on the outcome pair only. **It is the board the shipped visual language came from**, so it is kept, and it is `B-vault-01.png` now, which is the naming convention its own README had written three lines above and never used. The README describes it, so it is referenced by a document instead of by none, and reads it as an exploration: it carries the project's old name and the trader words the voice contract later banned. **0 tracked paths outside ASCII.** | Handoff step 6, the clean-clone test, 2026-08-23 | It sits in `concept/brand-toolkit/`, a folder of three prompt documents and their output. The other three files there are English and referenced. |

---

## Closed by Stage 11's own follow-up, 2026-08-15

| # | Item | Verdict |
|---|---|---|
| ~~A~~ | ~~A toast arriving and leaving needs a state~~ | **Closed, and no state was invented.** `@starting-style` makes the element's own first paint the entering state, so a toast that a controller inserts arrives with `opacity` and an 8px travel and no `.is-entering` class the product would never wear. Both engines checked before the rule was written. The four toasts written into `toasts.html` are at parse time and simply at rest, so the specimen page is unaffected |
| ~~B~~ | ~~Load more appending cards needs a state~~ | **Closed as NEVER.** `#loadMore` has **0 handlers anywhere in either tree**, and clicking it in a browser leaves the card count at 13 before and 13 after. There is no append, so there is no moment, and a movement needs a moment the way the odds delta needed a delta. It returns with the controller |
| ~~C~~ | ~~349 `<details>` expand with no transition~~ | **Closed, and the height is deliberately not animated.** All 349 fade in on `::details-content` with `transition-behavior:allow-discrete`, and the 339 that are positioned out of flow also travel 4px from the control that opened them. Three ways to animate the BOX were built and read at 120ms into a 600ms transition: `opacity` runs in both engines, `interpolate-size` runs in **Chromium only**, and `grid-template-rows:0fr` to `1fr` animated in **neither**. There is no cross-engine way, so the panel fades and the room for it appears at once |
| ~~D~~ | ~~38 font-size literals in `ui-kit/_page.css`~~ | **Closed, 34 moved and 4 declared.** The four have no ramp step: three 9px below `--text-10` and a 22px between two steps, and the stand's own title clamp is now `rem` rather than px. Proved inert at the default root: **0 of 3,724 font-size readings differ**, and every one of them now moves with a reader's font setting |

| # | Question | Why it blocks something |
|---|---|---|
| ~~6~~ | ~~Commission rates across competitors~~ **DECIDED 2026-08-10: 1.5% OF THE STAKE**, `decisions.md` same date, and `PRODUCT.md` carries it. The research was already in the repository and nobody had read it against the product: Kalshi charges `0.07 x p x (1-p)`, which is 1.75% of notional at a 50/50 midpoint, Polymarket 0.8% to 1.8% on crypto and 0.30% flat in the US, Hyperliquid HIP-4 0%. **And a rate was already shipping that nobody chose**: `fee = 0.03 * payout` in the page scripts of 13 painted screens, 17 constants, which is about 6% of the stake at even odds and 3.4x the dearest competitor. The basis moved to the stake because a person can check a percentage of the number they typed and cannot check a percentage of a number that does not exist yet. Applied: 17 constants and 13 labels, `Fee (only if you win)` to `Fee (1.5% of your bet)`, and a $5 bet now reads $0.07 where it read $0.39. | The business model is "commission per bet" with the % still TBD |
| ~~7~~ | ~~Min / max bet limits~~ **DECIDED 2026-08-10: $1 MINIMUM, NO MAXIMUM**, `decisions.md` same date. The question had three written answers in three places: `PRODUCT.md` said "$1 / $5 sizing", the microcopy said "No minimum or maximum", the deposit dialog said "Minimum deposit $10", and the amount chips on screen are $5 / $10 / $25 / $50. The minimum exists so the fee line is never absurd against the stake, and $1 is the try-it size the MVP scope already names. Applied: **21 strings across all three trees**, "No minimum or maximum. Payout depends on when you bet." to "$1 minimum, no maximum. The price you see is the price you get.", plus the row in `voice/docs/microcopy.md` that is their source. **The chips are a separate row, 94.** | Launch position is "no limits"; Polymarket uses a $0.01 minimum and the reason is unknown |
| ~~8~~ | ~~KYC thresholds~~ **DECIDED 2026-08-10: THE FIAT RAIL ONLY**, `decisions.md` same date. Required for card deposits, where the on-ramp provider performs it anyway; **a crypto-only user is never asked**. It keeps the product's core inversion intact - the wallet and the verification are not conditions of browsing or of forming a bet intent, they arrive at Confirm - and it is what Polymarket does. Geo-restrictions are unchanged. **No copy changed, and that is the finding**: the deposit dialog has read "KYC is required for card deposits; crypto-only users can connect a USDC wallet instead" since the voice pass, so the product had already promised this in words while the decision behind it was open. A decision with a legal component; this is the design default and not legal advice. | Required for fiat deposits; crypto-only users undecided (Polymarket operates without KYC for crypto) |
| ~~9~~ | ~~Blockchain / chain selection~~ **DECIDED 2026-08-10: BASE**, `decisions.md` same date, `PRODUCT.md` tech stack carries it. Chosen on the three things this product needs from a chain rather than on a general preference: **native USDC issued by Circle** rather than a bridged representation, which is what the funds-safety line "held 1:1" is claiming; **L2 fees low enough that the $1 minimum decided in row 7 is not eaten by gas**; and **the shortest fiat on-ramp**, since the card path is Coinbase's own and a fiat on-ramp is in the first release. Polygon is the proven alternative and is what Polymarket runs on, but its USDC is bridged. Ethereum mainnet is out on fees alone at a $1 minimum. | Ethereum vs Polygon vs Base vs Arbitrum; picked on fees, and nothing has been measured |
| ~~10~~ | ~~AMM mechanism specifics~~ **DECIDED 2026-08-10: SHARES AT A LOCKED PRICE**, `decisions.md` same date, and `PRODUCT.md` Event resolution is rewritten. You buy YES or NO at the price shown and that price is locked at Confirm; a winning share pays $1. **Timing matters because the PRICE moves, not because the payout rule computes differently**, which is what makes the number sayable in one line - and "explain the number" is the product's own differentiator. It also makes the Confirm reconcile (S5) the thing it already looked like: the price moved, here is the new one, commit or not. The rule it replaces, "payout depends on when the bet was placed", was never specified and could not be explained to a newcomer. Applied to the bet panel copy with row 7's string. | The payout rule ("payout depends on when the bet was placed") is stated but not specified |

---

## Product decisions deferred

| # | Item | Note |
|---|---|---|
| ~~11~~ | ~~Resolution mechanics for recurring markets~~ **DECIDED 2026-08-10: EVERY CADENCE INSTANCE IS ITS OWN EVENT**, `decisions.md` same date; `PRODUCT.md` and `ia/docs/sitemap.md` both carry it. "BTC above $150k this week" is one Event with one window, one price and one resolution, and next week is a different Event; the cadence is a **series** the instances belong to, and the Frequency filter filters by the series attribute. **Nothing new enters the model**, which is the whole reason to choose it: Active Bets, notifications, the win and loss screens and the resolution record all keep working on the Event they already work on. The alternative, one long-lived event that resolves repeatedly, needs a second kind of position, a second kind of notification and a payout rule per cycle. The sitemap had already sketched this in a parenthesis, "each cadence instance resolves on its own schedule", and the parenthesis is now the rule. **This was the last unwritten mechanic in the brief.** | Markets are one-time or recurring (Hourly / Daily / Weekly / Monthly) and the Frequency filter ships on the feed, but how each cadence instance resolves on its own schedule is unwritten. See `ia/docs/sitemap.md`, Event entity |
| ~~27~~ | ~~**The footer promises 16 destinations that do not exist, and 8 of them are not on the map.**~~ **DECIDED AND DONE 2026-08-10: THE EIGHT ARE CUT**, `decisions.md` same date. Each of the eight was either a node the map had to gain or a label the footer had to lose, and it is the label that goes: `Sports`, `Trending topics`, `API / Developers`, `Status`, `Careers`, `Press`, `Brand`, `Geo restrictions`. **`Sports` is why it went that way**: the four categories are locked for MVP and Sports is post-MVP, so a fifth in the footer contradicted the category decision and not only the map. **Applied by a throwaway sweep over the live trees, 283 document-edits over 264 documents**: 105 painted footers, 87 grey, 2 kit. **Three of the four `Company` links were in the eight**, so the column is gone and `About` sits in `Support` - a heading over one link is not a column. **`By topic` held one item after `Trending topics` went**, so the sub-label is gone and `View all events` joined the category list. After: **0 of the eight remain in any live tree, 0 empty lists, 0 footers with fewer than three columns, page overflow 0 and page errors 0 over 264 documents at two widths.** **The sweep took a destination it should not have and it was put back**: the grey tree's markup carries a `<span class="tbd">` badge after each dead link, so the second pass matched a wider block and removed `View all events` from all 87 grey footers and one kit page. Restored from `HEAD` on 88 documents. **A sweep written against one tree's markup is a sweep tested on one tree.** What is left dead in the footer is row 28's: five registered nodes awaiting screens, three the map declares `[ORPHAN]`, and five social links. `ia/docs/sitemap.md` records the cut, and **`Geo restrictions` is the one to re-read when compliance is written**: the requirement is real and stays in `PRODUCT.md`; if it needs a page it gets a node first and a label second. The original row: \| Owner: **Stage 03 (Information Architecture)**, the map half. Found by the Design System step 7 source walk, 2026-08-03 \| Measured in both trees: **16 distinct `href="#"` labels, each on all 104 painted screens = 1,664 links into nowhere**, and the same 16 in the grey tree (87 screens carry a footer there; the 17 overlay screens do not). They fall into three classes and only the third is IA's. **(a) Registered, 5:** Terms, Privacy, Responsible play, About, Contact are page nodes in `ia/docs/sitemap.md` awaiting a screen - that is item 28's half. **(b) Declared `[ORPHAN]`, 3:** Leaderboard, Help Center, FAQ, which the sitemap says explicitly not to build until a job is confirmed, so the footer is offering what the map refuses. **(c) On the map nowhere at all, 8:** `Sports`, `Trending topics`, `API / Developers`, `Status`, `Careers`, `Press`, `Brand`, `Geo restrictions`. The last class breaks a rule `ia/docs/sitemap.md` wrote about itself in the SYSTEM AND GLOBAL section: the legal and footer destinations are "registered here so the footer and the cookie banner never promise a destination the map omits". The reconcile was **named and half-done**: `ia/docs/pages/seo.md`, Global Footer section, already says "Register every destination the footer promises ... as a node in `sitemap.md`, so the map and the footer do not diverge. This is a Step 7 reconcile item", and it lists six. Six were registered; the other eight were never listed, so the instruction was followed exactly as written and the gap it was meant to close stayed open. Each of the eight is either a node the map has to gain or a label the footer has to lose, and only IA can say which. `Sports` is the sharpest: the four categories are locked for MVP and a fifth in the footer contradicts the category decision, not just the map \|| |
| ~~28~~ | ~~**The footer component promises what the system cannot deliver.**~~ **CLOSED 2026-08-10, once 27 had said which of the sixteen survive.** The row named three things and all three are answered. **(1) A link that goes nowhere is not an `<a>`**, which is the rule the side panel has had since it was written and this component never got: the three destinations the MAP REFUSES - `ia/docs/sitemap.md` declares Leaderboard, Help Center and FAQ `[ORPHAN]`, do not build until a job is confirmed - are `<span class="footer-soon">` now, **582 rows over 194 documents**, one step quieter than a live label so the difference is visible rather than only true. One rule in `footer.css` carries it. **(2) The sixteen labels are one markup repeated**, so 27's decision landed in one place, which is what made both halves of this cheap. **(3) The trust strip makes a dead row expensive**, and it is the reason the five that stay `<a href="#">` are the five with registered map nodes and screens still to build: a promise that is being kept slowly is not the same as a promise the map has declined. Dead footer anchors **2,534 to 1,952** over 264 documents, page overflow 0, page errors 0. What remains is those five plus five social placeholders per footer, and both are destinations that will exist. The original row: \| Owner: **Stage 09 (Design System)**, the component half - split from item 27 on purpose. Found the same pass, 2026-08-03 \| The other half of the same 1,664 links, and it is deliberately a second row because the two halves have different owners and different fixes. `components/footer.css` and the footer markup are the system's; what the footer LISTS is a composition the system ships on every screen, so a dead row is a system defect however the map is resolved. Three things belong here and not to IA: a link that goes nowhere should not be an `<a>` (the side panel already has this rule - "a row that goes nowhere is not an `<a>`" - and the footer never got it); the 16 labels are one markup repeated 104 times, so whatever IA decides lands in one place; and the trust strip above them makes the dead rows more expensive than a dead row usually is, because the block's whole job is to be checkable. **Not fixed in this stage** and that is a decision: the composition cannot be settled before item 27 says which of the eight survive, and guessing would put the system's answer ahead of the map's \|| |
| ~~93~~ | ~~**Five trader terms shipped in both trees, and the rule that should have caught them was a LIST rather than an invariant.**~~ **RENUMBERED FROM 29 ON 2026-08-10, which is item 74 arriving a fifth time**: two different rows carried the number 29, this closed one and the icon stroke in the section below, and the stroke is the one four stylesheets and a stand page now point at. A renumbering, not a new item. **CLOSED 2026-08-03, and it became gate 33.** All five rewritten in both trees by `wireframes/_generators/voice_reconcile.py` (97 replacements on 22 files, second run 0): `Holders` -> `Bettors`, `Liquidity` -> `Available to bet`, `Market` -> `How the odds are set`, `Market Context` -> `Background`, and `(AMM)` dropped from the fine print. The three that pass are untouched. `voice/docs/microcopy.md` Step 28. **And the gate found two things the decision had not.** Its own two-way proof reverted each of the five and demanded a red for each: `Holders` came back GREEN because `holder` was not in the lexicon at all - the list held `position` and not the person who has one - and `Liquidity` and `(AMM)` came back green because the scanner's regex could not see inside an element whose parent it had already consumed. `voice.md` gained `holder`; the scanner was rewritten. The original row: Owner: **Stage 09 (Design System)**, step 8, the copy half. Found by the authored fan-out, round 3; the invariant written 2026-08-03 | **THE DECISION IS ONE EDIT TO THE RULE, NOT FIVE TO THE SCREENS, and it is already made.** `voice/docs/voice.md` now carries the invariant beside the ban: *the ban is about PLACE, not about the word* - a trader term is forbidden wherever a person meets it while ACTING (a control label, a heading, a figure read to decide) and allowed inside a block whose whole job is to explain the mechanism, glossed in plain words; **and the head of an exempt block is not inside the exemption**, because a summary, a tab and a title are read by everyone who never opens them. The lexicon already carried that shape twice without stating it ("only in mechanics docs, never in the UI"; "allowed once, with a plain gloss"), which is why `voice/docs/microcopy.md` Step 24 could apply it correctly to four lines - it just logged the result instead of adding the rule. **The eight placements, sorted by the invariant. FIVE FAIL:** `Holders` on the `.seg` button inside the Comments tab, a control label (9 screens per tree); `Liquidity` in `.ms-label` / `.ms-val`, a bare figure - and the panel is exempt for EXPLANATION, which a naked number is not, so it also fails principle 1 (9 per tree); `Market` as `.market-title`, the `<summary>` of the collapsed panel, read by everyone who never opens it (9 per tree); `Market Context` as `.rules-tab`, decided first because it decides the other - a tab label is an invitation and not an explanation, and this pair is the one place `.rules-note` exists to keep unconfused, so it is the worst spot in the product for the product's most-confused word (9 per tree); and `(AMM)` in `.fine` under the bet panel, fine print read before Confirm (4 screens; 4 occurrences grey, 8 painted). **THREE PASS:** `order book` and `AMM` in `.md-sub` inside the open panel, which is the mechanism being explained and says what it is NOT (9 per tree each), and `AMM` on How It Works (1 per tree). Both trees carry every placement at the same count, so gate 18 is quiet and correct. **Route, when step 8 fixes it:** the grey tree is frozen against REGENERATION and not against a text edit, so an idempotent in-place post-processor beside `wireframes/_generators/footer_reconcile.py` plus the same edit in `ui-visual/`, because gate 18 compares `<main>`. **The class it belongs to:** an invariant written as a list of instances. Third time here - the screen map as five hand copies before `_twins.py`, Step 24's 43 shipped strings with no row, and Step 14, which declares "Clean on cents/spread/liquidity/order book/buy-sell" and left every instance outside its own list, including the `.fine` line it edited in that same pass |
| ~~77~~ | ~~**The featured hero on the feed is in both trees and in no document.** **(filed as 26 until 2026-08-09, and 26 was already taken)** Owner: Stage 12 (Handoff). Found by the Design System step 3 prohibitions sweep, 2026-08-03~~ **CLOSED 2026-08-13. KEPT, SCOPED AND WRITTEN DOWN, and the day it was decided is the day its photograph was found not to be drawing.** The three questions are answered in `wireframes/_conventions.md` S7 and `ia/docs/sitemap.md`: the featured slot is MVP, it stands on the Trending feed and nowhere else, and what fills it is the highest 24-hour volume among open markets, a figure the product already holds, so there is no editorial queue and no admin screen and it can never feature a resolved event. It adds no node to the map and no state to the feed, because the absent slot IS the feed empty state. **And the decision was taken with a fact rather than against it**: the hero photograph had rendered the top-left 9.8% of its frame since the day it landed, which is empty sky, and nobody noticed in three weeks. That is row 139, fixed the same hour. | `.feed-hero` stands on **1 screen of 104** (`event-feed.html`), painted and grey. It entered the paint in `42d1205` (Stage 08, the Vault canonicalisation) and was back-ported into `wireframes/event-feed.html` in `e5d60c0` (Stage 09 step 7d) so that gate 18 would pass. That is the path a block takes when it is **built rather than decided**: `wireframes/_conventions.md` owns block-level structure and its "Shared patterns and axes" section runs S1 to S6 with no hero among them, while `ia/docs/sitemap.md` uses the word only as a metaphor for the feed. Three questions therefore ship unanswered: is a featured slot MVP, on which screens does it appear, and what decides which event fills it. It is deliberately **not** a usage rule - one screen is one occurrence, so there is nothing to count and nothing to forbid - which is why the step sent it here rather than into "Rules of use". Handoff owns it because that is where a question a developer will hit gets delivered with the numbers already taken, the same reasoning that set the owner of item 25 |

---

## Design defects deferred

| # | Item | Source | Note |
|---|---|---|---|
| ~~198~~ | ~~**The phone's filter sheet arrives with no movement, and it is the only silent moment left that a duration cannot fix**~~ **CLOSED 2026-08-17, AND IT CLOSED BY DELETING A SPELLING RATHER THAN ADDING ONE.** The sheet takes `sheet-rise` and its scrim takes `sheet-appear`, both `--dur-slow` `--ease-enter`, and neither is new: they are what the 337 modal dialogs already do on a phone. **What the third reader forced was the thing the Animation stage had named and not finished**: the product performed ONE movement under two names and in two properties, `sheet-rise` at `translate:0 100%` in `dialog.css` and `betSheetUp` at `transform:translateY(100%)` in `betpanel.css`, and the stage unified the DURATION because a duration is what its instrument could see. Adding a third spelling to close this row would have been the drift growing. Both keyframes are one, in `base.css`, on `translate`, **and they left the media query they were declared in**: a `@keyframes` name is document-wide, so `sheet-rise` inside `dialog.css`'s `max-width:39.99875rem` block existed only below the desk rung and any other reader would have resolved to no animation at all, silently, at every other width. Measured in both engines: the sheet starts at `translate:0px 100%` with its top at 844 and settles at 578, the scrim starts at `opacity:0`, the bet sheet and the phone modal still rise, the desk modal still only fades, and under `prefers-reduced-motion` every one of them still ARRIVES. Rest state proved unchanged by stripping comments, transitions, animations and keyframes from all four stylesheets before and after: byte-identical, 4 of 4. The original row: \| **The phone's filter sheet arrives with no movement, and it is the only silent moment left that a duration cannot fix** \| `components/filters.css`, 2026-08-17, found by the motion census that gave 71 declarations a response \| The sheet is `.filters-toggle:checked ~ .feed-controls`, and it goes from `display:none` to `display:flex`: **1,012 property-on-placement readings over 44 screens change with nothing between the two states**, the ground, the top edge, the shadow and the two labels all at once. It is the largest surface in the product that appears without saying where it came from, and it is the one a thumb summons. **A transition cannot reach it as the system is written**: `display` is a discrete property, so the arrival needs `transition-behavior:allow-discrete` plus an `@starting-style` block, or an `animation` on the open state the way `search.css` does its panel. The second is the mechanism this system already owns, which is the argument for it. **The distance and the direction are already decided elsewhere** - `header.css` moves a dropdown 4px and `search.css` borrowed that rather than choose a second number - and a sheet rising from the foot is the `--dur-slow` arrival `dialog.css` performs 341 times. Owner: the next motion pass. Not done today because a new mechanism in the system is a decision and not a sweep \|| | |
| ~~206~~ | ~~**Ten documents tell the reader they are on a different page, and three screens stand in neither tree's panel**~~ **FILED AND CLOSED 2026-08-18, AND THE DISAGREEMENT BETWEEN THE TREES WAS THE FINDING.** The three search screens shipped 2026-08-16 by copying the feed, and the four Type 1 documents landed 2026-08-17 by copying `terms`; **the current-page marker came with the copy in both cases**. So all three search screens, in BOTH trees, marked `event-feed.html` as the page you are on, and the five Legal and Company documents marked `terms.html` in the paint. Separately the grey panel never got a row for search at all: `-results` and `-empty` were reachable from **0 of 113** grey documents, and the surface itself only through the header magnifier, which the 17 invoked-overlay screens do not render, while the painted sidebar had listed all three from day one. **Nothing could see any of it**: both panels are off-canvas, so this adds no height, no sideways scroll, no duplicate id and no page error, and the sweeps pass either way. **The instrument had to be read first and it was wrong twice.** Counting one marker instead of two called 85 grey documents defective when 82 were correct, because the panel marks the parent SCREEN and the state row separately. And a grep that wanted the href and the label on one line reported the painted sidebar as missing four rows on 109 documents that already had them; a first pass believed it and duplicated the block, which the rendered page caught, not the source. Fixed: three rows into the grey panel in 113 files, the canonical Legal and Company block into the 5 painted documents that did not list themselves, and the marker moved on 10. **Both registries now name the same 113 documents and every document marks itself.** 908 renders over two engines at 390 and 1280 with a positive control: 0 sideways scroll, 0 duplicate ids, 0 page errors; 454 height and body-width rows against the tree before the edit, **0 moved**, with the instrument proved at 0 first; 20,487 grey and 19,698 painted internal references, 0 broken. | The registry sweep, 2026-08-18 | **CLOSED**, `decisions.md` same date. |
| ~~207~~ | ~~**All 31 shared-region headers in the grey tree carry a stale number, and the preamble contradicts itself in one sentence**~~ **FILED AND CLOSED 2026-08-18.** This tree links no stylesheet, so the region headers are the only check it can carry: `SHARED (N of 113, R rules)` is a claim that a reduced copy contradicts. **Every denominator still read 109 against a tree of 113, and 19 of the 31 numerators were exactly four short** - the four Type 1 documents that landed the day before. The preamble called the file one of 109 copies and then said the rules stand in N of the 108 documents, in the same paragraph. A header that is wrong is not cosmetic, it is the check switched off. Recomputed by walking the tree rather than retyped; R untouched because no rule moved. **The pointer at its foot named a count, `chrome copied 108 times`, and now names its subject**, because a pointer carrying a number goes stale on a day nobody touched the thing it points at. The section it points to keeps its numbers: it is dated, and a dated reading is a record rather than a claim. | The registry sweep, 2026-08-18 | **CLOSED**, `decisions.md` same date. |
| ~~208~~ | ~~**FJ1 is the job the whole browse layer exists for and it has no flow, so search shipped with no route on any chart**~~ **FILED AND CLOSED 2026-08-18.** `ia/docs/flows.md` draws MJ, FJ2, FJ5+EJ3 and SJ1, and its coverage note names SJ2, the notifications list and the bet-history tab as covered passively **by design, not a gap**. FJ1 was in neither list. The word search appears 5 times in that file and **all five are the substring in `user-research/`**. So the layer that is four surfaces wide - feed, category pages, Favorites, search - had no chart, and the surface added on 2026-08-16 had nowhere to be drawn. Written now as its own flow above FJ2: 20 nodes, 28 edges, the two search faces split at the RAIL rung with the measurement that chose it, the cold-entry path that is also the 404 escape, and **T17 as a churn terminal rather than an error**, because a query with no match is the open set answering, not a failure. | The relationship audit, 2026-08-18 | **CLOSED**, `decisions.md` same date. |
| ~~209~~ | ~~**The Favorites view has no declared state set anywhere, and it is missing the one state its own source screen has**~~ **FILED AND CLOSED 2026-08-18.** Every `States:` line in `ia/docs/sitemap.md` was extracted and matched against the families on disk: **13 screens declare a set and 13 match it exactly.** The Favorites view is the only screen in that document with **no `States:` line at all**, because it is described as a view over the Event Feed rather than as a screen, and the trees had built loading and empty and **no error, for as long as the view has existed**. **A state declared nowhere cannot be compared to what stands**, which is the twin of the count rule and worse: it fails silently instead of drifting. Written both ways - the rule and the set into `sitemap.md` (**a view over the Event Feed carries the Event Feed's state set, because it fetches from the same place and fails the same way**), three rows into `voice/docs/microcopy.md` before the screens shipped, and `favorites-error.html` into both trees. **And the three asymmetries around it are DECLARED, not holes**: the Loss Screen has no error because the Win error is Share Card generation (T11) and the Loss Screen has no Share Card; the Wallet has no empty because a wallet with no transactions still has a balance, an Add funds control and the funds-safety line; and search is `page + 2 states` in `ia/docs/pages/system.md` and has exactly two, because it filters an open set the page already holds and has no fetch of its own to be pending or to fail. | The state-matrix read, 2026-08-18 | **CLOSED**, `decisions.md` same date. |
| ~~211~~ | ~~**The stand draws a slippage ladder the product rules out by name, and the component's page, its shelf and its own hero text tell three different stories**~~ **FILED AND CLOSED 2026-08-18, BY THE CLOSING CHECKLIST OF A STAGE THIS REPOSITORY NEVER HAD.** There is no roll-out row in the README table or in `assets/_roadmap.js`: this tree was painted family by family and every stage since kept both trees in step, so the roll-out happened without ever being a stage. Running its checklist anyway is what found this. **The two-sided idle control is the half nothing had ever taken from the RENDER.** Read from the browser's parsed rules crossed with the rendered DOM over the paint and the kit together: **818 classes declared, 820 standing**. Five stand with no rule and **all five are declared `Script hooks:`** in their component's header, which is backlog 203's fix holding. Three are declared and worn by nothing and **all three are runtime state** written by script, `next`, `open`, `planned`. **The finding was the ninth: `.md-table`, `.md-row`, `.md-row-head`, `.md-bar`, `.md-amt`, `.md-price` and `.md-get` drew a three-row price-impact table** - Bet / Avg YES price / You receive if YES, $1,000 moving the price from 38 to 42 per cent - **on 0 of the 9 product placements and on the SHELF.** So `ui-kit/market.html` drew the sentence the product draws, `ui-kit/molecules.html` drew the ladder, and the page's own hero text said "a table of what a bet of each size would cost" while the page under it showed the sentence. **Three artefacts, three stories about one block.** **It is not a face waiting for a placement, it is a face the product contradicts.** `PRODUCT.md` says "NOT a trader terminal: no order books, leverage sliders, PNL ranks", the market is AMM-priced so there is no book to be deep, and the sentence this block DOES ship promises the opposite in so many words: **"The price is locked when you confirm, so it cannot move against you."** A table showing the price moving against you at size is that guarantee denied on the same screen. Nine rules deleted the way `account.css` went, the shelf given the product's face, the hero sentence corrected, two inventory rows turned. **And one more, one line wide**: `.tk-mo-still span{animation:none}` in `ui-kit/_page.css`, a way to freeze a specimen that no page asked for, while the rule that does the freezing sits eight lines below under the real setting. **A source grep could not have found any of it**: it called the five live script hooks orphans in the same pass and could not see the classes the page scripts write. 1,160 renders over three trees on two engines: 0 sideways scroll, 0 duplicate ids, 0 page errors, and the control page needed its own `<meta name=viewport>` before it could see anything, because Chromium under `isMobile` gives a document without one a 980px layout viewport. Height and body width against HEAD: **4 of 352 rows moved and all four are the two kit pages that were meant to move; 0 of the 115 product documents**. | The roll-out checklist, 2026-08-18 | **CLOSED**, `decisions.md` same date. |
| ~~210~~ | ~~**114 grey documents carry a note saying the bet-panel states are still to come, and it lists a state that belongs to another axis**~~ **FILED AND CLOSED 2026-08-18.** The panel's note under Event Detail read "Bet intent + states (reconcile, insufficient-balance, event-closed, processing, on-chain error) now live in the Event Detail bet panel - **states to come**". All five bet-panel states shipped, the last of them `event-detail-bet-ready.html` on 2026-08-17 with backlog 185. **And `event-closed` is not a bet-panel state**: `ia/docs/sitemap.md` names it `resolved-while-reading / event-closed` at PAGE level and it ships as `event-detail-resolved.html`. **One state wearing two names in two places**, which is the identity rule that cost six fixture contradictions, reappearing in a note nobody re-read. Corrected in all 114 documents, and the note now says which axis owns which. | The state-matrix read, 2026-08-18 | **CLOSED**, `decisions.md` same date. |
| ~~205~~ | ~~**The logged-out header puts content past the right edge at the narrowest width**~~ **CLOSED 2026-08-18, AND BOTH HALVES OF THE ROW AS FILED WERE THE INSTRUMENT.** It said four documents and it said WebKit only. `document.scrollingElement.scrollLeft = 9999` is this repository's approved sideways-scroll probe, and **under `isMobile:true` Chromium widens the layout viewport instead of scrolling**: a control page with a box 120px past the edge reads back 0 while `innerWidth` reads 440 against a `clientWidth` of 320. Read with `scrollWidth` as well, the two engines agree exactly: **36 painted documents, every one of them logged-out, 2px over at 320 under mobile emulation, 0 at 360, 0 without it, 0 grey.** **And the cause was neither engine nor emulation, it was the touch floor doing its job.** `@media(pointer:coarse)` takes the two icon marks in that cluster from 32 to 44, so the logged-out utility asks **213.72px against the 306 the row can give**. The pair was not simply overflowing, it was being SHRUNK to pay: `Sign in` measured 49.27 and `Sign up` 52.45 against their natural 63.97 and 69.17, so a reader saw a squeezed pair with the 14px gutter gone and 1.81px of `Sign up` off the screen. `header.css` already carried the walk that missed it, `0 of 105 from 358 up`, taken with a fine pointer on a row 24px narrower than a thumb gets. **No rule was added, and that is the fix.** The logged-out heart has been `.desk-only` since it was written, because below the rung the thumb bar carries Favorites in slot 3. The bell logged-out is the same kind of control one along: no badge, because there is no account to count for, and a route to Sign In that this row already offers twice and the thumb bar a third time. It takes the class its sibling wears, `base.css` supplies the rule, and the cluster falls to 193.14 with the utility landing on 306 exactly. **Walked 320 to 400 at 2px on Chromium and WebKit over all 36 logged-out documents, coarse pointer asserted in the page, a positive control at every width: 0 over, at every width**, and the rung is exact, hidden at 639 and shown at 640 in both trees. | Found 2026-08-18 while verifying the four new Type 1 pages | **CLOSED**, `decisions.md` same date. |
| ~~212~~ | ~~**The five document pages read at the feed's blurb size, and the paint had drifted 120px from the structure**~~ **CLOSED 2026-08-19**, `decisions.md` same date. Reported by the user on a screenshot and the report was right. Three decisions stacked, each taken for a different page. **(a) The paint stood at a 600px column against the grey twin's 720**, and inside it on **three left edges** against the twin's one, six widths on one page: 600 / 346 / 300 centred / 417 offset 91. Column width is not among the seven differences `wireframes/_conventions.md` declares. The mechanism is one declaration meaning two opposite things: `.feed-seo` centres itself with `margin-left:auto;margin-right:auto`, written for a block box, and **on a flex item an auto inline margin cancels `align-items:stretch`**, so the item shrink-wraps to its content. 417 was the paragraph's own `46ch` cap sizing the block that holds it. **(b) The prose was `--text-13`**, the size of a card blurb on the feed `seo-plate` was born on, so the only surfaces in this product that are nothing but prose read end to end were set in the smallest prose size it has, and the 60-to-75-character band capped the column at 409px on a 1220px frame. **Every sweep passed: the band was doing its job and the job was the wrong size.** `--measure` is in `ch`, so swept at 13, 14 and 16px against 38 / 40 / 42 / 44 / 46 / 48ch the longest line is the same count down every size column, 62 / 66 / 69 / 71 / 73 / 77. `--text-16` bought 409 to **503px at 70 to 73 characters**, and `DESIGN.md` gained a Document rank. **(c) `--container-doc` was a second measure**, 600 around 409, and it is deleted by the sentence in its own comment. Plus the plate: `ia/docs/blocks.md` declares the desktop width goes unused and the paint drew a border round it, 1140 holding 214 + 600 with 153px of nothing to the right. `width:fit-content` at RAIL in both trees gives 795 on terms and 561 on about. | | |
| ~~213~~ | ~~**The IA promotes the contents rail at a width that cannot hold the column it stands beside**~~ **CLOSED 2026-08-19 BY TURNING THE IA, and the build was right all along.** `ia/docs/blocks.md` said desktop promotes B5 to a sticky left column at `min-width 760px`; both trees have always used RAIL 900 and nobody had read the difference. Measured by forcing the rail on at four widths on `terms.html`: the reading column arrives at **388px at 760, 428 at 800, 488 at 860 and its full 503 at 900**. So promoting at 760 would make the reading column **narrower than it is at 640 with no rail at all**, which is exactly what the next clause of the same sentence forbids: *the body keeps its 60 to 75 character measure rather than filling the remaining width*. 900 is not a compromise, it is the first width at which both halves of the declaration are true at once. **A declaration has two halves and a build can satisfy one of them.** | | |
| ~~214~~ | ~~**`about.html` runs the DOCUMENT body shape and the bank gives it STATEMENT**~~ **CLOSED 2026-08-20, AND TWO OF THE THREE THINGS IT NAMED WERE THIS FILE'S SOURCE BEING WRONG RATHER THAN THE PAGE.** The breadcrumb: `ia/docs/pages/seo.md` D says in one sentence that the four legal pages carry `Home > Legal > {document}` and **About sits at `Home > About`**, which is what both trees have rendered and what both schemas carry, so `blocks.md` B2 saying DOC was the error. The order line: it omitted B9 and B10 while the table above it gives both to BOTH and the page has carried both since it was built, so the audit measured About against a line that contradicted its own table. **What was genuinely missing was B15 and B19, and measuring for them found B3's lede standing on 0 of the 5 pages** - the row read one page where the defect was the set, which is the auth-convention shape again. B15 is built as the STATEMENT wording of B3's lede rather than as a second block, because two sentences of the same rank under one H1 is what rule 1 of that bank throws out, and the content rule survives intact. The body was five sections each beginning `This section states`; four now say the thing and **`#regulation` is deleted**, the only section on the page that was in NEITHER source and whose subject `PRODUCT.md` marks `[?]`. **B17 had to be the breakdown and not the headline repeated**: `trustbar` prints `1,284 events resolved` in the footer of the same document, so the band is `1,284` resolved, `25` open, `2` challenged and re-read. B19 routes to the feed with the lexicon's own label, `Browse events`. Two rules added, `.read-col .doc-lede` and `.seo-figures`, both in both trees, with a stand on each component's page. `docs/decisions.md` 2026-08-20 | Found 2026-08-19 while repairing the reading column, by reading `ia/docs/blocks.md` against the rendered DOM | The bank's STATEMENT order is B1, B3, B21, B15 hero, B16 resolution and custody, B17 numbers, B18 people (LATER), B19 closing action, B11. The disk holds a breadcrumb, the H1, the prototype notice, the money answer, a link to How It Works, **five `feed-seo` sections**, a contact box and the sibling list. So **B16 and B17 are built as prose sections** rather than as a custody block and a stats band, which is a fair rendering of both; **B15 hero and B19 closing action are declared and not built at all**; and the page carries **B2, a breadcrumb the bank gives to DOC only**. Two of the three are product decisions rather than layout ones: a numbers band needs numbers, and a closing action needs a destination chosen, and `blocks.md` B19 already rules out the competitor default of a signup button. Same class as `blocks.md` line 45, which records that the four documents were built against `terms.html` rather than from the table. Owner: whoever writes the About page's real content |
| ~~228~~ | ~~**The four legal pages are still the prototype's own description of themselves**~~ **CLOSED 2026-08-20 AS A DRAFT FOR REVIEW, WHICH IS WHAT THE ROW ASKED FOR AND IS SAID ON THE PAGE RATHER THAN ASSUMED.** 47 sections written across the four, from what this repository has already decided and from nothing else: 2,168 words on Terms, 1,505 on Privacy, 925 on Cookies, 1,284 on Responsible betting. It has not been through legal review and B21 says so, in a wording that had to change because the old one became false the day the sections said something. **Three facts are open and the sections that need them name the gap rather than filling it**: the operating entity, the governing law, the excluded regions. `PRODUCT.md` carries `Jurisdiction: [?]` and this is what that costs on a page a reader reads. **And writing them found two places where the product contradicted its own documents.** `responsible-betting` described a deposit ceiling, a loss ceiling, a cooldown and self-exclusion as if a reader could reach them, while `ia/docs/sitemap.md` marks the whole Responsible-play slot **reserved, post-MVP, not built**: section 4 opens with *Account limits are not built yet* and gives the two things a person can do today. `cookie-consent` offered a **Marketing** category for cookies the product does not set, that `PRODUCT.md` has no surface for and that the IA never specified, while section 5 of the Cookie Policy commits to setting none: the category is off. **A heading on these pages is in three places** and the first pass reached two, which `ia/docs/pages/seo.md` said was impossible by construction and is not: both lists are hand-written. Rendered and compared, **94 contents rows against 94 headings over two trees, 0 disagreeing**. `ia/docs/blocks.md`, `docs/decisions.md` 2026-08-20 | Filed 2026-08-20 by the pass that wrote About, naming what it did not write | 214 asked for one page's real content and got it. The other four keep B21 in its original wording, which is honest and correct: every section of `terms`, `privacy`, `cookies` and `responsible-betting` still says what that section WOULD decide, and B21 exists so a page carrying prototype copy says so. **This is not the same job.** About was a writing task with the facts already in the repository; a term of service is legal review first and copy second, and `ia/docs/blocks.md` says so by name: the bank decides the blocks, the copy is legal review plus `voice/docs/microcopy.md`, in that order. The blocks are all built and the B4 effective-date block, the B5 contents and the B6 numbered body are all standing, so this is a fill and not a build. Owner: whoever has legal counsel |
| ~~215~~ | ~~**Grey `wallet.html` does not carry the `.protect-page` class its painted twin does**~~ **FILED AND CLOSED 2026-08-20, AND THE ROW HAD THE WRONG NOUN, WHICH IS WHY IT COULD NOT BE DECIDED.** It said the class "renders as a smaller box and nothing else" and is therefore a FACE, arguably the paint's business. **Measured in both engines at 390, 900, 1280 and 1600, it is a LENGTH.** The class carries three things and only the first is paint: a brass tint, which the grey tree cannot have under convention 1; a padding and type step, which grey's own `.protect-page` rule already had; and `max-width`, which decides how many characters a reader gets. Uncapped in `.cat-main`, a column sized for cards, the grey wallet's trust sentence ran **117 characters on ONE line at 900, 1280 and 1600** while its painted twin ran **61 over two at every width**. Line length is exactly what the `.read-col` finding of 2026-08-19 established is not a declared difference, so this was drift by `_conventions.md`'s own definition. **THE TELL WAS ALREADY IN THE GREY TREE AND NOBODY HAD READ IT**: its five document pages carry `.read-col .protect{max-width:none}`, an override for a cap that had never been written, which is the `text-transform:none` shape from `hiw.css` arriving again. A rule only cancels what something else is doing. **And the number is measured rather than borrowed, because `ch` is the advance of a zero and the trees have different faces**: `46ch` is 346px in DM Sans at 11px and 276px in the grey face at 12px, which gave 49 characters instead of 61. Swept eight values and took `56ch`, the middle of the 54-to-58 plateau where the line reads **61 characters over two**, the same count the paint gives at 346px. After: grey wallet **61ch at 336px**, and the five document pages **untouched at 503px** because the override finally has something to override. 48 renders over the six documents on two engines at four widths: **heights moved on the wallet only, +15 at 390 and +30 above it, which is the notice becoming two lines**, 0 sideways scroll, 0 duplicate ids, 0 page errors. The general rule is in `wireframes/_conventions.md`: take the colour off and ask whether the BOX changed. | Found 2026-08-19 by the placement census, 6 painted documents against 5 grey | **CLOSED**, `decisions.md` same date. |
| ~~216~~ | ~~**There is no maximum bet, and the locked-price promise gets more expensive with every dollar of one**~~ **CLOSED 2026-08-20, AND ALL THREE ANSWERS THE ROW OFFERED ARE REJECTED BY ITS OWN ARITHMETIC ONCE THE ARITHMETIC IS FINISHED.** `b = S/(2f)` holds at a price of one half and nowhere else. The general form is **`b = S(1-p)/(2fp)`**, solved exactly against the cost function rather than from the expansion: at `f = 1.5%` and a $5 bet, **$167 at 50 per cent, $273 at 38, $672 at 20, $1,513 at 10**. So `$167` is not the break-even for a $5 bet, it is the break-even for a $5 bet at exactly 50/50, and at that `b` the product's own default stake is under water at every other price. Inverted, `S_max = 2fb x p/(1-p)` is **$9 at 50 per cent and $2.25 at 20** when `b = $300`, the row of the table that makes the number a market: below the panel's own $10 chip and below the default. **A cap that bites at the default is not a limit, it is a different product**, capping the zero-slippage fill is the ladder 211 deleted, and setting `b` from the largest bet is not a solvable equation while there is no maximum and `b` must grow without bound at the ends. **And all three fail absolutely at launch, where the commission is 0**: at `f = 0` there is no `b` at which filling at the quote breaks even. **The fourth answer is to price the bet rather than the book**: the quote is computed for the amount in the box, shown, and locked at Confirm, so nothing is given away and no maximum is needed. **Every shipped sentence survives word for word, and the one 211 read as forbidding this is the one that permits it**: it quoted `it cannot move against you` and the shipped line ends `between the panel and the bet`, which scopes the promise to an interval and not to size. It costs nothing to draw: at `b = 1000` the size-adjusted quote rounds to the market quote on **19 of 19 shipped prices** at the $5 default, first differing at $25 by one point. `event-detail-bet-ready.html` draws that $25 in both trees. `PRODUCT.md` Liquidity and risk and Bet limits, `docs/decisions.md` 2026-08-20 | Found 2026-08-19 by arithmetic, closing the mechanism question in `PRODUCT.md` | The screens fill a whole bet at the quoted price, 18 placements over 18 documents with 0 slippage, and the line beside them says the price cannot move against you. Filling at the quote hands the buyer the slippage, and that give-away breaks even against a fee `f` on a stake `S` at exactly **`b = S / (2f)`**, which at 1.5% is 33.3 times the bet: $167 for $5, $833 for $25, $3,333 for $100. `PRODUCT.md` Bet limits decided 2026-08-10 that there is **no maximum**, so at every `b` there is a size above which the product has promised to sell its own promise at a loss. **Three legal answers and all three are product decisions**: cap the bet, cap the zero-slippage fill by size and say so, or set `b` from the largest bet you will accept. Costs nothing while the currency is points, which is why it is filed rather than repaired. Owner: whoever sets `b` for the first real-money release |
| ~~217~~ | ~~**`on-chain` stands on 114 painted screens and a points launch cannot prove any of it**~~ **FILED 2026-08-19 AND CLOSED THE SAME DAY, AND THE ROW ITSELF WAS THE WRONG SHAPE.** It read as "delete the word", and the pass found the line is not vocabulary but CLAIM AGAINST MECHANISM. **217 placements moved and about 364 kept `on-chain`**: every resolution claim now points at the Reading, and every custody or settlement mechanic keeps the chain, because a segregated balance you can check, a payout that is settling and a bet that failed to register are all things a chain genuinely does. The largest single string in this product moved with it, `hiw-step-b` on **114 painted and 97 grey documents**. The correction is not that the chain is gone from v1: **a hash proves a decision was recorded and cannot say what the decision was made FROM**, which is true with a chain or without one. `docs/decisions.md` 2026-08-19. | Found 2026-08-19 while writing the launch decision, by grepping the painted tree for the trust claims | The funds line reads `held 1:1, never lent, verifiable on-chain` on 114 documents, `1,284 events resolved on-chain` twice, and `on-chain error` is a whole error family. `voice/docs/voice.md` principle 5 is that the specific PROVABLE thing gets said, so shipping points with that copy is the one thing the voice contract forbids by name. **The promise survives and the mechanism word does not**: what the product actually offers is a published, append-only resolution record carrying its source and its timestamp, which is also what `Audience` names as the answer to the #1 churn driver. Scoped as a voice pass over `voice/docs/microcopy.md` with a rebuild of `voice.html`, not a redesign: roughly ten distinct strings across two trees. Owner: the pass before the first public build |
| ~~218~~ | ~~**The launch scope is decided and nothing is built: there are 509 commits and 0 lines of product code**~~ **FILED 2026-08-19 AND CLOSED 2026-08-20 AS A PLAN RATHER THAN AS CODE**, which is what the row asked for: it named a missing Build stage, not missing files. `docs/build-plan.md` carries the stack with its prices, the schema mirroring the IA's entities, the resolver job with its three states, seven phases of work and what is deliberately not built. **The number that made it a plan rather than a wish**: 116 painted documents collapse to **6 routes**, because a static tree needs a document per category, auth state and load state and a running app gets all three from data. The feed alone is 44 documents and one route. **91 of the 116 live inside the v1 routes**, and the only surface with no drawing is `/admin`, whose reader is the operator rather than Alex, which is why the IA registers it as not a node of its map. `docs/decisions.md` 2026-08-20. | Named 2026-08-19 in `docs/decisions.md`, from the launch decision | `PRODUCT.md` Liquidity and risk settles the mechanism, the counterparty, the currency and the catalog size for the first release: 7 one-time events, points, no commission, no chain. What it does not settle is the build. The design is not the blocker and neither is the mechanism; **the blocker is that 25 markets are a daily newsroom** and this file's own decision of 2026-08-10 multiplies it, since every cadence instance is its own event. The narrow slice is feed, event detail, sign-in, balance, confirm, my bets and ONE admin page, against the 115 painted documents that exist. Everything in `MVP feature scope` that touches a wallet, a chain, a fiat rail or KYC is out of it by the same decision. Owner: a Build stage that does not exist in the README table yet |
| ~~224~~ | ~~**The How often filter offers five cadences and the product draws exactly one of them**~~ **CLOSED 2026-08-20, AND THE ROW WAS RIGHT ABOUT THE CONTROL AND SILENT ABOUT THE CATALOG.** Both halves are fixed and neither is the one the row named. The filter now filters, in the paint, through ONE pass over the grid rather than a second hider: the sub-category rail and the cadence menu were two independent `hidden` writers and whichever ran last cleared the other, so Bitcoin then Weekly gave every weekly market in the category back. Measured on `event-feed-crypto.html`: **Any 8, One-time 6, Weekly 2, and Weekly then Bitcoin 1**. **The other half is that the control was offering a catalog nobody was going to open**: `docs/launch-catalog.md` opens two Weekly and two Monthly series in release 1 and no Hourly and no Daily, so Hourly and Daily come off the menu, and the menu then narrows AGAIN to the cadences standing on the page, the way the rail has been built from the cards since 173. Politics offers two rows, Crypto three, and no reader is offered a value that returns an empty grid. The drawing: `data-freq` on **425 cards in both trees**, counted from the rendered DOM, the remaining 158 of 583 being 136 skeletons and 22 event-detail header cards, the WORD on recurring cards only because a default restated is the activity feed `PRODUCT.md` refuses; two weekly Bitcoin and Ethereum markets added to the Crypto and Trending feeds; the monthly temperature record converted from its one-time phrasing to the instance the catalog describes; **`event-detail-recurring.html` in both trees**, because a cadence is not a Type and the type-specimen convention would have put a card reading `Repeats: Weekly` in front of a page reading `One-time event`. A series gets no surface: four series do not earn one, and the record a reader wants is **Earlier in this series**, a second placement of the related component. `assets/search.js` re-taken at 27 events. `ia/docs/sitemap.md` and `docs/decisions.md` 2026-08-20 | Found 2026-08-20 while picking the launch seven, by reading the control against the catalog it filters | The menu lists Any, One-time, Hourly, Daily, Weekly and Monthly. **20 of 20 frequency words rendered in the painted tree say One-time, and 0 cards carry a cadence attribute at all**: a card has `data-cat` and `data-subcat` and nothing else. **Read the instrument before the finding**: `wireFilter('freqMenu','freqCurrent','freq')` is called with no `onChange`, so it relabels and closes the menu and does not filter, exactly like the language selector whose comment says so. **It is therefore not a broken filter, it is a control promising a cut of the catalog that does not exist**, which is the same class as a declared state nobody draws, and `docs/launch-catalog.md` now puts four recurring series in the first release, so the cut becomes real and the product still draws none of it. Two questions, and they are one decision: what a recurring instance looks like on a card (its own window, its own price, and a series it belongs to), and whether a series gets a surface of its own or is only a filter value. `ia/docs/sitemap.md` Event.Frequency and `PRODUCT.md` Market types both already answer the model half. Owner: the pass that draws a recurring market |
| ~~227~~ | ~~**The recurring MULTI face has no document, so one of the four launch series cannot be drawn**~~ **CLOSED 2026-08-20, AND THE FAMILY IS THE TWO AXES CROSSED RATHER THAN A LIST OF SPECIAL CASES.** `event-detail-recurring-multi.html` stands in both trees, so Event Detail is now **binary and multi against one-time and recurring**, four documents per tree, which is the smallest set in which no card has to point at a page that contradicts it. The monthly stablecoin series is drawn: the card converted from `at the end of 2026` to `at the end of September 2026`, `data-freq="Monthly"`, `Repeats: Monthly`, closing Sep 30, on the four documents per tree that carry it, and the search catalog re-taken. **Monthly is now carried by two markets rather than one.** **And building it found the thing the binary specimen never had to face: the RECORD has no destination.** `Earlier in this series` is a list of resolved events, and a resolved MULTI event has no page: the outcome family gained `win-multi` and `loss-multi` on 2026-08-20 and `event-detail-resolved` is still binary, so linking those months would put a multi row on a binary specimen, which is what 222 closed for the win and loss screens. **The record is a sentence in the resolution panel instead**, and the missing document is `docs/backlog.md` 229. The chart is three lines rather than five and its ranges are `1d`, `1w` and `all`, because a four-week-old instance has no year to draw. `ia/docs/sitemap.md`, `docs/decisions.md` 2026-08-20 | Filed 2026-08-20 by the pass that drew the other three, naming what it did not build | `docs/launch-catalog.md` row 4, *Which chain holds the largest stablecoin supply at the end of {month}*, is **Monthly and multi-outcome**. Its card would route to `event-detail-multi.html`, whose header reads `Politics &middot; One-time event &middot; 5 outcomes`, so drawing it would manufacture the fixture contradiction 224 was closing. It therefore ships in both trees in its one-time phrasing, *at the end of 2026*, and the Monthly filter value is carried by the temperature record alone. **This is a face with no placement rather than a placement with no face**: the recurring binary detail exists and the multi detail exists, and what is missing is the document where the two meet. Cost is one document per tree plus the two registry rows, which is exactly what 224 paid for the binary one. Owner: whoever opens the stablecoin series |
| ~~229~~ | ~~**A resolved multi-outcome event has no page, and two things now point at the hole**~~ **CLOSED 2026-08-20, AND THE ROW WAS RIGHT ABOUT THE DOCUMENT AND WRONG ABOUT THE ROW IT FILED BESIDE IT.** `event-detail-resolved-multi.html` stands in both trees, so Event Detail is **five documents per tree**: the two axes crossed plus the state that ends both. The fixture is the one market three surfaces already had to agree about, `Which chain held the largest stablecoin supply at the end of H1 2026?`, and the detail now carries the same Reading `loss-multi.html` and `active-bets-history.html` have printed since the outcome family took its multi half: Ethereum on Jul 1 at 00:05 ET, $73.4B against Tron $61.2B and Solana $12.8B. **`Earlier in this series` on the recurring multi is a list again**, which is the whole point of building it: it was prose for exactly one commit because every row would have landed on a binary specimen. **AND THE SECOND HALF OF THIS ROW WAS WRONG.** It said `ui-kit/card.html` describes a remainder row that 0 of 21 multi cards render, and called that a stale claim about a face nobody built. The face was built and then **deleted on purpose**: `docs/backlog.md` 188 took the whole field back off the desk and **189 removed the `+N more outcomes` link**, both decided by the product owner on a screenshot, 68 links across 33 documents in all three trees. **The row was written from the 2026-08-10 comment in `components/options.css` without reading the 2026-08-17 comment 100 lines below it in the same file**, which is the defect this repository keeps paying for, produced fresh. So nothing is built: the stale PROSE is corrected instead, in `ui-kit/card.html` and in a whole section of `ui-kit/options.html` whose table claimed 4 rows at 640 and up and `+2 more outcomes` at 390. Measured from the rendered DOM: **30 multi cards per tree and every one shows exactly two rows**. `data-full` is kept as a datum with its zero named, because the count is true and its reader was removed by a decision rather than never written. **A third thing fell out of the same read**: the multi chart's script writes `.ed-chart-now` and was replacing `62% at close, resolved Ethereum` with `62% now` on every load of a resolved page. The guard is one line and it travels with the script, on all 16 documents that carry it. `ia/docs/sitemap.md`, `docs/decisions.md` 2026-08-20 | Filed 2026-08-20 by the pass that built the recurring multi, which could not link its own record | `event-detail-resolved.html` is the specimen a resolved event routes to and its header reads `Politics &middot; One-time event &middot; Trading closed`: **binary.** The outcome family gained `win-multi` and `loss-multi` the same week, closing 222 on exactly this ground, and the DETAIL half of it was never taken. **Two things ask for it now.** `event-detail-recurring-multi.html` cannot carry `Earlier in this series` as a list, because every row would be a multi row landing on a binary page, so its record is prose. And any resolved multi market a reader reaches later, from a link or from their own history, lands on a page about a different SHAPE of market. **A third thing was found in passing and belongs in the same document**: `ui-kit/card.html` says in a `tk-note` that the last option row names the remainder and links to the event, and that 18 cards in each tree carry it, and **0 of the 21 multi cards render one** - the class does not exist in `components/options.css`, `data-full` is declared on every multi card and read by nothing, and the page's own specimen shows two rows and no remainder, which is the hero-text-against-specimen shape 211 found on `market.html`. Owner: whoever builds the resolved specimen |
| ~~230~~ | ~~**The logged-out header row was tuned to land on its container EXACTLY, and the one thing in it that scales took the slack away: 36 of 120 painted documents scroll sideways at a 24px browser default at 320px**~~ **CLOSED 2026-08-21, AND THE ROW WAS RIGHT ABOUT THE HEADER AND SILENT ABOUT THE LAST RENDER.** The answer was the one the row already carried three paragraphs up in `header.css`: `Sign in` and `Sign up` are `data-open="signin"` BOTH, they open one dialog, and that dialog's heading is `Sign in or create account`. A logged-out phone reader therefore had FIVE controls onto one dialog, the two here and all three thumb-bar slots, whose fourth is labelled `Sign in`. So the quiet half takes `.desk-only`, the class its two siblings in this row already wear, `base.css` supplies the rule and **no declaration was added**: the same move, for the same reason, as the logged-out bell. **76 buttons in 74 documents.** The row now asks **239.81 against 292** at a 24px default where it asked 307.78, so the slack is 52.19px rather than a 15.78px deficit, and `Sign up` is at its natural 90.77 instead of being shrunk to pay for the pair. **AND THE FIX LEFT ONE RENDER OF 2,880 STILL SCROLLING, WHICH WAS A DIFFERENT DEFECT ON A DIFFERENT ELEMENT.** `responsible-betting.html` at 320 on a 24px root, 23px over, and the culprit is `--measure`: `.read-col` is capped at `max-width:var(--measure)`, `46ch`, and a `ch` scales with the face, which is the whole reason it is in `ch`. **It had no upper bound in pixels**, so on the document pages at `--text-16` the cap computes to **755.14px against the 258px a 320px phone can give**, and `.cat-layout` is a COLUMN flex container where `.cat-main`'s `min-width:0` governs nothing. **A cap wider than its box is not a cap, it is a floor.** `max-width:min(var(--measure),100%)`, one declaration, and the measure still decides everywhere it fits: 503.4px at 62 characters at a 16px root and 755.1px at **63** at a 24px root, the same count at both, which is what `ch` was bought for. **0 of 960 renders scroll sideways at all three roots, both engines.** `docs/decisions.md` 2026-08-21. | `ui-visual/`, 2026-08-21, found by the root-font sweep re-run over the whole tree with the coarse pointer asserted | **2,880 renders, 120 documents, three browser defaults (16 / 20 / 24) at eight widths each including the rungs, which MOVE with the root: 40rem is 640 at the default and 960 at 24px. 0 sideways at root 16 and root 20 at every width, and 36 at root 24 at 320 alone**, by 10px on 24 documents and 23px on 12. The 36 are the logged-out set: 24 named `logged-out`, plus the four legal pages, `about`, `how-it-works`, `cookie-consent`, `maintenance` and the four public-profile states, which are logged-out by nature. **The cause is the wordmark, not the auth pair.** `.logo` is `--logo-size:var(--text-16)`, a rem token since backlog 115, so `.left` grows **86.09 to 103.61 to 121.14** across the three roots while the two icon marks stay on the 44px touch floor, which is px and does not move. The pair pays the difference until it cannot: the utility measures 193.14 / 180.39 / 186.64 and its right edge holds 306 at both 16 and 20 and reaches **329.72** at 24. **`components/header.css` records `the cluster falls to 193.14 with the utility landing on 306 exactly` as the proof the fix worked, and it is true at one root**: a row with zero slack has nowhere to put a reader's font setting. Three answers and none is mechanical: hide the wordmark's word below DESK the way the heart and the bell already go, let `.auth-btns` fall to one control at 320, or accept a 10px scroll at one width for a reader on a 320px phone at a 24px default. **Not fixed on purpose**: the sweep was run to produce a number and this is the number. |
| ~~231~~ | ~~**The one specimen of the `Event deadline approaching` notification cannot be true, because the catalog holds no event closing within its own horizon**~~ **CLOSED 2026-08-21, AND THE NOTIFICATION WAS THE SYMPTOM OF SOMETHING LARGER.** The string could not be fixed and was not: **the catalog's recurring instances had never been moved to the period the product is in.** A WEEKLY market whose open period ended **58 days out** is not weekly and a MONTHLY one ending **91 days out** is not monthly, and `event-detail-recurring-multi.html` listed July and August 2026 as already resolved while the account cluster dates the reader's today to early July - **two todays in one product, three months apart**. The account cluster wins on weight: wallet, active bets, history, both profiles, win, loss and every notification timestamp against one `Earlier in this series` block on one document. So today is pinned at **Fri, Jul 3, 2026** and every instance moved to its current period: the weekly Bitcoin and Ethereum markets read `in the week to Jul 3` and close **today**, the monthly stablecoin and temperature markets read `July 2026` and close Jul 31, and the series' earlier periods slid back to June and May. **The reader's own resolved bet was `at the end of H1 2026`, which is not a period a monthly series HAS**: it is the June instance now, resolving Jul 1 exactly as its row already said, so it is the period immediately before the open one and the series page lists it. **The notification names the weekly market that closes today and routes to it.** Its sibling was fixed the day before: `Will ETH flip BTC by 2027?` existed in 0 other documents. **And the horizon is written down for the first time**, in `ia/docs/sitemap.md`: three of the four types are triggered by an event and this is the only one triggered by the CLOCK, so it fires once per instance **24 hours** before close, and 24 rather than longer because **the horizon must be shorter than the shortest period the catalog opens**. 31 files moved. `docs/decisions.md` 2026-08-21. | `ui-visual/notifications.html` and `-push.html` plus their grey twins, 2026-08-21, found by reading every page as a reader rather than by rendering it | The notification reads **`Closing soon: "Which party wins the most UK seats" closes in 6 hours`** on 4 documents, and **13 documents print `Closes: Jul 1, 2027`** for that event. The fixture's today is about **Jul 1, 2026**: the wallet's newest entry is Jun 28, the newest resolution is Jul 1, 2026. **The soonest close in the whole catalog is Aug 28, 2026**, 58 days out, so there is no event this notification could name and stay true. This is not a copy defect and it cannot be fixed by editing the string: either a catalog event gets a near close date, or the type's horizon is a product decision that has never been written down. `ia/docs/` registers the type; nothing registers when it fires. **The sibling defect WAS fixable and is fixed**: `New in Crypto: "Will ETH flip BTC by 2027?"` named an event that exists in 0 documents outside the two notification screens, and now names a real Crypto event from the catalog. | 
| ~~225~~ | ~~**The stand tells the three how-it-works steps in words the product stopped using, and one of them is a claim that was retired three days ago**~~ **FILED AND CLOSED 2026-08-20, AND IT WAS 31 STRINGS ON 19 PAGES RATHER THAN 3 ON ONE.** The row asked one question, whether a specimen quotes the screen or `voice/docs/microcopy.md`, and the answer is neither: **a specimen holds a QUOTATION or a DEMONSTRATION**, a quotation being the product's words in the product's markup and a demonstration being a string chosen to show a property, and only the first has to match. **The mechanism was one sentence in `voice/CLAUDE.md`**: "a UI string gets a row in `docs/microcopy.md` before it ships, then goes into both screen trees". The kit is the THIRD tree that carries those strings, by this repository's own two-places rule, and **a pass that names two trees is run over two trees** - 217 turned the on-chain resolution claim over 217 placements in `ui-visual/` and `wireframes/` on 2026-08-19 and **0 in the kit**. Harvested every text node under a product class over all 61 kit pages and diffed against the paint: **31 drifted, on 19 pages**. `hiw`, `dialog` and `organisms` told the third how-it-works step in **three different sentences**, one of them the retired claim. `seo-plate.html` drew a **five-question FAQ where the product ships three**, with a heading and a body no screen has. `toc.html` and `molecules.html` kept a draft saying `service` and `market` where the product settled on `document` and `event`, with its row numbers one digit instead of two. `loadmore` said `markets`, `bp-hint` said `YES pre-selected` on four pages after the product settled on `YES selected`, `search` said `See all 3 results` after the seam was fixed, and five fixture rows had lost the year and the `net` column that the identity pass added on 2026-08-20. **And one was a FACE and not a word**: `vitrine.html` drew the notification row with `<b>` and `<br>` because `.nav-row-stack` stacks only inside `.notif-drop`, so the front door of the kit showed the row as one line where the product shows two. **The residue is 10 and every one says which kind it is**: 4 are the instrument, `<mark>` splitting one sentence into three text nodes on `search.html`; 3 are the kit's own sidebar, which wears product classes and is not a specimen; 2 are demonstrations, a bare `Sub-categories` and a `Sort:` prefix the control itself writes; and 1 is a real question left open, a France market that the IA's `@graph` names and no screen prices, shown at 29% by a kit suggestion row. 244 renders over 61 pages on two engines at two widths after the repair: 0 sideways scroll, 0 duplicate ids, 0 page errors. | Found 2026-08-20 while giving the stepper its movement | **CLOSED**, `decisions.md` same date. The rule is in `voice/CLAUDE.md`, `ui-kit/CLAUDE.md` and the root `CLAUDE.md`. |
| ~~226~~ | ~~**The grey how-it-works sheet still switches its steps, so it still jumps 47.5px where the paint no longer does**~~ **FILED AND CLOSED 2026-08-20, AND THE ROW AS FILED WAS HALF WRONG IN THE WAY A ROW WRITTEN FROM THE OTHER TREE ALWAYS IS.** It carried the paint's number into a tree it had not measured. Measured in Chromium 151 and WebKit 26.5 at 390 and 1280: the grey sheet stood **475.56, 475.56 and 521.75**, so it jumped **ONCE** and by **46.19px**, not twice and not 47.5. **The picture frame was 180 on all three steps and always was**, because a grey still is one dashed box with a label in it and not three real components, so the half of the paint's defect that took `min-height:19rem` to close **cannot exist in this tree**. That is the plate-wrapper row of `_conventions.md` showing its consequence one more time. **So the geometry splits, and the split is measured rather than declared**: the stacked grid cell stays in the paint because its only job is to give the movement two states to move between; the frame stays in the paint because there is nothing here for it to fix; the **reserved row** crosses, because the third step carries a way out the other two do not and that is a fact about the step's content; and the **text floor** crosses, because a floor stops a sheet resizing under a thumb and grey's was **150 against 154.58 of text**, below its own content, so the button moved 4.58px at 1280. **The block was safe to sweep because it is genuinely shared**: extracted from all 97 documents that carry it, **97 of 97 are byte-identical at 17 rules**. Its region header named `port_chrome.py` and said "nothing here is promised to another file" while being promised to 96 others, so it carries its own numbers now, `SHARED (97 of 116, 18 rules)`. **And the 19 documents that do not carry it were checked rather than assumed**: they are exactly the overlay screens, seven deposit, four sign-in, five win, three loss, each with 0 headers, 0 feeds and 0 footers, which is convention 5 drawing the sheet on a plain backdrop with no feed for a dialog to hang off. After: **527.17 and 526.97 on all three steps, frame 180, text 160, nav 153.78, button 340, dots 443.78, every number identical on both engines at both widths.** 232 renders over 116 grey documents against HEAD: **0 heights moved**, 0 sideways scroll, 0 duplicate ids, 0 page errors. | Found 2026-08-20 by the same pass, and named rather than swept | **CLOSED**, `decisions.md` same date. The table is in `wireframes/_conventions.md` beside the seventh difference. |
| ~~219~~ | ~~**The shipped catalog is designed as a newsroom: 14 of its 25 questions can only be resolved by a person**~~ **FILED 2026-08-19 AND CLOSED 2026-08-20 BY A SECOND AXIS THE FIRST CLASSIFICATION HAD MISSED.** Resolvability was the gate and **HORIZON** is what actually decides a launch catalog: measured over the 25 shipped questions, **2 resolve within a month and 1 of those two is machine-resolvable**, with eleven longer than six months. So a launch built from the shipped catalog alone gives a reader **one resolution in the first month and then a gap**, and a resolution is this product's only trust event and its only notification. **That overturned a rule written the day before**: `PRODUCT.md` said one-time only because five weekly series are 260 resolutions a year, which is an argument against 260 HAND resolutions, and the automation decision taken hours earlier the same day makes a machine-read series cost one template and zero. The seven are named with their resolvers in `docs/launch-catalog.md`: four recurring machine series for the heartbeat, three one-time from the shipped catalog for depth, **six automated and one editorial**. `docs/decisions.md` 2026-08-20. | Classified by hand 2026-08-19 over `assets/search.js`, against the Resolver rule added to `ia/docs/sitemap.md` the same day | **6 machine-resolvable from a free public source** (BTC above a threshold, ETH above a threshold, a monthly global temperature record, 2026 among the three warmest years, an Atlantic Category 5, largest stablecoin supply by chain), **5 borderline** (renewable capacity, leading energy source, top-grossing film, a crewed lunar return, the next Ethereum upgrade), **14 human-only**. And the whole painted tree names **2 distinct resolution sources**, both ending "Resolved by the Yonder team". **This is not a defect in the drawings**: they are sample data and they were drawn before the rule existed. It is a statement about what the product would cost to OPERATE, and the fix is a different catalog rather than a script over this one. The launch seven should be picked resolver-first: roughly six automated against one editorial. Owner: whoever writes the launch catalog |
| ~~222~~ | ~~**A multi-outcome resolution has never been drawn, and one history row lands on the binary win screen**~~ **FILED AND CLOSED 2026-08-20.** `win-multi` and `loss-multi` stand in both trees, the resolved multi row routes to the win specimen, and a fifth settled bet was added so the LOSS half has a market of its own: **both halves drawn, because principle 4 is design the loss and mark the win, and drawing only the winning half of a symmetric control is exactly what the chosen NO cost this repository.** The panel carries the pair in **117 painted and 116 grey** documents with `binary` / `multi` labels borrowed from the Event Detail group, and the current-page marker reads clean over every screen in both trees. **The Reading gained its third sentence shape as a side effect**, the one 221 filed as an unmade placement: a multi machine read compares figures ACROSS options. `docs/decisions.md` 2026-08-20. | Found 2026-08-20 by the routing measurement that closed 221, not by looking for it | The convention is exact and three of four resolved rows obey it. The fourth cannot: `Which genre led the 2026 spring box office?` is multi-outcome, and the outcome family is **six documents, `win`, `win-payout-pending`, `win-loading`, `win-error`, `loss`, `loss-loading`, and 0 of them multi**. So the row goes to the binary win, where the reader is told they won $13.16 on a government shutdown while the row they tapped says $21.10 on a box-office genre. **This is a DECLARED state that was never placed**, which is the class the chosen NO cost this repository: `ia/docs/sitemap.md` has said `Outcome: YES / NO (or which option in multi-outcome)` since the Resolution entity was written, `ia/docs/flows.md` carries the option through to Confirm, and the feed already ships 37 multi cards and a `event-detail-multi` specimen. **The shape is genuinely different and that is why it is a row rather than a link fix**: a multi win says WHICH OPTION won, not which side, and this market's bet was NO on Action while Animation led, so the screen has to explain a win on an option the reader bet AGAINST. **Costed before filing**: one document per tree, a panel row in **115 painted and 114 grey** documents, the current-page marker rule, the Reading in its third wording, microcopy rows, and the tree counts in six files. Owner: the pass that draws the multi half of the outcome family |
| ~~223~~ | ~~**51 table rows disagree with their own header, so their last columns vanish wherever this repository is rendered**~~ **FILED AND CLOSED 2026-08-20, AND THE DIRECTION WAS BACKWARDS.** The row said the dominant shape was a struck row folding its Source and Note into the item cell, and that on GitHub "those columns are simply gone". **Put through GitHub's own markdown API rather than the specification, on a control table built for it: a row with FEWER cells than its header is padded with blanks and loses NOTHING; a row with MORE has its excess dropped in silence.** So the 47 folded rows were ragged and harmless, and the real defect was the **11 rows carrying one cell too many**, 10 here and 1 in `ui-kit/docs/inventory.md`, each losing its entire last column - the longest and most substantive cell in the row. **Six of the eleven were a row QUOTING ITS OWN ORIGINAL ROW**, so the quote's pipes became delimiters: the quote is prose about a row and not a row, and its pipes are escaped now. Two carried a trailing `Owner:` cell that belongs at the end of the Note. One had its struck title and its closure as two cells. One carried a **truncated duplicate of its own Source**. And the eleventh was in the **wrong table entirely**: the `search` row stood in `Component / Uses / Declaration` wearing the shape of `Component / Its own width query / Placements / What it does with width`, three feet further down, and it is there now. **The decision the row asked for is in this file's own preamble**: a struck row keeps its header's cells, an emptied cell stays empty rather than being folded away, and a quoted row escapes its pipes. **Proved on the renderer with a control**, which is this repository's rule arriving in the one layer that had never been rendered: the five repaired rows show their recovered text, and the same five taken from HEAD show 4 cells with that text ABSENT. Re-measured over every markdown file with code spans stripped: **342 tables, 2,708 body rows, 0 disagreeing in either direction.** | Found 2026-08-20 after I broke one myself in the previous commit and went looking | **CLOSED**, `decisions.md` same date. |
| ~~221~~ | ~~**The Reading has a third wording and the fixture set puts its only market on a fourth screen**~~ **FILED 2026-08-19 AND STRUCK 2026-08-20 BY MEASURING THE TREE INSTEAD OF ARGUING ABOUT THE SLOT.** Both questions answered with one number. **148 feed cards over 15 painted documents route to the specimen of their TYPE, 111 to `event-detail.html` and 37 to `event-detail-multi.html`, and 0 of the 148 to their own market**, so a resolved row is a ROUTE and not a fourth surface, and the figure-against-threshold wording is DATA inside the same drawing rather than a face nobody wears: this tree ships a document per type and per state, never per market. And a row must not carry a source link: four rows of citations is the activity feed `PRODUCT.md` refuses by name, and the fact already stands on the surface the row leads to. **The row that filed this was half wrong and the measurement is what said which half.** `docs/decisions.md` 2026-08-20. | Named 2026-08-19 by the pass that built the other two, before it could become a face nobody wears | The sharpest Reading is a number against a number, and the two placed wordings are a date against a deadline and a published record against a window. The fixture set's one settled NUMERIC market is *Did Bitcoin close above $100,000 in the first half of 2026?*, and it stands on `active-bets-history.html`, which `ia/docs/sitemap.md` does not list among the three surfaces. **This is a placement that has not been made, not a face that does not exist**: the markup is identical and only the sentence changes. Two questions have to be answered together, which is why it is filed rather than done: whether a resolved ROW in a table is a fourth surface at all, and whether a row can carry a source link without becoming the trader's activity feed `PRODUCT.md` rules out by name. Owner: the pass that decides whether the history tab shows evidence or only outcomes |
| ~~220~~ | ~~**The Reading has an IA node and stands in neither tree**~~ **FILED AND CLOSED 2026-08-19.** `.reading` is a face of `notice` in both trees on the three surfaces the IA named, `voice/docs/microcopy.md` carries the label and both wordings, and `voice/voice.html` lost the principle-5 example that had been teaching the hash. `docs/decisions.md` 2026-08-19. | Named 2026-08-19 by the IA pass that created it, and declared rather than skipped silently | `ia/docs/sitemap.md` gives the Resolution entity a **Reading** field and names three surfaces it stands on: Win Screen, Loss Screen and `event-detail-resolved`. **0 of the 3 carry it in either tree.** The grey tree owns the structure and the copy, so it goes first, and the sentence is a voice question before it is a layout one: it has to carry a value, a threshold, a source link and a read time inside principle 1 (explain the number) without becoming the spec note principle 3 already threw off the Confirm button. It is also the block that pays off `docs/backlog.md` 217, so the two are one pass rather than two. Owner: the wireframe pass after the IA, per the missing-page order |
| ~~204~~ | ~~**The desktop table of contents is invisible on every DOCUMENT-profile page**~~ **FILED AND CLOSED 2026-08-18, AND IT WAS TWO CORRECT RULES.** `toc.css` turns a closed `<details>` into a permanent rail above the RAIL rung by forcing `::details-content` to `display:block;content-visibility:visible`, and `base.css` fades `details::details-content` from `opacity:0` with `opacity:1` on `details[open]`. **A rail is never `[open]`**, so the fade had no end state and the contents sat at zero. It arrived with the Animation stage on 2026-08-15 and shipped for three days on `terms.html`. **Nothing saw it**: `.toc-list` measures 214x601 with fourteen 206x36 links in a readable bone, no error is thrown, no scroll is added, and **`checkVisibility()` returns TRUE because the opacity is on the PSEUDO-ELEMENT rather than on the element it is asked about**. Found by SCREENSHOTTING the element instead of measuring it, on the day four more pages were about to inherit it. One declaration, `opacity:1`, and the rung boundary re-read at 899 and 900 in both engines: 0 above and 1 at and above the rail, with the phone's disclosure and its fade untouched. `decisions.md` same date. | | |
| ~~203~~ | ~~**Two stylesheet headers name a class in `Classes:` that the file does not style**~~ **FILED AND CLOSED 2026-08-18, FOUND BY WRITING `ui-kit/docs/architecture.md` AGAINST THE LIVE TREE.** `Classes:` says the file styles those classes. `button.css` listed `.prov-google` and `filters.css` listed `.filters-close`, and neither has one selector in its own file: the Google mark keeps its brand colours in the markup so no rule may set them, and the sheet's close control takes its face from `.icon-btn`. Both moved to a `Script hooks:` line with the reason beside them, which is the allow-list `chart.css` and `tabs.css` already use, so the licence now has one spelling in four files. Idle control after: **0 files whose `Classes:` line names something they do not style**. The same pass found `ui-kit/overview.html` badging 4,884 declarations against a real 4,809. `decisions.md` same date. | | |
| ~~202~~ | ~~**The roadmap prints SOON on Animation across 28 documents, three days after the stage shipped**~~ **FILED AND CLOSED 2026-08-18, ASKED BY A READER LOOKING AT THE SIDEBAR.** `assets/_roadmap.js` carried `{ label: 'Animation', planned: true }` while `README.md` had said Done since 2026-08-15. **The rule that should have caught it is what let it through**: `README.md`, `CLAUDE.md` and `STRUCTURE.md` each say a status lives in the README table AND NOWHERE ELSE, and it lives in two REGISTRIES as well, both of which are what a reader sees. `ui-kit/_nav.js` was right, 67 rows with a `done` flag and all 67 true with 0 targets missing, so one registry had drifted and one had not and nothing could say which. **Turning the row exposed a second defect**: `course-chrome.css` has carried `.sidebar-page-link.planned.next` and `content:'Next'` since the chrome was built and nothing ever wrote the class, so the roadmap had no word for the stage you are standing in front of. The renderer marks the first planned row now, which makes both rules live and Handoff read Next. Verified over 6 course pages at two depths in both engines with the href resolving HTTP 200 from every depth, 0 page errors. `decisions.md` same date. | | |
| ~~201~~ | ~~**A specimen on the stand wears a face the system does not have, so it draws the browser's default button**~~ **FILED AND CLOSED 2026-08-18, ASKED BY A READER LOOKING AT THE PAGE.** `motion.html` carried `class="btn btn-quiet btn-md"` and **`components/` contains no `.btn-quiet`**, so the control took the user agent's own ground, `rgb(239,239,239)` in Chromium and `rgb(192,192,192)` in WebKit, and **answered nothing**: rest, hover and press identical on background, colour, border and shadow. **`.btn` supplied the transition the whole time**, five properties at .16s, so the element gave a healthy row to every motion census: a transition with nothing to transition is the twin of a control with no transition. The inverse of `consistency.md` over both trees: **506 distinct classes in `ui-visual/` with 7 unanswered and 793 in `ui-kit/` with 7**, of which five are the script hooks the owning stylesheets declare in their headers, two are the instrument reading a JS template string and a `<code>` block of prose, and **two are real, both on the stand**. The second is `odds-bar` for `oddsbar` on `organisms.html`, twice, computing a 0x0 track and a 0x0 fill where the component draws 960x4. Both fixed, verified in both engines, 0 page errors. `decisions.md` same date. | | |
| ~~200~~ | ~~**The filter menu opens UNDER the first card and jumps on top when its fade lands**~~ **FILED AND CLOSED 2026-08-18, REPORTED BY A READER ON A SCREENSHOT, AND THE DECLARATION THAT FAILED WAS CORRECT.** `base.css` fades `::details-content` from `opacity:0` and a box under full opacity is a stacking context, so `.filter-panel`'s `z-index:var(--z-menu)` was resolved inside that group rather than against the page for the whole arrival. **14 of 14 panels on 6 screens, Chromium and WebKit both, and 14 of 14 on the way out in Chromium too.** The header's two menus were clean at 0 of 5 each because a sticky header is already a lifted group, and that is where the fix came from: the lift went onto the `<details>` on `[open]`, with the drop delayed by `--dur-fast` and `transition-behavior:allow-discrete`, and the panel's own number deleted. After: 0 and 0, with the settled control passing on all 14 first, 0 page errors and 0 sideways scroll over 8 screens at 390 and 1280 in both engines. `decisions.md` same date. | | |
| ~~199~~ | ~~**The hero photograph is 1400 x 788 and a 3x phone asks for 1600 x 900, so it upscales by 14 per cent**~~ **CLOSED 2026-08-17, AND THE ROW ASKED THE WRONG QUESTION.** The originals came back from the account that made them, so the masters are 1664 x 936 and the hero no longer upscales at any phone width. **But re-measuring generically - load each slot's own source, read its natural size, compute the cover scale - found the requirement is set by SLOT HEIGHT and slot height GROWS AS THE CARD NARROWS**, because a narrower card wraps the question onto more lines and the thumbnail is `align-self:stretch`. At 320 the tallest card thumbnail is 107px css against 88 at 640, so the same element wanted 569px of source at the narrow end and 313 at the wide one, and the 480px landscape file was short on **12 of 13 slots at every ordinary phone width**. **The fix is not a bigger file.** A 56px-wide slot rendering a 16:9 source with `cover` discards three quarters of every pixel it downloads, so the small variant is now the PORTRAIT COLUMN the card actually shows: `-crop 800 0 960 1440` on the 2560 original, which is the central 37.5 per cent `background-position:center` was already displaying, resized to 240 x 360. **340 of 341 slots read across 11 widths and 5 screens now upscale by nothing**, the feed's pictures went **833 KB to 251**, and the detail 282 to 41. **The one residue is named rather than chased**: the hero at 320 css with a 3x pointer wants 1926 and gets 1664, a 1.157 upscale of a photograph drawn at 42 per cent opacity under a veil, on a device class that does not ship. The original row: **The hero photograph is 1400 x 788 and a 3x phone asks for 1600 x 900, so it upscales by 14 per cent** | `ui-visual/event-feed.html`, 2026-08-17, found by measuring every photograph slot against its own device pixels | `.hf-photo` paints **359 x 300 css at 390, which is 1077 x 900 device at dpr 3**, and `object-fit:cover` from a 16:9 source makes HEIGHT the governing axis: the file needs to be 1600 wide to deliver 900 tall and it is 1400 x 788. Every other slot is comfortably oversampled - the card thumbnail needs 469 and gets 480, the detail head needs 384, the related row 245 - so this is the one under-resolved picture in the product and it is the largest one on the screen a person arrives on. **It cannot be fixed by re-encoding what is here**: upscaling a 1400px webp adds no detail. The 2560 x 1440 originals were deleted with the throwaway scratchpad, and they are recoverable from the Magnific account that generated them, which is where the fix starts. Owner: whoever next touches the hero. Cost: one file, about 20 KB more, on two documents |
| ~~180~~ | ~~**The bet panel promises a payout for a side nobody has chosen, and never once states what happens if the reader is wrong**~~ **CLOSED 2026-08-17, AND THE PRODUCT HAD NO DRAWING OF THE MOMENT IT WAS ABOUT.** The panel printed `Potential payout $13.16` with all 8 `aria-pressed` reading `"false"`, and the four `event-detail-bet-*` screens all have a side picked and all replace the breakdown with a status or an error box, so **the breakdown appeared ONLY when nothing was chosen and the chosen state was never drawn at all**. Both outcomes now, or neither: `.bp-outcomes` prints `If YES wins` and `If NO wins` in IBM Plex Mono at the same weight and the same ink, one of them `$0.00`, and at rest the block is `.bp-pick` at body scale where the figures were, over the two lines that are true whatever the reader picks. `Price now` and `Potential payout` are struck from `voice/docs/microcopy.md`; `.line.total` is deleted from `components/betpanel.css` with its brass, which is the zero where the MARKUP went on purpose. **And the held Confirm was an `<a href>`.** **Corrected 2026-08-17**: it did NOT navigate, because those four screens carry a `?side=` script whose click handler calls `preventDefault()` on anything inside `[aria-disabled="true"]`, measured on the pre-change tree at `1df6115c`. The change stands for a different reason - a control that is not a destination should not be a link, and safety should not rest on a script four of eight surfaces carry - and it is a `<button>` now, still `aria-disabled` for the reason `button.css` gives, with `aria-describedby` pointing at the instruction, and the disabled primary drops the brass fill for the control ground at **5.49:1** instead of 45 per cent opacity over a lit surface. 16 resting surfaces, 8 picked, and 108 how-it-works still-lifes that were teaching the upside alone. Verified over 277 documents, 1,108 renders, two engines, two widths: 0 sideways, 0 duplicate ids, 0 shut dialogs visible, 0 page errors, and the held Confirm no longer navigates. **185 opened** for the state that still has no page. `docs/decisions.md` 2026-08-17. | | |
| ~~185~~ | ~~**A side is picked and the bet is ready, and there is no screen that draws it**~~ **CLOSED 2026-08-17.** `event-detail-bet-ready.html` stands in both trees: YES pressed at 38%, $5.00, `Fee $0.08`, `Total to pay $5.08`, `If YES wins, your side $13.16` against `If NO wins $0.00`, and a live Confirm. **It does not carry the `?side=` arrival script, and the render is what found that**: with no query parameter that script clears every side and re-holds Confirm, so the first build shipped a pressed YES in the markup and rendered with nothing pressed. A static state page and a script that computes the same state from a URL cannot both own it. The rollout was **109 grey navs and 110 painted rails**, and the grey markers were recomputed for a 109-file tree, every `of 108` re-derived, with every SHARED region byte-identical again afterwards. Two copies were found out of step on the way: **9 of the 13 painted panel scripts carried a pre-2026-08-16 version** with an unrounded fee and no `Total to pay`, all 13 are one text now and `sideBtn()` lost its first-side fallback; and **4 of the 9 grey documents carrying `<dialog class="bet-sheet">` markup carried no rules for it**. Verified over 279 documents, 1,116 renders, two engines, two widths: 0 sideways, 0 duplicate ids, 0 shut dialogs visible, 0 page errors, 28,341 links and 0 dead. **186 opened.** `docs/decisions.md` 2026-08-17. | | |
| ~~186~~ | ~~**Arriving with `?side=yes` lights the side and releases Confirm while the panel still says to pick one**~~ **CLOSED 2026-08-17, AND THE MEASUREMENT DECIDED IT RATHER THAN THE PRODUCT OWNER.** The row said three answers were legible and none obviously right. Counted: **940 links carry `?side=`** across 40 documents, and the parameter serves a binary screen and a multi-outcome one in both auth states, so making the state static would want **YES and NO twins of four screens**, eight new pages and eight more nav rollouts, against **one script that stands as a single identical text in all 8 documents that read the parameter**. The script builds the pair now: it appends `.bp-outcomes` into the block the instruction was standing in, removes the instruction and the `aria-describedby` that pointed at it, and fills both rows from the amount in the field and the pressed side's own percentage. **The NO side finally gets its own price**: `?side=no` on a $5 bet reads `If YES wins $0.00` against `If NO wins, your side $8.06`, which is 5 / 0.62 and not the YES figure this panel used to print for either side. Verified on Chromium and WebKit at 390 and 1280, in both trees, by URL and by click: pressing NO then typing 25 gives `$40.32` with `Fee $0.38` and `Total to pay $25.38`, and switching to YES gives `$65.79` against `$0.00`. Full sweep 279 documents, 1,188 renders including every arrival state: 0 sideways, 0 duplicate ids, 0 shut dialogs visible, 0 page errors. `docs/decisions.md` 2026-08-17. | | |
| ~~181~~ | ~~**One row on the reputation screen prints a payout that no price can produce**~~ **CLOSED 2026-08-17, AND IT WAS TWO ROWS OF THREE RATHER THAN ONE.** The row named the NO position; re-derived against `PRODUCT.md` (shares at a locked price, a winning share pays $1, payout = stake / price): **$25 at 61% pays $40.98 and the screen read $41.00**, and **$10 at 33% pays $30.30 and the screen read $22.50**, which implies 44.4% and is neither the outcome's price nor its complement. Only the $5 row at 38% was right. Both corrected, in both trees. **And `Current value` is struck rather than renamed.** It is a mark-to-market dollar figure on a product with no sell path, defined on none of the 110 screens, and it can only make a reader feel worse with nothing to do about it. The slot carries **`Price now`** instead, the current price of the side held, beside `Avg price`, which is what was paid: 41%, 78% and 27%, each DERIVED from the dollar figure it replaces, so nothing was invented. The row reads Stake, Avg price, Price now, Potential payout - what you paid, what the market says today, what it pays if your side wins. **Three more copies were carrying the old shape and the sweeps could not see any of them**: the bet SHEET has its own recompute script in 5 painted documents, still writing `Price now` and `Potential payout` into two lines deleted the day before and never filling the pair; **15 `class="line total"` specimens on 5 kit pages** were rendering with a class that no longer exists in the system; and `ui-kit/position.html` carried an open position worth $31.80 on a $10 stake at 38%, which is a price above 100%. All fixed. Verified over 279 documents, 1,116 renders, two engines, two widths: 0 sideways, 0 duplicate ids, 0 shut dialogs visible, 0 page errors. `docs/decisions.md` 2026-08-17. | | |
| ~~182~~ | ~~**One taxonomy wears four controls with two different mechanics, and the two are the same pill**~~ **CLOSED 2026-08-17, DECIDED BY THE PRODUCT OWNER: the band keeps the taxonomy and the `Show:` row is deleted.** Re-measured first, and half the row nearly read as stale: every `.chip-quiet` in the header carries an `href`, which looked like the two mechanics having merged. They had not. **`.chip-quiet` is worn by two different strips** - the condensed sticky band, which is anchors, and the `Show:` row, which is `<button data-filter>` with no href at all. **So the row was right and reading one class was not enough to see it.** The sub-filter narrowed the list already on screen, wrote no URL, and could not be undone with Back, while a near-identical pill about 1,580px above it loaded a new page. **6 blocks and 10 scripts removed across 11 documents in all three trees**, with the rules taken out of `components/catnav.css`, the 15 grey inline copies and `ui-kit/catnav.html`; `.feed-subfilter` and `.subfilter-lab` are off the component manifest. **Measured before and after in the same run**: the feed goes **7,517 to 7,463 at 390 and 3,969 to 3,915 at 1280**, 273 controls to 268, and the word count of the taxonomy on one document **24 to 19**. **And the first removal broke the tree in a way the count caught**: a non-greedy regex cut at the first `</div>` on its own line and left 2 unbalanced closes, which put `article.card` 125px past the viewport on 3 documents at 1280. Reverted and redone with a depth-aware remover, asserting the div balance per file before writing. Verified over 279 documents, 1,116 renders, two engines, two widths: 0 sideways, 0 duplicate ids, 0 shut dialogs visible, 0 page errors, 0 sub-filter elements left. `docs/decisions.md` 2026-08-17. | | |
| ~~183~~ | ~~**Six of twelve markets on the feed are illustrated by a photograph from another category**~~ **CLOSED 2026-08-17 as far as four photographs can close it, and TWO OF THE ROW'S OWN CLAIMS WERE WRONG.** Measured across the painted tree: 84 cards carry both a `data-cat` and a photograph and **30 of them wore another category's**, on 5 documents, six distinct market-to-photo pairs including the gold Bitcoin coin on the Bond casting market. Every card photograph is bound to its own `data-cat` now, asserted from the RENDER over 1,116 renders: **0 mismatches, 0 failed image requests**. **The related-event thumbs were already right**, 30 of 30 matched when their market's category was read off the cards that declare it, so the row's second instance did not exist. **And the hero veil claim was the opposite of true.** `.hf-photo` is an `<img>` at `opacity:.42` under a two-layer veil, and the first probe read `backgroundImage` on it, which is `none` on an image element and looks exactly like a missing photograph. Rendering the hero with the photo and again with it `display:none` gives **214,831 against 213,626 bytes at 390 and 317,515 against 306,318 at 1440**: the photograph contributes about nine times more at the wide width than at the narrow one, which is the reverse of "veiled to near-invisibility at 1440". Nothing was changed there. **187 opened** for the part four photographs cannot answer. `docs/decisions.md` 2026-08-17. | | |
| ~~188~~ | ~~**A card carries as many outcome rows as the market has, and a race with twenty runners would put twenty of them in a feed**~~ **CLOSED 2026-08-17, DECIDED BY THE PRODUCT OWNER ON A SCREENSHOT.** The card showed 2, 3 or 4 outcome rows depending on the market, each with its own YES and NO, and there was a rung under it: below 640 the third row onwards was hidden and the link said how many, at 640 and above every row stood and the link went. **That answers a phone and it does not answer the market.** A card is a place a person decides whether to LOOK, not a place they choose, and nothing in the shape stopped a twenty-outcome race from arriving with twenty rows and forty buttons. **Two outcomes at every width now, the two most-backed, and the rest is a number.** The markup carries two rows and the link carries what is not there: 62 cards trimmed across 31 documents in all three trees, 100 rows dropped, and six kit specimens that had no link at all were given one. The two media rules are deleted from `components/options.css` and from the 15 grey inline copies, because a card that never ships a third row does not need a rule to hide one. **Measured before and after in the same run**: at 390 nothing moves, since the phone already showed two; at 1280 the multi cards go **407 to 317** and the feed document **4,149 to 3,969**. Verified over 279 documents, 1,116 renders, two engines, two widths: **256 visible multi cards, every one showing exactly 2 rows, 0 hidden links**, 0 sideways, 0 duplicate ids, 0 page errors. `docs/decisions.md` 2026-08-17. | | |
| ~~189~~ | ~~**The `+N more outcomes` link is the only route to what the card withheld and it is styled as metadata**~~ **CLOSED 2026-08-17 by deleting the link, decided by the product owner on a screenshot.** The critique measured it at `--text-muted` 12px, the same ink as the `Volume` and `Closes` line beneath it, against a brass `See all hot events` on the same screen. The answer was not to make it louder: **the card title is the route to the market and the question is what a person reads**, so a grey caption counting what is missing was a fourth thing to read on a surface being deliberately made smaller. **68 links removed across 33 documents in all three trees**, with the rules out of `components/options.css`, the 15 grey inline copies, and `.opt-more` off both the component manifest and the touch-floor selector list in `base.css`. The feed goes **3,915 to 3,847 at 1280**. Verified over 279 documents, 1,116 renders: 0 `.opt-more` anywhere, every visible multi card still showing exactly 2 rows, 0 sideways, 0 duplicate ids, 0 page errors. | | |
| ~~190~~ | ~~**Four facts about the reader's money disagree across documents, and one of them is yesterday's own correction**~~ **CLOSED 2026-08-17, opened by the second `/impeccable critique` and it caught an error of mine.** **The Conservatives row was wrong in the direction I had just corrected it.** Row 181 re-derived it to `Avg price 33%, Potential payout $30.30`, which is 10 / 0.33 - but the feed prices the Conservatives OUTCOME at 33%, so backing NO costs **67%** and $10 pays **$14.93**. The row was internally consistent and contradicted the feed. It reads `Avg price 67%, Price now 54%, Potential payout $14.93` now, every figure still derived from what was there. **Three more of the same class**: the bet panel offered `$42.00 cash` while the wallet said `Cash (available) $92.00`; `In-play (open bets) $50.00` against three open stakes summing to $40.00; and a notification saying Bitcoin `jumped from 58% to 64%` where the feed and the hot list both price it at 61%. **The account triple now adds up**: cash $92 + in-play $40 = **$132.00**, which is the figure in the header of all 221 documents that carry one, where it had been $142.00 and equal to nothing. Verified over 279 documents, 1,116 renders: 0 of `$42.00`, `$50.00`, `$30.30` or the 64% notification anywhere in the product trees. | | |
| ~~187~~ | ~~**The catalog is 25 events and the product owns four photographs**~~ **FULLY CLOSED 2026-08-17.** 25 pictures, one per market, generated with Seedream 5 Pro through Magnific from prompts written here, 1400 x 788, 1.7 MB for the set. **Verified by reading the render rather than the markup**: every thumbnail's computed background checked against the question of the card that holds it, over both trees, **299 of 299 agreeing, 25 distinct markets, 0 disagreeing**, and 379 of 379 decoding with 0 non-200 in Chromium and WebKit. **Two defects were found by that check and neither was visible in the source.** The first pass bound 5 thumbnails to the wrong market because it picked the first entry in MAP ORDER rather than the question nearest the element. And `components/event-detail.css` was naming a photograph in a stylesheet - `background:url(../assets/event-politics.jpg)` on `.ed-thumb` - so all ten detail screens wore the politics picture whatever their market was, bound to nothing at all, where the card grid was at least bound to `data-cat`. A photograph is a DATUM and belongs on the element. **And the pictures are GENERATED, which `NOTICE.md` now states as a requirement rather than a footnote**: no people, no legible text, the subject and never the outcome, and no red or green light source, because those two colours mean YES and NO here - the first attempt came back as a data hall lit by red and green indicators and was discarded on that ground. The earlier half-closure: ~~**The catalog is 25 events and the product owns four photographs**~~ **HALF CLOSED 2026-08-17: the words are corrected and the pictures are not bought.** There is no edit that turns four images into twenty-five, so the half that could be done was the half that was a claim: `DESIGN.md` said "real event photography carries the story" as a key characteristic of the system, and **it does not yet** - a card is illustrated by its CATEGORY, one capitol standing on three politics markets in a single viewport at 1280. The line now says so and says what makes it true: about 25 images. **Binding the picture to `data-cat` was a correction and not the feature**, and calling it the feature is how a plan becomes a description nobody re-reads. **What stays open is a purchase, not a decision**: commission or licence one photograph per open market, and the sentence in `DESIGN.md` goes back to being a description. | | |
| ~~115~~ | ~~**All type is in px, so a reader who set a bigger browser default gets nothing from it**~~ **CLOSED 2026-08-17, AND THE ROW'S PREMISE WAS ALREADY FALSE.** The ten type tokens are `--text-10:0.625rem` through `--text-30:1.875rem` and have been in `rem` for a while, so the tree answers a reader's browser default today. Measured across all 110 painted documents at default **16, 20 and 24** through CDP `Page.setFontSizes`, the only mechanism that moves both the type and a `rem` media query: **0 horizontal scroll at every root and both widths**, and the RAIL rung (56.25rem = 1350px at root 24) correctly reads **false at 1280**, so a big-default reader keeps one column until 1350. Control passed first: root 16 read twice gives the identical 358 clipped and 2,921px mean height. **The cost lands on one family and it is not a units problem.** Clipped elements at 390 go 358 to 439 and `p.why` goes 10 to 83, 73 of the 81 new clips, every other clipped family flat. `min-height:35px` under two lines of rem text was wrong on principle and is `2.1875rem` now - **and changing it moved the number by nothing**, 439 and 83 again. The cause is `-webkit-line-clamp:2` doing exactly what it was told: at a bigger default fewer words fit in two lines. **Whether two lines is still the right reservation at 24px is a design question about a fixed card height**, and it is the one thing this sweep leaves for a person. The eight `clamp()` display tokens mixing `px` and `vw` are the other. | | |
| ~~197~~ | ~~**The rule between two outcomes is a plate's line, and it is not centred**~~ **CLOSED 2026-08-17.** With the plates gone the rows kept `--border-hairline`, a flat line that belongs INSIDE a plate. They carry the card's own divider now, the engraved groove it draws above its meta row: `--shadow-ink-45` with an inset lit lip in `--bevel-faint`, verified from the render as the identical colour and shadow on both. **One card, one divider.** **And the line was not centred because of a gap, not a padding.** `.card .options` had `gap:8px`, which is how two OBJECTS are spaced, and it pushed the rule to the bottom of the space between rows. With the boxes gone the rows are two lines of one list, so the gap is 0 and the space is their own padding: measured **23px above the line and 24px below**, the 1px being the line. The card is **288 at 1280**, 8px more than the thinnest version and the price of a centred rule. **And all four of the day's card decisions are now one paragraph in `DESIGN.md`**, the Card Anatomy Rule, rather than four separate mentions in four stylesheets. Verified over 279 documents, 1,116 renders: 0 sideways, 0 duplicate ids, 0 page errors. | | |
| ~~195~~ | ~~**Every outcome on a card wears its own plate, so two outcomes read as two objects**~~ **CLOSED 2026-08-17, the third cut to the same surface in a day and decided by the product owner on a screenshot.** The row carried `--bg-chip`, a hairline in `--bevel-notice` and a 10px radius, which made each outcome a small card inside a card. **The card already IS the plate**, what identifies a row is its name and its figure, and what is interactive on it wears its own face, so the box drew a boundary nothing needed. The horizontal padding went with it, because a plate's inset has no meaning once the plate is gone. The multi card measures **352 at 390 and 280 at 1280**, against 407 at 1280 before the day's first cut. Verified over 279 documents, 1,116 renders: 0 sideways, 0 duplicate ids, 0 page errors. **The grey tree keeps its box**, because a background and a border are paint and the layer boundary says so. | | |
| ~~196~~ | ~~**At a large browser default every story line on the feed is cut, and nothing says so**~~ **CLOSED 2026-08-17 AS A DECISION RATHER THAN A REPAIR.** Measured on the feed at 390: at a 24px default **all 12 `.why` lines are cut**, where 2 of 12 are cut at 16; three lines would cut 2 of 12 and cost **29px on every card for every reader**. **The product owner chose the density**, so the two-line reservation stays and the cost is written into `components/card.css` beside the clamp: a reader who set a large default gets the question whole and the story shortened, with the whole sentence one tap away on the event. It is recorded because **a cost nobody wrote down is a defect the next pass files again**, which is this repository's own reason for keeping the reason next to the rule. The other half of the same question dissolved on measurement: the eight display tokens are `clamp(rem, vw, rem)` and not px, the `vw` term sits below the rem floor at 390, and the `h1` measures **19px at root 16 and 29px at root 24** - they already answer the reader. | | |
| ~~194~~ | ~~**The screen that draws a ready bet is, on a phone, one colour different from the screen that does not**~~ **CLOSED 2026-08-17.** Measured at 390: `event-detail-bet-ready.html` and `event-detail.html` were the same height to the pixel and differed in **exactly one computed value**, the dock's YES background. Everything the new screen was written to show lives in a `<dialog class="bet-sheet">` that a static file ships shut, and the dock gave no sign that figures existed behind it. **A screen whose whole mobile representation is a colour change is not a screen.** **The face it needed was already in the system with 0 placements and a reason beside the zero**: `.dock-meta` had been retired on 2026-08-16 when the four bet screens got a sheet, its rules whole and its markup gone, filed as the second kind of zero. It comes back on the four screens where a side IS picked, carrying the stake, the side and what that side returns - `$5.00 on YES / Returns $13.16` - so the dock is a summary and the chooser at once. **What does NOT come back is the Confirm it used to hold**, which was a label lying over four states where confirming was not the next act. Verified over 279 documents, 1,116 renders: the dock reads `YES 38% NO 62%` on the unpicked screen and `$5.00 on YES, Returns $13.16, YES 38% NO 62%` on the ready one, at the same 68px height, 0 sideways, 0 duplicate ids, 0 page errors. | | |
| ~~192~~ | ~~**The category band shows three of five at 390 and nothing says the list continues**~~ **CLOSED 2026-08-17.** Measured on `event-feed.html` at 390: the course is **556 wide inside 328**, `mask-image` computed **`none`**, and the cut lands 3px past the end of `Crypto`, so **Culture and General are 0 per cent visible and the row reads as a complete list of three categories**. It had a second control beside it until the same morning, and deleting that one is what made this the only route to two of the five. **Not `flex-wrap:wrap`, which is the fix it looks like**: the block above it in `catnav.css` already refused wrapping with numbers on 2026-08-14, two courses costing 83.0px against 37.5, and a wrapping row grows with the catalogue while a scrolling one does not. **The affordance was missing, the scroll was not the defect.** A `mask-image` fade on the trailing 32px, alpha rather than a solid overlay so it is theme-blind, in the system and in the 61 grey inline copies. **And the first version switched it off at DESK, which measured wrong at the rung and one pixel either side**: the course is 577 inside 577 at 639 and **656 inside 578 at 640**, because the desk header arrives and the chips grow with it, so the one width where the fade was off is a width where the row still scrolls. It fits again at 1280. There is no rung that separates fitting from not fitting and CSS cannot ask, so the mask stays on at every width. Verified over 279 documents, 1,116 renders: 492 category bands carrying the fade, 0 sideways, 0 duplicate ids, 0 page errors. | | |
| ~~193~~ | ~~**The four figures in the bet panel are all muted, so the money that decides the bet is caption ink**~~ **NOT A DEFECT, MEASURED 2026-08-17 AND THE CRITIQUE READ THE LABEL.** The panel's LABELS are `--text-muted` `rgb(164,157,143)` and every FIGURE is `--text-primary` `rgb(237,231,218)`: `Fee $0.08` at weight 400, `Total to pay $5.08` at 700, and both outcome rows at 600. That is the label-quiet, figure-bright ranking this system uses on the card meta row and the dock, and it is the opposite of the finding. **A colour read off the first `<span>` of a two-span row is a reading of the label**, which is the same class of error as reading `backgroundImage` on an `<img>`. Nothing changed. | | |
| ~~191~~ | ~~**"The price you see is the price you get" stands on the same document as a table showing $5,000 buying at 49%**~~ **CLOSED 2026-08-17, AND `PRODUCT.md` HAD ALREADY DECIDED IT.** The panel footnote promised a locked price; `HOW THE ODDS ARE SET` printed `Price by bet size` with `$10 -> 38%, $100 -> 39%, $1,000 -> 42%, $5,000 -> 49%` and the sentence "your own bet moves it, so a bigger bet buys at a worse average price". **That is the AMM model `PRODUCT.md` replaced on 2026-08-10**, kept alive in a table above the sentence that denies it, and calibrated for a $5,000 bettor on a product whose default stake is $5 and whose minimum is $1. A newcomer reading both concludes the reassuring one is marketing. **The table is deleted and what replaces it is the worked example the product did not have anywhere**: "A YES share costs 38 cents and pays $1 if the event happens. $5.00 buys 13.16 shares, so YES returns $13.16 and NO returns nothing. The price is locked when you confirm." 19 blocks replaced across 19 documents in all three trees, with the depth-aware remover after a first attempt failed its own div-balance assertion. Verified over 279 documents, 1,116 renders, two engines, two widths: 0 occurrences of the old model anywhere, 0 sideways, 0 duplicate ids, 0 shut dialogs visible, 0 page errors. `docs/decisions.md` 2026-08-17. | | |
| ~~184~~ | ~~**The stake field is `<input type="number">`, so the decimal separator and the spinner belong to the browser and a scroll edits the amount**~~ **CLOSED 2026-08-17.** `type="number"` hands the browser three things that belong to the product: the SPINNER, the DECIMAL SEPARATOR and the WHEEL. All three go with the attribute: **285 fields across 223 documents** are `type="text"` with `inputmode="decimal"` and a pattern, which keeps the numeric keypad on a phone and takes the other three back. `min` became `data-min`, because it does nothing on a text field and the intent of backlog 95 is worth keeping where a script can still read it; `step` is gone. **And the field is in the mono the system calls the spectator's honesty cue**, which it was not: it was the only figure in the panel set in Space Grotesk. **Proved rather than asserted**: rendered with the same value and the same font, a text input and a number input do not paint the same pixels, and the text input paints the SAME pixels in `en-US` and `de-DE` while the number input does not - 842 bytes either way in Chromium against 895, 1,142 either way in WebKit against 1,568 and 1,629. The wheel over the focused field left the stake at `5.00` on both engines where it had moved it to `5.01`. **The two rules that hid the spinner are deleted**, and their own comment had predicted this: they were written against the class rather than the type "so the rule cannot outlive the attribute quietly". Verified over 279 documents, 1,116 renders, two engines, two widths: 0 sideways, 0 duplicate ids, 0 shut dialogs visible, 0 page errors, 0 amount fields under 40px, and 0 of any type but `text`. `docs/decisions.md` 2026-08-17. | | |
| ~~170~~ | ~~**One face has two names and one of the names is a claim, and `.widget-box` is the same declaration set with different padding**~~ **CLOSED 2026-08-16, AND THE DECLARATION SETS ANSWERED THE QUESTION: TWO COMPONENTS, ONE SKIN.** Counted: **five declarations are the same and nine are not.** `.widget-box` reserves a 160px rectangle and centres its content on both axes, because it holds the SPACE a payment widget will occupy, 112 placements, every one inside a dialog; the other holds a SENTENCE, with no height, generous padding and loose leading. **Folding them would override four of the eight on one side, which is not a size, it is a different thing wearing the same skin.** So they stay two, and the five shared declarations became one rule, `:is(.widget-box,.quiet-box)`, because five declarations written twice is five chances to drift. **The name went**: `.spinner-box` is `.quiet-box`, `.sb-working` is `.qb-working`, `sb-sweep` is `qb-sweep`, **156 occurrences over 110 files**, of which 93 are inside the grey tree's inline `<style>` blocks. **Proved by measuring the face rather than reading the diff**: fourteen computed properties plus the box and the `::before` mark on every visible placement, before and after, **37 elements over 23 documents, 0 computed differences**, and the sweep still moves and still stops under `prefers-reduced-motion` on both engines. **What is not answered by a rename**: the prototype disclaimer is bookkeeping, which `_conventions.md` files as the grey tree's job, and it stands in the paint on `terms.html`. | `components/`, 2026-08-16, split off 169 once the placements were counted | `.spinner-box` is a dashed muted centred box that stands where content is not, and it is worn by nine placements: five running, two pending, **two a prototype disclaimer that will never finish**. The face is right and reusable; the NAME names one of the three jobs, and a legal notice on `terms.html` reading `class="spinner-box"` in the source is the sharp end of it. **And `.widget-box` is very nearly the same face**: dashed hairline, `--radius-10`, `--bg-control`, `--text-muted`, centred, differing in padding, a `min-height:160px` and one type step. So the question is not just a rename, it is whether these are one component with two paddings or two components. **Not done here because the cost is a sweep rather than a stylesheet change**: the rule lives in the inline `<style>` of about ninety grey files, which is the same shape as the 312 edits across 104 files that one harness width cost. Candidates: `.quiet-box` with `.qb-working`, and `.widget-box` folded into it as a size. |
| ~~169~~ | ~~**`.spinner-box` is worn by three different jobs and one of them is a legal disclaimer**~~ **HALF CLOSED 2026-08-16: THE STILLNESS IS FIXED, THE NAME IS ROW 170.** `.spinner-box.sb-working` carries a 2px rule at `--size-2` sweeping the top edge on `--pulse-period`, the SAME period as `sk-pulse`, because a spinner and a skeleton are one STATUS job in two geometries and Stage 11 rules that the same role takes the same duration. `--ease-standard` over `alternate` slows the sweep at each end; a rotation would have needed a fourth curve, since `ease` on a rotation reads as a stall and a stall is the one thing a status must never say. **Compound selector, and the placement count is the whole reason**: five of the nine are running, two are an out-of-band pending state and two are a prototype disclaimer, so a cycle on the bare class would have drawn a progress bar on a legal notice. Verified on both engines at 390: translate moves 0 to 77-82 per cent inside 350ms, and under `prefers-reduced-motion` the animation reads `none` and the bar becomes the full 346px width, still. **The cycle is REPLACED, never shortened**, the ruling `sk-pulse` already took. **And `ui-kit/docs/motion.md` had counted two status moments where there were three**: the sweep of step 1 looked for `transition`, `animation`, `@keyframes` and declared states, and this box had none of the four, so **a moment that is already still is invisible to an instrument that measures stillness against motion**. | `ui-visual/`, 2026-08-16, opened by the critique that took the product from 22/40 to 30/40 | The name says a process is running. Measured over every placement in the three trees: **four are a process running** (`sign-in-loading` "Redirecting to Google", `win-loading` "Generating your Share Card", `loss-loading` "Loading the resolution", `event-detail-bet-processing` "Registering your bet on-chain"), **two are an out-of-band pending state** (`deposit-pending` "Payment under review", `win-payout-pending` "Your payout is on the way"), and **two are a disclaimer that will never finish** (`terms.html` and `ui-kit/browse-shell.html`, "Nothing on this page is in force"). And there is no rotation keyframe in `components/` at all: the five that exist are `betSheetUp`, `edge-fade`, `sheet-appear`, `sheet-rise` and `sk-pulse`, so the STATUS job that Stage 11 named is served by the skeleton pulse and by nothing else. **The fix is not to make `.spinner-box` spin**, because that puts a spinner on a legal notice; it is to separate the running job from the quiet box, on a compound selector the way `.reconcile-box.rec-won` does it, and to give only the running one the cycle at `--pulse-period` with a static replacement under `prefers-reduced-motion`. `deposit-loading` needs it most and has the least: its box is a `.widget-box`, not a `.spinner-box`, and it is the longest wait in the product because a third-party KYC widget is on the other end. |
| ~~168~~ | ~~**Four loading screens announce nothing, and the pass that gave nineteen of them a status line could not see these four**~~ **CLOSED 2026-08-16 with the same edit that closed 169, and the box is the live region itself.** `role="status"` on the `.spinner-box.sb-working` in all four, both trees, plus the kit specimens. `deposit-loading` needed two things rather than one: it was wearing `.widget-box`, the payment-widget placeholder, so the longest wait in the product had neither the loading face nor the announcement. It is `.spinner-box.sb-working` now. Verified on both engines: `role="status"` present and the mark rendering on all five running placements, and absent on all four quiet ones. | `ui-visual/`, 2026-08-16, opened by the critique that took the product from 22/40 to 30/40 | `deposit-loading`, `sign-in-loading`, `win-loading` and `loss-loading` carry **0 `aria-busy` and 0 `role="status"`**. The status pass of 2026-08-16 walked `[aria-busy="true"]` regions and gave each one a `.sk-status` line, which is 19 of the 23 loading screens; these four have no `aria-busy` region because their content is a text box inside a modal rather than a skeleton grid, **so a sweep keyed on the marker could not report the screens that lack the marker**. The same shape as the convention with no reader: 19 of 23 looks like compliance from any single file. They are also the four longest waits in the product, all four handing off to something outside the page. |
| ~~173~~ | ~~**546 sub-category chips on 56 documents and not one of them changes anything, and on 8 the chip lights up as chosen while it does it**~~ **CLOSED 2026-08-16, AND TWO THIRDS OF IT HAD BEEN DECIDED BEFORE IT WAS OPENED.** `ia/docs/sitemap.md` says "Sub-category is a NEW Event attribute" and "the category page's left rail filters by sub-category, each with a count", with only the taxonomy marked provisional. **The screen file said the opposite in a comment, in 108 copies**: "visual selection; the feed filter is product scope". The two disagreed for as long as nobody clicked a chip, and the executable one won. The measurement added the rest: 35 chips of which only 15 had an event, **20 would have shown an empty grid**, counts summing to 7,585 against 25 events, and one event of 24 fitting no chip. **The rail is built from the page now**: the taxonomy keeps the vocabulary and the order, the chips and the counts come from the cards. `Europe` was added as a term because an event existed with no home, which is what "pending a real taxonomy" means in practice. 24 events mapped, **356 cards given `data-subcat` across 123 files in both trees**, `renderRail` and the handler rewritten in 108 painted files, 48 statically-typed rails rewritten in both trees. **Verified by clicking every chip on every document, both engines: 280 chips, 280 behaving exactly as declared, 0 defects** - 8 rails where each count equals what its chip reveals and reveals only its own, 48 on a no-results state carrying no counts and changing nothing. **546 chips that did nothing became 280 that do what they say.** | `ui-visual/`, 2026-08-16, found while closing 172 by clicking every chip instead of reading the script | Walked every chip on every document, both engines, clicking each and comparing the grid's hidden-signature before and after. **546 chips, 56 documents, 0 that change the grid.** On **8 of the 56** the `aria-current` marker moves, so the control confirms the click and delivers nothing, which is worse than a control that visibly does nothing. The cause is two halves built against different vocabularies: the rail's chips are SUB-categories (`Trump`, `Courts`, `Epstein`, `LA Mayor`), the only category datum on a card is the TOP-level one, and the category pages' cards carry no `data-cat` at all. **And the chips promise a catalog that does not exist**: the counts beside the labels sum to **7,585** across the four rails (`Midterm Elections 537`, `Trump 297`, `Global Elections 122`) against a product that draws 25 distinct events and shows 6 on a category page, which is the same figure class as the fee and the payout, a number with no derivation behind it. **Three product decisions, none of them taken here**: give every card a sub-category datum so the 546 chips work, or delete a navigation level `ia/docs/sitemap.md` declares, and either way decide where the counts come from. |
| ~~172~~ | ~~**Half the feed's category tags disagree with the category page the same event stands on, and the sub-filter filters by the tag**~~ **CLOSED 2026-08-16, AND THE HALF OF THE ROW ABOUT THE FILTER WAS NEVER TRUE.** The tags are corrected: set from the category pages, **60 attribute edits over 10 documents in both trees**, and verified on both engines across every feed document, **60 of 60 cards agree where 30 did not**. **They had been DEALT rather than decided**: exactly three per category over twelve cards, and after the correction the same twelve read politics 5, crypto 3, culture 3, general 1, which is what a real catalog looks like and is the proof the even four-by-three was arithmetic. In all six disagreements the category page was right on the merits, which is not a coin flip. **But the harm the row attributed to them was not there**: the consumer script is on those ten documents and the `.subcat` rail it binds to is `hidden` with zero chips on every one, so nothing could be clicked and the feed was never filtered by a wrong tag. The row inferred behaviour from the source instead of reading the page. **That reading is what found 173.** | `ui-visual/`, 2026-08-16, found while building the ItemList for row 171 | The `ItemList` needs each event's category, so the two sources were compared: `data-cat` on the feed card against the category page that actually lists the event. **6 of 12 agree and 6 do not.** The Ethereum upgrade is tagged `general` and stands on Crypto; the Bond film is tagged `crypto` and stands on Culture; the EU member state is tagged `general` and stands on Politics; the US Senate is tagged `culture` and stands on Politics; the warmest-years event is tagged `culture` and stands on General; the game console is tagged `general` and stands on Culture. **The feed's sub-filter reads `data-cat`**, so filtering the feed by Crypto shows a Bond film and hides an Ethereum upgrade, and the same event answers two different questions about itself depending on which screen asks. The schema took its categories from the category pages, which agree with each other, so this does not block it. **The open half is which source is the truth**: the tag is one attribute per card in two trees, the category pages are four documents times eight states, and `ia/docs/sitemap.md` gives the Event entity a category field without saying which rendering owns it. |
| ~~171~~ | ~~**The product carries 0 `application/ld+json` on 108 screens, and the IA specifies structured data on every page type**~~ **CLOSED 2026-08-16, AND THE ROW ASKED WHICH TREE WHILE THE ANSWER WAS NEITHER.** Both trees carry **0 of the five metadata classes** across 217 documents, and the only head element either has is `<title>`, which names the ARTEFACT: `UI Visual - Event Feed` and `Wireframe - Event Detail (logged in - state: success / binary)`. **Neither models a document head; each labels a drawing.** So the layer splits and the line is DRIFT. The meta tags are a fact written once and never derived from the page, so copying them into 109 files would make 109 copies of one fact, which the root file forbids by name; they stay in `ia/docs/pages/seo.md`, and `{ROOT}` is still `[?]`, so a canonical cannot be written without inventing a domain nobody has chosen. **Structured data is the opposite: it RESTATES the visible page, so it is the only half that can drift, and drift is measurable only where both halves stand together.** Built: **58 of 108 painted documents, one `@graph` each, 12 distinct page `@id`s**, relative URLs in the product's own URL space rather than the prototype's file names. **A state may show less, never something different**: the empty, error and loading feeds carry no `ItemList`, and the two loading event details carry no schema at all, because a skeleton does not show the question the URL's schema is about. Verified on both engines: **58 of 58 parse, 0 structural defects, 0 nodes disagreeing with the visible page, 0 subset violations**, and the three types `seo.md` rejects by name (`Event`, `QAPage`, `Product` / `Offer`) read 0. **The first check written for this was the wrong one**: it asserted one `@id`, one node shape, which is uniformity rather than truth, and it flagged five families that were behaving correctly. Replaced by the subset rule. | `ia/docs/pages/seo.md`, 2026-08-16, opened by the pass that built the visible breadcrumb | `ia/docs/pages/seo.md` names `WebPage`, `BreadcrumbList`, `FAQPage`, `Comment` and more, page type by page type, and `ia/docs/blocks.md` files the breadcrumb schema as MVP. Measured across all three trees: **0 `<script type="application/ld+json">`, 0 `BreadcrumbList`, 0 schema of any kind.** The visible breadcrumb shipped 2026-08-16 and deliberately emits none, because this is one absence rather than a gap in one component: emitting a `BreadcrumbList` from `crumb` alone would leave five other schema types unwritten and make the tree look half-instrumented. **The open question is not whether but WHERE**: JSON-LD is data rather than styling, so it is not barred from a screen file the way `@media` is, but it is also neither structure nor copy, which is what the grey tree owns. A decision about which tree carries it comes before any of it is written. |
| ~~167~~ | ~~**There is no back control and no breadcrumb anywhere in the product, at either width**~~ **CLOSED 2026-08-16, AND THE ROW WAS HALF WRONG IN A WAY THAT HID SOMETHING WORSE.** There IS a way back: measured on the rendered page, the logo stands at **y=8** and the second-level category rail at **y=77 on a phone and y=71 on the desk**, five links, the reader own category among them. The row was written from a selector search, and **the absence of a NAME is not the absence of a THING**, which is the same shape as the search row and the error-tone row. What the measurement found instead: `event-detail.html` carried **three `aria-current="page"` marks at once**, the sticky strip saying Trending, the rail saying Politics and the bottom nav saying Events, none of them the page the reader is on. **The two category controls contradicted each other on 35 of 60 documents** (13 event details and 22 logged-out category pages, the strip saying Trending in all 35), and the strip is the one pinned to the top while you read. The logged-out half is **S5 biting a second time**: the strip lives in the header, which is the one thing the convention licenses to differ. Fixed with the VALUE rather than a face: `location` where the link points at an ancestor, `page` where it is the page, nothing on search and favorites, and `chip.css` / `navitem.css` / 108 grey files answer both values with the same declarations because the distinction is one a screen reader needs and an eye does not. Verified both engines, both trees: **216 documents with a category nav, 0 contradictions.** And the thing the row asked for was built where it is genuinely missing: `crumb`, the fourteenth atom, on 3 of the 6 page types the IA names. | `ui-visual/`, 2026-08-16, opened by the critique that took the product from 22/40 to 30/40 | Nothing inside `.device` matches `.crumb`, a breadcrumb landmark, an `aria-label` containing "back", or a back link, on any screen at 390 or 1280, both engines. From an event detail the only route back is the bottom nav's "Events", which returns to the top of the default category and loses both the category and the scroll position. **Earlier readings of this said `back=true` on five screens and that was the review panel's own `.sidebar-back`**, one more page-level number taken without cropping the chrome out. The primary journey is feed to detail to bet to feed and it is the only journey in the product that cannot be reversed. |
| ~~166~~ | ~~**"This market runs on an AMM, not an order book" stands on 9 screens and is defined on none**~~ **CLOSED 2026-08-16, AND THE RULE THAT PROTECTED IT HAD A CONDITION NOBODY HAD MEASURED.** `voice.md` bans trader terms by PLACE and allows them "inside a block whose whole job is to explain the mechanism, **where it is glossed in plain words**". The first half was quoted every time this line came up; **the second half was never checked, and the term was named on 9 screens per tree and glossed on none.** Worse, the example the rule gives for the legitimate place does not exist: `voice.md` illustrates the exemption with "*AMM* in the How It Works explanation", and `how-it-works.html` contains the string **zero** times in either tree, so the term lived only where the exemption did not reach. And the reason the microcopy row gave, "an order book is what a trader would assume", **names the one reader principle 3 says not to write for** and the one `PRODUCT.md` excludes in a line of its own. To the spectator two unknown words, to the trader redundant: **a sentence with no reader.** The explanation was already written four sections away, on How It Works, in plain words and without the term. The caption now says what its own table proves: "Your own bet moves it, so a bigger bet buys at a worse average price", against "This market runs on an AMM, not an order book" stands on 9 screens and is defined on none0 at 38 per cent, "This market runs on an AMM, not an order book" stands on 9 screens and is defined on none00 at 39, "This market runs on an AMM, not an order book" stands on 9 screens and is defined on none,000 at 42, $5,000 at 49. 20 occurrences over 19 files, three trees. `AMM` and `order book` read **0 in `ui-visual/` and 0 in `wireframes/`**. | `ui-visual/`, 2026-08-16, opened by the critique that took the product from 22/40 to 30/40 | It is the only sentence in the product that assumes prior knowledge, and it stands on the screen where money is committed. `PRODUCT.md` names the audience as a News Junkie rather than a trader, and the lexicon pass took the product to 2 "market" against 22 "event" on the feed, so this sentence is now also the largest remaining pocket of the vocabulary the pass moved away from. Two legitimate fixes and they are different products: say what it means in the product's own words, or move it behind the `<details>` that already holds "How the odds are set". |
| ~~179~~ | ~~**The grey tree duplicates its chrome into 108 inline stylesheets and nothing checks that they agree**~~ **CLOSED 2026-08-17, AND THE COPIES ALREADY AGREED: WHAT HAD NO OWNER WAS THE PLACE.** Re-measured from source over all 108 stylesheets: **412 selectors stand in 50 documents or more and 390 carry identical source text**, and the markup-to-CSS contract holds on every shared chrome family with **0 documents carrying the markup and not the rules** (the 17 that carry none of it are the invoked-overlay screens, which is the layer boundary and the same 17 it declares). **The mechanism was that four region names in these files name the SCRIPT that wrote the rules rather than the subject they style**, so a rule's home is wherever a script's cursor happened to be: the notification block sits under `How it works` in **61** documents and under `chrome ported by port_chrome.py` in **3**, and those three are `404`, `500` and `toasts`, the exact three that carried 7 of its 12 rules. A file that has lost a shared block and a file that keeps it elsewhere are indistinguishable to anything that reads names, and **the fix made the day before landed in a fourth region and reproduced the mechanism it was closing**. The same parse found **17 documents carrying a second `<style>` block** and **13 carrying the 12-rule bell block twice**, 156 duplicate rules. **Closed with a label rather than a rewrite, because the rules were already right**: every region marker now reads `SHARED (N of 108, R rules)`, `SHARED BASE (N of 108, R rules)` or `THIS PAGE`, so a copy holding fewer than R contradicts its own header, which is a check that is READ. **2,413 markers, and every SHARED region is byte-identical across the documents that carry it**; the how-it-works region went from 7 texts to 1; the 17 second stylesheets are merged and 108 of 108 hold exactly one. Verified as a layout: **432 renders over two engines at two widths, 0 differing, 0 heights moved, 0 sideways, 0 duplicate ids, 0 page errors**, with the instrument proved by reading the same page twice. Left standing on purpose: the 44x44 bell `<summary>` on those three, and the 24 painted class names the ports left here with no grey rule behind them. `docs/decisions.md` 2026-08-17, `wireframes/_conventions.md`, `wireframes/CLAUDE.md`. | | |
| ~~178~~ | ~~**The closed search sheet renders in flow on 108 of 109 painted screens, 844px tall, after the footer**~~ **CLOSED 2026-08-17, REPORTED BY THE USER ON A SCREENSHOT, WHICH IS THE THIRD TIME IN TWO DAYS.** `dialog.app-dialog.search-sheet` in `components/dialog.css` declared `display:flex` unscoped, and the UA rule that keeps a dialog shut is `dialog:not([open]){display:none}` at author weight, so ours beat it. Measured at 1280 before the fix: **108 of 109 documents in `ui-visual/`**, the sheet 844px tall with `top` just past the footer, `404.html` at **1,855 where it should be 1,011** and `active-bets-empty-new.html` at 1,928. **`wireframes/` read 0**, because it links no stylesheet of ours, so the two trees disagreed and neither could say so. Fixed by scoping the whole declaration to `[open]`: a closed dialog needs no geometry. **Verified on both engines at 390 and 1280 over 217 documents: 0 closed dialogs rendering, 0 sideways scroll, 0 page errors**, with the probe proved able to come back red by forcing `display:flex!important` on the same element (control read 1). **Behaviour intact on both engines**: the phone sheet still opens from the mark with the field focused at 844px, and the desktop panel still fills by cloning `.search-body` out of the now `display:none` sheet, 1 row and 1 highlight mark for "bitcoin", "See all 1 result". **The row's value is the class, not the rule**: every instrument in `CLAUDE.md` reads ACROSS a page and none reads `scrollHeight`, so a screen-sized element below the fold is invisible to all of them. That is written down now. | `components/dialog.css`, 2026-08-17, reported by the user | Shipped with the search rebuild on 2026-08-17 and stood for one day through the settled-market pass's own 868-render sweep. |
| ~~176~~ | ~~**The resolved event detail still carries the betting apparatus of an open market**~~ **CLOSED 2026-08-17, AND THE ROW NAMED THREE SURFACES WHILE THE ORDERING WAS THE FINDING.** The page announced its own resolution only in `.resolved-panel`, at **y=167 on the desk and y=2,227 of a 4,566px document on a phone**, so a reader holding a phone met the odds explainer, a Price by bet size table running to $10,204.08, Available to bet $31,500, a 24h move on a market closed in June and a Background panel in the present tense, all before anything said it was over. `ia/docs/pages/seo.md` had already specified "outcome plus at-close odds" for a resolved event and two of the three were true. **`.ed-result` is new in `components/event-detail.css` and stands at y=344 on the phone**; the panel keeps its own job, which is YOUR bet rather than the market. The side reads `--outcome-yes-text` and not `--result-won-text`, same colour and different reason, at **10.00 Vault / 7.43 Daylight**. **`.ed-result-no` has 0 placements and is on the stand anyway**, the chosen-NO rule. The apparatus is off the page rather than reworded: stats read YES at close / NO at close / Final volume / Settled, the depth table is one paragraph in spectator language, the summary says `were`. **Verified over 277 documents on both engines at 390 and 1280, 1,108 renders: 0 sideways, 0 duplicate ids, 0 ghost dialogs, 0 errors, and 0 pages saying "Trading closed" while offering a price**, that probe proved able to come back red. `docs/decisions.md` 2026-08-17 | `ui-visual/event-detail-resolved.html`, 2026-08-17, found while splitting the settled set away from the open one | The page names a settled market now and its facts row says **Result YES** and **Resolved Jun 27, 2026**, so the identity is right. What is left is the machinery around it: the depth table still prints **Price by bet size** with four rows of "You receive if YES" up to $10,204.08, **Available to bet $31,500** stands in the odds explainer, and the Background panel opens with "YES is priced at 38%" in the present tense. **A closed market has no price and nothing available to bet**, so three surfaces on one page invite an action the same page has just withdrawn. The resolved panel itself is correct and deliberate: it says the event resolved while you were reading and sends you to your result rather than printing the outcome twice. **The row is small and it is not a rendering defect**, which is why 868 renders over two engines reported nothing: every value is present, computed and wrong only in tense. |
| ~~177~~ | ~~**The notification dropdown collapses to 38px of container on three grey system pages**~~ **CLOSED 2026-08-17, AND THE ROW'S OWN EVIDENCE WAS A READING OF SOMETHING THAT PAINTS NOTHING.** The 48-over-22 the row filed came from `getBoundingClientRect()` on a span inside a SHUT `<details>`: `checkVisibility()` reads **false** there and `elementFromPoint` at that box's own centre lands on the page behind it, so nothing was collapsing and nothing was clipped. **The defect was in the state no sweep opens.** Forced open, `wireframes/404.html`, `500.html` and `toasts.html` put the dropdown at `position:static` with no width, so it sat IN the header row instead of over the page and took the header from **50 to 236 at 390 and from 60 to 222 at 1280**, against 260px absolute and a 50px header everywhere else. **Cause measured over the set**: those three carry a REDUCED copy of the notification block, 7 rules where the other 61 carry 12, missing `.notif-menu .dropdown` entirely along with the row borders, the link colour and the footer link's ground. The canonical block was lifted verbatim from `wireframes/event-feed.html` into all three. **Verified with the bell forced open on every grey page that has one, both engines, both widths: 59 pages, dropdown width `[260]` and position `[absolute]` as single values, header heights `[48,50]` at 390 and `[58,60]` at 1280, 0 sideways scroll**, and the probe proved able to come back red by stripping the rule again, which took the header to 241. `docs/decisions.md` 2026-08-17 | `wireframes/404.html`, `500.html`, `toasts.html`, 2026-08-17, found by the control that proved it was not the pass's own doing | The bell row's text span measures **scrollWidth 48 over clientWidth 22** at 390 and **48 over 22** at 1280, inside a `.nav-row-stack` whose own width is 38 where the feed gives it 258. **It reads identically at HEAD**, before and after the settled-market split, on both engines, so nothing in that pass caused it: the same row on `wireframes/event-feed.html` is 238 over 238 and fits. `overflow` computes `visible`, so the text spills rather than clipping and no reading is lost, which is why every earlier sweep passed it. **The three files are the system pages, which are the ones a reader reaches when something has already gone wrong**, and they are the grey tree only: the painted twins are correct. Likely one missing width declaration in three inline `<style>` blocks that the shared chrome carries in the other 105. |
| ~~175~~ | ~~**A multi-outcome card hides two of four behind a link on a screen two columns wide, and the link counts something no sweep can check**~~ **CLOSED 2026-08-17, REPORTED BY THE USER ON A DESKTOP SCREENSHOT.** The truncation was written for a phone and applied at every width. Measured with animation frozen: the whole field takes the affected grid row **317 to 407** and the feed **1,756 to 1,936** at 1280, +10.3 per cent, with the column beside it already stretched to match; at 390 the same card would go **386 to 546**, 65 per cent of an 844px viewport for one card in a single-column scroll, which is why the phone keeps two. Rung is **DESK 40rem** and deliberately not the 744 where `auto-fit` gives the grid a second column, because that width belongs to no rung. **And the second half is the bigger one**: `.opt-more` counted what the DATA omitted, derived from the percentages, and five of six markets summed to 61, 77, 83, 92 and 77 per cent. Every market carries its whole field now and sums to 100, so the row counts what the LAYOUT hides and shown-plus-claimed equals the markup at nine widths on both engines in both trees. **The numbers did not change**, because each field was completed to exactly the size that card had already claimed. `docs/decisions.md` 2026-08-17 | | |
| ~~174~~ | ~~**Search is a page you go to and it should be a control you use: the field lands at y=221 on a phone, unfocused, and the header mark links to the page it stands on**~~ **CLOSED 2026-08-17, REPORTED BY THE USER RATHER THAN BY A SWEEP**, which is the finding under the finding: every instrument here reads a rendered page, and a page that renders correctly can still be the wrong page. Measured before the rebuild: at 390 the header is 61, the category rail 77 to 143, the h1 184, **the field 221**, with no `autofocus` in the file; at 1280 the field is 922 wide under an h1 while a 36px magnifier at x=1092 links to `event-feed-search.html` **on all three search pages**, which on the results page is a link to the page you are on that discards your query. Rebuilt as a surface: a full-bleed sheet below RAIL, a field in the header at 56.25rem and above. **The rung is a measurement** - the free middle of the header row is 69px at 640, 137 at 760 and 277 at 900, and 640 is the tightest width on the ladder because DESK turns on the balance pill, the heart and How it works at once. Body written once and cloned into the panel; catalog extracted into `assets/search.js`; **both result pages rebuilt on all 24 cards**, because the seam counts from the catalog and the page filtered a 12-card subset while printing "2 events for election" against a true 3. `docs/decisions.md` 2026-08-17 | | |
| ~~165~~ | ~~**176 of 190 sub-44 targets are one family the floor never named, and they are in the footer**~~ **CLOSED 2026-08-16, AND THE FULL TREE IS FIVE TIMES THE ROW.** Censused over all 108 documents rather than the 8 the row was written from: **6,218 targets, 2,566 under 44x44, 41.3 per cent**, and 2,375 of them three lists in one component: `.footer-col a` **1,187 at 36x25**, `.popular-links a` **756 at 80x16**, `.legal-links a` **432 at 31x14**. **Two of the three are under 24 as well**, so this was AA and not only the AAA the token cites. All three are `<ul><li><a>` navigation, so the inline exception does not reach them; the 6 links that ARE in a sentence, in `.fine`, stayed out. **Two of the three would have taken the floor and done nothing**: they computed `display:inline`, where `min-height` does not apply, so they got `display:inline-flex` in the same edit. Width joined too, because 864 of the 1,187 are under 44 wide and "Politics" is 36 pixels of word. `.logo` joined for its 121 placements at 96x30 outside the header. **After: 64 under 44x44, 1.0 per cent**, identical on both engines, and the remainder is a list rather than a residue: 35 icon buttons excluded BY NAME, the 6 inline links, 4 sheet grabs, 3 checkboxes whose label carries the target, 2 policy links, 1 crumb missing on width alone. **The cost is 334px of footer**, 1,276 to 1,610, and the mean document grew by the same 335, which is the check that matters: the whole growth is below the fold and nothing above it moved. **0 overlapping targets**, boxes 44x44 with a 4px gap. A mouse is unchanged at 36x25, because the floor lives inside `@media(pointer:coarse)`. | `ui-visual/`, 2026-08-16, opened by the critique that took the product from 22/40 to 30/40 | Measured with the pointer asserted coarse and the assertion verified (`matchMedia('(pointer:coarse)').matches` true) over 8 screens at 390: **190 of 496 targets are under 44x44**, and 176 of them are the footer's bare `<a>` links at 36x25. `base.css` raises a NAMED LIST of classes to 44 under `@media(pointer:coarse)` and this family was never named; being a bare `<a>` it would also need the `display:inline-flex` precondition the same file already documents for `.sys-link-list a`. **They clear WCAG 2.2 AA, which is 24x24; `tokens.css` cites 2.5.5, which is 44x44**, so the gap is between what the token says and what the floor reaches. The remaining 14: the logo at 96x30 on the screens where it is `.logo` without `.logo-btn`, and three `.icon-btn-small` at 28x28, which are excluded on purpose. **Every earlier tap-target reading in this repository that used a headless default was measuring `pointer:fine`, where the whole block is off.** |
| ~~164~~ | ~~**Search shipped at 25 events, and the deferral it re-opens was conditional on scale that has not arrived**~~ **CLOSED 2026-08-17 BY THE DECISION IT WAS WAITING FOR: about 25 open events, and the number is in `PRODUCT.md` now.** A curated set a person can scan, which is the premise the IA deferred search on and the size the product has always drawn. So search stays as rebuilt, the hand-kept catalog in `assets/search.js` is an accepted cost at this size, and **search indexes the OPEN set only** - a settled market is reached from your own history. **Measuring the count is what found the defect, because the count had no denominator**: the catalog was exact, 25 rows against 25 questions in the paint with 0 drift both ways, but two event-shaped texts stood outside it and both were markets the product already had under another name. **Three markets were Open and settled at the same time, on adjacent tabs of the same account**; the bell shipped the resolution on 73 of 109 painted and 56 of 108 grey screens including the feed where the same market is the hero at 38%; `win.html` and `loss.html` resolved one market both ways nine days apart; the profile listed the ETF bet under Past wins and as LOST beside it; one row held NO on Conservatives, Conservatives won and it read WON. **The settled set is its own four markets now, none of them in the open 25**, each market has exactly two names, every resolution date carries its year, and every money figure is labelled Paid out or net. `docs/decisions.md` 2026-08-17 | `ia/docs/sitemap.md`, 2026-08-16, opened by the pass that built it | The IA deferred search "until catalog scale" with a stated premise, "at 10-20 curated markets, users scan the Event Feed". Measured before the build: **the product draws 25 distinct events**, so the premise moved by five and was not overturned. What overturned it was reachability - no route to an event except scrolling a category, and a 404 that draws a magnifier over a control the product did not have. **The row is open because the decision is the user's to keep or reverse**, and because the thing that would settle it either way is a number nobody has: how many events Yonder intends to run open at once. At 25 the field is a convenience; at 250 it is the only way in; at 12 it is furniture. `PRODUCT.md` names no target. **Until that number exists, the honest reading is that this pass answered a reachability finding with a destination, and the scale argument is still standing where it was** |
| ~~163~~ | ~~**The busy region is `<main>` in the grey tree and an inner plate in the paint, on three screens**~~ **CLOSED 2026-08-16, AND THE PLATE WRAPPER WAS NOT THE CAUSE.** The row read it as the paint-only wrapper forcing the paint's hand, and it is not: **`.cat-main` exists in BOTH trees on all three screens**, so nothing forced anything and the two trees had simply chosen different elements. The paint had the better one. `<main class="feed">` also holds the category rail, which is not loading, so marking it busy tells a reader that navigation is unavailable; `.cat-main` is the column that actually carries the skeletons. So the grey moved to the paint: `aria-busy` off `<main>`, onto `.cat-main`, and the first `.sk-status` line lifted inside it so the announcement sits where the other twenty screens put it. Verified on both engines over both trees: every busy region is now the same element with `.sk-status` as its first child, and the two other pairs checked as controls (`notifications-loading` on `.pos-list`, `event-feed-loading` on `.grid`) were already identical. | `wireframes/`, 2026-08-16, found while giving every loading region a status line | `my-profile-loading`, `public-profile-loading` and `wallet-loading` put `aria-busy="true"` on `<main class="feed">` in grey and on `<div class="cat-main">` in the paint. **This is the plate-wrapper difference showing its consequence rather than drift**: `.cat-layout`, `.cat-main` and `.feed-inner` are declared paint-only, so grey has no inner container to mark and marks the nearest thing it has. It is now written into the boundary table in `_conventions.md`, which is what was actually missing. **The row stays open because the two trees answer the same accessibility question in two places, and nothing checks that they stay in step.** It also decided the shape of the fix it was found by: a `role="status"` on the busy container would have taken the `main` landmark away in grey, which is why the status line is a `<p>` INSIDE the region and works in both |
| ~~162~~ | ~~**`ui-kit/hero.html` shows one of the component's five blocks, and the chart that just gained a scale is not one of them**~~ **CLOSED 2026-08-16.** `hero.css` declares **53 classes across five blocks** and the page showed **7**, all of them the brand tile. **One specimen covers all 53**, because the band is one block: `.feed-hero` lifted from `ui-visual/event-feed.html` onto the page in both themes takes the coverage to **53 of 53**, measured against the stylesheet with its comments stripped. **The id rule cost two passes and the second was a typo of mine**: a component shown twice duplicates every id it carries, this hero carries six, and the stand's `-lt` suffix is the convention for it, but the first replacement string concatenated `'\\2'` in a non-raw literal, which is `chr(2)` rather than a backreference, so an id came out mangled and the light copy lost its hot list. **The render said so before the diff did**: two bands measuring 1264 and 1080 where they had to match, and a hot list counting 5 where it had to count 10. Reverted and rebuilt. Verified on both engines at 390 and 1280: identical heights, the chart drawn, five axis labels per copy, the photograph loaded, **0 duplicate ids**, 0 sideways scroll. | `ui-kit/`, 2026-08-16, found while looking for where to write the chart's new axis down | `hero.css` declares 50 classes across five blocks: the featured event with its photograph plane and its chart, the two trust tiles, the brand tile, the hot list and the grid that holds them. **`ui-kit/hero.html`, the page `hero.css` names as its stand, carries six of the fifty**, all of them `.brand-tile` and its parts, measured by listing the `class` attributes of the file. The chart's only specimens are `ui-kit/organisms.html#hero` (1) and `ui-kit/feed.html` (2), which are shelf and screen rather than the component's own page, and a shelf gives every component one specimen and one rule. That is the difference this repository named when it rebuilt the kit: comparing ten atoms is the shelf's job, and taking one apart needs room a shelf does not have. **This is not the chosen-NO class of zero** - the faces are drawn, on three pages - it is a component page that has drifted into being a page about one of its blocks. The fix is a specimen, not a rule, and the markup exists in two places to copy from |
| ~~161~~ | ~~**The kit's `N lines` figures are hand-typed snapshots, and twelve of thirteen had drifted by roughly a factor of two**~~ **CLOSED 2026-08-16, AND THE ANSWER WAS THAT THE NUMBER WAS THE WRONG ONE.** Re-taking thirteen figures would have been the same defect a month later. **82 per cent of `components/` is prose**: 11,190 lines, **2,055 of them code**, 4,884 declarations, 1,389 rules over 56 stylesheets, running from 68 per cent in `card.css` to **98 per cent in `logo.css`**, which is 106 lines of which 2 are code. **And the ranking is upside down**: by lines `dialog.css` at 559 is half again `hero.css` at 365; by declarations **hero is 339 and dialog is 116**. A metric that reverses the two largest organisms is not measuring what it is printed beside. **The badge is declarations now**, 30 rewritten over 8 pages, plus `overview.html`'s "51 stylesheets, 5,651 lines" (56 and 4,884) and six redundant lines badges dropped from `patterns.html`. **Two contradictions fell out**: `detail-shell.html` and `feed.html` each called their file "the smallest in the system", at 17 and 11 lines, and both were wrong (`feed.css` is 12, `detail-shell.css` 25); `position.html` said 22 lines of comment where there are 132. **And the counter was wrong before the pages were**: it required a trailing semicolon, so it missed the last declaration in every block and under-counted by one per rule, which six untouched badges caught by disagreeing with it. Recounted by splitting each block on `;`. **A figure that disagrees with an existing figure is a reading of the instrument until the instrument is proved.** Verified on both engines: every badge parsed out of the HTML and compared with its stylesheet, **0 disagreements**. | `ui-kit/organisms.html`, 2026-08-16, found while checking whether this pass had made one of them wrong | Re-taken by `wc -l` against the file each row names: header 202 -> 339, footer 79 -> 171, hiw 174 -> 350, dialog 279 -> 544, tabs 123 -> 216, event-detail 66 -> 136, betpanel 179 -> 290, bets-table 49 -> 86, chart 42 -> 154, card 97 -> 152, hero 166 -> 364, profile 45 -> 51. Only `feed` at 11 was still true. **The twelve are corrected in place, and the row stays open because the metric is the problem rather than the numbers.** A line count in this folder now measures the REASONING more than the code: `chart.css` went from 42 to 154 without gaining a rule, because the comments explaining it arrived. So the figure a reader takes as "how much CSS is this" is mostly prose, and the honest replacements are a declaration count or nothing at all. Whichever is chosen, a hand-typed number in a document nothing regenerates will drift again, which is the same shape as the panel that computed 55 pages while 57 stood on disk |
| ~~160~~ | ~~**The boundary of a control is below 3:1 against its own ground, in BOTH themes, and the Vault is the worse one**~~ **CLOSED 2026-08-16 AS NOT A DEFECT, AND THE SYSTEM HAD ALREADY ANSWERED IT IN WRITING.** The row is right that the edges are quiet: measured across 46 interactive families on 18 screens, both themes, both engines, **43 read under 3:1 on fill-or-border**. But 1.4.11 asks 3:1 of "visual information REQUIRED to identify" a control, and the boundary is not that information here. **24 of the 43 have no fill and no border at all** and are text links, identified by their words. For the rest the mark or the label does the identifying, and measured from the render with element screenshots at dsf 2: **the bordered icon controls read 8.44 Vault / 5.38 Daylight (`.icon-btn`), 9.43 / 17.97 (`summary`), 5.49 / 7.67 (`.icon-btn-tile`); the card YES reads 8.01 / 7.69; the amount chip 12.01 / 15.78; the bare bookmark 6.73 / 4.32 unsaved and 8.56 / 3.20 saved.** Every one clears the criterion. **And `components/tokens.css` had already drawn exactly this line and written the reason**, beside `--border-field`: "THE BOUNDARY THAT IS THE CONTROL ... a hairline SEPARATES two areas and 1.4.11 does not reach it; a field is the only thing on the screen that says text may be typed here." **A field has no glyph and no label, which is why it alone needed the role.** So the quiet edge is the Vault material correctly scoped, the role that answers 1.4.11 exists, and it is applied where the criterion actually bites. **Not changed, and the reason to record it is that changing it was authorised**: `--border-control` on icon buttons and the bet picker would have repainted 108 screens in both themes to satisfy a criterion that was already satisfied. The one number worth watching is the SAVED bookmark in Daylight at **3.20**, which passes with almost no headroom. | | |
| ~~159~~ | ~~**`voice/voice.html` carries 23 em dashes and it is the only file in the repository that carries any**~~ **CLOSED 2026-08-16, AND IT HAD BEEN CLOSED FOR A DAY WITHOUT BEING STRUCK, WHICH IS THE HALF OF IT WORTH KEEPING.** `voice/` measures **0** em dashes today and the commit that removed them is `3c93034`, the same pass that opened this row: the pass fixed the file it was reading and wrote the row about what it had just fixed. **And the row's claim was wrong in the other direction too.** "The only file in the repository that carries any" was measured over six folders, and there are more than six: `concept/brand-toolkit/` carried **35** across four prompt files and `concept/old/pre-vault-3d/` carries **3**. The 35 are gone today (`—` to `,` or to a colon, hand-checked for the two places where the substitution left a comma at the start of a line). The 3 stay, because `concept/old/` is frozen the way `docs/kit-archive/` is. **A rule stated with "anywhere" gets measured over the repository, not over the folders somebody remembered.** | `voice/`, 2026-08-16, found while correcting a payout figure in the same file | The root rule ends with "No em dash, anywhere", and measured across the whole tree it holds everywhere except here: `ui-visual/` 0, `wireframes/` 0, `ui-kit/` 0, `components/` 0, `ia/` 0, `voice/` **23**, all `&mdash;` in the voice guide's own prose and in its worked examples. One of the 24 went on 2026-08-16 because it stood inside a UI string the guide offers as a model line, and a model line that breaks the rule is the worst place to break it. **The other 23 are prose and each needs a reading, not a sweep**: some join two clauses where a colon is right, some stand in a table cell as a "not applicable" mark, and at least one sits inside an ANTI-example, where the dash may be part of what is being shown as wrong. A find-and-replace would flatten all three cases into one. Owner: the voice guide. |
| ~~157~~ | ~~**The paint built a screen and wired its route, and the grey tree has no file for it and still points the same 320 anchors at `#`**~~ **CLOSED 2026-08-15 the same day, by writing the grey page rather than by declaring the hole a boundary.** `wireframes/terms.html` is 1,437 lines, one `h1`, 25 `h2`, 14 numbered clause sections, and it is built from the grey `how-it-works.html` because that is the tree's own static-content page: same chrome, same logged-out header, same footer. **Structure matches the paint exactly and was read rather than assumed**: 1 h1, 25 h2, 14 `section[id]`, all 14 contents links resolving to an id on the page, the rail arriving at 900 and not at 899, sticky and 214px wide, the disclosure head hidden above it, identical in Chromium 151 and WebKit 26.5. **The six differences are honoured and each was checked**: `.feed-inner` dropped and `.cat-layout` kept, because the 2026-08-13 restoration ruled that one is a plate and the other is a flex parent; 0 `<img>` and 0 `background-image`; 23 raw `<path>` and 0 `<use>`; and the four sibling documents that are not built carry a `TBD` chip, which is the chip's whole job. **0 non-neutral hex on the page**, with the instrument proved against brass first. **The inset had to be declared and the measurement is why**: the paint takes its gutter from `.feed-inner`, which is the plate that just left, so first text sat at x=2 against the paint's x=31 and a 316px legal column ran into both edges at 320. It is 14 now. **196 dead `#` Terms anchors repointed across 105 files, and a `Legal` section added to all 105 screen trees**, mirroring the paint's own sidebar group. After: **0 broken of 34,950 links over 297 documents**, and the only painted screen with no grey twin is `overview.html`, which is the index of the tree rather than a screen in it. `docs/decisions.md`. | `ui-visual/` against `wireframes/`, 2026-08-15, found by asking each tree for the set of screens the OTHER one links to | `ui-visual/terms.html` is 882 lines and 25 sections, it has an SEO section of its own (`ia/docs/pages/seo.md` §6, route `/legal/terms`), and **320 anchors across the painted tree reach it**: the footer legal strip on every screen, the sign-in fine print, the deposit fine print. The grey tree has no `terms.html` at all, and its copy of every one of those anchors reads `<a href="#">Terms</a>`. **The mirror is clean**: every screen the grey tree links to exists in the paint, 0 exceptions, so this is one hole and not a pattern. It is also the one direction the layer rule forbids, "a block is decided in grey and the colour copy follows": a destination is structure, the grey tree owns structure, and here the paint decided a route the grey tree was never told about. **And the grey footer does not even mark it TBD** - its five `.tbd` chips are Tagline, Language, Help Center, FAQ and Contact, so Terms is not flagged as unbuilt, it reads as a link that goes nowhere. Two ways out and they are different sizes: back-port a grey `terms.html` from the painted copy and point the grey anchors at it, which restores the rule and costs one page plus one `href` per grey screen; or declare that legal documents are paint-only, put the reason in `wireframes/_conventions.md` beside the six differences, and give the grey anchors a `.tbd` chip so they stop looking finished. Owner: Stage 12 (Handoff) |
| ~~156~~ | ~~**The same 32 screens have two different filenames, one per tree, and nothing in the repository says which is right**~~ **CLOSED 2026-08-15. The grey files are `event-feed-<category>[-state].html` now, which is what the paint had always called them and what this tree's own naming line had always asked for.** The shorter name had a real argument and it was read rather than ignored: `ia/docs/pages/seo.md` gives the category its own route, `/c/{category}`, which says it is a screen and not a state. Against it, the same file gives it the H1 "{Category} events" and a description beginning "Bet YES or NO on live {category} events", the screen ships the feed's shell, the feed's cards and the feed's eight states, and **in a flat directory `politics.html` is a name that says nothing about what the page is**. A route and a filename answer different questions. **5,046 references rewritten in 107 files** plus 32 `git mv`, anchored so that a match cannot be preceded by a word character or a hyphen, because the naive pattern counts `event-feed-politics.html` as a reference to `politics.html`. `docs/decisions.md` is left alone at its 4, being append-only and true as of its own date. **The prose that DESCRIBED the divergence was rewritten rather than renamed**, in `STRUCTURE.md` and `ui-visual/CLAUDE.md`, because those two sentences existed only to carry the translation table, and the table is what hid the hole: pairing by filename cannot see an unpaired page, which is how 32 grey category screens stood against 4 painted ones for two stages. **After: a diff of the two file lists IS the pairing.** 106 painted, 105 grey, 0 names differing, and the single unpaired document is `ui-visual/overview.html`, the index of the tree rather than a screen in it. 0 broken of 34,950 links over 297 documents; 3,216 renders at six widths in both engines with 0 horizontal scroll, 0 duplicate ids, 0 page errors and 0 HTTP errors. `docs/decisions.md`. | `wireframes/` against `ui-visual/`, 2026-08-15, found by diffing the two file sets | The paint calls them `event-feed-politics.html`, `event-feed-crypto-loading.html` and so on; the grey tree calls the same screens `politics.html` and `crypto-loading.html`. **72 of the screens share a name, 32 do not, and every cross-tree comparison this repository will ever run needs a translation table for exactly those 32.** Neither side is obviously wrong, which is why this is a row and not a fix: `wireframes/_conventions.md` says "logged-in pages keep the base names (`event-feed*.html`)" and the grey files do not, but that sentence sits in a section about the logged-out variants and is a glob rather than a ruling; and the IA gives the category screen its OWN route, `/c/{category}` in `ia/docs/pages/seo.md`, which argues the category is a screen in its own right and `politics.html` is the truer name. **Filename is NOT one of the six declared differences in `wireframes/_conventions.md`**, so by that file's own definition this is drift rather than boundary. **Cost of the rename, measured rather than guessed: 5,012 true references**, 4,940 of them inside `wireframes/` and self-contained, plus 36 in `ia/annotations/category.html`, 32 in `voice/docs/microcopy.md`, 1 in `STRUCTURE.md` and 1 in `ui-visual/CLAUDE.md`; the 2 in `docs/decisions.md` are historical and that file is never edited. **The first count of this was 9,866 and it was the instrument**: a `\b` word boundary matches inside `event-feed-politics.html`, so 4,776 of the paint's own filenames were counted as references to the grey ones, and a check on one file gives 48 naive against 0 anchored. Decide the name once, in `_conventions.md`, then it is one sweep. Owner: the project, not a stylesheet |
| ~~155~~ | ~~**The `vh` fallback was deleted from the token that could not use it and left standing in the four declarations that can, so the system now says two things about the same browser**~~ **CLOSED 2026-08-15 by dropping it everywhere, because the other direction was proved impossible the day the row was written.** The pair cannot be written for a custom property: it accepts any token sequence, so the unit is never rejected and `max-height:92vh` before `max-height:var(--cap)` computes to `none` rather than to 92vh, measured in both engines. **So the system could be made to say one thing only by dropping the pair, never by adding it.** Five sites now, one sentence: `base.css`, `betpanel.css`, `catnav.css`, `toc.css` and `course-chrome.css`, whose pair had stood for one day. **The cost is named rather than waved at**: on an engine older than Safari 15.4 of March 2022 or Chrome 108 of November 2022, `.device` loses its minimum height so a short page stops filling the window, and three rails lose a maximum they still scroll inside. Cosmetic, bounded, and nothing becomes unreachable. Verified in both engines after: `.device` `min-height` 800 at an 800 viewport, `.toc` 718, `.bet-panel` and `.subcat` 664, the drawer 800, `--sheet-cap` still `92svh`, 53 stylesheets parsing, 0 page errors. `docs/decisions.md`. | `components/`, 2026-08-15, found by censusing the unit rather than the argument | `base.css`, `betpanel.css`, `catnav.css` and `toc.css` each write `vh` then `svh` as a pair, and `base.css` carries the paragraph arguing for it: "a browser that has not learned `svh` takes the first". `tokens.css` deleted exactly that half beside `--sheet-cap` one day later, 2026-08-14, on the measured ground that `CSS.supports` for `svh` is true in both engines here, "so it protected nothing". **Both are argued and they disagree, and the tree has been one day out of step with its own newer decision in four places ever since.** The deciding fact is now measured and it does not settle the question, it only rules out the tidy answer: **the pair CANNOT be written for a token**, because a custom property accepts any token sequence, so the unit is never rejected and `max-height:92vh` followed by `max-height:var(--cap)` computes to `none` in both engines, not to 92vh. So "make them consistent by adding the pair everywhere" deletes the cap on three sheets. What is left is a product call about audience rather than a CSS one: `svh` landed in Safari 15.4 in March 2022 and Chrome 108 in November 2022, and the question is whether this product serves a phone older than that. Decide it once and apply it to all five sites, or write the reason the two groups differ where a reader will meet it. Owner: the product, not the stylesheet |
| ~~158~~ | ~~**The stand wraps three labels the product never wraps, because a cell two themes wide is narrower than any placement**~~ **CLOSED 2026-08-15, and the row named the wrong cause and therefore the wrong fix.** The themes were ALREADY stacked in both cells, so stacking was not available as a cure: `header.html` and `betpanel.html` both carry `.tk-stack`, and the row was reading a symptom this kit had already paid for once. **The cause is that a component as wide as the WINDOW cannot be shown in a cell that has margins.** `.tk-theme-fig` has 16px of padding inside a `.tk-wrap` with 14 or 20 more, so a header specimen measured **258 / 298 / 328 against the product's 320 / 360 / 390, short by 62 at every width**. And the cost was not cosmetic: `.auth-btns` is INTRINSIC at **141 in the product from 320 upward** and was squeezed to 114 and 133 in the cell, so `Sign in` and `Sign up` painted on two lines at 320 and 360 on the page whose whole job is to say what that header is. `Confirm bet` did the same in a 258px dock against 320. **Dropping the cell's own padding is the interesting half fix**: it gives 290 / 330 / 360, which cures 360 and leaves 320 wrapping, so a sweep run at one width would have called it done. `.tk-bleed` cancels the page inset too, at both of the wrap's values because `.tk-wrap` is 14px below the desk and 20 above. **16 cells over 4 pages, chosen by asking the DOM which figures hold a `.app-header` or a `.bet-dock` rather than by matching class text**, which is what the first pass did and it missed two and marked the wrong ones. After: **0 wrapped labels over 912 kit renders at eight widths in both engines**, `.row` reading the viewport less this cell's own 2px border at every width to 900, `.auth-btns` at the product's 141, 0 horizontal scroll, 0 duplicate ids, 0 page errors, and the sweep asserts BOTH directions, 0 bled cells without a full-bleed component and 0 full-bleed components in an unbled cell. `docs/decisions.md`. | `ui-kit/`, 2026-08-15, found by sweeping all three trees for a label painting on more than one line | `ui-kit/header.html` wraps `Sign in` and `Sign up` at **320 and 360**, and `ui-kit/betpanel.html` wraps `Confirm bet` at 320, in both engines. **In the product none of the three ever wraps at any width.** This file already carries the rule it breaks: "a specimen measured in a cell narrower than any placement is not the component", and the answer taken for `organisms.html` was to stack the themes below 640 and pay the side-by-side. Two cells here did not take it. The fix is the one already in the kit, `.tk-stack` or the stacked-theme layout on those two sections, not a change to `button.css`, because **the component is fine and the cell is not**. Owner: `ui-kit/` |
| ~~154~~ | ~~**Two buttons in a `.cta-bar` wrap a two-word label at 320, because the bar splits the row and neither half is wide enough for its own words**~~ **CLOSED 2026-08-15, and the row's own sentence was the thing that was wrong.** Measured at 320 in both engines: `Browse events` needs **125.83** and has **125.00**; `Open Wallet` needs **108.61** and has **108.00**. **Each is short by less than one pixel while its neighbour has thirty and twelve to spare**, and both stop wrapping at **322**, two pixels above the narrowest width anybody reads. So neither half being wide enough was never true: one half is, by a rounding error, and `flex-basis:0` divides the row before asking what is in it. The fix is a FLOOR and not a query: `min-width:fit-content` on the children plus `flex-wrap:wrap`. Line breaking uses the hypothetical main size, which is the flex base size clamped by `min-width`, so a floor reaches the decision `flex:1` alone cannot, and growth still starts from 0, so **the halves stay exactly equal whenever both fit**: identical to the old tree at 360, 390, 640 and 1280 on all three screens, and at 320 only the pair that could not fit moves, 125/125 to 126/124. **`max-content` was measured and refused**: it fixes today's five buttons identically and sets a floor no container can refuse, so a 40-character label puts the page into **12px of horizontal scroll and a 44-character one into 60px**, which trades a wrapped two-word label for the defect this repository has spent the most days on. `fit-content` is capped by the space available and stacks the same label into two full-width rows instead. After: **0 wrapped labels inside a `.cta-bar` over 4,288 renders**, with the control proved by squeezing the BAR rather than the item, because squeezing the item stopped working the moment the floor existed. `docs/decisions.md`. | `ui-visual/`, 2026-08-14, found by sweeping every `.btn` in the tree for a label on two lines after the How-it-works label was shortened | Measured at 320, 360 and 390 in both engines over all 106 screens: **at 360 and 390 not one button in the product wraps its label.** At 320 four do, and two of them are the interesting pair: `Browse events` in a 125px button on `how-it-works.html` and `Open Wallet` in a 108px button on `my-profile.html`. **A 13-character label wrapping is not a long label, it is a narrow box**: the bar gives each of two buttons half a 320px row and `Browse events` needs about 135. The other two are `Notify me of new events in this category` in a 226px button on the two empty feeds, which is a genuinely long label and a different question. **320 is below the 360 this project designs from**, so this is filed rather than fixed, and the fix is a decision about what a `.cta-bar` does when its halves stop fitting: stack, or let the labels shrink. Owner: Stage 12 (Handoff) |
| ~~152~~ | ~~**The `Reads:` line in every stylesheet header claims to be the token register and no file carries a complete one**~~ **CLOSED 2026-08-14, `decisions.md` same date, and the number in this row's own title was wrong.** The decision was the expensive half and it is taken: **the line registers the SEMANTIC COLOUR ROLES and nothing else**, which is what the sentence directly under it in all 49 files has always said, `Colour goes through a role, geometry straight from a primitive`. Geometry is not registered because it has nothing for a theme to override. **43 of 43 complete now, 0 stale**, rewritten by one throwaway script from each file's own body with comments stripped, wrapped at 96 characters. It was **43 and not 44**: the opening sweep matched the STRING `Reads:` anywhere in a file and `tokens.css` carries it in a comment on line 1184, so the file that DEFINES the roles was counted as a file that fails to declare them. **A count taken by matching a string is a count of the string**, which is this repository's own lesson about a selector that agrees with every hypothesis, met from the other side. **11 files carry no `Reads:` line and every one of them is right to**: `tokens.css` defines the roles, and the other ten read **0 roles between them**, measured. Six of those are the whole of `patterns/`, which is the rung's own invariant rendered visible for the first time: **a pattern carries no colour, and now the absence of the line is what says so.** | `components/`, 2026-08-14, found by auditing the filters sheet against the system rather than by reading the files | Measured before: 43 files with the line, 0 complete. `hero.css` was missing 48 of 84, `hiw.css` 42 of 57, `course-chrome.css` 35 of 46, and the folder disagreed with itself about what the line was for, `seo-plate.css` listing 21 tokens all of them colour while `logo.css` listed `--space-8` and `--weight-bold` beside its inks. Owner: Stage 12 (Handoff) |
| ~~153~~ | ~~**`.bet-sheet` caps itself at `92vh` with no `svh` twin, so on a phone it can run under the browser's own bar**~~ **CLOSED 2026-08-14 for the half that is measurable.** `.bet-sheet` carries `max-height:92vh;max-height:92svh` now, the pair the rails in this file and in `toc.css` and `catnav.css` have always had. **AND THE BROADER QUESTION IS ANSWERED THE SAME DAY, AND THE ANSWER IS THAT IT WAS TWO QUESTIONS AND NOT ONE.** A modal SHEET caps at a fraction of the viewport; a sticky RAIL caps at the viewport MINUS its own top offset. The rails were already one answer, `calc(100svh - X - var(--space-16))` in `catnav.css`, `betpanel.css` and `toc.css`, one formula with a parameter. **The sheets were not**: three fixed surfaces over a page carrying `92vh`, `88dvh` and `92svh`, two numbers and two units, and only one of the three had an argument beside it. They read `--sheet-cap:92svh` now. 92 because it was already two of three; `svh` because it is the SMALL viewport, the one measured with the browser's bar shown, so the sheet always fits and never resizes under a thumb, where `dvh` moves while somebody is reading and `vh` lets the tail sit behind the bar. **The `vh` fallback beside two of them is deleted**: `CSS.supports` for `svh` is true in Chromium 151 and WebKit 26.5, measured, so it protected nothing and cost one number written twice. | `components/betpanel.css`, 2026-08-14, found while taking the filters sheet's cap from the system instead of inventing one | The system has four answers to "a surface that must not exceed the viewport": `92vh` on `.bet-sheet`, `88dvh` on a modal dialog, and `calc(100svh - 120px - var(--space-16))` on the two rails, which are the only two that carry the `vh` then `svh` pair. `vh` is the LARGE viewport on a phone, so a sheet capped in it is a sheet whose tail can sit behind the address bar until the bar retracts. The filters sheet took `92vh; 92svh` rather than a fifth number, and this row is the half that is left. **The cheap part is one line; the part worth doing is deciding whether the product wants ONE cap for every full-bleed surface** rather than four that agree by accident. Owner: Stage 12 (Handoff) |
| ~~150~~ | ~~**The filters sheet is a checkbox, so it has no focus trap and nothing returns focus to the button when it closes**~~ **CLOSED 2026-08-14, `decisions.md` same date, and the Tab walk that closed it found two more defects than the row named.** `inert` on every SIBLING along the path from the sheet up to `<body>`, and nothing else, because inert INHERITS DOWN. Three elements are skipped on purpose and they are the ones that close it. **The first build listened for `change` and Escape sets `checked` in script, so a property assignment fired no event and the sheet closed leaving 26 elements inert behind it**: every path goes through one function now. **And `Show results` and the close were `<label>`s, which is correct for a pointer and invisible to a keyboard**, so a 262px brass button was one Tab never reached; both are `<button>`s. Verified by a 40-press Tab walk in both engines: every stop is inside the sheet or is the toggle itself, which stays reachable because Space on it is a way out. Focus moves in on open, in a `requestAnimationFrame` because activating a label moves focus to its input AFTER the handler runs, and goes back on close. | `components/filters.css`, 2026-08-14, named in the decision that opened it rather than found afterwards | The sheet buys ONE markup and ONE set of radios by being a checkbox rather than a `<dialog>`, and a radio group is keyed by `name`, so the alternative is two copies that are one group with two sources of `checked`. That bargain is the right one and this is the half it does not cover. Escape is bought back by nine lines of script on the 57 screens that carry the strip; the trap is not. **A person tabbing with the sheet open walks straight out of it into the page behind the scrim, which is the same class of defect as the condensed band that handed a keyboard 440 stops it could not see**, and it is smaller only because the sheet is deliberate and the band was not. The two candidate fixes are `inert` on the page behind it, which is one attribute and needs a script to apply, and moving to `<dialog>` with `showModal()`, which returns the trap and the Escape for free and costs the single markup. Neither is a stylesheet change, which is why this is a row and not an edit. Owner: Stage 12 (Handoff) |
| ~~151~~ | ~~**40 painted screens run a script that queries a `.feed-subfilter` they do not contain**~~ **CLOSED 2026-08-14.** The whole `uv-subfilter` block is deleted from the 40 screens that carry no such markup and left untouched on the one that does. | `ui-visual/`, 2026-08-14, found while counting the placements the sheet work had to reach | `grep` for the string returns 41 screens and `grep` for `class="feed-subfilter"` returns 1. The other 40 carry `var group = document.querySelector('.feed-subfilter')` and every line after it is guarded, so nothing throws and nothing happens: measured, **0 page errors across all 106 screens in both engines**. It is inert and it is not nothing. **A selector that matches nothing agrees with every hypothesis**, which is the sentence `filters.css` already carries about a dead half-selector, and the cost here is the same: anybody counting placements by grepping the class name reads 41 for a component that stands once. That is exactly the mistake this row was found by making. Owner: Stage 12 (Handoff) |
| ~~149~~ | ~~**"How it works" is hidden from 641 to 759 by a rule whose reason the hamburger took with it**~~ | header.css, measured 2026-08-14 | **CLOSED 2026-08-14, and the row was half right: the rule had expired by 400px of width, not by all of it.** The DETAIL 760 cut was made on a measurement, at 641 the signed-in desk row asked 694px against a 641px window and 73 of 106 screens took horizontal scroll from 641 to 652, and **36 of that 694 was the hamburger with 8 more for its gap**. Re-measured rather than re-derived, with `.hiw-btn` forced visible across all 105 painted screens in Chromium and WebKit: 0 pages scroll sideways at 360, 639, 640, 641, 759 and 760, worst intrinsic row demand 535.8 at 641. **But it is not redundant, and walking it at 4px steps from 320 is what showed that**: 32 of the 105 overflow their header row with the label visible, by 38px at 320 falling 4px per 4px of width to 2px at 356, and **0 of 105 from 358 up**. The 32 are exactly the logged-out screens, all carrying `.auth-btns`, which is the mirror of the 73 in the original reading: the auth pair that used to be narrower than the balance figure is now the wider of the two. **358 does not become a rung.** The ladder holds 40rem, 47.5rem and 56.25rem, and a fourth number bought for one control is the exact thing the registry exists to stop, so the rule moves to the rung the system already declares. As shipped: hidden 0 of 105 below 640, shown 105 of 105 from 640 up, 0 pages scrolling sideways at 320, 390, 639, 640, 641, 759, 760 and 1280 in both engines. Registry unmoved at 34: `47.49875rem` goes 3 to 2 and `39.99875rem` 8 to 9. |
| ~~146~~ | ~~**Four of the thirteen course documents put content past the right edge on a phone, none of them SCROLLS, and the sweep that opened this row could not tell those two apart**~~ **FULLY CLOSED 2026-08-15, and the remainder was not 15 pixels on one document at one width, it was 171.** The row's last paragraph said `research/research.html` was 15px over at 320, exact at 360 and 390, and no single element accounted for it, in the `#benchmark` section. Re-read against the CLIPPING BOX instead of against `scrollWidth`: every one of them is in **`#lean-ux-canvas`**, and the Lean UX canvas stood in a `style=` attribute as `grid-template-columns:repeat(4,1fr)` with `overflow:hidden`, so on a phone each of four cells got about 78px and the words inside stood past it and were CUT. **19 elements at 320 with the worst 171px outside the box, 17 at 360 with 131, and 9 at 390 with 101**, identical in Chromium 151 and WebKit 26.5. The row could not see any of it because `scrollWidth` reads the page and a clip hides the evidence of the thing it causes, which is this repository's own sentence about content standing past an edge with no way to reach it. It is a class with three rungs now, four columns from the desk, two from 360, one below it, and the row track goes to `auto` with them; **a `style=` attribute is a rule in the one place a media query cannot reach**, which is why the declaration moved rather than being overridden. `repeat(auto-fit,minmax(...))` was measured and refused at three minimums: 150 and 170 give six and five columns at 1280 and 200 gives two at 640, and the canvas is four columns by definition. **After: 13 course documents at eight widths in both engines, 208 renders, 0 page scroll and 0 clipped content**, with the probe fixed first, because the version that tested `scrollLeft` readback skipped `overflow:hidden` entirely, which is scrollable from script, and reported a clean zero from a blind instrument. Control seen 2 of 2. `docs/decisions.md`. **MOSTLY CLOSED 2026-08-14, and both halves of the original row were wrong about the mechanism.** First, the fix is not `overflow-x:auto` per table and the cause is not a table: every element the opening sweep named as the culprit was already inside a scrolling container, and the real overflowers are FOUR pieces of `white-space:nowrap` on prose plus one flex row. `.funnel-step-metric` and `.gap-item .source` in `research/research.html`, `.evidence-source` in `user-research/jtbd.html` (264px of unbreakable filename), `.legend span` in `ia/sitemap.html` (359px, the one legend entry with a parenthetical), and `.appbar` in `concept/concept.html`, a seven-item flex row needing 370px with no `flex-wrap`. **A filename is exactly the string that has to be allowed to break**, so three of the five take `overflow-wrap:anywhere` and the legend takes `white-space:normal`; the header wraps. After: `scrollWidth` equals `clientWidth` exactly on jtbd at 320, 360 and 390, on sitemap at 320 and 360, and on concept at 320 and 360. **Second, and this is the part worth keeping: `scrollWidth > clientWidth` is not the same as a page that scrolls.** Set `document.scrollingElement.scrollLeft = 9999` on any of these four, before or after the fix, and it reads back **0**. They never scrolled. The content simply stood past the right edge with no way to reach it, which is worse than scrolling and is a different defect from the one the row's title claimed. Every horizontal-scroll number this repository has published was taken with the same predicate. **WHAT IS LEFT IS 15 PIXELS ON ONE DOCUMENT AT ONE WIDTH.** `research/research.html` went from 347 to 335 against a 320 viewport and is exact at 360 and 390. Bisected by hiding one leaf at a time: no single element accounts for it, the largest contributor is a `<p>` of ordinary wrapping prose that drops 8px, and it is container geometry in the `#benchmark` section rather than any unbreakable string. 320 is below the narrowest width the product targets, and the remainder is not the species this row was opened for. | `research/`, `user-research/`, `ia/`, `concept/`, 2026-08-13, found by the sweep that proved row 117 | The product trees are clean: **1,335 readings over 267 documents at five widths, 0 horizontal scroll.** The same sweep walked the 28 documents that carry the course roadmap and found **5 readings that scroll**, `research/research.html`, `ia/sitemap.md`'s page, `concept/concept.html` and `user-research/jtbd.html` at 320, and `jtbd.html` again at 390. **None of it is the panel and none of it is the system**: each of these pages carries its own inline stylesheet, and what overflows is content, a `TABLE` 643px wide inside a 390px viewport on `jtbd.html`, 287px of table at 320 on `sitemap.html`, and a `.chip` row on `concept.html`. They are review artefacts rather than product, which is why no rule here has ever governed them, and that is exactly what makes the row worth having: **a document nobody declared to be in scope is a document every measurement silently excludes.** The fix is a wrapper with `overflow-x:auto` per table, which is the answer row 125 already reached for the kit. Owner: Stage 12 (Handoff) |
| ~~147~~ | ~~**Read from disk, WebKit loads neither of the two faces the product is set in, and every engine-blind sweep this repository has run said the type was fine**~~ **CLOSED 2026-08-14 by inlining the four faces the product actually uses.** Proved by probe string rather than by inspection: at 40px from disk, WebKit measured `'DM Sans',serif` at **369px, the serif fallback to the pixel**, and `'Space Grotesk',serif` at 369 too, against 410 and 435 in Chromium; after the change both engines measure 410 and 435 over `file://` and over `http://`. **Only four of the eight files were ever fetched by anything**: all 163 documents were rendered and their font requests counted, `dm-sans-var-latin` 163, `space-grotesk-var-latin` 163, `ibm-plex-mono-600-latin` 109, `ibm-plex-mono-500-latin` 104, and the four `-latin-ext` files **0 times each**, because the product contains no extended-latin character. Those four stay as file references and will fail from disk in WebKit on the day one appears, which is written into `components/fonts.css` rather than left as a surprise. **The 326 `<link rel="preload">` lines are gone with it**, and so is the comment block above them, so no document in this repository now says anything about a font: a `data:` URI has no fetch to start, which is what a preload existed to start early. CLS re-measured at 400 Kbps over 27 screens before and after: **mean 0.0000 both ways, worst 0.0000 against 0.0001, 0 screens above 0.0005.** **The price is named rather than hidden: +38,605 bytes on the mean screen**, CSS 877,387 to 984,717 against fonts 68,725 to 0, and the 53 documents that never use the mono carry it now anyway. `docs/decisions.md`. | all 163 documents that link `index.css`, 2026-08-13, found by putting a second rendering engine in the harness while closing 140 | **A font is fetched in CORS mode even from its own origin**, which is why all 163 documents carry `<link rel="preload" ... crossorigin>` and why `components/CLAUDE.md` says so out loud. `file://` gives every file its own opaque origin, so from a page opened off the disk that fetch has no origin to match. Measured on `event-feed.html` at 1280: over `http://` WebKit reports `DM Sans`, `Space Grotesk` and `IBM Plex Mono` loaded; over `file://` it reports **only `IBM Plex Mono`**, which arrives through the single `local()` in `fonts.css` and not from `assets/fonts/` at all. The two preloads fail in BOTH engines over `file://`, and Chromium says so out loud in the console, `Access to font at 'file:///...' has been blocked`, twice per document, which means **the preload that took this tree from a worst layout shift of 0.0438 to 0.0000 does nothing at all on a disk page in either engine**. Chromium then loads the faces anyway through `@font-face` and WebKit does not, so **the same document is set in Space Grotesk and DM Sans in one browser and in a fallback face in the other**, and 57.25% of the footer strip's pixels differ between the two protocols on WebKit alone. **This is the same origin rule that blocked the trust masks in 140 and made `assets/icons.js` a script instead of an `.svg`, its third victim in five days**, and it is the one where the cost is not bytes but every reading anybody has taken of type, measure, line length or layout shift on a page opened from disk in Safari. It is a defect of how this repository is READ rather than of what it ships, because the product is served over `http://` where all eight faces load in both engines. The candidate fix is the one 140 took: the two variable latin faces are 36,980 and 22,320 bytes, so inlining them into `fonts.css` as `data:` URIs costs about 79 KB of base64 on every screen and removes the two `<link rel="preload">` lines from 163 documents at the same time, which is `docs/backlog.md` 141 arriving from the other side. **Decide it as a pair with 141, not alone.** Owner: Stage 12 (Handoff) |
| ~~148~~ | ~~**Two of the four trust drawings are still pictures, on one screen each, and they are the only reason `assets/` still carries either of them**~~ **CLOSED 2026-08-14.** `.ht-art` was six `<img>` elements across two documents and it is `.hero-trust::after` now, taking its drawing from `trust-art.css` like the other three placements. **Which drawing is a decoration's question and not a screen's**, so it is keyed on `nth-of-type` the way the footer strip keys its three, which is the opposite call `card.css` makes about the event photograph and the difference is the test: a photograph names the event and somebody editing the feed chooses it, while a column and a globe behind a trust claim are the component saying the same thing twice. Measured on both engines at 390 and 1280, control 0.00 on every reading: 13 to 30 per cent of the hero region's pixels differ at a mean of 1.58 to 2.59 of 255, the largest of the four placements because this one stands at `opacity:.6`, and the drawing reads slightly cleaner rather than worse. `trust-column.webp` and `trust-globe.webp` left `assets/` for `visuals/masters/`: **`assets/` 1,193,741 bytes to 985,277 in 23 tracked files**, and `event-feed.html` from 403,248 bytes of image to 194,784. | `ui-visual/event-feed.html`, `ui-kit/feed.html` and `components/hero.css`, 2026-08-13, opened by the fix for 140 | `.ht-art` is the FOURTH placement of these drawings and the most visible of them, an `<img>` at `opacity:.6` on the feed hero, and it was left as a picture when the footer strip, the card corner and the seo plate became masks. So `trust-column.webp` (90,158) and `trust-globe.webp` (118,306) stay in `assets/` for **one product screen and one kit page**, where they used to stand on 105. That is already the shape of the win and this row is the last 208,464 bytes of it. **It is not a stylesheet edit**: `.ht-art` is markup in two documents, so converting it means replacing an `<img>` with an element the mask can stand on, in both trees' worth of copies, and the rule that markup goes to two places and only two is what makes that a deliberate act rather than a sweep. Worth asking first whether the hero art should be a flat brass wash at all: at `opacity:.6` it is the one placement where the colour variation a mask cannot carry is actually visible. Owner: Stage 12 (Handoff) |
| ~~42~~ | ~~`.app-case` is a dependency the system requires and never declares~~ **CLOSED 2026-08-08.** **The chip now names the second place it stands, which is the idiom `input.css` already used.** `chip.css` asked for `.app-case` and nothing else on four rules, so a `.chip-amount` in a `<dialog>` got NO rule at all. Confirmed by opening the deposit dialog on `event-feed.html`: the User Agent's `2px outset`, an `rgb(239,239,239)` ground and square corners. The four rules gained `dialog.app-dialog .chip-amount` beside `.app-case .chip-amount`, which is exactly how `input.css` scopes the amount FIELD in the same sheet, and is why the field was always right and the chip beside it was not. A scope is kept rather than dropped, so the chip still cannot leak onto a course page, and `dialog.app-dialog` at 0,1,1 sits one step above `.app-case` at 0,1,0, which is the right way round. **Measured after, over all 106 painted screens: 420 User-Agent-painted controls to 0 with dialogs closed, and 0 with EVERY dialog forced open**, which is the state the defect needed and had never been put in. 480 chips checked, 0 wrong. The original note: \| **`.app-case` is a dependency the system requires and never declares** \| Design System step 3e, the consolidation's verification, 2026-08-08 \| Third sighting of one shape. `chip.css` writes `.app-case .chip-amount`, not `.chip-amount`, so a chip outside that class gets **no rule at all**: the User Agent's `2px outset` border, a `rgb(239,239,239)` ground and square corners, in a dark product. **28 of 40 `.chip-amount` elements sit in a `<dialog>` that is outside `.app-case`.** All 28 are in closed dialogs so none renders today, and the 12 that do render are correct, which makes this latent rather than live. The two earlier sightings: the unscoped `.amount-input` rendering as a white UA field (item 25, `vitrine.html`), and the same scope named on the icons page. **A large part of this system is scoped to a class that nothing declares as a requirement**, and the day a component is handed to a developer on its own it will arrive unstyled. The fix is a decision about whether the scope stays and gets written down, or goes \|| | |
| ~~43~~ | ~~`50%` against `--radius-pill`, and 81 layout dimensions~~ **CLOSED 2026-08-12, Responsive step 4. THE ANSWER IS NO, AND IT IS NOT A SHRUG.** Censused with comments and media queries excluded, because prose is not a rule and a rung is registered elsewhere: **88 genuine LAYOUT literals in 51 distinct values**, not 81. The row's 81 had counted **127 box-shadow numbers and 6 filter numbers**, and a shadow offset is not a layout dimension. A ladder is for values standing in a RELATION, which is what makes `--space-8` and `--space-12` two steps of one thing; 214, 300, 196, 322 and 160 stand in no relation at all, and a shared scale would invent an arithmetic nobody measured. **What some of them needed was a NAME, and the test is how many files use one.** 44 of the 88 stand in exactly one file, 36 values across 18 files, and each is that component's own measurement and stays there. Of the 15 shared values only **two are one fact**: `--rail-width:214px`, which `toc.css` and `catnav.css` had already agreed on **in prose**, the comment saying "the same 214px" while nothing held it, and `--menu-min:196px`, the floor of any panel hanging off a control, in `header.css` and `filters.css`. **One was refused for the reason `--grid-gap` was refused**: the `16px` at the foot of the three sticky columns is `var(--space-16)` now and not a `--sticky-gap`, because the space ladder is already that number. The three sticky TOP offsets stay raw, 120, 120 and 66, because they measure what happens to stand above each column and that differs. Tokenisation proved inert: **0 differing rows of 30** computed readings over five widths and six elements. | Split from item 41, 2026-08-08 | **THE SHAPE HALF CLOSED 2026-08-10**, `decisions.md` same date, and the rule is the BOX rather than the value. On a square `50%` and `--radius-pill` draw the same circle; on a rectangle `50%` is an ellipse and `--radius-pill` a stadium, so only one of the two directions is safe and it is not the one that looks tidier. **Read from the paint at 390 and 1280 across six screens: all 7 declarations in `components/` stand on a box whose two axes are equal and 0 stands on a rectangle** - `dialog.css` twice at 210x210, `comments.css` at `--size-28`, `hiw-dialog.css` at 224x224, `course-chrome.css` at `--size-4`, `hero.css` at `--size-8`, `toggle.css` at `--icon-16`. Two of the seven read 0x0 at rest because they sit inside a shut dialog, and both measure square on the screens that open them. So the ambiguity the row was filed for does not arise anywhere in the system, and the rule is written beside the radius ladder in `tokens.css` so that the first rectangle to reach for `50%` is caught rather than counted. **THE 81 STAY OPEN AND THEY ARE RESPONSIVE'S.** The original row: The value half of 41 is closed. What is left is not a value: **`50%` is a sixth corner shape the ladder does not name, 28 readings**, and on a rectangle it is an ellipse where `--radius-pill` is a stadium, so it needs a rule rather than a sweep. And **81 raw px are genuine layout dimensions** (a 120px column, a 560px sheet, the negative photo-bleed offsets) that should not be on a 4px ladder at all; whether they need a scale of their own is **Responsive's** question, not the system's |
| ~~39~~ | **`--hairline` exists, is used 10 times, and `1px` is typed 145 times** | Design System step 3d, `ui-kit/geometry.html`, 2026-08-07 | Always inside a `border` shorthand: `border:1px solid var(--border-hairline)` tokenises the colour and leaves the width as a number, and nobody notices because the colour looks done. 1,144 border readings across 10 screens and **every one renders 1px**, so this is one value in 145 places. Owner: step 3e. **Closed 2026-08-08.** 148 replacements, 0 raw border widths left. |
| ~~40~~ | ~~**1,738 of 5,004 controls accumulate their height instead of reading the ladder**~~ **CLOSED 2026-08-10**, `decisions.md` same date. Six families declared a rung: `.nav-row` **32.5 to 44** on 365, `.chip-lane` 40.5 to 44 on 339, `.chip-nav` 47 to 48 on 294, `.btn-bare` 24.5 to 28 on 72, `.chip-rail` 26 to 28 on 63. **`.nav-row` was two holes with one cause** and it also closes item 54: it stood 32.5 under BOTH pointers because `.nav-item` was not in the touch floor and nothing had declared a height, so there was nothing to raise. The family is the floor's fifteenth now, and the cost was measured first: the account panel 169 to 226, clear of a 900 viewport at both widths. **The three chips were each two controls depending on the pointer**, 40.5/44, 47/44 and 26/44. **The seventh was already right**: `.nav-slot` at 55 inside a 56 bar is the interior of a floored box, the bar carries the border, and declaring 56 on the slot moved the bar to 57 and took the rung off the box that had it. Tried, measured, put back, reason kept in the file. `.nav-row-stack` stays at 49, two lines of content above the floor. **One height per family, all on the ladder, 0 pages with horizontal scroll.** | `ui-kit/`, the control census, half closed 2026-08-09 | Owner: was Stage 09 (Design System) |
| ~~41~~ | `50%` is a sixth corner shape the ladder does not name, and 14 ladder-step literals | Design System step 3d, 2026-08-07 | `border-radius` is the most disciplined axis in the system, 100 per cent tokenised, and then `50%` appears **28 times**. On a square it is the same circle as `--radius-pill`; on a rectangle one is an ellipse and the other a stadium, and nothing says which is wanted. Separately: of 240 raw px in geometry properties, **81 are genuine layout dimensions** that should not be on a 4px ladder (a 120px column, a 560px sheet, the negative photo-bleed offsets) and **14 are ladder steps typed by hand**: 12px, 8px, 13px, 23px. One declaration writes `border-radius` with four identical corners. Owner: step 3e, except the 81, which belong to Responsive. **Closed 2026-08-08 in the half that was a value**: ten ladder-step literals are tokens. The `50%` question and the 81 layout dimensions stay open and are split out below. |
| ~~35~~ | **242 KB of the 373 KB font payload is the same file seven times** | Design System step 3c, `ui-kit/typography.html`, 2026-08-07 | Checksums: the four DM Sans latin faces are byte for byte one 36,980 byte file, the four latin-ext one 18,192 byte file, the three Space Grotesk latin faces one 22,320 byte file, the three latin-ext one 18,924 byte file. Both families were fetched as **variable fonts** and then copied once per declared weight. It renders correctly, and ink measurement proves it: DM Sans gives four distinct weights from the one file. **131 KB of unique typeface, 373 KB shipped.** The repair is one `@font-face` per family with `font-weight:400 700` as a range. Only IBM Plex Mono is right, because it was fetched as static faces. Owner: step 3e. **Closed 2026-08-08.** One `@font-face` per family over a weight range, 18 files and 373 KB to 8 files and 131 KB. Verified by counting painted pixels before and after: all twelve weight readings identical. |
| ~~36~~ | ~~`--weight-bold` is 700 and IBM Plex Mono has no 700 face~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and it was not a trap, it was live**. The row read the five mono DECLARATIONS in `components/` and concluded nothing asks for a weight that is missing. Font-weight inherits: an element that sets the mono family and no weight takes the body's 400, and there is no 400 face either. Measured over 160 documents: **1,041 mono elements in the painted tree compute 400 and 34 compute 700, against 310 at 500 and 262 at 600 - 1,075 of 1,647 asking for a face that is not there**. Nothing renders wrong and nothing is synthesised, because CSS font matching resolves 400 onto the 500 face and 700 onto the 600, which is what the original pixel counts showed. **So the 700 face is NOT added**: 33 KB for a step no screen can show is missing. What is fixed is the silence. `fonts.css` said `--weight-bold` was "documented as unavailable here", pointing at a token line that said nothing at all, and it also called two weights in four files "four static faces". Both corrected, and the limit now stands where the token is. The original row: | Design System step 3c, 2026-08-07 | Measured by counting painted pixels, because advance width cannot see a monospace weight: Plex renders **5380 / 5380 / 5993 / 5993** at 400, 500, 600, 700. Two faces, not four. Asking for bold renders semibold and says nothing. **A trap, not a live defect**: all five mono declarations in `components/` use `--weight-medium` or `--weight-semibold`. Either the 700 face is added or the limit is written down. Owner: step 3e |
| ~~37~~ | **400 has no token, and it is the most used weight in the product** | Design System step 3c, 2026-08-07 | The ramp names 500, 600 and 700. **192 of 260 text elements on the feed render at 400**, because it is the root default and nothing overrides it. `betpanel.css` already needed it and wrote `font-weight:normal`, the single weight literal in the system. A step a scale does not name is a step somebody types. Owner: step 3e. **Closed 2026-08-08.** `--weight-regular:400` declared and the one `font-weight:normal` in `betpanel.css` replaced. |
| ~~38~~ | Tracking is 13 values, 59 declarations and zero tokens | Design System step 3c, 2026-08-07 | Family, size, weight and leading are all tokenised and held. Letter spacing is not: `-.03` `-.02` `-.01` `0` `normal` `.01` `.03` `.04` `.05` `.06` `.07` `.08` `.1`, including **two spellings of nothing**. Three groups are visible in the data (tighten for display, neutral, open for small uppercase) and three or four tokens would hold all 59. Same shape as the padding and height findings, on the axis the census did not count. Also here: `chart.css` has the system's only `font-size` literal, `7px` on an SVG axis label, which is defensible in a scaled coordinate space and undocumented. Owner: step 3e. **Closed 2026-08-08.** Eight tracking tokens; 61 declarations tokenised, 14 values moved, none by more than .02em, 0 text clipped or wrapped over 40 renders. The 7px in `chart.css` is documented rather than changed. |
| ~~32~~ | ~~`--icon-brass` in daylight has two hundredths of headroom~~ **CLOSED 2026-08-10 as a constraint written where the ramp is**, `decisions.md` same date, **and the row had the direction backwards**. It wrote "any card ground made one step lighter puts it under", and a lighter stone RAISES the ratio: it is a deeper stone that kills it, and the chalk ramp numbers its deeper steps LOWER, which is where the wrong word came from. Computed against every step of the ramp: **3.43 at chalk-940, 3.40 at 930 (the page), 3.31 at 920, 3.28 at 910 (the plate), 3.20 at 900 (the card it stands on), 3.14 at 880, 3.06 at 870 and exactly 3.00 at chalk-860**. So there are three steps of headroom under it and the eighth lands on the floor. Measured in the browser against real composited grounds it is **4 placements in daylight, all the saved bookmark on a card, all at 3.20**. Nothing else in the system is within 0.5 of a floor. The table and the direction are in `tokens.css` beside the chalk surface ramp now, which is what the row asked for. The original row: | Design System step 3b, `ui-kit/colour.html`, 2026-08-07 | **3.20:1 on `--bg-card`**, 3.28 on the plate, 3.40 on the page. The floor for a graphical object is 3:1, so it passes, and it is **the only role in the system with nowhere left to go**: nothing else measured is within 0.5 of a floor. It is the saved bookmark and the active mark. Any card ground made one step lighter puts it under, so the constraint belongs written down beside the surface ramp and not only here. Owner: step 3e |
| ~~33~~ | ~~**The brass ink ladder has four rungs in the Vault and one in daylight**~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and it is not a ladder**. The row offered two answers, a real ladder in both themes or one role with three aliases, and the measurement gives a third. Re-read against the REAL composited ground on all 106 painted screens in both themes rather than against a page swatch: in graphite the four resolve to four values and stand at **5.70 to 8.98, 11.13 to 11.33, 9.86 to 11.79 and 10.08**; in chalk they resolve to ONE value on **704 placements, worst 5.24, best 7.40**. The graphite steps are not a hierarchy of emphasis, they are the same ink compensating for four different grounds - a brass-tinted chip, a photograph under a veil, a plate, bare stone - and on the pale stone one value clears every one of them with 0.74 to spare over the 4.5 floor. **Two roles may share a value as long as each says so**, which is the rule 27 other groups in `tokens.css` already live under, and it is now written at the chalk block with the numbers. **The fourth name is deleted**: `--text-brass-vol` had ONE placement in the whole product, `.hf-tag.vol`, and in daylight it was byte-identical to the other three; in graphite it was 10.08 against the eyebrow's 11.13 on the same hero plate, a step of 1.05 nobody chose. The tag takes `--text-brass-lit`, the role its own plate already uses, measured after: rgb(216,191,127) to rgb(230,200,119) in graphite and unchanged in daylight. Also removed from `ui-kit/colour.html` (two matrix rows, ten cells) and `_page.css`. The original row: | Design System step 3b, 2026-08-07 | `--text-brass`, `--text-brass-lit`, `--text-brass-chip` and `--text-brass-vol` measure 8.98, 11.68, 13.20 and 10.58 against `--bg-page` in graphite, and **all four measure 7.40 in chalk**. Three roles resolve to the fourth's value, so the hero eyebrow, the active chip label and the volume tag are all just the link colour in daylight. Either the ladder is real in both themes or it is one role with three aliases; both are defensible and neither is what the file says now. Owner: step 3e |
| ~~34~~ | Two roles stand on an inverting ground and are declared once | Design System step 3b, 2026-08-07 | 40 of 133 roles have no daylight value. **37 are correct**: brass is the brand and does not invert, an outcome solid is a coloured object, the ink on a constant ground must be constant too, a photograph is not a theme surface, and 8 are course chrome that should leave `tokens.css` entirely. **Two are candidates for a real hole**: `--control-knob`, the dot inside the switch, and `--line-brass-strong`, an edge. Both stand on a ground that inverts. Also: the rule in `components/CLAUDE.md` reads as "every role in both themes" and what it means is narrower, **a role whose GROUND inverts must be declared for both**, so the wording is the other half of this item. **Closed 2026-08-08 as NOT A DEFECT**, by reading where each role is used rather than how it is declared. `--control-knob` is the dot in the switch and the switch's ON ground is `--color-action`, which does not theme, so white is right in both. `--line-brass-strong` is `--brass-a60`, and an alpha over a theming ground is theme-aware by construction. All forty single-value roles are correct. `colour.html` corrected. See `ui-kit/docs/consolidation.md`. |
| ~~29~~ | ~~The icon stroke is a constant in user units, so it renders at six different weights~~ **CLOSED 2026-08-10**, `decisions.md` same date. **The row was right and its numbers were stale**, because `.ic` went 1.6 to 2.2 after it was written and nobody re-read it. Re-measured over **2,044 stroked marks**, at two widths, against the svg's CONTENT box rather than its border box, and **split by tree, which is the part that matters**: in the product, **1,818 marks at four weights, 1.20 at 12 on 550, 1.47 at 16 on 34, 1.65 at 18 on 1,056 and 2.02 at 22 on 178 - 1.68 to 1 between the ends**. The extremes the row would have been quoted for are both in the KIT: a **3.67 slab** on a 40px demonstration figure, and **0.92 on six trustbar specimens** that had no declaration at all and took the SVG default of 1, under one device pixel at DPR 1. The kit's own icons page had already worked out the 1.68 and said so; this pass did not read it first, and the first draft of every note written here blamed the product for the stand's two extremes. The fix is one token and one property: **`--stroke-mark:1.65` paired with `vector-effect:non-scaling-stroke`**, so the declared number is the rendered number and the weight stops depending on the box. Four files owned a stroked mark and each declares the token now; two of them were hand-computing the house weight in user units and one had never declared anything at all. After: **all 1,818 product marks render at exactly 1.65, ratio 1.0**, and 2,018 of 2,044 across both trees, verified against the PAINT rather than the formula - a straight bar screenshotted at device scale 10 and summed by pixel coverage gives 1.65px at boxes 12, 18, 22 and 40. The other 26 are `ui-kit/icons.html`'s blow-up specimens at 70 and 94px, which are the one deliberate exclusion: a drawing shown at four times its size wants its stroke at four times too, and `.tk-glyph` carries no `.ic`, so the rule never reaches it. **And the instrument had to be read twice**: the first census computed `declared x box / 24`, which is the right formula for a scaling stroke and the wrong one after the fix, so it reported the set had got THINNER. The original row: | Design System step 3a, `ui-kit/icons.html`, 2026-08-07 | `.ic` declares `stroke-width:1.6` once, inside a 24 unit box, and user units scale with the box. Measured on screen: **0.90px at 12, 1.07 at 16, 1.20 at 18, 1.33 at 20, 1.47 at 22, 2.67 at 40**. A factor of three, wrong at both ends: 0.90 is under one device pixel at DPR 1 and smears, 2.67 is heavier than the type beside it. **The set has no optical weight, it has one geometric weight that slides.** `.ic-sm` is the worst case and should go: smallest box, thickest declared stroke. Owner: step 3e | **RE-MEASURED 2026-08-09 and still open, and the note written earlier that day was wrong.** It said the ratio was untouched and that every figure could be multiplied by 1.375. **Read on all 106 painted screens, 998 stroked placements of 2,399 svg: 1.20px at 12 (266), 1.47 at 16 (17), 1.65 at 18 (528), 2.02 at 22 (178), plus 9 market chevrons at 16 painting 1.20. Four boxes, not six, and the ratio is 1.68 to 1, not 2.98.** Two things moved that a multiplier cannot carry: `.ic-sm` went 1.8 to 2.4, a different factor from `.ic`'s, and **nothing strokes at 20 or 40 any more**, so the two boxes that were the extremes left the set. The sharp end of the argument went with them: 1.20px is not under one device pixel. **And the constant is three constants**, `.ic` at 2.2, `.ic-sm` at 2.4 and `market.css` writing its own 1.8 for the chevron, and `toast.css` re-declares both the box and the stroke of `.ic-sm` on four placements. Owner unchanged.
| ~~30~~ | ~~The stroked family has four safe fields and the filled family has one~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and the row named one mark where there are two, because its number was the reading at one box of four.** **A safe field stopped being a property of the drawing the day `--stroke-mark` arrived.** While the stroke was 2.2 USER UNITS it scaled with the box, so half of it was 1.10 modules at every size and a mark had ONE field. `vector-effect:non-scaling-stroke` makes 1.65 a SCREEN width, so half of it is 0.825px, which is **1.65 modules in a 12px box and 0.90 in a 22px one**: the same drawing has a different field at every size it stands in. Measured by INK, painted at device scale 10 and summed by opaque pixel over the eleven stroked marks in the product, at 12 / 16 / 18 / 22: close and chevron **4.20 / 4.65 / 4.80 / 5.02**; envelope, large close, arrow-right, the two note marks and plus **3.20 / 3.75 / 3.87 / 4.04**; tick **2.20 / 2.70 / 2.80 / 3.05**; **menu and send 1.20 / 1.65 / 1.87 / 2.07**. **Two marks fall inside the rule and only below 22px**, and they fall together because both paths sit 3 modules from the edge: the menu's three bars run x 3 to 21 and the send arrow's tip touches x 21. The row read 1.9 for the menu, which is the 18px column, and it was blind to the second mark. **THE RULE IS RESTATED ON THE PATH RATHER THAN ON THE INK**, because the path is what a person draws and the overhang is arithmetic anybody can redo: a mark's geometry keeps 2 modules clear of the cell and the ink then overhangs it by 0.825px whatever the box. **All eleven clear that, the smallest at 3, so nothing is redrawn.** The table is in `tokens.css` beside the token that caused it. What it leaves is a different question and it is named rather than answered here: a 12px mark carries the same 1.65px of ink as a 22px one, so it reads heavier and stands closer to its own edge, and whether a 12px stroked mark should exist at all is optical sizing's. The original row: | Design System step 3a, 2026-08-07 | Measured against the **paint**, not the curve. Stroked, 33 glyphs: field 2.2 on 15, 3.2 on 10, 4.2 on 6, 5.2 on 2. Filled, 15 glyphs: 2.0 on 13. Exactly one owned glyph is INSIDE the field, `i-cat-politics` at 1.0, so this is not a violation to fix but **a rule that was never set**. The chevron, second most used glyph in the product, paints 13.6 x 7.6 in a 24 cell. Three glyphs are also off centre by 1.7 to 2.0 modules: `sort`, `trend-up`, `music`. Owner: step 3e | **RE-MEASURED 2026-08-09 against the consolidated set, and it is a much smaller row.** The numbers above are the hand-drawn set's and the set is 35 glyphs now, not 48. **Line, 6 marks: 4.9 on two, 3.9 on two, 2.9 on one, 1.9 on the menu**, so one mark is inside the field by a tenth and the chevron's 5.2 is gone. **Filled, 20 by ink: 2.0 on 17, 3.0 on one, 3.3 on two, and 0 inside the rule** after the clock was re-read and the magnifier rescaled on 2026-08-09, row 70. **The one glyph still inside the field is `i-cat-politics` at 1.0**, a category mark rather than a control mark, which is what this row said in 2026-08-07 and is the only part of it that has not moved. **The three off-centre drawings are gone with the redraw**: 19 of 21 filled glyphs sit at 0.0 / 0.0 and the other two are off by 0.2 and 0.3. What is left of this row is the menu's tenth and the question of whether the line family is redrawn to 2 at all. |
| ~~31~~ | ~~Two icon families, and four jobs are drawn in both~~ | Design System step 3a, 2026-08-07 | **CLOSED 2026-08-09, and the decision taken was FILLED, which turned out not to be available for all of it.** `decisions.md` same date. 33 stroked marks were read against Solar Bold and **six have no filled form at all**: a cross, a chevron, a plus, a tick and a hamburger are movements, not things, so there is nothing to fill, and what a filled set offers instead is a disc or a plate with the mark knocked out. A disc inside a round icon button is a disc inside a disc. **The rule is: an object is filled, a movement is a line, and WEIGHT holds them together**: 21 filled glyphs over 1,517 placements, 6 line marks over 990, stroke 1.6 to 2.2 at 22px and 1.8 to 2.4 at 12px. **52 glyphs to 35.** **Two of the four pairs this row named were logos**, the X mark and Discord, filed as duplicates by a census that reads path data; brand marks keep their own drawing and take no system ink. The other half of the defect was **ink**: nine `:has(use)` rules existed and three disagreed with the stroke beside them, so the header's bookmark shipped at two different inks for one job. Fifteen rules name both properties now. Proof: 5,064 icon readings over 40 screens at both widths in both themes, **0 boxes moved**, and every remaining ink difference is one of three named levellings |
| ~~76~~ | ~~`.amount-input` has no ground and no ink of its own~~ **CLOSED 2026-08-10 AS ALREADY ANSWERED, and the answer was the unscoping pass rather than an edit for this row.** The row was written when every rule in `input.css` was scoped to `dialog.app-dialog` or to `.bet-panel` / `.bet-sheet`, so an unscoped field took its ground and its ink from the User Agent: a white box with black text in the dark theme. `.app-case` came off 415 selectors on 2026-08-08 and this rule came with it. **Verified rather than assumed**, by rendering the atom outside every dialog and every panel in both themes: graphite gives ground `rgb(13,15,18)` and ink `rgb(237,231,218)`, daylight gives `rgb(253,251,245)` and `rgb(33,31,25)`, and the box is 44 tall in both, so the touch floor reaches it too. **The bet face is transparent on purpose** and that is not the same defect: `.amount-input.amount-bet` declares `border:0` with a 1.5px bottom rule and `background:transparent`, which is a face somebody chose. A row that was true when it was written can be closed by a pass that was aimed at something else, and the only way to know is to render the thing again. **(filed as 25 until 2026-08-09, and 25 was already taken)**** | Design System step 2.5, the vitrine, 2026-08-07 | **Found by rendering the atom outside its scope for the first time.** Every rule in `input.css` is scoped to `dialog.app-dialog` or to `.bet-panel` / `.bet-sheet`. Unscoped, the field gets a hairline, 8px of padding and an 18px size, and takes its background and its colour from the User Agent: **a white box with black text, in the dark theme**. Invisible on all 41 anchor screens, because on every one of them the field stands inside a dialog or a panel. It is `a missing value is a value`, the same trap that cost the 992 blue links. Owner: consolidation, step 3 |
| ~~26~~ | ~~The outcome pair's halves are told apart by DOM ORDER~~ **CLOSED 2026-08-10**, `decisions.md` same date. Sixteen selectors in `yesno.css` bound the outcome semantics to position, `:first-of-type` green and `:last-of-type` red, on the control this product is named after: **this system's one rule that decides others was being stored in the order of two siblings**. `.yes` and `.no` go on the control now, which is the idiom `hero.css` already uses for the same two meanings on `.hf-tag`. **778 controls tagged across 226 documents in three trees**, and every selector kept its specificity exactly - `.yesno > a:first-of-type button` and `.yesno > a > button.yes` are both (0,2,2) - so no cascade tie moved. Measured before and after, **1,504 readings in both themes: 0 colour differences**. **The first pass left 16 controls unpainted** and the before-and-after is what caught it: the hero's call to action reads `class="hf-cta yesno"` and the sweep only matched a class attribute STARTING with yesno, so those buttons lost the positional rule without gaining the class. The row's own test, run afterwards: reverse the pair in the DOM and "Back YES" stays green while "Back NO" stays red. Skeleton placeholders keep no side, because a grey box has no outcome. | Design System step 2, the inventory, 2026-08-07 | `.yesno > a:first-of-type button` and `:last-of-type`. Move the two buttons and green becomes red, on 81 readings of the control this product is named after. The vitrine carries the reversed pair as a labelled specimen so it can be seen rather than argued about. Owner: consolidation, step 3 |
| ~~12~~ | ~~Live odds-delta animation~~ | `/impeccable critique` P3, 2026-07-16 | **Closed as NEVER, 2026-08-15, by the rule the stage is built on.** A movement needs a moment and a moment needs a change. Every odds figure in this product is typed into the markup or written once by a page script on load, and it does not change again in any of the 105 documents: there is no delta, so there is nothing to animate and no row of the inventory could be written for it. It returns the day the figure moves, which is a data decision and not a motion one |
| ~~13~~ | ~~Error state vs empty state are not differentiated~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and the row was two rows.** Surveyed first, then measured: `.state-block` stands on **38 painted screens, 15 empty and 20 an error**, and the two differ in the MARK, the HEADING and the ACTION LABEL and **in nothing else** - same ground, same edge, same radius, same padding, same brass mark ink, same title and body ink. **The words already carry the whole difference** and `voice/docs/microcopy.md` sets it as a rule: every empty reads "No ... yet", every error "Couldn't ...". **(a) THE SILENT HALF WAS FREE AND WAS THE LITERAL ROW**: 30 of the 38 blocks carried no `role` at all, so for a screen reader the two situations really were one block. `role="alert"` on the 21 errors and `role="status" aria-live="polite"` on the 16 empties, in both trees, matching what the 8 that already had one were doing. **(b) THE FACE IS THE ERROR TOAST'S, VERBATIM**: `.state-block.state-problem` takes `--bg-control`, `--border-notice` and `--bevel-notice`, with the mark and the message at `--text-primary` and the message semibold. `--border-notice` is declared as "the neutralised error toast, warm grey, **never red**", which is the only way to raise an error in a product whose one rule is that red means NO. No new token, no new colour. **It is written AFTER `.cat-main .state-block`**, which takes the box away so an empty feed reads as an empty page: both are (0,2,0) and source order decides, so an error is the one case where the box comes back. **An error is an object and an empty is an absence.** Measured after, both themes: 21 problem blocks with ground rgb(36,40,47) and edge rgb(90,84,74) in graphite, rgb(252,250,244) and rgb(172,170,164) in daylight; the message goes from 6.85 to **12.01** in graphite and 7.41 to **15.78** in daylight; 17 empties unchanged and boxless; roles 42 alert, 32 status, 2 left alone (`event-detail-resolved`, which is neither). Overflow 0, page errors 0 over 264 documents. **The survey's own counter-argument was answered rather than ignored**: it said a ground modifier reintroduces the card exactly where errors mostly happen, and that is the intent, not the cost. | `/impeccable critique` P3, 2026-07-16 | Two different situations reading as one block |
| ~~14~~ | ~~The undeclared second alpha ladder~~ **CLOSED 2026-08-10 BY COUNTING AGAIN**, `decisions.md` same date, **and the ladder it named is gone.** The row said 20 declarations build a colour with `color-mix(in oklab, var(--color-action) N%, ...)` at 16 percentages. Counted today across `components/` and `components/patterns/`: **0 of them end in `,transparent)`**. Every one is a `--tint-brass-*` token now, six rungs, and the note beside `--tint-brass-16` records the three copies that were hidden by their own spelling. **What is left is 34 mixes and 28 of them end in a SOLID** - `--bg-control` 10, `--border-hairline` 7, `--bg-pressed` 4, `--bg-surface` 3, `--color-action` 2, `--text-primary` 1, `--control-knob` 1 - and **an alpha and a blend are not the same thing**. An alpha is one ink at a depth and belongs on a ladder, because the ground under it is whatever the element happens to stand on. A blend IS a ground: brass mixed into `--bg-control` at 11 per cent is a tinted control, and the number that is right depends on the surface being tinted, so a shared ladder would be a rung fitted to one surface and applied to another. **The six that do end in transparent are three other roles and none is a ladder**: `--scrim-photo` at 34/55/72 is one gradient over a photograph, `--outcome-yes` at 55 is the odds bar's fade written twice at one value, `--result-won` at 42 is a single dialog head. Three values inside one gradient are not steps a reader has to choose between. Written beside the tint block in `tokens.css`. The original row: | Stage 09 step 7c, 2026-07-28 | 20 declarations build a colour with `color-mix(in oklab, var(--color-action) N%, ...)` at 16 different percentages, beside the declared `--brass-a*` one. Gate 13 is satisfied (all read a role). Recorded as a decision, not fixed: which steps that ladder should have is a **states** question, and rounding them now would move hover and selected states for the legibility of the file rather than of the product. Stage 09 owns it |
| ~~21~~ | ~~Three focus rings that survive the cascade but not the contrast floor~~ | Design System step 1, 2026-08-02 | **Closed 2026-08-02, and one of the three never existed.** `.theme-switch-inline` was real (2.03:1 in daylight, a fixed chrome brass on a ground that flips) and is fixed by the rule that a ring answers to what it STANDS ON: 8.98:1 and 7.46:1 now. `.kit-field` in the frozen `kit.html` was real (no ring at all, a 16 per cent wash in its place) and is fixed by deleting one declaration, since the freeze forbids growth and not repair: 8.71:1 and 7.14:1 now, and the dead copy of that rule in `ui-kit/_specimen.css` went with it. **`.state-btn` on the push banner was not a defect.** The 2.72:1 came from a sweep that read the ground with a regex, and a regex reads the components of `color-mix(in oklab, ...)` as sRGB bytes, so a pale brass-tinted banner measured as near black. Measured with the browser parsing the colour: 6.95:1 and 6.73:1. See `decisions.md`, same date. Sweep now: **0 of 179 kinds flagged over 153 pages in both themes** |

| ~~119~~ | ~~**1440px stands in all 104 grey files and is on no ladder, and the derivation that named its twin does not transfer**~~ **CLOSED 2026-08-13. It derives now: 1170, which is `900 + 250 + 20`.** The row was the arithmetic and not the number, and the arithmetic is its twin's: 1140 is the RAIL rung plus the review sidebar plus its inset, and the grey screen-tree rail is 250px. 1440 was **270px of window that no rung, no rail and no gutter asked for**. Moved in all 104 grey files, **312 occurrences, three per file**, because a grey file writes the number in its media query, in its drawer script and in the sentence above it, which is also the reason a grey file cannot hold a token. 0 occurrences of 1440 are left in `wireframes/`. **A harness width that does not derive is a preference wearing a harness's name.** | `wireframes/`, 2026-08-12, found by re-reading the width table in `components/tokens.css` after backlog 116 closed | The table at `tokens.css` page frame already carries the finding and calls it "the responsive stage's own backlog": this is the row it is pointing at. Counted 2026-08-12: **312 occurrences of `1440` across all 104 grey files, and 0 in the 106 painted screens, 0 in `ui-kit/_page.css` and 0 in any declaration in `components/`**, where the twenty hits are all prose. It is the grey HARNESS, the same job 1140 does for the review sidebar: `wireframes/event-feed.html` docks the screen-tree rail as a fixed column at `min-width:1440px` and hides it below. **The row is the arithmetic, not the number.** 1140 is `900 + 220 + 20`, the RAIL rung plus the review sidebar plus its inset, which is why it is a harness and not an invention. The grey rail measures **250px** in the same file, so the same derivation gives **1170**, and 1440 is 270px of window that no rung, no rail and no gutter asks for. Backlog 116 was titled "three widths" and closed 2026-08-12 having deleted two of them, 960 and 1280, because those hard-coded a column count; this third one was left standing because a harness is not a column count, and nobody then checked whether the harness derives. Either it derives from the rail like its twin, or it is named in the ladder as a second harness with the 250 written beside it. Owner: Responsive step 4 |
| ~~120~~ | ~~**Six brass blends tint the same base at five different percentages, and closed row 14 decided they are not a ladder without deciding they are not one decision**~~ **CLOSED 2026-08-13. One role per job, `--bg-control-brass` and `--border-hairline-brass`.** Resolved in a browser in both themes, **three of the five distinctions do not exist**: 15 and 16 are 0.0045 apart in oklab lightness, the two 20s are the same number, and **the 42 edge and the 30 edge are 0.001 apart in DAYLIGHT**, so the one value that reads as a deliberate emphasis is invisible on half the product. Counted by placement, **568 of 576 already agreed at 20 per cent**. It can be a role because row 14's objection does not reach it: all six end in the SAME base and only the amount varied. Declared in both theme blocks, because a theme here is an attribute on any element and a custom property holding a `var()` resolves against the element that declares it; 0 mismatches over 57 kit pages. **And the 11 per cent was reported as having no placement and has six**: `.gallery .card` sits inside the Past wins tab panel, and a sweep that walks a page at load sees the tab that is open. | `components/`, 2026-08-12 | **Row 14 already counted these and it was right about what they are.** It found 28 `color-mix` declarations ending in a solid rather than in `transparent`, and it argued, correctly, that an alpha and a blend are different things: an alpha is one ink at a depth and belongs on a ladder, a blend IS a ground, so a shared rung fitted to one surface would be applied to another. That is why `--tint-brass-06/09/16/30/45/60` cannot hold them and why this is not a repeat of that row. **What row 14 did not ask is whether the blends agree with EACH OTHER.** Ten of the 28 are brass, in seven files, and they fall into three jobs: brass into `--bg-control`, which is a tinted control ground, at **11 (`card.css`), 15 (`position.css`), 16 (`hiw.css`), 20 (`comments.css`), 20 (`iconbtn.css`) and 22 (`hero.css`)**; brass into `--border-hairline`, a tinted edge, at **30 (`hero.css`), 30 (`hiw.css`) and 42 (`position.css`)**; and `--color-action-lit` into `--color-action` at **60 (`platehead.css`)**, which is one gradient's own step and stands alone. **Six placements of one blend on one base at five values, and no file says why its own number differs from its neighbour's.** Two of the three edges agree at 30 and the third is 12 points away. The answer is not a token: it is either one value per job written down once, or a sentence in each file saying what its surface needed that the others did not. Owner: the states pass |
| ~~121~~ | ~~**On 468 controls the only drawn boundary is `--border-hairline`, and it measures 1.23:1 against what it stands on**~~ **CLOSED 2026-08-12, AND WHAT WAS REPAINTED IS THREE CONTROLS, NOT 468.** The row's reading is true and it is not the criterion: 1.4.11 asks for 3:1 from the visual information **required to identify** the component, and a chip, a button and a menu each carry a word or a mark that identifies them, so their edge is decoration. Sampled from the RENDER at device pixel ratio 3, taking the modal colour of a control's box as its face and the colour furthest from it in luminance as its ink: **no icon-only control in this product is under 3:1 in either theme.** `.icon-btn-lift` 8.44 and 5.38, base `.icon-btn` 9.43 and 5.19, `.icon-btn-tile` 5.49 and 5.49. Every labelled control is carried by its label, and this repository's own audit already measured **0 text contrast failures over 29,929 and 29,984 elements per pass**. **The one exception is a field, and it is the criterion's own textbook case**: `.amount-input`, 3 placements, measured **1.32 in graphite and 2.11 in daylight**, because a field carries the PERSON'S text and the person's text says nothing about the box, so the edge is the only thing on the screen that says typing is possible here. It reads `--border-field` now, a SECOND role rather than a stronger hairline, and the distinction is the row's own question answered: a hairline separates two areas and 1.4.11 does not reach it, and raising it would have repainted every plate, card and divider in the product to fix three fields. **6.35 and 4.32 after, hairline unchanged at `#2b2f38` and `#acaaa4`.** `decisions.md`, "Four instruments failed their own control" | `ui-visual/`, 2026-08-12, WCAG 2.2 SC 1.4.11 non-text contrast | Read in a browser on `event-detail-multi.html` in both themes rather than off the token table, because the question is what a boundary measures against the surface it actually draws on. **`.filter-menu summary` is the strongest case and it is unambiguous**: its declared fill is `rgb(28,31,36)` and the ground behind it is `rgb(28,31,36)`, **the same colour, 1.00:1**, so the 1px `--border-hairline` at `rgb(43,47,56)` is the ONLY thing that says a control is there, and it measures **1.23:1 in graphite and 2.15:1 in daylight** against a 3:1 floor. **193 menus on 105 of the 106 painted screens.** **AND THE ROW'S OWN CENSUS WAS TAKEN ON ONE SCREEN AND IS WRONG IN BOTH DIRECTIONS, re-measured 2026-08-12 over all 105 screens in both themes with every ground composited: the population is 1,913 controls under 3:1 in graphite and 968 in daylight, not 468.** The largest class is `.chip`, **641 on 60 screens**, which this row excluded on the grounds that its edge needs compositing to read; composited, it reads 1.00 to 2.80 in graphite and 1.00 to 2.15 in daylight and belongs here. `.icon-btn` is 605 and not 1,361, because on the feed it computes `border-width:0` and draws no boundary at all, which 1.4.11 exempts. `summary` is 339 in graphite and 146 in daylight; `.btn` 182 and 96; `.no` 116 at 2.14 to 2.74. **What the correction does NOT change is the decision the row was filed to ask for**, and it makes it larger: clearing 3:1 on a graphite ground needs an edge near `rgb(105,105,105)` against today's `rgb(43,47,56)`, which is not a hairline raised a step, it is a visible edge on 1,913 controls and a different Vault. The same shape, same hairline, same two readings: `.icon-btn` (1,361 instances on 105 screens) and `.btn.btn-ghost` (105 on 105), both with a fully transparent fill, so the hairline is again the whole boundary; `.btn.btn-secondary` (509 on 105) is the softest case at 1.38:1 because its fill is 1.25:1 clear of the ground and does part of the work. **What is explicitly NOT in this row**: a control that draws no boundary at all, which 1.4.11 exempts, and `.chip-nav` (294 on 57 screens), whose edge is a white alpha over its own fill and needs compositing to read rather than the naive ratio a sweep returns. This is not a token change: `--border-hairline` is the system's one hairline and raising it repaints every plate in the product. It is a decision about whether a boundary is the ONLY boundary. Owner: the states pass |
| ~~122~~ | ~~**Three touch targets under the 44px floor, and each one is a different hole in how the floor is written**~~ **CLOSED 2026-08-12, AND THE ROW HAD COUNTED THREE OF 2,904.** Re-swept at 390 with the pointer asserted coarse over all 105 screens: **2,904 controls under 44 x 44**, of which 569 are the four `.icon-btn-*` exclusions `base.css` names on purpose and 2,309 are the footer, which the row never mentioned. **What it named was right and what it left out was the population.** Split by the criterion that is actually binding rather than by the project's own stricter one: **WCAG 2.5.8 AA went from 843 failures to 0.** `.footer-col a` was **36.5 x 14 at 20.5 centre to centre**, so it failed the 24 x 24 minimum AND the spacing escape that excuses a small target, 1,154 links on 105 screens, and it now measures **36 x 25 at 28.5** from `padding-block:var(--space-4)` in `footer.css`, costing 88px of footer at 390 and 33px at 1280. `.bp-change`, `.sys-link-list a` and `.related-list a` joined the coarse family in `base.css`. **The one reading that still fails is exempt and the probe did not know it**: `sign-in-error.html` has `Privacy Policy` at 69.2 x 15.4 inside the sentence "By continuing you agree to the Terms and Privacy Policy", and 2.5.8 carries an explicit Inline exception for a target in a sentence. `.popular-links a` and `.legal-links a` pass on spacing and miss the project's own 44, which is **137**. `decisions.md`, "A floor is a rule with a precondition" | `ui-visual/`, 2026-08-12, measured in a browser | The floor in `base.css` is declared once for the family, which is right, and it reaches a control only by NAME. Three things are therefore outside it and none of them is an exception anybody chose. **(a) `a.bp-change` measures 35.2 x 15** on `event-detail-multi.html` and `event-detail-logged-out-multi.html`, 4 placements, and it is in none of `base.css`'s coarse-pointer family lists. It is the smallest genuine target in the product and it is the control that takes a person back to the outcome they are changing. **(b) `404.html` has 7 recovery links at 21.5px tall**, bare `<a>` with no class at all (Home, How it works, Politics, Crypto, Culture, General, View all events), and on a dead end they are the only route off it; the one control on that page that clears the floor is the brass `Browse events`, which is a `.btn`. **(c) `terms.html` has 4 `<a href="#">` at 43.5px**, half a pixel under, and half a pixel is the whole point: **`base.css` names families and not tags**, so a control that is styled by its element gets no floor, which is the same defect `docs/kit-archive/` row 76 recorded for `.ed-actions button` one layer up. Fixing (a) is a family; fixing (b) and (c) is the question of whether a bare `<a>` in running prose is a family at all. Owner: the states pass |
| ~~123~~ | ~~**`ui-visual/event-feed.html` renders an `<h3>` before the page `<h1>`**~~ **CLOSED 2026-08-12, and NOT by a level.** An `<h2>` would still stand before the `<h1>` and would still open the outline at the second level, and the `<h1>` cannot move because it is the feed's own heading and the hero is physically above it. So the label stopped being a section and became what it is, the NAME OF THE LIST under it: `<p class="hh-title">` with `aria-labelledby` on the `<ol>`, in BOTH trees, and the face did not move. **The grey tree needed one extra edit the paint did not**, because it styles the heading by TAG in its own inline block, so the type had to be re-declared for the new element or the label would have lost its face in the tree that owns structure | `ui-visual/event-feed.html`, 2026-08-12 | The document order is `<h3>Hot right now`, then `<h1>Trending`, then eleven `<h2>`. One screen, both widths, both themes, and it renders correctly to the eye because the `<h3>` sits inside the hero column beside the heading rather than under it. **A heading level is the document outline and not a type size**, so a screen reader walking the page is told the first thing on it is a third-level section of a section that has not started. The fix is a level and not a face: the type is `hero.css`'s and does not have to move with it. Owner: the states pass |
| ~~124~~ | ~~**`.device{min-height:100vh}` is the one full-page shell and the only place that never got the `svh` reasoning**~~ **CLOSED 2026-08-13. Both declarations written, the fallback then the fix, with the argument beside them.** The row was right that the file wrapping all 106 painted screens was the one that never had the sentence three smaller files already wrote. **And it is the only fix in this backlog that no instrument here can see**: headless Chromium has no retracting chrome, so `svh`, `lvh` and `vh` are one number and this edit measures as zero on every sweep in the repository. That is exactly why the argument goes in the file rather than staying a value. | `components/base.css`, 2026-08-12 | Three files in this system already write the argument down: `betpanel.css` twice, `toc.css` three times and `catnav.css` twice all name `svh` and say why, because on a mobile browser `vh` is the LARGEST viewport, the one with the URL bar retracted, so a box sized in `vh` is taller than the window it stands in until the person scrolls. `base.css` mentions `svh` **0 times** and declares `min-height:100vh` on `.device`, which is the shell every one of the 106 painted screens stands in. It renders acceptably because `min-height` only ever adds room, so the cost is a page that can always be scrolled by the height of the retracting chrome rather than a clipped one. **This is the row for the reasoning and not only for the value**: the three files that discussed it are the small ones and the one that never did is the one that wraps them all. Owner: Responsive step 4 |
| ~~125~~ | ~~**Six responsive concerns the stage never considered, and each one is a zero rather than a wrong number**~~ **CLOSED 2026-08-13: TWO WERE DEFECTS AND FOUR WERE ALREADY ANSWERED.** **(a) PRINT, fixed** by `components/print.css`, imported last. Rendered at A4 with the medium emulated: the sticky header printed at 794x59 still sticky, the trust strip at **714x183 of decorative photography**, four elements still sticky or fixed, and **the largest thing on the sheet was the review chrome at 220x1123, which the row did not name**; the bottom nav, which it did, does not print at 794 wide. After: 0 sticky, ink #111 on white, `terms.html` 5,756px to 5,482. **The palette was written twice**, because the first one listed four ground tokens and the plates are gradients on four others: a list of grounds is a list, `*` is a rule. **(e) THE LONG TOKEN, fixed** with `overflow-wrap:break-word` on the frame plus a `<code>` exception. A 42-character address grew the document **107px at 320**; `anywhere` was written first and moved **23 kit pages of 269**, four changing height, including pages printing no long token at all, because it resizes tracks rather than breaking words. `break-word`: **259 of 269 identical, 0 size changes, every differing pixel at the instrument's own floor**, proved by shooting the same tree twice. **(c) ORIENTATION IS NOT THE AXIS**: 844x390 gives 86 clipped boxes and 844x900 gives **86**, so it is a width fact, and all 86 are a `.card` losing 2px to the bookmark button's 44px floor. **(b)** the height half of 1.4.10 reads **44.9% at 320x256 with 0 h-scroll**, so it passes; worst screen is `404.html`, not the one named. **(d)** the row re-ran its own scrollbar-less sweep at 280 to 310 and found 0. **(f)** `srcset` was answered by backlog 99 the same day: the box does not change with the viewport, so one correct asset shipped instead of a set. `docs/decisions.md`. | `components/`, `ui-visual/`, `wireframes/`, `ui-kit/`, 2026-08-12 | Counted across all four corpora, and every count is 0, which is why none of them was ever a finding: nothing measures what is not there. **(a) `@media print` is 0 everywhere**, while `terms.html`, `how-it-works.html` and the wallet history are the three documents in this product a person prints, and printing them today prints the sticky header, the bottom nav and the footer trust strip. **(b) The HEIGHT half of WCAG 1.4.10 was never read**: at 320 x 256 on `event-feed.html` the sticky chrome is **115px of 256, 44.9 per cent**, header 59 and bottom nav 56, with 0 clipped and 0 horizontal scroll, so it passes and nobody had looked. **(c) 0 orientation queries**, so a landscape phone runs the DESK and DETAIL rungs, which were reasoned from a wide window, against a viewport 390px tall. **(d) The instrument had no scrollbar**: a headless render draws no classic scrollbar, so every "320" in the audit was 320 of content against a real 305, and re-running the narrow sweep at 280 to 310 found 0. **(e) `overflow-wrap` is 0 in `components/`**, along with `word-break` and `hyphens`; the longest unbroken token the painted tree ships today is 23 characters and nothing overflows, and the first real 42-character `0x` address will not fit the 300px card floor at 320. **(f) `srcset` is 0**, so the event photograph is one resolution from 320 to 1600. Owner: Responsive step 4 |

---

## Component boundaries, named and not decided

Found while computing the level of every component from its markup (Stage 09 step 10). Each is a
question about where one component ends and the next begins, and none of them is a defect that
renders wrong today. They are here because the level arithmetic had to work around each one, and a
workaround that is not written down is a workaround that gets re-derived.

Item 16 was one row until the Design System stage's entry gate counted its members on the screens
and found three different jobs inside it. It is 16a to 16d now, because a single row
would have sent a pattern, a candidate and a misplaced class to the same place.

| # | Item | Source | Note |
|---|---|---|---|
| ~~15~~ | ~~The how-it-works dialog: fold it in as well?~~ **CLOSED 2026-08-11 AND THE ANSWER WAS (b), SPLIT FIRST**, `decisions.md` same date. `hiw-dialog.css` is `components/hiw.css` and keeps 34 rules; the ten that are about being a sheet are at the foot of `components/dialog.css`, where the reason is written out; `index.css` keeps the slot; `Reads:` dropped exactly `--text-on-brass` and `dialog.css` gained exactly that one; the stale `coverage.md` objection went with them, because an objection whose instrument has been deleted is a fact about a tree that no longer exists. The rename ran through `index.css`, `_nav.js`, `organisms.html`, `molecules.html`, `dialog.html`, `hiw.html`, the inventory, `ui-kit/CLAUDE.md`, `_page.css`, `base.css` and `tokens.css`, and the sheet gained a fourth-variant section on `ui-kit/dialog.html`. **Verified: 0 differing rows of 720,210** over 157 documents x 2 themes at the three rungs and one pixel either side, the tree at `018a721` served from a second port against the working tree, the sheet opened by `showModal()`. The original row:  **SURVEYED IN FULL 2026-08-10 AND THE ROW'S OBSTACLE NO LONGER EXISTS.** The row says `hiw-dialog.css` sits at #20 and `dialog.css` at #29, so folding moves **52 rules across nine files**. That was true when it was written and **`index.css` has been reordered three times since**. Read from the file today the order is the other way round: `dialog.css`, then `bets-table.css`, then `card.css`, then `hiw-dialog.css`. **Folding moves the rules three slots EARLIER past exactly two files**, and appended at the end of `dialog.css` they keep their position against everything else in the 49-import chain. The file is **44 rules over 139 lines, not 52**: six went with backlog 67 and the close disc took two more. **THE CONTEST SURFACE IS 84 RULES AND THE ANSWER IS 0.** Every element of all 160 linked pages was asked which rules of `hiw-dialog.css` and which of `bets-table.css` plus `card.css` both match it AND write a common longhand: **not one pair, at any specificity**. The zero is a zero because the same instrument pointed at `dialog.css` returns **427** candidate pairs. Control 0 of 5,183 rows on the unchanged tree. **BUT THE ANSWER IS NOT (a) FOLD, IT IS (b) SPLIT FIRST**, because the anatomy rule the row invokes is true of 10 of the 44 rules and false of the other 34. Reach was measured with `Element.matches()` against both hosts, at 390 and 1280 in both themes, byte-identical in all four passes, then re-run over 160 pages: **0 of the 44 matches nothing anywhere**, and they split into three buckets with no fourth and no ambiguity. **BOTH, 18 rules**, all unscoped, which is the explainer itself and stands in both hosts. **SHEET only, 10 rules**: `.hiw-dialog`'s width and clip, `.hiw-body`'s scroll container and padding, and `.hiw-full` with `.hiw-arrow` and their hover and press, 107 elements every one inside a sheet, and `.hiw-full` is the link that says read the full guide, which cannot exist on the page because the page IS the full guide. **PAGE only, 16 rules**, every one scoped to the three classes backlog 67 added. Folding all 44 would put a CSS grid, a 900 rail, a sticky side column and a `.pos` placement into `dialog.css`, which would then own the shell of a screen; `signin` and `outcome-dialog` were four rules each and were nothing but a dialog. **The cut is safe in any order**: SHEET against BOTH is 0 overlapping pairs, PAGE against BOTH is 6 and every one is decided by specificity, and only **3 of 15 intra-file pairs are decided by source order**, all three inside a single bucket. So the file can be cut along the three buckets and the computed result cannot change, provided each bucket keeps its own internal order. **The specified edit**: `hiw-dialog.css` becomes `components/hiw.css` and keeps 34 rules, because the name has to stop saying dialog; 10 rules append at the end of `dialog.css`; `index.css` keeps the same slot; `Reads:` drops exactly one role, `--text-on-brass`, and `dialog.css` gains exactly that one. **And a stale objection in `dialog.css` has to go with it**, not be left standing: lines 246-251 say `.hiw-body` must not come here because `coverage.md` is computed and would disagree, and `coverage.md` is in `docs/kit-archive/` and is read by nothing. Still open because it is a rename across `index.css`, `_nav.js`, three kit pages, the inventory and `STRUCTURE.md`, plus moving the sheet's specimen onto `dialog.html` as a fourth variant, which is a stand decision rather than a mechanical move. Verified after: 106 painted screens with `#howitworksDialog` opened by `showModal()`, the 54 kit pages, `wireframes/how-it-works.html` as a null control that must read identical, at 389/390/391/639/640/641/759/760/761/899/900/901/1279/1280/1281 in both themes, with `:hover` and `:active` driven rather than rested because three of the ten moved rules have no static face. Expected: 0 differences everywhere, and **any non-zero is the edit being wrong rather than the system being surprised**. The original row: | Stage 09 step 10, 2026-08-02; narrowed 2026-08-02 when `signin` and `outcome-dialog` folded in | Read from the DOM it has the SAME zone set as the shared sheet, zone for zone: a top band carrying the title (`.hiw-hero` for `.sheet-head`), a body, and a close button that is out of flow in both. Nobody in the family has an action row. So the anatomy rule says fold. What holds it back is not anatomy but cascade distance: `hiw-dialog.css` sits at #20 and `dialog.css` at #29, so folding moves **52 rules across nine files**, against four for the two already folded. Two of its shared classes were checked (`.brand-tile` at 0,3,0 beats hero's 0,2,0; every `.ic` rule is scoped under `.hiw-*`), so both known contests are decided by specificity rather than order. That is 2 rules of 52. Decide after the same measurement |
| ~~16a~~ | ~~Five compositions that are patterns, not component boundaries~~ **CLOSED 2026-08-10 AS ALREADY DONE, and never struck, which is item 74 arriving a sixth time.** All five are in `components/patterns/` and have been since the patterns step: `browse-shell.css` holds `.cat-layout`, `.cat-main` and `.read-col`, `detail-shell.css` holds `.ed-layout` and `.ed-main`, `card-grid.css` holds `.grid`, `position-list.css` holds `.pos-list`, and `action-bar.css` and `list-head.css` came with them. **`.feed-inner` is the one that did not go and that is a decision rather than an omission**: it is the PAGE FRAME, declared in `base.css` under a heading that says so in its first line, and the same reading applies to the plate. `.feed-inner>.cat-layout` and `.ed-main` take their two-stone gradient from the frame and their layout from the pattern, and `patterns/detail-shell.css` already writes the split down in its own header, 'What did NOT come with them: the plate .ed-main stands on'. Every other appearance of these names in a component file is a compound scope, a component saying where it stands, which is what row 17's rule permits and not what it forbids. The original row: | Design System entry gate, 2026-08-02 | Counted with the containment reader over the 105 painted screens: `.feed-inner` (feed) on **104** screens, `.cat-layout` and `.cat-main` (catnav) on **76**, `.ed-layout` and `.ed-main` (event-detail) on **11**, plus `.grid` (feed) on **23** and `.pos-list` (position) on **13**. Every one clears the three-screen threshold, so none of them is a question about where a component ends: they are a stable composition living inside a component file, and step 3 of the Design System stage moves them to `components/patterns/`. **The last two are absent from `_levels.SPECIMEN_DEBT`, because they open no gap in the containment map, and that is the point: step 3 counts patterns FROM THE SCREENS, not from the debt list.** A debt list is a list of what distorted a level; a pattern list is a list of what repeats |
| ~~16b~~ | ~~Two components in one file, still below the pattern threshold~~ **CLOSED 2026-08-10 AS A REGISTER RATHER THAN AN EDIT**, `decisions.md` same date. Re-counted from the rendered tree: `.ptab-panel` still stands on **2 screens, 3 placements each**, `my-profile` and `public-profile`, and the threshold is three SCREENS. The row asked for it to go on the step-3 'candidates, waiting for the third' list and **there was no such list**, only a section on `ui-kit/patterns.html` about `.read-col` standing on one screen. **That half of the register went the other way on 2026-08-19**: `.read-col` cleared the threshold when four more long documents landed, so the section is now a reading of a slot that BECAME a pattern, and `.ptab-panel` is the only member left below the line. There is one now: the page carries the register of what stands below the threshold, both members with their counts and how far short each is, and the sentence the row is really about - **the threshold counts SCREENS and not occurrences**, because what a pattern claims is that an arrangement repeats across the product and not that one page uses it three times. It also names the second half the row saw: `.ptab-panel` is a pattern candidate AND a component boundary at once, an L1 switcher and an L3 tab panel in one file. **A threshold with nothing standing below it is a threshold nobody can check.** The original row: | Design System entry gate, 2026-08-02 | `.ptab-panel` (tabs) stands on **2 screens, 6 times**. The threshold is three SCREENS and not three occurrences, so it is both things at once and neither is decided: a component boundary (`tabs` is an L1 switcher and an L3 tab panel in one file) and a pattern candidate waiting for a third screen. It goes on the step-3 "candidates, waiting for the third" list rather than into `patterns/` |
| ~~16c~~ | ~~A stand page's own class declared in a product component file~~ **CLOSED, AND IT WAS DONE BEFORE THE ROW WAS READ AGAIN.** Verified 2026-08-10: `.tc-page` is declared in `components/base.css` under a heading reading THE PAGE A CATALOGUE DEMONSTRATES ON, with the reason the row gave, and `components/toast.css` carries a comment at the top saying where it went and why. So the fix landed and the row stayed open, which is item 74's shape one more time: a number left standing over work already done. The original row: | Design System entry gate, 2026-08-02 | `.tc-page` (toast) stands on **1 screen**: `toasts.html`, the catalogue of every toast. Neither a pattern (one screen) nor a boundary (there is no second component in `toast.css`), so it is a third kind: the page that DEMONSTRATES a component has its class in the component's own file, which is why the toast reads as containing the cookie banner. It closes by moving the class, not by splitting anything |
| ~~16d~~ | ~~The five remaining rows that are two components each~~ **CLOSED 2026-08-10, AND IT WAS FOUR ROWS, AND ALL FOUR ARE ONE COMPONENT**, `decisions.md` same date. **`account` is not a row**: the file, its import, its shelf section, its inventory row and its kit page were all deleted on 2026-08-08 by row 63, and the vitrine is 54 pages rather than 55. **AND THE MACHINE ALL FIVE WERE OPENED TO PROTECT IS GONE.** Every one of them was filed because the level arithmetic had to work around it, and `ui-kit/_levels.py` went with the other 62 scripts on 2026-08-07; verified independently of the prose, there is no `.py` in the repository outside the tooling folders, no CI, no hook, and `ui-kit/docs/inventory.md` opens by saying it is read by nothing. **So none of the four splits would change a level, a cascade order or a rendered pixel**, and each would still cost what item 17 cost: every rule moved changes its position in the import order, which this folder has already paid for twice. **Each is closed with the sentence that closes it, in its own file, because a rule with no reason is a rule that gets argued away.** **`card`, on `ui-kit/card.html`**: one component with two contents, both cards carrying the same fourteen classes in the same order and differing only in what stands in the action row, **63 binary holding `.yesno` and 21 multi holding `.options`**. **`notice`, on `ui-kit/notice.html`**: six faces and one job, 113 `.protect`, 110 `.widget-box`, 8 `.inline-error`, 7 `.reconcile-box`, 7 `.spinner-box`, 2 `.push-banner`, and **0 of them hold a control except the permission banner, which holds two of `button`'s**, which is the page's own rule proved rather than asserted. **`position`, in `position.css`**: the record grid and the portfolio summary are this row with its box taken off, built entirely from `.pos*` and inventing no name, 2 and 1 placements against 18 plain rows and 14 that are the control; **the list already left** to `patterns/position-list.css` with 16a and 49 of the 57 stand in one. **`filters`, in `filters.css`**: the second component the row names is `toggle` and **it left on 2026-08-05**, which that file has recorded ever since; `.reverse-row` is the panel's own row, 3 of 3 inside a `.filter-panel`. The row was describing a file that had already stopped existing in that shape. The original row: | Stage 09 step 10, 2026-08-02; narrowed by the Design System entry gate, 2026-08-02 | What is left of the original item 16 once the three above are taken out of it: `card` (binary B is a molecule, multi D an organism), `notice` (six blocks), `position` (row / list / portfolio summary / resolved history), `filters` (menu plus toggle), `account` (CTA bar plus transaction list, and only one of the two has a specimen). These are boundary questions and nothing else: none of them is a composition that repeats across screens |
| ~~17~~ | ~~Five classes declared in the wrong file~~ **CLOSED 2026-08-03. 17 rules moved across five pairs of files, and TWO OF THE FOUR HAND-WRITTEN CYCLES WENT WITH THEM.** `.grid-l` feed -> chart (1 rule); `.pos-status` profile -> position (6); `.seg` tabs -> comments (7); the two `.bet-sheet .fine` rules dialog -> betpanel; `.rp-inner` event-detail -> betpanel (1). **What that bought, and it is the argument for doing it at all:** `_levels.ORDER_BREAK` lost `(comments, tabs)` and `(betpanel, event-detail)`, because both cycles were made OF the misfiled classes rather than of a real nesting - a hand-written tie-break is a place where the map stopped agreeing with the files. The level map moved twice as a result and both moves are recorded: `position` fell from organism to molecule, which is the distortion item 17 named being undone, and `comments` fell from organism to ATOM, which is the arithmetic being right about the wrong question, so `_levels.RAISE` now declares it a molecule beside `market` with the same stated reason. **The cascade order was recomputed, not adjusted**: `components/index.css` was rebuilt from `python3 ui-kit/_levels.py --order`, which moved four files. **Proof that nothing moved on screen: 525 snapshots, 105 painted pages at three widths, 0 differ, 0 elements changed.** The third `.bet-sheet` rule stays in `dialog.css` and the reason is written beside it: `dialog.app-dialog:modal:not(.bet-sheet)` has the dialog as its subject, and a rule that EXCLUDES a class is not a rule about that class. The original row: | Stage 09 step 10, 2026-08-02; a fifth added 2026-08-02 | Each distorts the level it feeds: `.pos-status`, a position-list divider, is owned by `profile.css`, which makes `position` an organism through `profile`; `.grid-l`, the CHART's grid line, is declared in `feed.css`, which makes `chart` contain `feed`; `.seg`, a segmented switcher, sits in `tabs.css`, and that is the cycle `_levels.ORDER_BREAK` has to declare by hand. A fourth surfaced on 2026-08-02 and did NOT close with the merge: **`.bet-sheet` is styled by three rules inside `dialog.css`** while the bet sheet is a different component by the same anatomy rule that merged the other two (no top band, no close, a `.sheet-grab` instead), so those three rules belong in `betpanel.css`. The fifth is **`.rp-inner`**, the resolved bet panel's own wrapper, declared in `event-detail.css`: with the panel's resolved state now on the stand it makes `betpanel` read as containing `event-detail`, which is the second hand-written `ORDER_BREAK` this item has cost |
| ~~18~~ | ~~`hiw-dialog` is the last level that is declared and not computed~~ **CLOSED 2026-08-11: THE FLOOR IS GONE AND THE LEVEL IS COMPUTED.** The cut that removes it is the one row 15 arrives at from the other side, 10 rules out and 34 staying together, and after it the page host HOLDS `hero` and `position`, which is the arithmetic saying what the ceiling rule already says. `ui-kit/organisms.html` carries no brass declaration row for this component any more. The original row:  **ANSWERED 2026-08-10 BY MEASURING BOTH HOSTS, AND IT IS NOT TWO COMPONENTS.** The floor exists because nobody can say what the component IS. Read from the DOM with the dialog opened rather than shut: it is **one BLOCK with two HOSTS**, and the hosts are different kinds of thing. The **sheet host** is `dialog.app-dialog.hiw-dialog`, one element wearing two component classes: it does not HOLD `dialog`, it WEARS it, and what it genuinely holds is `.sheet-close`, which is `dialog.css`'s class wearing `iconbtn.css`'s three. So its level is `dialog`'s and it has none of its own. The **page host** holds `hero` (level 3, `.brand-tile` and its six parts) and `position` (level 2, `.pos` and its four), which is the arithmetic saying what the ceiling rule already says: **level 3 also means the shell of a screen**, the same reading `header`, `footer`, `feed` and `event-detail` carry. The **block** is built entirely out of its own class names plus `svg.ic`, which `base.css` floors and no component owns, so the arithmetic reads 1 and would be wrong. **THEREFORE SPLITTING REMOVES THE FLOOR IF AND ONLY IF THE BLOCK GOES WITH THE PAGE SHELL.** Cut into sheet-plus-block and page, the floor comes straight back on the sheet-plus-block file; cut into three, it comes back on the block file. **There is exactly one cut that removes it and it is 10 rules out and 34 staying together**, which is the same cut row 15 arrives at from the other side. The two rows are one edit. Depth measured: the page block root sits at DOM depth 9 under `div.app-case > main.feed > div.feed-inner > div.cat-layout > div.cat-main`, the sheet at depth 3 under `<body>`; geometry and typography are identical in both themes at both widths and 55 colour roles differ, which is the theme doing its job. `ui-kit/organisms.html` also claimed the file is 118 lines and it is **139**, the 21 being backlog 67's own comment; corrected the same day. Still open with 15. The original row: | Stage 09 step 12, 2026-08-02 | One of fourteen `RAISE` floors, and after the mechanical revision one of thirteen. Twelve of the thirteen are a component whose parts are all its own classes or a screen shell, which is a reason arithmetic cannot reach. This one is different: the floor is there because **nobody can say what the component IS**. Read from `ui-visual/how-it-works.html`, `.hiw-hero` and `.hiw-cols` are blocks of the standalone PAGE, five levels under `div.app-case` and inside `main.feed`, while the narrow shared sheet hangs under `body` as a separate closed `<dialog>`. Two components on one vocabulary, which is item 16. Until they are split, a specimen showing either one is a choice and not a reading, so the stand was deliberately left alone. **Splitting it removes the floor**, and it is the only one of the thirteen that a split would remove |
| ~~19~~ | ~~`.fine` is a typographic role, not a part of the dialog~~ **CLOSED 2026-08-10 AS A PART, AND THE PART BELONGS TO `dialog`**, `decisions.md` same date. The question has a number and the row never took it. Measured over the 106 painted screens with every dialog **OPENED**, which is the whole difficulty: **216 of the placements sit in a dialog that is shut at load**, and a computed-style pass that skips them reads 27 elements and reports 248. **248 placements, 239 of them inside a `<dialog>`, 9 not, and deleting the one bare `.fine` rule changes exactly those 9** (4 in a bet panel, 1 in the spinner box inside one, and the 4 that stand nowhere near either). **The role governs nine elements and the dialog governs the other 239.** **WHAT DECIDES IT IS WHAT THE SYSTEM ALREADY DOES.** All **66** purely typographic classes here live in the file of the block they are a part of; there is no `components/typography.css`, the type SCALE is in `tokens.css`, and the page that shows it carries no `.fine` at all. The twin is **`.field-label` in `input.css`**: 245 placements, the same five containers, the same order of magnitude, and nobody has ever proposed a type file for it. And the threshold for a class no component owns is stated in `ui-kit/docs/inventory.md` as **three files or more**, compounded onto each owner's own class rather than written bare, which is what `.sel` is in seven. **This is written by two**, and the second is `betpanel.css` restyling what it contains, which the rules permit and which that file says in its own comment. **THREE OF THE ROW'S SUPPORTING FACTS HAD EXPIRED.** Its count: 246 was right on 2026-08-02 and the tree has carried **248 since 2026-08-03**, when the voice pass added the bet panel's minimum line and the sheets' terms line, and `ui-kit/dialog.html` already said 248. Its ownership: `dialog.css` is no longer the only file that writes it. Its named rules: the three it lists are **one**, because `.app-case` came off on 2026-08-08 and two went to `betpanel.css`, and that one, `.spinner-box .fine`, **decides nothing** and is the last rule in `dialog.css` naming another component's container. And its stated consequence, that `notice` reads as containing `dialog` with the reconcile box on the stand, **does not exist**: `ui-kit/notice.html` carries 0 `class="fine"`. The reason is written in `dialog.css` above the rule. The original row: | Stage 09 step 12, 2026-08-02 | The small print. `dialog.css` is the only file that writes it, so the ownership map gives it to `dialog`, and it stands on **246 elements** across dialogs, bet panels, spinner boxes and reconcile boxes, three of which `dialog.css` styles by name (`.app-case :is(.bet-panel,.bet-sheet) .fine`, `.app-case .spinner-box .fine`). The consequence is measurable: with the reconcile box on the stand, `notice` reads as containing `dialog` and moves L2 -> L3, the third `ORDER_BREAK` of the day. It is the same species as `.sel` in `_levels.SHARED` (a word no component owns) but not the same evidence, because `.sel` is written by six files and this by one. Deciding it means deciding whether a typographic role is a component at all, which is a **states and roles** question and therefore Stage 10's |
| ~~45~~ | ~~**Nine declarations in the system are keyed to a document-unique id, so two components can exist once per page**~~ **CLOSED 2026-08-13, and six of the nine went on 2026-08-10** with the pass that moved `tabs.css` off document-unique ids onto `:nth-of-type`; the row stayed open for three days. What is left is **two SVG paint references in `hero.css` and the four `#rm*` in the harness that this row itself calls correct**, and **the fix the row proposed for the two is not available**: an SVG `fill` takes a paint server, a paint server IS an element referenced by id, and `linear-gradient()` is not a legal value there. So the constraint is declared in `hero.css` instead: **a document may hold one hero chart**, and the kit's rule for a specimen drawn twice already says to suffix every id a cell redefines. Measured over **267 documents in all three trees: 0 with any duplicate id.** | Design System step 4c, 2026-08-08, found by building `ui-kit/organisms.html` | A class can appear a thousand times; **an id is unique by definition**, so a rule written against one is not a rule about a component, it is a rule about **one instance**. Counted across all 51 stylesheets: **`tabs.css` 6 rules over 10 ids** (`#edtab-comments:checked ~ .ed-panel-comments` and nine more, plus `#ptab-*` and `#p-*` for the profile set), **`hero.css` 2 paints over 2 ids** (`.hf-area{fill:url(#hfyes)}`, `.hf-vol-area{fill:url(#hfvol)}`), **`base.css` 1** which is `#rmSidebar` and is correct because there really is one panel per document. **Measured over the 106 painted screens: 0 of the 13 ids appears more than once**, so nothing renders wrong today: one hero per feed, one tab set per detail page. **It is latent, and the stand is the first thing that ever asked**, because a level page shows a component twice in one document by construction. The failure is quiet in both directions: two tab sets would share `name="edtab"` and uncheck each other, and a second hero would paint its area with the first copy's gradient, which looks plausible rather than broken. The fix is that a component keyed to an id is not a component: either the ids are written per instance by whatever renders it, or the tab state moves to a class and the gradient to a value the CSS can hold. Same family as `.app-case` (item 42): a dependency the system requires and never declares. Owner: Stage 12 (Handoff) |
| ~~46~~ | ~~Three prose claims in `components/patterns/` have gone stale~~ **CLOSED 2026-08-08, two of the three.** `browse-shell.css` no longer claims `feed` and says why in its place: the containment runs the other way, `<main class="feed">` opens at line 361 and `<div class="cat-layout">` at 469. `position-list.css` no longer claims `profile` and names the 2026-08-03 move that ended it. Both counts corrected, 76 to 77 and 70 to 71, each carrying the number it was written with. **`.read-col` at one screen stayed open** and was the third claim: it is not a stale sentence but a slot below the threshold, and what it needs is a second and a third long document, not an edit. **CLOSED 2026-08-19, BY ITS OWN CONDITION AND NOT BY AN EDIT.** Four more long documents landed on 2026-08-18 (about, privacy, cookies, responsible-betting), so `.read-col` stands on **5 painted screens and 5 grey twins**, counted from the rendered DOM of 115 painted, 114 grey and 61 kit documents, and the rung is cleared by three rather than missed by two. **And the four screens found what one screen had hidden**: measured together on 2026-08-19 the column carried six widths and three left edges, because `.feed-seo` centres itself with `margin-left:auto;margin-right:auto` written for a block box and `.read-col` is a flex column, where an auto inline margin CANCELS `align-items:stretch` and shrink-wraps the item, 417px at an offset of 91. A slot with one placement is a slot nobody can disagree with. The original note: \| **Three prose claims in `components/patterns/` have gone stale, and nothing reads them** \| Design System step 4d, 2026-08-08, found by building `ui-kit/patterns.html` \| Each pattern file opens with an `Assembled from` line and a `Stands on: N screens` count, and both are prose. Read from the screens instead, across all 106: **(a) `browse-shell` claims `feed` and the containment runs the other way** (on `event-feed.html`, `<main class="feed">` opens at line 361 and `<div class="cat-layout">` at line 469, so the feed HOLDS the browse shell). Nothing renders wrong; an `Assembled from` line that names a PARENT is the level arithmetic's blind spot written down by hand. **(b) `position-list` claims `profile`, and that stopped being true on 2026-08-03**: the record block in the stack is `.pos-record` with `.pos-figures` and `.pos-fig`, and those moved to `position.css` in the pass that closed item 17. `profile.css` owns seven classes today (`.av`, `.edit`, `.gallery`, `.handle`, `.idrow`, `.name`, `.who`) and none stands in a position list. **(c) `.read-col` is a slot in `browse-shell.css`, eight declarations over three rules, standing on ONE screen** (`ui-visual/terms.html`), two short of the three-screen threshold this rung exists to enforce. The reason it was written there is good and is a different question: *the container owns the distance, not the block* answers WHERE the rule goes, not WHETHER it exists yet, and the two were answered as one. It is named rather than moved, because deleting eight correct declarations to put them back on the third screen costs more than it returns; what it needs is a second and a third long document. **Two screen counts are also one light** (`.cat-layout` 76 claimed and 77 measured, `.feed-head` 70 and 71), because the headers were typed when the painted tree was 105 screens. That drift is the accepted price of the rule that a measurement is an act and not a machine, and it is one screen. Owner: Stage 09 (Design System), with the audit \|| | |

---

## Accessibility and keyboard, found by the states pass

Four findings from Design System step 2. One is now closed and one was measured and downgraded.
None of them was a state, which is why none was fixed there: three are markup and the fourth
looked like a behaviour change until somebody counted the tab stops it actually adds. They are here rather than in
the section above because they are not questions about where a component ends. Each one was
measured on the shipped screens, not inferred from a rule.

| # | Item | Source | Note |
|---|---|---|---|
| ~~22~~ | ~~The filter panel is unreachable from the keyboard~~ **CLOSED 2026-08-03**, and the objection in the original note turned out to be measurable | Design System step 2, 2026-08-02 | **The fix is CSS and it is the idiom this system had already chosen twice.** `display:none` became the 1x1 transparent box `components/tokens.css` names in `--hairline` ("the 1x1 box of a visually hidden input") and `components/tabs.css` writes in `.ptab-in`: `position:absolute;width:var(--hairline);height:var(--hairline);opacity:0;margin:0;pointer-events:none`. The ring then goes on the LABEL, `label:has(input:focus-visible)`, with the offset turned inward for the reason `tabs.css` turns it inward: the panel is `overflow:hidden` and the label carries 4px of side margin. **Measured before:** 400 Tab presses at 1440 in both themes, **0 stops inside `.filter-panel`**, 16 inputs all computing `display:none`. **Measured after:** the checked radio of an open menu is the first stop after its summary, the ring lands on the visible label at `2px solid @-2px`, and arrow keys move within the group. **Ring contrast**, composited through the translucent brass wash rather than read raw: graphite **8.31:1** on a chosen row and **7.80:1** on a plain one, daylight **4.48:1** and **7.14:1**, against a 3:1 floor. **The "eight or so new tab stops per screen" objection is answered with a number: ZERO when the menu is closed** - `<details>` content is not focusable while shut - and **one per OPEN menu**, because a radiogroup is one tab stop by construction and the arrows do the rest. So it was never a product decision. **Nothing at rest moved:** 20 snapshots across 4 screens at 2 widths, 0 differ, 0 elements changed. The original note read: `components/filters.css` line 27 hides every option with `.filter-panel input[type=radio],.filter-panel input[type=checkbox]{display:none}`. `display:none` takes an element out of the tab order AND out of the accessibility tree, so the sort radiogroup and the category checkboxes on 104 screens can be operated by a mouse and by nothing else, and `label:has(input:focus-visible)` can never match because the input can never be focused. It is CSS-only to fix (a visually-hidden technique that keeps the box in the layout) but it CHANGES BEHAVIOUR: eight or so new tab stops appear per screen, which is a product decision and not a state |
| ~~23~~ | ~~A multi-outcome row is a `<div>` that answers a click~~ **CLOSED 2026-08-10 AS ANSWERED BY THE TWO ROWS IT SPAWNED**, `decisions.md` 2026-08-10 for 61 and this file for 96, and the answer is that the container CANNOT be the control. The row was already measured NOT A BLOCKER in 2026-08-03 and stayed open on one question: whether `.opt-row` should become a target of its own. **Row 61's independent review settled it on the spec rather than on taste.** `.opt-row` is a `<div>` and HTML-AAM maps a div to `generic`; every role that would make it a widget - `option`, `radio`, `checkbox`, `switch`, `tab`, and `button`, which this row had already rejected - is **Children Presentational: True**, so taking one deletes the two real `<button>`s inside the row from the accessibility tree. The container can carry a GLOBAL state and nothing else, which is why 61 wrote `aria-current="true"` on the chosen row and why it moved the attribute with the class on all 17 call sites. **What the row asked for that was real is now two other rows and both are specified**: 61 gave the chosen state a name and put the word 'selected' back on screen, because colour alone is 1.4.1; 96 gives every YES and NO a name that says what it is about. **A route that is narrower is a naming problem and not a structure problem**, and this row's own measurement said so first: Enter and Space on the button produce a state identical to a mouse click on the row, in every field measured. The original row: | Design System step 2, 2026-08-02; **measured in a browser 2026-08-03** | **NOT A BLOCKER, and the test is the RESULT rather than the handler.** The question a structural fix has to answer first is not "who carries the listener" but "can a person finish the action from the keyboard". Walked with a real Tab on `ui-visual/event-detail-multi.html` at 390px: the YES button of row 2 is **tab stop #146 of 278 focusable elements**, and **Enter and Space each produce a state identical to a mouse click on the row** in every field measured - selected row, selected tag, the chart's lit line, the bet panel's outcome name and the "now" figure. The focus ring on the button reads **6.67:1**. The delegated handler sits on `#edOutcomes` and treats a click from an inner button as "this row, this side", so the keyboard reaches every distinct result the mouse does. **What is true is that the route is NARROWER, not that it is missing**, and a `role="button"` on the container would have made it worse, not better: the row contains two real `<button>`s, and interactive descendants inside a widget role is invalid. **And the count was wrong in the other direction too.** `.opt-row` is 52 rows on 14 screens in both trees, but on **12 of those 14 it is not a control at all**: on the feed and favourites it is a display row whose YES/NO are `<a href>` navigations, with no handler root on the page. Two screens carry the behaviour. The original note: `.opt-row` in `ui-visual/event-detail-multi.html` carries a JS click handler that loads the outcome into the bet panel, and it is a `<div>` with no `tabindex`, no role and no key handler. A person navigating by keyboard can still reach the YES and NO buttons inside it, so the function is not lost, but the row as a target does not exist for them. It is recorded in `_levels.STATIC` under `options` as the reason that component gets no states. **The fix is markup, so it belongs to the grey tree first** (`wireframes/` decides structure, gate 18 compares the two), which is why a states pass could not close it |
| ~~25~~ | ~~**The eight error blocks are announced to nobody, and the fix is an attribute**~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and the row's two axes stayed two.** `.inline-error` stands on the same 8 screens in both trees the row named, plus **4 kit specimens the row did not**, and every one was a bare `<div>` with no `id`. **(a) FIELD-BOUND, 2 screens**: the block takes an `id` and the field takes `aria-invalid="true"` with `aria-describedby` pointing at it, exactly as the row specified. **Both screens carry a SECOND `.amount-input` inside a closed deposit dialog** and it is untouched; they are told apart by value and by class, and the sweep asserted exactly one match per screen before writing anything. **(b) STATUS, the other 6 plus the kit's 4**: `role="alert"` on the block. **They are not both, deliberately** - a described-by target that is also an alert is announced twice, which is why the row split them and why the split is kept. **24 edits over 18 documents.** Verified from the rendered page rather than the source: `role="alert"` on 16 blocks, `aria-describedby` resolving to a real element on 2 of 2, `aria-invalid` 2 per tree, and the deposit field's own `validity.rangeUnderflow` true where the message says it should be. **Row 24 closes with it.** The original row: | Design System step 2, 2026-08-02; owner set 2026-08-03 | **Owner: Stage 12 (Handoff), and that is a decision rather than a deferral.** CSS cannot reach an attribute, and thawing the grey tree for one would open the twin contract for everything, so this is handed to the developer with the numbers already taken. `.inline-error` stands on **8 screens, in BOTH trees, the same 8**: `deposit-minimum-not-met`, `deposit-error-card`, `deposit-error-kyc`, `event-detail-bet-error`, `event-detail-bet-insufficient`, `sign-in-error`, `sign-in-provider-conflict`, `win-error`. Every one is `<div class="inline-error">` with **no `id`**, so nothing can point at it. Two axes, and they are different work: **(a) FIELD-BOUND, 2 screens.** The message is about a field a person is typing in, so that field needs `aria-invalid="true"` and `aria-describedby` pointing at the block's new id: `deposit-minimum-not-met` -> `input.amount-input[aria-label="Amount to add"]`, `event-detail-bet-insufficient` -> `input.amount-input[aria-label="Bet amount"]`. **(b) STATUS, the other 6.** Nothing is wrong with a field; the block appears as the RESULT of an action (a declined card, a rejected KYC, a bet that did not register, a provider that did not authenticate, a share card that did not build), so the block itself takes `role="alert"`. Repo-wide today: `aria-invalid` **0 of 105**, `aria-describedby` **0 of 105**. `aria-expanded` at 0 is correct and needs nothing, because every disclosure in the product is a native `<details>` |
| ~~24~~ | ~~What a screen reader is told when something changes, re-measured~~ **CLOSED 2026-08-10 WITH ITEM 25**, `decisions.md` same date, because they are one axis and it was answered once. The row's own conclusion was that the premise it replaced is closed and what is missing is the FORM ERROR axis, `aria-invalid` on 0 of 105 and `aria-describedby` on 0 of 105. Both are non-zero now and the numbers are in 25. `aria-expanded` at 0 is unchanged and still correct, because every disclosure in the product is a native `<details>`. The original row: | Design System step 2, 2026-08-02, replacing item 4 | Item 4 read "`aria-live` / `role=status` on 9 screens of 105; a toast appears and announces nothing". Measured this pass in BOTH trees: the 9 screens are identical in grey and in colour, and the toast on `toasts.html` already carries `role="status" aria-live="polite"` and `role="alert" aria-live="assertive"` on its two groups. So the premise as written is closed. What is actually missing is the FORM ERROR axis: `aria-invalid` on 0 of 105 screens and `aria-describedby` on 0 of 105, while `deposit-minimum-not-met`, `event-detail-bet-error` and `sign-in-error` all ship a visible error message that is tied to no field. `aria-expanded` at 0 is correct and not a gap: every disclosure here is a native `<details>` |

| ~~44~~ | ~~**The drawer toggle is 36px and the switch under it is 44, in one file**~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and it was unblocked by row 91 an hour earlier rather than by an argument**. The row filed this rather than fixing it for one stated reason: the button stood `position:fixed` over the TOP LEFT of every screen, so 8px more of it moved onto a header laid out around 36. It stands in the bottom right now, in a corner measured empty on 147 of 160 documents, so the 8px lands on nothing. It was the last control in the repository under the project's own floor, and it is the one control a person has no alternative to below the dock. **Verified: 44x44 on 1,440 readings**, 160 documents at 390, 700 and 900, three scroll positions each, page overflow 0. **And growing it moved a clearance**: at 36 the button fitted inside the 52px strip `.bet-dock` reserves under itself, at 44 it did not, and it crossed the dock's bottom edge by 4px in 16 readings of 960 the moment the floor was applied. The lift moved from the DESK rung to the DETAIL rung, which is where the dock actually goes, and the count went back to 0. **Growing a control is a reason to re-read the clearance it was written against.** The original row: | Design System step 4b+, 2026-08-08, measured at 390 on the seven kit pages | `course-chrome.css` writes `.rm-toggle{width:var(--control-36);height:var(--control-36)}` and, twelve lines down, `.theme-switch{min-height:var(--control-44)}`. Measured rather than read: **36x36 rendered** at 390 on all seven kit pages, and the toggle is the ONLY way to open the route below 860px, so it is the one control on the page a person has no alternative to. The 44px floor is this project's own, set by the Stage-08 critique. **It is one value in one file and it reaches 106 painted screens plus the kit**, which is exactly why it is a row here and not an edit taken in passing: the toggle is `position:fixed` over the top-left of every screen and growing it by 8px moves it over content that was laid out around 36. Owner: Stage 12 (Handoff), with the responsive pass |

---

## Found by the audit

Design System step 5, 2026-08-08. One run over 115 documents at two widths in two themes, 460
renders. The full account is in [`../ui-kit/docs/audit.md`](../ui-kit/docs/audit.md), including the
**eight** corrections the instrument needed before its numbers could be believed. **Two of the first
three below are re-measurements of rows that already exist**, and they are separate rows because the
numbers moved and because each carries something the original did not. **Rows 50 and 51 come from
the eighth correction**, which took item 49 apart: the audit had measured the whole product with a
mouse.

| # | Item | Source | Note |
|---|---|---|---|
| ~~47~~ | ~~The primitive ramps print each step's number on the colour it names~~ **CLOSED 2026-08-08, and the first fix was measured and rejected.** **A second ink does not solve it.** Testing all 72 cells against a fixed light ink and a fixed dark one: 69 clear 4.5 with one of the two and **three cannot clear it with either**, because a mid-tone has no good ink. `p-bone-650` tops out at 4.36, `p-green-700` at 3.99, `p-ink-300` at 3.97. A per-swatch class would have fixed 69 and left three defects wearing a fix. **So the label came off the swatch.** `.tk-rc` is a column now, the colour is a `::before` block and the number stands under it on the page ground, where `--text-muted` is a pairing this page's own matrix already measures in both themes. It is better for the ramp too: a swatch with a number printed on it is a swatch whose colour cannot be read clean. `.tk-onlight` went with it and its 30 usages, because a class that paints nothing is what cost the previous stylesheet 800 lines. **Measured after: 37 and 58 readings under floor to 0 and 0.** What is left on the page is 15 per theme, and they are the contrast matrix's own `icon-quiet` and `icon-brass` specimens, a graphical role held to the text floor by a general instrument; their floor is 3:1 and they clear it. The original note: \| **The primitive ramps on `colour.html` print each step's number on the colour it names** \| Design System step 5, the audit, 2026-08-08 \| 37 readings under 4.5:1 in Vault and 58 in Daylight, and they are the ONLY contrast failures anywhere in 460 renders. `600` on `p-bone-600` reads **1.36:1**; `930` on `p-graphite-930` reads **2.38:1** in Daylight. A 9px label on its own swatch cannot clear 4.5 and arguably should not, because the swatch is the specimen and the number is its name, not a word to read. It is a row here rather than a shrug because **a number nobody can read is a number that gets copied wrong**, and this page exists so somebody can copy a step name. The fix is a label that is not ON the swatch, or a label whose ink flips with the swatch's luminance. Owner: Stage 09 (Design System) \|| | |
| ~~48~~ | ~~**Twenty-three labels and 1,902 anchors go to `href="#"`, and one of them is a broken string**~~ **CLOSED 2026-08-13, AND THE THING WORTH FIXING WAS NOT IN THE ROW.** The count was 630 stale before this pass, because rows 27 and 28 closed on 2026-08-10 and cut the eight destinations the map refuses: **1,272 anchors and 17 labels in `ui-visual/`, now 1,059 and 15**; 1,086 in `wireframes/`; 327 in `ui-kit/`, now 320. **`Terms` was one of the five the footer keeps at `#` "because they are registered nodes with screens still to build", and `ui-visual/terms.html` was BUILT on 2026-08-03.** Measured: **1 link to it from the product** (on `overview.html`, the index of the tree rather than a screen in it) **against 106 from the review sidebar**, while **213 anchors labelled Terms sat at `#`**. A placeholder is a promise with a date on it and nothing goes back to the list when the date passes. 213 painted anchors plus 7 kit specimens now point at it, 221 product links, 0 broken; the grey tree keeps its 194 because the map records Terms of Service as the first screen with no grey twin. **The copy defect is real and is not the one described**: the footer carries only `Privacy` and shortens every legal node the same way (`Terms`, `Privacy`, `Contact`, `Responsible play` against the map's full names), which is register rather than drift. The drift is CASE - the cookie banner wrote `Privacy policy` and `Cookie policy` against 109 instances and the map itself. 10 labels on 3 documents, normalised. **And the instrument reproduced the row's own seventh fault**, `Privacy Policynot built`, because `textContent` joins two spans in a Related card. 267 documents: 0 console errors, 0 h-scroll, 0 broken links. 144 opened for the 525 social marks the row never named. `docs/decisions.md`. | Design System step 5, the audit, 2026-08-08, re-measuring items 27 and 28 | The record in item 27 says 16 distinct labels over 1,664 links; measured across the 106 painted screens today it is **23 distinct labels over 1,902 anchors**, and **17 stand on 105 screens each**: Sports, Trending topics, Leaderboard, API / Developers, Status, Help Center, FAQ, Contact, About, Careers, Press, Brand, Terms, Privacy, Privacy Policy, Responsible play, Geo restrictions. **Two are the same destination under three spellings**: `Privacy` and `Privacy Policy` in the footer, and `Privacy policy` on `cookie-consent.html`, which is a voice defect rather than a map one. (A fourth, `Privacy Policynot built`, was reported on 2026-08-08 and **withdrawn the same day**: the markup on `terms.html` is `<span class="rel-q">Privacy Policy</span><span class="rel-odds">not built</span>` inside one anchor, a Related card whose odds slot says "not built" on purpose, and the label extractor joined the two spans. Four cards there do the same. It was the audit's seventh instrument defect and the only one that reached a document.) This does not replace 27 and 28, which own the map half and the component half; it is the count they were written against, updated, plus the copy defect neither of them names. Owner: Stage 03 (IA) for the destinations, `voice/` for the two names of one thing |
| ~~49~~ | ~~1,787 of 2,709 product touch targets are under the project's own 44px floor~~ **CLOSED 2026-08-08, and the number was the instrument's, not the product's.** **THE AUDIT MEASURED THE PRODUCT WITH A MOUSE.** The 44px floor has been bound to `@media(pointer:coarse)` in this system since step 7, headless Chromium reports `pointer:fine` and `maxTouchPoints:0`, and nothing in the run asserted otherwise, so all 460 renders read the 36px branch. Re-read with touch emulation on and `matchMedia('(pointer:coarse)')` asserted true before every read, 106 screens at 390: **1,790 targets, 1,361 already clearing 44x44, 262 short.** The finding was real but it was one sixth of what was reported, and it was not the shape the row describes. **WHAT WAS ACTUALLY WRONG: the floor was written six times and every copy named a LIST.** `chip.css` named `.chip-quiet` and `.chip-nav` out of five chips, `tabs.css` named two faces of three, and `.market-head`, `.rules-tab` and `.toc-link` were named by nobody. So it is now ONE rule in `components/base.css`, beside the focus ring and for the same reason, keyed to the family and carrying the icon button's four exclusions unchanged. **Measured after: 262 short to 68 at 390, and 542 to 98 at 1280 with the same pointer. Every one of the 68 is text or a named exclusion.** The fine branch was measured to prove it did not move: 1,028 under 44 before and 1,028 after. 0 pages gained horizontal scroll. **Direction B is what this is**, arrived at from the other end: the floor is one value in one place, and it did not need every control to read a height token to get there, because `min-height` on the family out-specifies whatever the control computed. Item 40 stays open on its own terms and is no longer what the floor depends on. The original row: | Design System step 5, the audit, 2026-08-08, measured at 390 inside `.app-case` on 106 screens | **WCAG 2.5.8 AA (24x24) is met: 2,692 of 2,709 clear it**, and the 17 that do not are wide short rows (`.market-head` 310x18, `.hh-name` 202x20, two prose links at 83x23), not small dots. **The project's own 44x44 floor, set by the Stage-08 critique, is missed by 1,787**: 1,413 too short, 94 too narrow, 280 both. It is one decision repeated rather than a hundred mistakes: `.chip-quiet` renders 38px tall on 530 elements, `.chip-lane` 41px on 312, `<summary>` 35 to 36px on 234, `.logo-btn` 40px on 105, `.btn-primary`/`.btn-secondary` 36px on 138, `.btn-bare` 25px on 72, `.chip-rail` 26px on 63, `.icon-btn-tile` 28px on 27. **The system builds to 36 and 38 and the standard says 44.** This is not a separate fix: item 40 already records that three control heights are declared and twelve render, because 192 of 317 boxed controls take their height from padding plus font size plus a border. A control that read a height token would be one edit from 44; a control that computes its height from three other values is 317 edits away.
| ~~50~~ | ~~**212 touch targets stand 44 tall and under 44 wide, and WCAG 2.5.5 asks for both**~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and the row was right that this is a layout decision rather than a floor, so it was taken as one and the cost was measured first.** The floor in `base.css` names both axes now: a second `min-width` rule for the label-bearing families beside the one the square families already had. **401 controls of 4,560 missed 44x44 at 390 with the pointer asserted coarse, and 165 of them missed WIDTH ALONE**, standing a clean 44 tall on the ladder the height pass had put them on: `.btn-bare` 31 on 72 comment actions, the range chips 36 on 36, the rules tab 36, an amount chip 37, the compact YES / NO pair 43, the feed filter chip 42. **A word decides a chip's width the way the font used to decide a control's height**, and it is the same defect one axis over. **Measured after: 401 to 120, 21 of 106 pages changed height, +647px in total at 390 and the largest single page +202 of 7,678, which is 2.6 per cent. 0 pages gained horizontal scroll at either width, and 0 of the kit's 884 specimen cells.** The 120 that remain are **115 jump links on `ui-visual/overview.html`**, which is the painted tree's own contents page and not the product, **4 footer links and 1 hero title at 43.5 rounding to 44**, and **1 policy link inside a sentence**, which is WCAG 2.5.8's inline exception. The original row: | Design System, the touch floor pass, 2026-08-08, measured at 390 on 106 screens with a coarse pointer; 244 at 1280 | The floor of 2026-08-08 declared HEIGHT only, and that was a decision rather than an oversight: these are short WORDS and a word's box comes from the word. `NO` in the compact yes/no renders **42.7** wide, 1.3px short; `1d` in the event-detail range rail is 35.9; `Reply` in the comment actions is 35.9; `All` in the feed filter is 42.2. **Widening every one to 44 is a layout decision about six components, not a floor**, because it changes what a rail and a tab row look like rather than how big a box is. The 42.7 group is 94 of the 212 and is the cheapest of them: it misses by 1.3px on one component. Owner: Stage 10 (Responsive), with the pass that reads the 81 layout dimensions |
| ~~51~~ | ~~Three native checkboxes render 18x18 on `cookie-consent.html`~~ **CLOSED 2026-08-08. The row is the target now, and the structure was decided in grey first.** `<div class="cc-cat-main">` is `<label class="cc-cat-main">` in BOTH trees, so the name, the box and the space between them are one target. **Measured after: 233x36 at 390 under a fine pointer and 233x44 under a coarse one**, against 18x18 before. The checkbox is still 18x18 and that is correct, because it is a part inside the target rather than the target. **The accessible NAME did not move**: `aria-label` stays on the input, so a screen reader still hears "Analytics cookies, off by default", which is more than the visible word and is `voice/`'s to change and not this pass's. `.cc-cat-main` joined the one touch floor in `base.css`, which is where its 44 comes from, and `cookie-consent.css` gives it `--control-36` and a pointer because it is a control now. **Re-measured over ten screens under a coarse pointer: 8 targets remain under 24x24 and every one is a text link** (`a.hh-name` 202x20 on five, `a.hh-all` 110x20, two cookie prose links at 84x23), which 2.5.8 exempts. **0 controls that a finger has to hit rather than read are under the AA floor.** The original row: | They are `<input type="checkbox">` with an `aria-label` and no `<label>` element, in `.cc-cat-main`, so the target is exactly the box and nothing else. **These are the only targets in the product that a finger has to HIT rather than read and that fall under WCAG 2.5.8's 24x24**; the other eight under 24 are text links, which the criterion exempts. They took no floor from the rule of 2026-08-08 and were deliberately left out of it: that rule names control FAMILIES the system declared, and a bare checkbox is not one. The fix is markup plus a component, not a token: wrap the box and its text in a `<label>` so the row is the target, which is what `filters.css` already does with a 1x1 visually hidden input. That is new on a screen with no component behind it, which the rules say goes into the system first. Owner: Stage 09 (Design System) |
| ~~52~~ | ~~**Sixteen component headers say the painted tree is 105 screens and it is 106**~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and the row was right about the tree and wrong about the repair, which is what it predicted about itself.** Measured from the rendered DOM of all 106 screens, per SCREEN and not per occurrence, control 0 over 465 distinct classes: **15 of the 16 headers saying 105 are correct.** Every product component stops at 105 and the screen it misses is the same one each time, `overview.html`, a contact sheet carrying the course sidebar and its own `tk-*` markup and no product chrome at all. The sixteenth is `course-chrome.css`, the only file in `components/` that stands on all 106, and it is the only one that needed the edit the row asked for. **Two other headers were wrong for a different reason and both are one shared class counted as a placement**: `oddsbar` said 9 and stands on **21**, because the header counted `.ed-oddsbar` on the event-detail states and missed `.oddsbar` on twelve feeds, which do not overlap; `seo-plate` said 11 and stands on **9**, because `.feed-seo` is declared by `patterns/browse-shell.css` as well, as the SLOT, and on `event-feed-logged-out` and `terms` the slot stands with no plate in it. **Three headers edited, aggregate error 15 screens.** Under the naive reading, counting every declared class including the ten shared ones, 9 headers would read as wrong and the error would be **315**, and the gap between 15 and 315 is the row's own sentence proved. Also found and fixed: **`.footer-soon` is painted by `footer.css` and was absent from its own `Classes:` line** while standing on all 105. Four files carry no `Stands on:` line and should not grow one, because none of them is a component: `base`, `fonts`, `index`, `tokens`. The original row: | Design System, the Stand-pointer pass, 2026-08-08 | Each file in `components/` opens with a `Stands on:` line naming a count and a sample of screens. **16 of them say 105**, 0 say 106, and 31 carry a narrower count of their own that was not checked in this pass. The `Stand:` half of the same header block was wrong on all 42 files and was fixed the same day; this half was left because **it cannot be fixed by substitution.** A real count means asking which screens actually carry each component's classes, rendered, which is a measurement and not a find-and-replace, and `patterns.html` already showed what a guessed count costs: two of six pattern headers were one screen light. It is filed with its number rather than silently corrected. Owner: Stage 12 (Handoff), with the pass that has to read every header anyway |
| ~~53~~ | ~~Six rules in the stand's own stylesheet paint classes nothing wears~~ **CLOSED 2026-08-08, and it was NINE rules over six classes.** The count in the row was of CLASSES; three of the nine rules are compound (`.tk-mc.tk-fail`, `.tk-mc.tk-fail i`, `.tk-bar.tk-bar-off>u`) and carry a live class beside a dead one. **The first removal broke the file and the check caught it**: a line-based delete took the opening line of the multi-line `.tk-rul` rule and left its continuation and closing brace behind, 361 open braces against 362 close. Restored from git and done again brace-aware. `_page.css` is 680 lines from 686, **77 classes defined and 0 of them dead, 0 worn and undefined**, and all twelve kit pages render at 390 and 1280 with 0 scroll and 0 overflow. The original row: | `.tk-bar-off`, `.tk-brd`, `.tk-fail`, `.tk-ink-bar`, `.tk-rul` and `.tk-rulers` are declared in `ui-kit/_page.css` and worn on none of the ten kit pages. They are the residue of two replacements: the declaration bars that became tables on 2026-08-08, and a ruler specimen that never shipped. Six of 83 is small and it is the same rot the file exists to avoid: **the stylesheet this one replaced reached 854 lines of which 800 painted classes nothing rendered.** Not removed in the same pass that found it, deliberately: the first count said seven and included `.tk-onlight`, which has no rule at all and was matched inside a COMMENT, so the number is only trustworthy because it was taken again with comments stripped. A deletion made on the first reading would have removed a line of prose. Owner: Stage 09 (Design System) |


---

## Found by the component pages

Design System, 2026-08-08. Seventeen component pages written in one day, seven for the remaining
atoms and ten for the molecules, each one measured in a browser at 390 and 1280 and in both pointer
branches before a word of it was written. **Every row below is something the level page could not
have shown**, which is the argument for the pages standing up on its own. Three of the nine are the
same shape: a rule that reaches some of a component's placements and not the others.

| # | Item | Source | The reading |
|---|---|---|---|
| ~~54~~ | ~~**365 account-menu rows stand 33 tall with a finger on them**~~ **CLOSED 2026-08-10 with item 40**, `decisions.md` same date. `.nav-item` is the touch floor's fifteenth family and `.nav-row` declares 44 in `navitem.css`. The row's own caution was right and the number it feared was measured rather than guessed: five rows at 44 make the account dropdown **57px taller, 169 to 226**, and it stands clear of a 900 viewport at 390 and at 1280. The other two faces are above the floor and stay: `.nav-row-stack` at 49 because it is two lines, `.nav-slot` at 55 because it is the interior of a 56 bar. | `ui-kit/navitem.html`, the floor table | Owner: was Stage 10 (Responsive) |
| ~~55~~ | ~~**The switch is 40x24, which clears 24x24 with nothing to spare and fails 44**~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and the row's own condition is what decided the shape of the fix.** It asked for the control's own box and not an invisible pseudo-element, "because a hit area no measurement in this repository can see is a fix nobody can check", and the answer honours it by inverting the arrangement: **the BUTTON is 44x44 and the track is drawn on `::before`**. An invisible pseudo stretched over a 40x24 button leaves the button 40x24 and every instrument still reads a failing control; a 44x44 button with the pill inside reads 44x44 to anything that measures it, including the focus ring, which now shows the target rather than the drawing. **The switch looks identical**, verified in a browser under both pointers: box 44x44, track 40px x 24px, knob at 14/6 and 22 checked, the same 19px inner offset it always had. Four rules that painted `background` on the button paint `::before` now; `opacity` and `cursor` stayed, because a disabled control is disabled whole. Three placements, and it is the last of the three controls that failed the project's own floor. The original row: | `ui-kit/toggle.html`, the box table | Three placements on three screens, all of them Reverse sort on Favorites. The height was 22 until 2026-08-05 and was raised to 24 to clear 2.5.8; **44 was never argued either way**, and `.toggle` is not in the touch floor for the same reason as item 54. A switch is a target you hit rather than one you aim at, so the question is real: is a 24px track reachable with a thumb. The fix is the control's own box and not an invisible pseudo-element, because a hit area no measurement in this repository can see is a fix nobody can check. Owner: Stage 10 (Responsive), with 50 and 54 |
| ~~56~~ | ~~**Two skeleton hosts of 90 do not declare themselves decoration**~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and the row was right about the two and short by six**. The two are `<article class="card skeleton">` on `event-detail-loading` and `event-detail-logged-out-loading`, in both trees: **4 attributes**, and every one of the other 88 already said `aria-hidden="true"`, so the fix was to match the file next door. **90 of 90 in both trees now.** Measured after in a browser rather than in the source: 180 rendered hosts across the 38 screens that draw one, **0 still reaching the accessibility tree**. **AND THE SAME READING FOUND WHAT THE ROW COULD NOT SEE.** A host that is `aria-hidden` is silent, which is correct, and the LOADING has to be announced by the region around it. On `my-profile-loading`, `public-profile-loading` and `wallet-loading`, in both trees, **one host of four stood outside every `aria-busy` container**: the balance card's placeholder sits above the list, and only the list was marked. So those six screens said "busy" about three quarters of what was loading. `aria-busy` is raised to the region that holds all four - `.cat-main` in the paint, `main.feed` in the grey - and after: **180 of 180 hosts sit inside an announced region, 0 screens with a skeleton outside one**. A count of hosts is not a count of what a person is told. | `ui-kit/skeleton.html`, the aria reading | Measured across the 19 screens that carry a mark: **19 of 19 carry `aria-busy`** and the two lists are the same 19 files, but **88 of 90 hosts carry `aria-hidden="true"`**. The two that do not are one each on `event-detail-loading.html` and `event-detail-logged-out-loading.html`, so those marks reach the accessibility tree as an empty `<article>`. Two attributes in two trees, grey first. It is the same two screens that hold the six marks measuring 0x0 at 390, and those six are correct: they stand inside `.bet-panel`, which is `display:none` below 760. Owner: Stage 09 (Design System) |
| ~~57~~ | ~~The feed trust strip is in the markup of 105 screens and drawn on none of them~~ | `ui-kit/trustbar.html`, 2026-08-08 | **CLOSED 2026-08-08, and the rules went.** `decisions.md` same date. **Two readings in this row were wrong and the measurement corrected both.** The grey tree did NOT still draw the strip: `wireframes/` carries **0** `.feed-trustbar` and answers the same question with two `hero-trust` tiles, so the strip existed in one tree and rendered in neither. And the content was not unique to it: the footer tiles say both of its sentences plus a third, drawn, on the same 105 screens in both trees, and on `event-feed.html` the hero says both again **using the same two sprite marks**. It failed the test this row itself proposed. What the row did not know is that the markup above the block, on all 105, still carried the argument FOR the strip in a comment, so the file and the screen disagreed in writing. 11 rules, 2 icon rules, 5 classes and 105 blocks of markup left. Proof: two trees, 424 page pairs, **428,864 element readings, 0 differences**, and 4,200 strip elements in the before tree, every one a 0x0 box |
| ~~58~~ | ~~105 of 193 filter menus stand outside `.app-case` and never get half their rules~~ | `ui-kit/filters.html`, 2026-08-08 | **Closed 2026-08-08** by the pass that took `.app-case` off 415 selectors, `decisions.md` same date. The footer chooser now takes all three of the file's summary states: 8/20 padding, the transition, the hover and the press, and **122x35 against its twin's 152x35**, the 30px being the difference between "English" and "Sort: Trending". Hover and press verified by hovering and pressing in both themes, identical values. The private `.lang-menu summary:active` copy in `footer.css` went with it: **a workaround buys the one state somebody noticed and leaves the rest missing**, which is the reason the wrapper went rather than the control being copied a second time. |
| ~~59~~ | ~~**`comments.css` explains why its actions are not 44 tall, and they are 44**~~ **CLOSED 2026-08-10**, `decisions.md` same date. **The paragraph is corrected in place rather than deleted**, because the row is about a file that stopped agreeing with the product and the record of that is worth more than a tidy file. The height was taken by a family rule the day AFTER the paragraph was written and nobody re-read it; the width followed on 2026-08-10, so all 72 stand **44x44**. **The fear in the paragraph turned out to be about the wrong axis**: it argued that 44 "would make the comment row taller than the comment", and the row was already 44 tall, so growing the box sideways cost the row nothing. The nine screens carrying comments did grow 8 to 9px each and none of it is this: it is `.ed-tablabel` in the tab bar above, item 64, in the same pass. What survives of the argument is the half that was never about a number: these read as text and not as chips, no ground, no edge, no radius, and none of that moved. The original row: | `ui-kit/comments.html`, 2026-08-08 | The file records a measured decision from 2026-08-05: the Reply and Like controls went from 23x17 to 31x25 to clear WCAG 2.5.8, and "44 was NOT taken here and that is deliberate: it is a text control in a meta line and 44 would make the comment row taller than the comment". Measured today with touch emulation on: **all 72 render 31x44 to 37x44**, because they carry `btn btn-bare` and the one touch floor written in `base.css` on 2026-08-08 names `.btn`. Neither half is obviously wrong: a family floor is what stops six files each remembering a different part of the product, and a per-control argument written down is what stops a floor breaking a layout nobody re-read. **What cannot stand is a file stating a decision the product has stopped making.** Owner: Stage 10 (Responsive), with 50, 54 and 55 |
| ~~60~~ | ~~Four related rows of 31 have no thumbnail, and the column collapses rather than empties~~ | `ui-kit/related.html`, 2026-08-08 | **CLOSED 2026-08-08, and there was nothing there.** `decisions.md` same date. Measured at both widths: **all four are on `terms.html`, they are the whole of that list, and every question in it starts at the same 68 at 390 and the same 586.5 at 1280.** None of the four is a market; the block is headed "The other documents" and the rows read *Privacy Policy / not built*. **A count without a grouping is a defect looking for a screen**: the 4 are four of four on one screen and the 27 are 27 of 27 on nine others. The 58px is real and was BUILT to check it, by cloning a row into that list and giving it a thumbnail: 126 against 68, and 644.5 against 586.5. That arrangement exists nowhere in the product, so the close is a contract rather than a rule, written into `components/related.css` and `ui-kit/related.html`: the thumbnail is optional and **the LIST decides**, every row or no row. **No CSS changed** |
| ~~61~~ | ~~**A chosen outcome row says "chosen" in colour alone once the paint hides the word**~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and the row was right in its title and wrong in its fix.** It proposed `aria-checked` or `aria-selected` on the row. **Neither is legal there**: `.opt-row` is a `<div>`, HTML-AAM maps a div to `generic`, and a state is only valid on a role that supports it - `aria-selected` belongs to gridcell, option, row and tab, `aria-checked` to checkbox, option, radio and switch. Written on a generic they enter no accessibility tree at all, which is the worst of the candidates, because the markup then READS as though the state is handled. And every role that would make them legal is **Children Presentational: True** (option, radio, checkbox, switch, tab), so it deletes the two real buttons inside the row - which is the same finding that made row 23 reject `role="button"` on this element, arriving again under a different attribute name. **What is legal is `aria-current`**, which is GLOBAL in WAI-ARIA 1.2 and therefore supported by `generic` with no role, no required parent and no effect on descendants. Written: `aria-current="true"` on the chosen row, on the 2 interactive screens per tree and the 4 kit specimens, 12 rows. Unchosen rows get nothing: `false` is the default and four of them per screen is noise. **AND THE HALF THE ROW MISSED IS THE BIGGER HALF.** It filed the accessibility tree; the same reading shows that with the word hidden the painted chosen state is a background, a border-color and a soft green swapped for a solid one - **no shape, no weight, no mark, no text - colour alone, which is 1.4.1 and costs a sighted low-vision user too**. `.opt-sel-tag{display:none}` is deleted, so the word "selected" renders again. **The rule was unscoped, so it reached further than the screens**: `ui-kit/options.html` and `ui-kit/molecules.html` carry the span in their specimens, and the stand whose job is to show the chosen face was hiding it too. The grey tree kept the word the whole time, because it links nothing from `components/`. **And the state had to be taught to move**: both click handlers on those screens moved the CLASS and the word and not the attribute, so the prototype would have told the truth until the first click and lied after it - 17 call sites over 13 documents now move all three. Verified in a browser: at rest the chosen row carries the class, the word and `aria-current` in all three trees; after clicking the third row all three moved to it; overflow 0, page errors 0 over 264 documents. **Reviewed independently before the edit**, and the review is what turned `aria-selected` into `aria-current` and found the 1.4.1 half. | `ui-kit/options.html`, 2026-08-08 | `.opt-sel-tag` is the grey tree's way of saying a row is selected, and `.app-case .opt-sel-tag{display:none}` switches it off in the painted tree, correctly, because the tint, the brass edge and the chosen YES all say it there. **What is left for a screen reader is nothing**: the row carries `.sel` as a class and no state attribute, so the selection is not in the accessibility tree at all. The fix is `aria-checked` or `aria-selected` on the row in both trees, grey first, not a colour. Owner: Stage 09 (Design System), with the states pass |
| ~~66~~ | ~~The reconcile box takes its colour from the dialog around it, and the grey tree has no outcome variant at all~~ | `ui-kit/notice.html`, 2026-08-08, the pass that unscoped `.widget-box` and `.protect` | **CLOSED 2026-08-08 as a modifier**, `.reconcile-box.rec-won` and `.reconcile-box.rec-lost`, in both trees, grey first. `decisions.md` same date. **It was three container scopes across two files, not two in one**: `.outcome-dialog`, `.win-dialog`, `.loss-dialog`, six rules in `notice.css` and one in `input.css`. The argument for keeping them was real and **two readings ended it**: the grey tree carried 6 boxes with no outcome variant, so a state that exists in one tree only is a paint job; and **a kit specimen cannot be put inside a win dialog**, so two of three faces could not be stood by anybody. The centred arrangement came along as a reading, not a guess: the outcome-sheet set and the outcome set matched exactly, 5 and 5. Compound selectors, because a bare `.rec-won` is what `.flat` was. Proof: **0 differences on all five outcome screens** over the same 424 pairs. Both faces now stand on `ui-kit/notice.html` |
| ~~72~~ | ~~**The system takes eight different widths as a breakpoint, and nothing declares any of them**~~ **CLOSED 2026-08-10**, `decisions.md` same date, in two passes on one day. **THE FOURTH QUESTION, the one the half-close left open: three of the four one-offs stay and one collapses, and every answer is a number rather than a preference.** **520 is gone**: the record's four columns arrive at DESK now, because 520 was the width at which four columns first FIT and not the width at which they first read, and from 520 to 555 the four cells are 102.5 to 108.8 wide, "Member since" takes two lines and every figure in the row stands 91 tall instead of 80.5. Two columns hold to 639 at 213 to 272.5 with nothing wrapping. It is the one collapse that was free: a width out of the system and a 36-width wrap band with it. **560 stays**, and collapsing it would run a floor backwards: applied to 639 it shrinks the three detail tiles from 36 to 28 on 79 widths of window that has room for them. What it is for was measured with it - the head's content box crosses the actions' left edge by 4px below 560 and 24 above, because three 36px tiles 8 apart at a 16 offset ask 140 and the padding gives 118 - and **no line of the question has ever crossed them**, the nearest ending 31px short at 561 and 110px short at 640. **620 and 980 stay**, because they are one card folding and not the page frame arriving: this is the only three-level grid in the product. Taking 980 to RAIL lands the feature's columns at 251 and 256 on a 901px window, **28px under the 279.5 the same card already takes at 641**, which is today's minimum and is set by the gutter rather than by the card; taking 620 to DESK costs 19 widths and 288px of height and closes nothing, since at 621 the two columns are 295.5 each, wider than that same 279.5. **AND THE MEASUREMENT FOUND WHAT THE ROW COULD NOT SEE, for the second time in one day.** The ladder was written as PAIRS, `max-width:640px` in eight files and `min-width:640px` in five, and **both match at exactly 640**, so the rung rendered a page that exists at no other width: on nine of ten screens the desk utility, the balance figure and its icon button, stood on a 14px mobile gutter under a mobile header with no bottom nav, matching neither 639 nor 641. It is `max-width:639.98px` now, and .98 rather than 639 because a zoomed window reports a fractional width and an integer bound leaves a gap where NEITHER branch applies. The same pair stood at 760. **What the pair was hiding is worse than the pair**: the desk header asks for 694px - 40 of gutter, 36 + 8 + 149 + 8 + 88 down the left, 8, a 317px utility, 40 of gutter - and it turned on at 641, so **73 of the 106 painted screens took horizontal scroll from 641 to 652 and kept a right gutter under its 40 until 693**. The 73 are exactly the signed-in screens; the other 33 carry two auth buttons where the balance figure stands. `.hiw-btn` is 88 of it and the only control in the row carrying a word rather than a mark, so it waits for DETAIL now: without it the row asks 598 and fits from 641 on, and at 760 it asks 694 with 66 to spare. After: **overflow 73 to 0, no rung is a state of its own at 640, 760 or 900, and the element sweep differs at 520 to 639 (the record), at 640 and at 760 and nowhere else**. The earlier record: **Two of the three questions are answered and the third is the one that needed a measurement.** (1) **The row asked for a token and CSS cannot give one**: a media query condition does not read a custom property, `@custom-media` is unimplemented in every browser, and this repository has no build step to compile either, so `--bp-rail:900px` would be a value in the one place that lies. The ladder is declared by being READ, in the page frame of `tokens.css`, and **all 31 media rules now carry one line naming their rung or saying they are not one**. (2) **The eight widths were never eight decisions**: three are product rungs named by what arrives at them, 640 DESK (13 rules), 760 DETAIL (5), 900 RAIL (6); four are one-offs doing one local job, 520, 560, 620 and 980; and **the eighth was not the product at all**. (3) **860 was the review sidebar, and it was the defect the row could not see.** `course-chrome.css` says "Not product" in its first line and it docked a 220px panel 40px BELOW the widest product rung, so from 860 up every painted screen ran a branch chosen for a window 220px wider than the box it landed in. A media query reads the WINDOW and a layout gets the CONTAINER. Measured, 160 pages x 9 widths, control 0 of 1,440: `.cat-main` **530 to 297** at 900 and under 530 until 1134, narrower than the 360 phone this product is designed from; `.ed-main` **430 to 211** across one pixel, 859 to 860, because `.bet-panel` is `flex:0 0 322px` and does not hand the space back; and **73 of 160 pages took horizontal scroll at 860 and at no other width**, invisible to every audit here because they all read 390 and 1280. It is **1140** now, which is 900 + 220 + 20: **overflow 73 to 0, content columns under 360 outside the phone 59 to 0, and 0 differences at 360, 640, 859, 1140 and 1280**, so the change is contained in the band it was wrong in. **WHAT STAYS OPEN is the fourth question and only that**: whether 520, 560, 620 and 980 collapse onto the ladder. Each moves layout in a real band, so it is a measurement at nine widths in two themes and not a rename. Owner: Stage 10 (Responsive). The original row: | `components/`, 2026-08-09, the census taken to check a label's 900 | **31 media rules in 24 files at eight distinct widths**: 640 (13 rules), 900 (6), 760 (5), 860 (2), 560 (2), and one each at 520, 620 and 980. Not one of them is a token, so a width is typed as a literal in the one place a theme, a reader and this repository's own tooling cannot see it, and the file that reaches for a breakpoint reaches for whichever one its author had in mind. **The kit has just paid for this**: a stand label was written at 900, which is a real breakpoint belonging to five other components, and it stood beside a bar that goes at 640 and a panel that arrives at 760 for a day, silently wrong in a 260px band. The question is not "fewer breakpoints", it is whether these eight are a ladder with rungs that can be named or a pile of eight decisions taken separately. **Filed rather than taken because collapsing 860 into 900 or 620 into 640 moves layout on real screens**, which is a measurement across both trees at eight widths and not a rename. Owner: Stage 10 (Responsive) |
| ~~80~~ | ~~**Both header menus open off the right edge of the window, on 105 screens**~~ **CLOSED 2026-08-09 the day it was found**, `decisions.md` same date. `components/header.css` positioned the dropdown twice: lines 22 and 36 say `right:0`, a later rule at the same specificity added `left:0`, and with both offsets resolved LTR takes `left`. **At 390 the notification panel stood 142px past the right edge of a 390px window, 55 per cent of a 260px panel off-screen**, and the account menu 126px; at 1280, 116 and 100. **Nothing had ever opened them**: every sweep here reads the document as it loads and a `<details>` is shut then, so the panel had no box, no contrast and no target. Found by building the header's own kit page and opening the menu on purpose. 316 menus opened across both trees at both widths after the fix, **0 off-window or mis-aligned**. | `ui-kit/header.html`, 2026-08-09 | Owner: Stage 09 (Design System) |
| ~~79~~ | ~~**Twenty-one component pages still show less of their component than the product does**~~ **CLOSED 2026-08-09**, `decisions.md` same date. All 22 rebuilt from the product's own markup, the winning instance picked by rendering all 106 screens rather than by choosing one: **0 of 38 pages poorer than the product**, against 22 when the row was opened. `feed` 0 to 630, `tabs` 4 to 186, `event-detail` 5 to 186, `footer` 17 to 161, `header` 8 to 72, `hiw-dialog` 19 to 61. Reaching parity on the feed meant writing out what three page scripts do at run time, because a stand does not run them. And the product's markup brought its ids: **16 duplicates over 8 pages**, which is item 45 arriving from the other direction, fixed by making every cell's ids unique rather than by drawing the component once. Verified over both protocols: 320 renders, 3,439 glyphs drawn, 0 empty, 0 unpainted, 316 menus opened and 0 mis-placed, 0 scroll, 0 clipped cells, **0 duplicate ids**. | `ui-kit/`, measured 2026-08-09 across all 38 components with a page | Measured without a hand-written selector list: each `components/*.css` names its own classes in its header line, and for each component the biggest subtree wearing any of them was counted on its own page, on its level shelf and across 106 painted screens. **22 of 38 were poorer than the best specimen elsewhere**, which is upside down against this kit's own rule that a shelf gives one specimen and a page exists for the room a shelf has not got. `header` is rebuilt and closed; the rest, by gap: **`feed` 0 against 630, `tabs` 4 against 186, `event-detail` 5 against 186, `footer` 17 against 161, `hiw-dialog` 19 against 61**, then `toc` 32 short, `dialog` 30, `comments` 29, `catnav` 22, `betpanel` 21, `bets-table` 20, `filters` 16, `options` 15, `seo-plate` 14, `chart` 11, `toast` 8, `cookie-consent` 6, `market` 5, `state-block` 5, `button` 3, `position` 3, `iconbtn` 2. **The fix is the same every time and it is not a redesign**: take the container and the markup the screens ship, and where the product writes something with a script (the odds bar, the sub-category rail, the chart line) write it by hand and say so. Owner: Stage 09 (Design System) |
| ~~78~~ | ~~**Two component rules paint a filled glyph with nothing, and `getBBox` cannot see it**~~ **CLOSED 2026-08-09, and the row number was never struck until 2026-08-10**, which is item 74 arriving a fourth time: the text said CLOSED and the number said open, so this file counted it as open for a day. The fix is in `base.css`, `svg.ic:has(use),svg.ic-sm:has(use){fill:currentColor!important;stroke:none!important}`, verified present. | `components/`, 2026-08-09, found by measuring PAINT after the sprite became a script | `.footer-trust .tr-ic` and `.toast .ic-sm` both declare `fill:none`, correctly, for the stroked mark they were written for. Both are (0,2,0); the floor `svg.ic:has(use){fill:currentColor}` was (0,1,2). So a filled glyph in either place took **fill:none from the component and `stroke:none!important` from the floor, and painted nothing**: three trust marks per screen and the toast's error mark, invisible since the filled family arrived. **CLOSED the same day** by naming both properties in the floor, `fill:currentColor!important`, which is the shape the stroke beside it already had. 692 unpainted references went to 0. The row is kept open-then-closed rather than left out, because **this is the third instance of one trap** and the count is the argument: a rule that paints an icon must name both properties, and no sweep that reads geometry can find the ones that do not. Owner: Stage 09 (Design System) |
| ~~75~~ | ~~**This product draws its close cross two ways, and the row that found it was wrong about how**~~ **PREMISE CORRECTED 2026-08-10, THEN CLOSED THE SAME DAY WITH 88**, `decisions.md` same date. **The second half of the row was wrong too, and the answer is a number.** "One job, two drawings" was filed beside the warning mark that really was a circle on 16 screens and a triangle on 4, and this is not that: measured in a browser, the sheet close is a 12-unit cross in a 24 viewBox at stroke 2.2, rendered in a 16px svg inside a 32px button, so its ink spans **9.47px, 29.6% of the button**; the small face's two bars are 8 x 2 rotated 45 degrees in a 24px button, so their ink spans **7.07px, 29.5%**. **Two techniques, one mark, the same optical size to within half a per cent.** The technique differs because one control carries an svg and the other carries the letter the grey tree draws, and `iconbtn.css` had argued the geometry correctly all along: 12 in 24 would be an X pressed against its own edge. What was actually wrong was in the kit and it was markup, which is 88. The original row: | `ui-kit/iconbtn.html`, 2026-08-09; corrected by attempting the fix | The row said `.toast-close` "holds 0 `<svg>`, and its content is the text character `x`" **with nothing on `::before` or `::after`**. The first half is true and the second is not: `iconbtn.css` draws the cross with **two rotated bars on `::before` and `::after`**, `--size-8` in a 24 box, with a comment arguing that 12 in 24 would be an X pressed against its own edge, and the button carries `font-size:0`. So the letter draws nothing in the painted tree. **It was removed on that reading and the measurement said no: in the GREY tree the letter is the whole drawing**, because `wireframes/` styles `.toast-close` as a bordered box at font-size 11 with no pseudo-element cross, and an empty button measured **16 x 8 with nothing in it**. All 22 elements are back, byte-identical. **The same markup is inert in one tree and load-bearing in the other**, which no reading of one tree could find. **What is left is the real question, and it is smaller**: a close cross is a stroked path `M6 6l12 12M18 6L6 18` at 32 and 24 on 333 controls, and two CSS bars at 24 on four, and a third treatment stands on `ui-kit/vitrine.html` where the button is empty and drawn entirely by the pseudo-elements. Three treatments of one mark. It is a decision to confirm rather than a defect to fix, and the grey tree has to be part of the answer. Owner: Stage 09 (Design System) |
| ~~73~~ | ~~**`DESIGN.md` still says the build fails on three gates, and there is no build**~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and it was four gates, not three**: 20 was in the fonts paragraph as well. The fix was not a deletion. Each sentence names a real invariant, so **each now carries the reason it exists**, which is the thing a gate never told anybody. Two stale pointers went with them: `python3 ui-kit/_levels.py --order`, deleted with the other 62 scripts, and an architecture document that moved to `docs/kit-archive/` and describes 41 gates that no longer exist. | `DESIGN.md` section 4, 2026-08-09 | Owner: was Stage 12 (Handoff) |
| ~~84~~ | ~~**The price chart's legend item is declared and drawn in the bets table's stylesheet**~~ **CLOSED 2026-08-10**, `decisions.md` same date. **The row asked for the reason before the move and the reason was findable.** `git log -S` puts `.lg-item` into `bets-table.css` on 2026-07-26, the day the flat kit was split into 38 files, and the flat kit at the commit before that split has the three rules **directly under `.ed-legend`, beneath a comment reading "multi-outcome chart: one line per outcome + legend"**. A mechanical misfile at the split, not a home the rule had earned. Moved back with a comment on both sides. **The instrument needed one more repair first**: with animation frozen the control still gave 165 differing rows of 15,802 on the first pair of passes and 0 on every pair after, because **the first pass through a fresh page is a cold pass**. A warm-up pass is taken and thrown away now. Control 0 of 15,802; the real comparison over 17 documents, **0 rows differing**. | `ui-kit/chart.html`, 2026-08-10 | Owner: was Stage 09 (Design System) |
| ~~85~~ | ~~**Eight classes stand in the product markup and no rule anywhere reaches them**~~ **CLOSED 2026-08-10**, `decisions.md` same date. Read rather than counted, they split three ways. **Five were inert and are gone from all three trees**, 129 class tokens across 46 files: `ed-act` (27), `load-more` (9), `ed-market` (9), `cmt-post` (7), `toast-wrap` (1). Each sat beside classes that already draw the element, and `.ed-act` was a third name on a button that is already `.icon-btn.icon-btn-tile`. **Three were script hooks and are declared as such now**, under a new `Script hooks:` line in the file that owns the block: `.ed-chart` and `.ed-chart-multi` in `chart.css`, `.rules-panel` in `tabs.css`. **One was a false finding**, `prov-google`, answered in `button.css` before the sweep ever ran. **The proof needed the instrument repaired first**: the before-and-after read 31 files as different, and re-reading the SAME tree twice with no edit gave 3,587 differing rows of 18,390, all of them entrance animations caught mid-flight. With `animation:none` injected and 450ms of settle the control ran 0 of 18,409, and the real comparison then gave **0 rows differing over 19 screens**. | `ui-visual/`, 2026-08-10, found by the whole-system sweep | Owner: was Stage 09 (Design System) |
| ~~86~~ | ~~**Six of seven patterns declare no classes, and nineteen classes are styled by a component and claimed by none**~~ **CLOSED 2026-08-10**, `decisions.md` same date. 45 files carry a class list now, **436 declarations, 0 unclaimed**. The six patterns got theirs from what they style; the loose classes went to the file that draws them, `sel` to each of the five that style it compound, the width utilities to the three that use them. **The point of it was proved within the minute**: re-running the whole-system sweep against the corrected map opened **four holes that had been invisible because nobody had declared the classes** - `.scrolled` on the live header of 57 screens, `.win-dialog` on 4, `.ed-rules` on 9 and `.read-col` on 1. All four closed the same hour. **A sweep is only as complete as the declaration it reads.** | `components/`, 2026-08-10, the whole-system sweep | Owner: was Stage 12 (Handoff) |
| ~~83~~ | ~~**"Confirm bet" opens the sign-in sheet on six screens where the person is already signed in**~~ **CLOSED 2026-08-10**, `decisions.md` same date. A wire soldered to the wrong pin, not a design: the destination already existed and was already linked from the same block, the bet-error panel's own `Try again` going to `event-detail-bet-processing.html`. **14 controls rewired across both trees**, the two logged-out screens keeping the sign-in sheet because there it is right. Boxes unchanged: 288x48 in the panel, 108x48 in the dock, at both widths. | `ui-visual/`, 2026-08-09 | Owner: was Stage 09 (Design System) |
| ~~87~~ | ~~**The grey tree and the painted tree ship a structurally different bet dock on four screens**~~ **CLOSED 2026-08-11 BY RECONCILING THE GREY TREE UP TO ITS OWN CONTRACT**, `decisions.md` same date. The sheet is not a paint invention: `ia/docs/sitemap.md` and `wireframes/_conventions.md` have both specified "a bottom dock that expands to a sheet on mobile" since before either tree was built, the grey file's own comment above the dock says so over a dock that confirmed, and the grey stylesheet has carried `.bet-sheet .bp-amount-row` with **0 elements wearing it** the whole time. The grey dock is the CHOOSER now on the four screens that diverged, the sheet is a `<dialog>` plus a grab handle plus the file's own panel body, and the four bet-state screens are left alone because the confirmer is correct there in both trees. **Three defects in the paint went with it**: the sheet's Confirm opened sign-in on the screen where the person is signed in; the binary sheet read a $0.20 fee against the panel's $0.40 five lines above it; and `YES selected` was the one string in the sheet with no row in the inventory.  | `wireframes/` against `ui-visual/`, 2026-08-10, found while rewiring 83 | On `event-detail`, `event-detail-multi` and both logged-out twins the **painted dock is the CHOOSER**, two sides carrying `data-open-sheet`, and the **grey dock is the CONFIRMER**, two sides plus the stake, the payout and its own Confirm. Four screens, and they are not two states of one thing: one opens the bet sheet and the other places the bet. **The grey tree has no bet sheet at all** - `wireframes/` carries a `.bet-sheet` RULE in its own stylesheet and 0 elements wearing it, so the sheet and the chooser were added in paint and never back-ported. That is the layer boundary run backwards: `CLAUDE.md` says a block is decided in grey and the colour copy follows. **It is filed rather than fixed because back-porting a sheet into the grey tree is wireframe work of real size**, and because the alternative reading is also live: the sheet may be a paint-era decision that the grey tree should record rather than draw. `wireframes/_conventions.md` owns which. Owner: Stage 09 (Design System), with the conventions document |
| ~~88~~ | ~~**The kit shows three different treatments of one close mark, and one of them is the letter the grey tree draws**~~ **CLOSED 2026-08-10**, `decisions.md` same date. **The three treatments were the KIT's, not the system's**, and the row had the count right and the owner wrong. Read across all three trees: **28 small close controls, and the product ships one markup on all of them** - `<button class="icon-btn icon-btn-small toast-close" aria-label="Dismiss">x</button>`, 4 painted and 4 grey. The kit shipped three: **10 with the letter, 6 empty on `vitrine.html`, and 4 on `toast.html` with an `svg.ic-sm` cross inside the button**. That last one is not a variant, it is a defect with a picture: the bars draw from `::before` and `::after` regardless, so those four drew the mark **twice, a brass 9.47px path over a grey 7.07px pair of bars**, in the theme figure whose job is to show what ships. The page's own anti-rule says "never redraw the dismiss here". All 20 kit specimens carry the product's markup now: **28 of 28 in three trees are one treatment**, measured after. The two DRAWINGS stay and 75 holds the number that says why. The vitrine's label said "the cross is two pseudo-elements, no glyph" and is corrected: the glyph is there and it is the grey tree's whole drawing. Owner was: | `components/iconbtn.css`, 2026-08-10, what survived of row 75 | Inherited from 75 and kept separate because 75 is about the premise and this is about the mark. `.icon-btn-photo.sheet-close` draws a stroked path `M6 6l12 12M18 6L6 18` on **333 controls**; `.icon-btn-small` draws two rotated CSS bars at `--size-8` on **4**; and `ui-kit/vitrine.html` stands the same button **empty**, drawn entirely by the pseudo-elements, while `ui-kit/toast.html` and `ui-kit/iconbtn.html` put the letter `x` inside it. **Three treatments in the system and three again in the kit that shows the system.** The file argues its geometry (12 in 24 would be an X pressed against its own edge) so this is a decision to confirm, not a defect: either the small face keeps its bars and the kit stops showing three versions of one button, or the path scales down and the bars go. **The grey tree has to be in the answer**, because there the letter is the only drawing. Owner: Stage 09 (Design System) |
| ~~89~~ | ~~**818 buttons in this product are wrapped in a link, and an anchor may not contain interactive content**~~ **CLOSED 2026-08-11, THE BUTTON BECAME THE ANCHOR, AND THE SYSTEM WENT FIRST.** 24 widenings across `base.css`, `button.css`, `tabs.css`, `yesno.css` and `dialog.css` before one element moved, every one a strict superset, nothing deleted until `a > button` read 0 in all 264 documents. **THREE PROPERTIES WERE THE `<button>` ELEMENT'S OWN DEFAULTS AND NOBODY HAD WRITTEN THEM DOWN**: `text-decoration:none` (missing from `.btn`, 166 controls), `text-align:center` (missing from `.yesno` and `.tabs`, 614 controls) and `display:inline-block` (`.tabs`). That is the third time this system has paid for the same discovery, after `.chip` and `.icon-btn` on 2026-08-07. **The grey tree is a second half nobody had counted**: it links no stylesheet at all, 104 inline `<style>` blocks and 1,605 occurrences of a selector keyed to the tag, so seven of its own rules had to be widened in 103 files before its markup could move. F5 was not a mechanical unwrap: a tablist whose children are links owns nothing, and these tabs navigate to another document with no tabpanel anywhere, so `role="tablist"` and `role="tab"` came OFF and the row is a `<nav>` with `aria-current="page"`.  | all three trees, 2026-08-10, found by the touch-target walk that was looking for something else | The shape is `<a href="event-detail.html"><button type="button">NO</button></a>`, and it is the compact YES / NO pair on the feed card plus every other place a control has to navigate. **324 in `ui-visual/`, 326 in `wireframes/`, 168 in `ui-kit/`, on 78 of the 106 painted screens.** HTML's content model for `<a>` is transparent with one prohibition, and interactive content is the prohibition, so this is invalid markup rather than a style question. **What it costs is two things at once**: the accessibility tree reports a link containing a button, so a screen reader announces a control that is two controls, and there are two tab stops and two hit targets stacked on one visual object. It also hid a real measurement: the anchor and the button are separately sized, and each was separately short. **SURVEYED IN FULL 2026-08-10 AND THE ANSWER IS DECIDED, NOT THE SWEEP.** Three independent instruments made to agree (a stack-tracking parser, ripgrep, and the browser's own DOM over all 106 painted screens): **818, 324, 326 and 168 are exact**, and the one number that fails re-reading is the screen count, **77 of 106 and not 78**. Two ways the census was wrong before it was right, both of which a sweep must avoid: `<a[^>]*>` matches `<article`, which returned 882; and a `--glob '!old/**'` that silently did not fire added the 558 instances in `ui-visual/old/`. **THE LOAD-BEARING FACT IS THAT THE WRAPPER CARRIES EVERYTHING AND THE BUTTON CARRIES NOTHING**: all 818 anchors carry `href` and no other attribute at all, and all 818 buttons carry **0 `data-*`, 0 `on*` handlers and 0 `id`**. No script in any tree binds a click to one of them; the two `.opt-row` handlers are scoped to `#edOutcomes`, which exists only on the two screens where the buttons are NOT wrapped. Clicking the inner button really does navigate, through the wrapper, proved in a browser. **So the answer is: the BUTTON BECOMES THE ANCHOR** - delete the `<button>`, move its `class`, `role`, `aria-selected` and `aria-label` onto the surviving `<a href>`. Not 'the button becomes a span', which leaves the link as the only control anyway; not 'the anchor becomes a class', which replaces working native navigation with 650 new handlers in a static prototype. **10 structural families**, F1 feed card pair 352, F2 outcome row pair 212, F4 state block actions 108, F5 tabs 40, F6 dialog close 38, F7 dialog body CTA 28, F3 hero CTA 10, F8 bet panel CTA 14, F9 bet dock 10, F10 action bar 6. **THE COST IS THE SYSTEM AND NOT THE MARKUP**: 35 selectors in `components/` use the `button` TYPE and about twenty of them reach these controls - `base.css` `button`, `.tabs button` and `.yesno button` including the touch floor, all of `tabs.css`'s tab faces, sixteen paired rules in `yesno.css`, and `patterns/action-bar.css` - so an `<a>` wearing the same class inherits none of them and gains the User Agent's link underline instead. Every one has to be widened to `:is(a,button)` or keyed to the class, **in the system first and before a single element moves**, because `yesno.css` already records what removing a selector half early costs: four screens shipped a YES with no colour. Specificity works out: `.yesno > a.yes` is (0,2,1) and so is the `.yesno > button.yes` half beside it. **THREE DIVERGENCES WERE HIDING INSIDE 'CONSISTENT ACROSS ALL THREE TREES'**, which is the row's own sentence turned over: the grey tree is 2 elements ahead on the stale bet dock that is **row 87**; **11 of the 17 dialog-close anchors point at a different page in each tree**, which is **row 97**; and `ui-kit/state-block.html` documents two button classes standing nowhere in the product. Also found: `role="tablist"` on 9 screens per tree owns links rather than tabs, because the anchor sits between them, so F5 fixes a broken ARIA relationship in the same edit; **85 of the 324 painted instances are under 44px on one axis**, which is what the row means by 'each was separately short'; and in `ui-kit/` 104 of 168 hrefs are `#` and 24 point at a file that does not exist. **The order is: reconcile or scope out row 87, then `components/`, then `wireframes/`, then `ui-kit/` and `ui-visual/` together with row 96 in the same pass.** The full survey, with the verbatim markup of all ten families and the per-file counts, was taken this day. Still open, because it is a system change and a 818-element sweep and it is one pass rather than a passing edit. The original row: **It is consistent across all three trees, which is exactly why no tree-against-tree comparison ever caught it** and why it has to be decided rather than swept: the wrapper is almost certainly how the clickable prototype navigates, in which case the answer is that the CONTROL navigates (a button with a handler, or a link styled as the control) and the wrapper comes off in the same pass in all three trees. CSS cannot reach it. **The grey tree owns structure, so it decides first**, and 818 elements is a wireframe change of real size. Owner: Stage 12 (Handoff), with 87 |
| ~~90~~ | ~~**Four kit cells clip their specimen sideways, and the sweep that declared zero was measuring the other axis**~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and the four cells were the symptom of a product defect nobody could see**. The decision the row named - either the cell declares itself width-conditional or the specimen is cut - turned out to be the wrong pair, because the specimen was not too big for the cell: **the component overflows itself**. Measured on the four painted screens that carry holdings, with the panel's radio CHECKED: `.hold-cols` stood at 275 wide at 360 and 305 at 390 with a scrollWidth of **377**, so the block overflowed itself by **102px and 72px**. `.hold-col` declares `flex:1` and a flex item's `min-width` is `auto`, so it may not shrink below its content: the file already knew the fix and had written it three lines up, `.act-txt{flex:1;min-width:0}`. That halved it, and the rest was one word - `polly_predicts` and `hedge_hannah` are unbreakable 88 and 90px tokens - answered with the ellipsis `.hh-name` already gives the same datum one component over. **After: overflow 0 at 360, 390 and 640, and the kit's four clipped cells are 0.** **WHY NO SWEEP HAD SEEN IT is the part worth keeping**: the holdings panel is behind a CSS radio tab, so at rest it measures zero and every overflow pass skipped it. That is the third costume of one trap, after the closed `<details>` and the shut `<dialog>`: **a state that is a page is measurable, and a state that is a checked input has to be checked first**. The original row: | `ui-kit/bets-table.html`, 2026-08-10 | The four are both theme cells of "The three lists, as the screen ships them" and both of "The three, measured behind their tabs", at 390 only: **client 360 against scroll 391 to 393**, heights exact. Proved to pre-date this pass by measuring the tree with the change stashed: identical four. **The row exists because of how they were missed, not because 31px is urgent.** The 2026-08-09 sweep that took 6 clipped figures to 0 read `scrollHeight` against `clientHeight`, because the defect it was chasing was a dialog caught mid-animation; nothing read the width, and `.tk-theme-fig` declares `overflow-x:auto` so a cell that scrolls sideways looks deliberate. **A bets table wider than a phone cell may well BE deliberate**, which is the decision: either the cell says so the way the kit's other width-conditional cells do, or the specimen is cut to what a 360 cell holds. Owner: Stage 09 (Design System) |
| ~~91~~ | ~~**The review drawer's button covers the first 34px of the product's logo, and the rule written to prevent it has never once applied**~~ **CLOSED 2026-08-10 the same day it was opened**, `decisions.md` same date. **THE CHROME MOVED AND THE PRODUCT DID NOT**, which is the decision the dock took that morning, applied to paint instead of to width: an indent would have pushed the brand 42px off the column every line under it aligns to, in the review build only, for a tool. Which corner was measured rather than argued - 160 documents at 390, 700 and 1280, five candidate boxes, classifying what sits under each - and **bottom right is empty on 147 of 160 pages** above the desk rung, against top-left covering the brand on 88 and top-right a control on 89. It is also the corner a left drawer never reaches, so the button is now clickable with the panel open, which at top-left it was not: it drew ON the panel it had opened. Below the desk rung it lifts to **132**, which is the sum of the two things the product stacks on that edge: `.bottom-nav` is sticky at bottom 0 and 56 tall, `.bet-dock` is sticky at bottom 52 and **68 tall in all 48 readings of it**, so the dock's top sits exactly 120 from the edge and 132 is that plus the same 12 gap the button uses elsewhere. From the rung up the lift is dropped, because the nav is gone and the dock reserves 52px under itself for a nav that is not there. **The first number written here was 68 and it was wrong**, which is the part worth keeping: correct arithmetic for one of the two bars and blind to the other, and blind because the first reading took every page at scroll 0, where a sticky dock has not stuck yet. Re-read at the top, the middle and the end of every page: a lift of 68 lands on the bet dock in **32 readings of 960** and a lift of 12 on a nav item in **63**. **Reading a sticky element without scrolling is reading the source again.** And it still cannot be perfect: a sticky nav sits at the viewport's bottom on a long page and at the content's bottom on a short one, so no fixed offset tracks it and about ten readings of 960 land on a nav item that stopped short of the edge. Tuning until those go is refused - **120 measured slightly better and is a height fitted to today's content**, which is the mistake row 72 closed at 520 in the same pass. After: **the button overlaps the header on 0 of 160 documents at every width from 360 to 1139**, and read again scrolled - 160 documents at 390 and 700, top, middle and end, 960 readings - **overflow 0, header overlap 0, bet dock 0, a nav item 10, a page control 46, and 904 surface or text**, with the whole count at 700 being three controls. It is the topmost thing at its own centre on 858 of 960, and the missing 102 are exactly the 17 screens that ship with a modal sheet open times two widths times three scroll positions: a modal is in the top layer and made the page inert there in both positions. The dead `padding-left` rule is deleted rather than revived, with the reason kept where it stood. The original row: | `components/header.css` against `components/course-chrome.css`, 2026-08-10, found by the ladder pass | `.rm-toggle` is `position:fixed` at 12/12 and 36x36, so it occupies x 12 to 48 on every screen below the 1140 dock. `header.css` answers that inside its DESK block with `.app-header .row{padding-left:var(--space-56)}`, and **that declaration has never applied**: `.app-header .row` is set again at the same (0,2,0) thirty-four lines further down with `padding-left:var(--gutter)`, and equal specificity is decided by source order, so the later rule takes the property back. Measured across all 160 documents at ten widths in both themes: the row's padding-left computes 14px on a phone, the logo starts at x=14, and **the toggle covers 34px of it on 107 pages at every width from 360 to 1139**. Above the divide the gutter is 40 and it covers 8px of the hamburger instead; on the kit pages it is 3px. Its own scope was wrong too, even alive: the block stops at the DESK rung and the toggle exists to 1139. **Two things have to be decided and only one is this file's.** The dead rule is kept and marked rather than deleted, because deleting it removes the only evidence that somebody meant to prevent this. The live question is WHERE a review tool's button goes, and **there is no free corner**: the header owns the top, the bottom nav owns the bottom below 640, and the gutter is 14px wide on a phone. So it is either the product indenting for the harness - `body:has(#rmSidebar)` in the product's own file, the idiom `base.css` already uses for the 220px dock inset - or the toggle leaving the brand row. It changes every screenshot in the review tree, which is why it is a row and not an edit taken in passing. Owner: Stage 12 (Handoff), with 44 |
| ~~94~~ | ~~**The amount chips start at $5 on 216 documents and the minimum is now $1**~~ **CLOSED 2026-08-10, AND THE ROW'S OWN NUMBER WAS WRONG BY 197.** 216 counted every `.chip-amount` in the trees and the product has **two different chip sets**: the DEPOSIT dialog carries $10 / $20 / $50 / $100 on 97 painted and 96 grey documents, where $10 is right because the deposit minimum is $10, and the BET panel carries the set this row is about on **8 painted, 8 grey and 3 kit, 19 documents**. A count of a class is not a count of a decision. Applied: the bet ladder is **$1 / $5 / $10 / $25 with $5 selected**, 27 chip sets over 21 documents, and the two insufficient-funds screens keep $25 selected because that is the state they are showing. **And the field's own guard was wrong too**: `min="0"` on 35 bet inputs became `min="1"`, so the decision is enforced by the browser and not only by a sentence. The original row: | all three trees, 2026-08-10, found by applying the decision in row 7 | The bet panel's preset amounts are **$5 / $10 / $25 / $50 with $5 selected**, measured on **105 painted screens, 104 grey screens and 7 kit pages**. `PRODUCT.md` has said "$1 / $5 bet sizing (low min, $5 default)" since the CJM pass and the chips have never carried a $1. Row 7 decided a $1 minimum on 2026-08-10, so the smallest size the product allows is now a size its own control cannot express: a person who wants to try a dollar has to type it. **The presets are not the limit** - the minimum is enforced by the field, not by the chips - which is why this is a row and not part of row 7: changing them is a copy-and-markup sweep over 216 documents including the frozen grey tree, and the shape it should take is a product call. Recommended: **$1 / $5 / $10 / $25 with $5 still the default**, which keeps the default where the research put it and adds the try-it step at the bottom. Owner: Stage 12 (Handoff), or a one-off reconcile if the sweep is approved |
| ~~98~~ | ~~**The how-it-works sheet draws the shared sheet head a second time under a second name**~~ **ANSWERED 2026-08-11 IN THE OTHER DIRECTION, AND FOUR OF ITS SIX NUMBERS ARE CLOSED.** Re-measured after rows 15 and 18 cut the file, with both sheets open on one page in both themes, 823 computed properties per element: **the head is 812 of 823 identical, the pattern 822 of 823, the glow 813 of 823 and the body 813 of 823**, and the row's five differences are six. Settled in `components/hiw.css`, each to the value the FAMILY agrees on rather than the one written first: **radial stop 92 to 96** (two rules and 434 elements against one rule and 196, and the win head is 96 too), **pattern opacity .24 to .2** (an opacity answers to the GROUND, the outcome head has its own ground and its own .16, and this head has the plain head's two roles exactly in both themes), and **the glow to 210 / blur 42 / .42 / 60 per cent** (the 224 was a compensation for a wider sheet that held the wash at 37.2 per cent of a 462px head against 37.8 of a 418px one and at 18.7 on the 922px page, so it already failed on the block's other host). **What is NOT done is the rename, and it is refused rather than deferred**: it moves the duplication instead of removing it. That argument is row 108. The padding stays 32/24/28 on the page and 24/20/20 on the sheet; the body padding is row 104; the page host is row 105; the ring the glow change invalidates is row 106. The original row:  | `components/hiw-dialog.css` against `components/dialog.css`, 2026-08-10, found by the row 15 and 18 survey | Compared property by property with both sheets open on one page, in both themes: `.hiw-hero` against `dialog.app-dialog:not(.outcome-dialog) .sheet-head` is **20 of 24 properties identical**, the `::before` pattern **21 of 24** (same data URI, same mask, same repeat, same size), the glow **18 of 24**, the body **21 of 24**. What differs is five numbers that nothing declares as a decision: a radial stop 96 against 92, padding 24/20/20 against 32/24/28, pattern opacity .2 against .24, glow opacity .42 against .5 with a 42px blur against 40px and a 210 box against 224, and body padding 8 against 20. The glow is even drawn two ways, as `::after` on one head and as a real `<span class="hiw-glow">` on the other. **This is `.hiw-close` against `.sheet-close` from 2026-08-05 one layer up**: the same ZONE written twice under a second name, agreeing on everything except a handful of values nobody chose. Row 15 says the two have "the same zone set, zone for zone"; the measurement says more, that they are the same zones. **It is filed separately from 15 on purpose and the reason is the same one row 97 carries**: renaming `.hiw-hero` to `.sheet-head` and `.hiw-body` to `.sheet-body` is markup in 107 painted files, 2 kit specimens and the grey tree, while the 15 and 18 edit is provably inert. Two changes, two measurements, or neither result can be read. Owner: Stage 09 (Design System), after 15 |
| ~~101~~ | ~~**Eleven selectors stand on the painted screens and on no stand, and the largest of them is the whole tap target of My Bets**~~ **CLOSED 2026-08-11, AND TWO OF THE ELEVEN WERE NOT MISSING SPECIMENS AT ALL.** Every count in the row held when re-read from real element trees. Two are the second half of a rule whose first half already had a stand (`.ptab-panel>.pos .pos-figures` twice on `tabs.html`, `.bet-panel a:has(button)` four times on `event-detail.html`), and **two are specimens that DROPPED THE ANCHOR**: `ui-kit/betpanel.html` shipped the confirmer's Confirm bare where the screen wraps it, and `ui-kit/dialog.html` shipped the deposit body without either of its two trailing controls. That is the card with its photograph left out and the dialog shown shut, a third time: **a specimen with one element missing looks finished.** New sections on `position.html` and `card.html`, additions inside `notice.html#two` and `position.html#record`, and the two repairs.  | `ui-kit/` against `ui-visual/`, 2026-08-10, found by the row 19 and 16d survey | Every declared class in `card`, `notice`, `position` and `filters` is worn somewhere, so the class map is clean; **eleven SELECTORS are not**. In order of what they cost: **`a:has(>.pos)` and its four state rules, 14 placements, 0 specimens**, which is the row-as-control on My Bets and Notifications, with a hover and a press that `position.css` spends 22 lines justifying and that cannot be seen on a stand; **`p.pos-status` and its `::before`, 6 placements, 0 specimens**, the brass-ticked heading between groups, where the kit shows the `span` form and not the `p`; the two portfolio-summary rules at 1 placement each; and `.outcome-dialog .protect` (5), `.cat-main>.pos .pos-figures` (2), `dialog.app-dialog .sheet-body>a` (14), `.bet-dock a:has(button)` (4), `.pos-top .pos-status` (15). **And `card.css` draws four faces of `.card` while `ui-kit/card.html` stands two**: `.ed-main>.card` (4 rules, 11 placements) and `.gallery .card` (3 rules, 6 placements) have specimens only on other components' pages. This is row 79 arriving one layer in: that one measured the pages against the product by SUBTREE SIZE and rebuilt 22 of them, and a page can be the richest specimen in the tree and still miss a rule that only fires under another component's container. **It is a specimen debt and not a split**, which is what 16d concluded about the same four files. Owner: Stage 09 (Design System) |
| ~~102~~ | ~~**Five rules reach elements and decide nothing, and three of them are why one file claims a third of its territory**~~ **CLOSED 2026-08-11.** All five deleted. `position.css` drops `.skeleton`, `.w40` and `.w70` from its `Classes:` line and its `Stands on:` goes **36 to 23**, measured by the classes it actually owns: the 14 screens in the gap carry no position at all. `dialog.css` loses `.outcome-dialog .fine` and `.spinner-box .fine`, the last rule in that file naming another component's container.  | `components/`, 2026-08-10, found by the row 19 and 16d survey by deleting each rule and re-reading | Proved by deletion with the instances removed COUNTED, which is the part that matters: the first run of that instrument reported 0 removed and 0 pages differing for every rule, which reads exactly like "the rules are inert", because `deleteRule` over `document.styleSheets` reaches nothing here - **every component sheet is an `@import` and lives on `CSSImportRule.styleSheet`**. **A deletion test that removes nothing agrees with every hypothesis.** Recursed properly it removed 318 to 530 instances per pass, control 0 at both widths in both themes. **`position.css` lines 14, 15 and 31**, `.pos.skeleton .w70`, `.pos.skeleton .w40` and `.pos.skeleton`: `skeleton.css` already declares both widths, all 18 and all 26 matching elements carry `.sk-line`, and `.pos` already sets the gap that `.pos.skeleton` restates. 318 instances removed, **0 of 338 readings differ**. **The consequence is not cosmetic**: those three rules are the only reason `.skeleton`, `.w40` and `.w70` are on `position.css`'s `Classes:` line and the reason its `Stands on:` says 36. By the classes it actually OWNS it stands on **23**, and the 14 screens in the gap carry no position at all. **A file is claiming a third of its territory through three rules that change nothing**, which is row 52's shared-class defect arriving from the other end. **`dialog.css:47`** `.spinner-box .fine` sets a colour the bare rule fifteen lines above already sets; 0 of 248 change, and it is the last rule in that file naming another component's container. **`dialog.css:45`** `.outcome-dialog .fine` is (0,2,0) against `dialog.app-dialog .fine` at (0,2,1) on the same elements; 0 of 248 change, margins included. Filed rather than swept because deleting a rule is a change and this pass had already taken several; each needs its own before-and-after. Owner: Stage 09 (Design System) |
| ~~99~~ | ~~**The product ships 1.8 MB of artwork per feed screen to draw four thumbnails and three fades**~~ **CLOSED 2026-08-12, AND THE TWO HALVES NEEDED OPPOSITE FIXES.** 2,062,090 bytes of artwork to 604,892, **70.7%**; the feed screen 2,922 KB to 1,499 KB; the mean over all 106 documents 1,591 KB to 1,174 KB and its artwork 883 KB to 465 KB. **The photographs were 5.8 times too wide and not 14**: the row divided 1600 by the CSS box, and `cover` consumes more source than the box it fills, so 1600x1073 into 56x88 draws 131x88 and the demand is 137x92. Re-exported from `visuals/masters/` at 440x295 q82, **90.0%**. **The decorations were not too wide at all.** The row's density comparison was against `hero-capitol.webp`, which has **no alpha channel**, and all four decorations do: discarding alpha takes `trust-column.webp` from 181,828 to 49,242, so **the alpha plane is 65 to 73 per cent of each file**, its mean value is 25.5 to 55.2 of 255, and three of the four have **0.0% fully opaque pixels**, which is to say the drawing IS the alpha. Resizing them scored 82.5% and shipped nothing, because the third item is a **halftone globe** and the eye caught the dot pattern breaking up at every ratio. What shipped keeps every dimension and quantises only the alpha, 903,258 to 488,842, and the worst channel delta on the strip fell from 40-73 to 10-21 while the saving only halved. Also: the row's own box number was a fact about one slot, and the photograph stands in four, 56x88, 56x91.8, 72x72 and 46x46. `docs/decisions.md`. | `ui-visual/`, 2026-08-10, found by the row 5 measurement and re-read against the paint | **THE EVENT PHOTOGRAPH IS 1600x1073 AND IS DRAWN AT 56x88.** Four JPEGs, `event-crypto` 344,497, `event-general` 297,634, `event-culture` 264,422, `event-politics` 252,279, **1,158,832 bytes**, and measured in a browser the box they paint into is **56x88 at 390 AND 56x88 at 1280**. The source is 28 times wider than the box and 14 times wider than the box at DPR 2. **This is not a `srcset` problem and calling it one would send the fix the wrong way**: the box does not change with the viewport, so what is wanted is one correctly sized asset and not a set of them. The painted tree carries **0 `srcset` and only 5 `<img>` elements in total**; every other image is a CSS background, and the event photograph is one of the three things `CLAUDE.md` allows on a `style=` attribute, so 111 inline background declarations are correct and are not this row. **AND THE FOOTER DECORATION IS ENCODED AT EIGHTEEN TIMES THE DENSITY OF THE ONE PHOTOGRAPH THAT WAS ENCODED PROPERLY.** `trust-column.webp` 181,828, `trust-source.webp` 234,810 and `trust-globe.webp` 230,166, **646,804 bytes on 105 of the 106 screens**, are 640px wide and paint into a **137x94** box at `background-size:cover`, `opacity:.5`, behind a `linear-gradient` mask. That is **0.72 to 0.92 bytes per pixel**, against `hero-capitol.webp` in the same folder at 1400x788 and **0.041**. `trust-column-full.webp` is a fourth at 256,454 bytes and `opacity:.18`. **The proof that the strip is the payload**: `overview.html` is the one screen with no footer trust strip and it is 608 KB lighter than the next lightest screen. Nothing here is a design decision to re-argue; it is four photographs and four decorations exported once at a size nobody checked, and the fix is a re-export plus a rendered comparison of the composited result, because a decoration at `opacity:.18` behind a mask is exactly the kind of thing a byte count improves and an eye has to confirm. Owner: Stage 12 (Handoff) |
| ~~100~~ | ~~**The font swap moves 70 per cent of the text, and on 17 screens a modal turns that into a 0.2050 layout shift at one width**~~ **CLOSED 2026-08-13, BY THE HALF OF THE ROW THAT WAS NOT THE ONE IT ASKED FOR BY NAME.** **106 of 106 screens at exactly 0.0000** at 390 and at 1280, from a worst of 0.0438 and a mean of 0.0021, and it holds at 400 Kbps as well as at 1.6 Mbps. Two `<link rel="preload">` lines in each of 163 documents, with `crossorigin`, and nothing else. Verified: 0 console errors, 0 "preloaded but not used", 0 fonts fetched twice, and the zero PROVED by stripping the preload from one screen and watching 0.0438 come back while its sibling stayed at 0. **The row's structure held and its magnitudes did not**: the 17 modal screens are six of the six worst and the split is 11.3x rather than 65, 3 of the 17 sit at zero so it is a modal that REWRAPS and not a modal, and **no screen was ever over Google's 0.1**. Its 1280 readings of 0.0002 and 0.0003 came back to the digit, which is what makes 0.2050 a condition it did not write down. **And `size-adjust` / `ascent-override`, which the row asked for by name, was built, measured and refused**: mean 0.0021 to 0.0076, worst 0.0438 to 0.1793, 43 screens worse against 7 better. The account is in `components/fonts.css` and `docs/decisions.md`. 141 opened. |  `ui-visual/`, 2026-08-10, found by the row 5 measurement | Two halves, one cause. **THE CHAIN IS THREE LEVELS DEEP AND NOTHING SHORTENS IT**: **0 of 106 screens carry a `<link rel="preload">`**, so the face is discovered HTML then `index.css` then `@import fonts.css` then the woff2, and the DM Sans request does not start until the last of 50 stylesheets lands, 60 to 115ms on localhost and a full round-trip chain over a network. There is also **no `size-adjust` and no `ascent-override` anywhere**, so the fallback is not metric-matched and every glyph moves when the real face arrives. **THE AMPLIFIER IS THE STATE-AS-A-PAGE MODAL AND IT LIVES AT ONE WIDTH.** 17 screens open `dialog#outcomeDialog` with `showModal()`. At 390 the sheet fills the viewport, so when the swap shrinks its content by 18 to 25px the modal RE-CENTRES and the whole sheet moves, impact fraction near 1. Mean CLS at 390: **0.0260 for the 17 modal screens against 0.0004 for the other 89, a factor of 65.** Worst are `sign-in-error.html` at **0.2050** and `sign-in-provider-conflict.html` at **0.1810**, and at 1280 the same two documents read 0.0002 and 0.0003. **An audit reading only 1280, or averaging the two widths, would report nothing**, which is this repository's own rung lesson arriving on a new axis. Other named movers: `overview.html` at 1280 where a hero paragraph rewraps two lines to one and loses 23px, `active-bets.html` at 390 where `.pos-fig` loses 58px, and `500.html` and `event-detail-error.html` at 1280 where the footer loses 18px. **The structural shift is 0.0000 and the entrance animation contributes exactly 0**, so there is one thing to fix and not three. Owner: Stage 12 (Handoff), and the modal half is Responsive's to confirm |
| ~~144~~ | ~~**525 anchors on 105 screens point at social accounts that do not exist, and they are not the same kind of placeholder as the four beside them**~~ **CLOSED 2026-08-13: the marks are out until the accounts exist**, which is row 27's precedent applied to the group row 27 could not reach. 525 anchors in the paint and 435 in the grey, and `href="#"` in `ui-visual/` goes from 1,059 to **534**. **And it orphaned an entire icon face**: `.icon-btn-lift` had 525 placements and has 0 in the product and 9 on the stand. It is kept rather than deleted, because unlike `account.css` it is a face whose placements return on a date somebody chooses. **It also moved a number published four hours earlier**: backlog 120's 568 of 576 counted these 525, so the standing population is 43 of 57. The decision does not move with it, because it never rested on the majority. | `components/footer.css`, 2026-08-13, found by the row 48 census | Five marks per screen, X, Discord, Telegram, Instagram and TikTok, at `href="#"`, which is **525 of the 1,059 placeholder anchors left in `ui-visual/` and the single largest group by a factor of five**. Row 48 counted them and never named them, and rows 27 and 28 settled the footer's destinations without reaching them, because both were arguments about the MAP: a footer link that is not a map node was cut, a footer link that is a node with no screen yet was kept. **A social account is neither.** It will never become an internal route, so it cannot be waiting for a screen, and it is not a promise the map can keep or refuse. Three answers, and the choice is the product owner's rather than IA's: the accounts get created and the anchors get real URLs; or the marks come out until they do, which is what row 27 did to eight labels for the same reason; or they stay and the repository says out loud that the trust strip above them sits over five links that go nowhere. **The third is the one that is true today and it is the only one nobody has chosen.** Owner: product, with `ia/` |
| ~~143~~ | ~~**Both halves of every YES / NO pair point at the same href, 116 of 116**~~ **CLOSED 2026-08-13: the side is in the URL.** `?side=yes` and `?side=no`, **212 anchors in `ui-visual/`, 212 in `wireframes/` and 72 in `ui-kit/`**, and 0 pairs left where both halves share a destination out of 126 measured. **The reason it is an IA decision and not a markup one is what a URL buys**: a pre-selection that lives in the address survives a share, a bookmark and a back button, and one that lives in a click does not. It also gives the multi-outcome row somewhere to put the option `ia/docs/flows.md` already promises. Recorded in `flows.md`. | `ui-visual/`, 2026-08-13, found by the row 103 measurement | Measured over all 106 documents: **116 pairs, and in 0 of them do the two controls differ in destination.** The binary card's YES and NO both go to `event-detail.html`, the multi row's both go to `event-detail-multi.html`, and the card question above them goes to the same place again, so a card offers three tab stops to one URL. **Row 103 has just made every one of those controls say which side it takes**, so the accessible name now promises a distinction the link does not make, and that was already true of the 100 controls row 96 named on 2026-08-11. This is almost certainly a property of a static prototype with no query parameters rather than a decision: the product intends YES to arrive at the detail screen with YES pre-selected, which is a string `voice/docs/microcopy.md` already owns (`YES pre-selected`, backlog 87). **What is not settled is whether the side belongs in the URL**, which decides whether the pre-selection survives a share, a bookmark and a back button, and that is an IA question rather than a markup one. Owner: Stage 03a (IA), and it lands in markup at Stage 12 |
| ~~142~~ | ~~**48 painted screens and their grey twins ship a category strip that no code path can open**~~ **CLOSED 2026-08-13: the markup is out of the 48 and their 30 grey twins, 240 anchors and 150.** The 57 with a main band keep the strip and **it opens on 57 of 57**, measured by scrolling each to 1200px at 390. The row's other answer, another anchor for the observer, would put a category route on the wallet, the deposit, the profile and the error screens, **which is an addition to the navigation model rather than a repair**, and it stays refused until `ia/docs/sitemap.md` asks for it. Recorded there and in `voice/docs/microcopy.md`. | `ui-visual/`, `wireframes/`, 2026-08-13, found by the row 118 fix | The sticky strip is revealed by an `IntersectionObserver` installed only when `.feed-inner > .cat-nav` exists, and it returns early otherwise. **Measured by scrolling all 106 painted documents to 1200px at 390 and at 1280: the header gained `.scrolled` on 57 and the strip painted on 57, and on 0 of the 48 that carry it without a main band.** So those 48 carry seven lines of navigation markup each, five live links, that no person will ever see and no keyboard will ever reach. It is not dead in the harmless sense: it is a route the markup promises and the runtime withholds, and it reads as present to anyone opening the file. **The two answers are opposite and both belong to 03a**: either those screens should offer a category route from the sticky header, in which case the observer needs another anchor to watch, or they should not, in which case the strip comes out of 48 painted and their grey twins. What is not defensible is the third state it is in now. Row 118 named this population and read it as the one that was harmed; it is the one where the harm was impossible. Owner: Stage 03a (IA) with Stage 04 (Wireframes) |
| ~~141~~ | ~~**The font path is written out 163 times, and it has to be**~~ **CLOSED 2026-08-13 by taking the row's own answer**: the note in `components/fonts.css` names the 163 documents as a dependent, and the periodic re-measurement is one command. Resolved every `href` on every `<link rel="preload">` in `ui-visual/` and `ui-kit/` against the file system: **two distinct paths, 163 documents each, 326 references, 0 missing.** The date is in the file a rename would go through. It stays the second literal repeated across a whole tree, after the grey harness width, and that is now written down in the one place that would notice. | `ui-visual/` and `ui-kit/`, 2026-08-13, opened by the row 100 fix | Two `<link rel="preload">` lines now stand in every document that links `index.css`, and each spells `../assets/fonts/dm-sans-var-latin.woff2` and `../assets/fonts/space-grotesk-var-latin.woff2` in full. **There is no place else to put them.** A preload has to be in the HTML head to be early, which is the whole point of it, so no token, component, pattern or shell can own it, and a script that injects one has already lost the race it was written to win. That makes this the second literal in this repository repeated across an entire tree, after the 1440 in all 104 grey files, which is row 119. **The failure mode is silent in the same way**: rename or re-cut a face and the CSS follows it while 163 preloads point at a file that is gone, and the page still renders, because a preload that 404s costs a request and prints one warning nobody reads. What would make it safe is a build step, which this repository deleted on purpose along with 63 scripts and 41 gates, so the honest options are a periodic re-measurement or a note in `components/fonts.css` naming the 163 documents as a dependent, which is what it carries today. Owner: Stage 12 (Handoff) |
| ~~138~~ | ~~**8.5 MB of artwork nothing references sits in the folder the server serves**~~ **CLOSED 2026-08-13. Moved, and the count was bigger than the row.** The eight files the row named are unreferenced by anything outside this file, confirmed by a census of every `.html`, `.css`, `.js`, `.md`, `.json` and `.py` in the tree: the five 1254x1254 PNGs and `grey-ed.png` have exactly one reference each and it is row 138 itself. **The five are masters and they are in `visuals/masters/` now**, which is what the README always said, along with the two SVGs, which are not masters and are named in the README as the refused vector alternative that row 140 owns. `grey-ed.png` was DELETED rather than moved: 405x3109 is a full-page screenshot of the grey event detail, committed by accident in `e5d60c0`, and a screenshot from a sweep is a throwaway like the sweep. **`assets/` went from 9,690,253 bytes to 1,107,186**, and the second half of that is 140. **And the census found three the row had not**: `event-sports.jpg`, `spare-newspapers.jpg` and `spare-reader.jpg`, 486,949 bytes, are reached ONLY from `concept/old/` and `ui-visual/old/`, the archived pre-Vault exhibits. They stay, because moving them breaks eleven references in documents kept to be read, and they are written here so the next reader knows `assets/` holds 487 KB that no live screen loads. | `assets/`, 2026-08-12, found by the row 99 sweep | Row 99 measured what the screens LOAD and this is what the folder HOLDS: **`trust-column.png` 1,765,608, `trust-globe.png` 1,984,003, `trust-source.png` 1,453,584, `trust-source-alt.png` 1,111,862 and `trust-column-full.png` 2,007,119**, all 1254x1254, plus `trust-column.svg` 4,086 and `trust-globe.svg` 24,385, plus `grey-ed.png` 185,032 at the repository root. **Zero references between them** across every `.html`, `.css`, `.js` and `.md` outside `docs/kit-archive/`. They are not a per-screen cost and are not row 99, and they are also not masters where masters go: `visuals/README.md` declares that `masters/` holds the full-resolution originals and `assets/` holds the web copies, and `visuals/masters/` holds six files, all of them photographs, and none of the four decorations. So the decorations' originals are in the shipped folder and the photographs' originals are not, which is the one arrangement the README rules out. **The two SVGs are a separate question and are 140.** The fix is a move rather than a delete, and it is a move that makes the README true. Owner: Stage 12 (Handoff) |
| ~~139~~ | ~~**One decoration is UNDERSIZED, its first half was a BOX defect wearing a byte defect clothes, and what is left is a CROP nobody can recover**~~ **SECOND HALF CLOSED 2026-08-15 AS REFUSED, WITH BOTH NUMBERS, BECAUSE THE CURE CHANGES MORE THAN THE DISEASE.** The row offered a cheap answer that does not touch the artwork: cap `mask-size` instead of re-exporting. Measured on all 9 placements: below the RAIL rung the brand column is **171px tall and asks 209 of a 600px drawing**, so nothing is upscaled at all; from 900 up it is **406 to 594 tall and asks 722 to 725, an upscale of 1.20 to 1.21** at ratio 1. **Then the cure was measured instead of assumed.** `mask-size:auto min(122%,600px)` against the shipped tree, screenshotting the brand column and diffing in a canvas: **46.6 to 51.9 per cent of its pixels move, worst channel 31 to 33 of 255, mean about 4**, in Chromium 151 and WebKit 26.5 at ratio 1 and ratio 2, with the control at **0 of 195,755 and 0 of 783,020**. **That is a composition change and not a repair**: the 122% is a deliberate bleed past the top and bottom of the plate, and capping it removes the bleed exactly where the plate is largest. So the defect is a 1.21x upscale of a decoration at `opacity:.18`, and the fix moves half the column. A bigger export stays blocked on its own ground, the shipped 520x600 frames not being a crop of the 1254x1254 masters. **Re-open it as a design decision about the plate, not as a defect.** `docs/decisions.md`. | `assets/` and `components/`, 2026-08-12, found by the row 99 sweep | **THE OBJECTION THAT BLOCKED THE SECOND HALF IS GONE, 2026-08-13 last, AND A NEW AND SMALLER ONE STANDS IN ITS PLACE.** The drawing is a `data:` mask at q20 now, so a bigger export no longer adds bytes to 105 screens and row 140 no longer has to be paid first: at that quality the whole four-drawing set is 113,742 bytes and the fourth is 26,184 of it. **What blocks it now is that the shipped 520x600 frame cannot be found in the 1254x1254 master.** Searched three ways on 2026-08-14 and it is not there. A horizontal-only sweep at the shipped aspect gave a **flat curve, best 30.02 of 255 against a worst of 32.23**. A full two-dimensional sweep over offset AND scale found signal but no match: best **18.66** for this drawing, and 19.53, 21.78 and 32.49 for the other three. A local refinement that also fits the best gamma and gain at every candidate, which is the mapping that would explain an alpha plane made from a luminance one, only reaches **15.94 at gamma 1.2**, and a true alignment would be single digits. **So these four shipped frames are not a crop of these masters at all**; they are a different render, or they went through an editing step the repository does not record. A bigger export therefore means re-cropping by eye, and **that is a decision about how the plate should look rather than a re-export**. **AND THERE IS A CHEAPER ANSWER THAT DOES NOT TOUCH THE ARTWORK**: the demand is made by `background-size:auto 122%`, now `mask-size`, which ties the drawing's height to a plate that grows with the page. Cap it, or size it to `contain` the way `.card::after` does, and the upscale disappears without a single new pixel. `.card::after` asks 168x194 of the same drawing and needs nothing, which is the evidence that the drawing is not too small: the plate is asking it for too much. It is worth about a quarter of a per cent of the plate's ink and it is invisible at `opacity:.18`. **FIRST HALF CLOSED 2026-08-13.** `hero-capitol.webp` read at ratio 1, 1400x788 drawn at 1400x788 CSS px, and the reading was right and the cause was not: an absolutely positioned REPLACED element with `left` and `right` both given and a width of `auto` does not stretch, it takes the `width="1400" height="788"` attributes on the markup and drops the over-constrained `right`. So the photograph drew at its full size inside an `.hf-info` of 360x301 at 390 and 301x368 at 1280, `overflow:clip` cut the rest, and **the featured hero showed the top-left 9.8% of a frame composed to be seen whole, which is empty sky, from 2026-07-23 to today**. `object-fit:cover` and `object-position:center top` were inert the whole time. Two lengths fix it; sized to its box the same file is exact at ratio 2, worst case 1.04, and 1.14 to 1.56 short at ratio 3. **SECOND HALF STILL OPEN.** `trust-column-full.webp` is 520x600 and `.seo-brand::after` asks it for 629x726 at ratio 1, because `background-size:auto 122%` scales it to the height of a plate that grows with the page, so it upscales by 1.21 to 1.25 at every width from 760 up before the device pixel ratio is considered at all. It is not visible today, at `opacity:.18` behind a mask that is transparent by 90%, and **a defect that the mask hides is still a defect**. It closed for one hour on 2026-08-13 inside the fix for 140, which made the same file 640x738 AND lighter, and re-opened when that was rolled back: **the two rows are one edit and this half cannot be paid for on its own**, because a bigger export adds bytes to 105 screens that 140 exists to remove. `.card::after` is the counter-example and needs nothing: it asks 168x194 of the same file. Owner: Stage 12 (Handoff) |
| ~~140~~ | ~~**The trust decorations carry their drawing in the alpha plane, and the way OUT of it was found, shipped and rolled back in the same hour**~~ **CLOSED 2026-08-13, LAST, AND WHAT HAD TO BE FIXED FIRST WAS THE DIAGNOSIS.** The rollback blamed `mask-mode:luminance` for parsing without being honoured. **Chromium 151 and WebKit 26.5 both honour it**, and both honour the per-layer `luminance, alpha` list and `mask-composite:intersect`, measured directly on a stand. **What failed was a fetch: a mask image is a CORS-enabled fetch and a background image is not**, so from a `file://` page, where every file is its own origin, all four masks were BLOCKED in both engines while the same files loaded as backgrounds in the same document. The reverted commit was checked out and opened for real to prove it, and WebKit reading it from disk reproduces the reported screenshot exactly. It is the trap that made `assets/icons.js` a script rather than an `.svg`, four days earlier, in this repository. **So the drawings are `data:` URIs in `components/trust-art.css`**, which is not a fetch and therefore cannot be blocked, cannot 404 and cannot arrive late. **A SECOND FAILURE WAS FOUND BY VARYING THE INPUT RATHER THAN BY LOOKING AT THE PAGE**: keeping the fade as a second mask layer under `intersect` paints NOTHING in WebKit, because the bottom layer of a mask list is intersected with the transparent black beneath it, and against the shipped tree that read as a plausible approximation at a mean under 6 of 255. Rendering the mask at q20 and at q82 and diffing THOSE settled it: Chromium moved 10.57% of its pixels, WebKit moved 0.00%. The fade is in the paint now, one mask layer, no compositing operator, identical on both engines. **q20 chosen because the knob does not matter**: mean error 1.06 to 1.01 of 255 across q20 to q82, two engines, two widths, three placements, control 0.00% every time. Three footer tiles **346,492 to 87,558**, all four drawings 113,742, **153,439 as base64 on every screen against 346,492 on 105 screens and 488,842 on the feed: 55.7% off the median screen and 68.6% off the heaviest**. `assets/` 1,474,119 to 1,193,741. `docs/decisions.md`. | `assets/` and `components/trustbar.css`, 2026-08-12, opened by the row 99 fix; **re-opened 2026-08-13 by a rollback, closed the same day by a second engine** | 488,842 bytes stand on 105 screens and none of it comes out by encoding: the mean alpha is 25.5 to 55.2 of 255 with 0.0% fully opaque pixels in three of the four, so these are line art and halftone drawn in the ALPHA channel of a near-empty colour plane, and WebP stores alpha with the lossless coder. **THE CAUSE IS NOW KNOWN AND IT IS NOT A DESIGN QUESTION.** All five `visuals/masters/trust-*.png` are **100 per cent opaque**, brass line art on black, which is a LUMINANCE drawing, and the mean of a master luminance and the mean of the alpha made from it agree, **23.8 against 22.3**. The alpha plane was manufactured from a luminance drawing on the way to the shipped file. Put back into luminance and read as `mask-image` with `mask-mode:luminance` over a flat `--color-trust`, the four come to **256,422 bytes from 488,842**, three of them keeping their exact coverage because the mask IS the shipped file own alpha channel. The cost was measured with the instrument control proved 0 of 12: 9 to 33 per cent of pixels differ over six placements, two widths and two themes, mean 2.5 to 6.7 of 255, and the error is ground-independent. Two cheaper-looking variants were built and rejected on their numbers, an opaque colour plane plus a mask (95,438 against 90,158, MORE than today) and folding the shading into the mask (better in the dark, worse in daylight). **AND IT WAS REVERTED WHOLE, BECAUSE IT DOES NOT RENDER.** A mask image is read as ALPHA unless `mask-mode` says otherwise, and these files are opaque, so a browser that PARSES `mask-mode` and does not HONOUR it paints a flat brass rectangle over 46 per cent of every footer tile, the corner of every card and the whole seo plate. **`@supports (mask-mode:luminance)` returns true in exactly that browser**, so the guard written for this failure could not see it, and Chromium computed `mask-mode: luminance, alpha` correctly over both `http://` and `file://` while the product was visibly broken on screen. **What the row needs now is not another measurement, it is a mechanism that degrades to NOTHING rather than to a rectangle**, and there are three candidates: an SVG `<mask>` element, which is luminance by definition, tested in the browsers this is actually read in; the mask inlined as a `data:` URI so a failed load is impossible, at about 1.37x the bytes; or the drawing re-authored as one brass over an ALPHA mask, which is where it started. Owner: Stage 12 (Handoff) |
| ~~97~~ | ~~**Eleven dialog-close controls point at a different page in each tree, and the one they are on is called Close**~~ **CLOSED 2026-08-11. ONE DESTINATION AND A NAME THAT SAYS WHERE IT GOES.** The destination is the screen the overlay is invoked over, which is the rule the six that already agreed were following: `event-detail.html` for the seven deposit and four sign-in pages, `active-bets.html` for the four win and two loss pages, in both trees. **And the name stops saying Close**, because on these pages there is nothing to close: the page IS the overlay, and `aria-label="Close"` on a control that navigates announces a Close that closes nothing. It is `Back to the event` and `Back to My Bets` now, 34 controls over the two trees. The 105 in-page dialogs keep `Close`, correctly, because theirs is a button that closes.  | `wireframes/` against `ui-visual/`, 2026-08-10, found by the row-89 survey | The 17 standalone overlay screens (`deposit*`, `sign-in*`, `win*`, `loss*`) each carry `<a href="X"><button class="icon-btn icon-btn-photo sheet-close" aria-label="Close">`. **Six point at `active-bets.html` in both trees and the other eleven point at `event-detail.html` in `wireframes/` and `event-feed.html` in `ui-visual/`**: same element, same class, same accessible name, a different destination depending on which tree a reader opens. It is the same species as row 83, a wire soldered to the wrong pin rather than a design, and it is filed separately from 89 for the reason that pass proved twice: **one change per element**, and a markup rewrite carried out at the same time as a rewiring is a sweep whose result nobody can attribute. **There is a second question inside it and it is a product one**: `aria-label="Close"` on a control that navigates to the feed announces a Close that closes nothing, so either these are `<button data-close-dialog>` (an idiom `ui-visual/` already has on 105 pages) or the name stops saying Close. 17 elements per product tree plus 4 kit specimens. Owner: Stage 12 (Handoff), with 89 |
| ~~96~~ | ~~**Ten controls per screen are called "YES" and "NO" and nothing says what they are about**~~ **CLOSED 2026-08-11 WITH `aria-labelledby` AT THE SIBLING, AND THE POPULATION IS 100 PER TREE RATHER THAN 104.** Read from real element trees rather than by regex: **50 `.opt-row` on 14 screens per product tree, 100 controls, 38 rows and 76 controls in the kit**. The name is built out of what is already written and not typed a second time: `aria-labelledby` takes a LIST of ids, so pointing at the outcome span and then at the control itself gives "Sweden YES" with the outcome wording staying where `voice/docs/microcopy.md` owns it. An `aria-label` would have been a second copy of every outcome name in the markup. 0 duplicate ids and 0 dangling references over all 264 documents, and the accessibility tree reads `Sweden YES` / `Sweden NO` where it read `YES` / `NO`.  | `ui-visual/` and `wireframes/`, 2026-08-10, found by the independent review of row 61 | On the 14 screens that carry an outcome list there are **188 controls inside `.opt-row` per tree**, and the entire accessible name of each is the word in it: `YES` or `NO`. The outcome the control is about - Sweden, JD Vance, the option name - is in a SIBLING span the button does not reference. **A person tabbing hears "YES button, NO button" ten times with nothing to tell one row from the next**, which is 4.1.2 and it is not the same defect as row 61: 61 was the CHOSEN state on one row, this is the NAME of every control on the list. The shape of the fix is an `aria-label` on each control carrying the outcome and the side, or an `aria-labelledby` pointing at the name span, and it is 376 attributes across two trees, so the wording belongs to `voice/docs/microcopy.md` before the markup moves. **RE-MEASURED 2026-08-10 AND 188 IS ARITHMETIC RATHER THAN A POPULATION.** There are **104 `<button>` elements inside `.opt-row` per tree, and 84 of them are wrapped in an `<a href>`**: 104 + 84 = 188 exactly, so the row was counting each wrapped control twice, once as a link and once as a button, which is row 89's defect showing up in row 96's census. **Fixing 89 first takes this row from 188 to 104 per tree and from 376 attributes to 208.** The accessible text of all 104 is 52 `YES` and 52 `NO` and nothing else. **The name has to land on two different element types and that cannot be avoided**: on the 12 feed screens the surviving control is the `<a>`, for 84 per tree; on `event-detail-multi` and its logged-out twin there is no anchor and never was, the `<button>` survives, for 20 per tree. The two contexts genuinely differ - on the feed the control navigates to the multi-outcome page, on the detail page it selects an outcome into the bet panel through the `.opt-row` handler - so any plan assuming one element type will be wrong at one end. **`aria-labelledby` at the sibling `.opt-name` survives that split unchanged** and keeps the outcome wording in the one place it is already written, which is 104 ids rather than 208 hand-authored strings; `aria-label` is the alternative and it is `voice/docs/microcopy.md`'s call either way. **And the discriminator is the CONTAINER, not the button class**: the feed card's binary pair and the outcome row's compact pair both carry `class="yes"`, and a sweep keyed on the class will hit the wrong one. **It is also entangled with row 89**: on 12 of the 14 screens the control is a `<button>` inside an `<a href>`, so whichever element takes the name has to be the one that survives 89. Owner: Stage 12 (Handoff), with 89 and `voice/` |
| ~~95~~ | ~~**Every quick-amount chip cleared the field instead of filling it**~~ **FOUND AND CLOSED 2026-08-10**, `decisions.md` same date. The handler is `input.value = money(num(chip.textContent))` and `money()` returns `'$' + x.toFixed(2)`, so it wrote **`$5.00` into an `<input type="number">`**, which the browser rejects: the field went EMPTY and the fee and payout lines went to $0.00. **Every chip on every bet panel, and the blur handler too**, which reformats on the same call. It has been dead since the amount fields became `type="number"` on 2026-08-08 - the change that closed row 65 - and nothing caught it, because a source read sees a handler that assigns the value and a rendering read at rest sees the markup's own `value="5.00"`. **It took clicking the control.** Fixed by writing the NUMBER into a number field, `num(...).toFixed(2)`, and leaving `money()` for the display lines it was written for: **17 documents, 4 handler variants, 0 `x.value = money(` left in any tree**. Verified by clicking: $1 to 1.00, $5 to 5.00, $10 to 10.00, $25 to 25.00, and typing 3 then blurring gives 3.00, with the fee following at $0.01 for a $1 bet. **A control is not tested by reading the page it stands on.** | | |
| ~~92~~ | ~~**The stand for the trust strip shows three items the product does not have, drawn with marks the product does not use**~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and the copy half turned out not to be a question either.** The row split it: the mark half is the system's, the copy half might be `voice/`'s and deliberate. It is not. `voice/docs/microcopy.md` carries the trust strip as three numbered rows with the claim and its source split - "Your USDC is held 1:1" + "We never lend it.", "Every event resolves against a public source" + "You can check it.", "1,284 events resolved" + "On-chain, verifiable." - and the stand's three ("Odds move with money", "One named source", "Public settlement") **appear in no microcopy row and on no screen**, which is the defect row 82 closed for eighteen other strings. The stand's own closing line already points at microcopy as the source. **4 blocks replaced** on `trustbar.html` and `molecules.html`, marks and copy together: measured after, **200 of each claim across 196 documents, one set of three, and every kit and painted mark is `ic tr-ic` with a `<use>`**. **AND THE FIX EXPOSED THE NEXT ONE**: the product's claims are longer than the invented ones, so in a half-width theme cell each wrapped to one word a line - the kit's own trap, a specimen measured in a cell narrower than any placement. The section is a vertical pair now, the trade `organisms.html` had already taken by hand. | `ui-kit/trustbar.html` against `ui-visual/`, 2026-08-10, found by the stroke pass | The product's strip is three items - "Your USDC is held 1:1", "Every event resolves against a public source", "1,284 events resolved" - and each mark is `class="ic tr-ic"` with a `<use>` into the sprite, so it is a **filled** Solar glyph. The stand's strip is three DIFFERENT items - "Odds move with money", "One named source", "Public settlement" - and each mark is `class="tr-ic"` alone with a **hand-written stroked path** (a bar chart, a shield outline, a globe) that exists nowhere else in the repository. So the specimen diverges in three ways at once: the copy, the mark set, and the class that decides which family the mark belongs to. **It was invisible to both of the sweeps that would have caught it**: a string audit reads the product's screens and a glyph audit reads the sprite, and these are neither. It surfaced only because the stroke pass measured a weight the product does not have - those six were the whole population rendering at 0.92px, the thinnest mark in the repository. **The copy half is `voice/`'s** and may well be deliberate, since a stand may show a generic example; **the mark half is not**, because a stand that draws a filled family as a stroked one teaches the wrong family. Owner: Stage 09 (Design System), with `voice/` for the strings |
| ~~81~~ | ~~**A multi-outcome card shows two of the field and never says how many it is hiding**~~ **CLOSED 2026-08-10**, `decisions.md` same date. `.opt-more` is a link at the foot of the option list naming the remainder, and it is a link rather than a label because a person told that two outcomes are missing needs the place they are. **The word is the product's own**, `outcome`, and the Related list's `4 options` became `4 outcomes` in 24 places: one word for one thing. **The number is the smallest the arithmetic forces**, the remainder over the smallest percentage shown, rounded up, **and one market overrides it**: the UK election is named as four outcomes in the Related list, so its card says two more where the formula would have said one. **A count the product states beats a count the product implies.** A card whose rows sum to 100 gets no row: Republicans 52 and Democrats 48 are the whole market. Grey first, and 12 grey stylesheets needed a rule of their own or the link would have drawn in the browser's blue. Verified with a checker that recomputes the count from the percentages: **21 lists per tree, 18 with the row, 3 without, 0 wrong numbers, 0 wrong plurals.** | `ui-kit/card.html`, 2026-08-09 | Owner: was Stage 09 (Design System), with the voice inventory |
| ~~82~~ | ~~**18 content strings in the kit stand on no screen, and 4 of them are a shortened version of one that does**~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and the row undercounted it by 60.** Read from the rendered DOM instead of the source it was **78 strings on 22 pages**: the first count called every script-written half an invention and missed everything on pages the row had not thought to check. **174 strings replaced across 23 pages; 0 of 1,102 kit content strings are invented now**, against 402 distinct product strings. Four kinds of wrong, and only the first was what the row named: copy the product never wrote; copy trimmed to fit, where **the cut clause is the one that makes the block wrap**; **invented LABELS**, `Now worth` and `Paid out` against the product's `Current value` and `Payout`, on the page whose whole subject is that figure grid; and structure dressed as content, the market-depth head row wearing the data rows' classes so "Bet" and "Price" were being measured as figures. | `ui-kit/`, opened 2026-08-09, re-measured and closed 2026-08-10 | Owner: was Stage 09 (Design System) |
| ~~74~~ | ~~**Three backlog numbers name two different items each, and this file's own count has drifted**~~ **CLOSED 2026-08-09.** Read row by row: **25** was `.amount-input` in one section and the eight unannounced error blocks in another, **26** was the featured hero in one and the outcome pair's DOM order in another, **52** is the sixteen stale component headers and a closed skeleton row, **29** is a closed voice row and an open icon-stroke row, and **20 is missing from the table** although the Closed section cites it. **"Do 25" was an ambiguous instruction**, in the file that exists to be acted on. **The rule used to resolve it: a number belongs to the row that documents OUTSIDE this file cite.** Grepped: `ui-kit/docs/consolidation.md` and `decisions.md` cite 29 for the icon stroke and 52 for the component headers, and nothing outside cites 25 or 26 at all. So the two OPEN collisions were split, the later-written row taking a fresh number and saying so in its own text: `.amount-input` **25 to 76**, the featured hero **26 to 77**. The two CLOSED collisions keep their numbers, because renumbering a closed row rewrites the record for a reader who will never act on it. **20 is what item 4 became**, opened and closed inside 2026-08-02 by the skeleton fix, and the row was deleted instead of being struck; it is left missing rather than reused, because a reused number is the defect this row is about. | `docs/backlog.md`, counted 2026-08-09 | Owner: Stage 09 (Design System) |
| ~~71~~ | ~~**The product says "Couldn't load" with two different warning marks**~~ **CLOSED 2026-08-09 as the triangle**, `decisions.md` same date. **The row's premise was one layer short: both trees carried the same split, on the same files.** The grey tree drew a circle on the same 16 screens and a triangle on the same 3, so this was structure and not paint, and it was fixed grey first: 16 grey drawings, then 16 painted `<use>`. **And the circle then had 0 placements**, so the symbol left the sprite on 105 painted screens and 6 kit pages, the set went 35 to 34 and the filled family 21 to 20, and the stand lost its specimen. Proof: 212 renders over 106 screens at both widths, 3,860 `<use>`, **0 unresolved, 0 circles, 26.13 symbols per screen against 27.13**; both trees read a triangle on all 20 error surfaces. | `ui-visual/`, 2026-08-09, found by looking at the feed error screen | `i-danger-circle-b` stands **16** times and `i-danger-triangle-b` **4**, and every one of the twenty carries the same sentence on the same kind of screen: "Couldn't load events", "Couldn't load your bets", "Couldn't load Politics". **One job, two drawings**, which is the defect the whole consolidation exists to remove, one layer in. The two do not divide by severity or by surface: the circle carries the category feeds and the account screens, the triangle carries the main feed error, the 500 page and the error toast. **Filed rather than taken because the count says circle and the convention says triangle**: a triangle is the conventional error mark and it is what the loudest two surfaces already use, and 16 against 4 says the opposite. It is one line in the sprite reference either way, 20 placements. Owner: Stage 09 (Design System) |
| ~~70~~ | ~~**One filled glyph paints the whole cell, and it is the clock**~~ **CLOSED 2026-08-09, AND THE ROW WAS AN INSTRUMENT DEFECT**, `decisions.md` same date. The clock paints **20 x 20, field 2, centre 0.0 / 0.0**, the same box as `i-sort-b` beside it. The 24 x 24 came from `getBBox`, which returns the geometry of the painted path and **never the mask**: Solar delivers this glyph as a full-cell rectangle behind a `<mask>`. Read again by INK, painting each symbol into a canvas at 20x and finding the opaque pixels. **No swap was needed and none was made.** Two things were: the clock is one `evenodd` path now, Solar's two published subpaths composed with a fill rule instead of a mask, **0.09 per cent of pixels different** and all of them antialiasing, which also removed a document-unique id from the sprite on 112 files; and **the glyph that WAS breaking the rule turned out to be `i-magnifer-o` at field 1.23**, which the row never named, now placed at `scale(.9294)` about the cell centre for 20 x 20 field 2.0. Filled distribution by ink: **2.0 on 17, 3.0 on one, 3.3 on two, 0 inside the rule**. | `ui-kit/icons.html`, 2026-08-09, the audit re-run after the consolidation | `i-clock-circle-b` measures **paint 24 x 24, field 0**, against a rule of 2 modules that **17 of the 21 filled glyphs meet exactly** and the worst of the others meets at 3.3. Solar's grid is not the problem, the choice of glyph is: `clock-circle-bold` is drawn edge to edge, so beside `i-sort-b` in the same filter row it reads a size larger than its neighbour. **45 placements on 44 screens**, all of them the frequency filter. The fix is a swap for a smaller drawing from the same set rather than a redraw, which is one line in the sprite and one sweep, and it is filed rather than taken because picking the replacement is a look at four candidates side by side and not a measurement. Owner: Stage 09 (Design System) |
| ~~69~~ | ~~**The icon sprite is a fixed block, so fifteen symbols are defined on screens that use none of them**~~ **CLOSED 2026-08-09 as ONE FILE**, `decisions.md` same date. Measured before deciding: the inline block was **1,756.7 KB across 106 painted screens, 23.2 per cent of the tree's bytes**, 29 symbols defined per screen against **14.0 used on average**. Three options were costed in a browser: keep it whole (0 KB saved), trim each screen to what it uses (**921 KB, 52 per cent**, and a `<use>` with no symbol fails silently on every future edit), or one external file (**18.8 KB, 1 per cent**, fetched once and cached). The external file won on the product's terms and on a second reason a byte count does not show: **a block copied into 112 documents drifts**, and `i-bookmark-b` was already two different drawings, the product's on 111 documents and an older one on 3 kit pages. `currentColor` was verified to cross the document boundary before committing to it. **CORRECTED THE SAME DAY: the one file is `assets/icons.js`, a script, not an `.svg`.** The stated price came due within hours, because these pages are read from disk: over `file://` a screen drew **0 of 34 glyphs and logged 39 console errors** while the stroked inline marks beside them drew fine. A script has no cross-origin rule of that kind, so the sprite is injected and every `<use href="#i-name">` is same-document again. Verified over both protocols: 3,221 glyphs drawn, 0 empty, 0 unpainted, identical. The one file, the 20 KB and the no-drift argument are all unchanged; only the format was wrong. **The price, as it stood, was: the painted tree must be served**, because `file://` treats every file as its own origin, and a screen opened from disk drew 0 of 34 glyphs with 39 console errors. Proof: 92 renders over both trees at both widths, **917 of 917 visible glyphs drawn, 0 blank, 0 failed requests**. | Opened 2026-08-08 by the pass that removed the feed trust strip | Every painted screen embeds the same sprite, 12 symbols on 92 of them and 15 on 13. Measured over the 105 that carry one: **`#i-seo-chart` is defined and unused on 104**, `#i-seo-qa` and `#i-seo-guide` on 96 each, the five category marks on 48 each, `#i-bell-b` on 32, `#i-bookmark-b` on 26. The strip's removal added two more to that list, `#i-shield-b` and `#i-verified-b`, now used on `event-feed.html` alone. **This is a decision about the sprite and not about any component**: either it is a shared block that every screen carries whole, in which case the orphans are correct and the file should say so, or it is per-screen, in which case something has to trim it and that something is a machine. The repository's own rule points at the first. It is filed rather than fixed because the fix is one sentence in the right place and nobody has written it. Owner: Stage 12 (Handoff) | **RE-MEASURED 2026-08-09 and the row got bigger, which is the argument for answering it.** The consolidation took the sprite from 15 symbols to 36 on every painted screen, so the question is no longer about a handful of orphans: it is about eight or nine kilobytes of symbol per page, 105 times, most of it unused on any given screen. The choice is unchanged and the price of the wrong answer has trebled.
| ~~65~~ | ~~**A number field still accepts `e`, `+` and `-`, and markup cannot close it**~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and it was measured by TYPING rather than by assigning, which are two different paths through the value sanitiser and only one of them is what a person does.** On a field carrying `type="number" step="0.01" min="10"`: `abc12` gives **12**, `1.2.3` gives **1.23**, `+5` gives **5**, `-5` gives **-5**, `1e5` gives **1e5 and `validity.valid` is TRUE**, and `5e` gives **the empty string with `badInput` true**. **That last row is item 95 in a new costume**: the field shows two characters and reads as nothing, and only `badInput` can tell it from a field nobody touched, exactly as `money()` writing `$5.00` once emptied the same field. So the contract is two lines and not one: **digits and at most one dot, no exponent, no sign**, and **read `badInput` before reading `value`**. It is written in `components/input.css` as well as on `ui-kit/input.html`, because a person wiring this up opens the stylesheet that owns the class and not the stand that demonstrates it. **And the same block records that `min` is a flag rather than a filter**, which is `pattern` arriving a second time through the attribute added to fix a different row: `:invalid` matches and `validity.rangeUnderflow` is true, and nothing consults either, because this product still contains 0 `<form>` elements and no page script calls `checkValidity()`. The original row: | `ui-kit/input.html`, 2026-08-08, after the currency mark left the value | The 121 amount fields became `type="number"` on 2026-08-08, so the browser now refuses letters: typing `abc12x3.45qq` into the deposit field leaves `123.45`, measured. **What a number input still accepts is `1e5`**, measured, plus a leading `+` or `-`. That is the honest limit of markup: the last of the filtering is the implementation's, and `ui-kit/input.html` says so on the page rather than implying the field is shut. What is needed at handoff is one line of contract, not a component change: **digits and at most one dot, no exponent, no sign**. Owner: Stage 12 (Handoff) |
| ~~64~~ | ~~**36 event-detail tab labels stand 36 tall with a finger, and they are that screen's main navigation**~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and it is the row that showed the instrument had a blind spot the shape of an element name.** `.ed-tablabel` is in the floor and measures 44 on all 36 placements. **Every sweep in this repository reads controls with `a, button, input, select, textarea, summary`, and a `<label>` is none of those**, so the main navigation of the event detail was invisible to the measurement as well as to the floor, and it was found by walking one component's page instead. **A control is what a person taps, not what the query selector returns.** Putting `label` into the query then reported **1,012 more, and 1,012 was the instrument**: they are the language menu's options, and a closed `<details>` puts its content in `::details-content` with `content-visibility:hidden`, so each has a box and is never painted. This repository was caught by exactly that once before, on overflow. Asked properly with `checkVisibility({contentVisibilityAuto:true})`: **0 as the pages ship, and 455 real options at 140x33 with every menu opened**. `.filter-panel li label` is in the floor now and the panel is `position:absolute`, so the dropdown got taller and no page content moved: **455 to 0**. **The row's other two findings are decisions and both now say so in their own file**: the toast dismiss was already a named exclusion, and the footer's five social marks at 28x28 on **525 placements** were the largest exclusion in the system with its reason living nowhere but a `:not()` chain. It is written in `iconbtn.css` now. The original row: | `ui-kit/tabs.html`, 2026-08-08 | The one touch floor in `components/base.css` names `.tabs button`, `.ptab-lbl` and `.rules-tab`, and all three measure 44 with a finger. **`.ed-tablabel` is not in the list and measures 36 with a mouse and 36 with a finger**, on 9 screens, 4 labels each. It is a `<label>` rather than a `<button>`, so the tag-based half of the floor's selector misses it, and the class was never added because no per-file list had ever named it. Same shape as 54: a family the floor does not know about, found by walking one component. **Two more in the same reading, both under 44 and both above the AA floor**: the footer's five social marks at 28x28, 525 placements, and the toast dismiss at 32x32, which is one of the four NAMED exclusions and therefore a decision rather than a gap. Owner: Stage 10 (Responsive), with 50, 54, 55 and 59 |
| ~~63~~ | ~~The bar's own face is worn once of three, and the other two are its negation~~ | `ui-kit/action-bar.html`, 2026-08-08, the walk that ended "unmeasured" | **CLOSED 2026-08-08: the bar is ONE face and a component was deleted for it.** `decisions.md` same date. The row put it as a binary and **the shape of the stone answered it rather than the count**: a top border and two top corners only is the surface of a bar that FLOATS, and nothing in this product docks, so on the wallet it was an open-bottomed dock sitting mid-column. `.flat` and `.static` are gone and the bar is static with no stone. **The consequence was the whole of `components/account.css`**, which was two rules, the stone and its negation: the file, its `@import`, its shelf section, its inventory row and its kit page went with it, and **the vitrine is 54 pages rather than 55**. Its writing is on `action-bar.html`. Named cost: there is no sticky action bar in the system any more. Proof: 424 page pairs, 428,864 element readings, **3 files differ**, two of them by `bottom` and `z-index` alone with the box unmoved, and the wallet by 202 elements, 72 to 55 at both widths |
| ~~62~~ | ~~**The same prose class stands on 22 sections and 14 divs**~~ **CLOSED 2026-08-10**, `decisions.md` same date, **and both halves of the row needed correcting before it could be closed.** **(1) A bare `<section>` is not a landmark either.** It maps to `generic` until it has an accessible name, so converting the element without naming it would have changed nothing in the accessibility tree while looking like a fix. The element and the name move together. **(2) All 14 divs are on ONE screen**, `ui-visual/terms.html`, they are the numbered clauses of the Terms document, `wireframes/terms.html` does not exist, and each already carried the `id` its table of contents jumps to. They are `<section id aria-label>` now, the label taken verbatim from the clause's own `<h2>`, which is what the 43 painted sections beside them already do. After: **57 sections in the painted tree, 0 unnamed, 0 of the 14 jump targets broken.** The kit's four unnamed specimens on `seo-plate.html` took the label too. **AND THE CHANGE MOVED 15px, WHICH IS WHAT FOUND THE REAL DEFECT.** `.feed-seo:first-of-type` took the divider off the first block, and **`:first-of-type` means first among siblings WITH THE SAME TAG NAME**, so on the nine feed screens it fired and on `terms.html`, where a `.protect` div stood above fourteen `.feed-seo` divs, it matched none of them: the first clause drew a divider with nothing above to divide from and lost the stack's 28px lead-in. `:first-child` is not the repair either, measured: the first block is the first child on 9 screens of 11. **A divider goes between two blocks, so it is written between them**, `.feed-seo + .feed-seo`, which asks the actual question and is right whatever tag anybody reaches for next. Verified over the 14 documents that carry the class at 390, 760 and 1280 with a control of 0: every screen has exactly n-1 bordered blocks, the first has none, and `terms.html` is the only document that moved. The original row: | `ui-kit/seo-plate.html`, 2026-08-08 | Measured across 106 screens: 36 elements carry `.feed-seo`, 22 of them `<section>` and 14 `<div>`. The ones inside the plate are sections and the bare ones on the other feeds are divs. **The drawing does not care and the document outline does**: a section with a heading is a landmark a screen reader can jump to, and a div with a heading is a heading in the middle of nothing. Identical prose, so it is a markup consistency question in two trees rather than a CSS one. Owner: Stage 12 (Handoff) |
| ~~67~~ | ~~`hiw-dialog.css` keeps eight pre-refactor rules that out-specify the block's own face~~ | the wrapper sweep, 2026-08-08 | **Closed 2026-08-08**, `decisions.md` same date. Six of the eight are gone and two were `.hiw-full`, which only needed unscoping. Measured across 106 painted screens and 55 kit pages at 390 and 1280: **10 changes, all inside the 105 sheets**, the largest being the section label at 315 elements going from 11px uppercase to the 14px display bold the block declares. **The count was eight and the finding was nine**: the file's last `.app-case` had to go with them, because it meant "the page rather than the sheet" and a KIT page carries `app-case` on its body, so the specimen was a sheet wearing the page's hero edge, tagline, separators and FAQ indent. The split is `.hiw-page` on the page's hero and `.hiw-cols` for the rest, one class added in each tree, grey first. |
| ~~68~~ | ~~The kit puts `app-case` on a `<dialog>` to reach rules that no longer need it~~ | the wrapper sweep, 2026-08-08 | **Closed 2026-08-08**, `decisions.md` same date, and **the row undercounted it by 22**. It was not two attributes in one file: **24 dialogs wore the class, 21 painted screens across 21 files and 3 specimens across 2**, and every one is a screen or a stand whose subject IS a dialog. It was also not inert. `components/dialog.css` had already recorded what it cost: `.app-case` declares `position:relative`, a modal must be `fixed`, and the sheet scrolled with the page behind it until `dialog.app-dialog:modal{position:fixed}` was written to take the position back. Measured before and after over 322 page pairs at 390 and 1280 in both themes, 190,258 element readings: **0 differences in the product and 0 on the kit**, the only movement being the course panel scrolling to its own active row. The counter-rule stays and its comment now says why. |

---
| ~~103~~ | ~~**The feed card's own YES / NO pair has no accessible name either, and it is 18 controls per screen**~~ **CLOSED 2026-08-13 BY THE RULE ROW 96 HAD ALREADY WRITTEN DOWN.** **130 bare "YES"/"NO" names to 0**, and 232 of 232 controls in the family carry their outcome. `voice/docs/microcopy.md` says the wording stays where that file owns it and is not typed into markup twice, which decides this row: the binary card's outcome IS its question, so the name is the question, 61 characters against row 96's 11. **Length is the measurement, not the defect** - a name is as long as the thing it names, and the alternative is a second string per market to keep true. The counts: 116 pairs, 232 controls, 14 screens, in four placements (126 binary card, 84 multi row, 20 event detail, 2 feed hero), and what the row did not name is that **12 documents had two or more controls sharing one name**, five "YES" and five "NO" on one category feed. 0 now. **The measurement also found a straggler of row 96**: the SELECTED outcome row on four documents, whose `.opt-name` wraps a nested `selected` tag and so did not match the pattern that named its four siblings, which is the row a person is most likely to act on. 420 readings, both trees, both widths: 0 console errors, 0 h-scroll, 0 duplicate ids, 0 orphan label targets. 143 opened. `docs/decisions.md`. | `ui-visual/` and `wireframes/`, 2026-08-11, found by the accessibility tree after row 96 landed | Row 96 was scoped to `.opt-row` on purpose and it is closed. The same reading of the accessibility tree that proved it - 32 links containing a control before, 0 after, and the outcome rows now announcing "Sweden YES" - shows **18 controls per feed screen whose entire accessible name is still the word YES or NO**: family F1, the binary pair on a feed card, 126 per product tree. Their outcome is the card's question, in `a.q` two elements up, and it is a SENTENCE rather than a name: "Will the US government shut down before March 1, 2027? YES" is 60 characters where row 96's names are 11. **So this is not the same fix with a different selector, it is a different question**: whether the card is a group with an accessible name, whether the pair takes a shortened form of the question, or whether the answer is that a card is one navigable object and the pair should not be two links at all. It is `voice/` and `ia/` before it is markup. Owner: Stage 12 (Handoff), with `voice/` |
| ~~104~~ | ~~**The bare `.sheet-body` is 8 because nobody looked, and every body in the file that HAS been looked at is higher**~~ **CLOSED 2026-08-11, AND THE ARGUMENT IS NOT THE ONE THE ROW MADE.** "Every decided body is 16 or 20" is a comparison between siblings; what decides it is the head directly above the body. The bare `.sheet-body` stands on **115 elements across the painted tree and the kit and every one of them is `#depositDialog`**, a bare `app-dialog` with no skin, whose head is `dialog.app-dialog:not(.outcome-dialog) .sheet-head` at 24/20/20. **So the title stood 20px from the left edge while the amount field, the payment widget, both paragraphs and both full-bleed buttons stood at 8**: a 12px step down the middle of the sheet, on every screen that carries it. Both of this file's copies of the rule are `var(--space-20)` now and the body shares its head's left edge. **The gap is deliberately left at 8**, because one change per element is the only way to attribute what moved, and it goes with the rest of the family into 110. | `components/dialog.css`, 2026-08-11, found by the row 98 measurement | Four sheet bodies, one file: the bare rule at 8/8 on 113 painted elements, `.signin-dialog` at 16/12 on 109, `.outcome-dialog` at 20/12 on 6, and the how-it-works sheet at 20/16 on 105. **Every body whose padding has been decided is 16 or 20 and the only 8 is the one that was never decided.** It has been tried on the how-it-works body before and failed: `decisions.md` records `dialog.app-dialog .hiw-body{padding:8}` under backlog 67 as a rule that was already dead. Raising the bare rule to 20 moves **113 painted sheets**, so it is a visual change to shipped screens rather than a de-duplication, and it is filed rather than swept for the reason row 97 states: one change per element, or nobody can attribute what moved. Owner: Stage 09 (Design System) |
| ~~105~~ | ~~**`.hiw-page` is a modifier on a class that is about to mean something else**~~ **CLOSED 2026-08-11, AND IT LANDED WITH 108 AS ONE EDIT BECAUSE IT IS ONE EDIT.** `.hiw-hero.hiw-page` is `.hiw-page`, four elements, and `hiw.css` writes the page hero's rules outright instead of leaning on a class that means "the head of a sheet". The face it used to inherit comes from `.plate-head` now, which is what 108 is. **The grey tree had the same defect one step worse and it was found by landing this one**: `wireframes/_generators/port_chrome.py` flattened `.hiw-hero.hiw-page` to a bare `.hiw-hero` when it ported the chrome, so five page-only rules stood UNSCOPED in 87 files, and 86 of those files have no page at all. The how-it-works SHEET head in the grey tree has therefore been wearing the page hero's 32/24 padding, its 20px bottom margin, its 16px/44ch tagline and its 14px FAQ. **430 rules deleted in the 86 files that have no page, 12 re-keyed to `.hiw-page` and `.hiw-cols` in the one that does.** A generator that flattens a compound selector writes a rule that is right about the values and wrong about the element, and no sweep in this repository reads a grey stylesheet. | `components/hiw.css`, 2026-08-11, found by the row 98 survey | `.hiw-hero.hiw-page` stands on **4 elements**, one per tree plus two kit specimens. It is 922 x 161.89 at 1280, at DOM depth 9 under `div.app-case > main.feed > div.feed-inner > div.cat-layout > div.cat-main`, and it carries an edge, a 16px corner, a 20px bottom margin and an inset lit lip, none of which a sheet head has; its heading is an `<h1>` and its tagline 16px at 44ch against 14px at 32ch. **The two share a look and share nothing else.** Rows 15 and 18 established that a component is not named after one of its places; this is the same cut one class deeper. `<div class="hiw-hero hiw-page">` becomes `<div class="hiw-page">`, 4 elements, and `hiw.css` writes the page hero's rules outright instead of leaning on a class that means "the head of a sheet". It is the precondition for anything row 98 might still do. Owner: Stage 09 (Design System) |
| ~~106~~ | ~~**`.icon-btn-ring-strong` was measured against a glow that no longer exists**~~ **CLOSED 2026-08-11, AND NEITHER BRANCH THE ROW WROTE DOWN IS WHAT HAPPENED.** Re-measured at the button's own corner, in the annulus the ring actually covers, DPR 2, both widths, both themes, with the instrument first proving it reproduces the row's own 2.52 against the OLD glow put back at run time: the ordinary brass ring is **2.93:1 worst on the how-it-works head and 2.89:1 on the PLAIN sheet head** in the Vault, 4.61 and 4.61 in daylight. **The head that was carrying the exception is the better of the two by 0.04, and the 222 that were not carrying it are worse.** So the class cannot come off and it cannot be added either, because **a class on 105 of 333 discs is a LIST** and this system has already paid for a list: the 44px floor stood in six files as six lists and two of five chips had it. `.icon-btn-ring-strong` is deleted from `iconbtn.css` and from 198 class tokens in three trees, and `dialog.app-dialog .sheet-close:focus-visible` is one rule keyed to the family, in the file that owns the surface, for the reason `course-chrome.css` already writes over its own ring. The win head at 3.53 would pass without it and takes it anyway: half a point of contrast is not a reason, and six discs kept on the other value would be the list again. **The row's own "the other 117" was an arithmetic slip**, it is 222. And the record's 3.76 / 4.58 for the win head is the declared recipe COMPUTED rather than the page read: the paint is 3.53 / 4.48, and the 0.26 is the head's own green glow lightening the corner. | `components/dialog.css` and `components/iconbtn.css`, 2026-08-11, opened by the row 98 values pass | The close disc on the how-it-works head takes the strong ring because `.hiw-glow` put brass in exactly the corner the ring lands in: 2.88:1 average and 2.52:1 at the brightest point in the Vault, under the 3:1 floor, daylight passing at 4.75:1. **That measurement was taken against a 224px glow at opacity .5 with a 65 per cent inner mix, and the glow is 210 at .42 and 60 since 2026-08-11.** The exception was already half inconsistent: all 222 painted plain sheet heads carry a brass glow in the same corner and only 105 of them ask for the strong ring, and what separated them was 0.08 of opacity, 14px of box and 5 points of mix. Re-measure at the button's own corner. If it clears 3:1 with the ordinary ring the class comes off 105 painted screens and the paragraph in `dialog.css` goes with it; if it does not, it goes onto the other 117. Owner: Stage 09 (Design System) |
| ~~107~~ | ~~**The two trees hide the bet dock at different rungs, so between 640 and 759 a grey screen has no bet control at all**~~ **CLOSED 2026-08-11, AND HALF THE ROW'S PREMISE WAS WRONG.** The grey panel does not arrive at 760, it arrives at **640, in the same four-line block that hides the dock**. Measured at ten widths on all 16 event-detail documents in both trees: the set of widths where neither the dock nor the panel is visible is **empty on every one of them**. There is no 120px hole and no screen that cannot be bet from. **What is real is the other half**: the two trees put the same reflow at two different rungs, so from 640 to 759 the grey tree draws a second column the product does not have, and **halves its own content column to do it, 635 to 322.73 in one pixel of viewport**, with the chart going 569 wide to 255.7. `DESIGN.md` decides the number by name - 760 is where the event detail gains its second column - so the grey tree moves, in all 92 files, and **the WHOLE block moves rather than just the dock, because moving the dock alone is what would create the hole this row was filed about**. The paint needs no change. **And the pair trap went in the same sweep**: `@media(max-width:640px)` in 57 grey files and `@media(max-width:760px)` in 87 both match on the same pixel as their `min-width` twin, which is the defect `CLAUDE.md` says this repository has been bitten by twice; read at exactly 760 the grey footer trust strip computed one column where the paint computes three. 153 conditions are `639.98` and `759.98` now. | `wireframes/` against `components/betpanel.css`, 2026-08-11, found by the row 87 survey | The grey tree hides the dock at `min-width:640px` and the paint at `min-width:760px`, and the desktop panel appears at 760 in both. **So on a grey event-detail screen from 640 to 759 the dock is gone and the panel has not arrived**, which is 120 pixels of width where the screen cannot be bet from. It sits on the 640 rung this repository has already been bitten by twice and it is exactly the shape the rung rule exists to catch: two files answering the same question with two numbers. Owner: Stage 11 (Responsive) |
| ~~108~~ | ~~**One FACE worn by three heads under three names, which is what row 98 turns out to be**~~ **CLOSED 2026-08-11. THE FACE IS A FILE AND THE THREE HOSTS SAY ONLY WHERE THEY SIT.** `components/platehead.css`, level 1, four rules: the ground, the wave, the glow and the six shared declarations of the heading. **28 declarations that stood twice stand once**, and the rename the row refused is still refused. `.plate-head` is on **339 elements** in the painted tree and the kit - 225 plain sheet heads, 111 how-it-works sheet heads, 3 page heroes - and on **0 in the grey tree**, which links no stylesheet and takes the copy rather than the class. **The `.hiw-glow` SPAN went with it**: 200 elements over three trees drawing the circle the other host drew with `::after` and no markup, including 88 in the grey tree where it carried no background at all and painted nothing at any width. The outcome head is not a wearer and is filed as 109. | `components/`, 2026-08-11, re-filed from 98 after its values half closed | 98 asked for a rename, `.hiw-hero` to `.sheet-head` and `.hiw-body` to `.sheet-body`, and **the rename does not remove the duplication, it moves it**: the head's paint is six rules, written once today and drawn by one class in both of the block's hosts, and renaming the sheet head puts them in `dialog.css` for the sheet while the page hero, which is not and cannot be a `.sheet-head`, has to have them written again. That is a duplication against the plain sheet head traded for a duplication against the page hero, at a cost of **1,004 class tokens across 195 files** measured with whole-token matching (a `\bhiw-hero\b` regex returns 268 in `ui-visual` where the true count is 106, because `-` is not a word character). What it actually is: one face worn by the plain sheet head at 418px, the how-it-works sheet head at 462px and the how-it-works page hero at 922px. **The system already has the move and has written it down**: a skin can belong to a SURFACE rather than to a component, with the header band as the precedent. So a face class the three hosts wear, in the file that owns the face, each host saying only where it sits and how loud its heading is. Not a selector list: `components/CLAUDE.md` says the fix for a scope is never a second selector. Owner: Stage 09 (Design System), after 105 |
| ~~109~~ | ~~**The outcome head has the plate head's anatomy to the declaration and a different face, and nothing says which of the two it is**~~ **CLOSED 2026-08-11, AND THE ROW'S OWN FEAR WAS THE PART THAT DID NOT SURVIVE MEASUREMENT**, `decisions.md` same date. Read against `.plate-head` with both overlays open: of the 15 box properties **13 were already identical** and the two that were not are the ground and the content's own height; of the 15 on the wave, 13 identical and the difference is one opacity; of the 15 on the win glow, 12 identical; of the 11 on the heading, 10 identical and the difference is one `ch` count. **The wave's data URI was byte-identical, md5 c87f8063**, which is the same 300 bytes standing twice for the second time in one week. The row feared that folding it in would need two classes where 108 found one, and it needed none: **the host already carried the scope**, since `.outcome-dialog`, `.win-dialog` and `.loss-dialog` sit on the element's ancestors. So the answer to the row's own question is the third option it did not list: the face is **an anatomy plus a DEFAULT skin**, and a host that wants another skin names the parts and inherits the rest. `.plate-head` is on 10 more elements, 6 painted and 4 in the kit, and `dialog.css` went from six rules of anatomy-and-skin to seven of skin only. **The loss head is the one plate head with no glow and now has to say so out loud**, because inheriting the face means inheriting a brass one, and brass on a loss head is the brand borrowing an outcome's place: `content:none` rather than `opacity:0`, since a transparent 210px blur is still a composited layer. One number went with it: the win glow blurred at 44 here and 42 on the face and it is 42 now, **measured at 0 differing pixels of 27,160 with the control at 0**, because the glow is anchored outside the box and clipped, so 2px of blur radius never reached a pixel anybody could see. | `components/dialog.css`, 2026-08-11, found by writing `platehead.css` | Owner: was Stage 09 (Design System) |
| ~~110~~ | ~~**Three of the four sheet bodies do not share a left edge with their own head, and the fourth is the only one anybody checked**~~ **HALF CLOSED 2026-08-11: THE EDGE IS DECIDED AND THE GAP IS FILED**, `decisions.md` same date. The sentence the row said was missing is now written, in `dialog.css` beside the placement: **a body shares its head's horizontal padding**. Measured over all four sheets in both themes at 390 and 1280, identical at every one of the eight readings: the sign-in body sat at 16 under a head at 20 and the how-it-works body at 20 under 24, and **all four step +0px now**. Neither 4px was an optical correction, which is the argument the row left open: an optical correction is one value applied on purpose, and these were two different values arrived at by accident, a small sheet padding all four sides equally and the generic body value standing under a hero that pads 24 because a hero is bigger. **The vertical padding and the gap are deliberately not touched**: an edge is shared with another element and has to agree, a gap is between siblings of one body and says how dense that body's content is. The four gaps at 8, 12, 12 and 16 are **item 114**. | `components/dialog.css` and `components/hiw.css`, 2026-08-11, found by closing row 104 | Owner: was Stage 09 (Design System) |
| ~~111~~ | ~~**The close disc on the six outcome overlays stops being a circle the moment it takes focus**~~ **CLOSED 2026-08-11, AND IT IS 48 CONTROLS ON 17 PAGES RATHER THAN ONE ON SIX**, `decisions.md` same date. **The first reading said zero and it was the instrument**: focusing an anchor from script produces `:focus-visible` only when the last interaction was keyboard, so 2,689 anchors were focused and 17 matched, and the query that found them missed `a.hiw-full` because it asked for a DESCENDANT of `.hiw-full`. Re-read with CDP `CSS.forcePseudoState`, which does not guess: **88 anchors change radius under focus and 48 of them had a shape of their own** - 17 close discs from 100px to 6px, 17 `.hiw-full` blocks and 14 `.btn` blocks from 10px to 6px. The other 40 are the class-less text links the rule was written for. **An outline follows its element's `border-radius`**, so the only way to round a ring is to round the element, which is why a rule about a ring was silently a rule about every shape in a sheet. The fix is the scope and not a second selector: `dialog.app-dialog a:not([class]):focus-visible`, ONE condition rather than a list of the three components being overwritten, because a list fails in the wrong direction and this fails in the right one - a new component brings a class and keeps its shape with no edit here. After, with the control proving the rule fires at all (2,706 anchors read, ring changes on 2,706): **40 still take the corner, 48 keep their own**. | `components/dialog.css`, 2026-08-11, found by the row 106 measurement | Owner: was Stage 09 (Design System) |
| ~~112~~ | ~~**The grey tree disagrees with the paint at three more addresses, and one of them is the control that cost 73 screens a horizontal scroll**~~ **CLOSED 2026-08-11, all three**, `decisions.md` same date. **`.hiw-btn`**: the grey tree carried no rule for it at all and shipped it down to 320; it is `@media (max-width:759.98px){display:none}` in 87 files now and grey reads none / none / flex / flex at 390 / 759 / 760 / 1280, which is the paint exactly. **`.chart-svg`**: the grey tree had no rung and drew 130 flat; it steps at 760 in 92 files now and the base went 130 to 160, so both ends of the rung tell the paint's story rather than only one. **The rail**: the paint pins `.subcat` at the RAIL rung 900 in `catnav.css`, and the grey tree said 640 in 92 files and 900 in exactly one, which is the tree holding the right rule and the wrong rule for one component. All 92 say 900 now, and the `.cat-layout` declarations went with them because **0 grey files carry that markup**. What is left is markup rather than styling and is **item 113**: `<!-- /cat-layout -->` closes in 76 grey files with nothing opening it. Smoked after, all three trees at the rungs and one pixel either side: **3,724 readings, 0 horizontal scroll, 0 console errors**. | `wireframes/` against `components/`, 2026-08-11, found by the row 107 survey | Owner: was Stage 11 (Responsive) |
| ~~113~~ | ~~**76 grey pages close a wrapper that nothing opens, and the paint has it on 77 screens**~~ **CLOSED 2026-08-13. The tree lost the wrapper in a port, and its own stylesheet is the proof.** The row said it could not choose between two opposite answers by guessing. It did not have to: **92 grey files style `.cat-main { flex: 1 }` and, at the RAIL rung, `.subcat { flex: 0 0 210px }`, and both need a flex parent that was not there.** So **the sub-category rail has never once stood beside the content in this tree, at any width, on any screen**: two flex children of a block box. A closing comment with no opening tag is evidence; a stylesheet written against the missing element is proof. Both tags are back in all 76, 33 of them around a rail, with the `.cat-layout` rule in the 92. After: 104 grey files at six widths, **0 horizontal scroll, 0 page errors, 0 files where a rail that renders is not beside the content**. | `wireframes/`, 2026-08-11, found by closing row 112 | `class="cat-layout"` appears in **0 grey files and 77 painted ones**, and `class="cat-main"` in **1 grey file and 77 painted ones**, while `<!-- /cat-main --><!-- /cat-layout -->` survives in **76 grey files** with no opening element above it. So the browse shell, which is a PATTERN in `components/patterns/browse-shell.css` and the arrangement 77 painted screens stand in, has no structure in the tree that owns structure. Row 112 took the styling half: the rules went, because a rule for markup that is not there is a fossil. **The markup half is a decision this row cannot take by guessing**, because the two answers are opposite: either the grey tree lost the wrapper in a port and it goes back, or the grey tree never had it and the paint invented an arrangement the structure never asked for, in which case the 77 painted screens are the ones that are wrong. A closing comment with no opening tag is evidence of the first and not proof of it. `.subcat` is the same question one size smaller: 33 grey files carry it against 105 painted. Owner: Stage 04 (Wireframes) with Stage 09 |
| ~~114~~ | ~~**Four sheet bodies space their children four different ways and nothing says which rung is which**~~ **CLOSED 2026-08-13 with the sentence the row asked for, in `dialog.css`.** The four gaps are three and reading the bodies rather than the rules shows what sets them: **8 for a stack of PARTS** (the deposit body, six children of four kinds), **12 for a stack of CONTROLS** (sign-in's three provider buttons and a fine line; the outcome body's figure block and two actions), **16 for a stack of SECTIONS** (how-it-works, three `.hiw-sec` each carrying an icon, a label and a paragraph). **The gap grows with the size of the thing on either side of it**, which is the only rule a gap can honestly follow. Not tokenised, because three tokens would say these are rungs any body may pick from and they are not: a body has one of three shapes and the shape decides. | `components/dialog.css`, 2026-08-11, the open half of row 110 | Measured with row 110 and identical in both themes at both widths: the bare body gaps at **8**, the sign-in body and the outcome body at **12**, the how-it-works body at **16**. Row 104 left the 8 alone on purpose and row 110 left all four alone on purpose, because **an edge is shared with another element and has to agree, while a gap is between siblings of one body**, so four values may be four densities rather than drift. That is the whole question and it is not answered: a deposit body stacking a field, a widget, two paragraphs and two buttons is genuinely denser than a how-it-works body stacking sections, but **nothing in the system says so**, and four values with no sentence behind them read the same as four values nobody chose. What is missing is either one gap for every sheet body, or a named reason per body written beside it. Owner: Stage 09 (Design System) |
| ~~115~~ | ~~**The whole type scale is px, so the product ignores the reader's browser font setting entirely**~~ **CLOSED 2026-08-12.** Ten `--text-*` steps and eight `--display-*` clamps are ratios to the root; every step divides by 16 exactly, so the move is arithmetically inert at the default and **0 font sizes and 0 line heights differ of 44,547 readings over 210 screen-and-width pairs**, with 0 documents changing height and 0 gaining horizontal scroll. At a 24px root the phone page is **38.4 per cent longer and carries every word**, against 0.2 per cent and no extra word the day before. Two things came out of it and neither is this row: **135**, because the rungs' stated reason for being px was this row and it expired the moment the row closed, and **136**, one screen that gains 23px of horizontal scroll at 320 with a 24px root, whose cause is a hard-coded column count and not the type. `decisions.md`, "The reason a rule gives is the thing that expires" | `components/tokens.css`, 2026-08-11, found by the Responsive stage asking whether the rungs should be in `rem` | Measured with the control first, root font 16px against 24px: **`10rem` goes 160px to 240px, so `rem` responds**, and `body` follows because nothing sets it. Then nothing else moves. The heading stays **36px** because it is `clamp(28px,4vw,38px)`, vw and not rem; the feed prose stays **13px**; the footer legal line stays **11px**. **Eighteen size tokens are px literals and the row said all eighteen run from `--text-10` to `--text-30`, which is TEN**: re-counted 2026-08-12 from the comment-stripped source, the ladder is 10 `--text-*` steps plus 8 `--display-*` tokens, every one of the eight a `clamp()` of px and vw. **229 font-size declarations in `components/`, of which 213 go through a `--text-*` token, 8 through a `--display-*` one, 5 are the deliberate `font-size:0`, and exactly ONE is a raw px literal**, 7px in `chart.css`. Nothing sets `font-size` on `html`, `:root` or `body`, which is why `10rem` responded in the control at all. So a person who enlarges their browser text gets a 50 per cent larger default on the one element nobody styled and no change at all to a single word of the product. This is why the rungs stayed in px rather than moving to rem: a rung in rem while the type is in px is worse than one in px, because the layout would switch at a different window width for that person while every word stayed the same size, which looks like accessibility and does nothing. **THE COST LINE WAS WRONG BY AN ORDER OF MAGNITUDE AND THE CORRECTION CHANGES THE DECISION.** It read "40 type tokens, 46 stylesheets and 106 screens", written as though the sizes were scattered through the tree. They are gathered into a ladder, which is what a ladder is for: **the edit is 19 declarations in ONE file plus the 7px literal**. What is genuinely expensive is not the edit but its consequences, and each has to be measured rather than assumed. **(a)** The `vw` term of the eight clamps does not follow the setting, so at a large root a display size pins to its rem floor and stops responding to the very person the change is for. **(b)** Geometry stays px on purpose, `--space-*`, `--control-44`, `--size-*` and `--icon-*` being pointer measurements rather than text, so at a 24px root the type grows and the boxes do not and everything already tight gets tighter: **that sweep, at roots 16 / 20 / 24, is the actual work of this row.** **(c)** `--measure` is in `ch` and follows for free. **(d)** `--grid-col-min:300px` holds two thirds of the characters at a 24px root. **(e)** The grey tree does not follow at all, linking nothing from here. **(f)** The rungs become three lines AFTER this and mean nothing before it. **And the frame matters for priority: this is not a WCAG failure**, since browser page zoom scales px and satisfies SC 1.4.4. What is broken is the browser's default-font-size preference, which is a real group of people getting nothing, on a product whose feed prose is 13px and whose legal line is 11.

**THE SWEEP THE ROW WAS WAITING FOR WAS RUN 2026-08-12 AND ITS ANSWER IS THAT ALMOST NOTHING BREAKS.** The default font size was set through CDP `Page.setFontSizes`, which is the preference itself rather than a page writing `html{font-size}`, and the rem ladder was SIMULATED by overriding the 18 tokens at `:root` so the tree could be read both ways without being edited. Four controls came first. **(1)** The same screen read twice, root 16: **0 differing font sizes of 3,120**. **(2)** The root does move: 16px to 24px, reported by the document. **(3)** The rem ladder at root 16 is a NO-OP, **0 differing of 3,120**, which it has to be because every step is exact, 13/16 = 0.8125. **(4)** A 400px block dropped into a 60px clipped card takes the vertical probe from 6 spilling elements to 28, so it is not blind. Only then the readings, over all 105 screens at 360 and at 1280:

| | text cut off, 360 | page height, 360 | text cut off, 1280 |
|---|---|---|---|
| as it is, root 16 | 162 on 105 screens | 100% | 204 |
| **as it is, root 24** | **162, the identical set** | **100.2%** | **204** |
| rem ladder, root 20 | 203 | 115.8% | 206 |
| rem ladder, root 24 | 206 | 138.4% | 195 |

**The second row IS this backlog item, stated better than the row states it.** A person who sets a 24px default today gets a page **0.2 per cent taller and not one additional word visible**. The 717 declarations that do move at root 24 are containers with no text of their own, which is why the sentence "222 of 228 resolve to a number the setting cannot move" was true and still understated the result.

**And the fourth row is the answer to the cost question: exactly ONE class changes count.** Broken out, 360px: `skip-link` 105 in every condition (it is `width:1px;overflow:hidden;clip-path:inset(50%)`, the visually-hidden technique, and being clipped is its job), `rel-q` 27 in every condition, `plate-head` 14 then 13, `hf-info` 1. **The whole difference is `.why`, 15 to 60**, and `card.css` gives it `-webkit-line-clamp:2`. **A line clamp hiding more words at a larger type size is the clamp doing what it is for, not a layout failing.** No class that fits today stops fitting. No new horizontal scroll on any of 105 screens at either width in any of the four conditions, and the 61 horizontally clipped elements are the same set throughout.

**What is left to decide is therefore not "what breaks" but "is a 38 per cent longer phone page the right answer for a person who asked for bigger text"**, which it probably is, plus two smaller calls: the `vw` middle term of the eight display clamps, and whether `--grid-col-min:300px` should follow the type. Owner: Stage 10 (Responsive) or its own pass |
| ~~116~~ | ~~**Three widths live in every one of the 104 grey files and are on no ladder, and two of them hard-code a column count the paint computes with none** | `wireframes/`, 2026-08-11, found by the Responsive stage transcript | The product holds **12 distinct width values**. Nine are in the system and are really five: three rungs each written twice for its two sides, plus the harness, plus three named one-offs. `ui-kit/_page.css` invents nothing. **The other three are grey-only and stand in all 104 files.** `960px` is `.grid{repeat(3,minmax(280px,1fr))}` and `1280px` is `repeat(4,...)`, against the paint's `repeat(auto-fit,minmax(min(100%,var(--grid-col-min)),1fr))` in `patterns/card-grid.css`, which needs no query at all and works at 1100px, a width nobody designed for. So the grey tree spends two unnamed numbers and two rules to say what one fluid track says with none. `1440px` is the grey harness, `.wf-nav` and `.wf-toggle`, the twin of the paint's `1140` HARNESS **standing at a different number**, which is the same class of defect as the 640-against-900 rail that row 112 closed. This is not the same row as 112: that one moved three rules the paint already had, this one is a mechanism the grey tree never had. Owner: Stage 10 (Responsive), step 4~~ **CLOSED 2026-08-12, Responsive step 4.** The three rules and the two unnamed widths are deleted from all 104 grey files and replaced by `repeat(auto-fit,minmax(min(100%,300px),1fr))`, the mechanism the painted tree already used through `--grid-col-min`, so neither tree declares a column count any more and 960 and 1280 appear nowhere. Proved by counting the columns the browser lays out at eleven widths rather than by reading the rule: painted 1/1/1/1/2/2/2/3/3/4/4 and grey 1/2/2/2/2/2/3/3/4/4/4 from 360 to 1600. **The mechanism agrees and the container does not**, and that is a second and smaller thing: the grey content column is wider at the same window and its gap is 10px against the painted 16, so it reaches each step earlier. `1440`, the grey harness twin, is untouched by this and stays a harness. |
| ~~117~~ | ~~**The course roadmap sidebar is literal markup in 28 files, and one of the 28 had been four rows short since stage 09**~~ **CLOSED 2026-08-13. `assets/_roadmap.js`, and the kit had already written the argument.** The route is one registry read by a browser at load; a page declares neither its name nor its depth, because the active row is computed from the path and the prefix from the script own `src`, which are the two things a hand-copied panel gets wrong. What a page still declares is its own section anchors, which are headings of that document. **890 lines of markup out, 69 in, and the rendered panel is identical on 28 of 28**, control 0. The outline turned out to be a TREE and not a list, collapsing in three shapes the markup had settled and nobody had written down, and the registry names them: `group`, `nested` and `sub`. **And it nearly broke something silently on thirteen pages**: they carry an inline scrollspy that captures `.sidebar-sub-link` once at parse time, so a panel written on DOMContentLoaded hands it elements no longer in the document. It renders at parse time instead, under the `<aside>` it fills. Spy alive on 13 of 13 after. | `research/`, `user-research/`, `ia/`, `voice/`, `concept/`, 2026-08-11, found by linking the Responsive page into it | The kit already answered this exact question and wrote the answer into `ui-kit/_nav.js`: the route is ONE list, read by a browser at load, because nineteen copies of one list would be nineteen edits for every row and a fact written twice will drift. **The roadmap sidebar outside the kit never got that treatment.** It stands as hand-written rows in **28 HTML files across five folders**, so turning one `<span class="sidebar-page-link planned next">Responsive</span>` into a link was one edit repeated **27 times**, at two different path depths, `../` in twelve files and `../../` in fifteen. The 28th file did not need that edit and needed a bigger one: **`concept/concept.html` stops at UI + Visual** and had never gained Design System, Responsive, Animation or Handoff at all, so its copy had stood four rows behind since stage 09 shipped its page. **Nothing noticed, because a sidebar that ends early looks like a sidebar**, and no page declares how long the list is supposed to be, which is exactly the property `_nav.js` has and this one does not. The drift is closed by hand today and the mechanism is not: the next stage that finishes pays 28 edits again, or pays 27 and one silent omission. Owner: Stage 12 (Handoff), or the first stage that would rather spend the edit once |
| ~~118~~ | ~~**The condensed category band says `aria-hidden` while it is open and operable, and on 48 of 105 screens it is the only category navigation in the document**~~ **CLOSED 2026-08-13, AND THE ROW HAD ITS TWO HALVES THE WRONG WAY ROUND.** **285 focus stops marked as not existing, at 390 and at 1280, are 0.** The strip is `<nav aria-label="Categories (sticky header)">` in all 105 painted and 87 grey documents and carries no `aria-hidden`. **The 48 were never the problem**: the reveal is an `IntersectionObserver` on `.feed-inner > .cat-nav` that returns early when there is none, so on those 48 the band CANNOT OPEN, measured by scrolling every screen to 1200px at both widths - `.scrolled` appeared on 57 of 106 and the strip painted on 57 of 106 and on 0 of the 48. **The 57 were the whole defect**, 54px tall with five links painted and reachable. **And the attribute was never doing the hiding**: with it gone the accessibility tree holds the strip on exactly 57 documents and none of the other 49, because `visibility:hidden` removes a subtree from that tree too. It was redundant where the band is hidden and harmful where it is shown. 0 unnamed navigation landmarks and 0 name collisions over 106 screens; painted tree pixel identical at both widths; the grey tree grew 2px because its own rule draws a box around a landmark, which is the grey tree working. Not a model decision: no item and no route changed. 142 opened for the strip that can never open. `docs/decisions.md`. | `components/header.css` and the markup of 105 painted plus 87 grey screens, 2026-08-11, found by the Responsive step 3 tab pass | The collapsed half of this is CLOSED the same day: `visibility` was added and the band went from **440 focus stops on 88 screens to 0**, in both trees, with the layout control at 0 differing rows of 24. **What is left is the open state, and it is not a stylesheet's decision.** Measured with `.scrolled` forced on: the band is 54px tall, all five links visible, all five operable, and the container still carries `aria-hidden="true"`, so a person using a screen reader is told a visible navigation band does not exist. On the 57 screens that also carry the main `.cat-nav` that is arguably right, because announcing the same five categories twice is noise and the duplicate is what a sticky header shows once the original has scrolled away. **On the other 48 it is not right at all: those screens carry the condensed band and no main band, so the only route to a category is the one marked as not existing.** The two answers are opposite and the choice is about the navigation model rather than about the header: either those 48 gain the main band, or the condensed band stops pretending on the screens where it is the only one. Stage 10 may not take it, because the pack's own rule is that the navigation model is decided at 03a and this stage decides the FORM and never the items. Owner: Stage 03a (IA) with Stage 04 (Wireframes) |

| ~~126~~ | ~~**`ui-kit/docs/responsive.md` is reachable from no stand page, and the group it is missing from carries the rule it breaks**~~ **CLOSED 2026-08-13, and it was already done on 2026-08-12 by commit `ea508d0`.** `_nav.js` lists all five reports and carries a comment recording that the fifth was unreachable for a day, which is the sentence in that file working as written. **The row is struck three days late, which is item 74 arriving again**: a row is closed by an EDIT and struck by a HAND, and the hand is the half that gets forgotten. | `ui-kit/_nav.js`, 2026-08-12 | `ui-kit/docs/` holds **five** reports: `audit.md`, `census.md`, `consolidation.md`, `inventory.md` and `responsive.md`. The "The reports" group in `_nav.js` lists **four**, and the one it leaves out is the newest. The comment sitting directly above that group is the argument against itself: "a step whose artefact nobody can reach from the stand is a step that will be taken again". The tally below it is deliberately blind to this, and correctly so: it counts STAND pages and skips `kind:'doc'` groups, "because a report is done or it does not exist, so it can only ever add the same number to both sides". That is exactly why the omission is silent, and it is why nothing else in the kit will ever report it. **One line in one registry**, which is the whole point of the registry: the alternative route, the roadmap in 28 files, is row 117. Owner: Responsive step 5 |
| ~~127~~ | ~~**Fifteen colour roles have a class in `ui-kit/_page.css` and no swatch on `colour.html`**~~ **CLOSED 2026-08-13, and the fifteen were drawn on 2026-08-12 by the same commit that closed 126.** The three leftovers it also named, `tk-brand`, `tk-pair` and `tk-plain`, were deleted the same day and survive only in the comments recording it. Re-measured in a browser over all 57 kit pages: **294 `.tk-*` classes declared and 294 worn, 0 by nothing.** **The re-measurement found something bigger and different**: `colour.html` draws **66 of the 128 product roles**, and the 62 it does not are bevels, gradient stops, shadows, veils, masks, grain and two opacities, which are not flat colours and would teach the wrong thing as a square. Two of the 62 were created the same day by backlog 120, are flat grounds, and are drawn now. | `ui-kit/_page.css` and `ui-kit/colour.html`, 2026-08-12 | Counted after the 2026-08-12 pass that renamed every class in the file to the stated `.tk-*` prefix: **293 `.tk-*` class names declared and 18 worn by nothing on any of the 57 kit pages**, which is a much smaller number than the same reading gave that morning and is the half of it that survived. **Fifteen of the eighteen are one thing**: `tk-c-bg-brand-mark`, `tk-c-border-brass`, `tk-c-border-brass-hover`, `tk-c-border-hairline`, `tk-c-chart-line`, `tk-c-color-action-lit`, `tk-c-color-action-pressed`, `tk-c-color-trust`, `tk-c-outcome-no-figure`, `tk-c-outcome-no-line`, `tk-c-outcome-yes-line`, `tk-c-result-lost-line`, `tk-c-result-won-line`, `tk-c-text-on-photo` and `tk-c-text-strong`. Each is a swatch class for a role the system really declares and really uses, standing ready on a page that never draws it, so **the one foundation page about colour is missing fifteen of the roles it exists to show** and the stylesheet is the only place that records they were meant to be there. The other three are `tk-brand`, `tk-pair` and `tk-plain`. **A class worn by nothing is the same claim as a `Stand:` line naming a file that does not exist**, and it is the reason this is a row and not a deletion: either the swatch is drawn or the class goes, and only `colour.html` can say which. Owner: Responsive step 5 |
| ~~128~~ | ~~**This file's own counts have now drifted three times, and the second fix moved the copy without removing it**~~ **CLOSED 2026-08-13 ON THE FIRST OF ITS OWN TWO ANSWERS: the section headings do not count anything now.** They drifted a fourth time in the pass that closed this row, and the shape was the shape of all four: seven of eight were right and *Component boundaries* said 6 closed over 10 closed rows, the SAME section that was wrong the time before. **A count in a heading is not a fact about the rows, it is a copy of one**, and the answer tried twice, moving the copy into a paragraph, moved where it is written twice rather than that it is. One number is left in this file, **Open**, at the top, and it is the only one that never drifted because it is the one every pass has to edit to do its work. Everything else is countable from the rows, which is the second answer the row named. | `docs/backlog.md`, 2026-08-12 | **First** (item 74, 2026-08-09): the header total said 45 and the rows said 47, and every section heading but one was stale with it. **Second** (2026-08-10): the same defect again, and the fix was to name the four drifted section counts in the preamble instead of in their headings. **Third, today**: those four preamble counts are all four wrong, *Design defects deferred* 21 and 12 against 21 and 1, *Component boundaries* 6 and 4 against 10 and 1, *Accessibility and keyboard* 5 and 4 against 5 and 0, *Found by the audit* 7 and 3 against 7 and 1; and two section headings had gone stale under them, *Design defects deferred* reading 2 open and 19 closed against 1 and 20, and *Found by the component pages* reading 9 open against 8. All of it is corrected in this pass. **The row is open because the correction is not the fix.** Moving a count from a heading into a paragraph moved WHERE it is written twice and not the fact that it is; the file's own rule says a fact written twice will drift, and it has now drifted from both places. The two answers that would actually end it are to stop writing per-section counts at all and let **Open** be the single total, or to have the reader count. Nothing in this repository will hold it: there are no gates, so a count is kept by being re-read, and three passes say a count is the thing a reader does not re-read. Owner: whoever edits this file next, which is the point |
| ~~129~~ | ~~**The refusal of container queries no longer holds on its own stated ground, and three components carry the case**~~ **CLOSED 2026-08-13. The refusal holds and the threshold was the wrong test, and the defect was the window and the container moving in OPPOSITE directions.** One component in two columns of different widths is NECESSARY and was read as sufficient: the table this row cites also says **35 of 45 components fill their container with no width behaviour at all**, and a component with no rule has no branch that can fire wrongly. So the population was taken from the queries instead - **52 selectors inside 33 width queries: 14 the page frame, the shell or the harness, 2 a positioning context, 36 a component in a slot** - and the 25 standing on both sides of their own rung were read against their PARENT'S CONTENT BOX on all 105 screens and tested for whether ANY container width divides the placements the way the rung does. **24 of 25 did**, so a container query would have resolved identically at every placement, which is the sentence the stage wrote. **The two whose containers really do disagree are the two that set a positioning context**, `.chip-nav` and `.filter-menu`, and a containing block has no width in it to get wrong. Of the three named here, `navitem` has **no width rule at all** and the rail SEPARATES, 761 against 214. **`card` carried it and for none of the reasons given**: its one query moved the bookmark pull below the desk, the bare icon button pulls its 44px target back by 14px, a card has 13px from content edge to clip edge and is `overflow:clip`, so **one pixel of a 44px target was cut off 84 cards at every width from 640 to 1600**. It is unconditional now. **What the pass found instead was one token**: `--gutter` and `--plate-inset` both STEPPED at 640, 38px a side spent where the window gained one pixel, so the content column went 611 to 560 and the card 577 to 502 and did not recover until 715, and nine component rules keyed to that rung all fire as their own box shrinks. Both ramp now, DESK to DETAIL, at a length that is derived: 76px minimum or the column still goes backwards. **Boxes overflowing 247 to 163 at every width at and above 640, controls cut by their card 84 to 0, h-scroll 0 both ways, 0 differing readings of 18,660 outside the band, control 0 of 18,660.** Two queries left the registry, 35 to 33, and nothing replaced either. **And the fix made the first real container-query case this system has had**, which is 145. `docs/decisions.md`. | `components/`, 2026-08-12, found by re-measuring every placement rather than the widest one | Stage 10 refused `@container` with a threshold written out: "a container query with one placement is a media query wearing a different name", and "the threshold to revisit is the first component placed in two columns of different widths". Re-measured across all 105 product screens at 13 widths: **35 of 47 components meet it**, 22 standing in three or more slots more than 25 per cent apart, 15 in four, 10 in five. **The stage looked for the case among the organisms the audit had marked AIR and never looked at the atoms**, which is where it was. Three carry it. **`card`** declares one rule, `max-width:639.98px`, while its box measures 232 at 320, 500 at 640, 300.5 at 759 and 301.5 at 1440: the branch fires and does not fire for boxes of the same size, which is exactly the question a window cannot answer. **The rail** is one container change, `.subcat` going 761 to 214 at RAIL, written as a window query in TWO files, `catnav.css` and `chip.css`. **`navitem`**, 995 placements, has no rule at all and stands at 79 to 159 in the bottom bar, 194 in the avatar menu and 254 to 258 in the notification menu. What is NOT yet decided is whether a container query is the right answer or whether the placements should simply stop disagreeing; that is a design decision and this row is the measurement it needs. Owner: Stage 09 (Design System) or Stage 10 revisited |
| ~~130~~ | ~~**Favorites has no keyboard reach earlier than bottom-nav tab stop 84 on a phone**~~ **CLOSED 2026-08-13. Tab stop 96 to tab stop 11 at 390, on every screen**, as a sixth row in the account dropdown beside My Bets. Nothing moved into the phone header, the bottom bar keeps all four slots, and the closed header is pixel identical on 8 of 8 shots across all three trees at both widths. **The row was right and its walk and mine disagree by the skip link and by the rows an opened menu injects.** Two instrument faults first: the initial walk returned the same numbers on every screen because it was walking the **review chrome**, 109 stops that stand in these documents and in no product; and a walk that only presses Tab never reaches an account menu at all, because **a closed disclosure is one stop and its contents need an activation**. Pressing Enter on each `<summary>` gives My Profile 9, My Bets 10, Wallet 12 and Favorites 96. **The depth was the page**: 18 on Wallet, 51 on Event Detail, 64 on Favorites, 96 on the feed, because the bottom bar is the last thing in the document. At 1280 the heart shows and it was always stop 7. Chosen over the two alternatives because the dropdown already holds two of the four top-level destinations and Favorites was the only one it did not, so this closes an inconsistency rather than adding an affordance, and it reopens no thumb-zone decision. After: menu 6 rows, 196x271, Favorites row 194x44, shortest painted row 44.0px, 534 readings with 0 console errors, 0 h-scroll, 0 overflow. `components/navitem.css` recounted 365 to 438. `docs/decisions.md`. | `ia/docs/sitemap.md` and `components/header.css`, 2026-08-12, found by the step 3 Tab walk while building the skip link | On a 390px signed-in feed the four top-level destinations are not equally reachable: Events is tab stop 1 (the logo), My Bets and Portfolio are stop 3 (the avatar menu), and **Favorites is stop 84**, because the header heart carries `.desk-only` and Favorites is in no dropdown. The bottom bar is the only route and it sits after the footer in the DOM. This is not a layout defect and the skip link does not close it: it is the navigation model saying one of its four slots is a thumb target and nothing else. 03a chose that deliberately when it swapped Notifications and Favorites, and the tradeoff it recorded was about the thumb zone rather than about the tab order. Either the heart loses `.desk-only`, or Favorites joins the avatar menu, or the model states out loud that one destination is 84 stops deep for a keyboard. Owner: Stage 03a (IA) |
| ~~131~~ | ~~**`--text-icon-strong` is declared in both themes and read by nothing**~~ **CLOSED 2026-08-13. Deleted, in both themes, with its swatch on `colour.html` and its `.tk-c-*` class.** The row was right in every particular: zero readers in `components/`, `ui-visual/` and `wireframes/`, and the only thing left drawing it was a swatch on the page whose job is to show roles the product wears. **What it did not name is that the counts beside it had drifted in both directions**: `colour.html` said 133 roles for dark and 93 for light, and the light block held 96 the day that sentence was written, so its own section heading counted forty single-valued roles where there were thirty-seven and never listed the three it invented. 134 and 97 now, read out of the source. `docs/decisions.md`. | `components/tokens.css`, 2026-08-12, found by repairing the `Reads:` headers | `header.css` claimed to read it and does not; with the false claim removed the role has **zero readers in the whole system**. The bell was lifted to it once because it was the one filled mark in a row of stroked ones, and the row is filled now, so the exception went with the reason and the token stayed. It is the one dead token in a file that measured 0 dead tokens an hour earlier, and it was invisible to that sweep for the same reason the sweep exists: **a header naming a role IS a reader as far as a grep is concerned**. Delete it, or find the placement it was drawn for. Owner: Stage 09 (Design System) |
| ~~132~~ | ~~**Four hover transforms are guarded four times where the floor belongs in one place**~~ **CLOSED 2026-08-13, and the blanket rule the row suspected was refused by a census.** The system holds **20 transform declarations and 5 of them are movement**; nine are rest geometry, two are a state that carries meaning rather than motion, and the rest are the harness drawer and two keyframes. **`*:hover{transform:none}` would flatten `.market-chevron` while the pointer is on it**, so an open market would point its chevron the wrong way for as long as you looked at it: pseudo-elements survive that rule and `.market-chevron` is an element. So the floor is a VALUE and not a rule. Every moving transform multiplies its distance by `--motion`, which `base.css` sets to 0 under the query, and the five per-file blocks are gone. Proved: `--motion` reads 0 under reduce and 1 without, `translateY(calc(-3px * var(--motion)))` resolves to the identity matrix and to -3, and **0 of 40,404 computed transforms moved at the default**. One line inside those blocks was inert: `card.css` re-declared a transition under a global `!important` that already beat it. | `components/base.css`, 2026-08-12, opened by the fix it describes | `base.css` carries a global `prefers-reduced-motion` block that writes `transition-duration:.01ms!important`, which shortens a movement and never removes a `transform`. Four components therefore need `transform:none` of their own, and on 2026-08-12 all four got it: `hero.css`, `button.css`, `iconbtn.css`, `dialog.css`, plus `card.css` which already had it. **Five copies of one floor is the shape this folder has already paid for twice**, the 44px touch floor as six lists and the focus ring before it, and both ended as one rule in `base.css` keyed to the family. The reason it is filed rather than done is that the family is not obvious: a rule saying `*:hover{transform:none}` under reduced motion would reach a transform that is not a movement, and nobody has counted those. Owner: Stage 11 (Animation), which owns motion and is next |
| ~~133~~ | ~~**Two icon sizes exist in the ladder and are reachable only by writing a width and a height by hand**~~ **CLOSED 2026-08-13, and the fix the row asked for would have been the defect.** A class per rung puts a SIZE in a document, and the size of a mark is a fact about where it stands: a document writes `.ic` or `.ic-sm` and the PLACEMENT names the size, which is the same rule that says `container-type` is declared by whoever places a component. Counted: **13 rules resize a mark and 8 read the two rungs the row called unreachable**, and `.ic-sm` itself renders at 12 on 354 of 358 placements and at 18 and 22 on the other four. **What was wrong is one level up: three placements sized a mark off another ladder or off none** - `.nav-slot .ic` at `--size-20`, `.state-block .ic` at `--size-28` twice, and `.market-title .ic` at a raw `13px` on 9 placements, one pixel off an existing rung. `--icon-20` and `--icon-28` are on the ladder and drawn on `geometry.html`; the 13 reads `--icon-12`. **And the instrument was wrong first**: a sweep reported 362 placements off the ladder at 13, 28 and 40, and 40 is a 22px mark in a padded tile, because it read a border box. | `components/base.css` and `components/tokens.css`, 2026-08-12, found by drawing the icon ramp on `geometry.html` for the first time | `base.css` names `--icon-22` through `.ic` and `--icon-12` through `.ic-sm`, and the ladder also declares `--icon-16` and `--icon-18`. Any component wanting those two writes `width` and `height` itself, which is the same shape as the control ladder that turned out to hold 3 of its 6 rungs, found the same day and by the same act: **drawing a ladder is what finds the rungs nobody can reach.** Either the two get a class each or they leave the ladder. Owner: Stage 09 (Design System) |
| ~~134~~ | ~~**`.yesno.compact` is 42.7px wide on 52 of 52 placements and cannot grow anywhere**~~ **CLOSED 2026-08-12**, the same day it opened, by the product owner taking the call it was filed to ask for. The row wraps below rung DESK and the pair fills the second line: **103 x 44 at 320, 123 at 360, 138 at 390**, 0 clipped at every width, 0 names truncated, the desk unchanged at 42.7. `decisions.md`, "The product already answered this on the card above it" | `components/yesno.css` and `components/options.css`, 2026-08-12, opened by the plate-inset fix that did not close it | Its children are `flex:0 0 auto`, so the pair holds one width at 320, 360 and 390 alike: **42 placements in cards on 12 screens and 10 in the detail list on 2, all of them 42.7 to 46px**. Two things follow. **It misses the 44px touch floor on the axis nobody checked**, since `min-height:var(--control-44)` fixed the vertical and left the horizontal at 42.7. And it is why 12 elements on 6 screens are still clipped at 320 after the inset stepped: the row's min-content is the sum of its parts, so it overflows rather than compressing, and `.card{overflow:clip}` hides the result from every document-level sweep. **The binary card already answers this in the same product**: its YES / NO pair runs the full width of the card. The compact variant is the only place the same decision wears a second, cramped geometry. The measured fix is to let the row wrap below rung DESK, name and percentage on one line and the pair full-width under it, which takes the button to 103px at 320 and 123px at 360 and clears 320 completely, and costs 69px of card height and 3.2 per cent of feed length. It is filed rather than done because it changes how the product LOOKS rather than how much of it fits, and that is the product owner's call. Owner: Stage 10 (Responsive) |
| ~~135~~ | ~~**The rungs are px and the reason they gave for it was closed the same hour**~~ **CLOSED 2026-08-13. 40rem DESK, 47.5rem DETAIL, 56.25rem RAIL, and the three one-offs with them.** The narrow sides are written exactly rather than rounded, 39.99875rem and 47.49875rem, because the pair rule is what stops both sides of a rung matching at once. **The 1140 harness stays in px**, with the review toggle: a docked panel is 220 physical pixels whatever the reader's font is. At a 24px browser default the desk arrives at **960** and the rail at 1350. **6,300 readings over 105 screens at twenty widths and three browser defaults: 0 horizontal scroll, 0 readings with anything but exactly one navigation carrier**, and 0 differing readings of 2,100 at the default. **AND THE FIRST INSTRUMENT MEASURED NOTHING**: it set `html{font-size:24px}` and reported the rungs not moving, because **a length in a media query resolves against the INITIAL font size and ignores every declaration on the root element**. A reader sets the browser default, not `html{font-size}`, and only CDP `Page.setFontSizes` moves both. The same injection was right for item 115 and wrong here, and the two agree at the default, 0 of 2,100. | `components/tokens.css`, `DESIGN.md`, `ui-kit/docs/responsive.md`, 2026-08-12, opened by the fix that invalidated it | Three files argued the rungs stay px because the TYPE is px: a rung in `rem` while every word is a fixed size would switch the layout at a different window width for a reader with an enlarged browser font while nothing they read changed size, which "looks like accessibility and does nothing". **That was true, it was an argument about the type, and the type moved on 2026-08-12 as item 115.** A rung in `rem` now means the thing it is supposed to mean: a reader whose words are 50 per cent wider reaches the one-column layout at a wider window, because their line holds a third fewer characters at the same width. **The three sentences are corrected rather than deleted**, since the argument is why the type moved at all, but the rungs are now held by nothing except that nobody decided. The conversion itself is three numbers, 640 = 40rem, 760 = 47.5rem, 900 = 56.25rem, all exact; what it needs is the measurement the type move got, because the shell, the bottom bar and the desk header all change hands at 640 and none has been read at a 24px root with the rung following. **This row exists because taking the decision silently inside the sweep that moved the type would have been the fourth time here that a rule outlived its reason and nobody noticed.** Owner: Stage 10 (Responsive) |
| ~~136~~ | ~~**`repeat(3,1fr)` in the profile summary is the only thing in the product that the rem move broke, and the type is not what broke it**~~ **CLOSED 2026-08-13, and the row's reason for staying open did not survive.** It said every candidate fix moves the default rendering; the one that does not is the mechanism this stage's own ladder puts first. `1fr` is `minmax(auto,1fr)` and the figure's min-content is **3.875em at every root**, so three of them plus two gaps need 303px in a container of 224 at a 24px default. `auto-fit` counts columns from a floor and not from content, so a 62px floor in an 888px container gives twelve. What the summary wants is as many of its three figures per row as fit and never more than three, and with exactly three items **a wrapping flex line is that**: `flex:1 1 0` for equal thirds when all three fit, `min-width:min-content` to make the line wrap rather than overflow. **At a 16px default all eighteen figure widths are identical to the grid's, 320 included**, because free space distributed from a zero basis over three items with different minimums is what `minmax(auto,1fr)` computes. The 23px of scroll is 0. | `components/position.css`, 2026-08-12, found by the item 115 sweep | At 320px with a 24px root, `my-profile.html` gains **23px of horizontal scroll**, one screen of 105, at the narrowest width and the largest root. It is the only such reading in the whole sweep: 0 at every other width, 0 at every other root, and the 61 horizontally clipped elements are the same set throughout. The cause is `.pos[aria-label="Portfolio summary"] .pos-figures{grid-template-columns:repeat(3,1fr)}`, and `1fr` is `minmax(auto,1fr)`, so a track cannot shrink below its content and the grid overflows instead: at 24px `$50.00` in the mono figure needs 70.2px and a third of the content column is less. **This is the defect class the repository already deleted once**, backlog 116, where 960 and 1280 hard-coded `repeat(3,...)` and `repeat(4,...)` in all 104 grey files against a painted track that counts its own columns. It was latent because at a 16px root the figures happen to fit. **It is filed rather than fixed because every candidate fix moves the default rendering**, and the pass that found it had just proved 0 change at the default over 44,547 readings: `auto-fit` picks its column count from a floor and not from content, so it would drop to two columns at 16px too; `minmax(0,1fr)` clips the figure instead of scrolling the page; `overflow-wrap:anywhere` breaks a currency figure mid-number. What the grid actually wants is to keep three columns while three fit and take two when they do not, which is a decision about the component. Owner: Stage 09 (Design System) |

| ~~137~~ | ~~**The footer's other two link families clear WCAG and miss the project's own floor, and the gap between those two numbers is a stance nobody has written down**~~ **CLOSED 2026-08-13 AS A BOUNDARY, WRITTEN OUT LOUD.** 44x44 governs CONTROLS; a navigation list is held by WCAG 2.5.8 AA, which is the binding criterion and which all three footer families pass. The row was right that the gap between the two numbers was a stance nobody had written, and it is in `DESIGN.md` under the adaptive rules now, with the reason: 44 is the size of a thing you press to make something happen and a footer column is a table of contents you read and occasionally follow, and putting eleven links on a 44 floor adds 330px to a phone footer to buy nothing any criterion asks for. `components/footer.css` keeps the per-family measurement and points at the stance rather than at this row. | `components/footer.css`, 2026-08-12, opened by the fix to 122 | `.popular-links a` is **80 x 16 at 26.1 centre to centre** and `.legal-links a` is **30.8 x 14 at 41.9**, so both pass 2.5.8 AA on the spacing escape and neither is a WCAG defect. Both miss the 44 x 44 this project declares for itself, along with 1,154 footer column links that now stand 25 tall. **The row is the DIFFERENCE between the two floors and not any one control**: 44 is 2.5.5 AAA and a stance this repository took on its own, and it has never been applied to a dense link list because a column of eleven links on a 44px floor adds 330px to a phone footer. Either the stance says out loud that it governs controls and not navigation lists, or the footer is redesigned so that fewer, larger targets carry the same destinations. **What is not an option is the current state, where the project's own number is quietly true of some families and quietly false of others.** Owner: Stage 09 (Design System) |
| ~~145~~ | ~~**The first two rules in this system a container query would answer differently from a window query, and the ramp created them**~~ **CLOSED 2026-08-13 as a refusal, and the measurement inverts the row: non-separability is a reason to KEEP the window.** Forced into the inline layout at every width from 320 to 1600, **the pair's two halves measure 46 and 42.7px at every container width from 251 to 628**: they are content-sized inline and the name absorbs the rest, so the container tells you nothing about what the pair gets. What the container decides is whether the NAME fits, and it clips at 251 and not at 271, so a container query would have to be keyed near 260 and would put the inline layout on every phone, where the halves are 42.7 against this rule's 268 and one is under the 44px floor this project set itself. **The rule is about the pointer and a pointer is a screen fact.** So 129's test is necessary and not sufficient in both directions: a separable rule may still belong to the window, and a non-separable one may belong to it more. The third, `.icon-btn-tile` at the 560 one-off, needed no work for the reason the row already gave. | `components/options.css` and `components/yesno.css`, 2026-08-13, opened by the fix to 129 | Separability, the test 129 settled on, asks whether any container width divides a rule's placements the way its rung does. Before the page insets were made continuous it read **24 of 25**; after, it reads **22 of 25**. **Taking the discontinuity out of the content column at 640 also took away the thing that let a container threshold stand in for the window there.** `.opt-row` and `.yesno.compact` now sit in a container measuring **551 to 570 at 639 and 552 to 571 at 640**, continuous through the rung, while the rule they carry still flips the outcome pair from **268px a half to 46 and 42.5** across that one pixel. So two outcome rows of the same width get opposite layouts depending on a number neither of them can see. **This is the case Stage 10 went looking for and did not have**, and it did not exist until 129 made the column honest. It is filed rather than converted because the fix is `container-type:inline-size` on the row's container, and size containment changes what an element's width MEANS rather than what it measures: it makes the container's inline size independent of its contents, which is a layout change on every card in the feed and needs its own before-and-after. **The third non-separable rule is not this and needs no work**: `.icon-btn.icon-btn-tile` at the 560 one-off has a container measuring **92 on both sides of the rung**, so no threshold divides anything and the window is its honest owner. Owner: Stage 09 (Design System) or Stage 10 revisited |

## Closed

| Item | Closed by |
|---|---|
| Self-hosting the three font families (opened step 7b as "an open decision, not a silent default") | Stage 09 step 8, 2026-07-28 - 18 woff2 in `assets/fonts/`, gate 20 |
| The bottom sheet on mobile shipping only in the grey tree (recorded step 7e as a product decision) | Stage 09 step 8, 2026-07-28 - `:modal` geometry under 640px |
| The win overlay h2 clipped to "u were right" (found in step 6b, logged for step 7) | Stage 09 step 7, 2026-07-27 - `overflow:clip` on thirteen stones |
| 52 skeleton marks rendering at zero size on 5 loading screens (opened item 20 the same day) | Stage 09 step 13, 2026-08-02 - css only, eight lines of base rules in `skeleton.css`. The markup could not be the fix: a painted screen has a grey twin frozen since stage 04 and gate 18 compares them, so `<span class="sk-line">` stays a span and `display:block` makes it a box. **0 of 482 marks now draw at zero.** Measured on all 19 screens that carry a mark, three widths: 14 of 19 identical to the property, and the 5 that changed are the 5 the item named |
| The dialog family, for `signin` and `outcome-dialog` (opened as item 15) | Stage 09 step 11, 2026-08-02 - folded into `dialog.css` as variants. 36 components, and the 4 rules land at the END of the file because two of them tie `dialog.app-dialog:modal` on specificity. 0 of 15,585 measured elements moved |
