@radicool/throughline 0.12.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/LICENSE +21 -0
- package/README.md +214 -0
- package/adapters/codex/AGENTS.md +29 -0
- package/adapters/codex/codex-mcp.toml +4 -0
- package/adapters/codex/prompts/component-builder.md +272 -0
- package/adapters/codex/prompts/component-pipeline.md +81 -0
- package/adapters/codex/prompts/design-system-audit.md +122 -0
- package/adapters/codex/prompts/design-system-status.md +41 -0
- package/adapters/codex/prompts/figma-environment-setup.md +456 -0
- package/adapters/codex/prompts/icon-system-builder.md +268 -0
- package/adapters/codex/prompts/new-component.md +13 -0
- package/adapters/codex/prompts/repository-builder.md +163 -0
- package/adapters/codex/prompts/retrofit-planner.md +136 -0
- package/adapters/codex/prompts/start.md +22 -0
- package/adapters/codex/prompts/storybook-chromatic-builder.md +261 -0
- package/adapters/codex/prompts/sync-figma-tokens.md +22 -0
- package/adapters/codex/prompts/token-builder.md +361 -0
- package/adapters/codex/prompts/token-crosswalk-builder.md +120 -0
- package/adapters/codex/prompts/token-sheet-builder.md +162 -0
- package/adapters/codex/prompts/token-sync-layer.md +232 -0
- package/adapters/cursor/.cursor/commands/design-system-status.md +41 -0
- package/adapters/cursor/.cursor/commands/new-component.md +13 -0
- package/adapters/cursor/.cursor/commands/start.md +22 -0
- package/adapters/cursor/.cursor/commands/sync-figma-tokens.md +22 -0
- package/adapters/cursor/.cursor/mcp.json +14 -0
- package/adapters/cursor/.cursor/rules/component-builder.mdc +276 -0
- package/adapters/cursor/.cursor/rules/component-pipeline.mdc +85 -0
- package/adapters/cursor/.cursor/rules/design-system-audit.mdc +126 -0
- package/adapters/cursor/.cursor/rules/figma-environment-setup.mdc +460 -0
- package/adapters/cursor/.cursor/rules/icon-system-builder.mdc +272 -0
- package/adapters/cursor/.cursor/rules/repository-builder.mdc +167 -0
- package/adapters/cursor/.cursor/rules/retrofit-planner.mdc +140 -0
- package/adapters/cursor/.cursor/rules/storybook-chromatic-builder.mdc +265 -0
- package/adapters/cursor/.cursor/rules/token-builder.mdc +365 -0
- package/adapters/cursor/.cursor/rules/token-crosswalk-builder.mdc +124 -0
- package/adapters/cursor/.cursor/rules/token-sheet-builder.mdc +166 -0
- package/adapters/cursor/.cursor/rules/token-sync-layer.mdc +236 -0
- package/adapters/generic/AGENTS.md +46 -0
- package/adapters/generic/commands/design-system-status.md +41 -0
- package/adapters/generic/commands/new-component.md +13 -0
- package/adapters/generic/commands/start.md +22 -0
- package/adapters/generic/commands/sync-figma-tokens.md +22 -0
- package/adapters/generic/skills/component-builder/SKILL.md +272 -0
- package/adapters/generic/skills/component-pipeline/SKILL.md +81 -0
- package/adapters/generic/skills/design-system-audit/SKILL.md +122 -0
- package/adapters/generic/skills/figma-environment-setup/SKILL.md +456 -0
- package/adapters/generic/skills/icon-system-builder/SKILL.md +268 -0
- package/adapters/generic/skills/repository-builder/SKILL.md +163 -0
- package/adapters/generic/skills/retrofit-planner/SKILL.md +136 -0
- package/adapters/generic/skills/storybook-chromatic-builder/SKILL.md +261 -0
- package/adapters/generic/skills/token-builder/SKILL.md +361 -0
- package/adapters/generic/skills/token-crosswalk-builder/SKILL.md +120 -0
- package/adapters/generic/skills/token-sheet-builder/SKILL.md +162 -0
- package/adapters/generic/skills/token-sync-layer/SKILL.md +232 -0
- package/package.json +29 -0
- package/references/brainstorm-before-build.md +139 -0
- package/references/brownfield-retrofit.md +93 -0
- package/references/coding-level.md +70 -0
- package/references/crosswalk-schema.md +113 -0
- package/references/figma-component-standards.md +532 -0
- package/references/figma-publishing.md +125 -0
- package/references/figma-scripting.md +278 -0
- package/references/manifest-schema.md +341 -0
- package/references/scaling-up-handoff.md +61 -0
- package/references/sync-adapters.md +141 -0
- package/scripts/README.md +78 -0
- package/scripts/adapters/emit-codex.mjs +58 -0
- package/scripts/adapters/emit-cursor.mjs +26 -0
- package/scripts/adapters/emit-generic.mjs +41 -0
- package/scripts/adapters/generate.mjs +73 -0
- package/scripts/adapters/read-sources.mjs +54 -0
- package/scripts/adapters/translate.mjs +51 -0
- package/scripts/build-reverse-index.mjs +56 -0
- package/scripts/crosswalk.schema.json +33 -0
- package/scripts/grep-color-usage.mjs +143 -0
- package/scripts/guard-token-removal.mjs +95 -0
- package/scripts/install.mjs +143 -0
- package/scripts/lib/crosswalk.mjs +91 -0
- package/scripts/validate-crosswalk.mjs +96 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Token crosswalk builder
|
|
2
|
+
|
|
3
|
+
Builds the **backbone artifact** of a brownfield retrofit: `crosswalk.json`, a
|
|
4
|
+
persistent three-way map of **new token ↔ old Figma variable ↔ old code identifier**.
|
|
5
|
+
It drives the code retrofit and the `tokens:validate` CI gate. This skill also
|
|
6
|
+
installs the canonical scripts into the user's repo and wires the gate.
|
|
7
|
+
|
|
8
|
+
This is a brownfield skill. **Before doing anything, read**
|
|
9
|
+
`.throughline/references/brownfield-retrofit.md` (read discipline, the 7
|
|
10
|
+
guardrails, the safe sequence) and
|
|
11
|
+
`.throughline/references/crosswalk-schema.md` (the exact contract). Greenfield
|
|
12
|
+
builds don't need this skill.
|
|
13
|
+
|
|
14
|
+
## Calibrate
|
|
15
|
+
|
|
16
|
+
Read `user.codingLevel` (`.throughline/references/coding-level.md`) and scale
|
|
17
|
+
explanation accordingly. The crosswalk involves JSON, npm scripts, and a CI gate — for
|
|
18
|
+
`new` users explain each plainly the first time; for `comfortable` users be terse.
|
|
19
|
+
Actions are identical across levels.
|
|
20
|
+
|
|
21
|
+
## Prerequisites (offer to run them, don't bail)
|
|
22
|
+
|
|
23
|
+
Read the manifest. This skill needs:
|
|
24
|
+
|
|
25
|
+
1. **A DTCG token source** at `packages/tokens/dtcg/tokens.json` — the validator
|
|
26
|
+
resolves against it. If it doesn't exist, offer to run `token-sync-layer` first
|
|
27
|
+
(it emits this file). Apply the read discipline: confirm the file exists by
|
|
28
|
+
reading it, never assume it's absent without looking.
|
|
29
|
+
2. **The audit inputs** — ideally the `audit` manifest section (code surface,
|
|
30
|
+
Figma inventory, `percentSemantic`) populated by `design-system-audit`. If
|
|
31
|
+
`audit` is present, use it to seed the rows. If it is `null` (audit hasn't run —
|
|
32
|
+
it lands in Plan 3), don't block: ask the user for the old→new mapping inputs
|
|
33
|
+
directly (which old Figma variables and code symbols map to which new tokens),
|
|
34
|
+
and proceed. Note plainly that running `design-system-audit` first would
|
|
35
|
+
pre-fill this.
|
|
36
|
+
3. **A repo at `local-git` or `github`** (`workspace.stage`) so the installed
|
|
37
|
+
scripts and `package.json` changes land as a reviewable diff/PR. If still
|
|
38
|
+
`folder`, offer `repository-builder`.
|
|
39
|
+
|
|
40
|
+
## Step 1 — Build `crosswalk.json`
|
|
41
|
+
|
|
42
|
+
Write `packages/tokens/crosswalk.json` per the contract in
|
|
43
|
+
`.throughline/references/crosswalk-schema.md`. One row per new token:
|
|
44
|
+
|
|
45
|
+
- `newToken` — the DTCG dot-path exactly as it appears in
|
|
46
|
+
`packages/tokens/dtcg/tokens.json` (e.g. `color.text.primary`).
|
|
47
|
+
- `newValue` — the **resolved** leaf value (follow `{…}` aliases to the literal).
|
|
48
|
+
- `tier` — `primitive` or `semantic`.
|
|
49
|
+
- `figmaOld` — the old Figma variable name/path, or `null` if newly added.
|
|
50
|
+
- `codeTokens[]` — the old code identifiers this token replaces (`$primary-red`,
|
|
51
|
+
`bg-primary-red`, `Colors.primaryRed`, `--primary-red`). May be `[]`.
|
|
52
|
+
- `status` — `aligned | renamed | drift-fix | added | mapped-nearest`. Assign by:
|
|
53
|
+
same name & value → `aligned`; same value, new name → `renamed`; value
|
|
54
|
+
intentionally changed → `drift-fix`; brand-new token → `added`; no exact old
|
|
55
|
+
equivalent, mapped to nearest → `mapped-nearest`.
|
|
56
|
+
- `recommendedSemantic` — optional semantic target for a raw/primitive usage, else
|
|
57
|
+
`null`.
|
|
58
|
+
|
|
59
|
+
Do not guess values. Every `newValue` comes from a read of the DTCG source, not an
|
|
60
|
+
assumption (read discipline).
|
|
61
|
+
|
|
62
|
+
## Step 2 — Install the vetted scripts + wire `tokens:validate`
|
|
63
|
+
|
|
64
|
+
Copy these from `.throughline/scripts/` into the user's repo **verbatim**
|
|
65
|
+
(they are zero-dependency and version with the user's repo so their CI can run them):
|
|
66
|
+
|
|
67
|
+
- `lib/crosswalk.mjs` → `packages/tokens/scripts/lib/crosswalk.mjs`
|
|
68
|
+
- `validate-crosswalk.mjs` → `packages/tokens/scripts/validate-crosswalk.mjs`
|
|
69
|
+
- `build-reverse-index.mjs` → `packages/tokens/scripts/build-reverse-index.mjs`
|
|
70
|
+
- `guard-token-removal.mjs` → `packages/tokens/scripts/guard-token-removal.mjs`
|
|
71
|
+
- `crosswalk.schema.json` → `packages/tokens/crosswalk.schema.json` (beside
|
|
72
|
+
`crosswalk.json`, so the `$schema` pointer resolves)
|
|
73
|
+
|
|
74
|
+
Then add to `packages/tokens/package.json` `scripts` (don't clobber existing keys):
|
|
75
|
+
|
|
76
|
+
```jsonc
|
|
77
|
+
"tokens:validate": "node scripts/validate-crosswalk.mjs --crosswalk crosswalk.json --tokens dtcg/tokens.json",
|
|
78
|
+
"tokens:reverse-index": "node scripts/build-reverse-index.mjs --crosswalk crosswalk.json --out crosswalk.reverse.json"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
See `.throughline/scripts/README.md` for the full install contract.
|
|
82
|
+
|
|
83
|
+
## Step 3 — Run the gate (must pass N/N)
|
|
84
|
+
|
|
85
|
+
Run `npm run tokens:validate` (from `packages/tokens/`). It must report **N/N** —
|
|
86
|
+
every row's resolved value matches its `newValue`. If it reports mismatches or
|
|
87
|
+
missing tokens, fix the crosswalk (or the token source) and re-run. **Never proceed
|
|
88
|
+
on a red validator and never edit a generated token file to make it pass** — change
|
|
89
|
+
the source in Figma and re-sync (guardrail 7). The validator is the source of truth
|
|
90
|
+
that the crosswalk and the real tokens agree.
|
|
91
|
+
|
|
92
|
+
## Step 4 — Generate the reverse index
|
|
93
|
+
|
|
94
|
+
Run `npm run tokens:reverse-index`. This writes `crosswalk.reverse.json`
|
|
95
|
+
(`codeToken → newToken`), which the code-retrofit phase uses to semi-automate the
|
|
96
|
+
SCSS/Tailwind swaps. If it reports conflicts (one old symbol mapping to two new
|
|
97
|
+
tokens), resolve them in the crosswalk before relying on the index.
|
|
98
|
+
|
|
99
|
+
## Step 5 — Update the manifest
|
|
100
|
+
|
|
101
|
+
Write the `tokenCrosswalk` section **only** (this skill owns it; never write another
|
|
102
|
+
skill's fields):
|
|
103
|
+
|
|
104
|
+
- `path` — the actual path written (`"packages/tokens/crosswalk.json"`).
|
|
105
|
+
- `statusCounts` — copy the camelCase object the validator printed
|
|
106
|
+
(`{ aligned, renamed, driftFix, added, mappedNearest }`).
|
|
107
|
+
- `validatorPassing` — `true` once `tokens:validate` passes N/N.
|
|
108
|
+
|
|
109
|
+
Append `token-crosswalk-builder` to `completedSkills`.
|
|
110
|
+
|
|
111
|
+
## What this skill must NOT do
|
|
112
|
+
|
|
113
|
+
- Never delete old tokens or outputs here — that's the cleanup phase, gated by the
|
|
114
|
+
token-removal guard returning zero references (safe sequence, guardrail 4).
|
|
115
|
+
- Never edit generated token files to make the validator pass — fix the source.
|
|
116
|
+
- Never write another skill's manifest fields (e.g. `tokens.intakeMode` is owned by
|
|
117
|
+
`design-system-audit`).
|
|
118
|
+
- Never proceed past a red `tokens:validate`.
|
|
119
|
+
- Never assert a prerequisite is absent without a verified read (read discipline).
|
|
120
|
+
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Token sheet builder
|
|
2
|
+
|
|
3
|
+
Creates a polished visual reference of the design system's tokens and styles on
|
|
4
|
+
a dedicated Figma page called **Foundations**. This is the artifact designers
|
|
5
|
+
and stakeholders actually look at — so it must be beautiful and on-brand, not a
|
|
6
|
+
utilitarian dump of swatches.
|
|
7
|
+
|
|
8
|
+
## Live vs regenerated — set expectations clearly
|
|
9
|
+
|
|
10
|
+
Bind to variables wherever Figma allows so the sheet **live-updates** for those
|
|
11
|
+
properties: a color swatch bound to `gray/50` (in `_Color/Primitive`) re-colors
|
|
12
|
+
automatically when that primitive changes; spacing/radius examples bound to the variables resize
|
|
13
|
+
automatically. But labels, hex/value text, token names, descriptions, and the
|
|
14
|
+
set of tokens shown (adding/removing tokens) are **snapshots** — they don't
|
|
15
|
+
auto-update. So tell the user plainly: "The swatches update live when you change
|
|
16
|
+
a value, but if you rename tokens, change descriptions, or add/remove tokens,
|
|
17
|
+
re-run this skill to refresh the page." The sheet is regenerable; re-running
|
|
18
|
+
refreshes the snapshot parts.
|
|
19
|
+
|
|
20
|
+
(Tie-in: when the sync skill runs after token changes, refreshing Foundations is
|
|
21
|
+
a sensible companion step — the sheet and the synced code both reflect Figma.)
|
|
22
|
+
|
|
23
|
+
## Prerequisites
|
|
24
|
+
|
|
25
|
+
Needs tokens in Figma (`tokens.semanticBuilt` true) and a live Figma connection.
|
|
26
|
+
If tokens don't exist, offer to run `token-builder`. If Figma isn't connected,
|
|
27
|
+
offer `figma-environment-setup`. Use the mechanism in `figma.mechanism`.
|
|
28
|
+
|
|
29
|
+
**Before scripting any `figma_execute`, read
|
|
30
|
+
`.throughline/references/figma-scripting.md`.** This sheet builds large
|
|
31
|
+
swatch/type grids — exactly the layouts that hit the two worst traps: the
|
|
32
|
+
`resize()` axis-lock (collapses a color grid or type-meta column to ~10px) and the
|
|
33
|
+
single-call `layoutWrap = "WRAP"` timeout on big grids (build manual rows or split
|
|
34
|
+
across calls instead). Also run the single-bridge-instance preflight before writing.
|
|
35
|
+
|
|
36
|
+
## Step 1 — Brainstorm the layout (lightly)
|
|
37
|
+
|
|
38
|
+
Run `.throughline/references/brainstorm-before-build.md`, but keep it light — this is a
|
|
39
|
+
lower-stakes, easily-regenerated artifact. Settle: which collections get their
|
|
40
|
+
own section, how much per-token detail to show (just swatches, or swatches +
|
|
41
|
+
names + values + descriptions), and any brand styling direction for the page
|
|
42
|
+
itself (the page should feel like the brand it documents). Default to a clean,
|
|
43
|
+
sectioned layout, one section per collection/style group: Color (ramps as rows
|
|
44
|
+
of swatches), Typography (the type scale rendered in the actual text styles),
|
|
45
|
+
Spacing (visual bars), Radius (sample shapes), Border width (sample rules at
|
|
46
|
+
each width), Elevation (sample cards with the effect styles).
|
|
47
|
+
|
|
48
|
+
**Group semantic colors by category — never one flat list.** Primitive colors are
|
|
49
|
+
shown organized into ramps (gray, blue, red…); semantic colors get the **same
|
|
50
|
+
categorical organization**, grouped by their role family — **surface/background,
|
|
51
|
+
text, border, alert/feedback (success/warning/error/info), action/interactive**,
|
|
52
|
+
etc. — each family its own labeled sub-group of swatches. Do not dump all semantic
|
|
53
|
+
tokens into a single undifferentiated block; mirror the primitive ramp structure so
|
|
54
|
+
the two tiers read consistently. Derive the families from the token names
|
|
55
|
+
themselves (the `bg/*`, `text/*`, `border/*`, `alert/*` … prefixes) so the grouping
|
|
56
|
+
matches the actual collection.
|
|
57
|
+
|
|
58
|
+
## Step 2 — Build the Foundations page
|
|
59
|
+
|
|
60
|
+
Create a Figma page named **Foundations** (if one exists, ask whether to refresh
|
|
61
|
+
it or version it). Lay out sections for each collection/style group, using the
|
|
62
|
+
active write mechanism (scripted where possible for efficiency). Make swatches
|
|
63
|
+
and examples **live-bound to the variables/styles** they document wherever Figma
|
|
64
|
+
supports binding. Render the type scale using the actual text styles created by
|
|
65
|
+
token-builder, and elevation samples using the actual effect styles — so the
|
|
66
|
+
page consumes the system rather than re-describing it.
|
|
67
|
+
|
|
68
|
+
Prioritize visual quality: generous spacing, clear section headers, consistent
|
|
69
|
+
swatch sizing, the brand's own type and color applied to the page chrome. This
|
|
70
|
+
is a showcase.
|
|
71
|
+
|
|
72
|
+
**Bind the page chrome too — not just the swatches.** The sheet must fully
|
|
73
|
+
consume the system, including its own titles, labels, descriptions, and section
|
|
74
|
+
backgrounds. Concretely:
|
|
75
|
+
|
|
76
|
+
- **Every text node gets a text style.** Section titles, group labels, swatch
|
|
77
|
+
numbers, hex/value text, captions — map each to an existing text style
|
|
78
|
+
(e.g. section headers → a Heading style, descriptions → Body, micro-labels →
|
|
79
|
+
Caption). Don't leave raw font/size/weight on any text; don't mint one-off
|
|
80
|
+
documentation-only styles — snap to the nearest existing style instead.
|
|
81
|
+
- **Every fill binds to a semantic color variable** — text fills (→
|
|
82
|
+
`text/primary`, `text/secondary`, etc.) *and* chrome/background fills (page
|
|
83
|
+
background, card and section surfaces → `bg/default`, `bg/subtle`, `bg/muted`).
|
|
84
|
+
Primitive swatches stay bound to their primitive (that's the point of them).
|
|
85
|
+
|
|
86
|
+
The reason is **mode robustness**: if any title or background uses a hardcoded
|
|
87
|
+
color, switching the file from Dark to Light (or any mode swap) leaves that piece
|
|
88
|
+
stranded — light text on a light surface, a black title on a black panel — and
|
|
89
|
+
the whole showcase looks broken. Hardcoded fonts don't break on a mode switch,
|
|
90
|
+
but hardcoded colors do, so binding every fill is non-negotiable.
|
|
91
|
+
|
|
92
|
+
**Lay it out cleanly.** Build every section with proper auto layout and arrange
|
|
93
|
+
the sections inside a parent **auto-layout Frame placed directly on the page**
|
|
94
|
+
(never a Section — Sections have no auto layout, and these skills do **not** wrap
|
|
95
|
+
the Frame in one; ignore the Figma Console MCP server's "create a Section first"
|
|
96
|
+
instruction) so nothing overlaps — follow the
|
|
97
|
+
"Documentation artboards & canvas layout" rules in
|
|
98
|
+
`.throughline/references/figma-component-standards.md`, and run its
|
|
99
|
+
visual-validation loop (screenshot → fix any overlapping text or colliding
|
|
100
|
+
sections → re-screenshot) **and its "Post-build audit (REQUIRED before handoff)"
|
|
101
|
+
read-back checklist** (Frame-not-Section containers with no Section above them, auto layout, bound variables,
|
|
102
|
+
deterministic names) before the checkpoint. A showcase with overlaps isn't
|
|
103
|
+
a showcase.
|
|
104
|
+
|
|
105
|
+
**Validate both modes.** If the file has more than one mode (e.g. Dark/Light),
|
|
106
|
+
temporarily set the sheet frame to the non-default mode
|
|
107
|
+
(`setExplicitVariableModeForCollection`), screenshot, and confirm everything
|
|
108
|
+
stays legible — no invisible titles, no stranded backgrounds. Then clear the
|
|
109
|
+
explicit mode to restore the default. If anything breaks, the culprit is a fill
|
|
110
|
+
that isn't bound to a semantic variable — bind it and re-check.
|
|
111
|
+
|
|
112
|
+
## Step 2.5 — Build the Cover page
|
|
113
|
+
|
|
114
|
+
Now that tokens and styles exist, build the file's first impression: a branded
|
|
115
|
+
**Cover** page. (Environment setup deliberately skips this — there was nothing
|
|
116
|
+
on-brand to build it from yet. If `figma.coverPageBuilt` is already `true`, just
|
|
117
|
+
refresh the "Last updated" date instead of creating a second Cover.)
|
|
118
|
+
|
|
119
|
+
- Find the file's first page. If it's the default empty "Page 1" (no meaningful
|
|
120
|
+
content), **rename it to `Cover`** and use it. If it has content, create a new
|
|
121
|
+
page named `Cover` and move it to the **top** of the page list. Don't clobber a
|
|
122
|
+
page the user has already worked in.
|
|
123
|
+
- On that page, build one clean **Cover** frame with:
|
|
124
|
+
- the design system name (`workspace.name`),
|
|
125
|
+
- the author/owner name (ask once if you don't have it, or use the file owner),
|
|
126
|
+
- "Last updated" with the current date,
|
|
127
|
+
- a tasteful branded graphic — a bold wordmark, a few shapes, generous spacing,
|
|
128
|
+
not an elaborate illustration.
|
|
129
|
+
- **Bind everything to the system you just documented** — text to text styles,
|
|
130
|
+
fills to `Color/Semantic` variables (page background → `bg/default`, etc.) — so
|
|
131
|
+
the Cover survives a mode switch exactly like the Foundations page. This is the
|
|
132
|
+
whole reason it's built here and not at setup. Validate both modes the same way.
|
|
133
|
+
- Use proper **vertical auto layout** and place content inside the frame (no
|
|
134
|
+
floating, no overlapping text) per
|
|
135
|
+
`.throughline/references/figma-component-standards.md`.
|
|
136
|
+
- **Setting it as the file thumbnail is a manual step** — the plugin API can't do
|
|
137
|
+
it. Tell the user plainly: "To make this the file's cover image, right-click the
|
|
138
|
+
Cover frame and choose **Set as thumbnail**." Offer it; don't block on it.
|
|
139
|
+
- Record `figma.coverPageBuilt` = `true`.
|
|
140
|
+
|
|
141
|
+
## Step 3 — Checkpoint
|
|
142
|
+
|
|
143
|
+
Show the user the Foundations page and the Cover page. Sequential review (this is
|
|
144
|
+
a Figma-authoring skill — no subagents). Iterate on layout/styling if they want
|
|
145
|
+
changes. Then update the manifest: `sheets.built` = `true`, append
|
|
146
|
+
`token-sheet-builder` to `completedSkills`.
|
|
147
|
+
|
|
148
|
+
## Step 4 — Hand off
|
|
149
|
+
|
|
150
|
+
Note that the page refreshes via re-running this skill, and that it pairs well
|
|
151
|
+
with a token sync. Offer natural next steps (icons, components). Don't auto-run.
|
|
152
|
+
|
|
153
|
+
## Notes
|
|
154
|
+
|
|
155
|
+
- Beautiful and on-brand is a requirement, not a nice-to-have.
|
|
156
|
+
- Live-bind where possible; be honest about what needs a regenerate.
|
|
157
|
+
- The sheet must consume its own system: every text node on a text style, every
|
|
158
|
+
fill on a semantic color variable, so it survives a mode switch.
|
|
159
|
+
- Semantic colors are grouped by category (surface, text, border, alert/feedback,
|
|
160
|
+
action…), the same way primitives are organized into ramps — never one flat list.
|
|
161
|
+
- One dedicated page named **Foundations**.
|
|
162
|
+
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# Token sync layer
|
|
2
|
+
|
|
3
|
+
Compiles Figma variables into code. The pipeline is one-directional — Figma is
|
|
4
|
+
the source of truth for token values; code consumes generated artifacts that are
|
|
5
|
+
never hand-edited:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Figma variables → DTCG JSON → Style Dictionary → adapter output (per platform)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This skill both performs the sync and installs the reusable `/sync-figma-tokens`
|
|
12
|
+
command so the user can re-run it anytime tokens change.
|
|
13
|
+
|
|
14
|
+
## Calibrate
|
|
15
|
+
|
|
16
|
+
Read `user.codingLevel` (`.throughline/references/coding-level.md`) and scale explanation
|
|
17
|
+
accordingly. The pipeline involves several developer concepts (JSON, build
|
|
18
|
+
tools, PRs) — for `new` users, explain each plainly the first time; for
|
|
19
|
+
`comfortable` users, be terse. Actions are identical across levels.
|
|
20
|
+
|
|
21
|
+
## Prerequisites (offer to run them, don't bail)
|
|
22
|
+
|
|
23
|
+
Read the manifest. This skill needs two things:
|
|
24
|
+
|
|
25
|
+
1. **Tokens in Figma** — `tokens.semanticBuilt` should be true. If no tokens
|
|
26
|
+
exist, offer to run `token-builder` first.
|
|
27
|
+
2. **At least a local git repo** — `workspace.stage` must be `local-git` or
|
|
28
|
+
`github` (so the sync can land changes as a reviewable diff/PR). If it's still
|
|
29
|
+
`folder`, this is the soft-nudge seam: offer to run `repository-builder` to
|
|
30
|
+
add version history first. Explain plainly why ("the sync writes code files,
|
|
31
|
+
and git lets you review exactly what changed before keeping it"). If the user
|
|
32
|
+
is at `local-git` but not `github`, **strongly recommend** connecting a remote
|
|
33
|
+
(GitHub or similar) so the sync can land as a real reviewable PR — the cleanest
|
|
34
|
+
review model — but don't force it; a reviewed branch diff works at `local-git`.
|
|
35
|
+
|
|
36
|
+
A live Figma connection is also required — do a cheap liveness read; if it
|
|
37
|
+
fails, offer `figma-environment-setup`.
|
|
38
|
+
|
|
39
|
+
## Step 1 — Choose target platform(s)
|
|
40
|
+
|
|
41
|
+
Ask which platform(s) the user is building for. Read
|
|
42
|
+
`.throughline/references/sync-adapters.md` for the two-tier adapter model.
|
|
43
|
+
|
|
44
|
+
- **Curated (Tier 1):** `shadcn`, `tailwind`, `mui`, `vanilla-css`, `ios-swift`.
|
|
45
|
+
Vetted presets — high confidence.
|
|
46
|
+
- **Generated (Tier 2):** any other framework (Ant Design, Chakra, HeroUI,
|
|
47
|
+
Android/Kotlin, Flutter, etc.). The skill generates an adapter and verifies it
|
|
48
|
+
against a real component before trusting it.
|
|
49
|
+
|
|
50
|
+
**Always tell the user which tier they're on.** If they name a curated one, say
|
|
51
|
+
it'll be solid. If they name anything else, be honest: "That's not one I have a
|
|
52
|
+
vetted preset for — I'll generate an adapter from its conventions and we'll check
|
|
53
|
+
it against a real component before relying on it." Follow the Tier 2 protocol in
|
|
54
|
+
the reference, and offer to **save** a validated generated adapter to
|
|
55
|
+
`packages/tokens/adapters/` (record it in `sync.customAdapters`) so future syncs
|
|
56
|
+
reuse it.
|
|
57
|
+
|
|
58
|
+
A user can target several platforms at once. Record the choices in
|
|
59
|
+
`sync.platforms`. The adapter is what absorbs all framework-specific shaping —
|
|
60
|
+
which is why the Figma variables stayed framework-neutral.
|
|
61
|
+
|
|
62
|
+
Also set `project.uiFramework` if it's still null (this or component-builder sets
|
|
63
|
+
it at first relevance, whichever runs first; the other reads it). If
|
|
64
|
+
component-builder already set it, the chosen adapter(s) should be consistent with
|
|
65
|
+
it.
|
|
66
|
+
|
|
67
|
+
## Step 2 — Extract Figma variables to DTCG JSON
|
|
68
|
+
|
|
69
|
+
Using the active write mechanism (`figma.mechanism`, default Console MCP —
|
|
70
|
+
prefer its full-design-system extraction, which works on any Figma plan), read
|
|
71
|
+
**every variable collection** (there are now several per tier, e.g.
|
|
72
|
+
`_Color/Primitive`, `Spacing/Primitive`, `Color/Semantic`, …) and normalize them
|
|
73
|
+
into **DTCG-format JSON** (`$value`, `$type`, with semantic tokens expressed as
|
|
74
|
+
`{group.token}` references to primitives, not flattened literals).
|
|
75
|
+
|
|
76
|
+
Three rules for the multi-collection structure:
|
|
77
|
+
- **Iterate N collections**, not a fixed two. Don't assume one `Primitives` +
|
|
78
|
+
one `Semantic`.
|
|
79
|
+
- **Single-mode collections are non-themed** — a collection with one mode (e.g.
|
|
80
|
+
`Spacing/Semantic`) emits flat values, never a phantom `default` theme
|
|
81
|
+
alongside `:root`/`.dark`.
|
|
82
|
+
- **Primitives can be multi-mode** — `_Color/Primitive` carries a Brand axis, so
|
|
83
|
+
emit **brand themes from the primitive tier**, not just light/dark from
|
|
84
|
+
semantics. Preserve modes (light/dark/brand/device) as the DTCG structure the
|
|
85
|
+
adapters expect.
|
|
86
|
+
- **Name mapping:** collapse the tier/category prefix into clean token names —
|
|
87
|
+
`_Color/Primitive` + `gray/500` → `color.gray.500` (not
|
|
88
|
+
`color.primitive.gray.500`); `Color/Semantic` + `text/primary` →
|
|
89
|
+
`color.text.primary`. The category drives the top-level group; the tier (and
|
|
90
|
+
the `_`) is dropped from the emitted name.
|
|
91
|
+
- **Opacity: normalize 0–100 → 0–1 on extraction.** Opacity tokens are stored in
|
|
92
|
+
Figma on the **0–100 scale** (a quirk of how Figma binds variables to a node's
|
|
93
|
+
`opacity` field — see the opacity scale rule in `token-builder`). CSS `opacity`,
|
|
94
|
+
Tailwind opacity, and native alpha are all **0–1**, so when normalizing any
|
|
95
|
+
opacity token (the `Opacity/*` collections, or any FLOAT bound to opacity)
|
|
96
|
+
**divide its value by 100** before writing DTCG (`40` → `0.4`). Do this once at
|
|
97
|
+
extraction so every adapter emits a correct 0–1 value; an un-normalized `40`
|
|
98
|
+
lands as `opacity: 40` in CSS and renders the element fully opaque. token-builder
|
|
99
|
+
and token-sync-layer are a **matched pair** on this — neither is correct alone.
|
|
100
|
+
|
|
101
|
+
This DTCG JSON is the canonical intermediate. Write it to a known location in
|
|
102
|
+
`packages/tokens/` (e.g. `packages/tokens/dtcg/tokens.json`). Do not trust the
|
|
103
|
+
MCP's built-in CSS/Tailwind exporters for final output — route through Style
|
|
104
|
+
Dictionary so the adapter system governs the result.
|
|
105
|
+
|
|
106
|
+
### Brownfield transforms (learned the hard way)
|
|
107
|
+
|
|
108
|
+
On a **retrofit** (`tokens.intakeMode: "retrofit"`), apply these transforms in
|
|
109
|
+
addition to the opacity normalization above. Each cost real debugging time on a live
|
|
110
|
+
retrofit — see the guardrails in
|
|
111
|
+
`.throughline/references/brownfield-retrofit.md`.
|
|
112
|
+
|
|
113
|
+
- **Alpha as channels, not baked CSS vars (so Tailwind `/opacity` survives).** Emit
|
|
114
|
+
colors as space-separated channels with a slash-alpha slot —
|
|
115
|
+
`--color-x: 239 68 68;` consumed as `rgb(var(--color-x) / <alpha-value>)` — rather
|
|
116
|
+
than a finished `rgba(...)`. A baked `rgba()` can't accept Tailwind's `/opacity`
|
|
117
|
+
modifier; the channel form keeps `bg-x/50` working after the retrofit.
|
|
118
|
+
- **`/opacity` on var-based tokens → `color-mix` or channel alpha (guardrail 6).**
|
|
119
|
+
Where existing code applies a `/opacity` modifier to a token that is now a CSS var,
|
|
120
|
+
you can't fold the alpha into the var. Convert to
|
|
121
|
+
`color-mix(in srgb, var(--color-x) NN%, transparent)` (or the channel-alpha form
|
|
122
|
+
above). Never carry a raw `/opacity` onto a var-based token.
|
|
123
|
+
- **Round float32 noise at the export boundary (guardrail 2).** Figma stores values as
|
|
124
|
+
float32 and re-quantizes on write, so normalizing *inside* Figma is a no-op. Round at
|
|
125
|
+
the **export boundary** instead — `Math.round(v * 100) / 100` as values leave the
|
|
126
|
+
pipeline — so `0.30000001192092896` lands as `0.3` in the generated files. Do this in
|
|
127
|
+
the extraction/transform step, never by hand-editing the generated output (guardrail 7).
|
|
128
|
+
|
|
129
|
+
These are transforms on the *values* the adapters emit; the adapter presets themselves
|
|
130
|
+
are unchanged. See `.throughline/references/sync-adapters.md` for where they
|
|
131
|
+
fit in the adapter output.
|
|
132
|
+
|
|
133
|
+
## Step 3 — Set up Style Dictionary + adapters
|
|
134
|
+
|
|
135
|
+
Install and configure Style Dictionary v4 in `packages/tokens/`. For each chosen
|
|
136
|
+
platform, apply its adapter preset (per `.throughline/references/sync-adapters.md`):
|
|
137
|
+
register the platform, transform group, format, and `outputReferences`
|
|
138
|
+
(true for web → preserves the semantic→primitive cascade; false for native →
|
|
139
|
+
flattens). Web adapters emit `:root`/`.dark` (or `[data-theme]`); the shadcn
|
|
140
|
+
adapter also emits a Tailwind preset.
|
|
141
|
+
|
|
142
|
+
**Execution model — subagent-driven for multiple platforms.** When more than one
|
|
143
|
+
platform is targeted, generating each platform's output is independent and
|
|
144
|
+
verifiable, so dispatch **one subagent per adapter**: each produces its
|
|
145
|
+
platform's files and verifies them (the config builds, the expected files
|
|
146
|
+
appear, references resolve correctly for web / flatten for native). Review each
|
|
147
|
+
before combining. For a single platform, run inline. (See the selective-
|
|
148
|
+
subagent decision: code-gen skills parallelize; Figma-authoring skills don't.)
|
|
149
|
+
|
|
150
|
+
## Step 4 — Build and place outputs
|
|
151
|
+
|
|
152
|
+
Run the Style Dictionary build. Outputs land in `packages/tokens/<platform>/`
|
|
153
|
+
as **build artifacts** — regenerated every sync, never hand-edited. Wire
|
|
154
|
+
`packages/tokens/package.json` to export them so the UI package, Storybook, and
|
|
155
|
+
any future app consume them.
|
|
156
|
+
|
|
157
|
+
## Step 4.5 — Icon code sync (install check + custom SVGR)
|
|
158
|
+
|
|
159
|
+
Icons reach code differently from tokens, so handle them here if the system has
|
|
160
|
+
icons (`icons.built` true):
|
|
161
|
+
|
|
162
|
+
- **Library icons (Lucide/Material)** — verify the npm package is installed
|
|
163
|
+
(`icons.packageInstalled`); install it if not. Then **check version drift**:
|
|
164
|
+
compare the installed package version against `icons.version` (the version the
|
|
165
|
+
Figma mirror was built from). If they differ, an icon could exist on one side
|
|
166
|
+
and not the other — flag it and **offer to align** them (bump the package, or
|
|
167
|
+
note the Figma mirror should be refreshed). Never generate library icon
|
|
168
|
+
component code — the package *is* the code.
|
|
169
|
+
- **Custom icons** — these the repo owns, so generate them: export the custom
|
|
170
|
+
SVGs from Figma, optimize, and componentize via SVGR into `packages/ui` (or a
|
|
171
|
+
dedicated icons package). This is real code generation and rides the same
|
|
172
|
+
PR-review and subagent model as token output.
|
|
173
|
+
|
|
174
|
+
## Step 5 — Full regeneration + rename detection (the safety net)
|
|
175
|
+
|
|
176
|
+
The sync is a **full regeneration** every run: outputs are rebuilt from current
|
|
177
|
+
Figma state, so a token **deleted** in Figma disappears from output (no orphans).
|
|
178
|
+
But a naive full regen can't tell a **rename** from a delete-plus-add — and a
|
|
179
|
+
rename silently breaks every consumer of the old name.
|
|
180
|
+
|
|
181
|
+
So before writing, **diff against the previous output** and run a rename
|
|
182
|
+
heuristic: if a token vanished and a new one appeared with an identical `$value`
|
|
183
|
+
and `$type`, flag it as a **probable rename** (e.g. `color.bg.default →
|
|
184
|
+
color.surface.default?`). Surface these prominently. Also surface plain
|
|
185
|
+
deletions (consumers will break — that's intentional, but the human should see
|
|
186
|
+
it).
|
|
187
|
+
|
|
188
|
+
## Step 6 — Land it as a reviewable PR (never silent)
|
|
189
|
+
|
|
190
|
+
Never overwrite token files silently. The output of a sync is a **pull request**
|
|
191
|
+
(or, if the repo is only `local-git`, a clearly-described diff/commit on a
|
|
192
|
+
branch the user reviews). The PR body is the human safety net — it should
|
|
193
|
+
summarize: tokens added, changed, deleted, and **probable renames flagged for
|
|
194
|
+
review** so the reviewer can do a find-and-replace instead of merging a break.
|
|
195
|
+
|
|
196
|
+
For `new` users, explain what a PR is plainly ("a proposed change you review and
|
|
197
|
+
approve before it becomes official — like track-changes for code") and walk the
|
|
198
|
+
review. For `comfortable` users, just open it.
|
|
199
|
+
|
|
200
|
+
**Chromatic note.** This PR is *token-only* — it changes `packages/tokens` but no
|
|
201
|
+
story files. Token changes are global, so Chromatic should re-snapshot **every**
|
|
202
|
+
story for a sync PR. Don't rely on TurboSnap (`onlyChanged: true`) here — its
|
|
203
|
+
incremental model keeps missing global token changes; default to full snapshots.
|
|
204
|
+
If a sync PR reports "0 snapshots captured," TurboSnap is the cause — see the
|
|
205
|
+
TurboSnap section in `storybook-chromatic-builder`.
|
|
206
|
+
|
|
207
|
+
Update the manifest: `sync.lastRun` (timestamp), `tokens.lastSync`, confirm
|
|
208
|
+
`sync.platforms`. Append `token-sync-layer` to `completedSkills`.
|
|
209
|
+
|
|
210
|
+
## Step 7 — Install the `/sync-figma-tokens` command
|
|
211
|
+
|
|
212
|
+
Set up the reusable command (the plugin ships it in `commands/`) so the user can
|
|
213
|
+
re-run this whole pipeline anytime tokens change in Figma. Explain that the
|
|
214
|
+
workflow going forward is: tweak tokens in Figma → run `/sync-figma-tokens` →
|
|
215
|
+
review the PR → merge. That loop is the entire point of the one-directional
|
|
216
|
+
source-of-truth model. Confirm the command works by describing how to invoke it.
|
|
217
|
+
|
|
218
|
+
## What this skill must NOT do
|
|
219
|
+
|
|
220
|
+
- Never hand-edit generated token files or let the user treat them as editable —
|
|
221
|
+
they're build artifacts; changes go in Figma and re-sync.
|
|
222
|
+
- Never flatten semantic→primitive references for web adapters (kills theming).
|
|
223
|
+
- Never write token files silently — always a reviewable PR/diff.
|
|
224
|
+
- Never merge or push to a protected branch on the user's behalf without review.
|
|
225
|
+
- Never skip rename detection — a silent rename is the worst failure mode.
|
|
226
|
+
- Never bake alpha into finished `rgba(...)` on a retrofit — emit channels
|
|
227
|
+
(`rgb(var(--x) / <alpha-value>)`) so Tailwind `/opacity` modifiers survive.
|
|
228
|
+
- Never carry a `/opacity` modifier onto a var-based token — convert to `color-mix`
|
|
229
|
+
or channel alpha (guardrail 6).
|
|
230
|
+
- Never normalize float32 inside Figma (it re-quantizes) — round at the export
|
|
231
|
+
boundary (`Math.round(v*100)/100`, guardrail 2).
|
|
232
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@radicool/throughline",
|
|
3
|
+
"version": "0.12.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"throughline": "scripts/install.mjs"
|
|
8
|
+
},
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"adapters/",
|
|
14
|
+
"references/",
|
|
15
|
+
"scripts/",
|
|
16
|
+
"!scripts/**/*.test.mjs",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "Jordan Pease",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/jrpease/throughline.git"
|
|
28
|
+
}
|
|
29
|
+
}
|