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