@radicool/throughline 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -205,7 +205,8 @@ Future improvements and planned capabilities. Have a request? **[Open an issue](
205
205
  - **Expanded component starters** — a larger foundational kit out of the box.
206
206
  - **Richer status & auditing** — more from `/design-system-status`, including drift detection between Figma and code.
207
207
  - **Built-in accessibility checks** — automatic a11y validation when tokens and components are created, so modes can't be built with poor color contrast and components can't ship with accessibility gaps. Catches issues at creation time rather than in review.
208
- - **One library, many platforms** — support a single Figma token library that syncs to multiple platforms at once (React, Android, iOS), with a component lifecycle flexible enough to target per platformevery platform, or native-only components that don't need a React counterpart.
208
+ - **Token fan-out to more platforms** — the DTCG token source is platform-neutral, and web targets are in daily use. Native targets are **validated per build, not assumed** — 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 `tokens:validate-output` checks generated native output against its source, because the stock transforms have been measured emitting wrong-but-compiling values.
209
+ - **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.
209
210
 
210
211
  Versioning follows [Semantic Versioning](https://semver.org). The current version lives in [`.claude-plugin/plugin.json`](.claude-plugin/plugin.json); every release is recorded in [`CHANGELOG.md`](CHANGELOG.md).
211
212
 
@@ -65,6 +65,8 @@ Copy these from `.throughline/scripts/` into the user's repo **verbatim**
65
65
  (they are zero-dependency and version with the user's repo so their CI can run them):
66
66
 
67
67
  - `lib/crosswalk.mjs` → `packages/tokens/scripts/lib/crosswalk.mjs`
68
+ - `lib/dtcg.mjs` → `packages/tokens/scripts/lib/dtcg.mjs` (required by
69
+ `validate-crosswalk.mjs` — copying the validator without it breaks the gate at import)
68
70
  - `validate-crosswalk.mjs` → `packages/tokens/scripts/validate-crosswalk.mjs`
69
71
  - `build-reverse-index.mjs` → `packages/tokens/scripts/build-reverse-index.mjs`
70
72
  - `guard-token-removal.mjs` → `packages/tokens/scripts/guard-token-removal.mjs`
@@ -44,8 +44,8 @@ Ask which platform(s) the user is building for. Read
44
44
  - **Curated (Tier 1):** `shadcn`, `tailwind`, `mui`, `vanilla-css`, `ios-swift`.
45
45
  Vetted presets — high confidence.
46
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.
47
+ Android/Kotlin, Flutter, etc.). The skill generates an adapter and
48
+ verifies it against a real component before trusting it.
49
49
 
50
50
  **Always tell the user which tier they're on.** If they name a curated one, say
51
51
  it'll be solid. If they name anything else, be honest: "That's not one I have a
@@ -139,11 +139,53 @@ register the platform, transform group, format, and `outputReferences`
139
139
  flattens). Web adapters emit `:root`/`.dark` (or `[data-theme]`); the shadcn
140
140
  adapter also emits a Tailwind preset.
141
141
 
142
+ **Native targets import the shipped configuration; they do not transcribe it.**
143
+ Copy `.throughline/scripts/lib/sd-native.mjs` into
144
+ `packages/tokens/scripts/lib/` (see Step 4) and call it. The stock `ios-swift`
145
+ and `compose` transform groups emit every `px`-authored dimension at ×16 its
146
+ value — valid, compiling, silently wrong — and mishandle `color-mix()` and
147
+ dual-node DTCG the same way. Never build a native platform from a stock
148
+ `transformGroup`.
149
+
150
+ ```js
151
+ import StyleDictionary from 'style-dictionary';
152
+ import { registerNativeTransforms, nativePlatform, nativeSources }
153
+ from './scripts/lib/sd-native.mjs';
154
+
155
+ registerNativeTransforms(StyleDictionary);
156
+
157
+ for (const mode of MODES) { // e.g. ['light', 'dark']
158
+ const sd = new StyleDictionary({
159
+ source: nativeSources(sourcesFor(mode)), // guards against a mode collapse
160
+ preprocessors: ['dtcg/resolve-dual-node'],
161
+ platforms: {
162
+ ios: nativePlatform({ platform: 'ios-swift', buildPath: `ios/${mode}/` }),
163
+ // android also requires packageName:
164
+ // android: nativePlatform({ platform: 'android-kotlin',
165
+ // buildPath: `android/${mode}/`, packageName: 'com.example.tokens' }),
166
+ },
167
+ });
168
+ await sd.buildAllPlatforms();
169
+ }
170
+ ```
171
+
172
+ **One build per mode combination, and never a glob.** Style Dictionary dedupes
173
+ by dot-path, so a single build over the whole token directory collapses light
174
+ and dark into whichever file sorted last, silently dropping a mode. Passing each
175
+ mode's sources through `nativeSources` turns that into a thrown error naming the
176
+ colliding paths. The `dtcg/resolve-dual-node` preprocessor throws too, on its
177
+ own collision: a dual node's hoisted child renamed to a camel-joined name that
178
+ an existing sibling or an earlier hoist in the same pass already has. Then run
179
+ `tokens:validate-output` against each generated file with that same source
180
+ list. See `.throughline/references/native-adapter-config.md`.
181
+
142
182
  **Execution model — subagent dispatch with model routing.** Generating each
143
183
  platform's output is independent and verifiable. If your host supports subagent
144
184
  dispatch, dispatch **one `code-executor` per adapter** — each produces its
145
- platform's files and verifies them (the config builds, the expected files
146
- appear, references resolve for web / flatten for native) then a **`reviewer`**
185
+ platform's files and verifies them (for web: the config builds, the expected
186
+ files appear, references resolve; for native: `tokens:validate-output` passes
187
+ "the config builds" is not verification, it is the condition under which all
188
+ four known native failure modes ship silently) — then a **`reviewer`**
147
189
  to check each before combining. Choose each subagent's model from its role tier
148
190
  per `.throughline/references/agent-routing.md` (`code-executor` → fast,
149
191
  `reviewer` → balanced), and only dispatch once each adapter's spec is complete
@@ -159,6 +201,24 @@ as **build artifacts** — regenerated every sync, never hand-edited. Wire
159
201
  `packages/tokens/package.json` to export them so the UI package, Storybook, and
160
202
  any future app consume them.
161
203
 
204
+ **Install the native token toolkit — all four files, as a set.** Copy
205
+ `.throughline/scripts/validate-token-output.mjs` into
206
+ `packages/tokens/scripts/`, and
207
+ `.throughline/scripts/lib/dtcg.mjs`,
208
+ `.throughline/scripts/lib/native-literal.mjs`, and
209
+ `.throughline/scripts/lib/sd-native.mjs` into
210
+ `packages/tokens/scripts/lib/`. `sd-native.mjs` and the validator both import
211
+ `dtcg.mjs` and `native-literal.mjs`, so copying any of them without the others
212
+ breaks at import. Then register the gate so it stays live on every future sync:
213
+
214
+ ```json
215
+ "tokens:validate-output": "node scripts/validate-token-output.mjs --min-match 1"
216
+ ```
217
+
218
+ Invoke it once per native output file, passing the same `--source` list that
219
+ file's build used. `--min-match 1` is what makes it a gate: the flag defaults to
220
+ `0.5`, so without it a 60% match rate exits `0`.
221
+
162
222
  ## Step 4.5 — Icon code sync (install check + custom SVGR)
163
223
 
164
224
  Icons reach code differently from tokens, so handle them here if the system has
@@ -69,6 +69,8 @@ Copy these from `.throughline/scripts/` into the user's repo **verbatim**
69
69
  (they are zero-dependency and version with the user's repo so their CI can run them):
70
70
 
71
71
  - `lib/crosswalk.mjs` → `packages/tokens/scripts/lib/crosswalk.mjs`
72
+ - `lib/dtcg.mjs` → `packages/tokens/scripts/lib/dtcg.mjs` (required by
73
+ `validate-crosswalk.mjs` — copying the validator without it breaks the gate at import)
72
74
  - `validate-crosswalk.mjs` → `packages/tokens/scripts/validate-crosswalk.mjs`
73
75
  - `build-reverse-index.mjs` → `packages/tokens/scripts/build-reverse-index.mjs`
74
76
  - `guard-token-removal.mjs` → `packages/tokens/scripts/guard-token-removal.mjs`
@@ -48,8 +48,8 @@ Ask which platform(s) the user is building for. Read
48
48
  - **Curated (Tier 1):** `shadcn`, `tailwind`, `mui`, `vanilla-css`, `ios-swift`.
49
49
  Vetted presets — high confidence.
50
50
  - **Generated (Tier 2):** any other framework (Ant Design, Chakra, HeroUI,
51
- Android/Kotlin, Flutter, etc.). The skill generates an adapter and verifies it
52
- against a real component before trusting it.
51
+ Android/Kotlin, Flutter, etc.). The skill generates an adapter and
52
+ verifies it against a real component before trusting it.
53
53
 
54
54
  **Always tell the user which tier they're on.** If they name a curated one, say
55
55
  it'll be solid. If they name anything else, be honest: "That's not one I have a
@@ -143,11 +143,53 @@ register the platform, transform group, format, and `outputReferences`
143
143
  flattens). Web adapters emit `:root`/`.dark` (or `[data-theme]`); the shadcn
144
144
  adapter also emits a Tailwind preset.
145
145
 
146
+ **Native targets import the shipped configuration; they do not transcribe it.**
147
+ Copy `.throughline/scripts/lib/sd-native.mjs` into
148
+ `packages/tokens/scripts/lib/` (see Step 4) and call it. The stock `ios-swift`
149
+ and `compose` transform groups emit every `px`-authored dimension at ×16 its
150
+ value — valid, compiling, silently wrong — and mishandle `color-mix()` and
151
+ dual-node DTCG the same way. Never build a native platform from a stock
152
+ `transformGroup`.
153
+
154
+ ```js
155
+ import StyleDictionary from 'style-dictionary';
156
+ import { registerNativeTransforms, nativePlatform, nativeSources }
157
+ from './scripts/lib/sd-native.mjs';
158
+
159
+ registerNativeTransforms(StyleDictionary);
160
+
161
+ for (const mode of MODES) { // e.g. ['light', 'dark']
162
+ const sd = new StyleDictionary({
163
+ source: nativeSources(sourcesFor(mode)), // guards against a mode collapse
164
+ preprocessors: ['dtcg/resolve-dual-node'],
165
+ platforms: {
166
+ ios: nativePlatform({ platform: 'ios-swift', buildPath: `ios/${mode}/` }),
167
+ // android also requires packageName:
168
+ // android: nativePlatform({ platform: 'android-kotlin',
169
+ // buildPath: `android/${mode}/`, packageName: 'com.example.tokens' }),
170
+ },
171
+ });
172
+ await sd.buildAllPlatforms();
173
+ }
174
+ ```
175
+
176
+ **One build per mode combination, and never a glob.** Style Dictionary dedupes
177
+ by dot-path, so a single build over the whole token directory collapses light
178
+ and dark into whichever file sorted last, silently dropping a mode. Passing each
179
+ mode's sources through `nativeSources` turns that into a thrown error naming the
180
+ colliding paths. The `dtcg/resolve-dual-node` preprocessor throws too, on its
181
+ own collision: a dual node's hoisted child renamed to a camel-joined name that
182
+ an existing sibling or an earlier hoist in the same pass already has. Then run
183
+ `tokens:validate-output` against each generated file with that same source
184
+ list. See `.throughline/references/native-adapter-config.md`.
185
+
146
186
  **Execution model — subagent dispatch with model routing.** Generating each
147
187
  platform's output is independent and verifiable. If your host supports subagent
148
188
  dispatch, dispatch **one `code-executor` per adapter** — each produces its
149
- platform's files and verifies them (the config builds, the expected files
150
- appear, references resolve for web / flatten for native) then a **`reviewer`**
189
+ platform's files and verifies them (for web: the config builds, the expected
190
+ files appear, references resolve; for native: `tokens:validate-output` passes
191
+ "the config builds" is not verification, it is the condition under which all
192
+ four known native failure modes ship silently) — then a **`reviewer`**
151
193
  to check each before combining. Choose each subagent's model from its role tier
152
194
  per `.throughline/references/agent-routing.md` (`code-executor` → fast,
153
195
  `reviewer` → balanced), and only dispatch once each adapter's spec is complete
@@ -163,6 +205,24 @@ as **build artifacts** — regenerated every sync, never hand-edited. Wire
163
205
  `packages/tokens/package.json` to export them so the UI package, Storybook, and
164
206
  any future app consume them.
165
207
 
208
+ **Install the native token toolkit — all four files, as a set.** Copy
209
+ `.throughline/scripts/validate-token-output.mjs` into
210
+ `packages/tokens/scripts/`, and
211
+ `.throughline/scripts/lib/dtcg.mjs`,
212
+ `.throughline/scripts/lib/native-literal.mjs`, and
213
+ `.throughline/scripts/lib/sd-native.mjs` into
214
+ `packages/tokens/scripts/lib/`. `sd-native.mjs` and the validator both import
215
+ `dtcg.mjs` and `native-literal.mjs`, so copying any of them without the others
216
+ breaks at import. Then register the gate so it stays live on every future sync:
217
+
218
+ ```json
219
+ "tokens:validate-output": "node scripts/validate-token-output.mjs --min-match 1"
220
+ ```
221
+
222
+ Invoke it once per native output file, passing the same `--source` list that
223
+ file's build used. `--min-match 1` is what makes it a gate: the flag defaults to
224
+ `0.5`, so without it a 60% match rate exits `0`.
225
+
166
226
  ## Step 4.5 — Icon code sync (install check + custom SVGR)
167
227
 
168
228
  Icons reach code differently from tokens, so handle them here if the system has
@@ -65,6 +65,8 @@ Copy these from `.throughline/scripts/` into the user's repo **verbatim**
65
65
  (they are zero-dependency and version with the user's repo so their CI can run them):
66
66
 
67
67
  - `lib/crosswalk.mjs` → `packages/tokens/scripts/lib/crosswalk.mjs`
68
+ - `lib/dtcg.mjs` → `packages/tokens/scripts/lib/dtcg.mjs` (required by
69
+ `validate-crosswalk.mjs` — copying the validator without it breaks the gate at import)
68
70
  - `validate-crosswalk.mjs` → `packages/tokens/scripts/validate-crosswalk.mjs`
69
71
  - `build-reverse-index.mjs` → `packages/tokens/scripts/build-reverse-index.mjs`
70
72
  - `guard-token-removal.mjs` → `packages/tokens/scripts/guard-token-removal.mjs`
@@ -44,8 +44,8 @@ Ask which platform(s) the user is building for. Read
44
44
  - **Curated (Tier 1):** `shadcn`, `tailwind`, `mui`, `vanilla-css`, `ios-swift`.
45
45
  Vetted presets — high confidence.
46
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.
47
+ Android/Kotlin, Flutter, etc.). The skill generates an adapter and
48
+ verifies it against a real component before trusting it.
49
49
 
50
50
  **Always tell the user which tier they're on.** If they name a curated one, say
51
51
  it'll be solid. If they name anything else, be honest: "That's not one I have a
@@ -139,11 +139,53 @@ register the platform, transform group, format, and `outputReferences`
139
139
  flattens). Web adapters emit `:root`/`.dark` (or `[data-theme]`); the shadcn
140
140
  adapter also emits a Tailwind preset.
141
141
 
142
+ **Native targets import the shipped configuration; they do not transcribe it.**
143
+ Copy `.throughline/scripts/lib/sd-native.mjs` into
144
+ `packages/tokens/scripts/lib/` (see Step 4) and call it. The stock `ios-swift`
145
+ and `compose` transform groups emit every `px`-authored dimension at ×16 its
146
+ value — valid, compiling, silently wrong — and mishandle `color-mix()` and
147
+ dual-node DTCG the same way. Never build a native platform from a stock
148
+ `transformGroup`.
149
+
150
+ ```js
151
+ import StyleDictionary from 'style-dictionary';
152
+ import { registerNativeTransforms, nativePlatform, nativeSources }
153
+ from './scripts/lib/sd-native.mjs';
154
+
155
+ registerNativeTransforms(StyleDictionary);
156
+
157
+ for (const mode of MODES) { // e.g. ['light', 'dark']
158
+ const sd = new StyleDictionary({
159
+ source: nativeSources(sourcesFor(mode)), // guards against a mode collapse
160
+ preprocessors: ['dtcg/resolve-dual-node'],
161
+ platforms: {
162
+ ios: nativePlatform({ platform: 'ios-swift', buildPath: `ios/${mode}/` }),
163
+ // android also requires packageName:
164
+ // android: nativePlatform({ platform: 'android-kotlin',
165
+ // buildPath: `android/${mode}/`, packageName: 'com.example.tokens' }),
166
+ },
167
+ });
168
+ await sd.buildAllPlatforms();
169
+ }
170
+ ```
171
+
172
+ **One build per mode combination, and never a glob.** Style Dictionary dedupes
173
+ by dot-path, so a single build over the whole token directory collapses light
174
+ and dark into whichever file sorted last, silently dropping a mode. Passing each
175
+ mode's sources through `nativeSources` turns that into a thrown error naming the
176
+ colliding paths. The `dtcg/resolve-dual-node` preprocessor throws too, on its
177
+ own collision: a dual node's hoisted child renamed to a camel-joined name that
178
+ an existing sibling or an earlier hoist in the same pass already has. Then run
179
+ `tokens:validate-output` against each generated file with that same source
180
+ list. See `.throughline/references/native-adapter-config.md`.
181
+
142
182
  **Execution model — subagent dispatch with model routing.** Generating each
143
183
  platform's output is independent and verifiable. If your host supports subagent
144
184
  dispatch, dispatch **one `code-executor` per adapter** — each produces its
145
- platform's files and verifies them (the config builds, the expected files
146
- appear, references resolve for web / flatten for native) then a **`reviewer`**
185
+ platform's files and verifies them (for web: the config builds, the expected
186
+ files appear, references resolve; for native: `tokens:validate-output` passes
187
+ "the config builds" is not verification, it is the condition under which all
188
+ four known native failure modes ship silently) — then a **`reviewer`**
147
189
  to check each before combining. Choose each subagent's model from its role tier
148
190
  per `.throughline/references/agent-routing.md` (`code-executor` → fast,
149
191
  `reviewer` → balanced), and only dispatch once each adapter's spec is complete
@@ -159,6 +201,24 @@ as **build artifacts** — regenerated every sync, never hand-edited. Wire
159
201
  `packages/tokens/package.json` to export them so the UI package, Storybook, and
160
202
  any future app consume them.
161
203
 
204
+ **Install the native token toolkit — all four files, as a set.** Copy
205
+ `.throughline/scripts/validate-token-output.mjs` into
206
+ `packages/tokens/scripts/`, and
207
+ `.throughline/scripts/lib/dtcg.mjs`,
208
+ `.throughline/scripts/lib/native-literal.mjs`, and
209
+ `.throughline/scripts/lib/sd-native.mjs` into
210
+ `packages/tokens/scripts/lib/`. `sd-native.mjs` and the validator both import
211
+ `dtcg.mjs` and `native-literal.mjs`, so copying any of them without the others
212
+ breaks at import. Then register the gate so it stays live on every future sync:
213
+
214
+ ```json
215
+ "tokens:validate-output": "node scripts/validate-token-output.mjs --min-match 1"
216
+ ```
217
+
218
+ Invoke it once per native output file, passing the same `--source` list that
219
+ file's build used. `--min-match 1` is what makes it a gate: the flag defaults to
220
+ `0.5`, so without it a 60% match rate exits `0`.
221
+
162
222
  ## Step 4.5 — Icon code sync (install check + custom SVGR)
163
223
 
164
224
  Icons reach code differently from tokens, so handle them here if the system has
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radicool/throughline",
3
- "version": "0.15.0",
3
+ "version": "0.17.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": {
@@ -291,8 +291,8 @@ bailing or running silently.
291
291
 
292
292
  ### `sync`
293
293
  - `platforms` — array of adapter targets configured, e.g.
294
- `["shadcn", "ios-swift"]`. Curated adapters map to vetted presets; any other
295
- name is a generated (Tier 2) adapter.
294
+ `["shadcn", "android-kotlin"]`. Curated adapters map to vetted presets; any
295
+ other name is a generated (Tier 2) adapter.
296
296
  - `customAdapters` — names of validated generated adapters saved to
297
297
  `packages/tokens/adapters/` for reuse, so future syncs don't regenerate them.
298
298
  - `lastRun` — ISO timestamp the sync command last ran.