@motion-proto/live-tokens 0.71.0 → 0.72.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-check-compliance/SKILL.md +7 -1
- package/.claude/skills/live-tokens-create-component/SKILL.md +2 -2
- package/.claude/skills/live-tokens-create-component/references/contract-tests.md +99 -0
- package/.claude/skills/live-tokens-create-component/references/sketch-mode.md +1 -2
- package/.claude/skills/live-tokens-create-component/references/token-naming.md +6 -0
- package/CHANGELOG.md +77 -0
- package/README.md +1 -1
- package/bin/check-component.mjs +2 -11
- package/bin/check-page.mjs +3 -2
- package/bin/cli.mjs +2 -1
- package/bin/lib/catalogue.mjs +6 -2
- package/bin/lib/findings.mjs +17 -0
- package/bin/lib/report.mjs +1 -1
- package/bin/lib/tokenVocabulary.mjs +20 -0
- package/dist-plugin/adjust/index.cjs +33 -6
- package/dist-plugin/adjust/index.d.cts +2 -2
- package/dist-plugin/adjust/index.d.ts +2 -2
- package/dist-plugin/adjust/index.js +7 -6
- package/dist-plugin/{chunk-2YNERPXY.js → chunk-RIXO2E55.js} +1 -1
- package/dist-plugin/{chunk-RVE3MNKM.js → chunk-W6Y4BWFB.js} +3 -1
- package/dist-plugin/{chunk-GPIBU44G.js → chunk-YLCOIGQC.js} +25 -1
- package/dist-plugin/{dataPaths-bJTCEO4H.d.ts → dataPaths-BhWzd5cL.d.cts} +4 -0
- package/dist-plugin/{dataPaths-bJTCEO4H.d.cts → dataPaths-BhWzd5cL.d.ts} +4 -0
- package/dist-plugin/fontPairing/index.cjs +3 -1
- package/dist-plugin/fontPairing/index.d.cts +1 -1
- package/dist-plugin/fontPairing/index.d.ts +1 -1
- package/dist-plugin/fontPairing/index.js +1 -1
- package/dist-plugin/generateColorsAndType/index.cjs +28 -2
- package/dist-plugin/generateColorsAndType/index.d.cts +1 -1
- package/dist-plugin/generateColorsAndType/index.d.ts +1 -1
- package/dist-plugin/generateColorsAndType/index.js +2 -2
- package/dist-plugin/index.cjs +28 -2
- package/dist-plugin/index.d.cts +1 -1
- package/dist-plugin/index.d.ts +1 -1
- package/dist-plugin/index.js +3 -3
- package/dist-plugin/migrateData/index.cjs +28 -2
- package/dist-plugin/migrateData/index.d.cts +1 -1
- package/dist-plugin/migrateData/index.d.ts +1 -1
- package/dist-plugin/migrateData/index.js +3 -3
- package/dist-plugin/tokensCssMigrations/index.cjs +3 -1
- package/dist-plugin/tokensCssMigrations/index.d.cts +1 -1
- package/dist-plugin/tokensCssMigrations/index.d.ts +1 -1
- package/dist-plugin/tokensCssMigrations/index.js +1 -1
- package/package.json +5 -1
- package/src/editor/component-editor/SectionDividerEditor.svelte +1 -11
- package/src/editor/component-editor/contract.ts +175 -0
- package/src/editor/component-editor/scaffolding/StateBlock.svelte +0 -8
- package/src/editor/component-editor/scaffolding/TokenLayout.svelte +5 -0
- package/src/editor/component-editor/scaffolding/TypeEditor.svelte +0 -21
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +2 -2
- package/src/editor/component-editor/scaffolding/types.ts +1 -7
- package/src/editor/core/components/aliasKinds.ts +6 -2
- package/src/editor/core/themes/migrations/2026-09-02-sectiondivider-drop-title-outline.ts +39 -0
- package/src/editor/core/themes/migrations/index.ts +2 -0
- package/src/live-tokens/data/themes/autumn.json +1 -7
- package/src/live-tokens/data/themes/halloween.json +1 -7
- package/src/live-tokens/data/themes/midnight-study.json +1 -7
- package/src/live-tokens/data/themes/ocean.json +1 -7
- package/src/live-tokens/data/themes/royal-velvet.json +1 -7
- package/src/live-tokens/data/themes/sketchy.json +1 -7
- package/src/live-tokens/data/themes/spring-meadow.json +1 -7
- package/src/live-tokens/data/themes/sunset.json +1 -7
- package/src/system/components/SectionDivider.svelte +3 -143
- package/src/system/components/FloatingTokenTags.css +0 -284
- package/src/system/components/FloatingTokenTags.svelte +0 -570
|
@@ -52,7 +52,13 @@ in a line what the rule holds and which of two kinds the fix is:
|
|
|
52
52
|
Where a finding looks deliberate, a translucent overlay on an app shell or a
|
|
53
53
|
layout size the project owns, say so and name the config entry that would
|
|
54
54
|
record the decision: `"checks": { "rules": { "<rule>": "warn" } }` in
|
|
55
|
-
`live-tokens.config.json`.
|
|
55
|
+
`live-tokens.config.json`. Where a whole file is not a themed surface at all,
|
|
56
|
+
hand-tuned artwork or vendored CSS, the entry is
|
|
57
|
+
`"checks": { "exclude": ["src/art/hero.css"] }` — a project-relative path, a
|
|
58
|
+
directory covering what is under it, and naming the file on the command line
|
|
59
|
+
still checks it. Prefer the narrower one: an exclusion drops one file, a
|
|
60
|
+
severity change drops a rule everywhere. Recording either is the user's call,
|
|
61
|
+
not yours.
|
|
56
62
|
|
|
57
63
|
## Report
|
|
58
64
|
|
|
@@ -230,9 +230,9 @@ It *warns* (non-fatal) when a token-backed default still carries a px or rem ter
|
|
|
230
230
|
|
|
231
231
|
Exit code 0 means the static contract is met. Resolve warnings before shipping, or run with `--strict` to make them fail. `--json` prints findings with a stable `rule` id, so you can work through one rule at a time and re-run.
|
|
232
232
|
|
|
233
|
-
**Then run the registry contract test.**
|
|
233
|
+
**Then run the registry contract test.** `checkRegistryEntry`, from `@motion-proto/live-tokens/component-editor/contract`, takes one registry entry and returns a violation line per failure, so a suite over your own components is a `describe.each` and one call. Per component it verifies that the registration resolves to a real `sourceFile` and a non-empty schema, that schema variables are unique, that every editable token is declared in the runtime `<style>` block and seeded in `component-configs/<id>/default.json`, that a token declaring `minOpacity` seeds at or above its floor, and that `setComponentAlias` round-trips the alias through the slice. The test file, the two lines in it that are load-bearing, and the path options are in `references/contract-tests.md`.
|
|
234
234
|
|
|
235
|
-
|
|
235
|
+
Inside the package, `src/editor/component-editor/registryContract.test.ts` is that same file over `builtInRegistry`, so a new first-party component is auto-covered the moment it lands there — `npm test` fails if any check misses.
|
|
236
236
|
|
|
237
237
|
**If your component declares `intrinsics`, the intrinsics contract test covers it too.** `src/editor/component-editor/intrinsicsContract.test.ts` iterates every entry with an `intrinsics` array and asserts, per (intrinsic, variant), that the runtime `:global(:root)` declares a default, the default is one of the spec's `values`, and the editor's `default` equals the runtime default. This is what would have caught a getter defaulting to `center` while `:global(:root)` says `start`. Same auto-coverage rule: declare `intrinsics` on the registry entry and the test picks it up.
|
|
238
238
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# The registry contract as a test in your own project
|
|
2
|
+
|
|
3
|
+
`checkRegistryEntry` is the contract the package holds its own 26 components
|
|
4
|
+
to, exported so a project outside the package can run it over its own. It takes
|
|
5
|
+
one registry entry and returns a violation line per failure; an empty array is
|
|
6
|
+
the pass.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
// tests/registryContract.test.ts
|
|
10
|
+
// @vitest-environment happy-dom
|
|
11
|
+
import { describe, it, expect } from 'vitest';
|
|
12
|
+
import { getComponentRegistryEntries, registerComponent } from '@motion-proto/live-tokens';
|
|
13
|
+
import { checkRegistryEntry } from '@motion-proto/live-tokens/component-editor/contract';
|
|
14
|
+
import MyWidgetEditor, { allTokens } from '../src/system/components/MyWidgetEditor.svelte';
|
|
15
|
+
|
|
16
|
+
registerComponent({
|
|
17
|
+
id: 'mywidget',
|
|
18
|
+
label: 'My Widget',
|
|
19
|
+
icon: 'fas fa-magic',
|
|
20
|
+
sourceFile: 'src/system/components/MyWidget.svelte',
|
|
21
|
+
editorComponent: MyWidgetEditor,
|
|
22
|
+
schema: allTokens,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const mine = getComponentRegistryEntries().filter((e) => e.origin === 'custom');
|
|
26
|
+
|
|
27
|
+
describe.each(mine.map((e) => [e.id, e] as const))('%s', (_id, entry) => {
|
|
28
|
+
it('meets the registry contract', () => {
|
|
29
|
+
expect(checkRegistryEntry(entry)).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Two lines there are load-bearing.
|
|
35
|
+
|
|
36
|
+
- **Register at the top of the test file**, rather than importing `main.ts`.
|
|
37
|
+
The entries have to exist before `describe.each` reads them, and
|
|
38
|
+
`bootLiveTokens` would mount the app.
|
|
39
|
+
- **Filter on `origin`.** The registry always carries the shipped components
|
|
40
|
+
too, and their `sourceFile` paths are relative to the package root, not
|
|
41
|
+
yours. Without the filter every built-in fails on a path that does not exist
|
|
42
|
+
in your project.
|
|
43
|
+
|
|
44
|
+
## Setup
|
|
45
|
+
|
|
46
|
+
`vitest` and `happy-dom` as devDependencies, and the svelte plugin already in
|
|
47
|
+
`vite.config.ts` so the editor `.svelte` import resolves. The helper reads the
|
|
48
|
+
runtime file and `default.json` off disk, which is why it is node-only and has
|
|
49
|
+
its own subpath.
|
|
50
|
+
|
|
51
|
+
The package ships Svelte and TypeScript source, and `bootLiveTokens` imports the
|
|
52
|
+
FontAwesome stylesheet. Left external, Node meets that `.css` and stops with
|
|
53
|
+
`Unknown file extension ".css"`, before a single test runs. Inline both so Vite
|
|
54
|
+
transforms them:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// vitest.config.ts
|
|
58
|
+
import { defineConfig, mergeConfig } from 'vitest/config';
|
|
59
|
+
import viteConfig from './vite.config';
|
|
60
|
+
|
|
61
|
+
export default mergeConfig(
|
|
62
|
+
viteConfig,
|
|
63
|
+
defineConfig({
|
|
64
|
+
test: {
|
|
65
|
+
server: { deps: { inline: [/@motion-proto\/live-tokens/, /@fortawesome/] } },
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Paths
|
|
72
|
+
|
|
73
|
+
Paths resolve against `process.cwd()` and `src/live-tokens/data/component-configs`.
|
|
74
|
+
A project that moved either passes them:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
checkRegistryEntry(entry, { projectRoot, componentConfigsDir });
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`componentConfigsDir` is the same directory `live-tokens.config.json` names.
|
|
81
|
+
|
|
82
|
+
## What it holds
|
|
83
|
+
|
|
84
|
+
1. **Registration** — `sourceFile` resolves to a real file, the schema is non-empty.
|
|
85
|
+
2. **Uniqueness** — no schema variable is declared twice.
|
|
86
|
+
3. **Editor to runtime** — every editable token's CSS var is declared in the
|
|
87
|
+
runtime's `<style>` block, so an edit has something to repaint.
|
|
88
|
+
4. **Editor to default config** — every editable token has a seed alias in
|
|
89
|
+
`component-configs/<id>/default.json`, so the component adopts with full
|
|
90
|
+
defaults. A component with no `default.json` is editor-only; this check and
|
|
91
|
+
the next one skip it.
|
|
92
|
+
5. **Opacity floors** — a token declaring `minOpacity` ships a default at or
|
|
93
|
+
above it, so a floating panel starts out legible over page content.
|
|
94
|
+
6. **Round-trip** — `setComponentAlias` persists into the slice under the same key.
|
|
95
|
+
|
|
96
|
+
Checks 3 and 4 exclude `hidden: true` tokens, `kind: 'gradient'` tokens (stored
|
|
97
|
+
as gradient objects, not vars), and `-padding-(top|right|bottom|left)` suffixes
|
|
98
|
+
(written on demand by the split-padding UI and read through the `themed-padding`
|
|
99
|
+
mixin's fallback chain, so they exist as neither `:root` declarations nor seeds).
|
|
@@ -176,8 +176,7 @@ inherits, so one declaration covers everything under it:
|
|
|
176
176
|
|
|
177
177
|
Travel is stated in px against a glyph whose size the layer cannot know, so the
|
|
178
178
|
dial that suits a card's worth of artwork tears a 16px icon apart. Reach for the
|
|
179
|
-
soft bank before reaching for `none`.
|
|
180
|
-
example.
|
|
179
|
+
soft bank before reaching for `none`.
|
|
181
180
|
|
|
182
181
|
## First-party components
|
|
183
182
|
|
|
@@ -64,6 +64,12 @@ to that list, so the two cannot drift apart.
|
|
|
64
64
|
| `-padding` | Internal spacing |
|
|
65
65
|
| `-gap` | Spacing between sibling elements |
|
|
66
66
|
|
|
67
|
+
`-width`, `-height` and `-size` are the fall-through: any dimension with no
|
|
68
|
+
more specific name behind it. They read the `--space-*` scale through the same
|
|
69
|
+
picker `-gap` uses, and they match last, so `-border-width`, `-divider-height`,
|
|
70
|
+
`-icon-size` and the rest claim their token first. Reach for the specific name
|
|
71
|
+
when one fits; a stroke is `-border-width` even where the CSS says `outline:`.
|
|
72
|
+
|
|
67
73
|
## Typography
|
|
68
74
|
|
|
69
75
|
| Suffix | Meaning |
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,82 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.72.0 — The contract a consumer can run
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **The registry contract is a function a consumer can run.** The six
|
|
8
|
+
per-component checks that hold this package's own components — registration
|
|
9
|
+
resolves, schema variables are unique, every editable token is declared in
|
|
10
|
+
the runtime `<style>` and seeded in `default.json`, a declared `minOpacity`
|
|
11
|
+
is honoured, `setComponentAlias` round-trips — are now `checkRegistryEntry`,
|
|
12
|
+
exported from `@motion-proto/live-tokens/component-editor/contract`. It takes
|
|
13
|
+
one registry entry and returns a violation line per failure, so a project
|
|
14
|
+
outside this package holds its own components to the same contract with a
|
|
15
|
+
`describe.each` and one call. Paths resolve against `process.cwd()` and
|
|
16
|
+
`src/live-tokens/data/component-configs`, or the `projectRoot` and
|
|
17
|
+
`componentConfigsDir` passed in. Node-only, hence its own subpath.
|
|
18
|
+
`live-tokens-create-component` carries the test file in
|
|
19
|
+
`references/contract-tests.md`; filtering the registry on
|
|
20
|
+
`origin === 'custom'` is the line that matters, since the shipped entries
|
|
21
|
+
name paths relative to the package root.
|
|
22
|
+
|
|
23
|
+
- **`"checks": { "exclude": [...] }` in `live-tokens.config.json`.** Paths the
|
|
24
|
+
checkers skip when they discover their own targets: a project-relative path,
|
|
25
|
+
a directory covering what is under it. For a file that is not a themed
|
|
26
|
+
surface at all — hand-tuned artwork, vendored CSS — where the only other way
|
|
27
|
+
out was downgrading a rule for the whole project. Naming the file on the
|
|
28
|
+
command line still checks it, so the escape hatch cannot hide a file from
|
|
29
|
+
someone looking straight at it. The dev plugin now also recognises `checks`
|
|
30
|
+
and `componentDirs`, which the CLI has read for some time while the plugin
|
|
31
|
+
warned they were unknown keys.
|
|
32
|
+
|
|
33
|
+
### Changed (breaking)
|
|
34
|
+
|
|
35
|
+
- **`FloatingTokenTags` is demo artwork, not a shipped component.** It moved to
|
|
36
|
+
`src/demo/`, so it leaves the published package, the component query and the
|
|
37
|
+
count, which now agree with the registry at 26. **Breaking for anyone
|
|
38
|
+
importing `@motion-proto/live-tokens/components/FloatingTokenTags.svelte`;**
|
|
39
|
+
it was only ever the hero animation of the demo, and it never had a
|
|
40
|
+
`:global(:root)` block, an editor, or a registry entry.
|
|
41
|
+
|
|
42
|
+
- **The SectionDivider title outline is removed.** It was the only reason the title was an
|
|
43
|
+
SVG `<text>` behind a `feMorphology` filter rather than an element, and that
|
|
44
|
+
SVG carried a `getBBox()` viewBox, a per-instance `MutationObserver` on the
|
|
45
|
+
document's inline style (filter primitives cannot read a CSS var, so the
|
|
46
|
+
resolved width and colour had to be read back and pushed onto them by hand),
|
|
47
|
+
font-load listeners to re-measure, and a title that was neither selectable nor
|
|
48
|
+
findable. It shipped transparent in the default and in all eight presets, and
|
|
49
|
+
the trap-out it was built for is done by the layout: the `through-label`
|
|
50
|
+
hairlines flank the title in a flex row, so no rule ever runs behind the
|
|
51
|
+
glyphs. The title is now a span that inherits typography like every other
|
|
52
|
+
component's. `--sectiondivider-{lg,md,sm}-title-outline-width` and
|
|
53
|
+
`-title-outline-color` are dropped by a component-config migration; the
|
|
54
|
+
`outlineWidthVariable` / `outlineColorVariable` rows on `TypeGroupConfig` go
|
|
55
|
+
with them, since nothing else declared one.
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- **`registered` is read from the registry for shipped components too.** The
|
|
60
|
+
query assumed a component under `src/system/components` was registered, so
|
|
61
|
+
`live-tokens components` could name one the editor cannot open and
|
|
62
|
+
pick-component does not list. It now reads the package's own
|
|
63
|
+
`builtInRegistry`, the same parse `check-component` uses, which the two now
|
|
64
|
+
share. A component that is discovered but registered nowhere reports
|
|
65
|
+
`registered: false`, prints `(NOT registered)`, and is counted out of the
|
|
66
|
+
catalogue in the summary line.
|
|
67
|
+
|
|
68
|
+
### Fixed
|
|
69
|
+
|
|
70
|
+
- **A bare `-width`, `-height` or `-size` no longer renders a colour picker.**
|
|
71
|
+
The three sat in `KIND_RULES` under `surface`, so `--widget-panel-width` drew
|
|
72
|
+
a palette while the naming vocabulary documented it as geometry. They are now
|
|
73
|
+
a `length` kind, matched last among the geometry rules so `-border-width`,
|
|
74
|
+
`-divider-height`, `-icon-size` and the rest still claim their token first,
|
|
75
|
+
and drawn with the `--space-*` picker `-gap` uses. The suffix vocabulary is
|
|
76
|
+
unchanged, so no token is renamed and no project has to migrate. `adjust`
|
|
77
|
+
does not take the kind: its ladders are density and shape, and a panel width
|
|
78
|
+
is neither.
|
|
79
|
+
|
|
3
80
|
## 0.71.0 — Check this project
|
|
4
81
|
|
|
5
82
|
### Added
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ The editor is dev-only. Production builds get plain CSS variables and the compon
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
13
|
- **Live token editing.** Colors, typography, spacing, radii, shadows, motion, palettes, and gradients. Every input writes a CSS variable, so the page repaints with no reload and no build step.
|
|
14
|
-
- **Live component editing.** 26 shipped Svelte components (Button, IconButton, Input, Card, Dialog, Badge, Callout, Table, Tooltip, Toggle, TabBar, SegmentedControl, RadioButton, MenuSelect, ProgressBar, CornerBadge, SectionDivider, CollapsibleSection, Notification, Image, ImageLightbox, CodeSnippet, SideNavigation, Panel, InlineEditActions) declare their design-token aliases in a `:global(:root)` block. Rewire an alias from the component's editor and it updates everywhere that component is used, on your real pages.
|
|
14
|
+
- **Live component editing.** 26 shipped Svelte components (Button, IconButton, Input, Card, Dialog, Badge, Callout, Table, Tooltip, Toggle, TabBar, SegmentedControl, RadioButton, MenuSelect, ProgressBar, CornerBadge, SectionDivider, CollapsibleSection, Notification, Image, ImageLightbox, CodeSnippet, SideNavigation, Panel, InlineEditActions, Slider) declare their design-token aliases in a `:global(:root)` block. Rewire an alias from the component's editor and it updates everywhere that component is used, on your real pages.
|
|
15
15
|
- **Four dev-only routes.** `/live-tokens/editor` for tokens, `/live-tokens/colors` for palettes, `/live-tokens/components` for per-component aliases, `/live-tokens/docs` for the user guide.
|
|
16
16
|
- **Editor overlay.** Pins to the top right of every dev page and opens the editor in a side panel or floating window, so you edit on the page you are styling. Its "Page Source" button opens the current page's `.svelte` file in VS Code.
|
|
17
17
|
- **Themes.** A theme is a whole look in one file: colors and type plus a config for every component, stored by value. Loading one changes a single pointer file, and nothing your site ships changes until you Adopt. Export a theme and import it into another project to restore the look in one step.
|
package/bin/check-component.mjs
CHANGED
|
@@ -23,7 +23,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
|
23
23
|
import { extname, join, relative } from 'node:path';
|
|
24
24
|
import { hasColorLiteral, hasDimensionLiteral, stripVarFallbacks } from './lib/cssValues.mjs';
|
|
25
25
|
import { lineOf } from './lib/findings.mjs';
|
|
26
|
-
import { PKG_ROOT, declaredCustomProperties, extractGlobalRootBlocks, isContractToken, loadVocabulary } from './lib/tokenVocabulary.mjs';
|
|
26
|
+
import { PKG_ROOT, builtInIds, declaredCustomProperties, extractGlobalRootBlocks, isContractToken, loadVocabulary } from './lib/tokenVocabulary.mjs';
|
|
27
27
|
|
|
28
28
|
export const COMPONENT_RULES = {
|
|
29
29
|
'invalid-id': 'error',
|
|
@@ -71,18 +71,9 @@ function readKnownSuffixes(root) {
|
|
|
71
71
|
return [];
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
const BUILT_IN_REGISTRY = 'src/editor/component-editor/registry.ts';
|
|
75
|
-
|
|
76
74
|
/** True when `id` is one of the package's own components. */
|
|
77
75
|
function isBuiltIn(id) {
|
|
78
|
-
|
|
79
|
-
const path = join(base, BUILT_IN_REGISTRY);
|
|
80
|
-
if (!existsSync(path)) continue;
|
|
81
|
-
const src = readFileSync(path, 'utf8');
|
|
82
|
-
const block = src.match(/builtInRegistry[^=]*=\s*Object\.freeze\(\{([\s\S]*?)\n\}\);/);
|
|
83
|
-
if (block && new RegExp(`\\bid:\\s*'${id}'`).test(block[1])) return true;
|
|
84
|
-
}
|
|
85
|
-
return false;
|
|
76
|
+
return builtInIds(process.cwd(), PKG_ROOT).has(id);
|
|
86
77
|
}
|
|
87
78
|
|
|
88
79
|
// Per-side padding names (`--card-body-padding-top`) are written by the padding
|
package/bin/check-page.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
14
14
|
import { join, relative, resolve, basename } from 'node:path';
|
|
15
|
-
import { lineOf } from './lib/findings.mjs';
|
|
15
|
+
import { isExcluded, lineOf } from './lib/findings.mjs';
|
|
16
16
|
import { blankStrings, hasColorLiteral, hasDimensionLiteral, stripVarFallbacks } from './lib/cssValues.mjs';
|
|
17
17
|
import { isContractToken, loadVocabulary, walk } from './lib/tokenVocabulary.mjs';
|
|
18
18
|
import { resolveTokensCssPath } from './migrate.mjs';
|
|
@@ -382,6 +382,7 @@ export function discoverPages(root) {
|
|
|
382
382
|
return walk(src, ['.svelte', '.css', '.ts', '.js']).filter((f) => {
|
|
383
383
|
const rel = relative(root, f);
|
|
384
384
|
if (NOT_PAGES.some((d) => rel.startsWith(`${d}/`))) return false;
|
|
385
|
+
if (isExcluded(rel, root)) return false;
|
|
385
386
|
if (f === tokensCss || TOKEN_SOURCES.includes(basename(f))) return false;
|
|
386
387
|
if (/\.(test|spec)\.[tj]s$/.test(rel)) return false;
|
|
387
388
|
if (rel.endsWith('.ts') || rel.endsWith('.js')) return /main\.(ts|js)$/.test(rel);
|
|
@@ -396,7 +397,7 @@ export function checkPages(targets, { root = process.cwd(), vocabulary } = {}) {
|
|
|
396
397
|
const full = resolve(root, t);
|
|
397
398
|
if (!existsSync(full)) continue;
|
|
398
399
|
if (statSync(full).isDirectory()) {
|
|
399
|
-
files.push(...walk(full, ['.svelte', '.css']));
|
|
400
|
+
files.push(...walk(full, ['.svelte', '.css']).filter((f) => !isExcluded(relative(root, f), root)));
|
|
400
401
|
} else {
|
|
401
402
|
files.push(full);
|
|
402
403
|
}
|
package/bin/cli.mjs
CHANGED
|
@@ -75,7 +75,8 @@ Both check commands accept:
|
|
|
75
75
|
--strict Treat warnings as errors
|
|
76
76
|
--off=<rule,...> Silence rules; --warn=/--error= change severity
|
|
77
77
|
(or set "checks": { "rules": {...} } in
|
|
78
|
-
live-tokens.config.json
|
|
78
|
+
live-tokens.config.json; "checks": { "exclude":
|
|
79
|
+
[...] } drops paths from discovery entirely)
|
|
79
80
|
generate-theme <brief.json> [--no-activate] [--dry-run] [--carry-from <name>]
|
|
80
81
|
Build a full theme from a 10-seed OKLCH brief
|
|
81
82
|
(see the live-tokens-generate-theme skill),
|
package/bin/lib/catalogue.mjs
CHANGED
|
@@ -44,7 +44,7 @@ export function describeComponents(vocab, { root = process.cwd() } = {}) {
|
|
|
44
44
|
name: entry.name,
|
|
45
45
|
origin: entry.origin,
|
|
46
46
|
file: relative(root, entry.file),
|
|
47
|
-
registered: entry.
|
|
47
|
+
registered: vocab.builtIn.has(entry.id) || vocab.registered.has(entry.id),
|
|
48
48
|
description: descriptionOf(source),
|
|
49
49
|
variants: entry.props?.enums.get('variant') ? [...entry.props.enums.get('variant')] : [],
|
|
50
50
|
props,
|
|
@@ -99,7 +99,11 @@ export function formatComponents(list, { id } = {}) {
|
|
|
99
99
|
if (c.description) lines.push(`${''.padEnd(29)} ${c.description}`);
|
|
100
100
|
}
|
|
101
101
|
lines.push('');
|
|
102
|
-
|
|
102
|
+
const unregistered = list.filter((c) => !c.registered).length;
|
|
103
|
+
lines.push(
|
|
104
|
+
`${list.length} component(s)${unregistered ? `, ${unregistered} not registered and so not in the catalogue` : ''}. ` +
|
|
105
|
+
'`live-tokens components <id>` prints one with its props and tokens.',
|
|
106
|
+
);
|
|
103
107
|
return lines.join('\n');
|
|
104
108
|
}
|
|
105
109
|
|
package/bin/lib/findings.mjs
CHANGED
|
@@ -30,6 +30,23 @@ export function readChecksConfig(root) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* `checks.exclude` in live-tokens.config.json: paths the checkers do not read
|
|
35
|
+
* when they discover their own targets. Each entry is a project-relative path;
|
|
36
|
+
* a directory entry covers everything under it. For a file that is not a themed
|
|
37
|
+
* surface — hand-tuned artwork, vendored CSS — where the alternative is
|
|
38
|
+
* downgrading a rule for the whole project. Naming the file explicitly on the
|
|
39
|
+
* command line still checks it.
|
|
40
|
+
*/
|
|
41
|
+
export function isExcluded(relPath, root) {
|
|
42
|
+
const list = readChecksConfig(root).exclude;
|
|
43
|
+
if (!Array.isArray(list)) return false;
|
|
44
|
+
return list.some((entry) => {
|
|
45
|
+
const e = String(entry).replace(/\/+$/, '');
|
|
46
|
+
return relPath === e || relPath.startsWith(`${e}/`);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
33
50
|
/**
|
|
34
51
|
* Parse `--off=a,b --warn=c --error=d --strict --json` out of argv.
|
|
35
52
|
* Unrecognised flags are returned in `rest` for the caller to handle.
|
package/bin/lib/report.mjs
CHANGED
|
@@ -61,7 +61,7 @@ export function buildReport(vocab, { root = process.cwd() } = {}) {
|
|
|
61
61
|
name: c.name,
|
|
62
62
|
origin: c.origin,
|
|
63
63
|
file: relative(root, c.file),
|
|
64
|
-
registered: c.
|
|
64
|
+
registered: vocab.builtIn.has(c.id) || vocab.registered.has(c.id),
|
|
65
65
|
described: /^\s*<!--[\s\S]*?-->/.test(source),
|
|
66
66
|
tokens: c.tokens.size,
|
|
67
67
|
unread: unreadTokens(source, c.tokens.keys()),
|
|
@@ -164,6 +164,24 @@ function componentFiles(roots) {
|
|
|
164
164
|
return files;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
/** The package's own component ids, read from the frozen registry that declares
|
|
168
|
+
them. A shipped component is registered by the package rather than by the
|
|
169
|
+
project, so it never appears in the project's own `registerComponent` scan. */
|
|
170
|
+
const BUILT_IN_REGISTRY = 'src/editor/component-editor/registry.ts';
|
|
171
|
+
|
|
172
|
+
export function builtInIds(root = process.cwd(), pkgRoot = PKG_ROOT) {
|
|
173
|
+
const ids = new Set();
|
|
174
|
+
for (const base of [pkgRoot, root]) {
|
|
175
|
+
const path = join(base, BUILT_IN_REGISTRY);
|
|
176
|
+
if (!existsSync(path)) continue;
|
|
177
|
+
const block = readFileSync(path, 'utf8')
|
|
178
|
+
.match(/builtInRegistry[^=]*=\s*Object\.freeze\(\{([\s\S]*?)\n\}\);/);
|
|
179
|
+
if (!block) continue;
|
|
180
|
+
for (const m of block[1].matchAll(/\bid:\s*'([a-z][a-z0-9]*)'/g)) ids.add(m[1]);
|
|
181
|
+
}
|
|
182
|
+
return ids;
|
|
183
|
+
}
|
|
184
|
+
|
|
167
185
|
function registeredIds(root) {
|
|
168
186
|
const ids = new Set();
|
|
169
187
|
for (const file of walk(join(root, 'src'), ['.ts', '.js', '.mjs', '.svelte'])) {
|
|
@@ -223,12 +241,14 @@ export function loadVocabulary({ root = process.cwd(), pkgRoot = PKG_ROOT } = {}
|
|
|
223
241
|
});
|
|
224
242
|
}
|
|
225
243
|
const registered = registeredIds(root);
|
|
244
|
+
const builtIn = builtInIds(root, pkgRoot);
|
|
226
245
|
|
|
227
246
|
return {
|
|
228
247
|
themeTokens,
|
|
229
248
|
componentTokens,
|
|
230
249
|
components,
|
|
231
250
|
registered,
|
|
251
|
+
builtIn,
|
|
232
252
|
tokensCssPath,
|
|
233
253
|
/** True when `name` resolves to something real at runtime. */
|
|
234
254
|
knows(name) {
|
|
@@ -81,6 +81,10 @@ var KIND_RULES = [
|
|
|
81
81
|
prefix: ["--border-width-"]
|
|
82
82
|
},
|
|
83
83
|
{ kind: "border", suffix: ["-border"], prefix: ["--border-"] },
|
|
84
|
+
// A dimension with no more specific name behind it — a panel's width, an
|
|
85
|
+
// avatar's size. Last of the geometry rules, so every `-border-width`,
|
|
86
|
+
// `-divider-height`, `-icon-size` and the rest claim their token first.
|
|
87
|
+
{ kind: "length", suffix: ["-width", "-height", "-size"] },
|
|
84
88
|
// Fills. A tint is a wash over a surface, so it takes the surface picker: the
|
|
85
89
|
// full palette with an alpha, not just the tint stops it defaults to.
|
|
86
90
|
{
|
|
@@ -95,10 +99,7 @@ var KIND_RULES = [
|
|
|
95
99
|
"-accent",
|
|
96
100
|
"-color",
|
|
97
101
|
"-tint",
|
|
98
|
-
"-opacity"
|
|
99
|
-
"-width",
|
|
100
|
-
"-height",
|
|
101
|
-
"-size"
|
|
102
|
+
"-opacity"
|
|
102
103
|
],
|
|
103
104
|
prefix: ["--surface-", "--tint", "--color-"]
|
|
104
105
|
}
|
|
@@ -281,7 +282,9 @@ var KNOWN_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
281
282
|
"componentConfigsDir",
|
|
282
283
|
"themesDir",
|
|
283
284
|
"manifestsDir",
|
|
284
|
-
"tokensCssPath"
|
|
285
|
+
"tokensCssPath",
|
|
286
|
+
"componentDirs",
|
|
287
|
+
"checks"
|
|
285
288
|
]);
|
|
286
289
|
var cached = null;
|
|
287
290
|
function readLiveTokensConfig() {
|
|
@@ -1125,6 +1128,29 @@ var componentMigration_2026_09_01_gateSuffixEnabled = {
|
|
|
1125
1128
|
}
|
|
1126
1129
|
};
|
|
1127
1130
|
|
|
1131
|
+
// src/editor/core/themes/migrations/2026-09-02-sectiondivider-drop-title-outline.ts
|
|
1132
|
+
var DROPPED2 = new Set(
|
|
1133
|
+
["lg", "md", "sm"].flatMap((v) => [
|
|
1134
|
+
`--sectiondivider-${v}-title-outline-width`,
|
|
1135
|
+
`--sectiondivider-${v}-title-outline-color`
|
|
1136
|
+
])
|
|
1137
|
+
);
|
|
1138
|
+
var componentMigration_2026_09_02_sectiondividerDropTitleOutline = {
|
|
1139
|
+
id: "2026-09-02-sectiondivider-drop-title-outline",
|
|
1140
|
+
fromVersion: 25,
|
|
1141
|
+
toVersion: 26,
|
|
1142
|
+
appliesTo: "component-config",
|
|
1143
|
+
apply(rawVars, meta) {
|
|
1144
|
+
if (meta.component !== "sectiondivider") return { ...rawVars };
|
|
1145
|
+
const out = {};
|
|
1146
|
+
for (const [key, value] of Object.entries(rawVars)) {
|
|
1147
|
+
if (DROPPED2.has(key)) continue;
|
|
1148
|
+
out[key] = value;
|
|
1149
|
+
}
|
|
1150
|
+
return out;
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
|
|
1128
1154
|
// src/editor/core/themes/migrations/index.ts
|
|
1129
1155
|
var MIGRATIONS = [
|
|
1130
1156
|
colorsAndTypeMigration_2026_04_24_legacyKeysAndBgToCanvas,
|
|
@@ -1158,7 +1184,8 @@ var MIGRATIONS = [
|
|
|
1158
1184
|
colorsAndTypeMigration_2026_09_01_tintRename,
|
|
1159
1185
|
componentMigration_2026_09_01_tintRename,
|
|
1160
1186
|
componentMigration_2026_09_01_tabbarActiveTint,
|
|
1161
|
-
componentMigration_2026_09_01_gateSuffixEnabled
|
|
1187
|
+
componentMigration_2026_09_01_gateSuffixEnabled,
|
|
1188
|
+
componentMigration_2026_09_02_sectiondividerDropTitleOutline
|
|
1162
1189
|
];
|
|
1163
1190
|
function countFor(kind) {
|
|
1164
1191
|
return MIGRATIONS.filter((m) => m.appliesTo === kind).length;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { A as AliasDiskValue, a as ComponentConfig } from '../themeTypes-DSV3Zisf.cjs';
|
|
2
|
-
export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-
|
|
2
|
+
export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BhWzd5cL.cjs';
|
|
3
3
|
export { C as CURRENT_COMPONENT_SCHEMA_VERSION } from '../index-4N-Orzzi.cjs';
|
|
4
4
|
|
|
5
5
|
type AdjustKind = 'radius' | 'padding' | 'gap' | 'border-width';
|
|
@@ -37,7 +37,7 @@ interface AdjustResult {
|
|
|
37
37
|
}
|
|
38
38
|
declare function adjustAliases(configs: Record<string, ComponentConfig>, ops: readonly AdjustOp[], now: string): AdjustResult;
|
|
39
39
|
|
|
40
|
-
type TokenKind = 'surface' | 'border' | 'border-width' | 'radius' | 'divider-width' | 'divider-height' | 'divider-inset' | 'dot-size' | 'blur' | 'scale' | 'shadow' | 'font-family' | 'font-weight' | 'font-size' | 'line-height' | 'letter-spacing' | 'padding' | 'gap' | 'duration' | 'easing' | 'text-color';
|
|
40
|
+
type TokenKind = 'surface' | 'border' | 'border-width' | 'radius' | 'divider-width' | 'divider-height' | 'divider-inset' | 'dot-size' | 'length' | 'blur' | 'scale' | 'shadow' | 'font-family' | 'font-weight' | 'font-size' | 'line-height' | 'letter-spacing' | 'padding' | 'gap' | 'duration' | 'easing' | 'text-color';
|
|
41
41
|
/** Side-aware kind test. Per-side paddings (`--card-default-body-padding-top`)
|
|
42
42
|
are written by UIPaddingSelector and never declared as editor tokens, so
|
|
43
43
|
`rawKind` never meets one; the CLI does, and they belong with their parent. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { A as AliasDiskValue, a as ComponentConfig } from '../themeTypes-DSV3Zisf.js';
|
|
2
|
-
export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-
|
|
2
|
+
export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BhWzd5cL.js';
|
|
3
3
|
export { C as CURRENT_COMPONENT_SCHEMA_VERSION } from '../index-4N-Orzzi.js';
|
|
4
4
|
|
|
5
5
|
type AdjustKind = 'radius' | 'padding' | 'gap' | 'border-width';
|
|
@@ -37,7 +37,7 @@ interface AdjustResult {
|
|
|
37
37
|
}
|
|
38
38
|
declare function adjustAliases(configs: Record<string, ComponentConfig>, ops: readonly AdjustOp[], now: string): AdjustResult;
|
|
39
39
|
|
|
40
|
-
type TokenKind = 'surface' | 'border' | 'border-width' | 'radius' | 'divider-width' | 'divider-height' | 'divider-inset' | 'dot-size' | 'blur' | 'scale' | 'shadow' | 'font-family' | 'font-weight' | 'font-size' | 'line-height' | 'letter-spacing' | 'padding' | 'gap' | 'duration' | 'easing' | 'text-color';
|
|
40
|
+
type TokenKind = 'surface' | 'border' | 'border-width' | 'radius' | 'divider-width' | 'divider-height' | 'divider-inset' | 'dot-size' | 'length' | 'blur' | 'scale' | 'shadow' | 'font-family' | 'font-weight' | 'font-size' | 'line-height' | 'letter-spacing' | 'padding' | 'gap' | 'duration' | 'easing' | 'text-color';
|
|
41
41
|
/** Side-aware kind test. Per-side paddings (`--card-default-body-padding-top`)
|
|
42
42
|
are written by UIPaddingSelector and never declared as editor tokens, so
|
|
43
43
|
`rawKind` never meets one; the CLI does, and they belong with their parent. */
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CURRENT_COMPONENT_SCHEMA_VERSION
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-YLCOIGQC.js";
|
|
4
4
|
import {
|
|
5
5
|
readLiveTokensConfig,
|
|
6
6
|
resolveDataDirs
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-W6Y4BWFB.js";
|
|
8
8
|
|
|
9
9
|
// src/editor/core/components/aliasKinds.ts
|
|
10
10
|
var KIND_RULES = [
|
|
@@ -49,6 +49,10 @@ var KIND_RULES = [
|
|
|
49
49
|
prefix: ["--border-width-"]
|
|
50
50
|
},
|
|
51
51
|
{ kind: "border", suffix: ["-border"], prefix: ["--border-"] },
|
|
52
|
+
// A dimension with no more specific name behind it — a panel's width, an
|
|
53
|
+
// avatar's size. Last of the geometry rules, so every `-border-width`,
|
|
54
|
+
// `-divider-height`, `-icon-size` and the rest claim their token first.
|
|
55
|
+
{ kind: "length", suffix: ["-width", "-height", "-size"] },
|
|
52
56
|
// Fills. A tint is a wash over a surface, so it takes the surface picker: the
|
|
53
57
|
// full palette with an alpha, not just the tint stops it defaults to.
|
|
54
58
|
{
|
|
@@ -63,10 +67,7 @@ var KIND_RULES = [
|
|
|
63
67
|
"-accent",
|
|
64
68
|
"-color",
|
|
65
69
|
"-tint",
|
|
66
|
-
"-opacity"
|
|
67
|
-
"-width",
|
|
68
|
-
"-height",
|
|
69
|
-
"-size"
|
|
70
|
+
"-opacity"
|
|
70
71
|
],
|
|
71
72
|
prefix: ["--surface-", "--tint", "--color-"]
|
|
72
73
|
}
|
|
@@ -803,6 +803,29 @@ var componentMigration_2026_09_01_gateSuffixEnabled = {
|
|
|
803
803
|
}
|
|
804
804
|
};
|
|
805
805
|
|
|
806
|
+
// src/editor/core/themes/migrations/2026-09-02-sectiondivider-drop-title-outline.ts
|
|
807
|
+
var DROPPED2 = new Set(
|
|
808
|
+
["lg", "md", "sm"].flatMap((v) => [
|
|
809
|
+
`--sectiondivider-${v}-title-outline-width`,
|
|
810
|
+
`--sectiondivider-${v}-title-outline-color`
|
|
811
|
+
])
|
|
812
|
+
);
|
|
813
|
+
var componentMigration_2026_09_02_sectiondividerDropTitleOutline = {
|
|
814
|
+
id: "2026-09-02-sectiondivider-drop-title-outline",
|
|
815
|
+
fromVersion: 25,
|
|
816
|
+
toVersion: 26,
|
|
817
|
+
appliesTo: "component-config",
|
|
818
|
+
apply(rawVars, meta) {
|
|
819
|
+
if (meta.component !== "sectiondivider") return { ...rawVars };
|
|
820
|
+
const out = {};
|
|
821
|
+
for (const [key, value] of Object.entries(rawVars)) {
|
|
822
|
+
if (DROPPED2.has(key)) continue;
|
|
823
|
+
out[key] = value;
|
|
824
|
+
}
|
|
825
|
+
return out;
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
|
|
806
829
|
// src/editor/core/themes/migrations/index.ts
|
|
807
830
|
var MIGRATIONS = [
|
|
808
831
|
colorsAndTypeMigration_2026_04_24_legacyKeysAndBgToCanvas,
|
|
@@ -836,7 +859,8 @@ var MIGRATIONS = [
|
|
|
836
859
|
colorsAndTypeMigration_2026_09_01_tintRename,
|
|
837
860
|
componentMigration_2026_09_01_tintRename,
|
|
838
861
|
componentMigration_2026_09_01_tabbarActiveTint,
|
|
839
|
-
componentMigration_2026_09_01_gateSuffixEnabled
|
|
862
|
+
componentMigration_2026_09_01_gateSuffixEnabled,
|
|
863
|
+
componentMigration_2026_09_02_sectiondividerDropTitleOutline
|
|
840
864
|
];
|
|
841
865
|
function countFor(kind) {
|
|
842
866
|
return MIGRATIONS.filter((m) => m.appliesTo === kind).length;
|
|
@@ -16,6 +16,10 @@ interface LiveTokensFileConfig {
|
|
|
16
16
|
* migrate` CLI has no plugin options, so it reads this key to locate the file.
|
|
17
17
|
*/
|
|
18
18
|
tokensCssPath?: string;
|
|
19
|
+
/** Extra directories the component checkers and the registry query discover. */
|
|
20
|
+
componentDirs?: string[];
|
|
21
|
+
/** Checker settings: `rules` per-rule severities, `exclude` paths to skip. */
|
|
22
|
+
checks?: Record<string, unknown>;
|
|
19
23
|
}
|
|
20
24
|
interface ResolveDataDirsInput {
|
|
21
25
|
dataDir?: string;
|
|
@@ -16,6 +16,10 @@ interface LiveTokensFileConfig {
|
|
|
16
16
|
* migrate` CLI has no plugin options, so it reads this key to locate the file.
|
|
17
17
|
*/
|
|
18
18
|
tokensCssPath?: string;
|
|
19
|
+
/** Extra directories the component checkers and the registry query discover. */
|
|
20
|
+
componentDirs?: string[];
|
|
21
|
+
/** Checker settings: `rules` per-rule severities, `exclude` paths to skip. */
|
|
22
|
+
checks?: Record<string, unknown>;
|
|
19
23
|
}
|
|
20
24
|
interface ResolveDataDirsInput {
|
|
21
25
|
dataDir?: string;
|