@radicool/throughline 0.19.0 → 0.21.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.
- package/README.md +2 -2
- package/adapters/codex/prompts/component-builder.md +29 -2
- package/adapters/codex/prompts/document-component.md +10 -1
- package/adapters/codex/prompts/icon-system-builder.md +4 -0
- package/adapters/codex/prompts/storybook-chromatic-builder.md +114 -7
- package/adapters/codex/prompts/token-builder.md +110 -3
- package/adapters/codex/prompts/token-crosswalk-builder.md +2 -0
- package/adapters/codex/prompts/token-sheet-builder.md +4 -0
- package/adapters/codex/prompts/token-sync-layer.md +140 -20
- package/adapters/cursor/.cursor/commands/document-component.md +10 -1
- package/adapters/cursor/.cursor/rules/component-builder.mdc +29 -2
- package/adapters/cursor/.cursor/rules/icon-system-builder.mdc +4 -0
- package/adapters/cursor/.cursor/rules/storybook-chromatic-builder.mdc +114 -7
- package/adapters/cursor/.cursor/rules/token-builder.mdc +110 -3
- package/adapters/cursor/.cursor/rules/token-crosswalk-builder.mdc +2 -0
- package/adapters/cursor/.cursor/rules/token-sheet-builder.mdc +4 -0
- package/adapters/cursor/.cursor/rules/token-sync-layer.mdc +140 -20
- package/adapters/generic/commands/document-component.md +10 -1
- package/adapters/generic/skills/component-builder/SKILL.md +29 -2
- package/adapters/generic/skills/icon-system-builder/SKILL.md +4 -0
- package/adapters/generic/skills/storybook-chromatic-builder/SKILL.md +114 -7
- package/adapters/generic/skills/token-builder/SKILL.md +110 -3
- package/adapters/generic/skills/token-crosswalk-builder/SKILL.md +2 -0
- package/adapters/generic/skills/token-sheet-builder/SKILL.md +4 -0
- package/adapters/generic/skills/token-sync-layer/SKILL.md +140 -20
- package/package.json +1 -1
- package/references/component-doc-schema.md +11 -1
- package/references/figma-component-standards.md +61 -2
- package/references/figma-scripting.md +12 -2
- package/references/manifest-schema.md +42 -2
- package/references/proof-bundle.md +312 -0
- package/references/sync-adapters.md +38 -53
- package/scripts/README.md +150 -14
- package/scripts/build-doc-card-builder.mjs +1 -1
- package/scripts/build-docs-digest.mjs +1 -0
- package/scripts/grep-color-usage.mjs +10 -30
- package/scripts/guard-token-removal.mjs +10 -26
- package/scripts/lib/component-states.mjs +56 -0
- package/scripts/lib/contrast.mjs +117 -0
- package/scripts/lib/dtcg.mjs +3 -2
- package/scripts/lib/proof.mjs +186 -0
- package/scripts/lib/source-scan.mjs +45 -0
- package/scripts/validate-adherence.mjs +1178 -0
- package/scripts/validate-token-output.mjs +468 -63
- package/scripts/verify-check.mjs +718 -0
|
@@ -105,6 +105,10 @@ component once its slice of the spec is complete enough to transcribe. **Keep th
|
|
|
105
105
|
human checkpoint between components** — subagents run continuously within a
|
|
106
106
|
component, but you pause for the human between them. If your host has no subagent
|
|
107
107
|
dispatch, build and verify each component inline, sequentially, as before.
|
|
108
|
+
Inline, run the active-file preflight in
|
|
109
|
+
`.throughline/references/figma-scripting.md` before the first write and
|
|
110
|
+
again before replacing anything — the executor's file check does not come with
|
|
111
|
+
you.
|
|
108
112
|
|
|
109
113
|
## Step 3 — Build each component, bound to tokens/styles
|
|
110
114
|
|
|
@@ -167,7 +171,9 @@ deterministic naming):
|
|
|
167
171
|
visual-validation loop (screenshot → fix any overlaps/misalignment →
|
|
168
172
|
re-screenshot) **and its "Post-build audit (REQUIRED before handoff)"
|
|
169
173
|
read-back checklist** (container type, auto layout, bound variables,
|
|
170
|
-
deterministic names) before the checkpoint.
|
|
174
|
+
deterministic names) before the checkpoint. When you build inline, with no
|
|
175
|
+
`figma-executor`, that includes item 9's `focus-indicator` check: a miss stops
|
|
176
|
+
the build, as it would for the executor.
|
|
171
177
|
|
|
172
178
|
Checkpoint after each component: show all variants, confirm before the next.
|
|
173
179
|
|
|
@@ -252,7 +258,10 @@ reference register, not this skill's guide voice.
|
|
|
252
258
|
2. **Enrich from the archetype knowledge base.** Match the component to the nearest
|
|
253
259
|
archetype in `.throughline/references/component-doc-archetypes.md` and
|
|
254
260
|
seed `dos`, `donts`, `accessibility`, `whenToUse`, `whenNotToUse`. Provenance
|
|
255
|
-
`best-practice` (or `w3c-apg` for the accessibility block).
|
|
261
|
+
`best-practice` (or `w3c-apg` for the accessibility block). Record the matched
|
|
262
|
+
archetype's id in the record's `archetype` field — one of `button`, `input`,
|
|
263
|
+
`choice`, `card`, `modal`, `badge`, `other` — so `verify:check` can read it
|
|
264
|
+
later rather than re-guessing from the name.
|
|
256
265
|
3. **Specialize to `project.uiFramework`.** Align variant-meaning wording and the
|
|
257
266
|
accessibility idiom to the target framework (the same field you read for variant
|
|
258
267
|
vocabulary). Provenance `framework`.
|
|
@@ -347,6 +356,24 @@ any component built with the toggle + manual-swap fallback is listed in
|
|
|
347
356
|
`components.instanceSwapUpgradePending`. Append `component-builder` to
|
|
348
357
|
`completedSkills`.
|
|
349
358
|
|
|
359
|
+
**Record the stage entry, one call per component that actually finished:**
|
|
360
|
+
`node .throughline/scripts/verify-check.mjs --record --stage
|
|
361
|
+
component-builder --subject <Name> --entry <tmp>.json`. Build `<tmp>.json` with
|
|
362
|
+
`at` (an ISO timestamp you supply), `changed` (what was built), the attested checks `structural-read-back`,
|
|
363
|
+
`state-baseline` and `figma-name` from what the `figma-executor` returned (or
|
|
364
|
+
what your inline read-back found), `focus-indicator` when that check ran and
|
|
365
|
+
passed (not when it was skipped),
|
|
366
|
+
`review` when a reviewer ran, `screenshot` when one was captured, and
|
|
367
|
+
`advancedBecause`. The entry shape is
|
|
368
|
+
`.throughline/references/proof-bundle.md` — don't restate it here. Write
|
|
369
|
+
an entry only for a component that actually finished; a `BLOCKED` component gets
|
|
370
|
+
none. Do this after adding the component to `components.built` above — by the
|
|
371
|
+
time this loop runs, the whole run's components are already there, which is what
|
|
372
|
+
lets this stage's first `--record` capture the full adoption batch; each record
|
|
373
|
+
then removes its own subject from the captured `exempt` list, so running the
|
|
374
|
+
loop over every finished component (not a one-shot write) is what un-grandfathers
|
|
375
|
+
the rest of the batch.
|
|
376
|
+
|
|
350
377
|
**Upgrade pass:** if `components.instanceSwapUpgradePending` is non-empty, re-resolve
|
|
351
378
|
publish state (detect-or-ask per `.throughline/references/figma-publishing.md`);
|
|
352
379
|
only when it is **confirmed published** (`figma.libraryPublished` true via a verified
|
|
@@ -180,6 +180,10 @@ needed here. Route per `.throughline/references/agent-routing.md`. If
|
|
|
180
180
|
your host has no subagent dispatch, script the SVG-to-component pass inline,
|
|
181
181
|
sequentially, with the user in the loop. Either way, follow
|
|
182
182
|
`.throughline/references/figma-scripting.md`.
|
|
183
|
+
Inline, run the active-file preflight in
|
|
184
|
+
`.throughline/references/figma-scripting.md` before the first write and
|
|
185
|
+
again before replacing anything — the executor's file check does not come with
|
|
186
|
+
you.
|
|
183
187
|
|
|
184
188
|
## Step 3 — Normalize: page, naming, sizing, variants
|
|
185
189
|
|
|
@@ -27,9 +27,18 @@ system). Wire it to consume `packages/tokens` output so stories render with the
|
|
|
27
27
|
real design tokens (import the generated CSS/theme). Checkpoint: confirm
|
|
28
28
|
Storybook runs and shows the token-themed canvas.
|
|
29
29
|
|
|
30
|
-
Install the documentation scripts alongside the token scripts: copy the
|
|
31
|
-
files and register the
|
|
32
|
-
install as a set** in `.throughline/scripts/README.md`.
|
|
30
|
+
Install the documentation scripts alongside the token scripts: copy the twelve
|
|
31
|
+
files and register the five npm scripts listed under **Documentation scripts —
|
|
32
|
+
install as a set** in `.throughline/scripts/README.md`. `adherence:check`
|
|
33
|
+
needs the repo's own values substituted in — the UI package's specifier for
|
|
34
|
+
`--package`, and paths for `--root`/`--system` that match this repo's layout;
|
|
35
|
+
registering it with the table's placeholders leaves a script that cannot run.
|
|
36
|
+
`verify:check` needs the same treatment for its `--root` and `--tokens`, and
|
|
37
|
+
registering it without `--tokens` is worse than leaving a placeholder: the script
|
|
38
|
+
runs, and skips both `orphan-token` and `color-contrast` on every run. Pass **one
|
|
39
|
+
`--tokens` flag per mode file** — `color-contrast` checks each file as its own
|
|
40
|
+
mode, so a multi-mode system registered with a single `--tokens` silently checks
|
|
41
|
+
only one of them. That table is
|
|
33
42
|
the single source of truth for what a consuming repo gets; do not restate the
|
|
34
43
|
list here.
|
|
35
44
|
|
|
@@ -244,13 +253,83 @@ step and/or a Turbo task). It compares every surface against its record and exit
|
|
|
244
253
|
non-zero on drift; Figma surfaces report `edit-unverified` (checked live in a Figma
|
|
245
254
|
session). Run `docs:check` once here and confirm it passes before handing off.
|
|
246
255
|
|
|
256
|
+
Ensure `verify:check` is wired alongside it, with the repo's real `--tokens`
|
|
257
|
+
paths — not the registered placeholder, and one flag per mode file — so
|
|
258
|
+
`orphan-token` and `color-contrast` actually run instead of reporting as skipped.
|
|
259
|
+
All four rules (`orphan-token`, `state-incomplete`, `name-drift`,
|
|
260
|
+
`color-contrast`) go live now; if the orphan rule misfires on this repo,
|
|
261
|
+
`--skip orphan-token` is the documented answer, and `--skip color-contrast` is
|
|
262
|
+
the same answer on the same terms for a system whose colour roles are named
|
|
263
|
+
differently. Both print on the report's
|
|
264
|
+
`skipped:` line. Dropping `--tokens` is not an equivalent fix — that switches the
|
|
265
|
+
rules off silently instead of visibly. **Run `verify:check` once here, before
|
|
266
|
+
Step 6 promotes anything to `stable`, and confirm it passes before handing
|
|
267
|
+
off — running it here rather than after Step 6 is deliberate, not an
|
|
268
|
+
oversight**: this run's components are still `draft`, so
|
|
269
|
+
`storybook-chromatic-builder` owes them no entry yet and the gate passes; Step 6
|
|
270
|
+
is what promotes them to `stable`, and Step 7 is what records their entries.
|
|
271
|
+
Moving this call after Step 6, or moving Step 7's recording ahead of it, would
|
|
272
|
+
leave the components `stable`, owed an entry, and without one.
|
|
273
|
+
|
|
274
|
+
**A contrast failure the user already accepted.** `token-builder` stops on a pair
|
|
275
|
+
below 4.5:1, and a user may explicitly accept it anyway. That acceptance lives in
|
|
276
|
+
`design-system/proof/token-builder.json` under the repo root, on the `system`
|
|
277
|
+
entry's `contrast-baseline` check, as `accepted: [{ fg, bg, mode }]`. Read that
|
|
278
|
+
file — it may not exist, in which case nothing is accepted and any failure stops
|
|
279
|
+
this step as usual. Then compare **this run's live failures** against it: take
|
|
280
|
+
the `[color-contrast]` lines off the report you just ran, and match each one's
|
|
281
|
+
`fg` and `bg` — the canonical `CONTRAST_PAIRS` paths, identical on both sides and
|
|
282
|
+
needing no translation — against each `accepted` entry's `fg`/`bg`, on the same
|
|
283
|
+
normalized fold (lowercase, alphanumerics only). Then:
|
|
284
|
+
|
|
285
|
+
- **every live failure is an accepted role pair** → register the `verify:check`
|
|
286
|
+
script in `package.json` with `--skip color-contrast`, and say in one line which
|
|
287
|
+
pair was accepted, that contrast is therefore **not checked in CI at all** while
|
|
288
|
+
that flag is there, and that deleting the flag is what re-enables it;
|
|
289
|
+
- **no live failures** → register the script **without** the flag. The pair was
|
|
290
|
+
fixed, or never applied to this repo, and switching the rule off would hide a
|
|
291
|
+
future one;
|
|
292
|
+
- **any live failure whose roles are not in `accepted`** → stop, exactly as this
|
|
293
|
+
step already does.
|
|
294
|
+
|
|
295
|
+
**The live run is the condition, not the presence of the field.** `accepted`
|
|
296
|
+
persists until `token-builder` is re-run, which nothing asks for — so keying off
|
|
297
|
+
the field alone would switch CI's contrast rule off for a pair that now passes,
|
|
298
|
+
while telling the user the flag is what re-enables it.
|
|
299
|
+
|
|
300
|
+
**Matching on the roles, and ignoring the mode, is deliberate here.** `--skip` is
|
|
301
|
+
rule-wide: there is no way to skip contrast in Dark and keep it in Light, so a
|
|
302
|
+
per-mode test would decide nothing a role test does not. This skill also *cannot*
|
|
303
|
+
translate a mode name — the recorded `mode` is the Figma name, and this skill
|
|
304
|
+
neither read the Figma modes nor wrote the DTCG files. Only `token-sync-layer`,
|
|
305
|
+
which did both, matches on the mode. The cost, stated rather than hidden:
|
|
306
|
+
whenever an accepted pair's roles also fail in a mode the user did not accept,
|
|
307
|
+
now or later, the roles still match and CI stays skipped. The sync is the
|
|
308
|
+
backstop — it subtracts per mode, so it stops on that unaccepted mode, and every
|
|
309
|
+
token change goes through it.
|
|
310
|
+
|
|
311
|
+
**That backstop only holds on a tree the sync finished on.** A sync that stopped
|
|
312
|
+
leaves both mode files written and the failure standing, and this skill running
|
|
313
|
+
over that tree would see two failures, match both on roles, and register the flag
|
|
314
|
+
over one nobody accepted. Do not wire the script on a tree whose last sync
|
|
315
|
+
stopped: the pipeline stopped there too, and the fix belongs in Figma before the
|
|
316
|
+
storying stage runs at all.
|
|
317
|
+
|
|
318
|
+
This skill is where the registered script is created, so it is the only place
|
|
319
|
+
that can apply this consequence — `token-builder` runs at folder stage, before
|
|
320
|
+
any repo, and cannot edit a `package.json` that does not exist.
|
|
321
|
+
|
|
247
322
|
## Step 6 — Finalize component status (Figma write-back)
|
|
248
323
|
|
|
249
324
|
A component built and storied here is now **done** — but its Figma doc card was
|
|
250
325
|
stamped `draft` by component-builder and won't change on its own. Once the code
|
|
251
326
|
component renders and its stories build (and the user has approved the result),
|
|
252
327
|
**promote each finalized component to `stable`** so the design system tells the
|
|
253
|
-
truth in both places.
|
|
328
|
+
truth in both places. **This promotion is what makes a component owe a
|
|
329
|
+
`storybook-chromatic-builder` proof entry** — the stage's lifecycle gate reads
|
|
330
|
+
`components.meta[name].status`, and a component still at `draft` owes it
|
|
331
|
+
nothing. Step 7's recording only follows this step for that reason: reorder them
|
|
332
|
+
and a component would be `stable`, owed an entry, and recorded before it exists.
|
|
254
333
|
|
|
255
334
|
**Confirm the write-back once, up front.** Before touching Figma, state the batched
|
|
256
335
|
change in one line and get a yes — *"I'll update the N doc cards in Figma: flip the
|
|
@@ -263,15 +342,25 @@ For every component you finalized in this run, follow the **"Promoting a
|
|
|
263
342
|
component's status (write-back on finalize)"** routine in
|
|
264
343
|
`.throughline/references/figma-component-standards.md` (which also covers
|
|
265
344
|
the `figma_execute` scripting gotchas — `getNodeByIdAsync` and an explicit
|
|
266
|
-
`timeout` for the multi-card write)
|
|
345
|
+
`timeout` for the multi-card write). This write runs inline, so run the
|
|
346
|
+
active-file preflight in `.throughline/references/figma-scripting.md`
|
|
347
|
+
before it:
|
|
267
348
|
|
|
268
349
|
- Set `components.meta[name].status` = `"stable"` and refresh
|
|
269
|
-
`components.meta[name].updatedAt` to today
|
|
350
|
+
`components.meta[name].updatedAt` to today — for every component this pass
|
|
351
|
+
promotes, including ones this run didn't build. **No verification gate reads
|
|
352
|
+
`updatedAt`.** A promotion run moves the timestamp of components other stages
|
|
353
|
+
own, so `proof-missing` scopes itself with the stage's captured `exempt` list
|
|
354
|
+
instead of a date comparison.
|
|
270
355
|
- When promoting status (e.g. draft → stable), also set `status` + `updatedAt` in
|
|
271
356
|
the component's `.doc.json`, recompute its fingerprint, re-run `docs:digest`, and
|
|
272
357
|
re-render the affected surfaces so `docs:check` stays green.
|
|
273
358
|
- If Figma is connected (per `figma.mechanism`), open the component's doc card and
|
|
274
|
-
|
|
359
|
+
locate its status chip under either header shape, as the routine describes: a
|
|
360
|
+
`Status` chip with a `Status Label` text node on to-spec cards, a `Status Pill`
|
|
361
|
+
with one text node on legacy cards. If neither is there, don't guess — tell the
|
|
362
|
+
user that card still shows the old status. Otherwise set the label text to
|
|
363
|
+
`stable`, re-bind the chip fill to
|
|
275
364
|
the **success** semantic color variable (mode-aware, not a hardcoded hex), then
|
|
276
365
|
re-run the canonical doc-card builder against the same card to refresh the
|
|
277
366
|
header date from the `record.updatedAt` already set above (it locates the date
|
|
@@ -292,6 +381,24 @@ Set `storybook.initialized` = `true`, `storybook.chromatic` accordingly,
|
|
|
292
381
|
Step 6.) Note the ongoing loop: new components flow through the
|
|
293
382
|
component-pipeline orchestrator; token changes flow through `/sync-figma-tokens`.
|
|
294
383
|
|
|
384
|
+
**Record the stage entry, one call per component finalized this run:**
|
|
385
|
+
`node .throughline/scripts/verify-check.mjs --record --stage
|
|
386
|
+
storybook-chromatic-builder --subject <Name> --entry <tmp>.json`. Build
|
|
387
|
+
`<tmp>.json` with `at` (an ISO timestamp you supply), the attested checks `build` (the story build outcome) and
|
|
388
|
+
`review`, `advancedBecause`, and **the two component-scoped derived results,
|
|
389
|
+
`state-incomplete` and `name-drift`, read off the `verify:check` report Step
|
|
390
|
+
5.5 already ran** — recorded with `method: "derived"`. This is one of two stages
|
|
391
|
+
that record derived results, and it qualifies for the same reason the other does:
|
|
392
|
+
it has already run the gate before recording, so it holds the result rather than
|
|
393
|
+
asserting it (`.throughline/references/proof-bundle.md` names both); the
|
|
394
|
+
entry shape is
|
|
395
|
+
`.throughline/references/proof-bundle.md` — don't restate it here. As
|
|
396
|
+
in `component-builder`, this run's components are already in
|
|
397
|
+
`components.built` by the time this loop runs, so this stage's first `--record`
|
|
398
|
+
captures the whole batch into `exempt`; looping over every finalized component
|
|
399
|
+
here, rather than writing once, is what removes each one from that list instead
|
|
400
|
+
of leaving the rest of the batch grandfathered.
|
|
401
|
+
|
|
295
402
|
## Brownfield: baseline before retrofit + the verification triad
|
|
296
403
|
|
|
297
404
|
On a **retrofit** (`tokens.intakeMode: "retrofit"`), the order of operations and the
|
|
@@ -66,6 +66,10 @@ human between them. Route per
|
|
|
66
66
|
`.throughline/references/agent-routing.md`; never parallelize Figma
|
|
67
67
|
work. If your host has no subagent dispatch, build and verify each tier inline,
|
|
68
68
|
sequentially, as the steps below describe.
|
|
69
|
+
Inline, run the active-file preflight in
|
|
70
|
+
`.throughline/references/figma-scripting.md` before the first write and
|
|
71
|
+
again before replacing anything — the executor's file check does not come with
|
|
72
|
+
you.
|
|
69
73
|
|
|
70
74
|
## Step 1 — Brainstorm the structure (before building anything)
|
|
71
75
|
|
|
@@ -301,9 +305,84 @@ and later Device (`Spacing`/`Typography`). That's the cost of independent axes.
|
|
|
301
305
|
Verify the aliases resolve — use `figma_get_variables` (filtered to the new
|
|
302
306
|
semantic collection, `resolveAliases: true`) to confirm semantic tokens point at
|
|
303
307
|
primitives, not literals; if you read via a `figma_execute` script instead, use
|
|
304
|
-
the async APIs (see the dynamic-page note in Prerequisites).
|
|
305
|
-
|
|
306
|
-
|
|
308
|
+
the async APIs (see the dynamic-page note in Prerequisites).
|
|
309
|
+
|
|
310
|
+
**Then assert contrast, before the checkpoint.** Using that same
|
|
311
|
+
`figma_get_variables` read (filtered to `Color/Semantic`, `resolveAliases: true`),
|
|
312
|
+
convert Figma's 0–1 RGBA channels to hex and **run the shipped module rather than
|
|
313
|
+
doing the arithmetic in the transcript**: shape the per-mode values as
|
|
314
|
+
`[{ mode, values: { "<dotted token path>": "#rrggbb" } }]` and pass them to a
|
|
315
|
+
single `node` invocation that imports `CONTRAST_PAIRS`, `contrastRatio`,
|
|
316
|
+
`composite` and `AA_NORMAL_TEXT` from
|
|
317
|
+
`.throughline/scripts/lib/contrast.mjs` and prints one line per pair per
|
|
318
|
+
mode:
|
|
319
|
+
|
|
320
|
+
node --input-type=module -e "<import the module, read the JSON from
|
|
321
|
+
process.argv[1], loop CONTRAST_PAIRS × modes, print mode/fg/bg/ratio>" '<the JSON>'
|
|
322
|
+
|
|
323
|
+
The module ships precisely so the maths has one home: read the table and call the
|
|
324
|
+
function, and restate neither. The pairs are the semantic roles the system
|
|
325
|
+
already promises (`text/onEmphasis` on `bg/emphasis`, `text/primary` on
|
|
326
|
+
`bg/default`, each status text on its own status background); a role this system
|
|
327
|
+
does not have is not asserted.
|
|
328
|
+
|
|
329
|
+
**The keys are dotted token paths, not Figma variable names.** Figma calls the
|
|
330
|
+
role `text/onEmphasis` in the `Color/Semantic` collection; `CONTRAST_PAIRS`
|
|
331
|
+
spells it `color.text.onEmphasis`, and no normalized fold bridges the two —
|
|
332
|
+
`colorsemantictextonemphasis` is not `colortextonemphasis`. Apply the **Name
|
|
333
|
+
mapping** rule the sync already defines
|
|
334
|
+
(`.throughline/skills/token-sync-layer/SKILL.md`): the category drives
|
|
335
|
+
the top-level group and the tier is dropped, so `Color/Semantic` +
|
|
336
|
+
`text/onEmphasis` → `color.text.onEmphasis`. Both halves of this check have to
|
|
337
|
+
agree on the spelling, or the Figma-time half asserts pairs the CLI half never
|
|
338
|
+
sees, and the two never disagree because they never meet.
|
|
339
|
+
|
|
340
|
+
**A failing pair stops the tier.** Do not proceed to the checkpoint or to Step 4
|
|
341
|
+
with a pair below 4.5:1. Report it in guide voice
|
|
342
|
+
(`.throughline/references/guide-voice.md`): name the two roles, the
|
|
343
|
+
mode, the measured ratio and the required one, then give **one** recommended fix
|
|
344
|
+
— re-point that mode's alias at a primitive with more separation, naming a
|
|
345
|
+
specific step in the ramp that clears — rather than a menu of options. This is
|
|
346
|
+
the "modes can't be built with poor colour contrast" promise, and it is the
|
|
347
|
+
cheapest place in the whole system to catch it: the mode is being defined right
|
|
348
|
+
now, and nothing is built on it yet.
|
|
349
|
+
|
|
350
|
+
**If the user explicitly accepts a failing pair**, continue, and say plainly in
|
|
351
|
+
the same breath what it costs downstream — describing the mechanism that actually
|
|
352
|
+
exists, not a registration that cannot reach the sync:
|
|
353
|
+
|
|
354
|
+
1. The acceptance is recorded in this stage's entry (`contrast-baseline` at
|
|
355
|
+
`result: "fail"`, `advancedBecause` naming it, and the pair itself listed in
|
|
356
|
+
`accepted`), and `token-sync-layer` subtracts exactly that pair from its own
|
|
357
|
+
gate run — so **the next token sync is not blocked** by it, while any *other*
|
|
358
|
+
failing pair, including one introduced later, still stops the sync.
|
|
359
|
+
2. The repo's own registered `verify:check` — the one
|
|
360
|
+
`storybook-chromatic-builder` wires into CI later — derives the same ratio off
|
|
361
|
+
the DTCG source and would fail there. That skill runs the gate itself when it
|
|
362
|
+
wires the script, and if every failure it sees is a pair recorded here as
|
|
363
|
+
accepted, it registers the script with `--skip color-contrast` and says so at
|
|
364
|
+
the time; if the pair has since been fixed, it registers the script without the
|
|
365
|
+
flag. Nothing is owed to a script that does not exist yet, and nothing is
|
|
366
|
+
switched off on the strength of a record alone.
|
|
367
|
+
|
|
368
|
+
Say both, in that order, and then say how each one ends, because they do not end
|
|
369
|
+
the same way. Fixing the pair in Figma **retires the sync's side by itself**: the
|
|
370
|
+
failure stops appearing, the recorded triple matches nothing, and the sync goes
|
|
371
|
+
back to normal on its own — with one expected line on that first sync, where the
|
|
372
|
+
previously recorded `fail` disagrees with a rerun that now passes. That line does
|
|
373
|
+
not stop anything, and the same run re-records the pass. The registered
|
|
374
|
+
`--skip color-contrast` does **not** heal: it is a line in the repo's
|
|
375
|
+
`package.json` and stays until someone removes it, which is the one thing this
|
|
376
|
+
override leaves behind. Say that plainly rather than letting them find it — a
|
|
377
|
+
user who fixes the pair months later and still sees contrast skipped in CI has
|
|
378
|
+
been told the gate is live when it is not. Say the scope too: `--skip` is
|
|
379
|
+
rule-wide, so while that flag is registered CI checks **no** pair, including one
|
|
380
|
+
introduced after the acceptance. The sync is what catches that one, since every
|
|
381
|
+
token change goes through it — but the user should know CI is not the thing
|
|
382
|
+
standing guard meanwhile.
|
|
383
|
+
|
|
384
|
+
Then checkpoint: show the semantic layer and demonstrate the cascade if useful
|
|
385
|
+
("change `gray/50` and `bg/default` follows").
|
|
307
386
|
|
|
308
387
|
Update the manifest: `tokens.semanticBuilt` = `true`, add every semantic
|
|
309
388
|
collection to `tokens.collections`.
|
|
@@ -343,6 +422,34 @@ Update the manifest: set `tokens.stylesBuilt` = `true` and record which style
|
|
|
343
422
|
groups were created in `tokens.styleGroups`. Append `token-builder` to
|
|
344
423
|
`completedSkills`.
|
|
345
424
|
|
|
425
|
+
**Record the stage entry:** `node .throughline/scripts/verify-check.mjs
|
|
426
|
+
--record --stage token-builder --entry <tmp>.json` (subject defaults to
|
|
427
|
+
`"system"`; this stage is not per-component). Build `<tmp>.json` with `at` (an ISO
|
|
428
|
+
timestamp you supply), `changed` summarizing the collections and modes created, the attested check
|
|
429
|
+
`contrast-baseline` carrying `result` and a one-line `evidence` naming the
|
|
430
|
+
tightest ratio per mode (`"Light 5.2:1, Dark 4.9:1 — tightest is
|
|
431
|
+
text/onEmphasis on bg/emphasis"`), and `advancedBecause`.
|
|
432
|
+
|
|
433
|
+
When the user accepted a failing pair, the check's `result` is `"fail"`,
|
|
434
|
+
`advancedBecause` names the acceptance, and the check carries
|
|
435
|
+
`accepted: [{ fg, bg, mode }]` — one entry per accepted pair, `fg` and `bg` as the
|
|
436
|
+
dotted token paths `CONTRAST_PAIRS` uses, and `mode` as **the Figma mode name this
|
|
437
|
+
skill just read** (`Light`, `Dark` — the names in the collection it built), not a
|
|
438
|
+
file name. At this point in the pipeline no DTCG file exists to name a mode
|
|
439
|
+
after: the sync writes those later and chooses their names, so a filename here
|
|
440
|
+
would be a guess that never matches. The sync translates this name into its own
|
|
441
|
+
file label before matching, and recording what was actually observed is what
|
|
442
|
+
makes that translation possible. **Write the pair structurally as well as in
|
|
443
|
+
prose** — `advancedBecause` is for whoever reads the bundle later, and `accepted`
|
|
444
|
+
is what `token-sync-layer` matches against, so an acceptance recorded only in
|
|
445
|
+
prose would stop the next sync exactly as if it had never been given. Accept only
|
|
446
|
+
the pairs the user actually accepted; that list is the scope of the override.
|
|
447
|
+
|
|
448
|
+
The entry shape is `.throughline/references/proof-bundle.md` — do not
|
|
449
|
+
restate it here. Write the entry only when the tier actually completed: a run that
|
|
450
|
+
stopped on a failing pair and was not resumed records nothing, the same rule
|
|
451
|
+
`component-builder` follows for a `BLOCKED` component.
|
|
452
|
+
|
|
346
453
|
## Step 5 — Hand off
|
|
347
454
|
|
|
348
455
|
Tell the user what's unlocked and offer natural next steps without forcing them:
|
|
@@ -67,6 +67,8 @@ Copy these from `.throughline/scripts/` into the user's repo **verbatim**
|
|
|
67
67
|
- `lib/crosswalk.mjs` → `packages/tokens/scripts/lib/crosswalk.mjs`
|
|
68
68
|
- `lib/dtcg.mjs` → `packages/tokens/scripts/lib/dtcg.mjs` (required by
|
|
69
69
|
`validate-crosswalk.mjs` — copying the validator without it breaks the gate at import)
|
|
70
|
+
- `lib/source-scan.mjs` → `packages/tokens/scripts/lib/source-scan.mjs` (required by
|
|
71
|
+
`guard-token-removal.mjs` — copying the guard without it breaks it at import)
|
|
70
72
|
- `validate-crosswalk.mjs` → `packages/tokens/scripts/validate-crosswalk.mjs`
|
|
71
73
|
- `build-reverse-index.mjs` → `packages/tokens/scripts/build-reverse-index.mjs`
|
|
72
74
|
- `guard-token-removal.mjs` → `packages/tokens/scripts/guard-token-removal.mjs`
|
|
@@ -46,6 +46,10 @@ variables via `figma_get_variables`, not just a screenshot), gated by a
|
|
|
46
46
|
`.throughline/references/agent-routing.md`; never parallelize Figma
|
|
47
47
|
work. If your host has no subagent dispatch, build and verify the page inline,
|
|
48
48
|
sequentially, as the steps below describe.
|
|
49
|
+
Inline, run the active-file preflight in
|
|
50
|
+
`.throughline/references/figma-scripting.md` before the first write and
|
|
51
|
+
again before replacing anything — the executor's file check does not come with
|
|
52
|
+
you.
|
|
49
53
|
|
|
50
54
|
## Step 1 — Brainstorm the layout (lightly)
|
|
51
55
|
|
|
@@ -99,9 +99,17 @@ Three rules for the multi-collection structure:
|
|
|
99
99
|
and token-sync-layer are a **matched pair** on this — neither is correct alone.
|
|
100
100
|
|
|
101
101
|
This DTCG JSON is the canonical intermediate. Write it to a known location in
|
|
102
|
-
`packages/tokens
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
`packages/tokens/`, **one file per mode**, because every gate downstream reads a
|
|
103
|
+
file as a mode. A single-mode system writes `packages/tokens/dtcg/tokens.json`; a
|
|
104
|
+
light/dark system writes `packages/tokens/dtcg/primitives.json`,
|
|
105
|
+
`packages/tokens/dtcg/semantic.light.json` and
|
|
106
|
+
`packages/tokens/dtcg/semantic.dark.json` — the ramps once, then each mode's
|
|
107
|
+
semantic aliases in its own file. Nesting the modes as groups inside a single
|
|
108
|
+
file (`color.light.*` / `color.dark.*`) is the layout `color-contrast` abstains
|
|
109
|
+
on, and writing a multi-mode system to one `tokens.json` is what leaves the
|
|
110
|
+
registered `verify:check` checking a single mode and reporting that it checked
|
|
111
|
+
one. Do not trust the MCP's built-in CSS/Tailwind exporters for final output —
|
|
112
|
+
route through Style Dictionary so the adapter system governs the result.
|
|
105
113
|
|
|
106
114
|
### Brownfield transforms (learned the hard way)
|
|
107
115
|
|
|
@@ -182,12 +190,12 @@ list. See `.throughline/references/native-adapter-config.md`.
|
|
|
182
190
|
**Execution model — subagent dispatch with model routing.** Generating each
|
|
183
191
|
platform's output is independent and verifiable. If your host supports subagent
|
|
184
192
|
dispatch, dispatch **one `code-executor` per adapter** — each produces its
|
|
185
|
-
platform's files and verifies them (for web
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
193
|
+
platform's files and verifies them (for web and native alike:
|
|
194
|
+
`tokens:validate-output` passes — a clean build is not verification, it is the
|
|
195
|
+
condition under which every known failure mode, web or native, ships silently)
|
|
196
|
+
— then a **`reviewer`** to check each before combining. Choose each subagent's
|
|
197
|
+
model from its role tier per
|
|
198
|
+
`.throughline/references/agent-routing.md` (`code-executor` → fast,
|
|
191
199
|
`reviewer` → balanced), and only dispatch once each adapter's spec is complete
|
|
192
200
|
enough to transcribe. If your host has no subagent dispatch, generate and verify
|
|
193
201
|
each adapter inline instead. For a single platform, run inline either way. This
|
|
@@ -201,23 +209,34 @@ as **build artifacts** — regenerated every sync, never hand-edited. Wire
|
|
|
201
209
|
`packages/tokens/package.json` to export them so the UI package, Storybook, and
|
|
202
210
|
any future app consume them.
|
|
203
211
|
|
|
204
|
-
**Install the
|
|
205
|
-
`.throughline/scripts
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
`packages/tokens/scripts/lib
|
|
211
|
-
`
|
|
212
|
-
|
|
212
|
+
**Install the token toolkit — all four files, as a set, for web and native
|
|
213
|
+
targets alike.** Copy these from `.throughline/scripts/` into the
|
|
214
|
+
user's repo verbatim:
|
|
215
|
+
|
|
216
|
+
- `validate-token-output.mjs` → `packages/tokens/scripts/validate-token-output.mjs`
|
|
217
|
+
- `lib/dtcg.mjs` → `packages/tokens/scripts/lib/dtcg.mjs`
|
|
218
|
+
- `lib/native-literal.mjs` → `packages/tokens/scripts/lib/native-literal.mjs`
|
|
219
|
+
- `lib/sd-native.mjs` → `packages/tokens/scripts/lib/sd-native.mjs`
|
|
220
|
+
|
|
221
|
+
`sd-native.mjs` and the validator both import `dtcg.mjs` and
|
|
222
|
+
`native-literal.mjs`, so copying any of them without the others breaks at
|
|
223
|
+
import. Then register the gate so it stays live on every future sync:
|
|
213
224
|
|
|
214
225
|
```json
|
|
215
226
|
"tokens:validate-output": "node scripts/validate-token-output.mjs --min-match 1"
|
|
216
227
|
```
|
|
217
228
|
|
|
218
229
|
Invoke it once per native output file, passing the same `--source` list that
|
|
219
|
-
file's build used.
|
|
220
|
-
|
|
230
|
+
file's build used. For web output (`shadcn`, `tailwind`, `vanilla-css`), invoke
|
|
231
|
+
it once per mode block instead: name the block with `--block` and pass the
|
|
232
|
+
sources that block was built from. Pass the generated **CSS** file, the one that
|
|
233
|
+
declares the custom properties. For `tailwind` that is the CSS the theme config
|
|
234
|
+
points at, never the config itself: the gate reads custom properties, and a JS
|
|
235
|
+
config has none, so it would match nothing and fail. A MUI theme, or a Tailwind
|
|
236
|
+
build whose only output is a JS config, isn't checked yet (#127). The
|
|
237
|
+
full contract is in `.throughline/scripts/README.md`. `--min-match 1`
|
|
238
|
+
is what makes it a gate: the flag defaults to `0.5`, so without it a 60% match
|
|
239
|
+
rate exits `0`.
|
|
221
240
|
|
|
222
241
|
## Step 4.5 — Icon code sync (install check + custom SVGR)
|
|
223
242
|
|
|
@@ -252,6 +271,69 @@ color.surface.default?`). Surface these prominently. Also surface plain
|
|
|
252
271
|
deletions (consumers will break — that's intentional, but the human should see
|
|
253
272
|
it).
|
|
254
273
|
|
|
274
|
+
### Check contrast before it ships
|
|
275
|
+
|
|
276
|
+
Run the gate once against the DTCG sources this sync just wrote — **always**, with
|
|
277
|
+
no accepted-pair exception. The paragraphs below filter the result, never the run:
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
node .throughline/scripts/verify-check.mjs --root <repo root> \
|
|
281
|
+
--tokens <each DTCG source, one flag per mode file> \
|
|
282
|
+
--skip orphan-token --skip state-incomplete --skip name-drift
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**One `--tokens` flag per mode file** — that is what makes each pair get checked
|
|
286
|
+
in every mode. Passing a single file of a multi-mode system checks one mode, and
|
|
287
|
+
says so on the report's `contrast:` line.
|
|
288
|
+
|
|
289
|
+
**Why those three rules are skipped, so nobody removes the flags.** A sync that
|
|
290
|
+
adds a token is adding one nothing in the repo names yet, which is
|
|
291
|
+
`orphan-token`'s definition of an orphan — running it here would fail nearly
|
|
292
|
+
every sync that does its job. `state-incomplete` and `name-drift` are about doc
|
|
293
|
+
records and code surfaces this skill does not touch. All three stay live in
|
|
294
|
+
`storybook-chromatic-builder`'s run of the same gate.
|
|
295
|
+
|
|
296
|
+
**Honour an accepted pair, and only an accepted one.** After the gate has run,
|
|
297
|
+
read `design-system/proof/token-builder.json` under the same `--root` (it may not
|
|
298
|
+
exist, in which case there is nothing to honour). Collect the `accepted` array
|
|
299
|
+
from its `system` entry's `contrast-baseline` check, then drop from this run's
|
|
300
|
+
`color-contrast` failures every one whose `(fg, bg, mode)` matches an entry in it,
|
|
301
|
+
comparing `fg` and `bg` on the same normalized fold the rule matches roles with
|
|
302
|
+
(lowercase, alphanumerics only). **Translate `mode` before comparing it:** the
|
|
303
|
+
recorded value is the Figma mode name `token-builder` saw (`Dark`), and the gate
|
|
304
|
+
labels a mode by its DTCG file's basename (`semantic.dark`). This skill is the one
|
|
305
|
+
that knows both — it read the Figma modes and it wrote the files — so map each
|
|
306
|
+
Figma mode to the file it generated for that mode, and compare against that label.
|
|
307
|
+
Untranslated, the triple can never match and the override path silently does not
|
|
308
|
+
exist. Match the triple exactly, and subtract nothing else.
|
|
309
|
+
|
|
310
|
+
**What this reads.** The gate has no machine-readable output, so this operates on
|
|
311
|
+
`formatReport`'s failure lines — `<fg> on <bg> is <ratio>:1 in <mode> …` — which
|
|
312
|
+
carry all three fields in fixed positions. That is machine-formatted output with a
|
|
313
|
+
stated shape, not prose. Do **not** treat `result: "fail"` on its own as the
|
|
314
|
+
condition, and do **not** pattern-match `advancedBecause`: the first switches off a
|
|
315
|
+
whole rule on the strength of one pair, and the second reads prose written for a
|
|
316
|
+
human as though it were a test.
|
|
317
|
+
|
|
318
|
+
**Then judge what is left.** Nothing remaining → proceed, and say in one line which
|
|
319
|
+
pair was accepted and is not being re-litigated, both in the sync's message and in
|
|
320
|
+
the PR body. That line is where the override stays visible, and it is the only
|
|
321
|
+
place, since a subtracted failure is by definition not on the report. Anything
|
|
322
|
+
remaining → stop, exactly as if there had been no acceptance: a pair the user never
|
|
323
|
+
saw, in any mode, is the case this rule exists for, and an acceptance given for one
|
|
324
|
+
pair must not carry another one through. An `accepted` list that matches nothing —
|
|
325
|
+
because the pair was fixed, or was never recorded properly — subtracts nothing and
|
|
326
|
+
the sync behaves as though there were no acceptance at all, which is both the
|
|
327
|
+
fail-safe direction and how an acceptance retires itself once the pair clears.
|
|
328
|
+
|
|
329
|
+
**A `color-contrast` failure stops the sync.** Do not open the PR. Report the
|
|
330
|
+
failing pair and mode in guide voice
|
|
331
|
+
(`.throughline/references/guide-voice.md`), and say the fix is in Figma —
|
|
332
|
+
re-point that mode's alias — not in the generated output, which is a build
|
|
333
|
+
artifact. **Any other failure in the report does not stop the sync**: surface it to
|
|
334
|
+
the user and summarize it in the PR body, but the token sync owns the token tier,
|
|
335
|
+
and a component's missing proof entry is not its to block on.
|
|
336
|
+
|
|
255
337
|
## Step 6 — Land it as a reviewable PR (never silent)
|
|
256
338
|
|
|
257
339
|
Never overwrite token files silently. The output of a sync is a **pull request**
|
|
@@ -274,6 +356,44 @@ TurboSnap section in `storybook-chromatic-builder`.
|
|
|
274
356
|
Update the manifest: `sync.lastRun` (timestamp), `tokens.lastSync`, confirm
|
|
275
357
|
`sync.platforms`. Append `token-sync-layer` to `completedSkills`.
|
|
276
358
|
|
|
359
|
+
Alongside that, record the stage entry: `node
|
|
360
|
+
.throughline/scripts/verify-check.mjs --record --stage
|
|
361
|
+
token-sync-layer --entry <tmp>.json` (subject defaults to `"system"`, since this
|
|
362
|
+
stage isn't per-component). Build `<tmp>.json` with `at` (an ISO timestamp you supply), `changed` summarizing tokens
|
|
363
|
+
added, changed, deleted and probable renames, and the attested
|
|
364
|
+
`tokens:validate-output` result as a check, plus `advancedBecause`. The entry
|
|
365
|
+
shape is `.throughline/references/proof-bundle.md` — don't restate it
|
|
366
|
+
here.
|
|
367
|
+
|
|
368
|
+
The entry also carries the **derived** check `color-contrast`, its result read off
|
|
369
|
+
the Step 5 report, with `evidence` naming the modes checked and the tightest
|
|
370
|
+
ratio. Record it whenever the report's `contrast:` line compared at least one
|
|
371
|
+
pair. When it compared none (the report says `contrast-rule-inert`, or the run
|
|
372
|
+
passed `--skip color-contrast`) there is no result to record: omit the check, and
|
|
373
|
+
say why in `advancedBecause`. A `pass` written there would describe a comparison
|
|
374
|
+
that never happened, and the gate could never contradict it.
|
|
375
|
+
|
|
376
|
+
**When a failure was subtracted, record `result: "fail"` — what the gate actually
|
|
377
|
+
reported** — with `advancedBecause` naming the accepted pair as the reason the sync
|
|
378
|
+
proceeded anyway, and `evidence` saying which pair was subtracted. Do not record
|
|
379
|
+
`pass` on the grounds that nothing remained after subtraction: the gate does not
|
|
380
|
+
know about subtraction, the pair still fails on disk, and the next sync's own gate
|
|
381
|
+
run recomputes it, so a recorded `pass` would raise `proof-contradicted` on every
|
|
382
|
+
later sync report. A stored result that disagrees with the file it claims to
|
|
383
|
+
describe is an attestation wearing a derived label.
|
|
384
|
+
|
|
385
|
+
**Expect one `proof-contradicted` line on the first sync after the user fixes the
|
|
386
|
+
pair**, and say why when you relay it: the stored `fail` is now disagreed with by a
|
|
387
|
+
rerun that passes, which `checkProof` reports in both directions. It does not stop
|
|
388
|
+
the sync, and this step re-records the pass on that same run, so it clears itself
|
|
389
|
+
in one pass. Relaying an expected, self-healing line as a problem is how a user is
|
|
390
|
+
taught to distrust the gate.
|
|
391
|
+
|
|
392
|
+
**The ordering is the point.** The gate runs in Step 5 and the record is written
|
|
393
|
+
here in Step 6, and it is that order which makes recording a derived result honest:
|
|
394
|
+
this stage has the gate's output in hand rather than asserting it, which is the one
|
|
395
|
+
and only reason a stage may record `method: "derived"`.
|
|
396
|
+
|
|
277
397
|
## Step 7 — Install the `/sync-figma-tokens` command
|
|
278
398
|
|
|
279
399
|
Set up the reusable command (the plugin ships it in `commands/`) so the user can
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@radicool/throughline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Build a complete design system end to end — author in Figma, sync tokens to code, generate Storybook. Usable from Claude Code, Cursor, Codex, or any AGENTS.md agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -31,7 +31,7 @@ deterministically. JSON is equally machine-legible for AI consumers.
|
|
|
31
31
|
"type": { "primary": "Highest-emphasis action…", "secondary": "…", "ghost": "…" },
|
|
32
32
|
"size": { "sm": "…", "md": "…", "lg": "…" }
|
|
33
33
|
},
|
|
34
|
-
"states": { "hover": "…", "focus": "…", "disabled": "…", "loading": "…" },
|
|
34
|
+
"states": { "hover": "…", "focus": "…", "active": "…", "disabled": "…", "loading": "…" },
|
|
35
35
|
"dos": ["Lead with a verb", "One primary button per view"],
|
|
36
36
|
"donts": ["Don't use for navigation", "Don't stack >2 primaries"],
|
|
37
37
|
"accessibility": {
|
|
@@ -40,6 +40,7 @@ deterministically. JSON is equally machine-legible for AI consumers.
|
|
|
40
40
|
"notes": ["Icon-only buttons need an aria-label"]
|
|
41
41
|
},
|
|
42
42
|
"tokensUsed": ["color.bg.primary", "spacing.sm", "radius.md"],
|
|
43
|
+
"archetype": "button",
|
|
43
44
|
"status": "stable",
|
|
44
45
|
"updatedAt": "2026-07-14",
|
|
45
46
|
"provenance": {
|
|
@@ -56,6 +57,15 @@ deterministically. JSON is equally machine-legible for AI consumers.
|
|
|
56
57
|
- **Optional content:** `whenToUse`, `whenNotToUse`, `variants`, `states`, `dos`,
|
|
57
58
|
`donts`, `accessibility`, `tokensUsed`.
|
|
58
59
|
- **Lifecycle:** `status` (`draft`|`beta`|`stable`|`deprecated`), `updatedAt` (ISO date).
|
|
60
|
+
- **`archetype`** — optional classification metadata: one of `button`, `input`,
|
|
61
|
+
`choice`, `card`, `modal`, `badge`, `other`, written by the authoring pipeline
|
|
62
|
+
that already matches a component to its nearest archetype. **Excluded from the
|
|
63
|
+
fingerprint**, like `provenance`, because it is classification rather than
|
|
64
|
+
projected content — so adding it changes no existing fingerprint and forces no
|
|
65
|
+
re-render. `verify:check` reads it to decide which baseline interaction states a
|
|
66
|
+
component owes; with it absent the gate falls back to an exact name match, and
|
|
67
|
+
reports `archetype-unknown` when neither resolves. See
|
|
68
|
+
`${CLAUDE_PLUGIN_ROOT}/references/proof-bundle.md`.
|
|
59
69
|
- **`provenance`** — per-block author source, one of `imported`, `ai-inferred`,
|
|
60
70
|
`best-practice`, `w3c-apg`, `framework`, `user`, or a `+`-joined combination
|
|
61
71
|
(e.g. `best-practice+user`). Regeneration **re-infers** a block whose
|