@motion-proto/live-tokens 0.52.1 → 0.53.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 (60) hide show
  1. package/.claude/skills/live-tokens-adjust-shape-space/SKILL.md +7 -1
  2. package/CHANGELOG.md +63 -0
  3. package/bin/adjust.mjs +16 -39
  4. package/bin/generate-theme.mjs +20 -6
  5. package/dist-plugin/adjust/index.cjs +781 -21
  6. package/dist-plugin/adjust/index.d.cts +2 -1
  7. package/dist-plugin/adjust/index.d.ts +2 -1
  8. package/dist-plugin/adjust/index.js +43 -21
  9. package/dist-plugin/{chunk-6OZFXIQI.js → chunk-232GZGQU.js} +207 -12
  10. package/dist-plugin/chunk-NE6N66EE.js +753 -0
  11. package/dist-plugin/{chunk-JIGIIE5P.js → chunk-PB2JTK2H.js} +123 -0
  12. package/dist-plugin/generateColorsAndType/index.cjs +48 -32
  13. package/dist-plugin/generateColorsAndType/index.d.cts +2 -1
  14. package/dist-plugin/generateColorsAndType/index.d.ts +2 -1
  15. package/dist-plugin/generateColorsAndType/index.js +7 -773
  16. package/dist-plugin/{themeTypes-Dky03HL9.d.cts → index-DpTIRZ2H.d.cts} +49 -21
  17. package/dist-plugin/{themeTypes-Dky03HL9.d.ts → index-DpTIRZ2H.d.ts} +49 -21
  18. package/dist-plugin/index.cjs +1159 -85
  19. package/dist-plugin/index.js +102 -80
  20. package/dist-plugin/migrateData/index.cjs +956 -14
  21. package/dist-plugin/migrateData/index.js +10 -3
  22. package/package.json +6 -4
  23. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +9 -23
  24. package/src/editor/component-editor/scaffolding/LinkedBlock.svelte +8 -8
  25. package/src/editor/core/components/adjustAliases.ts +49 -22
  26. package/src/editor/core/components/aliasKinds.ts +7 -2
  27. package/src/editor/core/components/componentConfigService.ts +1 -1
  28. package/src/editor/core/store/editorStore.ts +7 -197
  29. package/src/editor/core/store/editorTypes.ts +9 -30
  30. package/src/editor/core/themes/lookSummary.ts +3 -25
  31. package/src/editor/core/themes/migrateComponentConfig.ts +205 -0
  32. package/src/editor/core/themes/migrations/2026-05-19-sectiondivider-rich-gradient.ts +1 -1
  33. package/src/editor/core/themes/migrations/2026-05-20-sectiondivider-slim-variants.ts +1 -1
  34. package/src/editor/core/themes/migrations/2026-05-22-sectiondivider-intrinsics-to-css.ts +1 -1
  35. package/src/editor/core/themes/parsers/gradient.ts +211 -0
  36. package/src/editor/core/themes/slices/gradients.ts +5 -58
  37. package/src/editor/core/themes/themeService.ts +22 -9
  38. package/src/editor/core/themes/themeTypes.ts +71 -21
  39. package/src/editor/docs/content/themes-workflow.md +9 -11
  40. package/src/editor/docs/content/where-themes-live.md +2 -2
  41. package/src/editor/docs/content.generated.ts +2 -2
  42. package/src/editor/index.ts +1 -0
  43. package/src/editor/styles/ui-editor.css +5 -2
  44. package/src/editor/ui/PaletteEditor.svelte +30 -15
  45. package/src/editor/ui/ThemePanel.svelte +24 -15
  46. package/src/editor/ui/Toggle.svelte +22 -22
  47. package/src/editor/ui/UIPillButton.svelte +1 -1
  48. package/src/editor/ui/UIReveal.svelte +47 -0
  49. package/src/editor/ui/UISquareButton.svelte +1 -1
  50. package/src/editor/ui/colors/ColorsTab.svelte +4 -5
  51. package/src/editor/ui/palette/OverridesPanel.svelte +4 -3
  52. package/src/editor/ui/palette/PaletteJumpButton.svelte +3 -3
  53. package/src/editor/ui/palette/ScaleCurveEditor.svelte +3 -2
  54. package/src/live-tokens/data/themes/autumn.json +77 -41
  55. package/src/live-tokens/data/themes/halloween.json +53 -52
  56. package/src/live-tokens/data/themes/midnight-study.json +136 -68
  57. package/src/live-tokens/data/themes/ocean.json +77 -41
  58. package/src/live-tokens/data/themes/royal-velvet.json +43 -42
  59. package/src/live-tokens/data/themes/spring-meadow.json +86 -52
  60. package/src/live-tokens/data/themes/sunset.json +101 -40
@@ -54,7 +54,13 @@ Magnitude words: "slightly" or "a bit" is 1 step, unqualified is 1 to 2, "much",
54
54
 
55
55
  ## Ladders
56
56
 
57
- Radius runs `none, sm, md, lg, xl, 2xl, 3xl, 4xl`, with `full` as the gated ninth rung. Space (padding and gap) is the editor picker's subset: `0, 2, 4, 6, 8, 10, 12, 16, 20, 24, 32, 48`, so every written value stays re-editable by hand. Border width is the full `--border-width-*` scale. `set` values must be on the ladder (`--space-64` is rejected); an alias sitting off the subset snaps to the nearest rung when shifted, and the report marks a snap that lands against the requested direction.
57
+ Radius runs `none, sm, md, lg, xl, 2xl, 3xl, 4xl`, with `full` as the gated ninth rung. Space (padding and gap) is the editor picker's subset: `0, 2, 4, 6, 8, 10, 12, 16, 20, 24, 32, 48`, so every written value stays re-editable by hand. Border width is the full `--border-width-*` scale. `set` values must be on the ladder (`--space-64` is rejected).
58
+
59
+ Content insets stop at `--space-4`. Below it the text sits against its own edge, so `--space-0` and `--space-2` are destinations a person picks on purpose, not ones a relative "tighter" hands you. Both stay available through the editor picker and through `set`. An alias already below the floor still moves up, and a shift that would push one under `--space-4` reports as clamped and writes nothing.
60
+
61
+ The floor guards `-padding` only. Outer space is exempt, because a 2px gap between an icon and its label, or a 2px margin under a bar, is ordinary design rather than a mistake. Note that `-margin` rides the `padding` kind, so a padding op moves margins too; it just does not floor them.
62
+
63
+ An alias sitting off the subset spends its first step reaching the rung the shift points at, so `--space-2` with `shift: 1` lands on `--space-4` rather than jumping past it.
58
64
 
59
65
  ## Scope
60
66
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.53.0 — Themes are complete documents
4
+
5
+ ### Fixed
6
+
7
+ - **The editor no longer imports build tooling.** `themeService.ts` and
8
+ `themeTypes.ts` read `THEME_SCHEMA_VERSION` from
9
+ `vite-plugin/themes/normalizeTheme`, a path that resolves in this repository
10
+ and in no installed copy, because the tarball ships no tooling. Any consumer
11
+ building the editor failed on an unresolved import. The constant now lives in
12
+ `themeTypes.ts` and the plugin re-exports it. `check:no-tooling-imports`
13
+ gates the direction.
14
+
15
+ ### Changed
16
+
17
+ - **Content insets stop at `--space-4`.** Below it the text sits against its
18
+ own edge, so a relative "tighter" no longer deposits a `-padding` alias on
19
+ `--space-0` or `--space-2`. Both stay available in the editor picker and
20
+ through an explicit `set` op, which is where that call belongs. A shift that
21
+ would push an alias under the floor reports as clamped and writes nothing.
22
+ Outer space is exempt: a 2px gap between an icon and its label, or a 2px
23
+ margin under a bar, is ordinary design.
24
+
25
+ - **An off-ladder value spends its first step reaching the ladder.** Shifting
26
+ such a value used to snap it to the nearest rung and then apply the full
27
+ shift on top, so a one-step request moved two visible steps, and an
28
+ above-ladder value could be quietly pulled *down* by a request to go up.
29
+ The snap now follows the shift's direction and counts as its opening step.
30
+ This retires the report card's `!` marker, which existed only to flag that
31
+ backwards case.
32
+
33
+ - **Themes are complete documents.** A theme now stores every component and
34
+ every alias, by value, at `schemaVersion` 4. Boot migrates and fills a
35
+ local theme once; an incomplete imported theme still loads with whatever
36
+ components it carries, filled from the current defaults, and the fill is
37
+ reported in the Theme panel. The preset generator that re-derived the
38
+ seven shipped presets on every run is gone. `seed-preset-theme.mjs <slug>`
39
+ seeds a new preset once and `check:preset-themes` guards the shipped seven
40
+ against drift.
41
+
42
+ ### Fixed
43
+
44
+ - **`generate-theme` no longer inherits gaps from an incomplete open theme.**
45
+ The live resolution path gained the missing default layer, so a theme
46
+ missing a component no longer leaves the generator working from a hole.
47
+ - **A pre-rename alias key in an old theme is migrated before the bake**,
48
+ instead of being emitted verbatim into `tokens.generated.css`.
49
+ - **A component's gradient default survives a fresh checkout.** The dev
50
+ server derives `component-configs/<comp>/default.json` from the component's
51
+ `:global(:root)` block, and it could not read a baked gradient back. On a
52
+ tree with no prior file it dropped the alias. It now parses the gradient
53
+ into its structured form, so the derivation covers every value the bake
54
+ emits.
55
+ - **A radial gradient bakes the shape the editor shows.** The production bake
56
+ carried its own copy of the gradient renderer, which ignored the centre and
57
+ both aspect factors and wrote `circle … at center` for every radial. Both
58
+ sides now share one renderer.
59
+ - **The shipped `panel` aliases sit in derivation order.** `Panel.svelte`
60
+ declares `--panel-stage-surface` before the stage spacing, but every
61
+ committed copy carried it last. On a fresh checkout, where the component
62
+ source is newer than the data tree, boot re-derived `default.json` into
63
+ source order while the seven presets kept the old one, and the preset gate
64
+ read two different key orders. The committed data now matches the source.
65
+
3
66
  ## 0.52.1 — Notification header actions are previewable
4
67
 
5
68
  ### Fixed
package/bin/adjust.mjs CHANGED
@@ -18,7 +18,6 @@ const packageThemesDir = join(pkgRoot, 'src/live-tokens/data/themes');
18
18
  const SOURCE_LABELS = {
19
19
  working: 'your unsaved edits',
20
20
  theme: 'the open theme',
21
- default: 'the shipped default',
22
21
  };
23
22
 
24
23
  const SKIP_LABELS = [
@@ -46,35 +45,6 @@ function readJsonIfExists(path) {
46
45
  return existsSync(path) ? readJson(path) : null;
47
46
  }
48
47
 
49
- function numericRung(token) {
50
- const match = /-(\d+)$/.exec(token);
51
- return match ? Number(match[1]) : null;
52
- }
53
-
54
- /** Net direction the ops asked for on this alias. An explicit `set` owns the
55
- * value outright, so it reports no direction. */
56
- function requestedDirection(ops, matchesKind, component, variable) {
57
- let total = 0;
58
- for (const op of ops) {
59
- if (op.target !== undefined && op.target !== component) continue;
60
- if (!matchesKind(variable, op.kind)) continue;
61
- if (op.shift === undefined) return 0;
62
- total += op.shift;
63
- }
64
- return Math.sign(total);
65
- }
66
-
67
- /** A shift can land below where it started: an off-subset value (`--space-64`)
68
- * snaps to its nearest writable rung first. Those changes are marked, never
69
- * listed as an ordinary shift. */
70
- function opposesShift(ops, matchesKind, component, change) {
71
- const from = numericRung(change.from);
72
- const to = numericRung(change.to);
73
- if (from === null || to === null) return false;
74
- const direction = requestedDirection(ops, matchesKind, component, change.variable);
75
- return direction !== 0 && Math.sign(to - from) !== direction;
76
- }
77
-
78
48
  /** Successive ops can touch the same alias (soften, then pill the buttons);
79
49
  * the report shows one entry per alias, first `from` to last `to`. */
80
50
  function collapseChanges(changes) {
@@ -98,9 +68,11 @@ function readActiveTheme(themesDir) {
98
68
  return theme ? { slug, theme } : null;
99
69
  }
100
70
 
101
- /** Each component's live config and where it came from: the buffer, else the
102
- * open theme's embedded copy, else the shipped default. Mirrors the dev
103
- * server's `resolveLiveComponentConfig`, so the CLI adjusts what the page runs. */
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. */
104
76
  function readLiveConfigs(dir, active) {
105
77
  const configs = {};
106
78
  const sources = {};
@@ -109,13 +81,19 @@ function readLiveConfigs(dir, active) {
109
81
  const comp = entry.name;
110
82
  const componentDir = join(dir, comp);
111
83
  const working = readJsonIfExists(join(componentDir, '_working.json'));
112
- const embedded = active?.theme?.componentConfigs?.[comp];
113
- const config = working ?? embedded ?? readJsonIfExists(join(componentDir, 'default.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;
114
92
  if (!config) {
115
93
  throw new Error(`component "${comp}": default.json is missing`);
116
94
  }
117
95
  configs[comp] = { ...config, component: comp };
118
- sources[comp] = working ? 'working' : embedded ? 'theme' : 'default';
96
+ sources[comp] = working ? 'working' : 'theme';
119
97
  }
120
98
  return { configs, sources };
121
99
  }
@@ -173,7 +151,7 @@ export async function runAdjust({
173
151
  components.push({
174
152
  component,
175
153
  source: sources[component],
176
- changes: changes.map((c) => ({ ...c, snapped: opposesShift(ops, matchesKind, component, c) })),
154
+ changes,
177
155
  skips,
178
156
  });
179
157
  }
@@ -228,8 +206,7 @@ export function formatAdjustResult(result) {
228
206
 
229
207
  const width = Math.max(0, ...entry.changes.map((c) => c.variable.length));
230
208
  for (const c of entry.changes) {
231
- const note = c.snapped ? ' ← snapped to the nearest editor rung, against the requested shift' : '';
232
- lines.push(` ${c.snapped ? '!' : ' '} ${c.variable.padEnd(width)} ${c.from} → ${c.to}${note}`);
209
+ lines.push(` ${c.variable.padEnd(width)} ${c.from} ${c.to}`);
233
210
  }
234
211
 
235
212
  for (const [reason, label] of SKIP_LABELS) {
@@ -21,7 +21,9 @@ import { fileURLToPath } from 'node:url';
21
21
  const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
22
22
  const ENGINE = resolve(pkgRoot, 'dist-plugin/generateColorsAndType/index.js');
23
23
  const packageDataDir = join(pkgRoot, 'src/live-tokens/data');
24
- const THEME_SCHEMA_VERSION = 3;
24
+ // Source of truth: vite-plugin/themes/normalizeTheme.ts. This copy cannot
25
+ // import TS, so `check:preset-themes` (Wave 5) is what catches a drift.
26
+ const THEME_SCHEMA_VERSION = 4;
25
27
 
26
28
  async function loadEngine() {
27
29
  if (!existsSync(ENGINE)) {
@@ -69,11 +71,18 @@ function stripMarkers(value) {
69
71
  return rest;
70
72
  }
71
73
 
74
+ /** A component's derived default, local tree first, then the installed
75
+ * package's shipped copy — same fallback order every other read here uses. */
76
+ function defaultComponentConfig(componentConfigsDir, comp) {
77
+ return readData(join(componentConfigsDir, comp), `component-configs/${comp}`, 'default');
78
+ }
79
+
72
80
  /**
73
81
  * The look the new theme carries forward. Without --carry-from that is the LIVE
74
82
  * state — buffer first, then the open theme, then the shipped default — because
75
83
  * what the user sees is what they expect to keep. With it, the named theme,
76
- * read by value.
84
+ * then the shipped default for anything that theme omits, so an incomplete
85
+ * source never produces an incomplete result.
77
86
  */
78
87
  function resolveCarrySource({ carryFrom, colorsAndTypeDir, componentConfigsDir, themesDir }) {
79
88
  const comps = componentNames(componentConfigsDir);
@@ -83,7 +92,7 @@ function resolveCarrySource({ carryFrom, colorsAndTypeDir, componentConfigsDir,
83
92
  if (!theme) throw new Error(`--carry-from theme "${carryFrom}" not found`);
84
93
  const componentConfigs = {};
85
94
  for (const comp of comps) {
86
- const cfg = theme.componentConfigs?.[comp];
95
+ const cfg = theme.componentConfigs?.[comp] ?? defaultComponentConfig(componentConfigsDir, comp);
87
96
  if (cfg) componentConfigs[comp] = stripMarkers(cfg);
88
97
  }
89
98
  return { colorsAndType: stripMarkers(theme.colorsAndType), componentConfigs };
@@ -98,12 +107,15 @@ function resolveCarrySource({ carryFrom, colorsAndTypeDir, componentConfigsDir,
98
107
  );
99
108
 
100
109
  // A working buffer is an unsaved delta from the active theme; absent theme
101
- // component entries fall through to the component defaults.
110
+ // component entries fall through to the component defaults. Mirrors
111
+ // `bin/adjust.mjs`'s `readLiveConfigs` and the dev server's
112
+ // `resolveLiveComponentConfig` — all three layers, in the same order.
102
113
  const componentConfigs = {};
103
114
  for (const comp of comps) {
104
115
  const live =
105
116
  readJsonIfExists(join(componentConfigsDir, comp, '_working.json')) ??
106
- activeTheme?.componentConfigs?.[comp];
117
+ activeTheme?.componentConfigs?.[comp] ??
118
+ defaultComponentConfig(componentConfigsDir, comp);
107
119
  if (live) componentConfigs[comp] = stripMarkers(live);
108
120
  }
109
121
  return { colorsAndType, componentConfigs };
@@ -121,7 +133,8 @@ export async function runGenerateTheme({
121
133
  themesDir,
122
134
  engine,
123
135
  } = {}) {
124
- const { buildColorsAndTypeFromSeeds, resolveDataDirs } = engine ?? (await loadEngine());
136
+ const { buildColorsAndTypeFromSeeds, resolveDataDirs, CURRENT_COMPONENT_SCHEMA_VERSION } =
137
+ engine ?? (await loadEngine());
125
138
 
126
139
  const briefFull = resolve(root, briefPath);
127
140
  if (!existsSync(briefFull)) {
@@ -162,6 +175,7 @@ export async function runGenerateTheme({
162
175
  schemaVersion: THEME_SCHEMA_VERSION,
163
176
  colorsAndType,
164
177
  componentConfigs: carry.componentConfigs,
178
+ componentSchemaVersion: CURRENT_COMPONENT_SCHEMA_VERSION,
165
179
  };
166
180
 
167
181
  if (!dryRun) {