sheleg-design-skill 1.45.0 → 1.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/CHANGELOG.md +1256 -4
  2. package/README.md +7 -4
  3. package/bin/cli.js +7 -2
  4. package/cursor/rules/sheleg-design.mdc +7 -1
  5. package/kits/atrium/src/styles.css +23 -5
  6. package/kits/babylove/.design-sync/config.json +14 -0
  7. package/kits/babylove/.design-sync/conventions.md +29 -0
  8. package/kits/babylove/README.md +16 -0
  9. package/kits/babylove/package.json +14 -0
  10. package/kits/babylove/src/Button.md +15 -0
  11. package/kits/babylove/src/Button.tsx +19 -0
  12. package/kits/babylove/src/Card.md +17 -0
  13. package/kits/babylove/src/Card.tsx +23 -0
  14. package/kits/babylove/src/Chip.md +13 -0
  15. package/kits/babylove/src/Chip.tsx +16 -0
  16. package/kits/babylove/src/DataTable.md +10 -0
  17. package/kits/babylove/src/DataTable.tsx +29 -0
  18. package/kits/babylove/src/EmptyState.md +16 -0
  19. package/kits/babylove/src/EmptyState.tsx +18 -0
  20. package/kits/babylove/src/Heading.md +15 -0
  21. package/kits/babylove/src/Heading.tsx +13 -0
  22. package/kits/babylove/src/Rule.md +11 -0
  23. package/kits/babylove/src/Rule.tsx +10 -0
  24. package/kits/babylove/src/Stat.md +14 -0
  25. package/kits/babylove/src/Stat.tsx +19 -0
  26. package/kits/babylove/src/index.ts +8 -0
  27. package/kits/babylove/src/styles.css +200 -0
  28. package/kits/babylove/tsconfig.json +15 -0
  29. package/kits/blueprint/src/styles.css +17 -8
  30. package/kits/bulletin/src/styles.css +17 -6
  31. package/kits/cyclorama/src/styles.css +1 -1
  32. package/kits/datasheet/src/styles.css +9 -9
  33. package/kits/field-notes/src/styles.css +16 -9
  34. package/kits/instrument-console/src/styles.css +18 -1
  35. package/kits/ledger/src/styles.css +56 -40
  36. package/kits/manpage/src/styles.css +1 -1
  37. package/kits/maquette/src/styles.css +9 -2
  38. package/kits/ora/src/styles.css +22 -7
  39. package/kits/orchard/src/styles.css +3 -3
  40. package/kits/outrank/.design-sync/config.json +14 -0
  41. package/kits/outrank/.design-sync/conventions.md +33 -0
  42. package/kits/outrank/README.md +16 -0
  43. package/kits/outrank/package.json +14 -0
  44. package/kits/outrank/src/Button.md +18 -0
  45. package/kits/outrank/src/Button.tsx +24 -0
  46. package/kits/outrank/src/Card.md +16 -0
  47. package/kits/outrank/src/Card.tsx +23 -0
  48. package/kits/outrank/src/Chip.md +17 -0
  49. package/kits/outrank/src/Chip.tsx +19 -0
  50. package/kits/outrank/src/DataTable.md +15 -0
  51. package/kits/outrank/src/DataTable.tsx +40 -0
  52. package/kits/outrank/src/EmptyState.md +15 -0
  53. package/kits/outrank/src/EmptyState.tsx +18 -0
  54. package/kits/outrank/src/Heading.md +15 -0
  55. package/kits/outrank/src/Heading.tsx +13 -0
  56. package/kits/outrank/src/Rule.md +11 -0
  57. package/kits/outrank/src/Rule.tsx +10 -0
  58. package/kits/outrank/src/Stat.md +16 -0
  59. package/kits/outrank/src/Stat.tsx +19 -0
  60. package/kits/outrank/src/index.ts +8 -0
  61. package/kits/outrank/src/styles.css +328 -0
  62. package/kits/outrank/tsconfig.json +15 -0
  63. package/kits/paperclip/src/styles.css +17 -17
  64. package/kits/patchbay/.design-sync/config.json +14 -0
  65. package/kits/patchbay/.design-sync/conventions.md +38 -0
  66. package/kits/patchbay/package.json +14 -0
  67. package/kits/patchbay/src/Button.md +17 -0
  68. package/kits/patchbay/src/Button.tsx +33 -0
  69. package/kits/patchbay/src/Card.md +18 -0
  70. package/kits/patchbay/src/Card.tsx +23 -0
  71. package/kits/patchbay/src/Chip.md +15 -0
  72. package/kits/patchbay/src/Chip.tsx +21 -0
  73. package/kits/patchbay/src/Diagram.md +39 -0
  74. package/kits/patchbay/src/Diagram.tsx +91 -0
  75. package/kits/patchbay/src/Heading.md +15 -0
  76. package/kits/patchbay/src/Heading.tsx +13 -0
  77. package/kits/patchbay/src/Rule.md +12 -0
  78. package/kits/patchbay/src/Rule.tsx +8 -0
  79. package/kits/patchbay/src/Stat.md +17 -0
  80. package/kits/patchbay/src/Stat.tsx +26 -0
  81. package/kits/patchbay/src/StatusDot.md +19 -0
  82. package/kits/patchbay/src/StatusDot.tsx +15 -0
  83. package/kits/patchbay/src/index.ts +8 -0
  84. package/kits/patchbay/src/styles.css +272 -0
  85. package/kits/patchbay/tsconfig.json +15 -0
  86. package/kits/prism/src/styles.css +4 -3
  87. package/kits/roster/src/styles.css +1 -1
  88. package/kits/scoreboard/src/styles.css +24 -11
  89. package/kits/showroom/src/styles.css +24 -9
  90. package/package.json +3 -2
  91. package/plugins/sheleg-design/.claude-plugin/plugin.json +2 -2
  92. package/plugins/sheleg-design/commands/sheleg-design.md +2 -2
  93. package/plugins/sheleg-design/skills/sheleg-design/DESIGN_SYNC_BRIDGE.md +2 -2
  94. package/plugins/sheleg-design/skills/sheleg-design/FIGMA_BRIDGE.md +4 -1
  95. package/plugins/sheleg-design/skills/sheleg-design/MOBILE_SURFACES.md +1 -1
  96. package/plugins/sheleg-design/skills/sheleg-design/MOTION_DOCTRINE.md +31 -0
  97. package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +20 -7
  98. package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +6 -4
  99. package/plugins/sheleg-design/skills/sheleg-design/styles/STYLE_PACK_TEMPLATE.md +110 -9
  100. package/plugins/sheleg-design/skills/sheleg-design/styles/almanac.md +5 -0
  101. package/plugins/sheleg-design/skills/sheleg-design/styles/atrium.md +186 -12
  102. package/plugins/sheleg-design/skills/sheleg-design/styles/awning.md +10 -2
  103. package/plugins/sheleg-design/skills/sheleg-design/styles/babylove.md +195 -0
  104. package/plugins/sheleg-design/skills/sheleg-design/styles/blueprint.md +4 -1
  105. package/plugins/sheleg-design/skills/sheleg-design/styles/briefing-room.md +6 -0
  106. package/plugins/sheleg-design/skills/sheleg-design/styles/bulletin.md +7 -1
  107. package/plugins/sheleg-design/skills/sheleg-design/styles/cyclorama.md +9 -6
  108. package/plugins/sheleg-design/skills/sheleg-design/styles/datasheet.md +4 -1
  109. package/plugins/sheleg-design/skills/sheleg-design/styles/daylight.md +5 -0
  110. package/plugins/sheleg-design/skills/sheleg-design/styles/editorial-luxury.md +3 -0
  111. package/plugins/sheleg-design/skills/sheleg-design/styles/field-notes.md +67 -12
  112. package/plugins/sheleg-design/skills/sheleg-design/styles/instrument-console.md +23 -4
  113. package/plugins/sheleg-design/skills/sheleg-design/styles/ledger.md +3 -0
  114. package/plugins/sheleg-design/skills/sheleg-design/styles/manpage.md +7 -4
  115. package/plugins/sheleg-design/skills/sheleg-design/styles/maquette.md +5 -1
  116. package/plugins/sheleg-design/skills/sheleg-design/styles/notation.md +5 -0
  117. package/plugins/sheleg-design/skills/sheleg-design/styles/ora.md +3 -0
  118. package/plugins/sheleg-design/skills/sheleg-design/styles/orchard.md +7 -4
  119. package/plugins/sheleg-design/skills/sheleg-design/styles/outrank.md +218 -0
  120. package/plugins/sheleg-design/skills/sheleg-design/styles/paperclip.md +4 -1
  121. package/plugins/sheleg-design/skills/sheleg-design/styles/patchbay.md +418 -0
  122. package/plugins/sheleg-design/skills/sheleg-design/styles/pigeonhole.md +24 -0
  123. package/plugins/sheleg-design/skills/sheleg-design/styles/prism.md +7 -4
  124. package/plugins/sheleg-design/skills/sheleg-design/styles/proscenium.md +8 -3
  125. package/plugins/sheleg-design/skills/sheleg-design/styles/roster.md +10 -5
  126. package/plugins/sheleg-design/skills/sheleg-design/styles/router.md +8 -3
  127. package/plugins/sheleg-design/skills/sheleg-design/styles/scoreboard.md +29 -11
  128. package/plugins/sheleg-design/skills/sheleg-design/styles/showroom.md +13 -3
  129. package/plugins/sheleg-design/skills/sheleg-design/styles/tenor.md +7 -4
  130. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/atrium.css +23 -5
  131. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/babylove.css +114 -0
  132. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/blueprint.css +3 -3
  133. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/bulletin.css +17 -6
  134. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/cyclorama.css +1 -1
  135. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/datasheet.css +1 -1
  136. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/field-notes.css +16 -9
  137. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/instrument-console.css +18 -1
  138. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/ledger.css +57 -40
  139. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/manpage.css +1 -1
  140. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/maquette.css +9 -2
  141. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/ora.css +22 -7
  142. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/orchard.css +3 -3
  143. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/outrank.css +158 -0
  144. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/paperclip.css +17 -17
  145. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/patchbay.css +137 -0
  146. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/prism.css +4 -3
  147. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/roster.css +1 -1
  148. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/scoreboard.css +21 -8
  149. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/showroom.css +24 -9
  150. package/plugins/sheleg-design/skills/sheleg-design/styles/vitrine.md +5 -0
  151. package/plugins/sheleg-design/skills/sheleg-design/styles/workbench.md +6 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,1258 @@ All notable changes to this project are documented in this file. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions
5
5
  follow [SemVer](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [Unreleased]
8
+
9
+ ## [1.47.0] - 2026-08-22
10
+
11
+ ### `patchbay` — the thirty-second pack, and the first read off a page with no tokens at all
12
+
13
+ Measured 2026-08-22 off <https://nautilustrader.io/> with `getComputedStyle` on the running
14
+ page. Named for its register under ADR-0001 — named ports, drawn cords, live signal — not for
15
+ the source brand.
16
+
17
+ **The reference declares zero custom properties.** It is Material UI with Emotion, so there was
18
+ no author-side vocabulary to lift and every value in the token layer was read off a painted
19
+ element. Two of the pack's findings come straight out of that: the MUI theme's own defaults
20
+ leak through where a variant was not overridden, so the page's **59.2px hero is tracked
21
+ +0.555px** — positive tracking on a display line, two selectors away from designed styles that
22
+ track −0.3px — and **fourteen text nodes render in Roboto**, which is not one of the three fonts
23
+ the page loads.
24
+
25
+ **The signature element is a live architecture diagram, and it is measured rather than
26
+ admired.** 21 cords carrying 32 particles, distributed 12 / 7 / 2 at one, two and three
27
+ particles each. Every multi-particle cord divides its own period evenly: the seven pairs are
28
+ offset by exactly half their duration — 1.25s on a 2.5s cord, all seven — and both triples by a
29
+ third, at 0.4 / 1.2333 / 2.0667 and 0.5 / 1.3333 / 2.1667. Consecutive cords start 0.1s apart,
30
+ so the board has no visible beginning. Each cord is an inline `<svg>` with a path, an endpoint
31
+ dot, an arrowhead marker and one `<circle>` per particle driven by `<animateMotion>` — no
32
+ JavaScript, no library, one element per dot.
33
+
34
+ Three corrections the arithmetic produced:
35
+
36
+ * **The reference paints two muted greys and only one of them is legible.** `#848895` is
37
+ 4.86:1; `#686B75`, in 13 text nodes, is **3.23:1**. There is no room for a corrected third
38
+ step below 4.86, so the pack ships **one** muted tier and says which token replaces the other
39
+ in each of its two uses.
40
+ * **The diagram's group labels are 2.70:1** — 9px mono uppercase at 30% white, the least
41
+ legible text on the page. Corrected to 50%, which is 5.19:1 and which the reference already
42
+ uses for its own diagram legend further down. The ladder already had the step.
43
+ * **The status set is derived and its constraint is unusual.** The accent is a mint-cyan, so
44
+ the ordinary dark-UI green collides with it under dichromacy: `instrument-console`'s `#46D39A`
45
+ separates from this accent by **3.51** under simulated CVD against a floor of 8. Every
46
+ candidate that clears the floor is light, which is why `--ok` sits above `--warn` and
47
+ `--danger` in luminance rather than beside them.
48
+
49
+ ### The reduced-motion doctrine gained the three holes this page measures
50
+
51
+ `MOTION_DOCTRINE.md` §9 has always said an animation without a reduced-motion path is a bug.
52
+ What it did not say is that the usual one-rule remedy — zero every duration — **cannot reach
53
+ three layers**, all three of them live on this one reference:
54
+
55
+ * **It cannot stop SMIL.** `<animateMotion>` is not a CSS animation and does not read
56
+ `animation-duration`. All 32 particles keep moving with the preference on. `pauseAnimations()`
57
+ is the fix and it has to be wired by hand; `kits/patchbay/src/Diagram.tsx` wires it.
58
+ * **It cannot stop JavaScript.** The reference's reveal writes `opacity` and `transform` inline
59
+ per scroll frame across 48 wrappers, so with the preference on the content is **still hidden
60
+ until scrolled**.
61
+ * **It teleports a loop that does not end where it began.** Collapsing to `.01ms` jumps to the
62
+ final keyframe. That is safe here only because all four ambient light loops are written with
63
+ `0%` and `100%` identical — so write perpetual loops that way and the cheap remedy stops being
64
+ a gamble.
65
+
66
+ ### What the reference gets wrong, kept because a pack that only admires is not evidence
67
+
68
+ * **`0+` renders on a live page, in two separate sections.** The count-up animates from zero and
69
+ has no state for *the fetch did not return*, so the failure renders as a precise, confident,
70
+ wrong number under `GITHUB STARS`, `DOWNLOADS`, `DISCORD MEMBERS` and `TESTING`. `Stat` in the
71
+ kit refuses: an empty value renders the skeleton and announces itself, never a zero.
72
+ * **The reveal freezes on a programmatic jump.** Measured at eight scroll positions from 700px
73
+ to 1700px reached with `scrollTo`: the target stayed `opacity: 0` at every one and the section
74
+ never appeared. Reached by wheel, it reveals normally — which is what makes it a deep-link,
75
+ anchor and print-stylesheet defect rather than a broken page.
76
+
77
+ ### Three numbers that were already stale beside this change
78
+
79
+ Found while registering the pack, none of them this run's regression: the slash command said
80
+ **"the twenty-nine are"** over a list of thirty-one — invisible to the count gate because the
81
+ numeral named no noun, which is the exact failure the skeleton's rule 6 warns about, so it now
82
+ names one; `SURFACE_COMPOSITION.md` said **sixteen** token layers declare `@role non-text:` and
83
+ the tree says nineteen; and `FIGMA_BRIDGE.md` said **ten** packs ship a theme twin against a
84
+ derived eleven. The widened remainder in `SKILL.md` went stale for the **third consecutive
85
+ release** and the gate caught it, which is what that check was written for.
86
+
87
+ ### ADR-0001 now records that it was broken twice
88
+
89
+ `outrank` and `babylove` both shipped on 2026-08-21 carrying their source brand as the pack
90
+ name, and neither run added an entry. Recorded rather than left to be rediscovered — a register
91
+ showing an unbroken chain of eleven applications was making a claim the library does not
92
+ support. **Neither is renamed:** the ADR's own Consequences make a pack name a public API across
93
+ four channels, which is what makes renaming after release a breaking change.
94
+
95
+ ## [1.46.0] - 2026-08-22
96
+
97
+ ### `babylove` — the thirty-first pack, and the other end of the same question
98
+
99
+ Measured 2026-08-21 off <https://www.babylovegrowth.ai/en> and its
100
+ `/dashboard/overview` in a logged-in session.
101
+
102
+ **The pair is the argument.** `outrank` and `babylove` are the same product category in the
103
+ same year — SEO and AI-visibility SaaS with a landing and a dashboard. `outrank` answers *how
104
+ much system do you need* with **536 custom properties across two borrowed design systems**.
105
+ This one answers it with **seven**: `--brand` and six steps of it, every grey taken from
106
+ Tailwind v4's defaults, and nothing else declared at all. Neither is wrong, and the library now
107
+ carries both ends priced.
108
+
109
+ Three findings the arithmetic produced:
110
+
111
+ * **The brand orange cannot carry a word in either direction** — `#FA5C12` is 3.18:1 on the
112
+ page and white on it is 3.18:1. The reference knows: its hero CTA is **near-black at
113
+ 19.66:1**. The one place it breaks its own rule is the nav button, white on `#F25533` at
114
+ **3.43:1**, in the same viewport as the one it got right.
115
+ * **The rail's section labels are 2.60:1** — Tailwind's gray-400 at 11px, and 2.49:1 on the
116
+ inner card. Corrected to gray-500 `#6A7282`, which is the framework's own next step rather
117
+ than a value from me.
118
+ * **`--brand-dark` is 4.49:1** — one hundredth under AA. Kept because the reference declares
119
+ it, and named as not-the-token-for-text.
120
+
121
+ Its signature is the **disconnected state as a first-class surface**: a card whose source is
122
+ not connected renders at full size with an illustration, a sentence naming what connecting
123
+ buys, and a button — so the dashboard's shape does not change when a source is added, and a new
124
+ account reads as unfinished rather than as broken.
125
+
126
+ ### Two gate defects the thirty-first pack exposed
127
+
128
+ **`NUMBER_WORDS` stopped at thirty, and a second copy of it stopped there too.**
129
+ `COUNT_WITHOUT_ITS_NOUN` spelled the same vocabulary out a second time. At thirty-one the first
130
+ turned every count check red — loudly, correctly — and the second **went quiet on exactly the
131
+ sentence it exists for**. The silent one is the dangerous half, and it surfaced only because
132
+ its own plant then reported the validator going red for an unrelated reason. The pattern is
133
+ built from the dictionary now, longest-alternative-first so `twenty` cannot shadow
134
+ `twenty-one`.
135
+
136
+ **Four plants are anchored on counts that every new pack moves.** Adding `outrank` broke two;
137
+ adding `babylove` broke all four. Each pinned the literal a count edit rewrites. All four
138
+ derive their anchor now. The residue — 57 plants not read for the same shape — is B-131.
139
+
140
+ ### `outrank` — the thirtieth pack, and the first that is two surfaces
141
+
142
+ Measured 2026-08-21 off <https://www.outrank.so> and its `/dashboard` in a logged-in
143
+ session, with `getComputedStyle` in both themes.
144
+
145
+ **Said before anything else: none of this is Outrank's invention.** The marketing site runs
146
+ **Tailwind + daisyUI** — `--rounded-box`, `--btn-text-case` and the `--p`/`--pc` HSL triplets
147
+ are daisyUI's own names — and the product runs **Semrush's Intergalactic**, 384
148
+ `--intergalactic-*` properties on the dashboard. A pack presenting this as a bespoke language
149
+ would be claiming authorship of two published systems.
150
+
151
+ That is the reason to have it. **Every other pack in this library is one surface.** This one is
152
+ the seam between a landing that must convert and a tool that must be worked in for hours, and
153
+ the two halves *disagree* — on the ink, the radius and the type family — while agreeing
154
+ completely on the brand. A team shipping both needs that agreement written down.
155
+
156
+ What the measurement produced, and it is not decoration: **the pack corrects the reference
157
+ three times, and each correction is the smallest the rule permits.**
158
+
159
+ * The eyebrow `#64748B` is **4.37:1 on `--surface`** at the 13px it is set at — below AA on
160
+ the field it appears on most. `#5B6779` is the nearest value clearing 4.5 on both fields.
161
+ * The warning `#FF642D` is **2.95:1**, under even the 3:1 non-text floor. Darkening alone was
162
+ not enough: at `#E5511B` it sits **7.2 OKLab from `--danger`** against a hard floor of 10.0,
163
+ so red and orange are one state to a reader who cannot separate them. `#C2740A` is an amber
164
+ 15.2 away — and amber is the conventional warning encoding, so separation and convention
165
+ agree.
166
+ * The brand violet **does not survive the dark theme** at 2.88:1 on the dark card. The twin
167
+ uses the reference's own `--violet-400` `#AB6CFE` at 4.76:1 — taken from its scale, not
168
+ invented.
169
+
170
+ And one the reference gets right that most packs get wrong: `--accent` `#882AFF` carries text
171
+ **in both directions** — 5.49:1 as a word on the page and 5.49:1 under white on the filled
172
+ button. Most brand violets at that saturation clear neither, which is why the value is kept.
173
+
174
+ The kit ships eight components on the `or-` prefix, including a `DataTable` whose header row is
175
+ the eyebrow carrying its own count, and an `EmptyState` that keeps its border — on the
176
+ reference's content calendar an empty day still renders its cell, because the month's shape is
177
+ the information.
178
+
179
+ **Disabled is a token pair here, not an opacity.** WCAG 1.4.3 exempts an inactive control, which
180
+ is why most packs reach for `opacity: 0.38` — the palette gate computes that composite and
181
+ refuses it. `--ink-eyebrow` on `--surface-2` is 4.40:1, so a disabled control still reads as the
182
+ control it is.
183
+
184
+ Registering the thirtieth pack moved 23 files' counted claims from twenty-nine to thirty and
185
+ broke two plants whose anchors were those very numbers — caught by the self-test and
186
+ re-anchored, which is the failure mode this repository already had on record.
187
+
188
+ ### Five defects the gate could not see, found by three blind readings (B-125…B-130)
189
+
190
+ `task-pipeline` v1.74.0 closes a node with three independent readings at escalating visibility
191
+ instead of one verdict. Its **first live dispatch** was aimed at this repository's own
192
+ `f88c14b` — the strict reduced-motion gate — and it returned two `fail` verdicts and five
193
+ `breaks` findings against work that had shipped green. What each tier caught is the argument for
194
+ the mechanism, so it is recorded here rather than summarised:
195
+
196
+ * **The gate matched durations by NAME, and the product tier proved it with a plant.** It removed
197
+ `--t-hero-art: 0s;` from `paperclip`'s reduced-motion branch in all three copies, left
198
+ `--t-hero-art: 1.1s` in `:root`, and watched the gate print `0 silent duration(s)` and exit 0.
199
+ Thirteen time-valued tokens across ten layers were outside the walk entirely — `paperclip`'s six
200
+ `--t-*`, seven `--stagger*`, `--marquee-cycle`, `--scan-period` — all answered today by the
201
+ authors' habit rather than by the check. **It matches by value now: 127 durations examined, was
202
+ 112**, and that plant is kept so the name-keyed form cannot return.
203
+ * **The ratchet priced the exception rather than the rule.** The seam tier instrumented `check()`
204
+ and collapsed `atrium`'s four kept durations — the remediation the requirement names *first* —
205
+ and measured the count **drop by 4 against a floor of 4026**, turning `npm test` red on the
206
+ stricter answer, where the only way through was lowering a ratchet whose own `_why` says a
207
+ falling count is how a deleted requirement hides. The walk now contributes **exactly one check
208
+ per duration** whatever its answer, so the floor is a property of the corpus.
209
+ * **`docs/DOCMAP.md`'s propagation row still stated the obligation the commit replaced** — one
210
+ collapsed duration, and the superseded check as its proof. **Both blind tiers found this
211
+ independently**, which is the clearest signal the two levels are reading different things. An
212
+ author obeying the row failed the gate; the commit had edited the CHANGELOG, the board, the
213
+ floors, the gate and the CSS, and not the table that tells a contributor what to do.
214
+ * **A published pair was restated and wrong.** *8 of the library's 11 re-declarations* resolved
215
+ against no reading of *re-declaration* the document defined. Measured: **112 at `0s`, 9 at the
216
+ 0.01ms idiom, 6 above an instant.** Both figures are computed here now.
217
+ * **B-120's residual scope did not resolve in the repository holding it** — it named
218
+ `test/*_test.js` and *the other three ratchet files*, and there are exactly three ratchet gates,
219
+ all covered, and no `*_test.js` in this tree.
220
+
221
+ Four more findings are filed rather than fixed, because each is a decision rather than a repair:
222
+ the re-declare-with-reason form is enforced by a gate and sanctioned by no document (**B-126**);
223
+ `atrium`'s canvas-removal promise is unverifiable here and reads as covered (**B-127**); `prism`'s
224
+ ambient point cloud has no stated still (**B-128**); the reason window crosses reduce-block
225
+ boundaries (**B-129**); and four competing definitions of *a duration token* now live in one file
226
+ (**B-130**). **B-125** is the one about me: every floor set on 2026-08-20 sits below its true
227
+ count, because the sequence was measure → keep editing → restate.
228
+
229
+ ### The pairing check the prose ratios needed had been unreachable since it was written (B-053)
230
+
231
+ Forty-six stated ratios sat in prose that named no partner, and the board carried them as a
232
+ residue to be closed one sentence at a time. **The check they needed already existed.** Twenty-nine
233
+ lines inside `validate_stated_ratios` — a full pairing pass that narrows to the token named on the
234
+ line and refuses a number no pair produces — sat behind a `continue`, unreachable from the day it
235
+ was written, reading to any reader as a live check.
236
+
237
+ The comment above it was honest about the bypass: nine lines fail the pass and eight of those are
238
+ correct writing. So the nine were adjudicated one at a time.
239
+
240
+ * **Three are out of scope by construction** — a comparison against a colour the pack does not
241
+ ship (*"7.53:1 on pure white"*), a bound over a set of surfaces (*"11.8:1 on every tint"*,
242
+ *"1.97:1 on any of the six field stops"*), and a candidate measured in order to be rejected.
243
+ Named in `ARGUED` now, each with its line.
244
+ * **Two were an indented table row misread as prose.** `line.startswith("|")` does not see a
245
+ leading space, so the closable-by-one-header-edit class under-reported and the prose residue
246
+ over-reported by exactly those two.
247
+ * **Four were wrong writing.** The figure and its subject had drifted onto different lines, or the
248
+ token on the line was the *remedy* rather than the subject — and a reader cannot tell either from
249
+ a claim. All four were right about the arithmetic (recomputed) and are reflowed in `manpage` and
250
+ `roster` so the subject travels with the figure.
251
+
252
+ **457 computed + 31 verified by the broad pass, 23 unguarded (4%), 6 prose the pass cannot pair.**
253
+ The printed line reports the broad pass as its own class: counting a claim the run just verified as
254
+ *unguarded* is the same untruth as the number it replaced, in the other direction. Two further
255
+ corrections fell out of that — the total is a sum of buckets, so adding one without adding it to the
256
+ total printed **495 claims where 526 exist**, a report getting quieter as coverage improved. And a
257
+ palette plant had been asserting only that a partnerless prose claim *landed in a bucket*; it now
258
+ asserts that a wrong number is **refused**, which is what the revived pass makes possible.
259
+
260
+ ### A carrier phrase was dropped twice, and the second time shipped in seven releases (B-052)
261
+
262
+ `T1` gives an agent five skill descriptions and fourteen tasks and passes only at 0 misses,
263
+ 0 false loads. Its own record already carried the lesson: the 1.12.0 run scored 13/14 because
264
+ *"scrubbed sections"* had been dropped to make room for a mobile trigger, and that phrase was
265
+ the only thing in the description carrying **section by section**. It was restored, the set
266
+ re-ran green, and the harness gained the rule that a description edit obliges the full trigger
267
+ set.
268
+
269
+ **The rule did not hold.** `v1.37.5` (`de09f9e`, *"an unqualified landing page reaches both
270
+ crafts"*) removed `scrubbed sections` while the description **grew** from 1009 to 1021 chars —
271
+ additive by every appearance. T1 was not re-run, so the regression shipped in **v1.37.5 through
272
+ v1.45.0**, seven releases. It was found by walking the phrase across every tag, not by reading
273
+ a diff, which is the only reason it was found at all.
274
+
275
+ Restored (1007/1024), and the class is mechanical now: `validate_t1_carriers_survive` pins the
276
+ twelve phrases T1's tasks depend on to the description and fails when one goes, watched failing
277
+ against the v1.37.5 edit itself.
278
+
279
+ **What this does not do is stated where it matters.** A string test cannot prove a phrasing
280
+ routes here rather than to `super-ux` or `copywriting`. The last fresh-context T1 result is
281
+ 2026-08-11 and the description has changed six times since, so T1's record now says *do not read
282
+ the green below as current* directly above it. That measurement is what B-052 stays open for.
283
+
284
+ ### Every plant names the check it exercises, and one of them never had (B-051)
285
+
286
+ The self-test's fourth tuple element is the string the output must contain — the only thing
287
+ separating *the gate went red* from *the check I wrote went red*. **13 of 60 plants carried
288
+ none.** Running those thirteen one at a time and reading what each actually made fail turned up
289
+ the defect the mechanism exists for: the container plant replaced a bullet's **label**
290
+ (*"Container queries"* → *"Breakpoints"*) while `scoreboard` answers the container question
291
+ twice in the same section, so `CONTAINER_ANSWER` still matched, the check stayed correctly
292
+ green, and the `.cursor` mirror-drift check supplied the red. **It had reported `caught` on
293
+ every run since it was written.** Four more of the thirteen produced a mirror failure ahead of
294
+ their own, which is the same defect one reordering away.
295
+
296
+ All thirteen carry their own check's string now, the container plant removes *every* answer in
297
+ the section rather than one, and `validate_every_plant_names_its_check` makes `expect`
298
+ mandatory — with a plant that strips one, planted into the plant table itself.
299
+
300
+ ### The run-stamp count is recomputed, and the retirement window is readable (B-051)
301
+
302
+ The Run stamps preamble said **twenty-six** rows carry no `Diverged?` answer; the table held
303
+ **thirty-four**. In the one document whose subject is what a run may conclude, that is the same
304
+ restated-number defect this gate refuses everywhere else — and it matters because the third
305
+ retirement trigger reads a stamp *count*, so the number decides what may be retired on absences
306
+ of evidence. `validate_reconstructed_stamp_count` recomputes it and fails when the paragraph
307
+ disagrees.
308
+
309
+ A derivation was tried before any of this and failed honestly: `Diverged?` cannot be recovered
310
+ from the Log, because **0 of the 34** rows have their commit named there. So the row's own
311
+ prescription was executed instead — this session's five runs wrote their own stamps. The newest
312
+ five now answer, which **opens the third trigger for the first time in eighteen releases**.
313
+ `validate_retirement_window` prints OPEN or CLOSED, and the trigger's wording now states that an
314
+ instruction may only be retired when all five newest stamps answer.
315
+
316
+ Two bugs in the new checks, both caught by their own output. `\s*$` under `re.M` eats the
317
+ newline, so `finditer` returned **every other row** — 29 of 57 — while the check printed a
318
+ confident number over half a table. And `"five rows"` matched the preamble's own aside (*"a list
319
+ that is short by five rows"*), so the count check read 5 against 34 and failed for the wrong
320
+ reason.
321
+
322
+ ### The doctrine's duration bands reach the token layer (B-049)
323
+
324
+ `validate_motion_bands` reads a pack's `## Components`, `## Micro-interactions` and
325
+ `## Motion flavor`, because prose is where a pack prescribes an interaction. It never read
326
+ the token layer, and the board named the hole with its instance: `bulletin`'s
327
+ `--dur-panel: 0.4s; /* the nav dropdown */`, 150 ms past §3's dropdown band and invisible to
328
+ every check here.
329
+
330
+ `validate_token_comments_respect_the_bands` reads a **declaration** whose token name or
331
+ trailing comment names a banded element. **9 subjects across 8 packs, all inside their band.**
332
+
333
+ The 30 durations mentioned inside comment *prose* were extracted and deliberately left out.
334
+ Every one of them describes the reference (*"the reference declares `--t-normal: 250ms`"*),
335
+ quotes the doctrine's own band, or explains a reduced-motion collapse. A check reading those
336
+ flags a pack for correctly reporting what it measured, which is how a gate teaches an agent to
337
+ route around it.
338
+
339
+ **Adjudicating the nine found what the row had not.** The same token was called *the nav
340
+ dropdown* in the CSS and *the nav sheet* in the pack's own Motion prose, and §3 bands dropdowns
341
+ at 150–250 ms and sheets at 200–500 — one word made 0.4s a violation and the other made it
342
+ correct. It is a sheet: full-width, `--dropdown-min-h` 420, a `--surface` plane with a bottom
343
+ `--line`. **The naming moved and the value did not.** 0.4s is a pack choice rather than a
344
+ measurement — the reference's whole budget is an entrance fade, a 0.12s press and a 0.3s hover
345
+ — and changing shipped motion on the strength of a word in a comment is the wrong repair. What
346
+ a pack's two files disagreeing costs any band gate is filed as B-122.
347
+
348
+ The first version of the band table silently judged **eight** subjects where nine exist:
349
+ `\bdropdown\b` does not match *"dropdowns"*, so `awning`'s `--dur-control: 200ms /* DERIVED —
350
+ dropdowns and selects */` escaped it. That value is legal, so nothing was hidden — the ninth
351
+ subject would have been the one to hide an illegal value written in the plural.
352
+
353
+ ### Two scoreboard ratios described pairings that never render (B-047)
354
+
355
+ All 30 ratio claims in `scoreboard` were recomputed. **28 agree** to within the repository's
356
+ 0.1 tolerance — including the composited focus halo, which measures 1.28 against a stated 1.29.
357
+ The row predicted five disagreements; there were two.
358
+
359
+ A Gotcha warned that the reference's positive delta is unreadable and put the number on the
360
+ wrong field: `#00D492` **on white — 1.84:1**. 1.84 is its ratio against this pack's paper
361
+ `--bg` `#FAF9F5`; on true white it is **1.94**. A reader who checked the figure against white
362
+ got a different number and a document that looked wrong about the very thing it was warning
363
+ them about.
364
+
365
+ The ring table's header measured every row against `--bg`, and the `--ring-focus-sand` row
366
+ confines its token to `--surface-sand` — the second ring exists *because* the first misses the
367
+ floor there, so `--bg` is the one surface it cannot be about. Its stated **15.88** was correct
368
+ arithmetic about a pairing that never renders; on sand the token measures **14.60**. The table
369
+ has a `Measured on` column now.
370
+
371
+ Both were fixed the way the row asked for rather than by editing a digit: each figure names a
372
+ partner the token layer can pair, so the palette gate **computes** them on every run.
373
+ `computed_at_least` moves 454 → 456. An unguarded correct number is one edit from an unguarded
374
+ wrong one.
375
+
376
+ **The broad check was tried and rejected.** 58 rows across 24 packs name a surface other than
377
+ their header's base, and in nearly all of them the row's *subject* is a surface token, measured
378
+ correctly on the declared base. A gate that flags those is a gate an agent learns to route
379
+ around. Only a row that **confines** its token elsewhere is making the mistake, and after the
380
+ fix there are zero of those — so `validate_confined_tokens_measured_where_used` ships as a
381
+ regression guard with a plant, and says `0` out loud rather than implying a measurement. The
382
+ undecidable half is filed as B-121.
383
+
384
+ Two footnotes on the row itself. Its own coverage figure — *150 unguarded at 22 packs* — had
385
+ gone stale during the same day and reads **57 unguarded (11%) at 17 packs** now. And the plant
386
+ for the new check needed correcting: changing only the table row left a 4-cell row under a
387
+ 5-column header, which the table-shape guard catches first, so the plant was proving that guard
388
+ worked. The defect as it shipped was internally consistent, and the plant is now too.
389
+
390
+ ### Every duration answers the reduced-motion query, and a promise is checked in the kit (B-045)
391
+
392
+ `validate_reduced_motion` was a floor: a token layer had to *have* a branch and the branch
393
+ had to collapse *something*. A layer collapsing one duration of nine passed it.
394
+ `validate_every_duration_answers_reduce` asks the whole question — every duration declared
395
+ outside the branch appears inside it, collapsed to an instant or re-declared at its own value
396
+ **with a reason** — and it verifies the part the board row asked for by name: where the reason
397
+ promises a stop in the component layer, the kit is checked for that stop.
398
+
399
+ Now **29 layers, 0 silent durations, 6 kept with a reason, 2 promising a component-layer stop
400
+ and both keeping it.** `0.01ms` counts as a collapse rather than an exception: it is the
401
+ standard idiom for *instant but still fires `animationend`*, and of the 15 in-branch declarations that are not plain `0s`, **9 are that idiom and 6 sit above an instant**. Both figures are computed from the tree, not restated: the pair this paragraph carried until 2026-08-21 was *8 of 11*, and neither number resolved against any reading of *re-declaration* the document defined — the product tier of the certification measured it
402
+ re-declarations are exactly that.
403
+
404
+ **Two layers were fixed.** `prism` had added `--dur-press: 0.14s` and never updated its branch.
405
+ `atrium`'s four `--flute-dur-*` are kept on purpose — under reduce the refraction canvas is
406
+ *removed* and the still underneath becomes the hero, so a collapsed duration would state the
407
+ opposite of what happens — and that reason now lives in the CSS beside the values instead of
408
+ only in prose a gate cannot read. The row's own measurement was half stale: the uncollapsed
409
+ pair was `atrium` and `prism`, not `roster`, which already used the re-declare-with-reason form
410
+ this check now requires.
411
+
412
+ **The first version of the check reproduced the defect the row exists for.** It returned early
413
+ on any duration that collapsed, so `pigeonhole`'s `--dur-marquee` — which goes to `0.01ms`
414
+ precisely *because* the kit pauses it, since 0.01ms strobes an infinite animation at the reader
415
+ the query protects — never reached the kit verification. Its plant stayed green. The promise is
416
+ a property of the layer, not of one token's verdict, and is checked once per layer now.
417
+
418
+ ### A gate constant was shadowed, and Python said nothing (B-120)
419
+
420
+ The check above shipped broken and passing. A new `DUR_DECL` near line 3120 of
421
+ `test/validate.py` was overwritten by an existing module-level constant of the same name at
422
+ 3716, whose capture groups mean something else — so the new check parsed every duration with
423
+ the wrong regex and printed **`0 kept`** against a tree where six were measured by hand. It
424
+ reported a number instead of failing, which is the exact shape this repository keeps finding.
425
+
426
+ `validate_gate_has_no_shadowed_names` walks the three gate modules with `ast` and refuses any
427
+ top-level name bound twice. It found one pre-existing duplicate — `CSS_COMMENT`, defined twice
428
+ identically, harmless and now deduped. Its own plant needed two fixes to prove anything: the
429
+ check first read `Path(__file__).parent`, which is the one directory a plant can never reach
430
+ because the self-test runs the real module against a copied tree.
431
+
432
+ ### A pack cannot prescribe a token its own layer never defines (B-043)
433
+
434
+ `instrument-console`'s prose named `--dur-press` and its token layer never declared it.
435
+ `var(--dur-press)` is then an undefined custom property — invalid at computed-value time,
436
+ falling back with no error anywhere — which is board B-038's class, described in a comment
437
+ inside the very file that had just acquired a fresh one. The commit that introduced it was
438
+ the one closing B-042, hours earlier.
439
+
440
+ `validate_prescribed_tokens_resolve` refuses it, and the naive form of that check was
441
+ measured before it was written rather than after: **19 of 29 packs quote a token their layer
442
+ does not define**, and nearly all of it is a pack quoting its *reference's* vocabulary —
443
+ `datasheet`'s `--gray-6`, `ora`'s `--font-lora`. What separates a promise from a quotation is
444
+ the prefix family: a pack naming `--dur-press` while defining four other `--dur-*` is speaking
445
+ its own vocabulary. That cut 60 candidates to 11; excluding the `--font-` family on principle
446
+ (a pack layer deliberately does not own font loading) left 3; one is a declared exemption with
447
+ its reason and a line reference. Two were real — the second, `roster`'s `--pattern-grid`, is
448
+ fixed in the prose rather than by inventing a token, because the pattern is composed from the
449
+ five `--pattern-*` values the layer does define.
450
+
451
+ **The plant found a hole in the check itself.** Dropping `--dur-press` from `:root` left the
452
+ reduced-motion block's copy standing, so the token still counted as defined. A declaration
453
+ inside `@media` is not a definition for anyone not asking for reduced motion; `_outside_media`
454
+ now strips those blocks. Zero tokens sit in that position today across 29 layers, which is why
455
+ it was worth closing before the first one does.
456
+
457
+ **`--section` was the library's only raw `vh`.** `clamp(9rem, 24vh, 20rem)` drove section
458
+ padding off the viewport, re-laying out every section when a mobile address bar shows or hides.
459
+ It is `24svh` now, with the reasoning written where the five packs carrying a `--hero-min-h`
460
+ wrote theirs: a hero wants the viewport visible right now and takes `dvh`; padding wants a
461
+ height that does not move.
462
+
463
+ Two of B-043's five claims were false and the row says so. The layer is **4869 bytes, 7th
464
+ smallest of 29** — not 1.4 KB and not the smallest; `briefing-room.css` is smaller. And the
465
+ focus-ring claim misread template rule 5, which governs fallback order for derived colour
466
+ rather than requiring a ring token: **20 of 29 packs ship none.** The type-scale half is real,
467
+ library-wide and refiled as B-119 with its measurement: 6 packs token it, 11 state the sizes in
468
+ prose, 12 do neither.
469
+
470
+ ### One site-wide curve and a mandated scrub cannot both be total (B-042)
471
+
472
+ `instrument-console` declared *"the one site-wide curve"* and then mandated scrubbed SVG
473
+ instruments. `MOTION_DOCTRINE.md` §6 settles it — *under `scrub`, easing must be `none`;
474
+ the scrollbar is already the clock* — and easing a scrubbed timeline eases against the
475
+ scroll position twice, which the doctrine itself calls the family's most common motion
476
+ bug. The pack never carved the exception, so an implementer reading it alone landed on
477
+ exactly that bug. **The carve is now written into its Motion tokens**, and
478
+ `validate_scrub_carves_its_exception` refuses a pack that declares one curve, mandates a
479
+ scrub and stays silent. Measuring the shape across the library found **five** such packs;
480
+ the other four are pinned at `scrub_uncarved_at_most: 4` and filed as B-118, because each
481
+ carve has to be written against that pack's own motion section rather than swept.
482
+
483
+ Two more things the row was right about. The pack's `Contract: core` line said the hero,
484
+ its line ceiling and per-component states were *"yours to decide"* while the pack states
485
+ all three — **the declination is narrowed to what it actually leaves open**, because a
486
+ reader who believed the old wording would have decided them twice. And its fastest step,
487
+ `0.18s`, sits outside the doctrine's 100–160 ms button-press band with no deviation clause
488
+ to appeal to; `--dur-press: 0.16s` is the band's own ceiling, derived from the doctrine
489
+ rather than picked, since ADR 0003 forbids widening this pack by invention.
490
+
491
+ The first plant for the new check was reported *caught* with the ratchet lifted past the
492
+ defect — an edit to a pack trips the `.cursor` mirror check too, so the plant had been
493
+ proving the mirror check worked and saying nothing about the check it was written for. It
494
+ carries the required `expect` string now: with the pin at 5 it reports `MISSED`.
495
+
496
+ ### The library's contrast coverage was 15 claims smaller than it said (B-013)
497
+
498
+ `stated ratios: … 336 guarded` counted a claim as covered before computing a single
499
+ pair. Fifteen of those 336 had never been through the arithmetic — a claim whose only
500
+ named partner is itself (`--ink` in a table declaring `--ink` as its base), or whose
501
+ subject is a composite the gate cannot resolve to a solid (`ledger.css:33`'s `--muted`).
502
+ **The word `guarded` is gone from the line.** What prints now is what happened:
503
+ `513 claims — 348 computed, 15 named a partner this pack cannot pair, 150 unguarded`,
504
+ and each of the 15 is listed by `file:line` on every run, because a number nobody can
505
+ act on is a number nobody acts on.
506
+
507
+ **The unguarded set is classified instead of lumped.** 177 said nothing about which of
508
+ them a person could close. The split says it: `0 in a table declaring no base, 10
509
+ placing the ratio at a gradient stop, 140 prose`. The 27 that sat in five tables closed
510
+ by declaring the base each table's own numbers already resolve against — `atrium`,
511
+ `router` and `orchard` on `--bg`, `cyclorama` on `--ink` with four cells naming
512
+ `--field-2`, `prism`'s components row on `--accent`. No ratio was changed and no base
513
+ was chosen: each was found by computing which token produces the numbers already
514
+ written. `atrium`'s row also had to spell `--ink-2` and `--ink-3` instead of `-2` / `-3`,
515
+ because a shorthand is not a token reference.
516
+
517
+ **A third guard was written and thrown away, and that is the useful half.** A wrapped
518
+ comment leaves the partner alone with the number (`4.40:1 on --bg-deep.`), so reading
519
+ the subject off the line above looked like free coverage — a probe said it resolved 7 of
520
+ the 15. It did not: the probe accepted a match under *either* comparison mode, so any
521
+ ratio above the claim counted as agreement. Inside the gate it produced two false
522
+ failures at `ledger.css:33`, where the real subject is a composite and the line above
523
+ offers a different token. Substituting a subject is the same act as guessing a partner,
524
+ which is what killed attempts one and two. The reason is recorded where the code would
525
+ have gone.
526
+
527
+ ### A pack's own semantic tokens were compared against nothing (B-017)
528
+
529
+ `validate_theme`'s peer set is `STATUS_TOKENS` plus `--accent`/`--primary`/`--cta`, so a
530
+ pack carrying meaning in tokens of its own naming was checked by nothing. `field-notes`
531
+ exists to render provenance, and all four of its provenance tokens were excluded **by
532
+ name**: `--brand-ink` (`[INFERRED]`) and `--witness-ink` (`[AMBIGUOUS]`) are **3.2 apart**
533
+ at full colour and **0.8 under deuteranopia** — the two labels a reader most needs to tell
534
+ apart, on the pack whose reason to exist is provenance. The dark theme is worse than the
535
+ board recorded: `--witness` and `--danger` are **1.2 apart** there, 0.4 under deuteranopia.
536
+
537
+ **The peer group is now whatever the pack declares**, and none of the three forms is new
538
+ syntax — all three already ship:
539
+
540
+ - the state map in a token layer, `[EXTRACTED] → --verify-ink · [INFERRED] → --brand-ink`;
541
+ - a distinctness claim, *"destructive — kept distinct from `--witness`"*;
542
+ - the pack's own disclosure that two tokens share a hue family.
543
+
544
+ What it deliberately does **not** do is infer a set from token names. `--brand` and
545
+ `--brand-ink` are one colour's two jobs; `--witness` and `--danger` are two meanings that
546
+ share a hue. Nothing in the naming separates those cases, so the pack has to say which it
547
+ means.
548
+
549
+ **A disclosure carries its own arithmetic.** *"One hue family, 2.8 apart"* is an honest
550
+ answer only while 2.8 is what the values produce, and `validate_stated_ratios` cannot see
551
+ it — that check reads contrast ratios and this is an OKLab distance. Any distance a
552
+ disclosure states is now computed, bound to the quantity it names: `N apart`, `N at full
553
+ colour`, `N under deuteranopia`.
554
+
555
+ **No hex moved.** `Origin: graphify.com (2026)` makes these values measurements, and a hue
556
+ read off a reference may not be re-stepped. So `field-notes` answers in writing: rust and
557
+ red-orange are one hue family by measurement, the provenance state is carried by the
558
+ bracketed word and its border, and `--danger` is separated from `--witness` by form — a
559
+ filled control, never an inline tag. The sentence *"kept distinct from `--witness`"* was
560
+ false at 2.8 apart and is gone; the Bans now say the conflation they forbid is **in role**,
561
+ and a new ban forbids a provenance state carried by colour alone. `Three semantic hues and
562
+ no fourth` became `three semantic ROLES, and two hue families rather than three`.
563
+
564
+ **Four false-positive shapes were found by measurement and closed, and each is recorded
565
+ where the code is.** A one-tier floor called a pair 19.7 apart at full colour a hard
566
+ failure. A substring test let a line about `--witness-ink` excuse `--witness`. A
567
+ paragraph-wide bind gave every figure in `cyclorama`'s status bullet to every pair — six
568
+ failures against six correct sentences. And two separate regexes cut *"6.4 apart under
569
+ deuteranopia"* in half, comparing a deuteranopia figure with a full-colour distance.
570
+
571
+ ### A row that states an instruction instead of a ratio was checked by nothing (B-048)
572
+
573
+ `validate_stated_ratios` reads a number the document states. A Components row often states
574
+ none — it states an **instruction**: *"`--surface-2` fill at 4% ink, `--ink` at 50%"* — and
575
+ nothing composited it. `scoreboard`'s secondary button shipped a **10–12px/600 label at
576
+ 3.16:1** that way, below AA, with every gate green over it because there was no number to
577
+ check.
578
+
579
+ **One sub-claim of the board row was itself wrong**, and the measurement says so: the row
580
+ held that 4% ink over the paper *"resolves lighter than the declared `--surface-2`"*. It
581
+ resolves **darker** — luminance 0.873 against 0.941 — which is precisely why it pushes the
582
+ label below AA rather than above it.
583
+
584
+ The fix replaces two ad-hoc composites with tokens the pack already ships (`--surface-2`
585
+ fill, `--ink-soft` label) **and states the ratio it renders**, 4.55:1. The first version of
586
+ the fix did not: correcting the spec moved the pair out of every check, which is the same
587
+ trap as B-017, where a false distinctness claim was the only thing making a pair
588
+ observable. A correction that removes the claim removes the coverage with it.
589
+
590
+ **Three behaviours with no token now have one.** `--t-display-sm` 42px and `--t-display-md`
591
+ 44px carry the headline's two unnamed breakpoint steps; `--label-col` 95px,
592
+ `--label-col-narrow` 75px and `--numeral-col-narrow` 70px carry the ledger's geometry, which
593
+ the kit had as three `width:` literals and the pack as prose numbers. Every value is the
594
+ measured one that was already written down — a token names it, it does not choose it.
595
+
596
+ ### A pair a Components row prescribes was computed by nobody (B-050)
597
+
598
+ A ratio is checked when the document states one. A Components row usually states none: it
599
+ **prescribes a pair** — *"`--accent` fill, `--on-accent` label"* — and its hover and active
600
+ cells replace one half of it. `contrast(label, fill)` is derivable at every state of every
601
+ component, and nothing derived it. That is how `instrument-console` shipped a pressed label
602
+ at 3.06:1 (B-039): the press dimmed the fill and the label stayed.
603
+
604
+ `validate_prescribed_pairs()` reads the resting cell for a fill and a label, applies each
605
+ state cell's `fill →` / `label →` swaps, and holds the result to the AA floor for the row's
606
+ type size. 13 rows across 7 packs name both halves and carry 8 state swaps between them.
607
+
608
+ **Both defects it found sit in the two rows the widened phrasings added:**
609
+
610
+ - **`maquette`'s leader label is invisible.** The row prescribes `--ink` on a `--model-top`
611
+ fill, and those are the same hex — `#FFF9F4`, **1.00:1** — in the pack whose central rule
612
+ is that type and object are built from one cream. The pack now ships `--on-model`
613
+ (`#151515`, the field's own colour) at 17.49:1, and the token layer says why in the place
614
+ a reader hits it.
615
+ - **`prism` sets a `$` prompt in `--ink-faint`** at **2.31:1**, a token its own layer calls
616
+ *"furniture only, never text"*. It is `--ink-soft` at 4.70:1 now — the token that layer
617
+ says *"MAY carry body copy"*.
618
+
619
+ As the board row predicted, this check could **not** have caught B-039: `instrument-console`
620
+ is a core-contract pack with no Components table, which is also why the defect survived
621
+ there. 16 rows state half a pair and the count prints on every run (B-059).
622
+
623
+ ### 88 prose ratios joined the arithmetic, and four of them were wrong to (B-003, B-053)
624
+
625
+ 140 stated ratios sat in prose that named no partner, so nothing computed them. They were
626
+ not closed by widening the check — a heuristic that guesses the other side is what this
627
+ file has thrown away three guards for. They were closed by testing **one hypothesis**
628
+ against all 140: *this ratio is against the pack's own field.* **88 agreed with it
629
+ arithmetically**, claim by claim, on 77 lines.
630
+
631
+ Those lines now name `--bg`. Many already said *"on the field"* or *"on the paper"* — the
632
+ same fact, written so a machine cannot read it — and the rest said nothing. Coverage went
633
+ from `353 computed / 150 unguarded` to **`448 / 57`**, 29% unguarded to 11%.
634
+
635
+ **Four of the 77 went red on the first run and every one was a real find**, because giving
636
+ a line a partner puts its *other* claims under the arithmetic too:
637
+
638
+ - `blueprint`'s `3:1` is the non-text **floor**, and the word *floor* had wrapped to the
639
+ next line, so `RATIO_SKIP` could not see it. Reflowed.
640
+ - `proscenium`'s subject was the words *"Body ink"* — no token — and its *"cool act"* is
641
+ `--panel-2`, which the token layer confirms at **16.49:1**. Both named now.
642
+ - `tenor`'s subject sat on the line above its claim, the same shape as the fifteen the
643
+ palette run prints as `unpairable`.
644
+
645
+ The 52 that refused the hypothesis were left alone and stay on the board, because a claim
646
+ whose partner is a panel, a literal the pack does not ship, or a token on the previous line
647
+ needs its own answer, not a rule.
648
+
649
+ ### The board's own table hid two `high` rows for ten iterations
650
+
651
+ A row's priority and status are read from the column its header names. An unescaped `|`
652
+ inside a row shifts every cell after it, and three rows here carried a shell pipe inside a
653
+ code span — `sort -u`, `cut -d/ -f2`, `grep … | wc -l`. Their status cells came back holding
654
+ `sort -u`, `` `datasheet` run, stage 0 `` and `medium`, so the queue that orders this
655
+ repository's work never saw **B-004** or **B-014**, the only two `high` rows in the file.
656
+
657
+ The rows were never wrong. The table was, and nothing looked at the table.
658
+
659
+ `validate_board_columns()` now takes each table's width from its own `| id | … |` header and
660
+ refuses any row that does not match, across `backlog.md` and `verification.md`. Planted with
661
+ an unescaped pipe and watched refusing. Floor 3560 → 3619, one check per row.
662
+
663
+ Measured across the whole family in the same pass: **13 such rows in five repositories**,
664
+ including rows whose columns were simply missing or doubled. All are fixed and the count is
665
+ zero.
666
+
667
+ ### A tag can no longer publish a release no clone can reach (B-014)
668
+
669
+ A tag pushed without its branch is a release that exists on npm and on GitHub and nowhere a
670
+ `git clone` can find it. Nothing fails loudly, because the release workflow fires on the
671
+ **tag**: the release is created, the package publishes, and `origin/main` still reads the
672
+ previous era. This repository hit it twice — the `v1.4.0` ghost on 2026-08-04, and `v1.18.0`
673
+ on 2026-08-12 with `main` two commits ahead of `origin/main` while the tag was public.
674
+
675
+ The release job now refuses it: `git merge-base --is-ancestor HEAD "origin/$DEFAULT"`,
676
+ against the repository's **actual** default branch read from the API rather than a hardcoded
677
+ `main`, so a fork that renamed its branch is not told its correct tag is unreachable. The tag
678
+ checkout gains `fetch-depth: 0`, because ancestry on a shallow clone is not an answer — it is
679
+ whatever the depth happened to give.
680
+
681
+ Watched refusing against a reproduction of the defect: a commit that was never pushed,
682
+ tagged, and the ancestry test says so. Watched passing on this repository's real HEAD. **All
683
+ eight members carry it**, and the umbrella requires both halves in each, watched failing when
684
+ either was removed.
685
+
686
+ ### `atrium` is widened, from the reference rather than from its own tokens (B-004)
687
+
688
+ Three packs were held at `Contract: core` because widening them from the token layer would
689
+ have been *inventing values with a citation attached* — the one thing the pack layer exists
690
+ to prevent. Their references are addressable, so the honest route was always a measurement
691
+ pass, and this is the first of three: `atrium`, read on 2026-08-20 from
692
+ `functionhealth.com`'s own `/_astro/utilities.*.css` and `/_astro/PageSections.*.css`, mapped
693
+ through the reference's **own** token names — `--fill-04` is `--accent`, `--fill-03` is
694
+ `--ink`, `--fill-01` is `--bg`, `--stroke-02` is `--line`.
695
+
696
+ - **`## Components`** is the reference's single `.fh-button` class and its four skins, with
697
+ every state: the pill radius (`999px`, not `--radius-lg`), 48px and 40px heights, the exact
698
+ paddings, and the behaviour that matters — **the press does not dim the accent, it replaces
699
+ it with the ink**, so the label goes from 4.59:1 to 13.47:1 rather than losing contrast.
700
+ Hover and focus-visible share a fill, so the 2px accent-at-35% ring is the only thing that
701
+ separates keyboard from pointer.
702
+ - **`## Hero`** is the numbers of the full-viewport media hero: a 540px text column at the
703
+ bottom of the stage, `--h1` fluid from **57px to 80px** at weight 300 and `line-height: .9`,
704
+ the `blur(15px)` eyebrow pill over 7% white, a stat rail with 58px hairline dividers, and
705
+ what changes below 768px — the stage becomes 666px and the rail leaves the media for a
706
+ `--bg` bar beneath it.
707
+ - **`## Responsive`** is its **184 media queries, counted**: 768px and 1100px carry 140 of
708
+ them. It also records the thing a reader would otherwise get wrong — **type does not step at
709
+ those boundaries, it slides**, `clamp()`-ed between 376px and 1440px, so mixing a fluid
710
+ heading with a font-size media query breaks the rhythm. And it answers the container
711
+ question with a technique rather than a shrug: the reference declares `container-type` in
712
+ exactly one place, a stage whose every dimension is in `em` and whose `font-size` is driven
713
+ by the container width, so one number scales the whole composition. 152 `vw` expressions
714
+ against 2 container-relative ones.
715
+ - **`## Signature element`** names the fluted-glass hero that Signature motifs already
716
+ measured, and says why it is the signature rather than one motif of several.
717
+
718
+ **The pass found a value the reference does not ship.** `--accent-tint` was `#F7EEEB` —
719
+ which appears **nowhere** in the reference's CSS or its HTML — against its
720
+ `--orange-10p: #F6E9DC`. The mapping is not a guess: its neighbour `--green-10p` is `#f1f3e5`,
721
+ this pack's `--good-tint` to the character. Corrected, with the search that found it recorded
722
+ in the token layer.
723
+
724
+ It also found what no section had ever said: **`--accent` is 4.17:1 on `--surface`**, below
725
+ AA. The accent is safe as a label on `--bg` and on `--ink`, and not on the cream card. The
726
+ reference never puts one there; now the pack says so.
727
+
728
+ Contract split **7 core / 22 widened → 6 / 23**. `orchard` (gutgutgoose.com) and
729
+ `editorial-luxury` (prowl.chat) remain, one measurement pass each.
730
+
731
+ ### `orchard` was measured and deliberately not widened (B-004, B-110)
732
+
733
+ The second of the three packs held at `Contract: core`. Its reference is addressable, so the
734
+ same measurement pass was run against `gutgutgoose.com` — and it stopped, on purpose, at
735
+ `## Components`.
736
+
737
+ **What the pass established.** All **13** of `orchard`'s token values are present in the
738
+ served document — 10 as hex and 3 as `rgb()` triples, which a hex-only search had first
739
+ reported as missing, so the first answer was wrong and looking harder was the fix rather than
740
+ concluding. Its responsive model is the **opposite of `atrium`'s**: three fixed frames
741
+ (desktop ≥1200, tablet 810–1199.98, phone ≤809.98) with a **390px phone frame** carrying a
742
+ 342px column, and the tablet/phone tier changing 61 rules dominated by `width` (32), `order`
743
+ (22), `height` (16) and `padding` (14). **No `clamp()`, no container query, no `vw` at all** —
744
+ variants are swapped, not scaled. The slab radii are 12px (23 uses), 20px (10), 24px (9) and
745
+ 40px (4).
746
+
747
+ **Why it was not widened.** `## Components` is per-component states, and a Framer document
748
+ serves none. The contract is all-or-nothing by design — *touch one of the four and you owe
749
+ all four* — so writing three from measurement and the fourth from the token layer would be
750
+ precisely the failure `core` exists to prevent.
751
+
752
+ **And the pass found a contradiction worth more than the widening.** The pack calls its candy
753
+ pill *"the only orange object on the page"* and `--cta` *"the ONE action colour, a fill"*.
754
+ On the served page `#FA7241` occurs **only inside SVG `fill=` attributes** and **never as a
755
+ `background-color`**; what paints controls there is `--surface`, `--bg`, **`--primary`
756
+ `#78934a` (14 declarations)**, its 10% wash, and `--surface-ink`. Three readings fit the
757
+ evidence and it does not choose between them, so the pack was **not** rewritten — filed as
758
+ B-110, to be settled in the same rendered pass that `## Components` needs.
759
+
760
+ ### `## Hero` had two readings, one worked example, and no check (B-007)
761
+
762
+ The board said the standalone reading was missing from the library. **It was not** —
763
+ `ledger` had already written it: *"A console's hero is its first viewport after sign-in"*, with
764
+ a kicker, a title at a console size rather than a display size, the answer before the evidence
765
+ that explains it, and an explicit list of what the screen must not carry. What was missing is
766
+ that **the skeleton did not carry it**, so every console-pack author re-derived the heading
767
+ alone, which is exactly what the T7 author did and said.
768
+
769
+ The skeleton now names both readings with a worked example each — `datasheet` for the
770
+ marketing page, `ledger` for the console — and states that both owe the same two things.
771
+
772
+ **And nothing had ever read the answer.** The skeleton has demanded a line ceiling *and the
773
+ container width that keeps it there* since 1.5.0, and no check looked. Across the 23 packs
774
+ that carry a `## Hero` **heading** — never the string, which a `core` pack names in its own
775
+ contract line to decline it — there was exactly one live defect of each kind:
776
+
777
+ - **`atrium` stated no ceiling.** Widened by this same loop one iteration earlier, with every
778
+ measurement in place and the one obligation the skeleton spells out left out. Fixed from the
779
+ reference: **two lines in the 540px column**, and the ceiling is a word budget rather than a
780
+ CSS rule — the reference's own headline is *"Check your health."*, three words.
781
+ - **`showroom` stated a ceiling with nothing holding it.** Fixed from its reference, and the
782
+ answer is the interesting one: **no `max-width` holds it at all**. The headline sits in a
783
+ centred column with side padding only — 24px, 56px, **58px from 1024px** — and
784
+ `text-wrap: balance` at `line-height .95` does the rest, so the ceiling is a **four-word
785
+ budget**. The reference's headline is *"Welcome to agentic revenue."*
786
+
787
+ Two plants, both against `maquette`, chosen by measurement rather than by eye: its hero carries
788
+ exactly one phrase of each kind, so removing one leaves the other standing and each plant
789
+ proves its own half. Floor 3619 → 3667.
790
+
791
+ ### Every pack now declares its two real silences, and the gate derives them (B-008)
792
+
793
+ `Contract: core` named four sections and nothing else, so a pack's **other** silences stayed
794
+ undeclared. The case on the board: `workbench` ships four status roles and no severity ramp,
795
+ and a fresh-context agent building an incident list inferred a rank scale from role
796
+ descriptions and discovered the gap by hitting it.
797
+
798
+ The row offered two answers and **both were refused, on evidence**. A free *"also not
799
+ specified here"* list is unbounded and uncheckable — every pack would write a different one
800
+ and nothing could compare them. And four is not the boundary, because two silences are real
801
+ and were counted before anything was chosen:
802
+
803
+ - **Themes: 10 ship a dark twin, 13 ship one block, 6 ship a second block that is a
804
+ *surface* rather than a theme** — `[data-surface="dark"]`, `[data-surface="panel"]`,
805
+ `[data-state="alarm"]`. So *"does this pack have a dark mode?"* had three different answers
806
+ depending on how a reader looked, and `tenor` in particular ships something dark that is
807
+ not a dark theme.
808
+ - **Rank: exactly one pack of twenty-nine ships an ordered ramp** — `tenor`, with
809
+ `--sev-ask` → `--sev-limit` → `--sev-never`. The other twenty-eight were silent.
810
+
811
+ So each pack carries a `Themes:` line and a `Rank:` line, **derived from its own token
812
+ layer**, and `validate_pack_declares_its_silences()` re-derives both on every run and refuses
813
+ a declaration that has drifted from the thing it describes. That is the property a prose list
814
+ could never have: the answer is checkable against the tokens, not merely present.
815
+
816
+ Four plants — each line removed, and each claiming what its own tokens contradict. The
817
+ skeleton carries both questions with the counts and the reason, and says plainly that a third
818
+ question is only added when it can be answered the same way. Floor 3667 → 3783.
819
+
820
+ The library's own counted-claims guard refused the first draft of that skeleton text, which
821
+ said *"11 packs"* where the checkable phrase means the pack total. Reworded, not exempted.
822
+
823
+ ### The code graph is current again, and the refresh needed no key (B-009)
824
+
825
+ The row had been re-filed as *staleness alone — a re-run, not a credential question*, at 13
826
+ commits behind. By the time this loop reached it, the same loop had pushed it to **31**.
827
+
828
+ `graphify update .` — the path whose own help says *no LLM needed* — moved `built_at_commit`
829
+ from `de09f9e` to **`856f12c`, which is HEAD**. And it did not do the thing the row's history
830
+ feared, where a code-only pass had once written fewer nodes than the curated graph held:
831
+ **every class grew.** Documents **1797 → 2173**, code **1646 → 2105**, concept 243 → 340,
832
+ rationale 126 → 155. In total **3812 → 4773 nodes** and 4185 → 4473 edges across 920
833
+ communities. The shrink guard never fired because there was nothing to guard against — and
834
+ the curated graph was copied to `graphify-out/2026-08-20/` before the run regardless, because
835
+ that is the order the row itself insists on.
836
+
837
+ **No placeholder labels survived.** 116 communities changed membership and were renamed by
838
+ their hub; a scan of all 4773 nodes finds **zero** carrying a bare `Community N`.
839
+
840
+ **Stage 9's own check passes.** All ten god-nodes resolve to documented seams — the two gates
841
+ and their `check`/`read`/`main`, the pack skeleton, two style packs, the CHANGELOG, and the
842
+ third-largest, `Component Spine`, which is a rationale node from `kits/atrium/src/Button.md`
843
+ named in the CHANGELOG and in two specs.
844
+
845
+ **Scope, stated rather than implied:** `graphify-out/` is gitignored — *machine state, not
846
+ source* — so *current* means current on this machine. A clone still builds its own, and this
847
+ close does not change that.
848
+
849
+ ### A count whose noun is only implied, and the one form that can be caught (B-016)
850
+
851
+ `validate_counted_claims` reads *"N packs"*. It cannot read *"the accent role resolves to
852
+ `--accent` in twenty-seven,"* where the noun is implied by context — which is how that one
853
+ sentence sat wrong at ten, then thirteen, then fourteen, corrected by hand each time and
854
+ unguarded again the moment it was.
855
+
856
+ **Three patterns were measured and two thrown away**, which is the useful half:
857
+
858
+ | Pattern | Spans | Verdict |
859
+ |---|---|---|
860
+ | every numeral with no counted noun after it | **2553** | ordinary prose — *three cards*, *one motion methodology* |
861
+ | a numeral **closing its clause** | 62 | nearly all correct ellipsis — *one gate of four*, *wrong two times out of three* |
862
+ | the same, in sentences naming a library noun | 69 | worse: mostly the word *one* |
863
+ | **`the <numeral>` closing a clause** | **7** | precise — a definite article asserts a known set |
864
+
865
+ Of those seven, three are library counts, and **one of the three was wrong**: `tenor.md:328`
866
+ said token names are *"not uniform across the twenty"* while thirty-one packs ship. A live
867
+ wrong number, invisible to every gate, found by narrowing rather than by widening.
868
+
869
+ All seven now name their noun — which hands them to the existing check forever — and
870
+ `validate_a_count_names_its_noun()` refuses the bare form. Both count checks read one shared
871
+ corpus now; it was inline in one of them, and two copies of a source list are two lists that
872
+ drift.
873
+
874
+ **Naming the noun immediately exposed two more defects, which is the point of the exercise.**
875
+ `the nine` became `the nine headings` and the gate refused it at once: the contract is
876
+ thirteen. It reads `nine of the thirteen headings` now. And a *true subset* — the accent role
877
+ resolving in 27 of 29 — is refused when written `twenty-seven packs`, because the check reads
878
+ any `N packs` as a claim about the total. A subset is written **`N of the M packs`**, and that
879
+ rule is now in the pack skeleton with the rest.
880
+
881
+ The skeleton carries this as its **sixth** rule — and writing it corrected the section's own
882
+ heading, which promised four rules over five. Floor 3783 → 3789.
883
+
884
+ ### `field-notes` ships thirty dark overrides that no prose carried (B-018)
885
+
886
+ The pack declares `Themes: light + dark — a full theme twin` and its Palette documented only
887
+ the light values. `tokens/field-notes.css:251-291` is now written out as a table, and writing
888
+ it confirmed all three consequences the T24b run reported — plus one it missed:
889
+
890
+ - **`--surface-2` and `--surface-3` are the same value** in dark, `#262019`, so the deepest
891
+ step the light table names does not exist there. **`--ink` and `--ink-2` collapse the same
892
+ way**, which the row did not catch.
893
+ - **No `--dawn-*` stop is re-declared**, so `--hero-dawn` still ends `--dawn-7` `#E9ECDF` at
894
+ 98% into `--bg` at 100%. In dark that is near-white meeting `#14110E` across two percent of
895
+ the gradient — the hard edge this pack's own Bans forbid. The Palette now says plainly:
896
+ **do not ship the hero in dark** until that decision is made.
897
+ - **The pack's stated decision and its shipped theme disagree.** Gotchas says the forest
898
+ family was kept over the reference's warm-brown `.dark`; the layer ships `--bg: #14110e`,
899
+ which is that warm-brown. **The reference cannot settle it**: `graphify.com` serves no dark
900
+ theme at all today — no `[data-theme="dark"]`, no `.dark`, no `prefers-color-scheme`, and
901
+ none of the thirty dark values anywhere in the 488 KB it returns. Recorded where a reader
902
+ hits it and filed as B-112, not guessed at.
903
+
904
+ The row's fourth claim was imprecise and is corrected rather than repeated: `SKILL.md` carries
905
+ no list of packs with a mode collection. `FIGMA_BRIDGE.md` uses `workbench` as an *example*,
906
+ and now says so, pointing at the `Themes:` line each pack carries.
907
+
908
+ ### A number this run published four hours ago was wrong
909
+
910
+ The theme split shipped into the pack skeleton as **11 / 13 / 5**. The tree says **10 / 13 /
911
+ 6**. Two derivations of one number existed in the run that wrote it — a loose regex over the
912
+ raw CSS, which counts any pack merely mentioning `dark`, and the strict one that generated the
913
+ `Themes:` declarations — and the prose quoted the loose one.
914
+
915
+ Corrected in all six places it had reached, and `validate_theme_split_is_derived()` now
916
+ computes the split from the declarations and refuses a document that states another. Planted
917
+ with the exact wrong number that shipped.
918
+
919
+ ### Routing: four of five findings held, and correcting one number found two more (B-019)
920
+
921
+ - **The dial precedence rule had no third case.** *"The row that names the surface wins over
922
+ the row that names a mood"* does not arbitrate a row naming **neither** — *"trust-first:
923
+ regulated, public-sector, clinical"* names who reads it, not what they read it on. Both T24
924
+ branches hit exactly that and both split two-to-one on MOTION, which is the tell. The rule
925
+ now says an audience posture does not arbitrate DENSITY and **wins on MOTION and ornament**,
926
+ because that is the whole of what it constrains.
927
+ - **`widened` appeared zero times in the selection text**, although every non-core pack
928
+ declares it. It appears now, beside the core marks.
929
+ - **"Fetch the kit first" was unconditional inside a core-scoped paragraph.** A kit exists for
930
+ every pack; for a widened pack the kit and `## Components` must agree, and for a core pack
931
+ the kit is the only answer either of them gives.
932
+ - **The greenfield contradiction does not hold against the current text.** Lines 219–226 give
933
+ the path explicitly — *build the page in the pack the brief's register points at*, with
934
+ `workbench` and `showroom` as the no-signal defaults. Recorded as not-reproduced rather than
935
+ repeated.
936
+
937
+ **Correcting one count exposed two more in the same file.** `SKILL.md` said **seven** packs are
938
+ on the core contract where six are; it still marked `atrium` `**core contract**` **eight
939
+ iterations after this loop widened it**; and it said **twenty-two** widened where twenty-three
940
+ are — a remainder the gate's own message records as stale across two consecutive releases.
941
+
942
+ The check that existed compared the prose *count* with the table, so it could only fire when
943
+ somebody edited the prose. `validate_table_marks_match_the_contract()` now reads the table
944
+ against each pack's own `Contract:` line, **in both directions**: a mark left behind after a
945
+ widening, and a core pack the table forgets to mark. Planted both ways.
946
+
947
+ Finding (1) is real and larger than the row said — filed as B-114 with the spread measured:
948
+ the motion ceiling has **two homes**, the doctrine names ten packs, four state one in their own
949
+ Register, the overlap is two, and six packs marked `(standalone)` have a ceiling in neither
950
+ place.
951
+
952
+ ### The motion ceiling had two homes; now it has one (B-114)
953
+
954
+ Measured before anything moved: `MOTION_DOCTRINE.md`'s standalone passage named ceilings for
955
+ **ten** packs, **four** packs stated one in their own `## Register`, the overlap was **two**,
956
+ `awning` stated a 4 the doctrine never mentioned, and **six** packs marked `(standalone)` in
957
+ `SKILL.md` had a ceiling in neither place. A reader who chose off the table and stopped took a
958
+ ceiling that might not exist.
959
+
960
+ **The Register is the home**, because it is what a reader of one pack actually has. Eleven
961
+ Registers gained a statement — each relocated from the doctrine with the doctrine's own reason
962
+ (`workbench` and `briefing-room` at 3 as not cinematic; `ledger` at 3 with its three state
963
+ loops stopped under reduced motion; `pigeonhole` at 4 at the family's floor; `roster` at 4,
964
+ keeping only its sticky nav; `field-notes` with **no hard ceiling**, the one standalone
965
+ exception) — or, for the six the doctrine never named, from the default it does state: the dial
966
+ is cut by §1's frequency table alone. All **fifteen** standalone packs now state a ceiling or
967
+ state they have none.
968
+
969
+ `validate_motion_ceiling_has_one_home()` asks three things: every `(standalone)` pack states
970
+ one, no Register states two, and where the doctrine names a number the Register agrees.
971
+
972
+ **Two defects in the check were found by its own plants**, which is why they exist. Reading the
973
+ number *before* the "no ceiling is pinned" phrase made a Register that says both report the
974
+ number and pass — hiding the exact case the next block was written to catch. And the doctrine
975
+ writes a ceiling two ways, *"`pigeonhole` … above **4**"* and *"`ora` at **4**, `tenor` at
976
+ **4**"*, so reading only the first left four of the packs it names uncompared.
977
+
978
+ Corrected, it failed immediately on `proscenium` — where **this run's own default sentence sat
979
+ on top of a Register that already named 4**. Removed. Floor 3795 → 3830.
980
+
981
+ ### The two radius rules were never competing (B-020)
982
+
983
+ The board said the skeleton teaches a rule a shipped pack records as a defect, and asked which
984
+ one is right. **Both are** — they govern different layers, and the row's framing was the thing
985
+ to fix. Measured across the library: **sixteen packs state radius arithmetic.**
986
+
987
+ - **The proportional ramp defines the token set.** `--radius-sm: calc(var(--radius) * 0.6)`.
988
+ That is where a value comes from.
989
+ - **Subtraction adjusts a nested instance** where the padding is known:
990
+ `calc(var(--radius-control) - 4px)`, and `datasheet`'s 16 − 8 = 8 is subtraction done right.
991
+
992
+ `ora` uses both and says which is which. `field-notes`' recorded error was **conflating them**:
993
+ its rule said subtraction, its worked example subtracted 12 from 12 and announced 7.2, and its
994
+ token layer used neither — three systems, and an agent applies the one it can read.
995
+
996
+ The skeleton teaches both layers now, what each governs, and that failure by name. And
997
+ `field-notes`' correction no longer over-reaches: it said *"not by subtraction"*, which reads as
998
+ *subtraction is wrong* rather than *subtraction is the other layer*.
999
+
1000
+ `validate_worked_radius_sums_compute()` refuses a written subtraction that does not compute,
1001
+ excluding a `*(Corrected …)*` note because that is a record of an error rather than a claim. It
1002
+ ships with **no live subject** — every sum in the sixteen packs computes — so it ships with a
1003
+ plant instead. It then caught **this run's own cautionary quote in the skeleton**, where the
1004
+ broken sum had been reproduced verbatim: a reader copies what they see, so the warning is now
1005
+ written as prose rather than as a copyable equation. Floor 3830 → 3833.
1006
+
1007
+ ### The Components list is a contract, and the library had already decided (B-025)
1008
+
1009
+ The board asked whether the skeleton's six component classes are a contract that ten widened
1010
+ packs owe entries for, or a menu an author picks from. **The library's own behaviour settles
1011
+ it.** Counted over the twenty-three packs carrying the heading: **buttons in all 23, cards in
1012
+ 22, inputs and navigation in 21, loaders and empty states in 19.** Calling the list a menu
1013
+ would license the gaps rather than close them.
1014
+
1015
+ So the skeleton says **contract** — and says the thing that makes it liveable: **"none, and
1016
+ here is why" is a full answer.** What is not an answer is silence, because a reader cannot tell
1017
+ a pack that has no loader from a pack whose author ran out of afternoon.
1018
+
1019
+ **`atrium`'s four gaps are closed by measurement**, read again off `functionhealth.com`:
1020
+
1021
+ - **Inputs** at the button's own 48px control height, a pill border in `--line-strong`, a white
1022
+ fill rather than `--bg`, an `--ink-3` placeholder — and the honest note that label position
1023
+ and error placement are **not on the reference's page**, so they are the implementer's.
1024
+ - **Navigation**'s mobile shape in full: the panel's fill goes `--accent` closed → `--ink`
1025
+ open, with an `::after` strip carrying the same fill so the two cannot disagree mid-transition.
1026
+ - **Empty states** as `--ink-2` at 16px/300, centred, no illustration and no button.
1027
+ - **Loaders: none** — and that is measured, not omitted. Zero skeleton, shimmer or spinner
1028
+ rules across both stylesheets.
1029
+
1030
+ `validate_component_classes_are_answered()` pins the remaining **nine** (class, pack) pairs as
1031
+ a ratchet that may only fall. It is a ratchet rather than a sweep because each fall needs its
1032
+ own reference read, and inventing an answer is precisely what the pack layer exists to prevent
1033
+ (B-115).
1034
+
1035
+ **Its plant needed two rewrites, and both failures are instructive.** Removing the Loaders
1036
+ bullet's *label* left the words *skeleton*, *shimmer* and *spinner* standing in its body, so the
1037
+ class still read as answered and the plant proved nothing. And the regex that then removed the
1038
+ whole bullet was anchored on *"have had"* where the pack says *"never had"* — a fixture that
1039
+ changed nothing, caught by the runner's own "the fixture changed nothing" branch.
1040
+
1041
+ ### An exclusion that held by accident now holds by declaration (B-026)
1042
+
1043
+ `pigeonhole`'s nine `--cat-*-ink` tokens sit outside the palette gate's semantic peer set, and
1044
+ **the exclusion is correct**. Re-measured with the gate's own OKLab metric: the closest pair is
1045
+ **4.92 ΔE** at full colour (`--cat-notification-ink` / `--cat-team-ink`) and **1.24 ΔE** under
1046
+ deuteranopia (`--cat-marketing-ink` / `--cat-notification-ink`), against a hard floor of 10 and
1047
+ a dichromacy floor of 8. No arrangement of nine hues clears those. The board's 4.42 was slightly
1048
+ off; its 1.24 was exact.
1049
+
1050
+ What was wrong is *how* the exclusion held: `STATUS_TOKENS` matches by **name**, so an author
1051
+ renaming a category token `--danger-ink` got a red gate for the right reason **by luck**, and one
1052
+ adding a tenth hue got nothing at all.
1053
+
1054
+ The pack now declares it where the categories are enumerated — with those numbers, and with the
1055
+ carrier named: the chip's **label word**, already required in Components, plus every ink clearing
1056
+ AA against its own tint. The hue is recognition, never identification, and a chip rendered as a
1057
+ bare swatch is a defect in this pack.
1058
+
1059
+ `validate_excluded_sets_are_declared()` holds the enumeration equal to the shipped tokens **in
1060
+ both directions** — a tenth hue with no row fails, a row with no token fails — and requires the
1061
+ carrier to be stated **in the section that enumerates the set**.
1062
+
1063
+ **That last scoping is the fix its own plant forced.** Searching the whole file let a chip rule
1064
+ three sections away satisfy a declaration about the hues: the plant broke two of the phrase's
1065
+ three occurrences and the third kept the check quiet. A declaration belongs where the reader of
1066
+ that set is standing.
1067
+
1068
+ B-017's widening, which this row asked to wait for, shipped earlier the same day. Floor 3835 →
1069
+ 3839.
1070
+
1071
+ ### A focus ring was guarded by a mechanism that cannot guard (B-027)
1072
+
1073
+ The board asked for 42 hand-restated channel literals to be migrated to relative colour, with
1074
+ the rule *"the literal must ship first as a fallback"*. Re-measured, the count is **71 across
1075
+ 14 packs**, not 42 — it grew with the library and nothing was watching it. And **56 of the 71
1076
+ are custom property definitions**, which is what settles the decision:
1077
+
1078
+ **For a custom property, a literal above a relative declaration is not a fallback.**
1079
+ `--x: rgb(from …)` **parses everywhere** — a custom property accepts almost any token sequence
1080
+ — so it wins the cascade unconditionally, and the invalidity surfaces only where the property
1081
+ is *substituted*, as invalid-at-computed-value-time. By then the literal declaration is gone
1082
+ and every `var(--x)` resolves to `unset`, not to the line above it.
1083
+
1084
+ **Twenty-three properties across `ledger` and `showroom` shipped exactly that pattern**, and
1085
+ `showroom`'s was **`--ring-focus`**. Its own comment named the stake — *"a dropped declaration
1086
+ is an invisible focus indicator, which is the one degradation this library may not ship"* — and
1087
+ then relied on the mechanism that cannot deliver it. A twenty-fourth, `ora`'s `--accent-hover`,
1088
+ had **no fallback at all**.
1089
+
1090
+ All twenty-four are inside `@supports (color: rgb(from red r g b))` now, which is where a
1091
+ literal fallback actually survives. `validate_relative_colour_is_guarded()` refuses any
1092
+ relative-colour custom property outside such a block, planted with `showroom`'s exact shipped
1093
+ pattern. `ora`'s literal is per-theme on purpose: `:root[data-theme="light"]` sets its own
1094
+ value, so a single literal carries one theme and needs to.
1095
+
1096
+ **The remaining 47 are not migrated, and that is the answer rather than a deferral.** Relative
1097
+ colour in a definition needs an `@supports` block per pack, which is machinery in the file a
1098
+ reader is told to copy verbatim. The cheaper fix is for each literal to state its source —
1099
+ `/* = --on-ink at 72% */`, which `ledger`'s eighteen now do — and for a check to recompute the
1100
+ declared relationship, so a token that moves fails in CI instead of drifting silently (B-116).
1101
+
1102
+ Floors 3839 → 3863 and 2220 → 2346; the palette count moved because the `@supports` blocks add
1103
+ a theme map the palette gate now reads.
1104
+
1105
+ ### The kit migration was finished, and a guard was complete by accident (B-029)
1106
+
1107
+ The board said *15 remain*. Fifteen kits carry no container query — and **eleven of those carry
1108
+ no width query at all**, so there is nothing in them to migrate. The actionable set was four
1109
+ kits, six blocks, and every one of them is already resolved:
1110
+
1111
+ - **`datasheet`** — two blocks, marked *"SELF for the guides, PAGE for the heading"* with the
1112
+ reason for each half.
1113
+ - **`field-notes`** — two blocks, marked PAGE and SELF.
1114
+ - **`maquette`** — one, SELF, with the reason that wrapping every model block to let it query
1115
+ itself is worse than the query.
1116
+ - **`pigeonhole`** — one, and it is a `:root` token switch, which the guard exempts **by
1117
+ design**: `:root` is inside nobody's container, so it cannot be replaced by one.
1118
+
1119
+ **Zero unmarked, unexempt viewport queries remain across 29 kits.**
1120
+
1121
+ What *was* worth fixing is the guard's reach. It read `*/src/styles.css` and nothing else.
1122
+ Measured today: zero width queries live outside `styles.css`, and zero CSS files fall outside
1123
+ that glob — so its coverage was complete **by accident**. A `@media (max-width: …)` inside a
1124
+ `.tsx` template literal, or a second stylesheet beside the first, would have been invisible.
1125
+
1126
+ It now reads every `.css`, `.tsx`, `.ts`, `.jsx` and `.js` a kit ships, refuses a corpus of
1127
+ fewer than two files rather than passing on an empty one, and is planted with a width query in
1128
+ a `.tsx` template literal. Floor 3863 → 3865.
1129
+
1130
+ ### Three of five typographic findings did not hold, and one counted comments (B-030)
1131
+
1132
+ The row listed five primitives the library specifies by hand. Re-measured today, **three of
1133
+ the five are wrong or have closed themselves**:
1134
+
1135
+ | The row said | Measured 2026-08-20 |
1136
+ |---|---|
1137
+ | 0 declare a measure in `ch` | **11** files use `ch`; 12 declare a px measure |
1138
+ | fluid spacing 0 of 16, type fluid in 5 | **6** and **15** |
1139
+ | `font-display` in 21 files, `size-adjust` in none — *"a guaranteed reflow"* | `font-display` in **three** places, all the **same comment line**; **zero** `@font-face` blocks |
1140
+
1141
+ The third is the one worth dwelling on. That count matched **string occurrences** — a token
1142
+ named `--font-display: aeonik`, and a comment in `manpage`'s token layer describing its
1143
+ reference. There is no `@font-face` anywhere in this library and no `font-display` declaration,
1144
+ so `size-adjust` and `ascent-override` have nowhere to attach: **font loading is the consumer's
1145
+ layer, and that is a decision rather than a gap.**
1146
+
1147
+ `text-box-trim` / `text-box-edge` and `@property` do stand, both at zero.
1148
+
1149
+ The skeleton's `## Type` section now carries all three answers with today's numbers and a
1150
+ support line each: `text-box-trim` as a **recommendation** rather than a rule because Firefox
1151
+ lacks it — and with the reason it matters, that a 60px display at `line-height: 1` is 60px of
1152
+ line box and not of cap height; `ch` as the rule a px measure stops obeying the moment the face
1153
+ changes; and font loading as explicitly not this layer, so a pack that wants metric-matched
1154
+ fallbacks says so in Gotchas instead of shipping an inert descriptor.
1155
+
1156
+ `validate_font_loading_stays_out()` holds that last decision — and **strips comments first**,
1157
+ because a comment is exactly what made the original count wrong. Planted both ways.
1158
+
1159
+ `@property` is filed rather than done (B-117): the token layers are files a reader is told to
1160
+ copy verbatim, and a block per token would roughly double them. The narrow version worth pricing
1161
+ is the tokens that are actually animated, and that list has to be measured from the kits'
1162
+ transitions rather than guessed.
1163
+
1164
+ Floor 3865 → 3981.
1165
+
1166
+ ### Two deferred breakpoints, and neither had the container answer the board assumed (B-032)
1167
+
1168
+ Both blocks carried `TODO-CONTAINER B-032` and both were held out of 1.23.0 on the right
1169
+ principle: **the breakpoint has to come from the component's own geometry**, and carrying 768
1170
+ or 640 into `@container` would be the same mistake in newer syntax. Doing that arithmetic is
1171
+ what closed the row — and it did not produce container queries.
1172
+
1173
+ **`datasheet`'s instrument grid needed no query at all.** It is already
1174
+ `repeat(auto-fit, minmax(180px, 1fr))`: two columns need 180 + 180 + 1px of border = **361px**
1175
+ of grid width, and below that `auto-fit` drops to one on its own. The
1176
+ `@media (max-width: 640px)` was not helping it — it was **overriding** it, forcing a single
1177
+ column on a 640px phone whose instrument may hold two comfortably. Resolved by deleting the
1178
+ block.
1179
+
1180
+ **`blueprint`'s two derive to numbers that give the game away.** The registration tick is
1181
+ `--tick-len: 8px` sitting `--tick-gap: 6px` off each corner, so its two top arms consume 16px
1182
+ of the element's width, and at the field's own `--grid-step` of 32px the marks stop reading as
1183
+ corners only below **~48px**. The column rule is `--rule-w: 1px` at each edge of a column whose
1184
+ inner padding is `--space-8` = 32px — and **this pack states no measure anywhere**, so the only
1185
+ derivable floor is the **64px** of padding itself.
1186
+
1187
+ 48px and 64px are far below anything a container reaches. That is the tell: the 768px
1188
+ breakpoint was never about fitting. A registration mark and an edge rule are **ornament**, and
1189
+ how much ornament a page carries is a page decision — so both are marked PAGE, with the
1190
+ arithmetic written at the block so the next reader does not redo it.
1191
+
1192
+ **TODO-CONTAINER is at zero and pinned there.** An escape with no ceiling becomes the habit it
1193
+ was meant to interrupt; the ratchet is planted with a marker added back. Floor 3981 → 3983.
1194
+
1195
+ ### Gate
1196
+
1197
+ - **Coverage is pinned, in both directions.** `check_ratio_coverage()` reads
1198
+ `validate_palette.py:ratios` from `test/floors.json`: `computed` may only rise,
1199
+ `unresolved` and `unguarded` may only fall. A check-count floor cannot see this —
1200
+ deleting one `on \u0060--bg\u0060` from a table header moved 27 claims out of the
1201
+ arithmetic while every other check stayed put. Watched failing at 348 → 342.
1202
+ - **Five plants, one per bucket**, so the *classification* is watched discriminating and
1203
+ not only the arithmetic: a table declaring a base its numbers contradict, the same
1204
+ claim with the base removed, prose naming no partner, a ratio placed at a gradient
1205
+ stop, and a named partner whose subject cannot be paired.
1206
+ - `PARTNER_PHRASE` now reads past a determiner — *"text on the `--accent` fill"* named a
1207
+ partner the gate could not see. The regex moved rather than the sentence.
1208
+ - `ARGUED` lists only terms that can reach it. `RATIO_SKIP` already drops a floor, a
1209
+ bound and a rejected candidate one step earlier, so repeating those would have been
1210
+ alternatives that never match — which reads as coverage and is not.
1211
+ - Floor `validate_palette.py` 1924 → 1951, all 27 from the newly-checked table claims,
1212
+ then 1951 → 2087 for the declared-set sweep.
1213
+ - **Four plants for the declared sets, and the third is the point**: a file-wide
1214
+ *"never by colour alone"* must NOT excuse a pair below the hard floor. Without it the
1215
+ check is a phrase-detector and one sentence silences it.
1216
+ - A figure with no qualifier is skipped rather than guessed, and the count of
1217
+ disclosure figures no check could bind prints on every run (7 today, B-055/B-056).
1218
+ - The `field-notes` kit's token block was re-copied from the pack's layer, which the
1219
+ copy-never-transcribe check caught on the first run after the comment changed.
1220
+ - **`validate_composited_fill_contrast` composites in sRGB gamma space**, because that is
1221
+ where the browser does it. A first draft composited this module's linear values and
1222
+ reported 1.88:1 where the page renders 3.16 — wrong in the direction that fails a
1223
+ correct pack.
1224
+ - **Composites are read per CELL.** A Components row is resting | hover | active |
1225
+ disabled, and reading the whole row made `cyclorama`'s hover fill the resting chip's
1226
+ text colour: 1.26:1 reported against a chip that renders at 12.38:1. There is a plant
1227
+ for it.
1228
+ - Two live subjects, one of each verdict — which is what makes it a check. Measured
1229
+ first: 44 lines in the library pair a token with a percentage and only two state a
1230
+ composite fill AND a type size. The other 42 are filed (B-058), not guessed at.
1231
+ - Floors `validate_palette.py` 2087 → 2092, `computed_at_least` 348 → 349.
1232
+ - **Five plants for the prescribed pairs**, including the hover-swap path B-039 is made of
1233
+ and the `as above` inheritance. The fixture had to be corrected before they meant
1234
+ anything: it used B-039's own values, where the resting pair already fails, so three of
1235
+ the five were passing for the wrong reason.
1236
+ - A claim added in this change had to be rewritten because `on` and its token landed on
1237
+ either side of a line break — the same refusal B-039's own fix met, and correct both
1238
+ times.
1239
+ - Floors 2092 → 2119 and `computed_at_least` 349 → 353.
1240
+ - **`scripts/sync-kit-tokens.py`, and `npm run sync-kits`.** A kit's `styles.css` is its
1241
+ pack's token layer verbatim plus its own component half, so every edit to a token
1242
+ layer — down to a comment — makes the kit red until the block is copied again. That
1243
+ copy was done by hand three times today and the third touched **15 kits at once**. The
1244
+ script takes the block boundary from the file's *committed* version instead of guessing
1245
+ it, and a kit that was hand-edited inside its token block reports `STUCK` and is left
1246
+ byte-identical. Watched refusing in a worktree, with the component half intact.
1247
+ - Floors 2119 → 2214, `computed_at_least` 353 → 448, `unguarded_at_most` 150 → 57.
1248
+
1249
+ ### Fixed
1250
+
1251
+ - **1.45.0's release notes were stranded under `[Unreleased]`.** The release commit wrote
1252
+ a summary above the accumulated section and left it in place, so the shipped version's
1253
+ detail was labelled unreleased and the next entry would have landed on top of it. The
1254
+ detail is folded into 1.45.0 below, where it belongs. `validate_release_register()`
1255
+ now refuses an `[Unreleased]` heading that is not the first section, and refuses a
1256
+ second one — the same class as the duplicate `## [1.35.0]` found on 2026-08-20, one
1257
+ heading over, and nothing had been looking at it.
1258
+
7
1259
  ## [1.45.0] - 2026-08-20
8
1260
 
9
1261
  **Four packs contradicted their own token layers, and the layer is what an implementer
@@ -36,7 +1288,7 @@ Registers that had stopped:
36
1288
  at sixteen packs and asserted all 71 had been hand-verified. Measured today: **513 claims,
37
1289
  336 guarded, 177 unguarded at 22 packs** — and the run prints that pair every time, with
38
1290
  the hand-verification dated for what it was. Two more restated counts went the same way.
39
- - **`package.json` said twenty-nine packs and named 27** — `awning` and `bulletin` missing
1291
+ - **`package.json` said thirty-one packs and named 27** — `awning` and `bulletin` missing
40
1292
  from the surface an npm reader chooses from. It is an enumeration site now.
41
1293
  - **Two `## [1.35.0]` sections** under one version, and four releases in the CHANGELOG with
42
1294
  no tag. The duplicate is resolved; the untagged four are **reported, not created** — a tag
@@ -63,7 +1315,7 @@ that failed.
63
1315
  Checks: `validate.py` 3398 to **3543**, `validate_palette.py` 1912 to **1924**, `sloplint.py`
64
1316
  635. Ten new plants, all caught; 49 planted defects across the three self-tests, 0 missed.
65
1317
 
66
- ## [Unreleased]
1318
+
67
1319
 
68
1320
  ### Four packs contradicted themselves, and six sweeps now say so
69
1321
 
@@ -164,7 +1416,7 @@ measured, and never gates content.
164
1416
  the comment says plainly which of them was ever verified.
165
1417
  - `test/validate.py` said "the six packs shipped before the widening" (seven) and
166
1418
  "seven of the ten widened packs" (twenty-two widened). The split is printed now.
167
- - `package.json` said "twenty-nine locked style packs" over a list of **27** —
1419
+ - `package.json` said "thirty-one locked style packs" over a list of **27** —
168
1420
  `awning` and `bulletin` were missing. `ENUMERATION_SITES` gained `package.json`,
169
1421
  which is the description npmjs.com renders: its count was already policed and
170
1422
  the names beside it were not.
@@ -288,7 +1540,7 @@ with their numbers.
288
1540
  Rendered and confirmed at 1440×1000, including the press: the primary's offset goes
289
1541
  3px → 1px as the control travels 2px, while the secondary beside it keeps its 3px.
290
1542
  - **Three stale counts in `SURFACE_COMPOSITION.md` that no gate could see.** It said the
291
- token names were not uniform "across the twenty-one" when twenty-nine packs ship, and
1543
+ token names were not uniform "across the twenty-one" when thirty-one packs ship, and
292
1544
  that the accent is `--accent` "in eighteen" when it is twenty-seven — `--brand` in
293
1545
  `field-notes` and `--cta` in `orchard` are still the only two exceptions, counted. The
294
1546
  third was a count of packs carrying an `@role non-text:` colour, which is now stated as