@radicool/throughline 0.21.3 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +8 -8
  2. package/adapters/codex/prompts/component-builder.md +7 -6
  3. package/adapters/codex/prompts/design-system-audit.md +3 -1
  4. package/adapters/codex/prompts/figma-environment-setup.md +28 -30
  5. package/adapters/codex/prompts/storybook-chromatic-builder.md +4 -0
  6. package/adapters/codex/prompts/token-builder.md +1 -1
  7. package/adapters/codex/prompts/token-sheet-builder.md +1 -1
  8. package/adapters/codex/prompts/token-sync-layer.md +6 -3
  9. package/adapters/cursor/.cursor/rules/component-builder.mdc +7 -6
  10. package/adapters/cursor/.cursor/rules/design-system-audit.mdc +3 -1
  11. package/adapters/cursor/.cursor/rules/figma-environment-setup.mdc +28 -30
  12. package/adapters/cursor/.cursor/rules/storybook-chromatic-builder.mdc +4 -0
  13. package/adapters/cursor/.cursor/rules/token-builder.mdc +1 -1
  14. package/adapters/cursor/.cursor/rules/token-sheet-builder.mdc +1 -1
  15. package/adapters/cursor/.cursor/rules/token-sync-layer.mdc +6 -3
  16. package/adapters/generic/skills/component-builder/SKILL.md +7 -6
  17. package/adapters/generic/skills/design-system-audit/SKILL.md +3 -1
  18. package/adapters/generic/skills/figma-environment-setup/SKILL.md +28 -30
  19. package/adapters/generic/skills/storybook-chromatic-builder/SKILL.md +4 -0
  20. package/adapters/generic/skills/token-builder/SKILL.md +1 -1
  21. package/adapters/generic/skills/token-sheet-builder/SKILL.md +1 -1
  22. package/adapters/generic/skills/token-sync-layer/SKILL.md +6 -3
  23. package/package.json +1 -1
  24. package/references/figma-component-standards.md +17 -19
  25. package/references/figma-scripting.md +2 -1
  26. package/references/manifest-schema.md +6 -4
  27. package/references/state-baseline.md +25 -0
  28. package/scripts/README.md +3 -2
  29. package/scripts/build-state-baseline.mjs +69 -0
  30. package/scripts/guard-token-removal.mjs +4 -4
  31. package/scripts/lib/component-states.mjs +24 -6
  32. package/scripts/lib/source-scan.mjs +3 -3
package/README.md CHANGED
@@ -73,9 +73,9 @@ Everything above was created during a single working session and synced directly
73
73
 
74
74
  | | |
75
75
  |---|---|
76
- | **A supported agent** | Required — [Claude Code](https://docs.claude.com/en/docs/claude-code) (native plugin), or **Cursor**, **Codex**, or any **AGENTS.md**-aware agent via `npx @radicool/throughline@0.21.3 init`. |
76
+ | **A supported agent** | Required — [Claude Code](https://docs.claude.com/en/docs/claude-code) (native plugin), or **Cursor**, **Codex**, or any **AGENTS.md**-aware agent via `npx @radicool/throughline@0.22.0 init`. |
77
77
  | **Figma** | Required, **desktop app** (the browser version causes connection errors). **Professional plan or higher recommended** — multi-mode variables (Light/Dark, brand themes) need it. |
78
- | **Figma access token** | Required — read/write your file. The setup skill walks you through it; your token stays yours and is never shared in chat. |
78
+ | **Figma access token** | Optional — only for the few Figma calls the Desktop Bridge can't make, like reading a published library. Without it, you work through the bridge alone. The setup skill walks you through it; your token stays yours and is never shared in chat. |
79
79
  | **GitHub** (or similar) | Optional — only when you're ready to graduate to a real remote repo with PRs and CI. |
80
80
 
81
81
  ### What it runs on your machine
@@ -114,9 +114,9 @@ Update anytime with `/plugin marketplace update throughline-marketplace`.
114
114
  Run the installer in your project — it stamps in the skills, the reference docs, the scripts, and the Figma MCP config for your tool:
115
115
 
116
116
  ```
117
- npx @radicool/throughline@0.21.3 init --target=cursor # → .cursor/rules + .cursor/mcp.json
118
- npx @radicool/throughline@0.21.3 init --target=codex # → prompts/ + AGENTS.md index + codex-mcp.toml
119
- npx @radicool/throughline@0.21.3 init --target=generic # → skills/ + AGENTS.md index
117
+ npx @radicool/throughline@0.22.0 init --target=cursor # → .cursor/rules + .cursor/mcp.json
118
+ npx @radicool/throughline@0.22.0 init --target=codex # → prompts/ + AGENTS.md index + codex-mcp.toml
119
+ npx @radicool/throughline@0.22.0 init --target=generic # → skills/ + AGENTS.md index
120
120
  ```
121
121
 
122
122
  It's safe to re-run (it merges `AGENTS.md` and `.cursor/mcp.json` non-destructively) and stages everything the skills read into `.throughline/`. Then open the `figma-environment-setup` skill/rule/prompt for your tool to begin. For Codex, add the printed `codex-mcp.toml` block to your Codex config to enable Figma access.
@@ -127,7 +127,7 @@ ThroughLine is more than a pile of skills — it's a small system designed to st
127
127
 
128
128
  **🛠 Twelve skills — the steps.** Each owns one stage of the journey, from connecting Figma to retrofitting an existing system to standing up CI. They're sequenced, and each knows its prerequisites.
129
129
 
130
- **📐 Ten reference docs — the constitution.** Shared standards every skill obeys: Figma component rules (auto-layout-on-everything, slot contracts, naming-as-contract, a required post-build audit), the brainstorm-before-build protocol, the sync-adapter specs, the manifest schema, coding-level adaptivity, the publishing flow, and the brownfield-retrofit discipline (read-before-assert, the safe migration sequence, the crosswalk contract). This is *why* two different runs produce the same structure — the rules live in one place, not scattered per skill.
130
+ **📐 The reference docs — the constitution.** Shared standards every skill obeys: Figma component rules (auto-layout-on-everything, slot contracts, naming-as-contract, a required post-build audit), the brainstorm-before-build protocol, the sync-adapter specs, the manifest schema, coding-level adaptivity, the publishing flow, and the brownfield-retrofit discipline (read-before-assert, the safe migration sequence, the crosswalk contract). This is *why* two different runs produce the same structure — the rules live in one place, not scattered per skill.
131
131
 
132
132
  **🧭 An orchestration layer — the memory.** A `design-system.json` manifest in your project records exactly what's set up, with a versioned schema and immutability rules. Every skill reads it, tells you what it's about to do, and offers to run anything missing first. Run **`/design-system-status`** anytime for a plain-language picture of where you stand.
133
133
 
@@ -162,7 +162,7 @@ For the technically curious — how the machine actually runs.
162
162
 
163
163
  **Modes, within Figma's limits.** Light/Dark lives on the semantic collections; brand variants live on the primitive palette. Splitting the two axes across two collections keeps each one under Figma's 4-modes-per-collection cap on the Professional plan while still resolving correctly (`bg/default` → `{gray/50}` → the active brand's gray).
164
164
 
165
- **Model routing.** ThroughLine routes work by *cognition, not guesswork* — the expensive thinking on the best model, the mechanical doing on a cheap one. Setup runs on **Haiku** automatically to keep first-run costs low. Inside a skill, the deciding — a token architecture, a variant matrix, a slot contract — is planned once on the strongest tier, while the long mechanical part — placing Figma nodes, transcribing component code, running SVGR — runs on a cheap tier and self-verifies. You get the best model where it matters and a cheap one where it doesn't, without micromanaging it. The recommended mapping is **Haiku → Sonnet → Opus** for the `fast < balanced < deep` tiers, but nothing is hardcoded — the ladder resolves against whatever models your install actually has, and on hosts without subagents skills degrade cleanly to a single model. You can still steer the session yourself (`/model opus` for the heaviest authoring, back to `/model sonnet` after); nothing forces an expensive model on you.
165
+ **Model routing.** ThroughLine routes work by *cognition, not guesswork* — the expensive thinking on the best model, the mechanical doing on a cheap one. Inside a skill, the deciding — a token architecture, a variant matrix, a slot contract — is planned once on the strongest tier, while the long mechanical part — placing Figma nodes, transcribing component code, running SVGR — runs on a cheap tier and self-verifies. You get the best model where it matters and a cheap one where it doesn't, without micromanaging it. The recommended mapping is **Haiku → Sonnet → Opus** for the `fast < balanced < deep` tiers, but nothing is hardcoded — the ladder resolves against whatever models your install actually has, and on hosts without subagents skills degrade cleanly to a single model. You can still steer the session yourself (`/model opus` for the heaviest authoring, back to `/model sonnet` after); nothing forces an expensive model on you.
166
166
 
167
167
  ## Who it's for
168
168
 
@@ -212,7 +212,7 @@ Future improvements and planned capabilities. Have a request? **[Open an issue](
212
212
  - **Deeper Code Connect coverage** — richer Figma-to-code mappings as more plans support it.
213
213
  - **Expanded component starters** — a larger foundational kit out of the box.
214
214
  - **Richer status & auditing** — more from `/design-system-status`, including drift detection between Figma and code.
215
- - **Built-in accessibility checks** — the color half is shipped. Eight semantic text-on-surface pairs are checked against WCAG AA's 4.5:1 when tokens are created in Figma, and derived again from the token source when they sync to code — in every mode, so a system that clears in light and fails in dark fails. A failing pair stops the build rather than warning. Components are checked too: on web frameworks, a Figma build stops when a focus state has no visible ring, and in JSX code the adherence gate fails an icon-only button with no accessible name and a status badge that relies on color alone. Still ahead: focus-ring contrast (WCAG's 3:1 for non-text).
215
+ - **Built-in accessibility checks** — the color half is shipped. Eight semantic text-on-surface pairs are checked against WCAG AA's 4.5:1 when tokens are created in Figma, and derived again from the token source when they sync to code — in every mode, so a system that clears in light and fails in dark fails. A failing pair stops the build rather than warning. Components are checked too: on web frameworks, a Figma build stops when a focus state has no visible ring, and in JSX code the adherence gate is built to fail an icon-only button with no accessible name and a status badge that relies on color alone — proven by unit tests. The one real app it has run on had almost nothing for it to check, so it hasn't caught a bug in the wild yet. Still ahead: focus-ring contrast (WCAG's 3:1 for non-text).
216
216
  - **Token fan-out to more platforms** — the DTCG token source is platform-neutral, and web targets are in daily use. Every target is **validated per build, not assumed**: `tokens:validate-output` checks generated output against its source, native Swift and Kotlin as well as web CSS, because the stock transforms have been measured emitting wrong-but-compiling values. iOS/Swift is a curated adapter whose Style Dictionary configuration ships as tested code, and Android/Kotlin uses the same configuration through the Tier 2 protocol. JavaScript theme output (MUI, a Tailwind v3 config) isn't checked yet.
217
217
  - **Native component code generation** — producing a SwiftUI view or a Compose composable the way Storybook components are produced for React. This does not exist yet; it is a separate, larger effort than token fan-out, and the two were previously described as one roadmap item.
218
218
 
@@ -8,7 +8,7 @@ translate cleanly to code later.
8
8
 
9
9
  Needs tokens (`tokens.semanticBuilt` true) — offer to run `token-builder` if
10
10
  missing. Needs a live Figma connection (offer `figma-environment-setup` if not).
11
- Use the mechanism in `figma.mechanism`.
11
+ The plugin writes to Figma through Console MCP. If `figma.mechanism` holds anything other than `console-mcp`, say that value is no longer supported and offer `figma-environment-setup` to reset it.
12
12
 
13
13
  **Before scripting any `figma_execute`, read
14
14
  `.throughline/references/figma-scripting.md`** — the single-bridge-instance
@@ -67,8 +67,9 @@ inconsistent output:
67
67
  - **Sizes** (sm, md, lg). Each size is its **own variant row**, not a state — see
68
68
  the layout law below.
69
69
  - **States** — **include the full relevant set, don't trim it.** The baseline is
70
- default, hover, focus, active (pressed), disabled; add the conditional states
71
- wherever they apply (loading, selected, success/error). Decide which conditional
70
+ the set `.throughline/references/state-baseline.md` owes that component
71
+ (default included); add the conditional states wherever they apply (loading,
72
+ success/error). Decide which conditional
72
73
  states a given component can reach, but never drop a state it genuinely has. See
73
74
  "State handling" in `.throughline/references/figma-component-standards.md`
74
75
  for the per-component checklist.
@@ -400,9 +401,9 @@ Offer next steps: build the code counterparts and stories
400
401
  auto-layout grid where **variants are rows and states are columns**: one row per
401
402
  variant (each `type`, and each `size`, since size is a variant) stepping through
402
403
  states across the columns, size groups stacked vertically (see the standards doc).
403
- - Never ship a component with only its `default` state — include the full relevant
404
- state set (default/hover/focus/active/disabled plus applicable
405
- loading/selected/success/error).
404
+ - Never ship a component with only its `default` state — include every state
405
+ `.throughline/references/state-baseline.md` owes that component, plus
406
+ `default`, plus any applicable loading/success/error.
406
407
  - Never run the component header and the component area together with no division —
407
408
  every doc card segments the header from the component area with a divider line or
408
409
  a distinct header surface.
@@ -22,7 +22,9 @@ across levels.
22
22
 
23
23
  Read the manifest. This skill needs Figma connected (`figma.connected: true`) for the
24
24
  inventory step, and a repo path for the code-surface step (`workspace.localPath`). If
25
- Figma isn't connected, offer to run `figma-environment-setup` first. The code-surface
25
+ Figma isn't connected, offer to run `figma-environment-setup` first. Before the
26
+ inventory, do a cheap liveness read to confirm the connection is actually live right
27
+ now; if it fails, offer `figma-environment-setup`. The code-surface
26
28
  step works on any repo regardless of `workspace.stage`.
27
29
 
28
30
  ## Step 1 — Size the code surface
@@ -9,8 +9,10 @@ This skill does three foundational jobs, in order:
9
9
  3. **Connect Codex to Figma** so later skills can read and write the file,
10
10
  then verify the connection works.
11
11
 
12
- It is the required first step. Every other Figma skill does a cheap liveness
13
- check and, if it fails, points the user back here.
12
+ It is the required first step. Every other skill that calls Figma itself does a
13
+ cheap liveness check and, if it fails, points the user back here. (The orchestrators
14
+ `component-pipeline` and `retrofit-planner` call Figma only through the skills they
15
+ invoke, and `token-crosswalk-builder` works from the manifest and token files.)
14
16
 
15
17
  ## Who you're talking to
16
18
 
@@ -223,28 +225,23 @@ Make clear it changes only how much detail they get, never what they can build.
223
225
  This is set now because it colors every later interaction. The Figma phase
224
226
  itself doesn't lean on it much, but the code phase (repo, sync, Storybook) does.
225
227
 
226
- ## Step 3 — Choose the Figma write mechanism
228
+ ## Step 3 — Figma write mechanism
227
229
 
228
- Explain there are two ways to connect Codex to Figma, and recommend the first:
230
+ ThroughLine connects to Figma through the **Figma Console MCP**. It can create
231
+ many variables at once efficiently (which saves time and usage), and it can read
232
+ your variables on any Figma plan, including free and Pro. The setup is light:
233
+ you'll run a small helper plugin inside Figma, and can optionally create one
234
+ access token.
229
235
 
230
- - **Figma Console MCP (recommended).** More capable — it can create many
231
- variables at once efficiently (which saves time and usage), and it can read
232
- your variables on any Figma plan, including free and Pro. Slightly more setup:
233
- you'll create one access token and run a small helper plugin inside Figma.
234
- - **Official Figma plugin (simpler fallback).** Less setup, but more limited —
235
- some bulk operations are slower, and reading variables through it can be
236
- restricted on non-Enterprise plans. Fine if the user wants the lightest path.
237
-
238
- Let the user choose. Record their choice in `figma.mechanism`
239
- (`"console-mcp"` or `"official-plugin"`). Default to `console-mcp` if they have
240
- no preference.
236
+ Record `figma.mechanism` as `"console-mcp"`. There is nothing for the user to
237
+ choose.
241
238
 
242
239
  The MCP server configuration ships **bundled with this plugin** (in the plugin's
243
240
  `.mcp.json`), so the user does not hand-edit any config files. The one thing the
244
241
  bundle can't contain is their personal access token — that's a per-user secret,
245
242
  handled in the next step.
246
243
 
247
- ## Step 4 — Connect to Figma (Console MCP path)
244
+ ## Step 4 — Connect to Figma
248
245
 
249
246
  Walk through these one at a time, confirming each before moving on. Never rush
250
247
  the user past a step.
@@ -257,10 +254,16 @@ browser version of Figma causes connection and token errors that are painful to
257
254
  debug. If they don't have the desktop app, point them to figma.com/downloads to
258
255
  install it first. Desktop app, every time.
259
256
 
260
- ### 4b. Create a Figma access token
257
+ ### 4b. Create a Figma access token (optional)
258
+
259
+ A token is like a password that lets Codex talk to Figma on your behalf. It's
260
+ **optional**: the desktop bridge does the reading and writing on its own. The
261
+ token only adds the few calls the bridge can't make, such as reading a published
262
+ library or a file's version history. Offer it, and say plainly that they can skip
263
+ it and add it later in **Configure options**. If they skip, record nothing about
264
+ it, leave the field blank, and go straight to 4c. Nothing later in setup needs it.
261
265
 
262
- A token is like a password that lets Codex talk to Figma on your behalf. Walk
263
- them through it:
266
+ If they want it, walk them through it:
264
267
 
265
268
  1. In a browser, go to figma.com and sign in (same account as the desktop app).
266
269
  2. Click your account menu, then Settings.
@@ -309,14 +312,6 @@ Codex and Figma are linked for this session."
309
312
  If the config was just added (token placed for the first time), the MCP client
310
313
  may need a restart to load it. Tell them how, simply, and confirm they're back.
311
314
 
312
- ## Step 4 (alt) — Connect to Figma (official plugin path)
313
-
314
- If the user chose the official plugin, the flow is lighter: install the official
315
- Figma plugin for Codex per its setup, ensure the desktop app is open with
316
- the file, and authenticate through the desktop app (again: desktop, not
317
- browser). The same secret-handling rule applies — any token goes into config by
318
- the user, never through the chat.
319
-
320
315
  ## Step 5 — Capture the file key
321
316
 
322
317
  Ask the user for the URL of the Figma file they want to build the design system
@@ -384,7 +379,7 @@ tokens or pairing here is useless. Diagnose in order:
384
379
 
385
380
  **B. The server started but the connection failed.** If there's no launch error
386
381
  and the server is clearly running, diagnose gently and in order: Is the desktop
387
- app open with the file? Was the token placed correctly (have them re-check,
382
+ app open with the file? If they added a token, was it placed correctly (have them re-check,
388
383
  without showing you the value)? Is the bridge plugin running and paired? Did the
389
384
  client need a restart? Walk them back through the relevant sub-step.
390
385
 
@@ -400,7 +395,8 @@ neutral placeholder built against nothing.
400
395
 
401
396
  - Create one trivial throwaway node (e.g. a small frame or text node named
402
397
  `__throughline write test`) on any page.
403
- - Confirm it landed with a quick read or `figma_capture_screenshot`.
398
+ - Confirm it landed with a quick read or `figma_capture_screenshot`. Neither needs
399
+ a token.
404
400
  - **Delete it.** If both the write and the delete succeed, writes are proven.
405
401
 
406
402
  Leave `figma.coverPageBuilt` = `false` here — that flag is set later, when the
@@ -445,7 +441,9 @@ restore point:
445
441
  `figma_get_file_versions`) or have the user name a Figma version so there's a known-good
446
442
  point to restore to. The plugin can read versions; it does not auto-create named
447
443
  versions, so if none exists, ask the user to add one ("File → Save to version
448
- history") and record that you did.
444
+ history") and record that you did. If the user skipped the access token and
445
+ the version read fails, say in one sentence that you can't read version history
446
+ without it, and have them name the version themselves.
449
447
  - **Token export** — if the repo already emits tokens, snapshot the current generated
450
448
  output (git is the natural baseline once `workspace.stage` is `local-git`+).
451
449
 
@@ -16,6 +16,10 @@ This skill needs:
16
16
  - Figma components (`components.built`) with their slot contracts, so stories
17
17
  reflect the real component APIs.
18
18
 
19
+ The Figma-touching steps (Code Connect in Step 5, the doc-card write-back in Step 6)
20
+ also need a live Figma connection — do a cheap liveness read before the first Figma
21
+ call; if it fails, offer `figma-environment-setup`.
22
+
19
23
  Strongly recommend `github` stage so Chromatic's CI integration works; it can be
20
24
  set up locally first and wired to CI when the remote exists.
21
25
 
@@ -35,7 +35,7 @@ now. If it isn't, say so plainly and offer to run the `figma-environment-setup`
35
35
  skill first: "Figma isn't connected yet — want me to set that up? It's a
36
36
  one-time thing." Don't proceed until Figma is reachable.
37
37
 
38
- Use the write mechanism recorded in `figma.mechanism` (default `console-mcp`).
38
+ The plugin writes to Figma through Console MCP. If `figma.mechanism` holds anything other than `console-mcp`, say that value is no longer supported and offer `figma-environment-setup` to reset it.
39
39
  With Console MCP, prefer `figma_execute` to create variables in scripted loops
40
40
  rather than one tool call per variable — this is dramatically more
41
41
  token-efficient for the potentially hundreds of primitives across modes.
@@ -24,7 +24,7 @@ a sensible companion step — the sheet and the synced code both reflect Figma.)
24
24
 
25
25
  Needs tokens in Figma (`tokens.semanticBuilt` true) and a live Figma connection.
26
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`.
27
+ offer `figma-environment-setup`. The plugin writes to Figma through Console MCP. If `figma.mechanism` holds anything other than `console-mcp`, say that value is no longer supported and offer `figma-environment-setup` to reset it.
28
28
 
29
29
  **Before scripting any `figma_execute`, read
30
30
  `.throughline/references/figma-scripting.md`.** This sheet builds large
@@ -66,8 +66,8 @@ it.
66
66
 
67
67
  ## Step 2 — Extract Figma variables to DTCG JSON
68
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
69
+ The plugin talks to Figma through Console MCP. If `figma.mechanism` holds anything other than `console-mcp`, say that value is no longer supported and offer `figma-environment-setup` to reset it. Using Console MCP's
70
+ full-design-system extraction (which works on any Figma plan), read
71
71
  **every variable collection** (there are now several per tier, e.g.
72
72
  `_Color/Primitive`, `Spacing/Primitive`, `Color/Semantic`, …) and normalize them
73
73
  into **DTCG-format JSON** (`$value`, `$type`, with semantic tokens expressed as
@@ -140,7 +140,10 @@ fit in the adapter output.
140
140
 
141
141
  ## Step 3 — Set up Style Dictionary + adapters
142
142
 
143
- Install and configure Style Dictionary v4 in `packages/tokens/`. For each chosen
143
+ Install Style Dictionary pinned to an exact version with
144
+ `pnpm add -D style-dictionary@4.4.0` (or the project's package manager) in
145
+ `packages/tokens/`; the generated config is verified against 4.4.0, and a newer
146
+ release can rename or add stock transforms. Then configure it. For each chosen
144
147
  platform, apply its adapter preset (per `.throughline/references/sync-adapters.md`):
145
148
  register the platform, transform group, format, and `outputReferences`
146
149
  (true for web → preserves the semantic→primitive cascade; false for native →
@@ -12,7 +12,7 @@ translate cleanly to code later.
12
12
 
13
13
  Needs tokens (`tokens.semanticBuilt` true) — offer to run `token-builder` if
14
14
  missing. Needs a live Figma connection (offer `figma-environment-setup` if not).
15
- Use the mechanism in `figma.mechanism`.
15
+ The plugin writes to Figma through Console MCP. If `figma.mechanism` holds anything other than `console-mcp`, say that value is no longer supported and offer `figma-environment-setup` to reset it.
16
16
 
17
17
  **Before scripting any `figma_execute`, read
18
18
  `.throughline/references/figma-scripting.md`** — the single-bridge-instance
@@ -71,8 +71,9 @@ inconsistent output:
71
71
  - **Sizes** (sm, md, lg). Each size is its **own variant row**, not a state — see
72
72
  the layout law below.
73
73
  - **States** — **include the full relevant set, don't trim it.** The baseline is
74
- default, hover, focus, active (pressed), disabled; add the conditional states
75
- wherever they apply (loading, selected, success/error). Decide which conditional
74
+ the set `.throughline/references/state-baseline.md` owes that component
75
+ (default included); add the conditional states wherever they apply (loading,
76
+ success/error). Decide which conditional
76
77
  states a given component can reach, but never drop a state it genuinely has. See
77
78
  "State handling" in `.throughline/references/figma-component-standards.md`
78
79
  for the per-component checklist.
@@ -404,9 +405,9 @@ Offer next steps: build the code counterparts and stories
404
405
  auto-layout grid where **variants are rows and states are columns**: one row per
405
406
  variant (each `type`, and each `size`, since size is a variant) stepping through
406
407
  states across the columns, size groups stacked vertically (see the standards doc).
407
- - Never ship a component with only its `default` state — include the full relevant
408
- state set (default/hover/focus/active/disabled plus applicable
409
- loading/selected/success/error).
408
+ - Never ship a component with only its `default` state — include every state
409
+ `.throughline/references/state-baseline.md` owes that component, plus
410
+ `default`, plus any applicable loading/success/error.
410
411
  - Never run the component header and the component area together with no division —
411
412
  every doc card segments the header from the component area with a divider line or
412
413
  a distinct header surface.
@@ -26,7 +26,9 @@ across levels.
26
26
 
27
27
  Read the manifest. This skill needs Figma connected (`figma.connected: true`) for the
28
28
  inventory step, and a repo path for the code-surface step (`workspace.localPath`). If
29
- Figma isn't connected, offer to run `figma-environment-setup` first. The code-surface
29
+ Figma isn't connected, offer to run `figma-environment-setup` first. Before the
30
+ inventory, do a cheap liveness read to confirm the connection is actually live right
31
+ now; if it fails, offer `figma-environment-setup`. The code-surface
30
32
  step works on any repo regardless of `workspace.stage`.
31
33
 
32
34
  ## Step 1 — Size the code surface
@@ -13,8 +13,10 @@ This skill does three foundational jobs, in order:
13
13
  3. **Connect Cursor to Figma** so later skills can read and write the file,
14
14
  then verify the connection works.
15
15
 
16
- It is the required first step. Every other Figma skill does a cheap liveness
17
- check and, if it fails, points the user back here.
16
+ It is the required first step. Every other skill that calls Figma itself does a
17
+ cheap liveness check and, if it fails, points the user back here. (The orchestrators
18
+ `component-pipeline` and `retrofit-planner` call Figma only through the skills they
19
+ invoke, and `token-crosswalk-builder` works from the manifest and token files.)
18
20
 
19
21
  ## Who you're talking to
20
22
 
@@ -227,28 +229,23 @@ Make clear it changes only how much detail they get, never what they can build.
227
229
  This is set now because it colors every later interaction. The Figma phase
228
230
  itself doesn't lean on it much, but the code phase (repo, sync, Storybook) does.
229
231
 
230
- ## Step 3 — Choose the Figma write mechanism
232
+ ## Step 3 — Figma write mechanism
231
233
 
232
- Explain there are two ways to connect Cursor to Figma, and recommend the first:
234
+ ThroughLine connects to Figma through the **Figma Console MCP**. It can create
235
+ many variables at once efficiently (which saves time and usage), and it can read
236
+ your variables on any Figma plan, including free and Pro. The setup is light:
237
+ you'll run a small helper plugin inside Figma, and can optionally create one
238
+ access token.
233
239
 
234
- - **Figma Console MCP (recommended).** More capable — it can create many
235
- variables at once efficiently (which saves time and usage), and it can read
236
- your variables on any Figma plan, including free and Pro. Slightly more setup:
237
- you'll create one access token and run a small helper plugin inside Figma.
238
- - **Official Figma plugin (simpler fallback).** Less setup, but more limited —
239
- some bulk operations are slower, and reading variables through it can be
240
- restricted on non-Enterprise plans. Fine if the user wants the lightest path.
241
-
242
- Let the user choose. Record their choice in `figma.mechanism`
243
- (`"console-mcp"` or `"official-plugin"`). Default to `console-mcp` if they have
244
- no preference.
240
+ Record `figma.mechanism` as `"console-mcp"`. There is nothing for the user to
241
+ choose.
245
242
 
246
243
  The MCP server configuration ships **bundled with this plugin** (in the plugin's
247
244
  `.mcp.json`), so the user does not hand-edit any config files. The one thing the
248
245
  bundle can't contain is their personal access token — that's a per-user secret,
249
246
  handled in the next step.
250
247
 
251
- ## Step 4 — Connect to Figma (Console MCP path)
248
+ ## Step 4 — Connect to Figma
252
249
 
253
250
  Walk through these one at a time, confirming each before moving on. Never rush
254
251
  the user past a step.
@@ -261,10 +258,16 @@ browser version of Figma causes connection and token errors that are painful to
261
258
  debug. If they don't have the desktop app, point them to figma.com/downloads to
262
259
  install it first. Desktop app, every time.
263
260
 
264
- ### 4b. Create a Figma access token
261
+ ### 4b. Create a Figma access token (optional)
262
+
263
+ A token is like a password that lets Cursor talk to Figma on your behalf. It's
264
+ **optional**: the desktop bridge does the reading and writing on its own. The
265
+ token only adds the few calls the bridge can't make, such as reading a published
266
+ library or a file's version history. Offer it, and say plainly that they can skip
267
+ it and add it later in **Configure options**. If they skip, record nothing about
268
+ it, leave the field blank, and go straight to 4c. Nothing later in setup needs it.
265
269
 
266
- A token is like a password that lets Cursor talk to Figma on your behalf. Walk
267
- them through it:
270
+ If they want it, walk them through it:
268
271
 
269
272
  1. In a browser, go to figma.com and sign in (same account as the desktop app).
270
273
  2. Click your account menu, then Settings.
@@ -313,14 +316,6 @@ Cursor and Figma are linked for this session."
313
316
  If the config was just added (token placed for the first time), the MCP client
314
317
  may need a restart to load it. Tell them how, simply, and confirm they're back.
315
318
 
316
- ## Step 4 (alt) — Connect to Figma (official plugin path)
317
-
318
- If the user chose the official plugin, the flow is lighter: install the official
319
- Figma plugin for Cursor per its setup, ensure the desktop app is open with
320
- the file, and authenticate through the desktop app (again: desktop, not
321
- browser). The same secret-handling rule applies — any token goes into config by
322
- the user, never through the chat.
323
-
324
319
  ## Step 5 — Capture the file key
325
320
 
326
321
  Ask the user for the URL of the Figma file they want to build the design system
@@ -388,7 +383,7 @@ tokens or pairing here is useless. Diagnose in order:
388
383
 
389
384
  **B. The server started but the connection failed.** If there's no launch error
390
385
  and the server is clearly running, diagnose gently and in order: Is the desktop
391
- app open with the file? Was the token placed correctly (have them re-check,
386
+ app open with the file? If they added a token, was it placed correctly (have them re-check,
392
387
  without showing you the value)? Is the bridge plugin running and paired? Did the
393
388
  client need a restart? Walk them back through the relevant sub-step.
394
389
 
@@ -404,7 +399,8 @@ neutral placeholder built against nothing.
404
399
 
405
400
  - Create one trivial throwaway node (e.g. a small frame or text node named
406
401
  `__throughline write test`) on any page.
407
- - Confirm it landed with a quick read or `figma_capture_screenshot`.
402
+ - Confirm it landed with a quick read or `figma_capture_screenshot`. Neither needs
403
+ a token.
408
404
  - **Delete it.** If both the write and the delete succeed, writes are proven.
409
405
 
410
406
  Leave `figma.coverPageBuilt` = `false` here — that flag is set later, when the
@@ -449,7 +445,9 @@ restore point:
449
445
  `figma_get_file_versions`) or have the user name a Figma version so there's a known-good
450
446
  point to restore to. The plugin can read versions; it does not auto-create named
451
447
  versions, so if none exists, ask the user to add one ("File → Save to version
452
- history") and record that you did.
448
+ history") and record that you did. If the user skipped the access token and
449
+ the version read fails, say in one sentence that you can't read version history
450
+ without it, and have them name the version themselves.
453
451
  - **Token export** — if the repo already emits tokens, snapshot the current generated
454
452
  output (git is the natural baseline once `workspace.stage` is `local-git`+).
455
453
 
@@ -20,6 +20,10 @@ This skill needs:
20
20
  - Figma components (`components.built`) with their slot contracts, so stories
21
21
  reflect the real component APIs.
22
22
 
23
+ The Figma-touching steps (Code Connect in Step 5, the doc-card write-back in Step 6)
24
+ also need a live Figma connection — do a cheap liveness read before the first Figma
25
+ call; if it fails, offer `figma-environment-setup`.
26
+
23
27
  Strongly recommend `github` stage so Chromatic's CI integration works; it can be
24
28
  set up locally first and wired to CI when the remote exists.
25
29
 
@@ -39,7 +39,7 @@ now. If it isn't, say so plainly and offer to run the `figma-environment-setup`
39
39
  skill first: "Figma isn't connected yet — want me to set that up? It's a
40
40
  one-time thing." Don't proceed until Figma is reachable.
41
41
 
42
- Use the write mechanism recorded in `figma.mechanism` (default `console-mcp`).
42
+ The plugin writes to Figma through Console MCP. If `figma.mechanism` holds anything other than `console-mcp`, say that value is no longer supported and offer `figma-environment-setup` to reset it.
43
43
  With Console MCP, prefer `figma_execute` to create variables in scripted loops
44
44
  rather than one tool call per variable — this is dramatically more
45
45
  token-efficient for the potentially hundreds of primitives across modes.
@@ -28,7 +28,7 @@ a sensible companion step — the sheet and the synced code both reflect Figma.)
28
28
 
29
29
  Needs tokens in Figma (`tokens.semanticBuilt` true) and a live Figma connection.
30
30
  If tokens don't exist, offer to run `token-builder`. If Figma isn't connected,
31
- offer `figma-environment-setup`. Use the mechanism in `figma.mechanism`.
31
+ offer `figma-environment-setup`. The plugin writes to Figma through Console MCP. If `figma.mechanism` holds anything other than `console-mcp`, say that value is no longer supported and offer `figma-environment-setup` to reset it.
32
32
 
33
33
  **Before scripting any `figma_execute`, read
34
34
  `.throughline/references/figma-scripting.md`.** This sheet builds large
@@ -70,8 +70,8 @@ it.
70
70
 
71
71
  ## Step 2 — Extract Figma variables to DTCG JSON
72
72
 
73
- Using the active write mechanism (`figma.mechanism`, default Console MCP —
74
- prefer its full-design-system extraction, which works on any Figma plan), read
73
+ The plugin talks to Figma through Console MCP. If `figma.mechanism` holds anything other than `console-mcp`, say that value is no longer supported and offer `figma-environment-setup` to reset it. Using Console MCP's
74
+ full-design-system extraction (which works on any Figma plan), read
75
75
  **every variable collection** (there are now several per tier, e.g.
76
76
  `_Color/Primitive`, `Spacing/Primitive`, `Color/Semantic`, …) and normalize them
77
77
  into **DTCG-format JSON** (`$value`, `$type`, with semantic tokens expressed as
@@ -144,7 +144,10 @@ fit in the adapter output.
144
144
 
145
145
  ## Step 3 — Set up Style Dictionary + adapters
146
146
 
147
- Install and configure Style Dictionary v4 in `packages/tokens/`. For each chosen
147
+ Install Style Dictionary pinned to an exact version with
148
+ `pnpm add -D style-dictionary@4.4.0` (or the project's package manager) in
149
+ `packages/tokens/`; the generated config is verified against 4.4.0, and a newer
150
+ release can rename or add stock transforms. Then configure it. For each chosen
148
151
  platform, apply its adapter preset (per `.throughline/references/sync-adapters.md`):
149
152
  register the platform, transform group, format, and `outputReferences`
150
153
  (true for web → preserves the semantic→primitive cascade; false for native →
@@ -8,7 +8,7 @@ translate cleanly to code later.
8
8
 
9
9
  Needs tokens (`tokens.semanticBuilt` true) — offer to run `token-builder` if
10
10
  missing. Needs a live Figma connection (offer `figma-environment-setup` if not).
11
- Use the mechanism in `figma.mechanism`.
11
+ The plugin writes to Figma through Console MCP. If `figma.mechanism` holds anything other than `console-mcp`, say that value is no longer supported and offer `figma-environment-setup` to reset it.
12
12
 
13
13
  **Before scripting any `figma_execute`, read
14
14
  `.throughline/references/figma-scripting.md`** — the single-bridge-instance
@@ -67,8 +67,9 @@ inconsistent output:
67
67
  - **Sizes** (sm, md, lg). Each size is its **own variant row**, not a state — see
68
68
  the layout law below.
69
69
  - **States** — **include the full relevant set, don't trim it.** The baseline is
70
- default, hover, focus, active (pressed), disabled; add the conditional states
71
- wherever they apply (loading, selected, success/error). Decide which conditional
70
+ the set `.throughline/references/state-baseline.md` owes that component
71
+ (default included); add the conditional states wherever they apply (loading,
72
+ success/error). Decide which conditional
72
73
  states a given component can reach, but never drop a state it genuinely has. See
73
74
  "State handling" in `.throughline/references/figma-component-standards.md`
74
75
  for the per-component checklist.
@@ -400,9 +401,9 @@ Offer next steps: build the code counterparts and stories
400
401
  auto-layout grid where **variants are rows and states are columns**: one row per
401
402
  variant (each `type`, and each `size`, since size is a variant) stepping through
402
403
  states across the columns, size groups stacked vertically (see the standards doc).
403
- - Never ship a component with only its `default` state — include the full relevant
404
- state set (default/hover/focus/active/disabled plus applicable
405
- loading/selected/success/error).
404
+ - Never ship a component with only its `default` state — include every state
405
+ `.throughline/references/state-baseline.md` owes that component, plus
406
+ `default`, plus any applicable loading/success/error.
406
407
  - Never run the component header and the component area together with no division —
407
408
  every doc card segments the header from the component area with a divider line or
408
409
  a distinct header surface.
@@ -22,7 +22,9 @@ across levels.
22
22
 
23
23
  Read the manifest. This skill needs Figma connected (`figma.connected: true`) for the
24
24
  inventory step, and a repo path for the code-surface step (`workspace.localPath`). If
25
- Figma isn't connected, offer to run `figma-environment-setup` first. The code-surface
25
+ Figma isn't connected, offer to run `figma-environment-setup` first. Before the
26
+ inventory, do a cheap liveness read to confirm the connection is actually live right
27
+ now; if it fails, offer `figma-environment-setup`. The code-surface
26
28
  step works on any repo regardless of `workspace.stage`.
27
29
 
28
30
  ## Step 1 — Size the code surface
@@ -9,8 +9,10 @@ This skill does three foundational jobs, in order:
9
9
  3. **Connect the agent to Figma** so later skills can read and write the file,
10
10
  then verify the connection works.
11
11
 
12
- It is the required first step. Every other Figma skill does a cheap liveness
13
- check and, if it fails, points the user back here.
12
+ It is the required first step. Every other skill that calls Figma itself does a
13
+ cheap liveness check and, if it fails, points the user back here. (The orchestrators
14
+ `component-pipeline` and `retrofit-planner` call Figma only through the skills they
15
+ invoke, and `token-crosswalk-builder` works from the manifest and token files.)
14
16
 
15
17
  ## Who you're talking to
16
18
 
@@ -223,28 +225,23 @@ Make clear it changes only how much detail they get, never what they can build.
223
225
  This is set now because it colors every later interaction. The Figma phase
224
226
  itself doesn't lean on it much, but the code phase (repo, sync, Storybook) does.
225
227
 
226
- ## Step 3 — Choose the Figma write mechanism
228
+ ## Step 3 — Figma write mechanism
227
229
 
228
- Explain there are two ways to connect the agent to Figma, and recommend the first:
230
+ ThroughLine connects to Figma through the **Figma Console MCP**. It can create
231
+ many variables at once efficiently (which saves time and usage), and it can read
232
+ your variables on any Figma plan, including free and Pro. The setup is light:
233
+ you'll run a small helper plugin inside Figma, and can optionally create one
234
+ access token.
229
235
 
230
- - **Figma Console MCP (recommended).** More capable — it can create many
231
- variables at once efficiently (which saves time and usage), and it can read
232
- your variables on any Figma plan, including free and Pro. Slightly more setup:
233
- you'll create one access token and run a small helper plugin inside Figma.
234
- - **Official Figma plugin (simpler fallback).** Less setup, but more limited —
235
- some bulk operations are slower, and reading variables through it can be
236
- restricted on non-Enterprise plans. Fine if the user wants the lightest path.
237
-
238
- Let the user choose. Record their choice in `figma.mechanism`
239
- (`"console-mcp"` or `"official-plugin"`). Default to `console-mcp` if they have
240
- no preference.
236
+ Record `figma.mechanism` as `"console-mcp"`. There is nothing for the user to
237
+ choose.
241
238
 
242
239
  The MCP server configuration ships **bundled with this plugin** (in the plugin's
243
240
  `.mcp.json`), so the user does not hand-edit any config files. The one thing the
244
241
  bundle can't contain is their personal access token — that's a per-user secret,
245
242
  handled in the next step.
246
243
 
247
- ## Step 4 — Connect to Figma (Console MCP path)
244
+ ## Step 4 — Connect to Figma
248
245
 
249
246
  Walk through these one at a time, confirming each before moving on. Never rush
250
247
  the user past a step.
@@ -257,10 +254,16 @@ browser version of Figma causes connection and token errors that are painful to
257
254
  debug. If they don't have the desktop app, point them to figma.com/downloads to
258
255
  install it first. Desktop app, every time.
259
256
 
260
- ### 4b. Create a Figma access token
257
+ ### 4b. Create a Figma access token (optional)
258
+
259
+ A token is like a password that lets the agent talk to Figma on your behalf. It's
260
+ **optional**: the desktop bridge does the reading and writing on its own. The
261
+ token only adds the few calls the bridge can't make, such as reading a published
262
+ library or a file's version history. Offer it, and say plainly that they can skip
263
+ it and add it later in **Configure options**. If they skip, record nothing about
264
+ it, leave the field blank, and go straight to 4c. Nothing later in setup needs it.
261
265
 
262
- A token is like a password that lets the agent talk to Figma on your behalf. Walk
263
- them through it:
266
+ If they want it, walk them through it:
264
267
 
265
268
  1. In a browser, go to figma.com and sign in (same account as the desktop app).
266
269
  2. Click your account menu, then Settings.
@@ -309,14 +312,6 @@ the agent and Figma are linked for this session."
309
312
  If the config was just added (token placed for the first time), the MCP client
310
313
  may need a restart to load it. Tell them how, simply, and confirm they're back.
311
314
 
312
- ## Step 4 (alt) — Connect to Figma (official plugin path)
313
-
314
- If the user chose the official plugin, the flow is lighter: install the official
315
- Figma plugin for your coding agent per its setup, ensure the desktop app is open with
316
- the file, and authenticate through the desktop app (again: desktop, not
317
- browser). The same secret-handling rule applies — any token goes into config by
318
- the user, never through the chat.
319
-
320
315
  ## Step 5 — Capture the file key
321
316
 
322
317
  Ask the user for the URL of the Figma file they want to build the design system
@@ -384,7 +379,7 @@ tokens or pairing here is useless. Diagnose in order:
384
379
 
385
380
  **B. The server started but the connection failed.** If there's no launch error
386
381
  and the server is clearly running, diagnose gently and in order: Is the desktop
387
- app open with the file? Was the token placed correctly (have them re-check,
382
+ app open with the file? If they added a token, was it placed correctly (have them re-check,
388
383
  without showing you the value)? Is the bridge plugin running and paired? Did the
389
384
  client need a restart? Walk them back through the relevant sub-step.
390
385
 
@@ -400,7 +395,8 @@ neutral placeholder built against nothing.
400
395
 
401
396
  - Create one trivial throwaway node (e.g. a small frame or text node named
402
397
  `__throughline write test`) on any page.
403
- - Confirm it landed with a quick read or `figma_capture_screenshot`.
398
+ - Confirm it landed with a quick read or `figma_capture_screenshot`. Neither needs
399
+ a token.
404
400
  - **Delete it.** If both the write and the delete succeed, writes are proven.
405
401
 
406
402
  Leave `figma.coverPageBuilt` = `false` here — that flag is set later, when the
@@ -445,7 +441,9 @@ restore point:
445
441
  `figma_get_file_versions`) or have the user name a Figma version so there's a known-good
446
442
  point to restore to. The plugin can read versions; it does not auto-create named
447
443
  versions, so if none exists, ask the user to add one ("File → Save to version
448
- history") and record that you did.
444
+ history") and record that you did. If the user skipped the access token and
445
+ the version read fails, say in one sentence that you can't read version history
446
+ without it, and have them name the version themselves.
449
447
  - **Token export** — if the repo already emits tokens, snapshot the current generated
450
448
  output (git is the natural baseline once `workspace.stage` is `local-git`+).
451
449
 
@@ -16,6 +16,10 @@ This skill needs:
16
16
  - Figma components (`components.built`) with their slot contracts, so stories
17
17
  reflect the real component APIs.
18
18
 
19
+ The Figma-touching steps (Code Connect in Step 5, the doc-card write-back in Step 6)
20
+ also need a live Figma connection — do a cheap liveness read before the first Figma
21
+ call; if it fails, offer `figma-environment-setup`.
22
+
19
23
  Strongly recommend `github` stage so Chromatic's CI integration works; it can be
20
24
  set up locally first and wired to CI when the remote exists.
21
25
 
@@ -35,7 +35,7 @@ now. If it isn't, say so plainly and offer to run the `figma-environment-setup`
35
35
  skill first: "Figma isn't connected yet — want me to set that up? It's a
36
36
  one-time thing." Don't proceed until Figma is reachable.
37
37
 
38
- Use the write mechanism recorded in `figma.mechanism` (default `console-mcp`).
38
+ The plugin writes to Figma through Console MCP. If `figma.mechanism` holds anything other than `console-mcp`, say that value is no longer supported and offer `figma-environment-setup` to reset it.
39
39
  With Console MCP, prefer `figma_execute` to create variables in scripted loops
40
40
  rather than one tool call per variable — this is dramatically more
41
41
  token-efficient for the potentially hundreds of primitives across modes.
@@ -24,7 +24,7 @@ a sensible companion step — the sheet and the synced code both reflect Figma.)
24
24
 
25
25
  Needs tokens in Figma (`tokens.semanticBuilt` true) and a live Figma connection.
26
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`.
27
+ offer `figma-environment-setup`. The plugin writes to Figma through Console MCP. If `figma.mechanism` holds anything other than `console-mcp`, say that value is no longer supported and offer `figma-environment-setup` to reset it.
28
28
 
29
29
  **Before scripting any `figma_execute`, read
30
30
  `.throughline/references/figma-scripting.md`.** This sheet builds large
@@ -66,8 +66,8 @@ it.
66
66
 
67
67
  ## Step 2 — Extract Figma variables to DTCG JSON
68
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
69
+ The plugin talks to Figma through Console MCP. If `figma.mechanism` holds anything other than `console-mcp`, say that value is no longer supported and offer `figma-environment-setup` to reset it. Using Console MCP's
70
+ full-design-system extraction (which works on any Figma plan), read
71
71
  **every variable collection** (there are now several per tier, e.g.
72
72
  `_Color/Primitive`, `Spacing/Primitive`, `Color/Semantic`, …) and normalize them
73
73
  into **DTCG-format JSON** (`$value`, `$type`, with semantic tokens expressed as
@@ -140,7 +140,10 @@ fit in the adapter output.
140
140
 
141
141
  ## Step 3 — Set up Style Dictionary + adapters
142
142
 
143
- Install and configure Style Dictionary v4 in `packages/tokens/`. For each chosen
143
+ Install Style Dictionary pinned to an exact version with
144
+ `pnpm add -D style-dictionary@4.4.0` (or the project's package manager) in
145
+ `packages/tokens/`; the generated config is verified against 4.4.0, and a newer
146
+ release can rename or add stock transforms. Then configure it. For each chosen
144
147
  platform, apply its adapter preset (per `.throughline/references/sync-adapters.md`):
145
148
  register the platform, transform group, format, and `outputReferences`
146
149
  (true for web → preserves the semantic→primitive cascade; false for native →
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radicool/throughline",
3
- "version": "0.21.3",
3
+ "version": "0.22.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": {
@@ -123,19 +123,17 @@ run, and it mirrors how the states/types map to code props.
123
123
 
124
124
  - **Always include every relevant state for the component — completeness is the
125
125
  default, not a judgment call.** A component's `state` axis must enumerate its full
126
- applicable interaction surface, not just `default`. The baseline interactive set
127
- is **default, hover, focus, active (pressed), disabled**; add the **conditional**
128
- states whenever they apply to that component: **loading** (anything that triggers
129
- async work — buttons, submit inputs), **selected** (toggles, segmented controls,
130
- list/menu items, chips), and **success / error** (validated inputs, form fields,
131
- async-result buttons). Decide *which* conditional states apply, but never drop a
132
- state that does apply to keep the matrix small.
133
- - **Button:** default, hover, focus, active (pressed), disabled — plus loading
134
- (and selected / success / error where the button supports them).
135
- - **Input / text field:** default, hover, focus, disabled — plus error, success,
136
- and (where async) loading.
137
- - **Checkbox / radio / toggle / chip:** default, hover, focus, active, disabled —
138
- plus selected (and indeterminate where it applies).
126
+ applicable interaction surface, not just `default`. The states a component owes
127
+ come from the table in `references/state-baseline.md` (see below); add the
128
+ **conditional** states whenever they apply to that component: **loading** (anything
129
+ that triggers async work — buttons, submit inputs), **selected** for components the
130
+ table does not cover (segmented controls, list/menu items), and **success / error**
131
+ (validated inputs, form fields, async-result buttons). Decide *which* conditional
132
+ states apply, but never drop a state that does apply to keep the matrix small.
133
+ - **Which components owe which states is one table**, shared with the CLI's
134
+ `state-incomplete` check: `references/state-baseline.md`, generated from
135
+ `scripts/lib/component-states.mjs`. Add loading, success / error, and
136
+ indeterminate where they apply; those are not in the table.
139
137
  - Keep these on a `state` variant axis where they change appearance meaningfully;
140
138
  each becomes a column in the set per "Component set arrangement".
141
139
  - Distinguish *component states* (part of the component's definition) from
@@ -557,8 +555,8 @@ For each generated artboard / doc card / icon grid, read the nodes back (via
557
555
  a header container whose surface fill differs from the component area (both
558
556
  token-bound). A card with no header/component segmentation is a fail.
559
557
  8. **States complete** — each component set's `state` axis includes every relevant
560
- state for that component (default/hover/focus/active/disabled plus the applicable
561
- conditional states — loading/selected/success/error), with variants (incl. each
558
+ state for that component (every state the table in `references/state-baseline.md`
559
+ owes it, plus default, plus the applicable loading/success/error), with variants (incl. each
562
560
  size) as rows and states as columns. A set shipping only `default` is a fail.
563
561
  9. **Focus state matches the library idiom** — the focus state is built as
564
562
  `project.uiFramework`'s real pattern (see "State handling"), not a house-style
@@ -581,10 +579,10 @@ For each generated artboard / doc card / icon grid, read the nodes back (via
581
579
  error: fix and re-audit like any other item. If the ring is still missing
582
580
  when the pass budget below runs out, **stop the build** rather than hand off.
583
581
  - **Scope.** It runs only for a component whose name folds (lowercase,
584
- punctuation stripped) to a Button, an Input / text field, or a Checkbox /
585
- radio / toggle / chip, the same exact-name match as the state check. A Switch
586
- or a TextInput isn't in that list, so neither Figma check reads it (the CLI's
587
- `state-incomplete` does). It also
582
+ punctuation stripped) to a name in the table in `references/state-baseline.md`
583
+ (not its exempt list), the same
584
+ exact-name match as the state check and the same list the CLI's
585
+ `state-incomplete` reads. It also
588
586
  needs `project.uiFramework` to be exactly `shadcn`, `tailwind`, `mui`,
589
587
  `vanilla-css` or `vanilla`, or absent or `null`. Anything else (`ios-swift`,
590
588
  a tier-2 library, any other value) is skipped, and the skip is said.
@@ -68,7 +68,8 @@ wrong document.
68
68
  Run this before the first write, and again right before any replace — the
69
69
  build-verify-then-replace finalize deletes the existing node, so it is the write
70
70
  that destroys work in the wrong file. The `figma-executor` agent runs it as step 1
71
- of its contract. **When a skill builds inline instead** (a host with no subagent
71
+ of its contract; the `architect` and `reviewer` agents, which read Figma but cannot
72
+ navigate, run the compare and return `BLOCKED` on a mismatch. **When a skill builds inline instead** (a host with no subagent
72
73
  dispatch, or a write-back the skill does itself), nothing runs it for you: the
73
74
  skill has to.
74
75
 
@@ -162,10 +162,12 @@ bailing or running silently.
162
162
  - `syncLayer` — `style-dictionary.config.js` or `*.style-dictionary.js` files present at the project root or within immediate subdirectories
163
163
 
164
164
  ### `figma`
165
- - `mechanism` — which write mechanism is active. One of `"console-mcp"`
166
- (default, recommended) or `"official-plugin"` (lower-setup fallback). Set by
167
- skill 0. The adapter layer in every Figma skill reads this to know which tool
168
- names to use.
165
+ - `mechanism` — which write mechanism is active. `"console-mcp"` is the only
166
+ supported value. Set by skill 0. The adapter layer in every Figma skill reads
167
+ this to know which tool names to use. A manifest written by an earlier version
168
+ may hold `"official-plugin"`; that value is unsupported (no agent or bundled
169
+ MCP server backs it), and re-running `figma-environment-setup` resets it to
170
+ `"console-mcp"`.
169
171
  - `fileKey` — the Figma file key the system writes into (extracted from the
170
172
  file URL). Lets skills target the right file without re-asking.
171
173
  - `connected` — whether setup completed successfully at least once. Note this is
@@ -0,0 +1,25 @@
1
+ <!-- GENERATED by scripts/build-state-baseline.mjs from scripts/lib/component-states.mjs. Do not edit; run: node scripts/build-state-baseline.mjs -->
2
+ # State baseline
3
+
4
+ Which interaction states a component owes. This is the one list both checks read:
5
+ `verify:check` (`state-incomplete`) reads it from `scripts/lib/component-states.mjs`,
6
+ and `figma-executor` (its state check and the `focus-indicator` check) reads
7
+ this table. CI fails when the table is stale.
8
+
9
+ A component name matches a row when it folds (lowercase, punctuation and spaces
10
+ stripped) to exactly one of that row's names. `Text Field` folds to `textfield`.
11
+ Matching is exact on purpose: `IconButton` matches nothing, and a component that
12
+ matches nothing owes no states.
13
+
14
+ | Archetype | Names | States owed |
15
+ |---|---|---|
16
+ | button | `button` | `hover`, `focus`, `active`, `disabled` |
17
+ | input | `input`, `textfield`, `textinput` | `hover`, `focus`, `disabled` |
18
+ | choice | `checkbox`, `radio`, `toggle`, `switch`, `chip` | `hover`, `focus`, `active`, `selected`, `disabled` |
19
+
20
+ Every state in the table must be a value on the component's `state` axis. On the
21
+ Figma side the matrix also carries `default` as the resting state; the CLI does not
22
+ count it. `checked` counts as `selected`, and `on` counts as `selected`, since that is what shadcn and Radix call it. Conditional states (`loading`, `error`, `success`) are not in the table:
23
+ whether one applies is a design judgment.
24
+
25
+ Names that fold to `card`, `modal`, `dialog`, `badge`, `tag` owe none, so neither check asserts states on them.
package/scripts/README.md CHANGED
@@ -9,7 +9,7 @@ tested here; copied verbatim by `token-crosswalk-builder` into the user's
9
9
  | --- | --- | --- |
10
10
  | `validate-crosswalk.mjs` | Resolve every `newToken` against the DTCG token source; assert resolved value == `newValue`, N/N. The CI gate. | `tokens:validate` |
11
11
  | `build-reverse-index.mjs` | Emit a `codeToken -> newToken` map from the crosswalk to semi-automate SCSS/Tailwind swaps. | `tokens:reverse-index` |
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 |
12
+ | `guard-token-removal.mjs` | Grep every source file the shared walker reads — `.scss`, `.css`, `.tsx?`, `.jsx?`, `.html` and the rest of `SOURCE_EXT` (minus generated + tests) for about-to-be-deleted symbols; blocks cleanup until zero references remain. | run during the cleanup phase |
13
13
  | `validate-token-output.mjs` | Assert generated token output — Swift, Kotlin, or web CSS custom properties — matches its DTCG source: authored-unit fidelity and no mode collisions everywhere, plus no leaked CSS syntax and no bare unit literals in Swift and Kotlin. Fails when no emitted symbol matches a source token, and reports match rate, unparsed lines, and unemitted tokens on every run. | `tokens:validate-output` |
14
14
  | `validate-adherence.mjs` | Assert the code *consuming* a design system still adheres to it: every referenced component exists, every literal variant value is one the system declares, and no colour, spacing, radius or type literal duplicates a token that already holds that value. `unnamed-control` fails a Button or IconButton (or a component whose doc record says `archetype: "button"`) with no accessible name: no text, and no `aria-label`, `aria-labelledby` or `title` on it or anything inside it. `colour-only-status` fails a Badge or Tag (or a record with `archetype: "badge"`) the same way, so its status isn't carried by colour alone. Both read script files only (`.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`), abstain rather than guess on anything they can't read, print how many elements they checked and abstained on, and can be switched off with `--skip`. Fails when an enabled rule had nothing to check, so a green run always means something was verified. | `adherence:check` |
15
15
  | `lib/source-scan.mjs` | Shared source-tree walker (`walk`, `DEFAULT_EXCLUDES`, `SOURCE_EXT`) plus `normalizeName`, the display-name-to-code-identifier fold. Every gate that scans a consumer's repo reads it from here rather than carrying its own copy. | copied alongside `guard-token-removal.mjs`, and in the documentation set for `validate-adherence.mjs` and `verify-check.mjs` |
@@ -29,6 +29,7 @@ tested here; copied verbatim by `token-crosswalk-builder` into the user's
29
29
  | `lib/component-states.mjs` | The archetype table and resolver behind `state-incomplete` — which baseline interaction states each kind of component owes, and when to abstain. `validate-adherence.mjs` also uses it to find Badges. | copied alongside `verify-check.mjs` and `validate-adherence.mjs` |
30
30
  | `lib/contrast.mjs` | The WCAG contrast maths and the semantic colour pairs behind `color-contrast` — which text role must clear which surface, in every mode, and where the rule deliberately abstains. | copied alongside `verify-check.mjs` |
31
31
  | `build-doc-card-builder.mjs` | Generate `references/doc-card-builder.md` from the planner + the Figma renderer template (`lib/doc-card-render.figma.js`). `--check` gates CI. | plugin-internal (not installed) |
32
+ | `build-state-baseline.mjs` | Generate `references/state-baseline.md`, the table of which components owe which interaction states, from `lib/component-states.mjs`. The Figma executor reads the table; `verify:check` reads the module. `--check` gates CI. | plugin-internal (not installed) |
32
33
  | `build-native-adapter-config.mjs` | Generate `references/native-adapter-config.md` by slicing `lib/sd-native.mjs` on its `@doc-section` markers and interleaving each fragment under its prose. Fails when module code falls outside every section, so the doc cannot silently ship incomplete. `--check` gates CI. | plugin-internal (not installed) |
33
34
 
34
35
  **Documentation scripts — install as a set.** Copying these files without
@@ -245,7 +246,7 @@ the committed `adapters/<target>/` tree plus the runtime payload
245
246
  (`references/` + `scripts/`, minus `scripts/adapters/`) into `.throughline/`,
246
247
  rewriting `${CLAUDE_PLUGIN_ROOT}` → `.throughline`:
247
248
 
248
- npx @radicool/throughline@0.21.3 init --target=cursor|codex|generic
249
+ npx @radicool/throughline@0.22.0 init --target=cursor|codex|generic
249
250
 
250
251
  See `scripts/install.mjs` (pure core + CLI + `install.test.mjs`).
251
252
 
@@ -0,0 +1,69 @@
1
+ // Generates references/state-baseline.md from scripts/lib/component-states.mjs.
2
+ // The table is the one list of which components owe which interaction states:
3
+ // `verify:check` reads the module, and `figma-executor` (which cannot import
4
+ // JavaScript) reads this rendering. Mirrors build-native-adapter-config.mjs:
5
+ // run bare to write, --check to gate CI. Zero dependencies.
6
+ import { readFileSync, writeFileSync } from 'node:fs';
7
+ import { join, dirname } from 'node:path';
8
+ import { fileURLToPath, pathToFileURL } from 'node:url';
9
+ import { ARCHETYPES, BASELINE_STATES, NAME_SYNONYMS, STATE_SYNONYMS } from './lib/component-states.mjs';
10
+
11
+ const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
12
+ export const OUT = join(REPO_ROOT, 'references', 'state-baseline.md');
13
+
14
+ export function render(states = BASELINE_STATES, synonyms = NAME_SYNONYMS, archetypes = ARCHETYPES, stateSynonyms = STATE_SYNONYMS) {
15
+ const aliases = Object.keys(stateSynonyms).map((s) => `\`${s}\` counts as \`${stateSynonyms[s]}\``).join(', and ');
16
+ const owing = archetypes.filter((a) => (states[a] ?? []).length > 0);
17
+ const rows = owing.map((a) => {
18
+ const names = Object.keys(synonyms).filter((n) => synonyms[n] === a);
19
+ return `| ${a} | ${names.map((n) => `\`${n}\``).join(', ')} | ${states[a].map((s) => `\`${s}\``).join(', ')} |`;
20
+ });
21
+ const exempt = archetypes.filter((a) => !owing.includes(a) && a !== 'other');
22
+ const exemptNames = exempt.flatMap((a) => Object.keys(synonyms).filter((n) => synonyms[n] === a));
23
+ return `<!-- GENERATED by scripts/build-state-baseline.mjs from scripts/lib/component-states.mjs. Do not edit; run: node scripts/build-state-baseline.mjs -->
24
+ # State baseline
25
+
26
+ Which interaction states a component owes. This is the one list both checks read:
27
+ \`verify:check\` (\`state-incomplete\`) reads it from \`scripts/lib/component-states.mjs\`,
28
+ and \`figma-executor\` (its state check and the \`focus-indicator\` check) reads
29
+ this table. CI fails when the table is stale.
30
+
31
+ A component name matches a row when it folds (lowercase, punctuation and spaces
32
+ stripped) to exactly one of that row's names. \`Text Field\` folds to \`textfield\`.
33
+ Matching is exact on purpose: \`IconButton\` matches nothing, and a component that
34
+ matches nothing owes no states.
35
+
36
+ | Archetype | Names | States owed |
37
+ |---|---|---|
38
+ ${rows.join('\n')}
39
+
40
+ Every state in the table must be a value on the component's \`state\` axis. On the
41
+ Figma side the matrix also carries \`default\` as the resting state; the CLI does not
42
+ count it. ${aliases}, since that is what shadcn and Radix call it. Conditional states (\`loading\`, \`error\`, \`success\`) are not in the table:
43
+ whether one applies is a design judgment.
44
+
45
+ Names that fold to ${exemptNames.map((n) => `\`${n}\``).join(', ')} owe none, so neither check asserts states on them.
46
+ `;
47
+ }
48
+
49
+ function main() {
50
+ const check = process.argv.includes('--check');
51
+ const rendered = render();
52
+ if (!check) {
53
+ writeFileSync(OUT, rendered);
54
+ console.log(`wrote ${OUT}`);
55
+ return;
56
+ }
57
+ const current = readFileSync(OUT, 'utf8');
58
+ if (current === rendered) {
59
+ console.log('references/state-baseline.md is up to date');
60
+ return;
61
+ }
62
+ console.error(
63
+ 'references/state-baseline.md is stale.\n' +
64
+ 'Run: node scripts/build-state-baseline.mjs',
65
+ );
66
+ process.exit(1);
67
+ }
68
+
69
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) main();
@@ -1,4 +1,4 @@
1
- // Repo-wide token-removal guard: grep .ts/.tsx (minus generated + tests) for
1
+ // Repo-wide token-removal guard: grep source files (minus generated + tests) for
2
2
  // references to about-to-be-deleted symbols. Zero dependencies.
3
3
  //
4
4
  // Cleanup must not proceed until this returns zero references — deleted Tailwind
@@ -33,9 +33,9 @@ export function scanFile(path, symbols) {
33
33
 
34
34
  export function guard(root, symbols, excludes = DEFAULT_EXCLUDES) {
35
35
  const findings = [];
36
- // .ts/.tsx is this guard's own contract, so the filter stays here rather
37
- // than moving into the shared walker's default.
38
- for (const file of walk(root, { excludes, fileFilter: /\.tsx?$/ })) {
36
+ // A token can be referenced from a stylesheet or markup as easily as from
37
+ // TypeScript, so this reads every file type the shared walker defaults to.
38
+ for (const file of walk(root, { excludes })) {
39
39
  for (const hit of scanFile(file, symbols)) {
40
40
  findings.push({ file: relative(root, file), ...hit });
41
41
  }
@@ -1,13 +1,18 @@
1
1
  // The archetype baseline for `state-incomplete` (`docs/specs/2026-09-12-verification-proof-bundle.md`,
2
- // step 3). Source of truth for the prose is `references/figma-component-standards.md:126-138`.
2
+ // step 3). The prose in `references/figma-component-standards.md` (State handling) points at
3
+ // `references/state-baseline.md`, which is generated from this file.
3
4
  //
4
5
  // `default` is deliberately excluded from every baseline below: documenting the
5
6
  // resting state is not documentation, and the Figma matrix requirement for it is
6
7
  // the executor's, not this table's. Conditional states (`loading`, `error`,
7
- // `success`, `selected`) are deliberately excluded too — whether one applies to a
8
- // given component is a design judgment, not something a baseline can assert.
8
+ // `success`) are deliberately excluded too — whether one applies to a given
9
+ // component is a design judgment, not something a baseline can assert. `selected`
10
+ // is not conditional for a choice control: a checkbox, radio, toggle, switch or
11
+ // chip that cannot be selected is not one.
9
12
  //
10
- // MAINTENANCE: a change to the prose baseline must change this table.
13
+ // This table is the single source for both checks. `scripts/build-state-baseline.mjs`
14
+ // renders it into `references/state-baseline.md`, which the Figma executor reads;
15
+ // CI fails when that file is stale. MAINTENANCE: change this table, then run it.
11
16
 
12
17
  import { normalizeName } from './source-scan.mjs';
13
18
 
@@ -16,7 +21,7 @@ export const ARCHETYPES = ['button', 'input', 'choice', 'card', 'modal', 'badge'
16
21
  export const BASELINE_STATES = {
17
22
  button: ['hover', 'focus', 'active', 'disabled'],
18
23
  input: ['hover', 'focus', 'disabled'],
19
- choice: ['hover', 'focus', 'active', 'disabled'],
24
+ choice: ['hover', 'focus', 'active', 'selected', 'disabled'],
20
25
  card: [],
21
26
  modal: [],
22
27
  badge: [],
@@ -40,6 +45,14 @@ export const NAME_SYNONYMS = {
40
45
  tag: 'badge',
41
46
  };
42
47
 
48
+ // Other names a recorded state goes by. shadcn and Radix call a checked checkbox
49
+ // `checked` and a pressed-in toggle `on`; both are `selected` here. `pressed` is
50
+ // not one: the standards use it for `active`.
51
+ export const STATE_SYNONYMS = {
52
+ checked: 'selected',
53
+ on: 'selected',
54
+ };
55
+
43
56
  export function resolveArchetype(record) {
44
57
  if (ARCHETYPES.includes(record.archetype)) return record.archetype;
45
58
  const bySynonym = NAME_SYNONYMS[normalizeName(record.name)];
@@ -51,6 +64,11 @@ export function missingStates(record) {
51
64
  if (archetype === null) return [];
52
65
  const baseline = BASELINE_STATES[archetype];
53
66
  if (baseline.length === 0) return [];
54
- const recordedKeys = new Set(Object.keys(record.states ?? {}).map(normalizeName));
67
+ const recordedKeys = new Set(
68
+ Object.keys(record.states ?? {}).map((key) => {
69
+ const name = normalizeName(key);
70
+ return STATE_SYNONYMS[name] ?? name;
71
+ }),
72
+ );
55
73
  return baseline.filter((state) => !recordedKeys.has(normalizeName(state)));
56
74
  }
@@ -5,9 +5,9 @@
5
5
  // extensions it yielded. The duplication was the shape #57 was filed for: two
6
6
  // definitions of one rule, agreeing today, with nothing keeping them in step.
7
7
  //
8
- // The file filter is the caller's, not a built-in: a colour scan wants
9
- // stylesheets and a symbol guard does not, and neither should have to fork the
10
- // walker to say so.
8
+ // The file filter is the caller's, not a built-in: a caller that
9
+ // wants stylesheets and HTML takes them, and one that wants only .ts/.tsx can
10
+ // narrow it, without forking the walker.
11
11
  import { readdirSync, statSync } from 'node:fs';
12
12
  import { join } from 'node:path';
13
13