@motion-proto/live-tokens 0.67.1 → 0.68.1

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 (63) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/bin/generate-theme.mjs +5 -4
  3. package/dist-plugin/adjust/index.d.cts +1 -1
  4. package/dist-plugin/adjust/index.d.ts +1 -1
  5. package/dist-plugin/{chunk-J2JT4UEA.js → chunk-2UX6EVVA.js} +25 -25
  6. package/dist-plugin/{dataPaths-DZUzVv8H.d.cts → dataPaths-bJTCEO4H.d.cts} +1 -1
  7. package/dist-plugin/{dataPaths-DZUzVv8H.d.ts → dataPaths-bJTCEO4H.d.ts} +1 -1
  8. package/dist-plugin/fontPairing/index.d.cts +1 -1
  9. package/dist-plugin/fontPairing/index.d.ts +1 -1
  10. package/dist-plugin/generateColorsAndType/index.d.cts +1 -1
  11. package/dist-plugin/generateColorsAndType/index.d.ts +1 -1
  12. package/dist-plugin/index.cjs +27 -27
  13. package/dist-plugin/index.d.cts +1 -1
  14. package/dist-plugin/index.d.ts +1 -1
  15. package/dist-plugin/index.js +5 -5
  16. package/dist-plugin/migrateData/index.cjs +25 -25
  17. package/dist-plugin/migrateData/index.d.cts +1 -1
  18. package/dist-plugin/migrateData/index.d.ts +1 -1
  19. package/dist-plugin/migrateData/index.js +1 -1
  20. package/dist-plugin/tokensCssMigrations/index.d.cts +1 -1
  21. package/dist-plugin/tokensCssMigrations/index.d.ts +1 -1
  22. package/package.json +4 -4
  23. package/src/editor/bootstrap.ts +6 -6
  24. package/src/editor/component-editor/ImageLightboxEditor.svelte +1 -1
  25. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +4 -4
  26. package/src/editor/core/fonts/fontLoader.ts +2 -2
  27. package/src/editor/core/preview/{lookPreview.ts → themePreview.ts} +27 -27
  28. package/src/editor/core/productionPulse.ts +3 -3
  29. package/src/editor/core/sketch/index.ts +52 -44
  30. package/src/editor/core/sketch/maskField.ts +9 -9
  31. package/src/editor/core/sketch/sketchLayer.ts +9 -9
  32. package/src/editor/core/sketch/sketchRegistry.ts +34 -34
  33. package/src/editor/core/sketch/sketchStore.ts +98 -83
  34. package/src/editor/core/sketch/sketchStyleService.ts +4 -4
  35. package/src/editor/core/sketch/sketchStyles.ts +28 -28
  36. package/src/editor/core/themes/colorsAndTypeService.ts +1 -1
  37. package/src/editor/core/themes/loadRows.ts +8 -8
  38. package/src/editor/core/themes/themeDocumentSync.ts +2 -2
  39. package/src/editor/core/themes/themeInit.ts +2 -2
  40. package/src/editor/core/themes/themeService.ts +17 -17
  41. package/src/editor/core/themes/{lookSummary.ts → themeSummary.ts} +9 -9
  42. package/src/editor/core/themes/themeTypes.ts +10 -6
  43. package/src/editor/docs/content/getting-started.md +1 -1
  44. package/src/editor/docs/content/sketch-mode.md +22 -17
  45. package/src/editor/docs/content/themes-workflow.md +11 -11
  46. package/src/editor/docs/content/where-themes-live.md +6 -6
  47. package/src/editor/docs/content.generated.ts +4 -4
  48. package/src/editor/index.ts +2 -2
  49. package/src/editor/ui/ThemePanel.svelte +52 -52
  50. package/src/editor/ui/sketch/SketchPreview.svelte +2 -2
  51. package/src/editor/ui/sketch/SketchTab.svelte +64 -38
  52. package/src/live-tokens/data/sketch-styles/dry.json +5 -5
  53. package/src/live-tokens/data/sketch-styles/hatched.json +9 -9
  54. package/src/live-tokens/data/themes/autumn.json +1 -1
  55. package/src/live-tokens/data/themes/halloween.json +1 -1
  56. package/src/live-tokens/data/themes/midnight-study.json +1 -1
  57. package/src/live-tokens/data/themes/ocean.json +1 -1
  58. package/src/live-tokens/data/themes/royal-velvet.json +1 -1
  59. package/src/live-tokens/data/themes/sketchy.json +1 -1
  60. package/src/live-tokens/data/themes/spring-meadow.json +1 -1
  61. package/src/live-tokens/data/themes/sunset.json +1 -1
  62. package/src/system/backdrop/backdrop.ts +1 -1
  63. package/src/system/components/SectionDivider.svelte +1 -1
@@ -6,7 +6,7 @@ import dashed from '../../../live-tokens/data/sketch-styles/dashed.json';
6
6
  import napkin from '../../../live-tokens/data/sketch-styles/napkin.json';
7
7
  import dry from '../../../live-tokens/data/sketch-styles/dry.json';
8
8
 
9
- export interface SketchStyle {
9
+ export interface SketchStyleSettings {
10
10
  label: string;
11
11
  blurb: string;
12
12
  /** How far the fill's edge travels at its furthest, in px. Every dial that
@@ -80,7 +80,7 @@ export interface SketchStyle {
80
80
  /** Whether the two move together. Unlinked they part, and the field comes out
81
81
  stretched: blobs wider than they are tall read as a wash dragged sideways,
82
82
  the way ink pulled across a page does. Stored rather than inferred from
83
- the pair matching, so a look that stretches to exactly square keeps its
83
+ the pair matching, so a sketchstyle that stretches to exactly square keeps its
84
84
  dials apart. */
85
85
  maskBlobLinked: boolean;
86
86
  /** Output levels on the coverage field, 0 to 1: the palest the fill gets and
@@ -144,40 +144,40 @@ export interface SketchStyle {
144
144
  /**
145
145
  * The shipped sketchstyles, read from the files the package distributes. The
146
146
  * files are the source: a project shadows one by saving a sketchstyle under the
147
- * same id, the editor restores a shipped look by deleting that file, and
147
+ * same id, the editor restores a shipped sketchstyle by deleting that file, and
148
148
  * `themeFileApi` serves these as the read-only fallback behind the project's own
149
- * directory. Editing a look here means editing its JSON, which is what the
149
+ * directory. Editing a sketchstyle here means editing its JSON, which is what the
150
150
  * Sketchstyle view already writes.
151
151
  *
152
152
  * Each file carries every dial, so there is nothing to merge a default into.
153
153
  * `sketchStyles.test.ts` pins that: the seven key sets have to match, and a dial
154
- * added to `SketchStyle` has to reach all seven before the suite goes green.
154
+ * added to `SketchStyleSettings` has to reach all seven before the suite goes green.
155
155
  *
156
156
  * Order is picker order.
157
157
  */
158
158
  const SHIPPED_FILES = { pencil, marker, whiteboard, hatched, dashed, napkin, dry };
159
159
 
160
- export const SKETCH_STYLES: Record<string, SketchStyle> = Object.fromEntries(
161
- Object.entries(SHIPPED_FILES).map(([id, file]) => [id, file.settings as unknown as SketchStyle]),
160
+ export const SHIPPED_SKETCH_SETTINGS: Record<string, SketchStyleSettings> = Object.fromEntries(
161
+ Object.entries(SHIPPED_FILES).map(([id, file]) => [id, file.settings as unknown as SketchStyleSettings]),
162
162
  );
163
163
 
164
164
  export const DEFAULT_SKETCH_STYLE = 'marker';
165
165
 
166
- /** The id of the look a theme carries, in the same id namespace as the shipped
166
+ /** The id for sketch settings a theme carries, in the same id namespace as the shipped
167
167
  sketchstyles so one picker row and one `setSketch` call cover both. Never a
168
- key of `SKETCH_STYLES`: a shipped style claiming it would shadow the theme's
169
- own look in every picker. `index.test.ts` pins that. */
168
+ key of `SHIPPED_SKETCH_SETTINGS`: a shipped style claiming it would shadow the theme's
169
+ own sketchstyle in every picker. `index.test.ts` pins that. */
170
170
  export const THEME_SKETCH_ID = 'theme';
171
171
 
172
172
  /** Reconciled against a full sketchstyle in both directions: a value stored before a
173
173
  control existed picks up the default, and a value stored for a control since
174
174
  retired is dropped. Without the drop, a stale key survives every spread and
175
175
  makes the settings compare unequal to any baseline forever. */
176
- export function hydrateSketchStyle(raw: unknown): SketchStyle {
177
- const base = SKETCH_STYLES[DEFAULT_SKETCH_STYLE];
178
- const stored = (raw ?? {}) as Partial<SketchStyle>;
176
+ export function hydrateSketchSettings(raw: unknown): SketchStyleSettings {
177
+ const base = SHIPPED_SKETCH_SETTINGS[DEFAULT_SKETCH_STYLE];
178
+ const stored = (raw ?? {}) as Partial<SketchStyleSettings>;
179
179
  const out = { ...base };
180
- for (const key of Object.keys(base) as (keyof SketchStyle)[]) {
180
+ for (const key of Object.keys(base) as (keyof SketchStyleSettings)[]) {
181
181
  if (stored[key] !== undefined) (out[key] as unknown) = stored[key];
182
182
  }
183
183
  // A retired option: the fill's presence belongs to the theme, not the effect.
@@ -194,9 +194,9 @@ export function hydrateSketchStyle(raw: unknown): SketchStyle {
194
194
  }
195
195
 
196
196
  /** The second pass used to sit at a distance derived from the stroke width,
197
- with no dial of its own. A look stored before the dial comes back at that
197
+ with no dial of its own. A sketchstyle stored before the dial comes back at that
198
198
  distance rather than at whatever the fallback sketchstyle happens to carry. */
199
- function restoreDerivedRetrace(stored: Record<string, unknown>, out: SketchStyle): void {
199
+ function restoreDerivedRetrace(stored: Record<string, unknown>, out: SketchStyleSettings): void {
200
200
  if (stored.retraceOffset === undefined) {
201
201
  out.retraceOffset = Number(Math.max(1.2, out.strokeWidth * 0.55).toFixed(2));
202
202
  }
@@ -204,7 +204,7 @@ function restoreDerivedRetrace(stored: Record<string, unknown>, out: SketchStyle
204
204
 
205
205
  /** The four displacement dials used to be stated as the map's own `scale`,
206
206
  which is the full swing: the number on the dial was twice the furthest
207
- anything actually moved. They are peak travel in px now, so a look stored
207
+ anything actually moved. They are peak travel in px now, so a sketchstyle stored
208
208
  under the old names comes back halved and renders identically. */
209
209
  const SWING_DIALS = {
210
210
  fillScale: 'fillTravel',
@@ -216,11 +216,11 @@ const SWING_DIALS = {
216
216
  /** The pen wobble used to be stated as `frequency`, in cycles per px, which is
217
217
  the number the filter wants and not one anybody can picture. It is a
218
218
  wavelength in px now, the way the mask states its blobs. */
219
- function convertCyclesToWavelength(stored: Record<string, unknown>, out: SketchStyle): void {
219
+ function convertCyclesToWavelength(stored: Record<string, unknown>, out: SketchStyleSettings): void {
220
220
  const cycles = stored.frequency;
221
221
  if (typeof cycles === 'number' && cycles > 0) out.wobble = Math.round(1 / cycles);
222
222
  // The layer count was `octaves`, and its dial ran to 5. The top two moved
223
- // nothing, so a look stored there comes back at the roughest that reads.
223
+ // nothing, so a sketchstyle stored there comes back at the roughest that reads.
224
224
  // Both were multipliers on the frequency, so they ran the other way.
225
225
  for (const [legacy, key] of [
226
226
  ['borderFrequency', 'borderWavelength'], ['iconFrequency', 'iconWavelength'],
@@ -234,9 +234,9 @@ function convertCyclesToWavelength(stored: Record<string, unknown>, out: SketchS
234
234
  if (typeof layers === 'number') out.roughness = Math.min(3, Math.max(1, layers));
235
235
  }
236
236
 
237
- /** The blob size was one number for both axes. A look stored before the split
238
- comes back square, with the two dials linked, which is the look it had. */
239
- function splitBlobAxes(stored: Record<string, unknown>, out: SketchStyle): void {
237
+ /** The blob size was one number for both axes. A sketchstyle stored before the split
238
+ comes back square, with the two dials linked, which is the shape it had. */
239
+ function splitBlobAxes(stored: Record<string, unknown>, out: SketchStyleSettings): void {
240
240
  const blob = stored.maskBlob;
241
241
  if (typeof blob !== 'number') return;
242
242
  out.maskBlobX = blob;
@@ -249,13 +249,13 @@ function splitBlobAxes(stored: Record<string, unknown>, out: SketchStyle): void
249
249
  of the field, so it came out either untouched or gone. It is a share of the
250
250
  glyph now, and the old default reads as one period across the glyph, which
251
251
  is what that size was aiming at. */
252
- function convertIconTileToScale(stored: Record<string, unknown>, out: SketchStyle): void {
252
+ function convertIconTileToScale(stored: Record<string, unknown>, out: SketchStyleSettings): void {
253
253
  const tile = stored.iconMaskTile;
254
254
  if (typeof tile !== 'number') return;
255
255
  out.iconMaskScale = Math.min(5, Math.max(0.5, Number((tile / 90).toFixed(2))));
256
256
  }
257
257
 
258
- function halveSwingDials(stored: Record<string, unknown>, out: SketchStyle): void {
258
+ function halveSwingDials(stored: Record<string, unknown>, out: SketchStyleSettings): void {
259
259
  for (const [legacy, key] of Object.entries(SWING_DIALS)) {
260
260
  const value = stored[legacy];
261
261
  if (typeof value === 'number') out[key as 'fillTravel'] = value / 2;
@@ -265,7 +265,7 @@ function halveSwingDials(stored: Record<string, unknown>, out: SketchStyle): voi
265
265
  /** The mask used to be a 600-unit tile painted at `maskScale` px, with the
266
266
  coverage point buried in the hardness slope. Recover page-px blobs and
267
267
  softness, and the levels the old slope and floor put the edge at. */
268
- function convertTiledMask(stored: Record<string, unknown>, out: SketchStyle): void {
268
+ function convertTiledMask(stored: Record<string, unknown>, out: SketchStyleSettings): void {
269
269
  const scale = stored.maskScale;
270
270
  if (typeof scale !== 'number') return;
271
271
  const freq = stored.maskFrequency;
@@ -283,7 +283,7 @@ function convertTiledMask(stored: Record<string, unknown>, out: SketchStyle): vo
283
283
  * levels the pair put the edge between, and rescale them onto the field as it
284
284
  * is now: stretched onto its full range before the levels see it.
285
285
  */
286
- function convertCutToLevels(stored: Record<string, unknown>, out: SketchStyle): void {
286
+ function convertCutToLevels(stored: Record<string, unknown>, out: SketchStyleSettings): void {
287
287
  const contrast = stored.maskContrast;
288
288
  const coverage = stored.maskCoverage;
289
289
  if (typeof contrast !== 'number' || typeof coverage !== 'number') return;
@@ -297,9 +297,9 @@ function convertCutToLevels(stored: Record<string, unknown>, out: SketchStyle):
297
297
 
298
298
  /** The pair were input levels, a cut through the field, and are output levels
299
299
  now, the palest and densest the fill gets. The same two numbers carry over:
300
- a look cut between 20% and 50% comes back as a wash between 20% and 50% ink,
300
+ a sketchstyle cut between 20% and 50% comes back as a wash between 20% and 50% ink,
301
301
  which keeps its spread and loses only the hole. */
302
- function carryLevelsToOutput(stored: Record<string, unknown>, out: SketchStyle): void {
302
+ function carryLevelsToOutput(stored: Record<string, unknown>, out: SketchStyleSettings): void {
303
303
  if (typeof stored.maskLevelMin === 'number') out.maskOutputMin = stored.maskLevelMin;
304
304
  if (typeof stored.maskLevelMax === 'number') out.maskOutputMax = stored.maskLevelMax;
305
305
  }
@@ -65,7 +65,7 @@ export const sanitizeFileName = sanitizeFileNameImpl;
65
65
 
66
66
  /** Flush the editor state to the buffer under `displayName`, the name of the
67
67
  * theme the buffer belongs to, and clear the dirty flag. A capture reads the
68
- * buffer back, so this is what makes Save and Adopt mean the look on screen. */
68
+ * buffer back, so this is what makes Save and Adopt mean the theme on screen. */
69
69
  export async function persistColorsAndType(
70
70
  state: EditorState,
71
71
  displayName: string,
@@ -1,7 +1,7 @@
1
1
  import type { ThemeMeta, ColorsAndTypeMeta } from './themeTypes';
2
2
 
3
3
  /**
4
- * The one Load list. A theme is the whole look; a colors and type file is a
4
+ * The one Load list. A theme is the whole theme; a colors and type file is a
5
5
  * preset holding that half of one. Both belong in the same window, told apart
6
6
  * by a badge rather than by living in separate managers.
7
7
  *
@@ -10,7 +10,7 @@ import type { ThemeMeta, ColorsAndTypeMeta } from './themeTypes';
10
10
  * that copy is exactly the file the list must keep reachable.
11
11
  */
12
12
 
13
- export type LoadRowKind = 'look' | 'layer';
13
+ export type LoadRowKind = 'theme' | 'layer';
14
14
 
15
15
  export interface LoadRow {
16
16
  /** `<kind>:<slug>`. The two kinds are separate resources with separate name
@@ -25,12 +25,12 @@ export interface LoadRow {
25
25
 
26
26
  export const loadRowId = (kind: LoadRowKind, slug: string): string => `${kind}:${slug}`;
27
27
 
28
- export function buildLoadRows(looks: ThemeMeta[], layers: ColorsAndTypeMeta[]): LoadRow[] {
29
- const lookRows: LoadRow[] = looks
28
+ export function buildLoadRows(themes: ThemeMeta[], layers: ColorsAndTypeMeta[]): LoadRow[] {
29
+ const themeRows: LoadRow[] = themes
30
30
  .map((f): LoadRow => ({
31
- fileName: loadRowId('look', f.fileName),
31
+ fileName: loadRowId('theme', f.fileName),
32
32
  slug: f.fileName,
33
- kind: 'look',
33
+ kind: 'theme',
34
34
  name: f.name,
35
35
  updatedAt: f.updatedAt,
36
36
  isProtected: f.isProtected,
@@ -46,12 +46,12 @@ export function buildLoadRows(looks: ThemeMeta[], layers: ColorsAndTypeMeta[]):
46
46
  updatedAt: f.updatedAt,
47
47
  isProtected: false,
48
48
  }));
49
- return [...lookRows, ...layerRows];
49
+ return [...themeRows, ...layerRows];
50
50
  }
51
51
 
52
52
  /**
53
53
  * Whether picking this row loads colors and type alone. A layer file holds
54
- * nothing else, so it ignores the toggle; a look honors it.
54
+ * nothing else, so it ignores the toggle; a theme honors it.
55
55
  */
56
56
  export function isColorsOnly(row: LoadRow | null, colorsOnly: boolean): boolean {
57
57
  if (row?.kind === 'layer') return true;
@@ -2,7 +2,7 @@ import { liveMovedSinceBake } from '../productionPulse';
2
2
  import { openThemeSlug } from '../store/editorConfigStore';
3
3
  import { loadThemeFromApi } from '../store/editorStore';
4
4
  import { migrateColorsAndTypeFonts } from '../fonts/fontMigration';
5
- import { openThemeSketchStyle } from '../sketch/sketchStore';
5
+ import { openThemeSketchSettings } from '../sketch/sketchStore';
6
6
  import type { ApplyThemeResult } from './themeService';
7
7
 
8
8
  const CHANNEL_NAME = 'live-tokens:active-theme:v1';
@@ -43,7 +43,7 @@ export function hydrateAppliedTheme(fileName: string, result: ApplyThemeResult):
43
43
  migrateColorsAndTypeFonts(colorsAndType);
44
44
  loadThemeFromApi(colorsAndType, structuredClone(result.componentConfigs));
45
45
  openThemeSlug.set(fileName);
46
- openThemeSketchStyle(result.theme.sketchStyle);
46
+ openThemeSketchSettings(result.theme.sketchSettings);
47
47
  liveMovedSinceBake.set(false);
48
48
  if (typeof document !== 'undefined') {
49
49
  document.dispatchEvent(new CustomEvent<AppliedThemeDetail>(THEME_APPLIED_EVENT, {
@@ -65,9 +65,9 @@ export async function initializeTheme(): Promise<void> {
65
65
 
66
66
  const active = await safeFetch<Theme>(`${API_BASE}/themes/active`);
67
67
  // A failed fetch is not "the theme carries no sketchstyle": treating null as
68
- // absent would tell the panel the look is off the theme, or hand a fresh
68
+ // absent would tell the panel the sketchstyle is off the theme, or hand a fresh
69
69
  // browser a blank buffer, over a fetch that will likely succeed next time.
70
70
  // The same call a built site makes (`@motion-proto/live-tokens/sketch`), so
71
71
  // one rule decides what a theme's sketchstyle means at boot in both.
72
- if (active) seedSketchFromTheme(active.sketchStyle);
72
+ if (active) seedSketchFromTheme(active.sketchSettings);
73
73
  }
@@ -7,15 +7,15 @@ import { getActiveColorsAndType } from './colorsAndTypeService';
7
7
  import { broadcastAppliedTheme, hydrateAppliedTheme } from './themeDocumentSync';
8
8
  import { CURRENT_COMPONENT_SCHEMA_VERSION } from './migrations';
9
9
  import { THEME_SCHEMA_VERSION } from './themeTypes';
10
- import { liveSketchStyle, themeSketchStyle } from '../sketch/sketchStore';
10
+ import { liveSketchSettings, themeSketchSettings } from '../sketch/sketchStore';
11
11
 
12
12
  export type { ThemeFillReport };
13
13
 
14
14
  /**
15
15
  * REST client for theme files, the documents of the editor. A theme carries a
16
- * whole look by value: the colors and type plus a config for every component
16
+ * whole content by value: the colors and type plus a config for every component
17
17
  * this install has. One theme is open (`themes/_active.json`) and one is
18
- * published (`themes/_production.json`); the live look is the open theme plus
18
+ * published (`themes/_production.json`); the live content is the open theme plus
19
19
  * whatever the `_working` buffers hold over it.
20
20
  *
21
21
  * `default` is the protected baseline — cannot be overwritten or deleted, and
@@ -75,7 +75,7 @@ export interface ApplyThemeResult {
75
75
  * Open a theme: the server clears the `_working` buffers, points
76
76
  * `themes/_active.json` at it, and returns the resolved state in one payload.
77
77
  * Live reads then fall through to the theme's embedded layers.
78
- * Production is untouched, so trying a look cannot change what the site ships.
78
+ * Production is untouched, so trying a content cannot change what the site ships.
79
79
  * The resolved response hydrates the caller immediately, then broadcasts the
80
80
  * same typed state to every already-open same-origin host/editor document.
81
81
  * Each store renderer projects that state into its own document, so a theme
@@ -96,7 +96,7 @@ export async function applyTheme(fileName: string): Promise<ApplyThemeResult> {
96
96
  return result;
97
97
  }
98
98
 
99
- export interface AdoptLookResult {
99
+ export interface AdoptThemeResult {
100
100
  ok: boolean;
101
101
  /** The theme production now ships. */
102
102
  productionTheme: { fileName: string; name: string; updatedAt: string };
@@ -111,7 +111,7 @@ export interface AdoptLookResult {
111
111
  * Answers 409 `ACTIVE_IS_PROTECTED` while the Default theme is open, which the
112
112
  * caller recovers from by forking it under a name of the user's own.
113
113
  */
114
- export async function adoptLook(): Promise<AdoptLookResult> {
114
+ export async function adoptTheme(): Promise<AdoptThemeResult> {
115
115
  const res = await fetch(`${API_BASE}/production`, { method: 'PUT' });
116
116
  if (!res.ok) {
117
117
  const body = await res.json().catch(() => ({}));
@@ -135,7 +135,7 @@ function withoutLiveMarkers<T extends { _fileName?: string; _source?: unknown }>
135
135
  }
136
136
 
137
137
  /**
138
- * The look as it stands: the live colors and type plus the live config of
138
+ * The content as it stands: the live colors and type plus the live config of
139
139
  * every component this install has, all by value. A theme is a complete
140
140
  * document (`docs/plans/theme-completeness.md`), so the capture carries every
141
141
  * component, not just the ones off their default.
@@ -145,7 +145,7 @@ function withoutLiveMarkers<T extends { _fileName?: string; _source?: unknown }>
145
145
  * way out of `GET /colors-and-type/active`, which matters: the server trusts an
146
146
  * already-embedded copy and runs no migrations over it on write.
147
147
  */
148
- async function captureLook(): Promise<Pick<Theme, 'colorsAndType' | 'componentConfigs' | 'sketchStyle'>> {
148
+ async function captureThemeContent(): Promise<Pick<Theme, 'colorsAndType' | 'componentConfigs' | 'sketchSettings'>> {
149
149
  const liveColorsAndType = await getActiveColorsAndType();
150
150
  if (!liveColorsAndType) {
151
151
  throw new Error('No live colors and type to capture');
@@ -159,11 +159,11 @@ async function captureLook(): Promise<Pick<Theme, 'colorsAndType' | 'componentCo
159
159
  });
160
160
  // Sketchstyle has no server door of its own (RJC 8): the live buffer, not a
161
161
  // fetch, is the source of truth for what the dials currently say.
162
- return { colorsAndType: withoutLiveMarkers(liveColorsAndType), componentConfigs, sketchStyle: liveSketchStyle() };
162
+ return { colorsAndType: withoutLiveMarkers(liveColorsAndType), componentConfigs, sketchSettings: liveSketchSettings() };
163
163
  }
164
164
 
165
165
  /**
166
- * Capture the current look into a new theme file and open it. Used by the
166
+ * Capture the current content into a new theme file and open it. Used by the
167
167
  * theme panel's Save As action and by the fork-then-Adopt flow the protected
168
168
  * Default theme forces.
169
169
  */
@@ -171,7 +171,7 @@ export async function saveAsTheme(
171
171
  fileName: string,
172
172
  displayName: string,
173
173
  ): Promise<void> {
174
- const look = await captureLook();
174
+ const content = await captureThemeContent();
175
175
  const now = new Date().toISOString();
176
176
  await saveTheme(fileName, {
177
177
  name: displayName,
@@ -179,15 +179,15 @@ export async function saveAsTheme(
179
179
  updatedAt: now,
180
180
  schemaVersion: THEME_SCHEMA_VERSION,
181
181
  componentSchemaVersion: CURRENT_COMPONENT_SCHEMA_VERSION,
182
- ...look,
182
+ ...content,
183
183
  });
184
- themeSketchStyle.set(look.sketchStyle);
184
+ themeSketchSettings.set(content.sketchSettings);
185
185
  liveMovedSinceBake.set(true);
186
186
  await setActiveTheme(fileName);
187
187
  }
188
188
 
189
189
  /**
190
- * Re-capture the current look into the open theme file. Used by the theme
190
+ * Re-capture the current content into the open theme file. Used by the theme
191
191
  * panel's Save action and by both Adopt paths, which publish what is saved.
192
192
  * The server rejects with 403 while the protected `default` theme is open.
193
193
  */
@@ -196,16 +196,16 @@ export async function saveActiveTheme(displayName?: string): Promise<void> {
196
196
  if (!active || !active._fileName) {
197
197
  throw new Error('No active theme');
198
198
  }
199
- const look = await captureLook();
199
+ const content = await captureThemeContent();
200
200
  await saveTheme(active._fileName, {
201
201
  name: displayName ?? active.name,
202
202
  createdAt: active.createdAt,
203
203
  updatedAt: new Date().toISOString(),
204
204
  schemaVersion: THEME_SCHEMA_VERSION,
205
205
  componentSchemaVersion: CURRENT_COMPONENT_SCHEMA_VERSION,
206
- ...look,
206
+ ...content,
207
207
  });
208
- themeSketchStyle.set(look.sketchStyle);
208
+ themeSketchSettings.set(content.sketchSettings);
209
209
  liveMovedSinceBake.set(true);
210
210
  }
211
211
 
@@ -1,17 +1,17 @@
1
1
  import type { ComponentSummary } from '../components/componentConfigService';
2
2
 
3
- export interface LookProductionInput {
3
+ export interface ThemeProductionInput {
4
4
  /** Slug of the theme the editor has open. */
5
5
  openTheme: string;
6
6
  /** Slug of the published theme, or null while the read has not landed. */
7
7
  productionTheme: string | null;
8
- /** Live look moved past what the published theme holds: unsaved edits, or a
8
+ /** Live theme moved past what the published theme holds: unsaved edits, or a
9
9
  * save since the last Adopt. */
10
10
  unpublished: boolean;
11
11
  }
12
12
 
13
- export interface LookProductionState {
14
- /** True when production is known to ship the look on screen. */
13
+ export interface ThemeProductionState {
14
+ /** True when production is known to ship the theme on screen. */
15
15
  inProduction: boolean;
16
16
  /** True while the production theme is unread: neither claim can be made. */
17
17
  unknown: boolean;
@@ -22,9 +22,9 @@ export interface LookProductionState {
22
22
  }
23
23
 
24
24
  /**
25
- * Whether production is running the look on screen. Production is one saved
25
+ * Whether production is running the theme on screen. Production is one saved
26
26
  * theme, so the first half is an identity check against the open one; the
27
- * second half is the live look sitting ahead of what was published, which
27
+ * second half is the live theme sitting ahead of what was published, which
28
28
  * `unpublished` carries.
29
29
  *
30
30
  * A null production read is not an answer, so it is neither state: `unknown`
@@ -32,11 +32,11 @@ export interface LookProductionState {
32
32
  * neutral state, which keeps a mount from flashing the alarm without letting a
33
33
  * read that never lands read as shipped forever.
34
34
  */
35
- export function lookProductionState({
35
+ export function themeProductionState({
36
36
  openTheme,
37
37
  productionTheme,
38
38
  unpublished,
39
- }: LookProductionInput): LookProductionState {
39
+ }: ThemeProductionInput): ThemeProductionState {
40
40
  const unknown = productionTheme === null;
41
41
  const themeOff = !unknown && productionTheme !== openTheme;
42
42
  return {
@@ -48,6 +48,6 @@ export function lookProductionState({
48
48
  }
49
49
 
50
50
  /** How many components run an unsaved buffer, diverging from what the open theme carries. */
51
- export function countComponentsOffLook(components: ComponentSummary[]): number {
51
+ export function countComponentsOffTheme(components: ComponentSummary[]): number {
52
52
  return components.filter((c) => c.source === 'working').length;
53
53
  }
@@ -2,13 +2,17 @@ import type { CurveAnchor } from '../../ui/curveEngine';
2
2
  import type { GradientValue } from './parsers/gradient';
3
3
  import type { Oklch } from '../palettes/oklch';
4
4
  import type { HarmonyAxis } from '../palettes/colorHarmony';
5
- import type { SketchStyle } from '../sketch/sketchStyles';
5
+ import type { SketchStyleSettings } from '../sketch/sketchStyles';
6
6
  /** Single source of truth for the theme schema version
7
7
  * (docs/plans/theme-completeness.md, Wave 2 step 5). It lives here, on the
8
8
  * shipped side: `vite-plugin/` is build tooling and is not in the tarball, so
9
9
  * anything under `src/` that reaches into it resolves in this repo and
10
10
  * nowhere else. `normalizeTheme.ts` re-exports this. */
11
- export const THEME_SCHEMA_VERSION = 4;
11
+ /** 5 renamed the sketch field from `sketchStyle` to `sketchSettings`. The bump
12
+ * is what makes the boot pass rewrite a theme still on the old key: the read
13
+ * accepts both, but a built site reads its theme JSON raw and would see
14
+ * `undefined` until the file itself was converted. */
15
+ export const THEME_SCHEMA_VERSION = 5;
12
16
 
13
17
  /** What the completeness fill had to add to reach a whole theme. `components`
14
18
  * names each component the input carried no entry for; `aliases` counts keys
@@ -240,7 +244,7 @@ export interface ComponentConfigMeta {
240
244
  }
241
245
 
242
246
  /**
243
- * A saved look, encapsulated: the colors and type plus a config for every
247
+ * A saved theme, encapsulated: the colors and type plus a config for every
244
248
  * component this install has, all carried by value. Themes are the documents
245
249
  * of the editor. Applying one opens it by clearing the reserved `_working`
246
250
  * buffers and updating `themes/_active.json`; live reads fall through to its
@@ -267,7 +271,7 @@ export interface Theme {
267
271
  * component schema and then fills any gap from the local default, on every
268
272
  * read (Waves 1 and 2 of `docs/plans/theme-completeness.md`); both the
269
273
  * migration and the fill are persisted on the next write, so a theme is a
270
- * whole-look document rather than a diff against a moving baseline.
274
+ * whole-theme document rather than a diff against a moving baseline.
271
275
  * `component-configs/<id>/default.json` is the derivation product of that
272
276
  * component's `:global(:root)`, not a resolution layer this map defers to
273
277
  * — a config for a component this install does not have, or an alias key
@@ -280,10 +284,10 @@ export interface Theme {
280
284
  * buy no isolation. `normalizeTheme` sets this on every read; an embedded
281
285
  * config's own `schemaVersion` (if hand-authored) is ignored and stripped. */
282
286
  componentSchemaVersion: number;
283
- /** The sketchstyle this look paints, by value. Absent means the look is
287
+ /** The sketchstyle this theme paints, by value. Absent means the theme is
284
288
  * crisp: presence is the on state, so there is no separate flag that can
285
289
  * disagree with the dials beside it (RJC 1). */
286
- sketchStyle?: SketchStyle;
290
+ sketchSettings?: SketchStyleSettings;
287
291
  /** Server-attached file-name marker. Same role as `ColorsAndType._fileName`. */
288
292
  _fileName?: string;
289
293
  }
@@ -54,7 +54,7 @@ the editor swaps live.
54
54
  To ship, click **Adopt** in the Theme panel. That saves the open theme and bakes
55
55
  it into `src/live-tokens/data/tokens.generated.css`, which your build bundles
56
56
  alongside `tokens.css`. Adopt is the only action that changes what your site
57
- ships, so try any look you like first. The editor itself never reaches
57
+ ships, so try any theme you like first. The editor itself never reaches
58
58
  production.
59
59
 
60
60
  Already have a Svelte 5 + Vite app? The
@@ -22,7 +22,7 @@ separately wobbled boxes.
22
22
 
23
23
  ## The sketchstyles
24
24
 
25
- Seven looks ship with the package, and each is a complete set of dials rather
25
+ Seven sketchstyles ship with the package, and each is a complete set of dials rather
26
26
  than just a name:
27
27
 
28
28
  - **Pencil.** Two graphite passes on their own seeds, so the outline disagrees
@@ -38,8 +38,8 @@ than just a name:
38
38
  - **Napkin.** Ballpoint in a hurry. Everything loose at once.
39
39
  - **Dry marker.** Ink that ran out. One scratchy pass over a mostly eaten fill.
40
40
 
41
- All seven ship as files, one per look, under
42
- `src/live-tokens/data/sketch-styles/` in the package. There is no look that
41
+ All seven ship as files, one per sketchstyle, under
42
+ `src/live-tokens/data/sketch-styles/` in the package. There is no sketchstyle that
43
43
  exists only as code, so every one of them can be read, copied and edited.
44
44
 
45
45
  Pick one, then move whatever you like. **Save As** keeps your dials under a name
@@ -51,6 +51,11 @@ project's own copy under the same name, which takes its place in the list;
51
51
  delete that copy and the shipped file behind it comes back. Both are a
52
52
  different gesture from saving a theme; see "Where the settings live" below.
53
53
 
54
+ A theme carries its sketch settings by value, so it can hold a set that belongs
55
+ to no file. The list shows that set as **Unsaved**. It owns no file, so there is
56
+ nothing for Save to write over; **Save As** turns it into a sketchstyle like any
57
+ other, which you can then save over and delete.
58
+
54
59
  Your sketchstyles and the shipped ones are one list. A sketchstyle named after
55
60
  a shipped one replaces it, keeping its place in the list, so a project that
56
61
  wants its own Pencil saves one and every picker shows that one instead.
@@ -97,9 +102,9 @@ is disabled there; use **Save As** to fold the dials into a theme of your
97
102
  own.
98
103
 
99
104
  **Save** and **Save As** in the **Sketchstyle** view are a different gesture.
100
- They write a named sketchstyle to `src/live-tokens/data/sketch-styles/`, a look
105
+ They write a named sketchstyle to `src/live-tokens/data/sketch-styles/`, a sketchstyle
101
106
  you can pick from any theme. Neither touches the open theme, and neither marks
102
- the look off the theme.
107
+ the theme as changed.
103
108
 
104
109
  ## Shipping the layer
105
110
 
@@ -110,11 +115,11 @@ has no server to ask, so it hands the field over itself:
110
115
  import { seedSketchFromTheme } from '@motion-proto/live-tokens/sketch';
111
116
  import theme from './live-tokens/data/themes/sketchy.json';
112
117
 
113
- seedSketchFromTheme(theme.sketchStyle);
118
+ seedSketchFromTheme(theme.sketchSettings);
114
119
  await bootLiveTokens(App, '#app');
115
120
  ```
116
121
 
117
- Call it before mounting, so the look is up on the first frame. Pass the field
122
+ Call it before mounting, so the sketch layer is up on the first frame. Pass the field
118
123
  raw. A theme written against older dial names is carried forward on the way in,
119
124
  the same reconciliation the dev server runs on every theme it reads.
120
125
 
@@ -122,7 +127,7 @@ Nothing is baked. `tokens.generated.css` still holds token values only, and the
122
127
  layer stays JavaScript the page runs, because it builds an SVG filter bank
123
128
  rather than a set of custom properties.
124
129
 
125
- A visitor who has picked a look of their own keeps it, None included. The theme
130
+ A visitor who has picked a sketchstyle of their own keeps it, None included. The theme
126
131
  seeds a browser that has decided nothing and never overwrites one that has, so
127
132
  calling this on every boot is safe.
128
133
 
@@ -138,26 +143,26 @@ const files = import.meta.glob<{ name?: string; settings: unknown }>(
138
143
  );
139
144
 
140
145
  await bootLiveTokens(App, '#app', {
141
- sketchLooks: Object.entries(files).map(([path, file]) => {
146
+ sketchStyles: Object.entries(files).map(([path, file]) => {
142
147
  const id = path.split('/').pop()!.replace('.json', '');
143
148
  return { id, label: file.name || id, settings: file.settings };
144
149
  }),
145
150
  });
146
151
  ```
147
152
 
148
- Projects made with `create` ship this already. The file's slug is the look's
153
+ Projects made with `create` ship this already. The file's slug is the sketchstyle's
149
154
  id, so a sketchstyle picked in the editor keeps working once the site is built.
150
155
 
151
156
  ### Building a picker
152
157
 
153
- `sketchLooks` is every look on offer, shipped and your own, as a store. Give
154
- each row `setSketch(look.id)`, and add your own **None** row: off is a state of
155
- the effect rather than one of the looks.
158
+ `sketchStyles` is every sketchstyle on offer, shipped and your own, as a store. Give
159
+ each row `setSketch(style.id)`, and add your own **None** row: off is a state of
160
+ the effect rather than one of the sketchstyles.
156
161
 
157
- `themeSketchLook` is the theme's own look as one more row. It is null when the
158
- theme carries none, and null when what it carries is a look already in
159
- `sketchLooks`, since that row names it. Its id goes to `setSketch` like any
160
- other, so a visitor who wanders off the theme's look can come back to it.
162
+ `unsavedSketchStyle` is the settings the theme carries as one more row. It is null when the
163
+ theme carries none, and null when what it carries matches a sketchstyle already in
164
+ `sketchStyles`, since that row names it. Its id goes to `setSketch` like any
165
+ other, so a visitor who wanders off the theme's own settings can come back to it.
161
166
 
162
167
  ## Drawing your own elements
163
168