@motion-proto/live-tokens 0.72.1 → 0.74.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 (81) hide show
  1. package/.claude/skills/live-tokens-build-page/SKILL.md +37 -2
  2. package/.claude/skills/live-tokens-build-page/references/layout-sources.md +48 -0
  3. package/.claude/skills/live-tokens-check-compliance/SKILL.md +2 -2
  4. package/.claude/skills/live-tokens-create-component/SKILL.md +4 -4
  5. package/.claude/skills/live-tokens-create-theme/SKILL.md +80 -0
  6. package/.claude/skills/live-tokens-create-theme/references/design-directions.md +94 -0
  7. package/.claude/skills/live-tokens-pick-component/SKILL.md +1 -1
  8. package/.claude/skills/live-tokens-set-colors/SKILL.md +140 -0
  9. package/.claude/skills/live-tokens-set-colors/references/color-anchors.md +80 -0
  10. package/.claude/skills/{live-tokens-adjust-geometry → live-tokens-set-geometry}/SKILL.md +12 -7
  11. package/.claude/skills/live-tokens-set-geometry/references/geometry-anchors.md +61 -0
  12. package/.claude/skills/{live-tokens-pair-fonts → live-tokens-set-type}/SKILL.md +18 -15
  13. package/.claude/skills/live-tokens-set-type/references/type-anchors.md +60 -0
  14. package/CHANGELOG.md +141 -0
  15. package/README.md +24 -15
  16. package/bin/check-page.mjs +3 -3
  17. package/bin/cli.mjs +92 -55
  18. package/bin/lib/liveState.mjs +110 -0
  19. package/bin/save-theme.mjs +177 -0
  20. package/bin/set-colors.mjs +191 -0
  21. package/bin/{adjust.mjs → set-geometry.mjs} +18 -50
  22. package/bin/{set-fonts.mjs → set-type.mjs} +21 -55
  23. package/dist-plugin/{chunk-RIXO2E55.js → chunk-7VRTBGJT.js} +1 -1
  24. package/dist-plugin/{chunk-YLCOIGQC.js → chunk-V3YF6CGT.js} +56 -2
  25. package/dist-plugin/index.cjs +58 -3
  26. package/dist-plugin/index.js +11 -11
  27. package/dist-plugin/migrateData/index.cjs +56 -1
  28. package/dist-plugin/migrateData/index.js +2 -2
  29. package/dist-plugin/{generateColorsAndType → setColors}/index.cjs +1109 -1071
  30. package/dist-plugin/{generateColorsAndType → setColors}/index.d.cts +33 -24
  31. package/dist-plugin/{generateColorsAndType → setColors}/index.d.ts +33 -24
  32. package/dist-plugin/{generateColorsAndType → setColors}/index.js +45 -63
  33. package/dist-plugin/{adjust → setGeometry}/index.cjs +60 -5
  34. package/dist-plugin/{adjust → setGeometry}/index.d.cts +1 -1
  35. package/dist-plugin/{adjust → setGeometry}/index.d.ts +1 -1
  36. package/dist-plugin/{adjust → setGeometry}/index.js +1 -1
  37. package/dist-plugin/{fontPairing → setType}/index.cjs +4 -4
  38. package/dist-plugin/{fontPairing → setType}/index.d.cts +1 -1
  39. package/dist-plugin/{fontPairing → setType}/index.d.ts +1 -1
  40. package/dist-plugin/{themeTypes-DSV3Zisf.d.cts → themeTypes-BxRtuN5V.d.cts} +1 -1
  41. package/dist-plugin/{themeTypes-DSV3Zisf.d.ts → themeTypes-BxRtuN5V.d.ts} +1 -1
  42. package/package.json +10 -2
  43. package/src/editor/core/themes/{generateColorsAndType.ts → buildColors.ts} +82 -96
  44. package/src/editor/core/themes/migrations/2026-09-03-drop-legacy-component-keys.ts +62 -0
  45. package/src/editor/core/themes/migrations/index.ts +2 -0
  46. package/src/editor/docs/content/creating-components.md +13 -0
  47. package/src/editor/docs/content/themes-workflow.md +2 -2
  48. package/src/editor/docs/content.generated.ts +2 -2
  49. package/src/editor/overlay/LiveEditorOverlay.svelte +519 -28
  50. package/src/editor/skill-atlas/SkillAtlas.svelte +836 -0
  51. package/src/editor/skill-atlas/SkillAtlas.svelte.d.ts +4 -0
  52. package/src/editor/skill-atlas/SourcePane.svelte +364 -0
  53. package/src/editor/skill-atlas/TreeNodeCard.svelte +206 -0
  54. package/src/editor/skill-atlas/skillSources.generated.ts +45 -0
  55. package/src/editor/skill-atlas/skillSources.ts +1 -0
  56. package/src/editor/skill-atlas/skillTrees.ts +3844 -0
  57. package/src/editor/skill-atlas/types.ts +65 -0
  58. package/src/live-tokens/data/colors-and-type/autumn.json +1 -37
  59. package/src/live-tokens/data/colors-and-type/default.json +1 -37
  60. package/src/live-tokens/data/colors-and-type/halloween.json +1 -37
  61. package/src/live-tokens/data/colors-and-type/midnight-study.json +1 -37
  62. package/src/live-tokens/data/colors-and-type/ocean.json +1 -37
  63. package/src/live-tokens/data/colors-and-type/royal-velvet.json +1 -37
  64. package/src/live-tokens/data/colors-and-type/sketchy.json +1 -37
  65. package/src/live-tokens/data/colors-and-type/spring-meadow.json +1 -37
  66. package/src/live-tokens/data/colors-and-type/sunset.json +1 -37
  67. package/src/live-tokens/data/themes/autumn.json +1 -37
  68. package/src/live-tokens/data/themes/halloween.json +1 -37
  69. package/src/live-tokens/data/themes/midnight-study.json +1 -37
  70. package/src/live-tokens/data/themes/ocean.json +1 -37
  71. package/src/live-tokens/data/themes/royal-velvet.json +1 -37
  72. package/src/live-tokens/data/themes/sketchy.json +1 -37
  73. package/src/live-tokens/data/themes/spring-meadow.json +1 -37
  74. package/src/live-tokens/data/themes/sunset.json +1 -37
  75. package/src/live-tokens/data/tokens.generated.css +0 -36
  76. package/.claude/skills/live-tokens-generate-theme/SKILL.md +0 -156
  77. package/.claude/skills/live-tokens-generate-theme/references/mood-vocabulary.md +0 -43
  78. package/.claude/skills/live-tokens-generate-theme/references/named-themes.md +0 -18
  79. package/.claude/skills/live-tokens-generate-theme/references/style-vocabulary.md +0 -35
  80. package/bin/generate-theme.mjs +0 -260
  81. /package/dist-plugin/{fontPairing → setType}/index.js +0 -0
@@ -0,0 +1,191 @@
1
+ // `live-tokens set-colors` worker.
2
+ //
3
+ // Reads a base color file (JSON), builds the theme's whole color state via the
4
+ // compiled engine (dist-plugin/setColors — the CLI never imports TS sources),
5
+ // enforces the AA contrast gate, and writes the result into
6
+ // `colors-and-type/_working.json`: the same buffer the editor's own palette
7
+ // edits land in, so a recolor is an unsaved edit the user keeps by saving the
8
+ // open theme or by running `save-theme`. Named colors-and-type files, themes,
9
+ // tokens.css and fonts.css are never touched, and nothing is activated.
10
+ //
11
+ // Non-color content carries forward from the live colors and type, so a recolor
12
+ // replaces the color identity and nothing else. Swatch gradients ride along
13
+ // when user-tuned; stock ones are rebuilt from the new families by the engine.
14
+
15
+ import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
16
+ import { dirname, join, relative, resolve } from 'node:path';
17
+ import { fileURLToPath } from 'node:url';
18
+
19
+ import {
20
+ readActiveTheme,
21
+ readLiveColorsAndType,
22
+ readSavedColorsAndType,
23
+ sameContent,
24
+ savedColorsAndTypeSource,
25
+ } from './lib/liveState.mjs';
26
+
27
+ const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
28
+ const ENGINE = resolve(pkgRoot, 'dist-plugin/setColors/index.js');
29
+
30
+ const SOURCE_LABELS = {
31
+ working: 'your unsaved edits',
32
+ theme: 'the open theme',
33
+ default: 'the package default',
34
+ };
35
+
36
+ async function loadEngine() {
37
+ if (!existsSync(ENGINE)) {
38
+ throw new Error(
39
+ `setColors engine not found at ${relative(process.cwd(), ENGINE)}. ` +
40
+ `Build the plugin first (npm run build:plugin).`,
41
+ );
42
+ }
43
+ return import(ENGINE);
44
+ }
45
+
46
+ function readJson(path) {
47
+ return JSON.parse(readFileSync(path, 'utf8'));
48
+ }
49
+
50
+ /** `engine` is a test seam; the CLI always runs the compiled bundle. */
51
+ export async function runSetColors({
52
+ baseColorsPath,
53
+ dryRun = false,
54
+ root = process.cwd(),
55
+ colorsAndTypeDir,
56
+ themesDir,
57
+ engine,
58
+ } = {}) {
59
+ const { buildColors, resolveDataDirs } = engine ?? (await loadEngine());
60
+
61
+ const fullPath = resolve(root, baseColorsPath);
62
+ if (!existsSync(fullPath)) {
63
+ throw new Error(`base color file not found at ${relative(root, fullPath)}`);
64
+ }
65
+ let input;
66
+ try {
67
+ input = readJson(fullPath);
68
+ } catch (err) {
69
+ throw new Error(`base color file is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
70
+ }
71
+
72
+ const resolved = colorsAndTypeDir && themesDir ? null : resolveDataDirs();
73
+ const colorsDir = colorsAndTypeDir ?? resolved.colorsAndTypeDir;
74
+ const themes = themesDir ?? resolved.themesDir;
75
+ if (!existsSync(colorsDir)) {
76
+ throw new Error(`no colors and type at ${relative(root, colorsDir)}. Run the dev server once to create it.`);
77
+ }
78
+
79
+ const active = readActiveTheme(themes);
80
+ const { colorsAndType: live, source } = readLiveColorsAndType(colorsDir, active);
81
+
82
+ const { colors, report } = buildColors(input, {
83
+ cssVariables: live.cssVariables,
84
+ gradients: live.gradients,
85
+ });
86
+
87
+ const next = {
88
+ ...live,
89
+ ...colors,
90
+ updatedAt: new Date().toISOString(),
91
+ schemaVersion: live.schemaVersion,
92
+ };
93
+
94
+ const workingPath = join(colorsDir, '_working.json');
95
+ const saved = readSavedColorsAndType(colorsDir, active);
96
+
97
+ // Returning the buffer to what the layer under it already holds is a discard,
98
+ // not an edit — the same call the dev server's own PUT makes.
99
+ const backToSaved = saved !== null && sameContent(next, saved);
100
+ let wrote = null;
101
+ if (!dryRun) {
102
+ if (backToSaved) {
103
+ if (existsSync(workingPath)) rmSync(workingPath);
104
+ wrote = 'cleared';
105
+ } else {
106
+ writeFileSync(workingPath, JSON.stringify(next, null, 2) + '\n');
107
+ wrote = 'buffer';
108
+ }
109
+ }
110
+
111
+ return {
112
+ baseColorsPath: fullPath,
113
+ colorsAndTypeDir: colorsDir,
114
+ workingPath,
115
+ openTheme: active?.slug ?? null,
116
+ source,
117
+ // Which layer the discard compared against, so the report can name it. It
118
+ // is the open theme only when one is open; with none, it is the default the
119
+ // package ships, and calling that "the open theme" named a file nobody had
120
+ // opened.
121
+ savedSource: savedColorsAndTypeSource(active),
122
+ // The base color file's `name` used to pick a theme file name. This verb
123
+ // writes no file of its own now, so it names nothing; say so rather than
124
+ // drop it in silence.
125
+ ignoredName: input.name === undefined ? null : String(input.name),
126
+ dryRun,
127
+ wrote,
128
+ report,
129
+ };
130
+ }
131
+
132
+ export function formatSetColorsResult(result) {
133
+ const root = process.cwd();
134
+ const lines = [];
135
+
136
+ const from =
137
+ result.source === 'theme' && result.openTheme
138
+ ? `theme "${result.openTheme}"`
139
+ : SOURCE_LABELS[result.source];
140
+ const verb = result.dryRun ? 'Would replace' : 'Replaced';
141
+ lines.push(`${verb} the color identity, carrying everything else forward from ${from}.`);
142
+ if (result.ignoredName !== null) {
143
+ lines.push(
144
+ `Ignored "name": "${result.ignoredName}". The base color file no longer names a theme; ` +
145
+ `name it when you run save-theme.`,
146
+ );
147
+ }
148
+
149
+ lines.push(`\nContrast report (${result.report.scheme} scheme):`);
150
+ const width = Math.max(...result.report.checks.map((c) => c.textVar.length));
151
+ for (const c of result.report.checks) {
152
+ const mark = c.pass ? '✓' : '✗';
153
+ const fixed = c.corrected ? ' (auto-corrected)' : '';
154
+ lines.push(
155
+ ` ${mark} ${c.textVar.padEnd(width)} ${c.ratio.toFixed(2).padStart(6)}:1 vs ${c.against} floor ${c.floor}:1${fixed}`,
156
+ );
157
+ }
158
+ if (result.report.failures.length > 0) {
159
+ lines.push(`\nUnmet floors — adjust the base colors and re-run:`);
160
+ for (const f of result.report.failures) lines.push(` ! ${f}`);
161
+ }
162
+
163
+ lines.push(
164
+ result.report.gradients === 'carried'
165
+ ? '\nGradients: kept your tuned swatch gradients.'
166
+ : '\nGradients: swatch tokens rebuilt from the theme families.',
167
+ );
168
+ lines.push(`Shadows: ${result.report.shadows}; carried geometry kept.`);
169
+ if (result.report.canvasGradient) {
170
+ lines.push(`Canvas sky: ${result.report.canvasGradient}.`);
171
+ }
172
+
173
+ if (result.wrote === 'buffer') {
174
+ lines.push(
175
+ `\nReload the app to see it. This is an unsaved edit: save the open theme in the ` +
176
+ `editor's Theme panel to keep it, or run save-theme to write a new one.`,
177
+ );
178
+ } else if (result.wrote === 'cleared') {
179
+ const held =
180
+ result.savedSource === 'theme' ? `theme "${result.openTheme}"` : 'the package default';
181
+ lines.push(
182
+ result.source === 'working'
183
+ ? `\nThat is what ${held} already holds, so the unsaved buffer was discarded. ` +
184
+ `Reload the app to see it.`
185
+ : `\nThat is what ${held} already holds, and there was no unsaved buffer, so nothing was written.`,
186
+ );
187
+ } else if (result.dryRun) {
188
+ lines.push(`\nDry run: nothing written under ${relative(root, result.colorsAndTypeDir)}.`);
189
+ }
190
+ return lines.join('\n');
191
+ }
@@ -1,19 +1,20 @@
1
- // `live-tokens adjust` worker.
1
+ // `live-tokens set-geometry` worker.
2
2
  //
3
3
  // Reads an ops file (JSON), applies it to every component's LIVE config via the
4
- // compiled engine (dist-plugin/adjust — the CLI never imports TS sources), and
4
+ // compiled engine (dist-plugin/setGeometry — the CLI never imports TS sources), and
5
5
  // writes the result into that component's `_working.json` buffer: the same slot
6
6
  // the editor's own edits land in, so an adjustment is an unsaved edit the user
7
7
  // saves into a theme when they want to keep it. `default.json`, named preset
8
8
  // files, themes, colors and type, and tokens.css are never touched.
9
9
 
10
- import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
10
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
11
11
  import { dirname, join, relative, resolve } from 'node:path';
12
12
  import { fileURLToPath } from 'node:url';
13
13
 
14
+ import { componentNames, readActiveTheme, readLiveComponentConfigs } from './lib/liveState.mjs';
15
+
14
16
  const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
15
- const ENGINE = resolve(pkgRoot, 'dist-plugin/adjust/index.js');
16
- const packageThemesDir = join(pkgRoot, 'src/live-tokens/data/themes');
17
+ const ENGINE = resolve(pkgRoot, 'dist-plugin/setGeometry/index.js');
17
18
 
18
19
  const SOURCE_LABELS = {
19
20
  working: 'your unsaved edits',
@@ -30,7 +31,7 @@ const SKIP_LABELS = [
30
31
  async function loadEngine() {
31
32
  if (!existsSync(ENGINE)) {
32
33
  throw new Error(
33
- `adjust engine not found at ${relative(process.cwd(), ENGINE)}. ` +
34
+ `setGeometry engine not found at ${relative(process.cwd(), ENGINE)}. ` +
34
35
  `Build the plugin first (npm run build:plugin).`,
35
36
  );
36
37
  }
@@ -41,10 +42,6 @@ function readJson(path) {
41
42
  return JSON.parse(readFileSync(path, 'utf8'));
42
43
  }
43
44
 
44
- function readJsonIfExists(path) {
45
- return existsSync(path) ? readJson(path) : null;
46
- }
47
-
48
45
  /** Successive ops can touch the same alias (soften, then pill the buttons);
49
46
  * the report shows one entry per alias, first `from` to last `to`. */
50
47
  function collapseChanges(changes) {
@@ -57,49 +54,20 @@ function collapseChanges(changes) {
57
54
  return [...byVariable.values()].filter((c) => c.from !== c.to);
58
55
  }
59
56
 
60
- /** The open theme, read the way every other door reads it: the local file
61
- * first, then the copy the installed package ships. */
62
- function readActiveTheme(themesDir) {
63
- if (!themesDir) return null;
64
- const slug = readJsonIfExists(join(themesDir, '_active.json'))?.activeFile ?? 'default';
65
- const theme =
66
- readJsonIfExists(join(themesDir, `${slug}.json`)) ??
67
- readJsonIfExists(join(packageThemesDir, `${slug}.json`));
68
- return theme ? { slug, theme } : null;
69
- }
70
-
71
- /** Each component's live config, and whether a buffer or the open document
72
- * answered. The document's own copy falls back to the shipped default for a
73
- * component it somehow lacks, which reports as the document either way.
74
- * Mirrors the dev server's `resolveLiveComponentConfig`, so the CLI adjusts
75
- * what the page runs. */
76
- function readLiveConfigs(dir, active) {
57
+ /** A config document names its own component, and a theme-embedded copy can
58
+ * omit it, so stamp it back on before the buffer is written from it. */
59
+ function stampedLiveConfigs(dir, active) {
60
+ const { configs: live, sources } = readLiveComponentConfigs(dir, active);
77
61
  const configs = {};
78
- const sources = {};
79
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
80
- if (!entry.isDirectory()) continue;
81
- const comp = entry.name;
82
- const componentDir = join(dir, comp);
83
- const working = readJsonIfExists(join(componentDir, '_working.json'));
84
- // Every theme carries every component by value, but this CLI reads the
85
- // file straight off disk, ahead of any server-side fill — a theme
86
- // written before this component existed can still omit it, so a missing
87
- // entry falls back to the shipped default the same way the theme's own
88
- // fill would.
89
- const embedded =
90
- active?.theme?.componentConfigs?.[comp] ?? readJsonIfExists(join(componentDir, 'default.json'));
91
- const config = working ?? embedded;
92
- if (!config) {
93
- throw new Error(`component "${comp}": default.json is missing`);
94
- }
95
- configs[comp] = { ...config, component: comp };
96
- sources[comp] = working ? 'working' : 'theme';
62
+ for (const comp of componentNames(dir)) {
63
+ if (!live[comp]) throw new Error(`component "${comp}": default.json is missing`);
64
+ configs[comp] = { ...live[comp], component: comp };
97
65
  }
98
66
  return { configs, sources };
99
67
  }
100
68
 
101
69
  /** `engine` is a test seam; the CLI always runs the compiled bundle. */
102
- export async function runAdjust({
70
+ export async function runSetGeometry({
103
71
  opsPath,
104
72
  dryRun = false,
105
73
  root = process.cwd(),
@@ -132,7 +100,7 @@ export async function runAdjust({
132
100
  }
133
101
 
134
102
  const active = readActiveTheme(themesDir ?? resolved?.themesDir);
135
- const { configs, sources } = readLiveConfigs(dir, active);
103
+ const { configs, sources } = stampedLiveConfigs(dir, active);
136
104
  const now = new Date().toISOString();
137
105
  const { configs: next, report } = adjustAliases(configs, ops, now);
138
106
 
@@ -175,7 +143,7 @@ export async function runAdjust({
175
143
  };
176
144
  }
177
145
 
178
- export function formatAdjustResult(result) {
146
+ export function formatSetGeometryResult(result) {
179
147
  const root = process.cwd();
180
148
  const lines = [];
181
149
  const { components: changedCount, aliases, skips } = result.totals;
@@ -192,7 +160,7 @@ export function formatAdjustResult(result) {
192
160
  }
193
161
  if (result.ignoredName !== null) {
194
162
  lines.push(
195
- `Ignored "name": "${result.ignoredName}". adjust edits the open buffer now, ` +
163
+ `Ignored "name": "${result.ignoredName}". set-geometry edits the open buffer, ` +
196
164
  `so it writes no file of its own.`,
197
165
  );
198
166
  }
@@ -1,7 +1,7 @@
1
- // `live-tokens set-fonts` worker.
1
+ // `live-tokens set-type` worker.
2
2
  //
3
- // Reads a pairing brief (JSON), verifies each family against Google Fonts,
4
- // binds it to a font stack via the compiled engine (dist-plugin/fontPairing —
3
+ // Reads a pairing file (JSON), verifies each family against Google Fonts,
4
+ // binds it to a font stack via the compiled engine (dist-plugin/setType —
5
5
  // the CLI never imports TS sources), and writes the result into
6
6
  // `colors-and-type/_working.json`: the same buffer the editor's own font edits
7
7
  // land in, so a retype is an unsaved edit the user saves into a theme when they
@@ -12,11 +12,11 @@ import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
12
12
  import { dirname, join, relative, resolve } from 'node:path';
13
13
  import { fileURLToPath } from 'node:url';
14
14
 
15
+ import { readActiveTheme, readLiveColorsAndType, readSavedColorsAndType, sameContent } from './lib/liveState.mjs';
15
16
  import { resolveTokensCssPath } from './migrate.mjs';
16
17
 
17
18
  const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
18
- const ENGINE = resolve(pkgRoot, 'dist-plugin/fontPairing/index.js');
19
- const packageDataDir = join(pkgRoot, 'src/live-tokens/data');
19
+ const ENGINE = resolve(pkgRoot, 'dist-plugin/setType/index.js');
20
20
 
21
21
  // Google serves a different stylesheet per user agent; ask as a browser would
22
22
  // so the weight census matches what the page will actually load.
@@ -32,7 +32,7 @@ const SOURCE_LABELS = {
32
32
  async function loadEngine() {
33
33
  if (!existsSync(ENGINE)) {
34
34
  throw new Error(
35
- `font pairing engine not found at ${relative(process.cwd(), ENGINE)}. ` +
35
+ `setType engine not found at ${relative(process.cwd(), ENGINE)}. ` +
36
36
  `Build the plugin first (npm run build:plugin).`,
37
37
  );
38
38
  }
@@ -43,53 +43,22 @@ function readJson(path) {
43
43
  return JSON.parse(readFileSync(path, 'utf8'));
44
44
  }
45
45
 
46
- function readJsonIfExists(path) {
47
- return existsSync(path) ? readJson(path) : null;
48
- }
49
-
50
46
  function browserFetch(url) {
51
47
  return fetch(url, { headers: { 'user-agent': BROWSER_UA } });
52
48
  }
53
49
 
54
- /** The open theme, read the way every other door reads it: the local file
55
- * first, then the copy the installed package ships. */
56
- function readActiveTheme(themesDir) {
57
- const slug = readJsonIfExists(join(themesDir, '_active.json'))?.activeFile ?? 'default';
58
- const theme =
59
- readJsonIfExists(join(themesDir, `${slug}.json`)) ??
60
- readJsonIfExists(join(packageDataDir, 'themes', `${slug}.json`));
61
- return theme ? { slug, theme } : null;
62
- }
63
-
64
- /** Mirrors the dev server's live colors-and-type resolution: the unsaved
65
- * buffer, then the open theme's own copy, then the package default. */
66
- function readLiveColorsAndType(colorsAndTypeDir, active) {
67
- const working = readJsonIfExists(join(colorsAndTypeDir, '_working.json'));
68
- if (working) return { colorsAndType: working, source: 'working' };
69
- if (active?.theme?.colorsAndType) return { colorsAndType: active.theme.colorsAndType, source: 'theme' };
70
- const shipped =
71
- readJsonIfExists(join(colorsAndTypeDir, 'default.json')) ??
72
- readJsonIfExists(join(packageDataDir, 'colors-and-type', 'default.json'));
73
- if (!shipped) throw new Error(`no colors and type to read at ${colorsAndTypeDir}`);
74
- return { colorsAndType: shipped, source: 'default' };
75
- }
76
-
77
50
  function normalizeFace(slot, value) {
78
51
  if (typeof value === 'string') return { name: value };
79
52
  if (value && typeof value === 'object' && typeof value.name === 'string') {
80
53
  return { name: value.name, ...(value.url ? { url: value.url } : {}) };
81
54
  }
82
- throw new Error(`brief slot "${slot}" must be a family name or { "name": "...", "url": "..." }`);
83
- }
84
-
85
- function sameJson(a, b) {
86
- return JSON.stringify(a) === JSON.stringify(b);
55
+ throw new Error(`pairing slot "${slot}" must be a family name or { "name": "...", "url": "..." }`);
87
56
  }
88
57
 
89
58
  /** `engine` and `fetcher` are test seams; the CLI always runs the compiled
90
59
  * bundle against the live API. */
91
- export async function runSetFonts({
92
- briefPath,
60
+ export async function runSetType({
61
+ pairingPath,
93
62
  dryRun = false,
94
63
  verify = true,
95
64
  root = process.cwd(),
@@ -110,21 +79,21 @@ export async function runSetFonts({
110
79
  resolveDataDirs,
111
80
  } = engine ?? (await loadEngine());
112
81
 
113
- const briefFull = resolve(root, briefPath);
114
- if (!existsSync(briefFull)) throw new Error(`brief file not found at ${relative(root, briefFull)}`);
115
- let brief;
82
+ const fullPath = resolve(root, pairingPath);
83
+ if (!existsSync(fullPath)) throw new Error(`pairing file not found at ${relative(root, fullPath)}`);
84
+ let input;
116
85
  try {
117
- brief = readJson(briefFull);
86
+ input = readJson(fullPath);
118
87
  } catch (err) {
119
- throw new Error(`brief file is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
88
+ throw new Error(`pairing file is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
120
89
  }
121
90
 
122
- const requested = SLOT_ORDER.filter((slot) => brief[slot] !== undefined).map((slot) => ({
91
+ const requested = SLOT_ORDER.filter((slot) => input[slot] !== undefined).map((slot) => ({
123
92
  slot,
124
- face: normalizeFace(slot, brief[slot]),
93
+ face: normalizeFace(slot, input[slot]),
125
94
  }));
126
95
  if (requested.length === 0) {
127
- throw new Error(`brief names no slot. Use one or more of: ${SLOT_ORDER.join(', ')}.`);
96
+ throw new Error(`pairing names no slot. Use one or more of: ${SLOT_ORDER.join(', ')}.`);
128
97
  }
129
98
 
130
99
  const resolved = colorsAndTypeDir && themesDir ? null : resolveDataDirs();
@@ -176,14 +145,11 @@ export async function runSetFonts({
176
145
  }
177
146
 
178
147
  const workingPath = join(colorsDir, '_working.json');
179
- const savedColorsAndType =
180
- active?.theme?.colorsAndType ??
181
- readJsonIfExists(join(colorsDir, 'default.json')) ??
182
- readJsonIfExists(join(packageDataDir, 'colors-and-type', 'default.json'));
148
+ const savedColorsAndType = readSavedColorsAndType(colorsDir, active);
183
149
 
184
150
  // Returning the buffer to what the open theme already holds is a discard,
185
151
  // not an edit — the same call the dev server's own PUT makes.
186
- const backToSaved = savedColorsAndType !== null && sameJson(next, savedColorsAndType);
152
+ const backToSaved = savedColorsAndType !== null && sameContent(next, savedColorsAndType);
187
153
  let wrote = null;
188
154
  if (!dryRun && report.changed) {
189
155
  if (backToSaved) {
@@ -196,7 +162,7 @@ export async function runSetFonts({
196
162
  }
197
163
 
198
164
  return {
199
- briefPath: briefFull,
165
+ pairingPath: fullPath,
200
166
  colorsAndTypeDir: colorsDir,
201
167
  workingPath,
202
168
  openTheme: active?.slug ?? null,
@@ -212,7 +178,7 @@ export async function runSetFonts({
212
178
  };
213
179
  }
214
180
 
215
- export function formatSetFontsResult(result) {
181
+ export function formatSetTypeResult(result) {
216
182
  const root = process.cwd();
217
183
  const lines = [];
218
184
 
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  CURRENT_COMPONENT_SCHEMA_VERSION,
3
3
  runMigrations
4
- } from "./chunk-YLCOIGQC.js";
4
+ } from "./chunk-V3YF6CGT.js";
5
5
 
6
6
  // vite-plugin/files/versionedFileResourceServer.ts
7
7
  import fs from "fs";
@@ -826,6 +826,60 @@ var componentMigration_2026_09_02_sectiondividerDropTitleOutline = {
826
826
  }
827
827
  };
828
828
 
829
+ // src/editor/core/themes/migrations/2026-09-03-drop-legacy-component-keys.ts
830
+ var DROPPED3 = /* @__PURE__ */ new Set([
831
+ "--badge-trait-surface",
832
+ "--badge-trait-text",
833
+ "--badge-trait-text-font-family",
834
+ "--badge-trait-text-font-size",
835
+ "--badge-trait-text-font-weight",
836
+ "--badge-trait-text-line-height",
837
+ "--badge-trait-border",
838
+ "--badge-trait-border-width",
839
+ "--badge-trait-shadow",
840
+ "--sectiondivider-title",
841
+ "--sectiondivider-title-font-family",
842
+ "--sectiondivider-title-font-size",
843
+ "--sectiondivider-title-font-weight",
844
+ "--sectiondivider-title-line-height",
845
+ "--sectiondivider-title-border-width",
846
+ "--sectiondivider-title-stroke-color",
847
+ "--sectiondivider-description",
848
+ "--sectiondivider-description-font-family",
849
+ "--sectiondivider-description-font-size",
850
+ "--sectiondivider-description-font-weight",
851
+ "--sectiondivider-description-line-height",
852
+ "--dialog-primary-default-surface",
853
+ "--dialog-primary-default-text",
854
+ "--dialog-primary-default-border",
855
+ "--dialog-primary-default-border-width",
856
+ "--dialog-primary-hover-surface",
857
+ "--dialog-primary-hover-text",
858
+ "--dialog-primary-hover-border",
859
+ "--dialog-primary-hover-border-width",
860
+ "--dialog-secondary-default-text",
861
+ "--dialog-secondary-default-border",
862
+ "--dialog-secondary-default-border-width",
863
+ "--dialog-secondary-hover-surface",
864
+ "--dialog-secondary-hover-text",
865
+ "--dialog-secondary-hover-border",
866
+ "--dialog-secondary-hover-border-width"
867
+ ]);
868
+ var colorsAndTypeMigration_2026_09_03_dropLegacyComponentKeys = {
869
+ id: "2026-09-03-drop-legacy-component-keys",
870
+ fromVersion: 7,
871
+ toVersion: 8,
872
+ appliesTo: "colors-and-type",
873
+ apply(rawVars) {
874
+ const out = {};
875
+ for (const [key, value] of Object.entries(rawVars)) {
876
+ if (DROPPED3.has(key)) continue;
877
+ out[key] = value;
878
+ }
879
+ return out;
880
+ }
881
+ };
882
+
829
883
  // src/editor/core/themes/migrations/index.ts
830
884
  var MIGRATIONS = [
831
885
  colorsAndTypeMigration_2026_04_24_legacyKeysAndBgToCanvas,
@@ -860,7 +914,8 @@ var MIGRATIONS = [
860
914
  componentMigration_2026_09_01_tintRename,
861
915
  componentMigration_2026_09_01_tabbarActiveTint,
862
916
  componentMigration_2026_09_01_gateSuffixEnabled,
863
- componentMigration_2026_09_02_sectiondividerDropTitleOutline
917
+ componentMigration_2026_09_02_sectiondividerDropTitleOutline,
918
+ colorsAndTypeMigration_2026_09_03_dropLegacyComponentKeys
864
919
  ];
865
920
  function countFor(kind) {
866
921
  return MIGRATIONS.filter((m) => m.appliesTo === kind).length;
@@ -877,7 +932,6 @@ function runMigrations(kind, fileVersion, rawVars, meta = {}) {
877
932
  }
878
933
 
879
934
  export {
880
- CURRENT_COLORS_AND_TYPE_SCHEMA_VERSION,
881
935
  CURRENT_COMPONENT_SCHEMA_VERSION,
882
936
  runMigrations
883
937
  };
@@ -1610,6 +1610,60 @@ var componentMigration_2026_09_02_sectiondividerDropTitleOutline = {
1610
1610
  }
1611
1611
  };
1612
1612
 
1613
+ // src/editor/core/themes/migrations/2026-09-03-drop-legacy-component-keys.ts
1614
+ var DROPPED3 = /* @__PURE__ */ new Set([
1615
+ "--badge-trait-surface",
1616
+ "--badge-trait-text",
1617
+ "--badge-trait-text-font-family",
1618
+ "--badge-trait-text-font-size",
1619
+ "--badge-trait-text-font-weight",
1620
+ "--badge-trait-text-line-height",
1621
+ "--badge-trait-border",
1622
+ "--badge-trait-border-width",
1623
+ "--badge-trait-shadow",
1624
+ "--sectiondivider-title",
1625
+ "--sectiondivider-title-font-family",
1626
+ "--sectiondivider-title-font-size",
1627
+ "--sectiondivider-title-font-weight",
1628
+ "--sectiondivider-title-line-height",
1629
+ "--sectiondivider-title-border-width",
1630
+ "--sectiondivider-title-stroke-color",
1631
+ "--sectiondivider-description",
1632
+ "--sectiondivider-description-font-family",
1633
+ "--sectiondivider-description-font-size",
1634
+ "--sectiondivider-description-font-weight",
1635
+ "--sectiondivider-description-line-height",
1636
+ "--dialog-primary-default-surface",
1637
+ "--dialog-primary-default-text",
1638
+ "--dialog-primary-default-border",
1639
+ "--dialog-primary-default-border-width",
1640
+ "--dialog-primary-hover-surface",
1641
+ "--dialog-primary-hover-text",
1642
+ "--dialog-primary-hover-border",
1643
+ "--dialog-primary-hover-border-width",
1644
+ "--dialog-secondary-default-text",
1645
+ "--dialog-secondary-default-border",
1646
+ "--dialog-secondary-default-border-width",
1647
+ "--dialog-secondary-hover-surface",
1648
+ "--dialog-secondary-hover-text",
1649
+ "--dialog-secondary-hover-border",
1650
+ "--dialog-secondary-hover-border-width"
1651
+ ]);
1652
+ var colorsAndTypeMigration_2026_09_03_dropLegacyComponentKeys = {
1653
+ id: "2026-09-03-drop-legacy-component-keys",
1654
+ fromVersion: 7,
1655
+ toVersion: 8,
1656
+ appliesTo: "colors-and-type",
1657
+ apply(rawVars) {
1658
+ const out = {};
1659
+ for (const [key, value] of Object.entries(rawVars)) {
1660
+ if (DROPPED3.has(key)) continue;
1661
+ out[key] = value;
1662
+ }
1663
+ return out;
1664
+ }
1665
+ };
1666
+
1613
1667
  // src/editor/core/themes/migrations/index.ts
1614
1668
  var MIGRATIONS = [
1615
1669
  colorsAndTypeMigration_2026_04_24_legacyKeysAndBgToCanvas,
@@ -1644,7 +1698,8 @@ var MIGRATIONS = [
1644
1698
  componentMigration_2026_09_01_tintRename,
1645
1699
  componentMigration_2026_09_01_tabbarActiveTint,
1646
1700
  componentMigration_2026_09_01_gateSuffixEnabled,
1647
- componentMigration_2026_09_02_sectiondividerDropTitleOutline
1701
+ componentMigration_2026_09_02_sectiondividerDropTitleOutline,
1702
+ colorsAndTypeMigration_2026_09_03_dropLegacyComponentKeys
1648
1703
  ];
1649
1704
  function countFor(kind) {
1650
1705
  return MIGRATIONS.filter((m) => m.appliesTo === kind).length;
@@ -3816,7 +3871,7 @@ ${lines.join("\n")}
3816
3871
  return "corrupt";
3817
3872
  }
3818
3873
  function notAThemeError(fileName) {
3819
- return `"${fileName}" is a colors-and-type file, not a theme. Before 0.48 those lived in themes/; move it to ${import_path4.default.basename(COLORS_AND_TYPE_DIR)}/ or delete it. To build a theme, use the theme generator skill "generate-theme".`;
3874
+ return `"${fileName}" is a colors-and-type file, not a theme. Before 0.48 those lived in themes/; move it to ${import_path4.default.basename(COLORS_AND_TYPE_DIR)}/ or delete it. To build a theme, use the theme skill "create-theme".`;
3820
3875
  }
3821
3876
  function respondUnreadableTheme(res, fileName, missingError) {
3822
3877
  switch (unreadableThemeReason(fileName)) {
@@ -4408,7 +4463,7 @@ ${lines.join("\n")}
4408
4463
  const { theme, filled } = read;
4409
4464
  if (!theme.colorsAndType) {
4410
4465
  jsonResponse(res, 422, {
4411
- error: 'This theme carries no colors and type. Build one with the theme generator skill "generate-theme".'
4466
+ error: 'This theme carries no colors and type. Build one with the theme skill "create-theme".'
4412
4467
  });
4413
4468
  return;
4414
4469
  }