@radicool/throughline 0.13.0 → 0.15.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 +1 -1
- package/adapters/codex/AGENTS.md +11 -10
- package/adapters/codex/prompts/component-builder.md +107 -0
- package/adapters/codex/prompts/design-system-audit.md +20 -0
- package/adapters/codex/prompts/document-component.md +58 -0
- package/adapters/codex/prompts/repository-builder.md +14 -0
- package/adapters/codex/prompts/retrofit-planner.md +21 -1
- package/adapters/codex/prompts/storybook-chromatic-builder.md +84 -2
- package/adapters/cursor/.cursor/commands/document-component.md +58 -0
- package/adapters/cursor/.cursor/rules/component-builder.mdc +108 -1
- package/adapters/cursor/.cursor/rules/component-pipeline.mdc +1 -1
- package/adapters/cursor/.cursor/rules/design-system-audit.mdc +21 -1
- package/adapters/cursor/.cursor/rules/figma-environment-setup.mdc +1 -1
- package/adapters/cursor/.cursor/rules/icon-system-builder.mdc +1 -1
- package/adapters/cursor/.cursor/rules/repository-builder.mdc +15 -1
- package/adapters/cursor/.cursor/rules/retrofit-planner.mdc +22 -2
- package/adapters/cursor/.cursor/rules/storybook-chromatic-builder.mdc +85 -3
- package/adapters/cursor/.cursor/rules/token-builder.mdc +1 -1
- package/adapters/cursor/.cursor/rules/token-crosswalk-builder.mdc +1 -1
- package/adapters/cursor/.cursor/rules/token-sheet-builder.mdc +1 -1
- package/adapters/cursor/.cursor/rules/token-sync-layer.mdc +1 -1
- package/adapters/generic/AGENTS.md +11 -10
- package/adapters/generic/commands/document-component.md +58 -0
- package/adapters/generic/skills/component-builder/SKILL.md +107 -0
- package/adapters/generic/skills/design-system-audit/SKILL.md +20 -0
- package/adapters/generic/skills/repository-builder/SKILL.md +14 -0
- package/adapters/generic/skills/retrofit-planner/SKILL.md +21 -1
- package/adapters/generic/skills/storybook-chromatic-builder/SKILL.md +84 -2
- package/package.json +1 -1
- package/references/component-doc-archetypes.md +90 -0
- package/references/component-doc-schema.md +154 -0
- package/references/doc-card-builder.md +565 -0
- package/references/doc-writing-standard.md +144 -0
- package/references/figma-component-standards.md +63 -16
- package/references/guide-voice.md +96 -0
- package/references/manifest-schema.md +46 -6
- package/scripts/README.md +34 -0
- package/scripts/build-doc-card-builder.mjs +143 -0
- package/scripts/build-docs-digest.mjs +74 -0
- package/scripts/docs-check.mjs +117 -0
- package/scripts/docs-lint.mjs +163 -0
- package/scripts/install.mjs +13 -1
- package/scripts/lib/doc-card-plan.mjs +101 -0
- package/scripts/lib/doc-card-render.figma.js +371 -0
- package/scripts/lib/doc-record.mjs +54 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Retrofit planner (orchestrator)
|
|
2
2
|
|
|
3
|
-
Sequences a brownfield retrofit through the safe
|
|
3
|
+
Sequences a brownfield retrofit through the safe seven-phase order (with `docs` inserted
|
|
4
|
+
as a gated Phase 6.5), gating each phase on a
|
|
4
5
|
human confirmation. Like `component-pipeline`, this skill holds **zero domain logic of
|
|
5
6
|
its own** — it is a sequencer that invokes the real skills and the phase work, and only
|
|
6
7
|
updates the manifest fields it owns (`retrofit.*`, `completedSkills`). All the
|
|
@@ -102,6 +103,25 @@ triad as you go — `check-types`, `build-storybook` + Chromatic, **and run the
|
|
|
102
103
|
+ spot-check 5–7 routes (the build alone is blind to story-unreachable SCSS). **Gate:**
|
|
103
104
|
confirm the triad passes before continuing.
|
|
104
105
|
|
|
106
|
+
### Phase 6.5 — `docs` (adopt existing documentation, then fill gaps)
|
|
107
|
+
|
|
108
|
+
Set `retrofit.phase = "docs"`. Bring the documentation layer onto the system's
|
|
109
|
+
components **adopt-first**, so no existing human-written doc is lost:
|
|
110
|
+
|
|
111
|
+
1. **Adopt.** For each component, run the doc-authoring ingest (Step 4.5 of
|
|
112
|
+
`component-builder`): read existing code JSDoc/MDX/README and Figma
|
|
113
|
+
`description`, seed the canonical `.doc.json` marked `provenance: imported`, and
|
|
114
|
+
stamp fingerprints. This first pass **claims** existing content — it is not a
|
|
115
|
+
re-render and must not overwrite it.
|
|
116
|
+
2. **Fill gaps.** Run the remaining generation layers (infer → enrich → specialize
|
|
117
|
+
→ interview) only for blocks the adoption did not populate; the user approves.
|
|
118
|
+
3. **Project + gate.** Render the code surfaces (Step 5.5 of
|
|
119
|
+
`storybook-chromatic-builder`), run `docs:digest`, and run `docs:check` — it
|
|
120
|
+
should pass (surfaces just rendered) with Figma surfaces `edit-unverified`.
|
|
121
|
+
|
|
122
|
+
Confirm with the user before writing, consistent with every other phase. On a large
|
|
123
|
+
system, size the batch from `audit.docSurface` and adopt in reviewable chunks.
|
|
124
|
+
|
|
105
125
|
### Phase 7 — `cleanup`
|
|
106
126
|
|
|
107
127
|
Remove the old token outputs **only after** the repo-wide token-removal guard returns
|
|
@@ -27,6 +27,17 @@ 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 five
|
|
31
|
+
files and register the three npm scripts listed under **Documentation scripts —
|
|
32
|
+
install as a set** in `.throughline/scripts/README.md`. That table is
|
|
33
|
+
the single source of truth for what a consuming repo gets; do not restate the
|
|
34
|
+
list here.
|
|
35
|
+
|
|
36
|
+
These are the documentation analog of `tokens:validate`; see
|
|
37
|
+
`.throughline/scripts/README.md`. This copy is setup, not a forever-fork:
|
|
38
|
+
`/document-component` re-checks these files' freshness on every run and refreshes
|
|
39
|
+
them from the plugin when it has moved on.
|
|
40
|
+
|
|
30
41
|
**pnpm build-script allowlist (first-run gotcha).** On pnpm workspaces, the
|
|
31
42
|
`@storybook/react-vite` install pulls `esbuild`, whose postinstall is blocked by
|
|
32
43
|
pnpm's default `onlyBuiltDependencies` policy — Storybook then fails to start with a
|
|
@@ -149,6 +160,43 @@ Configure Chromatic to snapshot everything (do **not** set `onlyChanged`), and
|
|
|
149
160
|
verify a token-only PR re-snapshots all stories — they should flip orange against
|
|
150
161
|
the green baseline.
|
|
151
162
|
|
|
163
|
+
### Usage & cost guardrails
|
|
164
|
+
|
|
165
|
+
The full-snapshot default above is the right call for catching regressions, and it
|
|
166
|
+
is also the **maximum-usage** choice: every story, every run. Chromatic bills per
|
|
167
|
+
snapshot, so name this cost shape to the user when you set Chromatic up, and put the
|
|
168
|
+
guardrails in *before* the first big token PR, not after the bill.
|
|
169
|
+
|
|
170
|
+
What Chromatic actually offers (re-verify the live numbers at
|
|
171
|
+
`chromatic.com/pricing` — they drift):
|
|
172
|
+
|
|
173
|
+
- **Free plan (~5,000 snapshots/month):** testing **auto-pauses** when the ceiling
|
|
174
|
+
is hit. No surprise bill, but visual-regression coverage silently *stops* until
|
|
175
|
+
the monthly reset or an upgrade. For a full-suite design system that ceiling
|
|
176
|
+
arrives fast — treat a *paused* build as a red flag, not a passing one.
|
|
177
|
+
- **Paid plans: no hard spending cap.** Overage snapshots auto-bill at month-end.
|
|
178
|
+
The only native guardrail is **usage alerts** — an email when consumption crosses
|
|
179
|
+
a threshold you set (e.g. 90%).
|
|
180
|
+
|
|
181
|
+
The math, so the user sizes the plan honestly: **snapshots ≈ stories × modes ×
|
|
182
|
+
builds.** One `/sync-figma-tokens` PR re-snapshots the *entire* suite × every mode
|
|
183
|
+
in a single build — e.g. 40 components × 2 modes = 80 snapshots per build, and a
|
|
184
|
+
handful of token PRs plus daily `main` builds clears a free tier in a week.
|
|
185
|
+
|
|
186
|
+
So the guardrails, all of them user-controlled (Chromatic will not cap you):
|
|
187
|
+
|
|
188
|
+
1. **Set usage alerts** at ~80% on a paid plan so the bill cannot sneak up. On the
|
|
189
|
+
free plan, make sure the user knows testing *pauses* at the ceiling.
|
|
190
|
+
2. **Scope the CI trigger.** Run Chromatic on **pull requests and `main` only** —
|
|
191
|
+
never on every branch push — path-filter out docs-only changes, and keep it to
|
|
192
|
+
one Chromatic build per commit (no duplicate runs).
|
|
193
|
+
3. **Size the plan to the math** before the first token PR. A large story count is
|
|
194
|
+
also the *only* reason to revisit TurboSnap (see above), and even then treat
|
|
195
|
+
every token change as a full run.
|
|
196
|
+
|
|
197
|
+
Do not silently pick a plan or leave the trigger wide open. Name the tradeoff and
|
|
198
|
+
let the user choose with the numbers in front of them.
|
|
199
|
+
|
|
152
200
|
## Step 5 — Code Connect (plan-gated, skip gracefully)
|
|
153
201
|
|
|
154
202
|
Code Connect ties Figma components to their code counterparts so Figma's dev
|
|
@@ -170,6 +218,32 @@ mode shows the real code. It's plan-gated (Figma Organization/Enterprise).
|
|
|
170
218
|
run after the user publishes. This does **not** block the code side: implement
|
|
171
219
|
each slot prop from the recorded slot contract regardless of the Figma dropdown.
|
|
172
220
|
|
|
221
|
+
## Step 5.5 — Render documentation to code
|
|
222
|
+
|
|
223
|
+
For each component that has a canonical record
|
|
224
|
+
(`design-system/docs/components/<Name>.doc.json`), render the code-side surfaces
|
|
225
|
+
from it (read `.throughline/references/component-doc-schema.md` for the
|
|
226
|
+
projection contract):
|
|
227
|
+
|
|
228
|
+
- **Storybook autodocs (MDX).** Generate `<Name>.mdx` next to the component (e.g.
|
|
229
|
+
`packages/ui/src/<Name>/<Name>.mdx`) rendering summary, description,
|
|
230
|
+
when-to-use/not, do's/don'ts, accessibility, and a variant/state table. Put the
|
|
231
|
+
record's fingerprint in MDX frontmatter as `docFingerprint: <fp>`.
|
|
232
|
+
- **JSDoc.** Add a doc comment to the code component from `summary` + `description`
|
|
233
|
+
and per-prop descriptions from `variants`/`states` meanings, so `argTypes`
|
|
234
|
+
descriptions surface in the Storybook controls table.
|
|
235
|
+
- **AI digest.** Run `docs:digest` to (re)generate `design-system/docs/index.json`
|
|
236
|
+
+ `design-system/docs/llms.txt` from all records.
|
|
237
|
+
|
|
238
|
+
**Update the manifest:** add the `storybookMdx` surface to
|
|
239
|
+
`components.meta[<Name>].doc.surfaces` as `{ src: <fp>, render: <hash of the MDX
|
|
240
|
+
file>, file: "<repo-relative MDX path>" }`.
|
|
241
|
+
|
|
242
|
+
**Wire the gate.** Ensure `docs:check` is part of the repo's verification (a CI
|
|
243
|
+
step and/or a Turbo task). It compares every surface against its record and exits
|
|
244
|
+
non-zero on drift; Figma surfaces report `edit-unverified` (checked live in a Figma
|
|
245
|
+
session). Run `docs:check` once here and confirm it passes before handing off.
|
|
246
|
+
|
|
173
247
|
## Step 6 — Finalize component status (Figma write-back)
|
|
174
248
|
|
|
175
249
|
A component built and storied here is now **done** — but its Figma doc card was
|
|
@@ -193,10 +267,15 @@ the `figma_execute` scripting gotchas — `getNodeByIdAsync` and an explicit
|
|
|
193
267
|
|
|
194
268
|
- Set `components.meta[name].status` = `"stable"` and refresh
|
|
195
269
|
`components.meta[name].updatedAt` to today.
|
|
270
|
+
- When promoting status (e.g. draft → stable), also set `status` + `updatedAt` in
|
|
271
|
+
the component's `.doc.json`, recompute its fingerprint, re-run `docs:digest`, and
|
|
272
|
+
re-render the affected surfaces so `docs:check` stays green.
|
|
196
273
|
- If Figma is connected (per `figma.mechanism`), open the component's doc card and
|
|
197
274
|
update the `Status Label` text to `stable`, re-bind the `Status` chip fill to
|
|
198
|
-
the **success** semantic color variable (mode-aware, not a hardcoded hex),
|
|
199
|
-
|
|
275
|
+
the **success** semantic color variable (mode-aware, not a hardcoded hex), then
|
|
276
|
+
re-run the canonical doc-card builder against the same card to refresh the
|
|
277
|
+
header date from the `record.updatedAt` already set above (it locates the date
|
|
278
|
+
node under either header shape) — then screenshot to confirm the chip
|
|
200
279
|
recolored and the date changed.
|
|
201
280
|
- If Figma isn't connected, still update the manifest and tell the user the card
|
|
202
281
|
will reconcile next Figma session (or offer to reconnect and fix it now).
|
|
@@ -257,6 +336,9 @@ that may not exist.
|
|
|
257
336
|
- Never rely on TurboSnap (`onlyChanged: true`) for a token-driven design system
|
|
258
337
|
— its incremental model keeps missing global token changes. Default to full
|
|
259
338
|
snapshots (every story, every run); revisit only at large story counts.
|
|
339
|
+
- Never leave Chromatic's cost shape unspoken or the CI trigger wide open — there
|
|
340
|
+
is no hard spend cap on paid plans, so set usage alerts, run it on PRs + `main`
|
|
341
|
+
only, and size the plan to stories × modes × builds before the first token PR.
|
|
260
342
|
- Never use the sequential model for story-gen — parallelize via subagents.
|
|
261
343
|
- Never capture the Chromatic baseline *after* a code retrofit — baseline before, so
|
|
262
344
|
intended drift-fixes are distinguishable from regressions.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@radicool/throughline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.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": {
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Component documentation archetypes
|
|
2
|
+
|
|
3
|
+
The best-practice knowledge layer for the documentation generation pipeline
|
|
4
|
+
(`component-builder` Step: *Author the documentation record*). When authoring a
|
|
5
|
+
component's `.doc.json`, match the component to the nearest **archetype** below and
|
|
6
|
+
seed its `dos`, `donts`, `accessibility`, `whenToUse`, and `whenNotToUse` from that
|
|
7
|
+
entry, then specialize to the target framework and confirm with the user. Stamp
|
|
8
|
+
`provenance` as `best-practice` (or `w3c-apg` for the accessibility block) for
|
|
9
|
+
anything sourced here.
|
|
10
|
+
|
|
11
|
+
These are **seeds, not gospel** — the user's approval and the actual built
|
|
12
|
+
component override them. Sources: W3C ARIA Authoring Practices Guide (roles +
|
|
13
|
+
keyboard), Material 3, Shopify Polaris (content/usage), IBM Carbon (usage).
|
|
14
|
+
|
|
15
|
+
Seeds are clause-style shorthand; the authoring pipeline expands them into full sentences per references/doc-writing-standard.md before they enter a record.
|
|
16
|
+
|
|
17
|
+
## Button
|
|
18
|
+
|
|
19
|
+
- **whenToUse:** trigger an action or event (submit, confirm, open a dialog).
|
|
20
|
+
- **whenNotToUse:** navigation between pages/URLs (use a Link).
|
|
21
|
+
- **dos:** lead the label with a verb; keep one primary (highest-emphasis) button
|
|
22
|
+
per view; keep labels short (≤ ~3 words).
|
|
23
|
+
- **donts:** don't use a button for navigation (use a Link); don't stack multiple
|
|
24
|
+
primary buttons (keep one per view); don't disable without telling the user why.
|
|
25
|
+
- **accessibility (w3c-apg):** role `button`; Enter and Space activate; an
|
|
26
|
+
icon-only button needs an `aria-label`; disabled buttons are not focusable.
|
|
27
|
+
|
|
28
|
+
## Input / text field
|
|
29
|
+
|
|
30
|
+
- **whenToUse:** collect a single line of free-form text.
|
|
31
|
+
- **whenNotToUse:** choosing from a fixed set (use Select/Radio); long multi-line
|
|
32
|
+
text (use Textarea).
|
|
33
|
+
- **dos:** always pair with a visible label; show format hints as helper text;
|
|
34
|
+
reserve space for error text to avoid layout shift.
|
|
35
|
+
- **donts:** don't use placeholder text as the only label (pair with a visible
|
|
36
|
+
label); don't validate on every keystroke before first blur.
|
|
37
|
+
- **accessibility (w3c-apg):** every input has a programmatically associated
|
|
38
|
+
`<label>`; error state sets `aria-invalid` and links the message via
|
|
39
|
+
`aria-describedby`.
|
|
40
|
+
|
|
41
|
+
## Checkbox / radio / toggle
|
|
42
|
+
|
|
43
|
+
- **whenToUse:** checkbox/toggle for independent on/off; radio for one-of-many.
|
|
44
|
+
- **whenNotToUse:** a single either/or action that takes effect immediately with no
|
|
45
|
+
save (prefer a toggle) vs. a form choice (prefer radio/checkbox).
|
|
46
|
+
- **dos:** label the control, not just the group; make the label clickable.
|
|
47
|
+
- **donts:** don't use a radio group for multi-select (use checkboxes); don't use
|
|
48
|
+
a toggle for choices needing a separate Save (use radio/checkbox).
|
|
49
|
+
- **accessibility (w3c-apg):** roles `checkbox` / `radio` / `switch`; Space
|
|
50
|
+
toggles; radio groups navigate with arrow keys; state exposed via
|
|
51
|
+
`aria-checked`.
|
|
52
|
+
|
|
53
|
+
## Card
|
|
54
|
+
|
|
55
|
+
- **whenToUse:** group related content and actions about a single subject.
|
|
56
|
+
- **whenNotToUse:** primary page layout scaffolding (use a layout/grid component); a
|
|
57
|
+
bare list of text (use a List).
|
|
58
|
+
- **dos:** make the primary action obvious; keep one main call-to-action per card.
|
|
59
|
+
- **donts:** don't nest cards more than one level; don't make the whole card AND an
|
|
60
|
+
inner button separately clickable in conflicting ways.
|
|
61
|
+
- **accessibility:** if the whole card is a link/button, it needs an accessible
|
|
62
|
+
name; don't bury interactive controls that keyboard users can't reach in order.
|
|
63
|
+
|
|
64
|
+
## Modal / dialog
|
|
65
|
+
|
|
66
|
+
- **whenToUse:** interrupt for a focused task or a decision that blocks the flow.
|
|
67
|
+
- **whenNotToUse:** non-critical messages (use an inline banner or toast).
|
|
68
|
+
- **dos:** trap focus while open; return focus to the trigger on close; provide an
|
|
69
|
+
explicit close affordance.
|
|
70
|
+
- **donts:** don't stack modals; don't put long scrolling forms in a small modal.
|
|
71
|
+
- **accessibility (w3c-apg):** role `dialog` with `aria-modal="true"`; labelled by
|
|
72
|
+
its title (`aria-labelledby`); Escape closes; focus is trapped within.
|
|
73
|
+
|
|
74
|
+
## Badge / chip / tag
|
|
75
|
+
|
|
76
|
+
- **whenToUse:** short status, count, or category label (badge); a removable/
|
|
77
|
+
selectable item (chip).
|
|
78
|
+
- **whenNotToUse:** interactive primary actions (use a Button).
|
|
79
|
+
- **dos:** keep text to a word or two; match badge color to its semantic meaning
|
|
80
|
+
(success, warning, error).
|
|
81
|
+
- **donts:** don't rely on color alone to convey status (include text or an icon).
|
|
82
|
+
- **accessibility:** a removable chip's remove control needs an accessible name
|
|
83
|
+
(e.g. "Remove <label>"); status conveyed with text, not color only (WCAG 1.4.1).
|
|
84
|
+
|
|
85
|
+
## Fallback (unlisted archetype)
|
|
86
|
+
|
|
87
|
+
For a component without an entry above: derive `dos`/`donts` from its role and
|
|
88
|
+
built structure, source the `accessibility` block from the matching W3C APG
|
|
89
|
+
pattern, and mark everything for user confirmation. Add a new archetype section
|
|
90
|
+
here once the component's guidance stabilizes.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Component documentation record
|
|
2
|
+
|
|
3
|
+
The canonical, folder-resident source of truth for a component's usage
|
|
4
|
+
documentation. One JSON file per component at
|
|
5
|
+
`design-system/docs/components/<ComponentName>.doc.json`. Every other
|
|
6
|
+
documentation surface (Figma component description, the doc card, Storybook
|
|
7
|
+
autodocs/MDX, the AI digest) is a **projection** rendered from this file — never
|
|
8
|
+
authored independently.
|
|
9
|
+
|
|
10
|
+
It lives in the working folder from the moment a component is built (exactly like
|
|
11
|
+
`design-system.json`), so it exists during the Figma-only *folder* stage, before
|
|
12
|
+
any repo. The path stays `design-system/docs/` across folder→repo; files never
|
|
13
|
+
move.
|
|
14
|
+
|
|
15
|
+
## Why JSON (not YAML)
|
|
16
|
+
|
|
17
|
+
The plugin's scripts are zero-dependency and there is no YAML parser available, so
|
|
18
|
+
the record is JSON to keep `docs:check` and `docs:digest` able to parse it
|
|
19
|
+
deterministically. JSON is equally machine-legible for AI consumers.
|
|
20
|
+
|
|
21
|
+
## Schema
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"name": "Button",
|
|
26
|
+
"summary": "Triggers an action or event.",
|
|
27
|
+
"description": "A clickable control that initiates an action…",
|
|
28
|
+
"whenToUse": ["Submitting a form", "Confirming a decision"],
|
|
29
|
+
"whenNotToUse": ["Navigating to a new page — use a Link"],
|
|
30
|
+
"variants": {
|
|
31
|
+
"type": { "primary": "Highest-emphasis action…", "secondary": "…", "ghost": "…" },
|
|
32
|
+
"size": { "sm": "…", "md": "…", "lg": "…" }
|
|
33
|
+
},
|
|
34
|
+
"states": { "hover": "…", "focus": "…", "disabled": "…", "loading": "…" },
|
|
35
|
+
"dos": ["Lead with a verb", "One primary button per view"],
|
|
36
|
+
"donts": ["Don't use for navigation", "Don't stack >2 primaries"],
|
|
37
|
+
"accessibility": {
|
|
38
|
+
"role": "button",
|
|
39
|
+
"keyboard": ["Enter / Space activates"],
|
|
40
|
+
"notes": ["Icon-only buttons need an aria-label"]
|
|
41
|
+
},
|
|
42
|
+
"tokensUsed": ["color.bg.primary", "spacing.sm", "radius.md"],
|
|
43
|
+
"status": "stable",
|
|
44
|
+
"updatedAt": "2026-07-14",
|
|
45
|
+
"provenance": {
|
|
46
|
+
"description": "ai-inferred",
|
|
47
|
+
"dos": "best-practice+user",
|
|
48
|
+
"accessibility": "w3c-apg"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Fields (v1 core)
|
|
54
|
+
|
|
55
|
+
- **Required:** `name`, `summary`, `description`.
|
|
56
|
+
- **Optional content:** `whenToUse`, `whenNotToUse`, `variants`, `states`, `dos`,
|
|
57
|
+
`donts`, `accessibility`, `tokensUsed`.
|
|
58
|
+
- **Lifecycle:** `status` (`draft`|`beta`|`stable`|`deprecated`), `updatedAt` (ISO date).
|
|
59
|
+
- **`provenance`** — per-block author source, one of `imported`, `ai-inferred`,
|
|
60
|
+
`best-practice`, `w3c-apg`, `framework`, `user`, or a `+`-joined combination
|
|
61
|
+
(e.g. `best-practice+user`). Regeneration **re-infers** a block whose
|
|
62
|
+
provenance includes `ai-inferred`, `framework`, `best-practice`, or `w3c-apg`,
|
|
63
|
+
and **never overwrites** one whose provenance includes `user` or `imported`.
|
|
64
|
+
Every value is assigned to exactly one of those two tiers: generated content is
|
|
65
|
+
re-inferred, human input (`user`) and pre-existing external content
|
|
66
|
+
(`imported`) are protected. Protection takes precedence: a combination that
|
|
67
|
+
contains both — `best-practice+user`, say — is protected. A block is
|
|
68
|
+
re-inferred only when it carries no `user` or `imported` marker at all. A
|
|
69
|
+
protected block may still be rewritten when the user approves the rewrite at
|
|
70
|
+
the record-approval gate; the result is stamped `imported+user`, which is
|
|
71
|
+
protected from then on and never re-proposed.
|
|
72
|
+
|
|
73
|
+
Deferred to a later version (do not emit in v1): `anatomy`, `content` (writing
|
|
74
|
+
guidelines), `examples`.
|
|
75
|
+
|
|
76
|
+
## Fingerprint algorithm
|
|
77
|
+
|
|
78
|
+
`fingerprint = sha256(stableStringify(record_without_provenance)).slice(0, 16)`
|
|
79
|
+
|
|
80
|
+
- `provenance` is **excluded** — it is authoring metadata, not projected content.
|
|
81
|
+
- `stableStringify` sorts object keys recursively so formatting/key-order never
|
|
82
|
+
affects the hash.
|
|
83
|
+
- The 16-hex-char result is the stamp recorded per surface and per canonical file.
|
|
84
|
+
|
|
85
|
+
Implemented once in `scripts/lib/doc-record.mjs` (`canonicalFingerprint`) and
|
|
86
|
+
reused by `docs:check`, `docs:digest`, and — for the Figma surfaces — by the
|
|
87
|
+
Figma-connected skill computing the identical hash over the description content.
|
|
88
|
+
|
|
89
|
+
## Projection mapping
|
|
90
|
+
|
|
91
|
+
| Block(s) | Figma component description | Doc card | Storybook autodocs (MDX) + JSDoc | AI digest |
|
|
92
|
+
|---|---|---|---|---|
|
|
93
|
+
| summary, description | ✔ | ✔ | ✔ | ✔ |
|
|
94
|
+
| whenToUse / whenNotToUse | ✔ | ✔ | ✔ | ✔ |
|
|
95
|
+
| variants, states (meanings) | — | ✔ legend | ✔ argTypes | ✔ |
|
|
96
|
+
| dos / donts | ✔ | ✔ | ✔ | ✔ |
|
|
97
|
+
| accessibility | ✔ | ✔ | ✔ | ✔ |
|
|
98
|
+
| tokensUsed | — | — | ✔ | ✔ |
|
|
99
|
+
|
|
100
|
+
Each surface carries a fingerprint stamp of the record it was rendered from:
|
|
101
|
+
- **Figma component description** — a trailing marker line `<!-- tl:doc <fp> -->`.
|
|
102
|
+
- **Doc card** — a named metadata node `Doc Fingerprint` holding `<fp>`.
|
|
103
|
+
- **Storybook MDX** — a frontmatter field `docFingerprint: <fp>`.
|
|
104
|
+
|
|
105
|
+
## Manifest pointer (`components.meta[name].doc`)
|
|
106
|
+
|
|
107
|
+
The manifest stores pointers + per-surface fingerprints, never content:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"doc": {
|
|
112
|
+
"path": "design-system/docs/components/Button.doc.json",
|
|
113
|
+
"fingerprint": "<canonical fingerprint at last render>",
|
|
114
|
+
"surfaces": {
|
|
115
|
+
"figmaDescription": { "src": "<fp>", "render": "<hash of description text>" },
|
|
116
|
+
"docCard": { "src": "<fp>", "render": "<hash of card content>", "renderer": "4" },
|
|
117
|
+
"storybookMdx": { "src": "<fp>", "render": "<hash of mdx file>", "file": "packages/ui/src/Button/Button.mdx" }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
- `src` — the canonical fingerprint the surface was rendered from (detects **stale**).
|
|
124
|
+
- `render` — a hash of the surface's rendered content at render time (detects
|
|
125
|
+
**edited**, for surfaces the tooling can re-read).
|
|
126
|
+
- `file` — repo-relative path for code surfaces so `docs:check` can re-read them.
|
|
127
|
+
- `renderer` — (docCard only) the layout version of the builder that last
|
|
128
|
+
rendered the card: `DOC_CARD_RENDERER_VERSION` in `scripts/lib/doc-card-plan.mjs`,
|
|
129
|
+
currently `"4"`. Additive and optional — absence means the card predates the
|
|
130
|
+
versioned builder. Stamped from the builder's returned summary, never by
|
|
131
|
+
re-reading the card.
|
|
132
|
+
|
|
133
|
+
## Drift + reconciliation contract
|
|
134
|
+
|
|
135
|
+
`docs:check` classifies each surface:
|
|
136
|
+
- **canonical-changed** — the `.doc.json` fingerprint ≠ `doc.fingerprint`.
|
|
137
|
+
- **stale** — `surface.src` ≠ current canonical fingerprint.
|
|
138
|
+
- **edited** — a re-readable surface's current content hash ≠ `surface.render`.
|
|
139
|
+
- **layout-upgrade-available** — informational, never failing, docCard only:
|
|
140
|
+
`surfaces.docCard.renderer` is missing or lower than the current
|
|
141
|
+
`DOC_CARD_RENDERER_VERSION`. The card's content is not in drift — its layout
|
|
142
|
+
predates the current builder. Re-render on next touch (no unprompted Figma
|
|
143
|
+
writes; untouched brownfield cards must not generate a standing warning wall).
|
|
144
|
+
- **missing-surface** — a repo surface that declares a `file` which is now gone.
|
|
145
|
+
Failing, and distinct from `edit-unverified`: the surface *was* re-readable and
|
|
146
|
+
its rendered output has been deleted, not merely unreadable this run.
|
|
147
|
+
- **edit-unverified** — a surface the CLI can't read (Figma); checked live by the
|
|
148
|
+
Figma-connected skill instead.
|
|
149
|
+
|
|
150
|
+
Reconciliation is **per item, reviewable**: for each drift the user chooses
|
|
151
|
+
**re-render** (canonical wins) or **pull-back** (fold the surface edit into the
|
|
152
|
+
record), landed as a PR. **Brownfield first run is an adoption**, not a re-render:
|
|
153
|
+
existing surface content is claimed into the record as `provenance: imported` and
|
|
154
|
+
fingerprints are stamped, rather than treated as `edited` drift.
|