@radicool/throughline 0.13.0 → 0.14.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 +2 -1
- package/adapters/codex/prompts/component-builder.md +63 -0
- package/adapters/codex/prompts/design-system-audit.md +20 -0
- package/adapters/codex/prompts/document-component.md +26 -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 +39 -0
- package/adapters/cursor/.cursor/commands/document-component.md +26 -0
- package/adapters/cursor/.cursor/rules/component-builder.mdc +63 -0
- package/adapters/cursor/.cursor/rules/design-system-audit.mdc +20 -0
- package/adapters/cursor/.cursor/rules/repository-builder.mdc +14 -0
- package/adapters/cursor/.cursor/rules/retrofit-planner.mdc +22 -2
- package/adapters/cursor/.cursor/rules/storybook-chromatic-builder.mdc +39 -0
- package/adapters/generic/AGENTS.md +2 -1
- package/adapters/generic/commands/document-component.md +26 -0
- package/adapters/generic/skills/component-builder/SKILL.md +63 -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 +39 -0
- package/package.json +1 -1
- package/references/component-doc-archetypes.md +86 -0
- package/references/component-doc-schema.md +136 -0
- package/references/manifest-schema.md +27 -5
- package/scripts/README.md +11 -0
- package/scripts/build-docs-digest.mjs +74 -0
- package/scripts/docs-check.mjs +103 -0
- package/scripts/lib/doc-record.mjs +54 -0
|
@@ -227,6 +227,66 @@ Two rules for every slot:
|
|
|
227
227
|
Record each component's slots, variant matrix, and token bindings in the
|
|
228
228
|
component spec (for Code Connect when available, else the repo component spec).
|
|
229
229
|
|
|
230
|
+
## Step 4.5 — Author the documentation record (and project it)
|
|
231
|
+
|
|
232
|
+
Every component gets a canonical documentation record — the source of truth for
|
|
233
|
+
its usage docs — written to the working folder next to `design-system.json`
|
|
234
|
+
(**folder-resident from day one**, exactly like the manifest; no repo required).
|
|
235
|
+
Read `.throughline/references/component-doc-schema.md` for the exact JSON
|
|
236
|
+
schema, the fingerprint algorithm, and the projection contract.
|
|
237
|
+
|
|
238
|
+
**Run the generation pipeline (each layer only fills what it legitimately knows;
|
|
239
|
+
stamp `provenance` per block):**
|
|
240
|
+
|
|
241
|
+
0. **Ingest existing docs — brownfield only, runs first.** If docs already exist
|
|
242
|
+
for this component (code JSDoc/MDX/README, or a populated Figma component
|
|
243
|
+
`description`), read them and seed the record marked `provenance: imported`.
|
|
244
|
+
**Never silently overwrite existing human-written docs** — this is the
|
|
245
|
+
read-before-you-assert rule. Skip on greenfield.
|
|
246
|
+
1. **Infer from the built artifact.** From the component you just built — its
|
|
247
|
+
variants, states, slots, and bound tokens — author `description`, `variants`,
|
|
248
|
+
`states`, and `tokensUsed` (`tokensUsed` comes from the real variable bindings,
|
|
249
|
+
not a guess). Provenance `ai-inferred`.
|
|
250
|
+
2. **Enrich from the archetype knowledge base.** Match the component to the nearest
|
|
251
|
+
archetype in `.throughline/references/component-doc-archetypes.md` and
|
|
252
|
+
seed `dos`, `donts`, `accessibility`, `whenToUse`, `whenNotToUse`. Provenance
|
|
253
|
+
`best-practice` (or `w3c-apg` for the accessibility block).
|
|
254
|
+
3. **Specialize to `project.uiFramework`.** Align variant-meaning wording and the
|
|
255
|
+
accessibility idiom to the target framework (the same field you read for variant
|
|
256
|
+
vocabulary). Provenance `framework`.
|
|
257
|
+
4. **Interview for the non-inferable.** Ask the user for brand/product-specific
|
|
258
|
+
do's & don'ts and intent. Provenance `user`. **Show the whole drafted record and
|
|
259
|
+
get explicit approval before writing anything** — layers 1–4 only fill blocks the
|
|
260
|
+
ingest step did not, and an `imported`/`user` block is never overwritten.
|
|
261
|
+
|
|
262
|
+
**Write the record and project it:**
|
|
263
|
+
|
|
264
|
+
- Write `design-system/docs/components/<Name>.doc.json` (JSON; required fields
|
|
265
|
+
`name`, `summary`, `description`).
|
|
266
|
+
- **Figma component description.** Set the component's native `description` field
|
|
267
|
+
(via `figma_set_description`) to a compact markdown rendering — summary,
|
|
268
|
+
when-to-use/not, do's/don'ts, and the a11y summary — and append a fingerprint
|
|
269
|
+
marker line `<!-- tl:doc <fp> -->` (this is the surface Dev Mode and Code Connect
|
|
270
|
+
read).
|
|
271
|
+
- **Doc card body.** Extend the existing doc card (name/short-desc/status/date, per
|
|
272
|
+
`.throughline/references/figma-component-standards.md`) with a usage body:
|
|
273
|
+
when-to-use, do's/don'ts, an a11y line, and a variant/state legend — all
|
|
274
|
+
token-bound (no hardcoded hex/px). Add a metadata text node named
|
|
275
|
+
`Doc Fingerprint` holding `<fp>`.
|
|
276
|
+
- Compute `<fp>` as the canonical fingerprint defined in the schema reference
|
|
277
|
+
(sha256 of the projected record without `provenance`, first 16 hex chars).
|
|
278
|
+
|
|
279
|
+
**Update the manifest (fields this skill owns):** set
|
|
280
|
+
`components.meta[<Name>].doc` to `{ path, fingerprint: <fp>, surfaces: {
|
|
281
|
+
figmaDescription: { src: <fp>, render: <hash of the description text> }, docCard: {
|
|
282
|
+
src: <fp>, render: <hash of the card body content> } } }`. The code surfaces
|
|
283
|
+
(`storybookMdx`) are added later by `storybook-chromatic-builder`.
|
|
284
|
+
|
|
285
|
+
Run the standard doc-card visual-validation + post-build audit
|
|
286
|
+
(`.throughline/references/figma-component-standards.md`) after enriching
|
|
287
|
+
the card. `docs:check` runs at the code stage; at folder stage the record + Figma
|
|
288
|
+
surfaces are the fallback.
|
|
289
|
+
|
|
230
290
|
## Step 5 — Naming as contract
|
|
231
291
|
|
|
232
292
|
Name components deterministically so Figma↔code mapping is automatic: `Button` ↔
|
|
@@ -284,4 +344,7 @@ Offer next steps: build the code counterparts and stories
|
|
|
284
344
|
internal architecture) **detaches every instance** that referenced its variants (the
|
|
285
345
|
Card's footer buttons, etc.). Record which components consume which, warn before an
|
|
286
346
|
architectural rebuild, and re-instance the affected consumers afterward.
|
|
347
|
+
- Never overwrite an existing component `description` or imported doc content
|
|
348
|
+
without reading it first and marking it `provenance: imported` — brownfield docs
|
|
349
|
+
are seeds, not blank slates.
|
|
287
350
|
|
|
@@ -56,6 +56,23 @@ counts:
|
|
|
56
56
|
repo with no Tailwind simply scores `0` there. Detect what the repo actually uses (is
|
|
57
57
|
there a `tailwind.config`? SCSS? CSS-in-JS?) and explain the counts in those terms.
|
|
58
58
|
|
|
59
|
+
## Step 1.5 — Size the documentation surface
|
|
60
|
+
|
|
61
|
+
Inventory existing documentation the same way the code surface is sized — from
|
|
62
|
+
**verified reads, never assumptions**. Per component (or per code component when no
|
|
63
|
+
Figma component exists yet), record whether usage docs already exist and where:
|
|
64
|
+
|
|
65
|
+
- **Code:** JSDoc/TSDoc on the component, an `.mdx` doc page, a per-component
|
|
66
|
+
README.
|
|
67
|
+
- **Figma:** a populated component `description` field.
|
|
68
|
+
|
|
69
|
+
Write the totals to `audit.docSurface` in the manifest, e.g. `{ "documented": 12,
|
|
70
|
+
"undocumented": 34, "sources": { "codeJsdoc": 8, "mdx": 4, "figmaDescription": 6,
|
|
71
|
+
"readme": 3 } }`. This right-sizes the documentation retrofit (how much exists to
|
|
72
|
+
adopt vs. author from scratch) so `retrofit-planner`'s `docs` phase can be planned
|
|
73
|
+
against real numbers. See `.throughline/references/component-doc-schema.md`
|
|
74
|
+
for what a full record contains.
|
|
75
|
+
|
|
59
76
|
## Step 2 — Inventory the Figma file (verified per-class reads)
|
|
60
77
|
|
|
61
78
|
Variables, text styles, and effect/paint styles are **different surfaces** — read each
|
|
@@ -103,6 +120,9 @@ Set `tokens.intakeMode: "retrofit"` (this skill establishes the brownfield path
|
|
|
103
120
|
owns this transition). Append `design-system-audit` to `completedSkills`.
|
|
104
121
|
|
|
105
122
|
Then recommend the next step:
|
|
123
|
+
- Report the documentation debt from `audit.docSurface` (documented vs.
|
|
124
|
+
undocumented) and note that the retrofit's `docs` phase will adopt existing docs
|
|
125
|
+
before authoring the gaps.
|
|
106
126
|
- If the user wants the guided, gated end-to-end retrofit → **`retrofit-planner`**
|
|
107
127
|
(the orchestrator; recommended for multi-session retrofits).
|
|
108
128
|
- If they only want the crosswalk backbone next → **`token-crosswalk-builder`** (it
|
|
@@ -106,6 +106,20 @@ in it" — they already have a working local repo.
|
|
|
106
106
|
On success, set `workspace.stage` and `repo.stage` to `github` and record
|
|
107
107
|
`repo.remote`. Append `repository-builder` to `completedSkills`.
|
|
108
108
|
|
|
109
|
+
### Adopt the documentation store
|
|
110
|
+
|
|
111
|
+
The folder-resident documentation store at `design-system/docs/` (canonical
|
|
112
|
+
`*.doc.json` records plus the generated `index.json` + `llms.txt`) already exists
|
|
113
|
+
from the Figma phase. Bring it under version control as-is — **do not relocate it**
|
|
114
|
+
(the path is stable across folder→repo, and every manifest `doc.path` points at it).
|
|
115
|
+
Ensure it is committed (not git-ignored).
|
|
116
|
+
|
|
117
|
+
Wire the documentation drift gate into the repo's verification so it runs in CI
|
|
118
|
+
alongside `tokens:validate`: add a `docs:check` step (the `docs-check.mjs` script is
|
|
119
|
+
installed by `storybook-chromatic-builder`; if code hasn't been set up yet, note
|
|
120
|
+
that the gate comes online with the Storybook step). `docs:check` exits non-zero on
|
|
121
|
+
drift; Figma surfaces report `edit-unverified` and are checked in a Figma session.
|
|
122
|
+
|
|
109
123
|
## Secrets: the part most people have never done
|
|
110
124
|
|
|
111
125
|
This is where users with low coding experience get stuck — many have never made
|
|
@@ -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,16 @@ 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 from the
|
|
31
|
+
plugin's `scripts/` — `build-docs-digest.mjs`, `docs-check.mjs`, and
|
|
32
|
+
`lib/doc-record.mjs` — into the repo and register npm scripts):
|
|
33
|
+
|
|
34
|
+
- `"docs:digest": "node scripts/build-docs-digest.mjs"`
|
|
35
|
+
- `"docs:check": "node scripts/docs-check.mjs"`
|
|
36
|
+
|
|
37
|
+
These are the documentation analog of `tokens:validate`; see
|
|
38
|
+
`.throughline/scripts/README.md`.
|
|
39
|
+
|
|
30
40
|
**pnpm build-script allowlist (first-run gotcha).** On pnpm workspaces, the
|
|
31
41
|
`@storybook/react-vite` install pulls `esbuild`, whose postinstall is blocked by
|
|
32
42
|
pnpm's default `onlyBuiltDependencies` policy — Storybook then fails to start with a
|
|
@@ -170,6 +180,32 @@ mode shows the real code. It's plan-gated (Figma Organization/Enterprise).
|
|
|
170
180
|
run after the user publishes. This does **not** block the code side: implement
|
|
171
181
|
each slot prop from the recorded slot contract regardless of the Figma dropdown.
|
|
172
182
|
|
|
183
|
+
## Step 5.5 — Render documentation to code
|
|
184
|
+
|
|
185
|
+
For each component that has a canonical record
|
|
186
|
+
(`design-system/docs/components/<Name>.doc.json`), render the code-side surfaces
|
|
187
|
+
from it (read `.throughline/references/component-doc-schema.md` for the
|
|
188
|
+
projection contract):
|
|
189
|
+
|
|
190
|
+
- **Storybook autodocs (MDX).** Generate `<Name>.mdx` next to the component (e.g.
|
|
191
|
+
`packages/ui/src/<Name>/<Name>.mdx`) rendering summary, description,
|
|
192
|
+
when-to-use/not, do's/don'ts, accessibility, and a variant/state table. Put the
|
|
193
|
+
record's fingerprint in MDX frontmatter as `docFingerprint: <fp>`.
|
|
194
|
+
- **JSDoc.** Add a doc comment to the code component from `summary` + `description`
|
|
195
|
+
and per-prop descriptions from `variants`/`states` meanings, so `argTypes`
|
|
196
|
+
descriptions surface in the Storybook controls table.
|
|
197
|
+
- **AI digest.** Run `docs:digest` to (re)generate `design-system/docs/index.json`
|
|
198
|
+
+ `design-system/docs/llms.txt` from all records.
|
|
199
|
+
|
|
200
|
+
**Update the manifest:** add the `storybookMdx` surface to
|
|
201
|
+
`components.meta[<Name>].doc.surfaces` as `{ src: <fp>, render: <hash of the MDX
|
|
202
|
+
file>, file: "<repo-relative MDX path>" }`.
|
|
203
|
+
|
|
204
|
+
**Wire the gate.** Ensure `docs:check` is part of the repo's verification (a CI
|
|
205
|
+
step and/or a Turbo task). It compares every surface against its record and exits
|
|
206
|
+
non-zero on drift; Figma surfaces report `edit-unverified` (checked live in a Figma
|
|
207
|
+
session). Run `docs:check` once here and confirm it passes before handing off.
|
|
208
|
+
|
|
173
209
|
## Step 6 — Finalize component status (Figma write-back)
|
|
174
210
|
|
|
175
211
|
A component built and storied here is now **done** — but its Figma doc card was
|
|
@@ -193,6 +229,9 @@ the `figma_execute` scripting gotchas — `getNodeByIdAsync` and an explicit
|
|
|
193
229
|
|
|
194
230
|
- Set `components.meta[name].status` = `"stable"` and refresh
|
|
195
231
|
`components.meta[name].updatedAt` to today.
|
|
232
|
+
- When promoting status (e.g. draft → stable), also set `status` + `updatedAt` in
|
|
233
|
+
the component's `.doc.json`, recompute its fingerprint, re-run `docs:digest`, and
|
|
234
|
+
re-render the affected surfaces so `docs:check` stays green.
|
|
196
235
|
- If Figma is connected (per `figma.mechanism`), open the component's doc card and
|
|
197
236
|
update the `Status Label` text to `stable`, re-bind the `Status` chip fill to
|
|
198
237
|
the **success** semantic color variable (mode-aware, not a hardcoded hex), and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@radicool/throughline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.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,86 @@
|
|
|
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
|
+
## Button
|
|
16
|
+
|
|
17
|
+
- **whenToUse:** trigger an action or event (submit, confirm, open a dialog).
|
|
18
|
+
- **whenNotToUse:** navigation between pages/URLs — use a Link.
|
|
19
|
+
- **dos:** lead the label with a verb; keep one primary (highest-emphasis) button
|
|
20
|
+
per view; keep labels short (≤ ~3 words).
|
|
21
|
+
- **donts:** don't use a button for navigation; don't stack multiple primary
|
|
22
|
+
buttons; don't disable without telling the user why.
|
|
23
|
+
- **accessibility (w3c-apg):** role `button`; Enter and Space activate; an
|
|
24
|
+
icon-only button needs an `aria-label`; disabled buttons are not focusable.
|
|
25
|
+
|
|
26
|
+
## Input / text field
|
|
27
|
+
|
|
28
|
+
- **whenToUse:** collect a single line of free-form text.
|
|
29
|
+
- **whenNotToUse:** choosing from a fixed set (use Select/Radio); long multi-line
|
|
30
|
+
text (use Textarea).
|
|
31
|
+
- **dos:** always pair with a visible label; show format hints as helper text;
|
|
32
|
+
reserve space for error text to avoid layout shift.
|
|
33
|
+
- **donts:** don't use placeholder text as the only label; don't validate on every
|
|
34
|
+
keystroke before first blur.
|
|
35
|
+
- **accessibility (w3c-apg):** every input has a programmatically associated
|
|
36
|
+
`<label>`; error state sets `aria-invalid` and links the message via
|
|
37
|
+
`aria-describedby`.
|
|
38
|
+
|
|
39
|
+
## Checkbox / radio / toggle
|
|
40
|
+
|
|
41
|
+
- **whenToUse:** checkbox/toggle for independent on/off; radio for one-of-many.
|
|
42
|
+
- **whenNotToUse:** a single either/or action that takes effect immediately with no
|
|
43
|
+
save (prefer a toggle) vs. a form choice (prefer radio/checkbox).
|
|
44
|
+
- **dos:** label the control, not just the group; make the label clickable.
|
|
45
|
+
- **donts:** don't use a radio group for multi-select; don't use a toggle for
|
|
46
|
+
choices that only apply after a separate Save.
|
|
47
|
+
- **accessibility (w3c-apg):** roles `checkbox` / `radio` / `switch`; Space
|
|
48
|
+
toggles; radio groups navigate with arrow keys; state exposed via
|
|
49
|
+
`aria-checked`.
|
|
50
|
+
|
|
51
|
+
## Card
|
|
52
|
+
|
|
53
|
+
- **whenToUse:** group related content and actions about a single subject.
|
|
54
|
+
- **whenNotToUse:** primary page layout scaffolding; a bare list of text.
|
|
55
|
+
- **dos:** make the primary action obvious; keep one main call-to-action per card.
|
|
56
|
+
- **donts:** don't nest cards more than one level; don't make the whole card AND an
|
|
57
|
+
inner button separately clickable in conflicting ways.
|
|
58
|
+
- **accessibility:** if the whole card is a link/button, it needs an accessible
|
|
59
|
+
name; don't bury interactive controls that keyboard users can't reach in order.
|
|
60
|
+
|
|
61
|
+
## Modal / dialog
|
|
62
|
+
|
|
63
|
+
- **whenToUse:** interrupt for a focused task or a decision that blocks the flow.
|
|
64
|
+
- **whenNotToUse:** non-critical messages (use an inline banner or toast).
|
|
65
|
+
- **dos:** trap focus while open; return focus to the trigger on close; provide an
|
|
66
|
+
explicit close affordance.
|
|
67
|
+
- **donts:** don't stack modals; don't put long scrolling forms in a small modal.
|
|
68
|
+
- **accessibility (w3c-apg):** role `dialog` with `aria-modal="true"`; labelled by
|
|
69
|
+
its title (`aria-labelledby`); Escape closes; focus is trapped within.
|
|
70
|
+
|
|
71
|
+
## Badge / chip / tag
|
|
72
|
+
|
|
73
|
+
- **whenToUse:** short status, count, or category label (badge); a removable/
|
|
74
|
+
selectable token (chip).
|
|
75
|
+
- **whenNotToUse:** interactive primary actions (use a Button).
|
|
76
|
+
- **dos:** keep text to a word or two; bind color to a semantic tone token.
|
|
77
|
+
- **donts:** don't rely on color alone to convey status — include text/icon.
|
|
78
|
+
- **accessibility:** a removable chip's remove control needs an accessible name
|
|
79
|
+
(e.g. "Remove <label>"); status conveyed with text, not color only (WCAG 1.4.1).
|
|
80
|
+
|
|
81
|
+
## Fallback (unlisted archetype)
|
|
82
|
+
|
|
83
|
+
For a component without an entry above: derive `dos`/`donts` from its role and
|
|
84
|
+
built structure, source the `accessibility` block from the matching W3C APG
|
|
85
|
+
pattern, and mark everything for user confirmation. Add a new archetype section
|
|
86
|
+
here once the component's guidance stabilizes.
|
|
@@ -0,0 +1,136 @@
|
|
|
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 `ai-inferred`/`framework`
|
|
62
|
+
blocks and **never overwrites** a block whose provenance includes `user` or
|
|
63
|
+
`imported`.
|
|
64
|
+
|
|
65
|
+
Deferred to a later version (do not emit in v1): `anatomy`, `content` (writing
|
|
66
|
+
guidelines), `examples`.
|
|
67
|
+
|
|
68
|
+
## Fingerprint algorithm
|
|
69
|
+
|
|
70
|
+
`fingerprint = sha256(stableStringify(record_without_provenance)).slice(0, 16)`
|
|
71
|
+
|
|
72
|
+
- `provenance` is **excluded** — it is authoring metadata, not projected content.
|
|
73
|
+
- `stableStringify` sorts object keys recursively so formatting/key-order never
|
|
74
|
+
affects the hash.
|
|
75
|
+
- The 16-hex-char result is the stamp recorded per surface and per canonical file.
|
|
76
|
+
|
|
77
|
+
Implemented once in `scripts/lib/doc-record.mjs` (`canonicalFingerprint`) and
|
|
78
|
+
reused by `docs:check`, `docs:digest`, and — for the Figma surfaces — by the
|
|
79
|
+
Figma-connected skill computing the identical hash over the description content.
|
|
80
|
+
|
|
81
|
+
## Projection mapping
|
|
82
|
+
|
|
83
|
+
| Block(s) | Figma component description | Doc card | Storybook autodocs (MDX) + JSDoc | AI digest |
|
|
84
|
+
|---|---|---|---|---|
|
|
85
|
+
| summary, description | ✔ | ✔ | ✔ | ✔ |
|
|
86
|
+
| whenToUse / whenNotToUse | ✔ | ✔ | ✔ | ✔ |
|
|
87
|
+
| variants, states (meanings) | ✔ compact | ✔ legend | ✔ argTypes | ✔ |
|
|
88
|
+
| dos / donts | ✔ | ✔ | ✔ | ✔ |
|
|
89
|
+
| accessibility | ✔ | ✔ | ✔ | ✔ |
|
|
90
|
+
| tokensUsed | — | — | ✔ | ✔ |
|
|
91
|
+
|
|
92
|
+
Each surface carries a fingerprint stamp of the record it was rendered from:
|
|
93
|
+
- **Figma component description** — a trailing marker line `<!-- tl:doc <fp> -->`.
|
|
94
|
+
- **Doc card** — a named metadata node `Doc Fingerprint` holding `<fp>`.
|
|
95
|
+
- **Storybook MDX** — a frontmatter field `docFingerprint: <fp>`.
|
|
96
|
+
|
|
97
|
+
## Manifest pointer (`components.meta[name].doc`)
|
|
98
|
+
|
|
99
|
+
The manifest stores pointers + per-surface fingerprints, never content:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"doc": {
|
|
104
|
+
"path": "design-system/docs/components/Button.doc.json",
|
|
105
|
+
"fingerprint": "<canonical fingerprint at last render>",
|
|
106
|
+
"surfaces": {
|
|
107
|
+
"figmaDescription": { "src": "<fp>", "render": "<hash of description text>" },
|
|
108
|
+
"docCard": { "src": "<fp>", "render": "<hash of card content>" },
|
|
109
|
+
"storybookMdx": { "src": "<fp>", "render": "<hash of mdx file>", "file": "packages/ui/src/Button/Button.mdx" }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- `src` — the canonical fingerprint the surface was rendered from (detects **stale**).
|
|
116
|
+
- `render` — a hash of the surface's rendered content at render time (detects
|
|
117
|
+
**edited**, for surfaces the tooling can re-read).
|
|
118
|
+
- `file` — repo-relative path for code surfaces so `docs:check` can re-read them.
|
|
119
|
+
|
|
120
|
+
## Drift + reconciliation contract
|
|
121
|
+
|
|
122
|
+
`docs:check` classifies each surface:
|
|
123
|
+
- **canonical-changed** — the `.doc.json` fingerprint ≠ `doc.fingerprint`.
|
|
124
|
+
- **stale** — `surface.src` ≠ current canonical fingerprint.
|
|
125
|
+
- **edited** — a re-readable surface's current content hash ≠ `surface.render`.
|
|
126
|
+
- **missing-surface** — a repo surface that declares a `file` which is now gone.
|
|
127
|
+
Failing, and distinct from `edit-unverified`: the surface *was* re-readable and
|
|
128
|
+
its rendered output has been deleted, not merely unreadable this run.
|
|
129
|
+
- **edit-unverified** — a surface the CLI can't read (Figma); checked live by the
|
|
130
|
+
Figma-connected skill instead.
|
|
131
|
+
|
|
132
|
+
Reconciliation is **per item, reviewable**: for each drift the user chooses
|
|
133
|
+
**re-render** (canonical wins) or **pull-back** (fold the surface edit into the
|
|
134
|
+
record), landed as a PR. **Brownfield first run is an adoption**, not a re-render:
|
|
135
|
+
existing surface content is claimed into the record as `provenance: imported` and
|
|
136
|
+
fingerprints are stamped, rather than treated as `edited` drift.
|
|
@@ -11,11 +11,11 @@ what changed. Gating decisions are made by reading this file: if a prerequisite
|
|
|
11
11
|
field is unset, the skill **offers** to run the prerequisite skill rather than
|
|
12
12
|
bailing or running silently.
|
|
13
13
|
|
|
14
|
-
## Schema (schemaVersion
|
|
14
|
+
## Schema (schemaVersion 5)
|
|
15
15
|
|
|
16
16
|
```json
|
|
17
17
|
{
|
|
18
|
-
"schemaVersion":
|
|
18
|
+
"schemaVersion": 5,
|
|
19
19
|
"user": {
|
|
20
20
|
"codingLevel": "new"
|
|
21
21
|
},
|
|
@@ -87,7 +87,8 @@ bailing or running silently.
|
|
|
87
87
|
"ranAt": null,
|
|
88
88
|
"codeSurface": null,
|
|
89
89
|
"figmaInventory": null,
|
|
90
|
-
"percentSemantic": null
|
|
90
|
+
"percentSemantic": null,
|
|
91
|
+
"docSurface": null
|
|
91
92
|
},
|
|
92
93
|
"tokenCrosswalk": {
|
|
93
94
|
"path": null,
|
|
@@ -249,6 +250,17 @@ bailing or running silently.
|
|
|
249
250
|
the manifest and the artboard never disagree. Re-running a component refreshes
|
|
250
251
|
its `updatedAt`. Keep `built` (names) as the source of truth for "exists";
|
|
251
252
|
`meta` is supplementary doc metadata.
|
|
253
|
+
- `meta[name].doc` — documentation pointer + per-surface fingerprints for the
|
|
254
|
+
component (v1: components only). **Pointers and hashes, never content** — the
|
|
255
|
+
content lives in `design-system/docs/components/<name>.doc.json`. Shape:
|
|
256
|
+
`{ path, fingerprint, surfaces: { <surfaceName>: { src, render, file? } } }`,
|
|
257
|
+
where `fingerprint` is the canonical fingerprint at last render, `src` is the
|
|
258
|
+
canonical fingerprint a surface was rendered from (detects stale), `render` is a
|
|
259
|
+
hash of the surface's rendered content (detects edits, for re-readable surfaces),
|
|
260
|
+
and `file` is the repo-relative path of a code surface. Written by
|
|
261
|
+
`component-builder` (Figma + card surfaces) and `storybook-chromatic-builder`
|
|
262
|
+
(code surfaces); read by the `docs:check` gate. See
|
|
263
|
+
`${CLAUDE_PLUGIN_ROOT}/references/component-doc-schema.md`.
|
|
252
264
|
- `instanceSwapUpgradePending` — array of component names whose icon/component
|
|
253
265
|
slots were built with the **toggle + manual-swap fallback** because the
|
|
254
266
|
library wasn't published yet, so the typed `INSTANCE_SWAP` dropdown is still
|
|
@@ -292,6 +304,11 @@ system so the retrofit can be right-sized.
|
|
|
292
304
|
semantic. The single number that decides rename+cleanup vs. rewrite — see
|
|
293
305
|
`${CLAUDE_PLUGIN_ROOT}/references/brownfield-retrofit.md` (safe sequence, `audit`
|
|
294
306
|
phase) for how it's used.
|
|
307
|
+
- `docSurface` — object sizing the documentation debt for a brownfield retrofit,
|
|
308
|
+
from verified per-component reads: e.g. `{ "documented": 12, "undocumented": 34,
|
|
309
|
+
"sources": { "codeJsdoc": 8, "mdx": 4, "figmaDescription": 6, "readme": 3 } }`.
|
|
310
|
+
`null` until the audit's documentation-sizing pass runs. Counts come from real
|
|
311
|
+
reads, never assumptions (same discipline as `codeSurface` / `figmaInventory`).
|
|
295
312
|
|
|
296
313
|
### `tokenCrosswalk`
|
|
297
314
|
Populated by the `token-crosswalk-builder` skill. Points at the backbone artifact
|
|
@@ -307,8 +324,9 @@ that maps new token ↔ old Figma token ↔ code identifier.
|
|
|
307
324
|
Populated by the `retrofit-planner` orchestrator. Tracks where a multi-phase
|
|
308
325
|
retrofit stands so a later session can resume.
|
|
309
326
|
- `phase` — one of `"audit"`, `"refine"`, `"rebind"`, `"sync"`, `"baseline"`,
|
|
310
|
-
`"code"`, `"cleanup"`, `"done"`, or `null` (no retrofit in progress).
|
|
311
|
-
in that order;
|
|
327
|
+
`"code"`, `"docs"`, `"cleanup"`, `"done"`, or `null` (no retrofit in progress).
|
|
328
|
+
Phases run in that order; the `docs` phase adopts existing documentation (see
|
|
329
|
+
`retrofit-planner`). See the safe sequence in
|
|
312
330
|
`${CLAUDE_PLUGIN_ROOT}/references/brownfield-retrofit.md`.
|
|
313
331
|
- `startedAt` / `completedAt` — ISO timestamps bounding the retrofit.
|
|
314
332
|
- `journalScaffolded` — whether the `docs/design-system/` decision journal has been
|
|
@@ -339,3 +357,7 @@ retrofit stands so a later session can resume.
|
|
|
339
357
|
6. **`workspace.origin` is immutable after intake.** Written once by
|
|
340
358
|
`figma-environment-setup` Step 0 and must not be overwritten by any downstream
|
|
341
359
|
skill. Skills read it to adapt behavior — they do not modify it.
|
|
360
|
+
|
|
361
|
+
**v4 → v5 migration:** add `audit.docSurface` (default `null`) and the `docs`
|
|
362
|
+
retrofit phase; `components.meta[name].doc` is added lazily per component as docs
|
|
363
|
+
are authored. Bump `schemaVersion` to `5`. No existing field changes.
|
package/scripts/README.md
CHANGED
|
@@ -12,6 +12,8 @@ tested here; copied verbatim by `token-crosswalk-builder` into the user's
|
|
|
12
12
|
| `guard-token-removal.mjs` | Grep `.ts/.tsx` (minus generated + tests) for about-to-be-deleted symbols; blocks cleanup until zero references remain. | run during the cleanup phase |
|
|
13
13
|
| `lib/crosswalk.mjs` | Shared loader + structural validation for `crosswalk.json` (used by the validator and reverse-index). | copied alongside |
|
|
14
14
|
| `crosswalk.schema.json` | The finalized JSON Schema for `crosswalk.json` (contract + editor support). | copied beside `crosswalk.json` |
|
|
15
|
+
| `build-docs-digest.mjs` | Aggregate every `design-system/docs/components/*.doc.json` into `design-system/docs/index.json` + `llms.txt` for AI/human consumers. | `docs:digest` |
|
|
16
|
+
| `docs-check.mjs` | Drift gate — verifies each component's doc surfaces still match its canonical record (via `lib/doc-record.mjs` fingerprints). Exits 1 on drift. | `docs:check` |
|
|
15
17
|
|
|
16
18
|
The crosswalk contract is documented in
|
|
17
19
|
`${CLAUDE_PLUGIN_ROOT}/references/crosswalk-schema.md`.
|
|
@@ -76,3 +78,12 @@ rewriting `${CLAUDE_PLUGIN_ROOT}` → `.throughline`:
|
|
|
76
78
|
npx @radicool/throughline init --target=cursor|codex|generic
|
|
77
79
|
|
|
78
80
|
See `scripts/install.mjs` (pure core + CLI + `install.test.mjs`).
|
|
81
|
+
|
|
82
|
+
## Documentation scripts
|
|
83
|
+
|
|
84
|
+
`docs:digest` and `docs:check` operate on the folder-resident documentation store
|
|
85
|
+
at `design-system/docs/`. Both share `lib/doc-record.mjs` (record loading +
|
|
86
|
+
fingerprinting). `docs:check` re-reads repo surfaces (Storybook MDX); Figma
|
|
87
|
+
surfaces are marked `edit-unverified` and are checked live by the Figma-connected
|
|
88
|
+
skills. See `${CLAUDE_PLUGIN_ROOT}/references/component-doc-schema.md` for the
|
|
89
|
+
record schema and fingerprint contract.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// docs:digest — aggregates every component doc record into two AI-facing
|
|
2
|
+
// artifacts: index.json (machine map) and llms.txt (narrative index).
|
|
3
|
+
// Zero dependencies.
|
|
4
|
+
//
|
|
5
|
+
// Usage: node build-docs-digest.mjs [--root <dir>]
|
|
6
|
+
import { readdirSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { parseArgs } from 'node:util';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
10
|
+
import { loadRecord } from './lib/doc-record.mjs';
|
|
11
|
+
|
|
12
|
+
const DOCS_DIR = join('design-system', 'docs');
|
|
13
|
+
const COMPONENTS_DIR = join(DOCS_DIR, 'components');
|
|
14
|
+
|
|
15
|
+
export function buildIndex(records) {
|
|
16
|
+
return {
|
|
17
|
+
generatedFrom: 'design-system/docs/components/*.doc.json',
|
|
18
|
+
components: records.map((r) => ({
|
|
19
|
+
name: r.name,
|
|
20
|
+
summary: r.summary ?? '',
|
|
21
|
+
description: r.description ?? '',
|
|
22
|
+
whenToUse: r.whenToUse ?? [],
|
|
23
|
+
whenNotToUse: r.whenNotToUse ?? [],
|
|
24
|
+
variants: r.variants ?? {},
|
|
25
|
+
states: r.states ?? {},
|
|
26
|
+
dos: r.dos ?? [],
|
|
27
|
+
donts: r.donts ?? [],
|
|
28
|
+
accessibility: r.accessibility ?? {},
|
|
29
|
+
tokensUsed: r.tokensUsed ?? [],
|
|
30
|
+
status: r.status ?? 'draft',
|
|
31
|
+
})),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function buildLlmsTxt(records) {
|
|
36
|
+
const lines = ['# Design system — component usage guide', ''];
|
|
37
|
+
lines.push('Generated documentation for AI and human consumers. One section per component.', '');
|
|
38
|
+
for (const r of records) {
|
|
39
|
+
lines.push(`## ${r.name}`, '');
|
|
40
|
+
if (r.summary) lines.push(r.summary, '');
|
|
41
|
+
if (r.description) lines.push(r.description, '');
|
|
42
|
+
if ((r.whenToUse ?? []).length) lines.push('**When to use:** ' + r.whenToUse.join('; '));
|
|
43
|
+
if ((r.whenNotToUse ?? []).length) lines.push('**When not to use:** ' + r.whenNotToUse.join('; '));
|
|
44
|
+
if ((r.dos ?? []).length) lines.push('**Do:** ' + r.dos.join('; '));
|
|
45
|
+
if ((r.donts ?? []).length) lines.push("**Don't:** " + r.donts.join('; '));
|
|
46
|
+
if ((r.tokensUsed ?? []).length) lines.push('**Tokens:** ' + r.tokensUsed.join(', '));
|
|
47
|
+
lines.push('');
|
|
48
|
+
}
|
|
49
|
+
return lines.join('\n');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function loadAllRecords(root) {
|
|
53
|
+
const dir = join(root, COMPONENTS_DIR);
|
|
54
|
+
if (!existsSync(dir)) return [];
|
|
55
|
+
return readdirSync(dir)
|
|
56
|
+
.filter((f) => f.endsWith('.doc.json'))
|
|
57
|
+
.sort()
|
|
58
|
+
.map((f) => loadRecord(join(dir, f)));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function main() {
|
|
62
|
+
const { values } = parseArgs({ options: { root: { type: 'string', default: '.' } } });
|
|
63
|
+
const root = values.root;
|
|
64
|
+
const records = loadAllRecords(root);
|
|
65
|
+
const outDir = join(root, DOCS_DIR);
|
|
66
|
+
if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
|
|
67
|
+
writeFileSync(join(outDir, 'index.json'), JSON.stringify(buildIndex(records), null, 2) + '\n');
|
|
68
|
+
writeFileSync(join(outDir, 'llms.txt'), buildLlmsTxt(records));
|
|
69
|
+
console.log(`✓ docs:digest — ${records.length} component(s) → design-system/docs/{index.json,llms.txt}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
73
|
+
main();
|
|
74
|
+
}
|