@motion-proto/live-tokens 0.47.0 → 0.48.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/.claude/skills/live-tokens-adjust-shape-space/SKILL.md +68 -0
- package/.claude/skills/live-tokens-create-component/SKILL.md +2 -2
- package/.claude/skills/live-tokens-generate-theme/SKILL.md +152 -0
- package/CHANGELOG.md +292 -0
- package/README.md +37 -26
- package/bin/adjust.mjs +254 -0
- package/bin/cli.mjs +94 -7
- package/bin/generate-theme.mjs +251 -0
- package/bin/migrate.mjs +95 -6
- package/dist-plugin/adjust/index.cjs +259 -0
- package/dist-plugin/adjust/index.d.cts +45 -0
- package/dist-plugin/adjust/index.d.ts +45 -0
- package/dist-plugin/adjust/index.js +176 -0
- package/dist-plugin/{chunk-H4TRUINI.js → chunk-44RSTAII.js} +0 -48
- package/dist-plugin/chunk-6OZFXIQI.js +316 -0
- package/dist-plugin/chunk-76TFDTJO.js +556 -0
- package/dist-plugin/chunk-D3ZVKOR4.js +52 -0
- package/dist-plugin/dataPaths-DBN0RPuT.d.cts +54 -0
- package/dist-plugin/dataPaths-DBN0RPuT.d.ts +54 -0
- package/dist-plugin/generateColorsAndType/index.cjs +1781 -0
- package/dist-plugin/generateColorsAndType/index.d.cts +81 -0
- package/dist-plugin/generateColorsAndType/index.d.ts +81 -0
- package/dist-plugin/generateColorsAndType/index.js +1210 -0
- package/dist-plugin/index.cjs +901 -533
- package/dist-plugin/index.d.cts +3 -2
- package/dist-plugin/index.d.ts +3 -2
- package/dist-plugin/index.js +658 -1082
- package/dist-plugin/migrateData/index.cjs +728 -0
- package/dist-plugin/migrateData/index.d.cts +60 -0
- package/dist-plugin/migrateData/index.d.ts +60 -0
- package/dist-plugin/migrateData/index.js +348 -0
- package/dist-plugin/themeTypes-DMHZOnUn.d.cts +211 -0
- package/dist-plugin/themeTypes-DMHZOnUn.d.ts +211 -0
- package/dist-plugin/tokensCssMigrations/index.cjs +2 -1
- package/dist-plugin/tokensCssMigrations/index.d.cts +3 -15
- package/dist-plugin/tokensCssMigrations/index.d.ts +3 -15
- package/dist-plugin/tokensCssMigrations/index.js +4 -2
- package/package.json +18 -4
- package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +183 -158
- package/src/editor/component-editor/scaffolding/ComponentFileMenu.svelte +0 -4
- package/src/editor/component-editor/scaffolding/SaveAsDialog.svelte +4 -4
- package/src/editor/component-editor/scaffolding/TokenLayout.svelte +2 -59
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +1 -1
- package/src/editor/core/components/adjustAliases.ts +180 -0
- package/src/editor/core/components/aliasKinds.ts +73 -0
- package/src/editor/core/components/componentConfigService.ts +26 -38
- package/src/editor/core/flashStatus.ts +1 -1
- package/src/editor/core/fonts/fontMigration.ts +13 -13
- package/src/editor/core/fonts/fontPairing.ts +35 -0
- package/src/editor/core/palettes/paletteDerivation.ts +128 -16
- package/src/editor/core/preview/lookPreview.ts +141 -0
- package/src/editor/core/productionPulse.ts +34 -21
- package/src/editor/core/storage/files/versionedFileResourceClient.ts +18 -45
- package/src/editor/core/store/editorConfigStore.ts +2 -2
- package/src/editor/core/store/editorPersistence.ts +0 -1
- package/src/editor/core/store/editorStore.ts +114 -51
- package/src/editor/core/store/editorTypes.ts +0 -1
- package/src/editor/core/store/gradientSource.ts +2 -2
- package/src/editor/core/themes/colorsAndTypeService.ts +95 -0
- package/src/editor/core/themes/generateColorsAndType.ts +433 -0
- package/src/editor/core/themes/loadRows.ts +64 -0
- package/src/editor/core/themes/lookSummary.ts +75 -0
- package/src/editor/core/themes/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +3 -3
- package/src/editor/core/themes/migrations/2026-05-13-primary-to-brand.ts +2 -2
- package/src/editor/core/themes/migrations/2026-05-26-drop-overlay-extra-stops.ts +3 -3
- package/src/editor/core/themes/migrations/2026-08-13-drop-legacy-shape-space-keys.ts +38 -0
- package/src/editor/core/themes/migrations/2026-08-15-line-height-scale-rename.ts +57 -0
- package/src/editor/core/themes/migrations/index.ts +19 -11
- package/src/editor/core/themes/slices/components.ts +4 -4
- package/src/editor/core/themes/slices/fonts.ts +1 -1
- package/src/editor/core/themes/slices/gradients.ts +22 -0
- package/src/editor/core/themes/slices/palettes.ts +2 -2
- package/src/editor/core/themes/themeInit.ts +21 -29
- package/src/editor/core/themes/themeService.ts +218 -78
- package/src/editor/core/themes/themeTypes.ts +94 -51
- package/src/editor/docs/Docs.svelte +1 -0
- package/src/editor/docs/chapters.ts +1 -0
- package/src/editor/docs/content/01-overview.md +1 -1
- package/src/editor/docs/content/editing-tokens.md +6 -6
- package/src/editor/docs/content/getting-started.md +9 -7
- package/src/editor/docs/content/themes-workflow.md +74 -34
- package/src/editor/docs/content/where-themes-live.md +61 -0
- package/src/editor/docs/content.generated.ts +5 -4
- package/src/editor/index.ts +27 -27
- package/src/editor/pages/ComponentEditorPage.svelte +2 -2
- package/src/editor/pages/EditorShell.svelte +4 -30
- package/src/editor/ui/BezierCurveEditor.svelte +2 -2
- package/src/editor/ui/ColorEditPanel.svelte +1 -1
- package/src/editor/ui/FileLoadList.svelte +67 -7
- package/src/editor/ui/GradientEditor.svelte +3 -3
- package/src/editor/ui/PaletteEditor.svelte +26 -12
- package/src/editor/ui/ThemePanel.svelte +1067 -0
- package/src/editor/ui/UIDialog.svelte +11 -9
- package/src/editor/ui/UIPillButton.svelte +7 -2
- package/src/editor/ui/colors/ColorWheel.svelte +34 -1
- package/src/editor/ui/curveEngine.ts +48 -0
- package/src/editor/ui/index.ts +4 -2
- package/src/editor/ui/palette/PaletteBase.svelte +75 -51
- package/src/editor/ui/palette/PaletteJumpButton.svelte +3 -8
- package/src/live-tokens/data/colors-and-type/autumn.json +2490 -0
- package/src/live-tokens/data/{themes → colors-and-type}/default.json +816 -293
- package/src/live-tokens/data/colors-and-type/halloween.json +2529 -0
- package/src/live-tokens/data/colors-and-type/midnight-study.json +2536 -0
- package/src/live-tokens/data/colors-and-type/ocean.json +2489 -0
- package/src/live-tokens/data/colors-and-type/royal-velvet.json +2520 -0
- package/src/live-tokens/data/colors-and-type/spring-meadow.json +2476 -0
- package/src/live-tokens/data/colors-and-type/sunset.json +2528 -0
- package/src/live-tokens/data/themes/autumn.json +3918 -0
- package/src/live-tokens/data/themes/halloween.json +3992 -0
- package/src/live-tokens/data/themes/midnight-study.json +3932 -0
- package/src/live-tokens/data/themes/ocean.json +3917 -0
- package/src/live-tokens/data/themes/royal-velvet.json +3983 -0
- package/src/live-tokens/data/themes/spring-meadow.json +3904 -0
- package/src/live-tokens/data/themes/sunset.json +3931 -0
- package/src/live-tokens/data/tokens.generated.css +106 -117
- package/src/system/styles/CONVENTIONS.md +1 -1
- package/src/system/styles/fonts.css +1 -1
- package/template/README.md +7 -5
- package/template/_gitignore +0 -6
- package/src/editor/core/components/componentPersist.ts +0 -62
- package/src/editor/core/manifests/manifestService.ts +0 -172
- package/src/editor/ui/ManifestFileManager.svelte +0 -446
- package/src/editor/ui/ThemeFileManager.svelte +0 -785
- package/src/live-tokens/data/manifests/default.json +0 -35
package/README.md
CHANGED
|
@@ -8,12 +8,13 @@ A foundational design system for quickly styling and building Svelte + Vite micr
|
|
|
8
8
|
|
|
9
9
|
- **Real-time token editing.** Pick a color, drag a hue slider, retype a font size — the page repaints on every input event via CSS-variable writes. No reload, no save-and-refresh, no build step. Works across colors, typography, spacing, radii, shadows, motion, palettes, and gradients.
|
|
10
10
|
- **Real-time component editing.** Each of ~24 shipped Svelte components (Button, Input, Card, Dialog, Badge, Callout, Table, Tooltip, Toggle, TabBar, SegmentedControl, RadioButton, MenuSelect, ProgressBar, CornerBadge, SectionDivider, CollapsibleSection, Notification, Image, ImageLightbox, CodeSnippet, SideNavigation, and more) declares its own design-token aliases in a `:global(:root)` block. Rewire any alias from a per-component picker and see that component update everywhere it's used — live, on your real pages, not in a Storybook sandbox.
|
|
11
|
-
- **Theme editor** (`/live-tokens/editor` route, dev-only) — the home of real-time token editing. Save themes to disk as JSON,
|
|
11
|
+
- **Theme editor** (`/live-tokens/editor` route, dev-only) — the home of real-time token editing. Save themes to disk as JSON, then Adopt one to bake it into static CSS for the build.
|
|
12
12
|
- **Per-component editor** (`/live-tokens/components` route, dev-only) — the home of real-time component-alias editing. Pick token aliases per component without writing CSS.
|
|
13
13
|
- **Live editor overlay** — pins to the top-right of every dev page. Opens the editor in a side panel or floating window so you edit *on the page you're styling*, not in a separate tab. Includes a "Page Source" button that opens the current page's `.svelte` file in VS Code.
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
14
|
+
- **Themes** — a theme is a whole look in one file: colors and type plus a config for every component you changed, held by value. In the editor this is the Theme panel, with Colors & Type and Components as its parts. Themes are documents: loading one opens it, filling the editor's working buffer and returning every component it does not carry to its default, and nothing your site ships changes until you Adopt. A narrower load takes the colors and type alone and leaves your shapes. Export one and import it into another project to restore the full styling in one step.
|
|
15
|
+
- **Seven example looks** — Autumn, Halloween, Midnight Study, Ocean, Royal Velvet, Spring Meadow and Sunset each ship as a full theme: preset colors and type plus a shape personality of radius, padding, gap and border-width aliases. Each preset also names its own Google Fonts pairing, one display family and one body family, so a look carries type as well as colour and shape. They need no local files, so Load one to try a whole look on your own pages and load Motion Proto to come back. Saving over a preset writes a local copy that shadows the shipped one; delete that copy and the shipped version returns.
|
|
16
|
+
- **Vite plugin** — hosts the `/api/live-tokens/{colors-and-type,component-configs,themes}/*` routes the editor reads and saves through. The single namespace keeps live-tokens' routes from colliding with anything your app serves under `/api`.
|
|
17
|
+
- **Claude Code skill suite** — five bundled skills so you can drive the package in plain English. `build-page` composes pages from the shipped components. `pick-component` decides between confusing pairs (TabBar vs SegmentedControl, Card vs CollapsibleSection). `create-component` authors a new editable component against the project's naming, state-model, and import rules. `generate-theme` turns a mood brief ("bright and cheerful", "dark night theme") into a complete AA-checked color theme. `adjust-shape-space` turns "make the buttons pill shaped" or "space it out" into new radius, padding, gap, and border-width aliases. One command to install them all: `npx @motion-proto/live-tokens setup-claude`. See [Claude Code skills](#claude-code-skills) below.
|
|
17
18
|
|
|
18
19
|
## Quick install
|
|
19
20
|
|
|
@@ -42,14 +43,20 @@ export default defineConfig({
|
|
|
42
43
|
```
|
|
43
44
|
|
|
44
45
|
The `themeFileApi` plugin:
|
|
45
|
-
-
|
|
46
|
+
- Resolves the `default` colors and type from the installed package, so you start on the shipped defaults without a local copy.
|
|
46
47
|
- Discovers components at `src/components/*.svelte` (and `src/system/components/*.svelte` for back-compat) and seeds `src/live-tokens/data/component-configs/{comp}/default.json` from each component's `:global(:root)` block.
|
|
48
|
+
- Writes `src/live-tokens/data/themes/default.json` on dev-server start: the Default theme, derived from the shipped colors and type and those component defaults, and regenerated whenever they change. It is protected, so the editor never deletes it and an outside deletion heals on the next start.
|
|
49
|
+
- Bakes `tokens.generated.css` from the production theme at startup, so a fresh checkout builds against the look you shipped.
|
|
47
50
|
- Hosts the `/api/live-tokens/*` routes the editor uses to save and load themes + per-component configs.
|
|
48
51
|
- Auto-injects `__PROJECT_ROOT__` for the overlay's "Page Source" link and `__LIVE_TOKENS_API_BASE__` so the client uses whatever `apiBase` you configured.
|
|
49
52
|
|
|
53
|
+
A project last opened on 0.47.1 or earlier still keeps its colors and type in `themes/` and its whole looks in `manifests/`, the names 0.48 reassigned. The plugin recognises that layout, writes nothing at all, and says so. `npx live-tokens migrate` moves `themes/` to `colors-and-type/` and `manifests/` to `themes/`, then heals what is inside: it reads what the retired per-layer `_active.json` / `_production.json` pointers resolved to, records it as the production theme, and clears them. Restart the dev server afterwards.
|
|
54
|
+
|
|
50
55
|
### Where data lands — and how to move it
|
|
51
56
|
|
|
52
|
-
By default, the plugin reads and writes under one folder: `src/live-tokens/data/`. Inside that folder live three subdirectories — `
|
|
57
|
+
By default, the plugin reads and writes under one folder: `src/live-tokens/data/`. Inside that folder live three subdirectories — `colors-and-type/`, `themes/`, `component-configs/` — each owned by the plugin.
|
|
58
|
+
|
|
59
|
+
`themes/` holds the documents: one file per whole look, plus `_active.json` naming the one the editor has open and `_production.json` naming the one your site ships. `colors-and-type/` and `component-configs/{comp}/` hold each layer's `default.json` baseline, any preset you save by name, and the `_working.json` buffer for edits you have not saved into a theme. A buffer exists only where the look sits off the shipped default, so a new project has none.
|
|
53
60
|
|
|
54
61
|
To move them, create a `live-tokens.config.json` at your project root:
|
|
55
62
|
|
|
@@ -59,17 +66,19 @@ To move them, create a `live-tokens.config.json` at your project root:
|
|
|
59
66
|
}
|
|
60
67
|
```
|
|
61
68
|
|
|
62
|
-
All four keys are optional. `dataDir` is the headline knob — it relocates all three subfolders at once. The per-folder overrides exist for unusual layouts (e.g. a monorepo where
|
|
69
|
+
All four keys are optional. `dataDir` is the headline knob — it relocates all three subfolders at once. The per-folder overrides exist for unusual layouts (e.g. a monorepo where colors and type are shared across packages but component-configs aren't):
|
|
63
70
|
|
|
64
71
|
```json
|
|
65
72
|
{
|
|
66
73
|
"dataDir": "src/live-tokens/data",
|
|
67
|
-
"
|
|
74
|
+
"colorsAndTypeDir": "../shared/colors-and-type",
|
|
68
75
|
"componentConfigsDir": "src/live-tokens/data/component-configs",
|
|
69
|
-
"
|
|
76
|
+
"themesDir": "src/live-tokens/data/themes"
|
|
70
77
|
}
|
|
71
78
|
```
|
|
72
79
|
|
|
80
|
+
`colorsAndTypeDir` holds the colors-and-type files; `themesDir` holds the whole-look themes.
|
|
81
|
+
|
|
73
82
|
Resolution order, per folder: explicit `themeFileApi(opts)` argument > matching key in `live-tokens.config.json` > `<dataDir>/<sub>`. The dev server reads the file once at startup — restart vite to pick up changes.
|
|
74
83
|
|
|
75
84
|
### Bootstrap in `main.ts`
|
|
@@ -204,8 +213,8 @@ import '@motion-proto/live-tokens/app/fonts.css'; // optional: Fraunces + Manro
|
|
|
204
213
|
```
|
|
205
214
|
|
|
206
215
|
…or copy `node_modules/@motion-proto/live-tokens/src/system/styles/tokens.css` into
|
|
207
|
-
your project and edit.
|
|
208
|
-
|
|
216
|
+
your project and edit. It stays hand-authored: the editor writes what you Adopt
|
|
217
|
+
into the sidecar `tokens.generated.css`, never back into `tokens.css`.
|
|
209
218
|
|
|
210
219
|
## Consuming live-tokens from scratch
|
|
211
220
|
|
|
@@ -269,8 +278,8 @@ src/
|
|
|
269
278
|
system/styles/tokens.css # vendored Layer-1 tokens — committed
|
|
270
279
|
live-tokens/data/ # editor state — committed
|
|
271
280
|
tokens.generated.css # editor output
|
|
272
|
-
themes/
|
|
273
|
-
|
|
281
|
+
themes/ # one file per whole look, plus the two pointers
|
|
282
|
+
colors-and-type/ component-configs/
|
|
274
283
|
vite.config.ts # svelte({ preprocess: vitePreprocess() }) + themeFileApi
|
|
275
284
|
svelte.config.js # vitePreprocess()
|
|
276
285
|
```
|
|
@@ -279,7 +288,7 @@ Conventions that make this work:
|
|
|
279
288
|
|
|
280
289
|
- **Vendor `tokens.css` into `src/` and commit it.** Point `themeFileApi({ tokensCssPath })` at that file, not at one inside `node_modules`. The dev server writes your edits there; a copy under `node_modules` is wiped on every `npm install`.
|
|
281
290
|
- **All editable state lives under `src/` and is committed** — `tokens.css`, `tokens.generated.css`, and everything in `live-tokens/data/`. This is the invariant that makes upgrades safe: `npm install` only ever touches `node_modules` + `package.json` + the lockfile, never your `src/`.
|
|
282
|
-
-
|
|
291
|
+
- **Nothing is backed up for you.** The dev server keeps no snapshots, so git is the safety net: commit a theme you care about before editing over it.
|
|
283
292
|
- **Preprocess with `vitePreprocess()`** (bundled in `@sveltejs/vite-plugin-svelte`), keeping `sass` installed for the components' `scss`. No `svelte-preprocess`, no `legacy-peer-deps` `.npmrc` — the dependency tree resolves cleanly on its own (since 0.19.1).
|
|
284
293
|
- **Import only from the public surface** — `@motion-proto/live-tokens`, `/components/*`, `/vite-plugin`, `/app/*`.
|
|
285
294
|
|
|
@@ -311,13 +320,15 @@ The component appears in the `/live-tokens/components` page under a **CUSTOM** g
|
|
|
311
320
|
|
|
312
321
|
## Claude Code skills
|
|
313
322
|
|
|
314
|
-
The package ships a suite of Claude Code skills that encode the project's conventions so Claude can drive the package in plain English. They cover the
|
|
323
|
+
The package ships a suite of Claude Code skills that encode the project's conventions so Claude can drive the package in plain English. They cover the jobs the README itself can't carry well: deciding which shipped component fits a need, composing a page from the catalogue, generating a color theme from a mood brief, adjusting shape and space from plain language, and (for the long-tail case) authoring a new editable component. Each skill auto-triggers from natural-language requests — no slash commands. (Plain `npm install` plus the README handle first-time setup.)
|
|
315
324
|
|
|
316
325
|
| Skill | Triggers on | What it knows |
|
|
317
326
|
|--------------------------------|----------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|
|
|
318
327
|
| `live-tokens-build-page` | "build a pricing page using live-tokens components" | shipped-component catalogue, column grid, `pageSources` registration, token-only styling rule |
|
|
319
328
|
| `live-tokens-pick-component` | "what's the difference between TabBar and SegmentedControl?" | decision tables for each confusable family (selection, container, messaging, on/off); when to author a new one instead |
|
|
320
329
|
| `live-tokens-create-component` | "author a new Toggle component for my live-tokens project" | runtime + editor + `registerComponent()` recipe, naming scheme, state model, public-imports rule, verification checklist |
|
|
330
|
+
| `live-tokens-generate-theme` | "make me a bright and cheerful color theme" | mood → OKLCH seed framework (chroma budget, per-role bands, gamut guardrails, holiday palettes); drives `npx live-tokens generate-theme`, which enforces AA contrast |
|
|
331
|
+
| `live-tokens-adjust-shape-space` | "make the buttons pill shaped", "space it out" | shape and space idioms (pill, sharper, softer, tighter, airier) → radius/padding/gap/border-width ops; drives `npx live-tokens adjust`, which moves aliases along the shipped token scales |
|
|
321
332
|
|
|
322
333
|
### Install
|
|
323
334
|
|
|
@@ -345,8 +356,8 @@ It enforces the file layout, `:global(:root)` block, token-suffix vocabulary, th
|
|
|
345
356
|
|
|
346
357
|
## How the editor ships changes to prod
|
|
347
358
|
|
|
348
|
-
1. Edit in `/live-tokens/editor` or `/live-tokens/components`.
|
|
349
|
-
2.
|
|
359
|
+
1. Edit in `/live-tokens/editor` or `/live-tokens/components`. Your edits sit in the working buffer (`_working.json`); **Save** in the Theme panel captures that buffer into the open theme at `<dataDir>/themes/{name}.json`.
|
|
360
|
+
2. **Adopt** the theme. It becomes the production theme, and its variables are baked into `tokens.generated.css` next to your authored `tokens.css`. Nothing else writes that file, so trying a look never changes what you ship.
|
|
350
361
|
3. `npm run build` bundles both as plain CSS. No editor code, no JSON lookups, no dev surfaces ship to prod.
|
|
351
362
|
|
|
352
363
|
## File ownership — what the plugin writes
|
|
@@ -362,19 +373,19 @@ Knowing which files the plugin touches matters when upgrading the package or wor
|
|
|
362
373
|
|
|
363
374
|
It never writes to your project root, your `src/` outside the data folder, or anywhere else.
|
|
364
375
|
|
|
365
|
-
**At dev-server startup, the plugin
|
|
376
|
+
**At dev-server startup, the plugin fills gaps and refreshes its own derived files — it never overwrites authored ones:**
|
|
366
377
|
|
|
367
|
-
- `<dataDir>/themes/default.json` —
|
|
368
|
-
- `<dataDir>/themes/_active.json` and `_production.json` — written **only if missing
|
|
369
|
-
- `<dataDir>/component-configs/{comp}/_active.json` and `_production.json` — same: only if missing.
|
|
378
|
+
- `<dataDir>/themes/default.json` — the derived Default theme, regenerated when the shipped colors and type or a component default changes.
|
|
379
|
+
- `<dataDir>/themes/_active.json` and `_production.json` — written **only if missing**, and healed when they name a theme that no longer resolves.
|
|
370
380
|
- `<dataDir>/component-configs/{comp}/default.json` — regenerated from the component's `:global(:root)` block **only when the `.svelte` source is newer than the existing default**. This file is a build artifact of the source; don't hand-edit it.
|
|
381
|
+
- `<tokensCssPath sibling>/tokens.generated.css` — rebaked from the production theme, so a fresh checkout builds against the look you shipped.
|
|
371
382
|
|
|
372
|
-
**At dev-time editor actions, these files get rewritten by
|
|
383
|
+
**At dev-time editor actions, these files get rewritten by what you do:**
|
|
373
384
|
|
|
374
|
-
- `<dataDir>/
|
|
375
|
-
- `<
|
|
376
|
-
- `<
|
|
377
|
-
- `<
|
|
385
|
+
- `<dataDir>/colors-and-type/_working.json` and `<dataDir>/component-configs/{comp}/_working.json` — the buffer, written as you save each part of the look and cleared when a theme you open does not carry it.
|
|
386
|
+
- `<dataDir>/themes/{name}.json` — every Save and Save As in the Theme panel.
|
|
387
|
+
- `<dataDir>/colors-and-type/{name}.json` and `<dataDir>/component-configs/{comp}/{name}.json` — only when you save a preset by name. Nothing machine-written lands among them.
|
|
388
|
+
- `<tokensCssPath sibling>/tokens.generated.css` and `fonts.css` — regenerated from the production theme when you Adopt.
|
|
378
389
|
|
|
379
390
|
The developer-authored `tokens.css` itself is **never written** by the plugin — it holds defaults you're free to hand-edit. The editor's overrides land in the sidecar `tokens.generated.css`, which the package imports immediately after `tokens.css`.
|
|
380
391
|
|
package/bin/adjust.mjs
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// `live-tokens adjust` worker.
|
|
2
|
+
//
|
|
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
|
|
5
|
+
// writes the result into that component's `_working.json` buffer: the same slot
|
|
6
|
+
// the editor's own edits land in, so an adjustment is an unsaved edit the user
|
|
7
|
+
// saves into a theme when they want to keep it. `default.json`, named preset
|
|
8
|
+
// files, themes, colors and type, and tokens.css are never touched.
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
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
|
+
|
|
18
|
+
const SOURCE_LABELS = {
|
|
19
|
+
working: 'your unsaved edits',
|
|
20
|
+
theme: 'the open theme',
|
|
21
|
+
default: 'the shipped default',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const SKIP_LABELS = [
|
|
25
|
+
['raw-value', 'raw value, not a token'],
|
|
26
|
+
['off-ladder', 'off the ladder'],
|
|
27
|
+
['clamped', 'already at the ladder end'],
|
|
28
|
+
['pill-preserved', 'pill preserved (pass "full": true to move it)'],
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
async function loadEngine() {
|
|
32
|
+
if (!existsSync(ENGINE)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`adjust engine not found at ${relative(process.cwd(), ENGINE)}. ` +
|
|
35
|
+
`Build the plugin first (npm run build:plugin).`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return import(ENGINE);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readJson(path) {
|
|
42
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readJsonIfExists(path) {
|
|
46
|
+
return existsSync(path) ? readJson(path) : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
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
|
+
/** Successive ops can touch the same alias (soften, then pill the buttons);
|
|
79
|
+
* the report shows one entry per alias, first `from` to last `to`. */
|
|
80
|
+
function collapseChanges(changes) {
|
|
81
|
+
const byVariable = new Map();
|
|
82
|
+
for (const change of changes) {
|
|
83
|
+
const entry = byVariable.get(change.variable);
|
|
84
|
+
if (entry) entry.to = change.to;
|
|
85
|
+
else byVariable.set(change.variable, { ...change });
|
|
86
|
+
}
|
|
87
|
+
return [...byVariable.values()].filter((c) => c.from !== c.to);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The open theme, read the way every other door reads it: the local file
|
|
91
|
+
* first, then the copy the installed package ships. */
|
|
92
|
+
function readActiveTheme(themesDir) {
|
|
93
|
+
if (!themesDir) return null;
|
|
94
|
+
const slug = readJsonIfExists(join(themesDir, '_active.json'))?.activeFile ?? 'default';
|
|
95
|
+
const theme =
|
|
96
|
+
readJsonIfExists(join(themesDir, `${slug}.json`)) ??
|
|
97
|
+
readJsonIfExists(join(packageThemesDir, `${slug}.json`));
|
|
98
|
+
return theme ? { slug, theme } : null;
|
|
99
|
+
}
|
|
100
|
+
|
|
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. */
|
|
104
|
+
function readLiveConfigs(dir, active) {
|
|
105
|
+
const configs = {};
|
|
106
|
+
const sources = {};
|
|
107
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
108
|
+
if (!entry.isDirectory()) continue;
|
|
109
|
+
const comp = entry.name;
|
|
110
|
+
const componentDir = join(dir, comp);
|
|
111
|
+
const working = readJsonIfExists(join(componentDir, '_working.json'));
|
|
112
|
+
const embedded = active?.theme?.componentConfigs?.[comp];
|
|
113
|
+
const config = working ?? embedded ?? readJsonIfExists(join(componentDir, 'default.json'));
|
|
114
|
+
if (!config) {
|
|
115
|
+
throw new Error(`component "${comp}": default.json is missing`);
|
|
116
|
+
}
|
|
117
|
+
configs[comp] = { ...config, component: comp };
|
|
118
|
+
sources[comp] = working ? 'working' : embedded ? 'theme' : 'default';
|
|
119
|
+
}
|
|
120
|
+
return { configs, sources };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** `engine` is a test seam; the CLI always runs the compiled bundle. */
|
|
124
|
+
export async function runAdjust({
|
|
125
|
+
opsPath,
|
|
126
|
+
dryRun = false,
|
|
127
|
+
root = process.cwd(),
|
|
128
|
+
componentConfigsDir,
|
|
129
|
+
themesDir,
|
|
130
|
+
engine,
|
|
131
|
+
} = {}) {
|
|
132
|
+
const { adjustAliases, matchesKind, resolveDataDirs } = engine ?? (await loadEngine());
|
|
133
|
+
|
|
134
|
+
const opsFull = resolve(root, opsPath);
|
|
135
|
+
if (!existsSync(opsFull)) {
|
|
136
|
+
throw new Error(`ops file not found at ${relative(root, opsFull)}`);
|
|
137
|
+
}
|
|
138
|
+
let doc;
|
|
139
|
+
try {
|
|
140
|
+
doc = readJson(opsFull);
|
|
141
|
+
} catch (err) {
|
|
142
|
+
throw new Error(`ops file is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const ops = doc?.ops;
|
|
146
|
+
if (!Array.isArray(ops) || ops.length === 0) {
|
|
147
|
+
throw new Error('ops file needs a non-empty "ops" array');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const resolved = componentConfigsDir && themesDir ? null : resolveDataDirs();
|
|
151
|
+
const dir = componentConfigsDir ?? resolved.componentConfigsDir;
|
|
152
|
+
if (!existsSync(dir)) {
|
|
153
|
+
throw new Error(`no component configs at ${relative(root, dir)}. Run the dev server once to create them.`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const active = readActiveTheme(themesDir ?? resolved?.themesDir);
|
|
157
|
+
const { configs, sources } = readLiveConfigs(dir, active);
|
|
158
|
+
const now = new Date().toISOString();
|
|
159
|
+
const { configs: next, report } = adjustAliases(configs, ops, now);
|
|
160
|
+
|
|
161
|
+
const components = [];
|
|
162
|
+
for (const entry of report.components) {
|
|
163
|
+
const { component, skips } = entry;
|
|
164
|
+
const changes = collapseChanges(entry.changes);
|
|
165
|
+
|
|
166
|
+
if (changes.length > 0 && !dryRun) {
|
|
167
|
+
writeFileSync(
|
|
168
|
+
join(dir, component, '_working.json'),
|
|
169
|
+
JSON.stringify({ ...next[component], component }, null, 2),
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
components.push({
|
|
174
|
+
component,
|
|
175
|
+
source: sources[component],
|
|
176
|
+
changes: changes.map((c) => ({ ...c, snapped: opposesShift(ops, matchesKind, component, c) })),
|
|
177
|
+
skips,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
components.sort((a, b) => a.component.localeCompare(b.component));
|
|
181
|
+
|
|
182
|
+
const changed = components.filter((c) => c.changes.length > 0);
|
|
183
|
+
return {
|
|
184
|
+
configsDir: dir,
|
|
185
|
+
openTheme: active?.slug ?? null,
|
|
186
|
+
// The ops file's `name` used to pick a file name. Buffers are fixed slots,
|
|
187
|
+
// so it now names nothing; say so rather than drop it in silence.
|
|
188
|
+
ignoredName: doc.name === undefined ? null : String(doc.name),
|
|
189
|
+
dryRun,
|
|
190
|
+
buffered: !dryRun && changed.length > 0,
|
|
191
|
+
components,
|
|
192
|
+
totals: {
|
|
193
|
+
components: changed.length,
|
|
194
|
+
aliases: changed.reduce((n, c) => n + c.changes.length, 0),
|
|
195
|
+
skips: components.reduce((n, c) => n + c.skips.length, 0),
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function formatAdjustResult(result) {
|
|
201
|
+
const root = process.cwd();
|
|
202
|
+
const lines = [];
|
|
203
|
+
const { components: changedCount, aliases, skips } = result.totals;
|
|
204
|
+
|
|
205
|
+
if (changedCount === 0) {
|
|
206
|
+
lines.push(
|
|
207
|
+
skips > 0
|
|
208
|
+
? `Nothing changed: every matching alias was skipped (reasons below).`
|
|
209
|
+
: `Nothing to change: no alias matched the ops.`,
|
|
210
|
+
);
|
|
211
|
+
} else {
|
|
212
|
+
const verb = result.dryRun ? 'Would edit' : 'Edited';
|
|
213
|
+
lines.push(`${verb} the open buffer of ${changedCount} component(s).`);
|
|
214
|
+
}
|
|
215
|
+
if (result.ignoredName !== null) {
|
|
216
|
+
lines.push(
|
|
217
|
+
`Ignored "name": "${result.ignoredName}". adjust edits the open buffer now, ` +
|
|
218
|
+
`so it writes no file of its own.`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
for (const entry of result.components) {
|
|
223
|
+
const from =
|
|
224
|
+
entry.source === 'theme' && result.openTheme
|
|
225
|
+
? `theme "${result.openTheme}"`
|
|
226
|
+
: SOURCE_LABELS[entry.source];
|
|
227
|
+
lines.push(`\n${entry.component} (from: ${from})`);
|
|
228
|
+
|
|
229
|
+
const width = Math.max(0, ...entry.changes.map((c) => c.variable.length));
|
|
230
|
+
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}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
for (const [reason, label] of SKIP_LABELS) {
|
|
236
|
+
const hit = entry.skips.filter((s) => s.reason === reason);
|
|
237
|
+
if (hit.length === 0) continue;
|
|
238
|
+
lines.push(` skipped, ${label}: ${hit.map((s) => s.variable).join(', ')}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
lines.push(
|
|
243
|
+
`\n${changedCount} component(s) changed, ${aliases} alias(es), ${skips} skipped.`,
|
|
244
|
+
);
|
|
245
|
+
if (result.buffered) {
|
|
246
|
+
lines.push(
|
|
247
|
+
`Reload the app to see it. This is an unsaved edit: save the open theme in the ` +
|
|
248
|
+
`editor's Theme panel to keep it, or load a theme to discard it.`,
|
|
249
|
+
);
|
|
250
|
+
} else if (result.dryRun) {
|
|
251
|
+
lines.push(`Dry run: nothing written under ${relative(root, result.configsDir)}.`);
|
|
252
|
+
}
|
|
253
|
+
return lines.join('\n');
|
|
254
|
+
}
|
package/bin/cli.mjs
CHANGED
|
@@ -4,15 +4,25 @@
|
|
|
4
4
|
// create <dir> Scaffold a new app that depends on this package.
|
|
5
5
|
// setup-claude [--force] Copy bundled Claude Code skills into ./.claude/skills/.
|
|
6
6
|
// check-component <id> Validate a component against the add-component skill contract.
|
|
7
|
+
// generate-theme <brief> Build a theme from a 10-seed OKLCH brief and open it.
|
|
8
|
+
// adjust <ops.json> Apply radius/padding/gap/border-width ops to the open buffer.
|
|
9
|
+
// migrate [...] Reconcile tokens.css, the data tree, and route references.
|
|
7
10
|
|
|
8
11
|
import { cpSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
|
|
9
12
|
import { dirname, join, resolve } from 'node:path';
|
|
10
13
|
import { fileURLToPath } from 'node:url';
|
|
11
14
|
import process from 'node:process';
|
|
12
15
|
import { checkComponent, formatReport } from './check-component.mjs';
|
|
13
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
runMigrate,
|
|
18
|
+
formatMigrateResult,
|
|
19
|
+
runMigrateData,
|
|
20
|
+
formatMigrateDataResult,
|
|
21
|
+
} from './migrate.mjs';
|
|
14
22
|
import { runMigrateRoutes, formatRouteResult } from './migrate-routes.mjs';
|
|
15
23
|
import { runCreate, formatCreateResult } from './create.mjs';
|
|
24
|
+
import { runGenerateTheme, formatGenerateThemeResult } from './generate-theme.mjs';
|
|
25
|
+
import { runAdjust, formatAdjustResult } from './adjust.mjs';
|
|
16
26
|
|
|
17
27
|
const USAGE = `Usage: npx @motion-proto/live-tokens <command> [options]
|
|
18
28
|
|
|
@@ -22,15 +32,37 @@ Commands:
|
|
|
22
32
|
setup-claude [--force] Install bundled Claude Code skills into ./.claude/skills/
|
|
23
33
|
check-component <id> Validate <id>'s runtime, editor, and registration
|
|
24
34
|
against the live-tokens-create-component contract
|
|
35
|
+
generate-theme <brief.json> [--no-activate] [--dry-run] [--carry-from <name>]
|
|
36
|
+
Build a full theme from a 10-seed OKLCH brief
|
|
37
|
+
(see the live-tokens-generate-theme skill),
|
|
38
|
+
enforce AA contrast on derived text tokens, write
|
|
39
|
+
themes/<slug>.json, and open it in the editor.
|
|
40
|
+
Opening never changes what your site ships; Adopt
|
|
41
|
+
in the editor does that.
|
|
42
|
+
--no-activate writes the theme without opening it;
|
|
43
|
+
--dry-run prints the contrast report without
|
|
44
|
+
writing. Non-color content (gradients, fonts,
|
|
45
|
+
component aliases) carries forward from the live
|
|
46
|
+
look, or from theme <name> with --carry-from.
|
|
47
|
+
adjust <ops.json> [--dry-run]
|
|
48
|
+
Move radius, padding, gap, and border-width
|
|
49
|
+
aliases along their token scales (see the
|
|
50
|
+
live-tokens-adjust-shape-space skill). Reads each
|
|
51
|
+
component's live config and writes the result to
|
|
52
|
+
that component's unsaved buffer, so save the open
|
|
53
|
+
theme in the editor to keep it. --dry-run prints
|
|
54
|
+
the report without writing.
|
|
25
55
|
migrate [--check] [--write] [--tokens <path>]
|
|
26
56
|
Reconcile your project with the installed package:
|
|
27
|
-
applies additive tokens.css migrations
|
|
28
|
-
|
|
57
|
+
applies additive tokens.css migrations, moves a
|
|
58
|
+
pre-0.48 data tree onto the current directory
|
|
59
|
+
names, heals what the retired pointer files named,
|
|
60
|
+
and reports source references to the
|
|
29
61
|
editor/components/docs routes that moved to
|
|
30
62
|
/live-tokens/* in 0.35.0. --write also rewrites the
|
|
31
63
|
unambiguous route references (never /docs). --check
|
|
32
|
-
|
|
33
|
-
|
|
64
|
+
prints both plans without writing (exit 1 when
|
|
65
|
+
either is pending; route findings are advisory).
|
|
34
66
|
`;
|
|
35
67
|
|
|
36
68
|
function fail(message, code = 1) {
|
|
@@ -71,6 +103,51 @@ if (command === 'check-component') {
|
|
|
71
103
|
process.exit(result.errors.length === 0 ? 0 : 1);
|
|
72
104
|
}
|
|
73
105
|
|
|
106
|
+
if (command === 'generate-theme') {
|
|
107
|
+
const briefPath = rest.find((a) => !a.startsWith('-'));
|
|
108
|
+
if (!briefPath) {
|
|
109
|
+
fail(`Usage: npx @motion-proto/live-tokens generate-theme <brief.json> [--no-activate] [--dry-run]`);
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const carryIdx = rest.indexOf('--carry-from');
|
|
113
|
+
const carryFrom = carryIdx !== -1 ? rest[carryIdx + 1] : undefined;
|
|
114
|
+
if (carryIdx !== -1 && !carryFrom) fail(`--carry-from requires a theme name`);
|
|
115
|
+
const result = await runGenerateTheme({
|
|
116
|
+
briefPath,
|
|
117
|
+
activate: !rest.includes('--no-activate'),
|
|
118
|
+
dryRun: rest.includes('--dry-run'),
|
|
119
|
+
carryFrom,
|
|
120
|
+
});
|
|
121
|
+
console.log(formatGenerateThemeResult(result));
|
|
122
|
+
process.exit(result.report.failures.length === 0 ? 0 : 1);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
fail(`generate-theme failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (command === 'adjust') {
|
|
129
|
+
const opsPath = rest.find((a) => !a.startsWith('-'));
|
|
130
|
+
if (!opsPath) {
|
|
131
|
+
fail(`Usage: npx @motion-proto/live-tokens adjust <ops.json> [--dry-run]`);
|
|
132
|
+
}
|
|
133
|
+
if (rest.includes('--no-activate')) {
|
|
134
|
+
fail(
|
|
135
|
+
`adjust has no --no-activate: it edits the open buffer, which is what the page already runs. ` +
|
|
136
|
+
`Drop the flag and re-run.`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const result = await runAdjust({
|
|
141
|
+
opsPath,
|
|
142
|
+
dryRun: rest.includes('--dry-run'),
|
|
143
|
+
});
|
|
144
|
+
console.log(formatAdjustResult(result));
|
|
145
|
+
process.exit(0);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
fail(`adjust failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
74
151
|
if (command === 'migrate') {
|
|
75
152
|
const check = rest.includes('--check');
|
|
76
153
|
const write = rest.includes('--write');
|
|
@@ -81,15 +158,23 @@ if (command === 'migrate') {
|
|
|
81
158
|
const result = await runMigrate({ tokensArg, check });
|
|
82
159
|
console.log(formatMigrateResult(result, { check }));
|
|
83
160
|
|
|
161
|
+
// Data-tree pass: retires the pre-working-set pointer files and the copies
|
|
162
|
+
// they named. Runs on every migrate, --write included, because leaving a
|
|
163
|
+
// tree half on each model is what the heal exists to end.
|
|
164
|
+
const data = await runMigrateData({ check });
|
|
165
|
+
const dataOut = formatMigrateDataResult(data);
|
|
166
|
+
if (dataOut) console.log('\n' + dataOut);
|
|
167
|
+
|
|
84
168
|
// Route-reference pass: advisory by default, rewrites the unambiguous hits
|
|
85
169
|
// only with --write (and never under --check).
|
|
86
170
|
const routes = runMigrateRoutes({ root: process.cwd(), apply: write && !check });
|
|
87
171
|
const routeOut = formatRouteResult(routes, { check });
|
|
88
172
|
if (routeOut) console.log('\n' + routeOut);
|
|
89
173
|
|
|
90
|
-
// Route findings are advisory;
|
|
174
|
+
// Route findings are advisory; token migrations and the data heal gate the
|
|
175
|
+
// exit code.
|
|
91
176
|
if (result.status === 'no-path') process.exit(1);
|
|
92
|
-
if (check && result.status === 'would-change') process.exit(1);
|
|
177
|
+
if (check && (result.status === 'would-change' || data.status === 'planned')) process.exit(1);
|
|
93
178
|
process.exit(0);
|
|
94
179
|
} catch (err) {
|
|
95
180
|
fail(`migrate failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -144,6 +229,8 @@ const SAMPLE_PROMPTS = {
|
|
|
144
229
|
'live-tokens-build-page': 'build a pricing page using live-tokens components',
|
|
145
230
|
'live-tokens-pick-component': "what's the difference between TabBar and SegmentedControl?",
|
|
146
231
|
'live-tokens-create-component': 'author a new Toggle component for my live-tokens project',
|
|
232
|
+
'live-tokens-generate-theme': 'make me a bright and cheerful color theme',
|
|
233
|
+
'live-tokens-adjust-shape-space': 'make the buttons pill shaped',
|
|
147
234
|
};
|
|
148
235
|
|
|
149
236
|
const installedSamples = skills
|