@motion-proto/live-tokens 0.77.0 → 0.78.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 +2 -0
- package/.claude/skills/live-tokens-create-page/SKILL.md +5 -4
- package/.claude/skills/live-tokens-fix-findings/SKILL.md +7 -0
- package/CHANGELOG.md +53 -0
- package/bin/check-page.mjs +131 -9
- package/bin/cli.mjs +25 -3
- package/bin/contractRunner.mjs +343 -29
- package/bin/lib/pageRoutes.mjs +309 -0
- package/package.json +5 -3
- package/src/editor/overlay/ColumnsOverlay.svelte +1 -1
- package/src/editor/overlay/LiveEditorOverlay.svelte +1 -0
- package/src/editor/overlay/LiveTokensRouter.svelte +1 -0
- package/src/editor/skill-atlas/skillSources.generated.ts +3 -3
- package/src/editor/skill-atlas/trees/check-compliance.ts +18 -18
- package/src/editor/skill-atlas/trees/create-page.ts +26 -26
- package/src/editor/skill-atlas/trees/fix-findings.ts +46 -11
- package/src/testing-js/{chunk-7TI7Z6Y6.js → chunk-GNIUPIU2.js} +2 -2
- package/src/testing-js/{chunk-ZMSX6CXR.js → chunk-NB3NZRBM.js} +13 -2
- package/src/testing-js/chunk-NB3NZRBM.js.map +1 -0
- package/src/testing-js/{chunk-L73N4NSO.js → chunk-WIZ6W7UT.js} +2 -2
- package/src/testing-js/component-alias.contract.js +2 -2
- package/src/testing-js/component-editor.contract.js +3 -3
- package/src/testing-js/component-render.contract.js +3 -3
- package/src/testing-js/index.d.ts +8 -2
- package/src/testing-js/index.js +19 -1
- package/src/testing-js/index.js.map +1 -1
- package/src/testing-js/page-compliance.contract.js +829 -0
- package/src/testing-js/page-compliance.contract.js.map +1 -0
- package/src/testing-js/{vitest-BE6uGF31.d.ts → vitest-C-wNWcoA.d.ts} +21 -1
- package/src/testing-js/vitest.d.ts +1 -1
- package/src/testing-js/vitest.js +1 -1
- package/template/README.md +16 -9
- package/template/package.json +1 -1
- package/template/src/pages/Home.svelte +13 -0
- package/src/testing-js/chunk-ZMSX6CXR.js.map +0 -1
- /package/src/testing-js/{chunk-7TI7Z6Y6.js.map → chunk-GNIUPIU2.js.map} +0 -0
- /package/src/testing-js/{chunk-L73N4NSO.js.map → chunk-WIZ6W7UT.js.map} +0 -0
|
@@ -9,6 +9,8 @@ Run `npx live-tokens report`. The CLI prints a report with the sections in the R
|
|
|
9
9
|
|
|
10
10
|
`report` never runs the component contract suites. The `check-component` test run does, and it is what live-tokens-create-component calls to validate a component's runtime behavior.
|
|
11
11
|
|
|
12
|
+
`report` stays static for pages too. The `check-page` test run is what live-tokens-create-page calls for a rendered page, in a browser against the project's own route, and it reports `page-component-paint`, `page-text-style`, `page-contrast`, `page-grid`, and `page-overflow` findings the same way `report`'s static findings read: by rule, with a fix.
|
|
13
|
+
|
|
12
14
|
## Workflow
|
|
13
15
|
|
|
14
16
|
When `report` is an unknown command, route the dependency upgrade to **live-tokens-fix-findings**. Resume the audit after the upgrade.
|
|
@@ -17,7 +17,7 @@ Assemble the page from the shipped components at their defaults and the theme's
|
|
|
17
17
|
6. Write the page CSS in design tokens.
|
|
18
18
|
7. Set the hierarchy: one text style per element, the shipped size on every control, one primary action, and one space step per position.
|
|
19
19
|
8. Add the route, with a lazy import and the source path.
|
|
20
|
-
9. Run **live-tokens-check-compliance
|
|
20
|
+
9. Run **live-tokens-check-compliance** and `npx live-tokens check-page <file> --tests --strict`, then check the rendered page.
|
|
21
21
|
10. Reply with the sections and the layout each took, the components placed, the route, and the compliance result.
|
|
22
22
|
|
|
23
23
|
## Layout
|
|
@@ -34,7 +34,7 @@ Decide the sections before the columns. Read the page top to bottom and name eac
|
|
|
34
34
|
| Grid of equals | The reader compares or scans items of one kind. | Equal spans. Up to seven per section. |
|
|
35
35
|
| Single column | The reader fills a form or reads at length. | Half the columns (6), centered. |
|
|
36
36
|
|
|
37
|
-
The stage is the canvas, player, or strip the work is about. Stretch a section's containers to one height (`align-items: stretch`) so their bottom edges align.
|
|
37
|
+
The stage is the canvas, player, or strip the work is about. Stretch a section's containers to one height (`align-items: stretch`) so their bottom edges align.
|
|
38
38
|
|
|
39
39
|
### Grid
|
|
40
40
|
|
|
@@ -49,6 +49,8 @@ To place a section's children at page-column positions:
|
|
|
49
49
|
|
|
50
50
|
A grid that follows the page columns takes `var(--columns-count)` or a `calc()` of it as its count, so it stays in step with `ColumnsOverlay`. A local grid of two or three equal columns writes its own count. A column number in `grid-column` is fixed to the count read in step 1.
|
|
51
51
|
|
|
52
|
+
No scaffold collapses the page grid on a phone: the theme's own column gutter alone exceeds a phone's width at the full column count, so every page writes its own `@media (max-width: 767px)` rule setting `grid-template-columns: 1fr` and `column-gap: 0`, with each section's children spanning `grid-column: 1 / -1`. A section's columns then stack in reading order.
|
|
53
|
+
|
|
52
54
|
### Separation
|
|
53
55
|
|
|
54
56
|
The page shows one thing, and every other element stays out of its way. Each element that is not content costs attention: a hairline, a border, a header bar, a shadow. Keep an element only when it serves a purpose no other element serves.
|
|
@@ -163,7 +165,7 @@ const pages = {
|
|
|
163
165
|
|
|
164
166
|
## Verify
|
|
165
167
|
|
|
166
|
-
Run **live-tokens-check-compliance
|
|
168
|
+
Run **live-tokens-check-compliance**, then `npx live-tokens check-page <file> --tests --strict`. The Playwright suite runs against the page's own route and proves what only a rendered page can: the cascade leaves every component painting from its semantic properties, every run of text sits in one shipped text style, every text and surface pair meets AA, sections sit on the page grid, and nothing overflows. Each finding carries a rule id and a line; `--off=<rule>` silences a rule for one run. The two reports carry every finding by rule, and **live-tokens-fix-findings** takes the fix list. Repeat until the page is clean.
|
|
167
169
|
|
|
168
170
|
The checkers cannot see a layout. Open the page at the width it is built for and check each line below.
|
|
169
171
|
|
|
@@ -172,7 +174,6 @@ The checkers cannot see a layout. Open the page at the width it is built for and
|
|
|
172
174
|
- No label is larger than the page's body copy.
|
|
173
175
|
- A line of copy runs 45 to 90 characters.
|
|
174
176
|
- The containers in a section align at the bottom.
|
|
175
|
-
- Every control stays inside its wrapper. A `width: 100%` field takes `box-sizing: border-box`.
|
|
176
177
|
- The actions sit where the eye goes last, with the one primary at the end.
|
|
177
178
|
- Every row of actions holds an action that leaves without committing.
|
|
178
179
|
- An action that destroys saved work confirms in a `Dialog`.
|
|
@@ -80,6 +80,13 @@ When `package.json` has no `check:design` script, add `"check:design": "live-tok
|
|
|
80
80
|
| `control-size` | Delete the `size` prop. The shipped default is the page's size. When that default is wrong for the project, retune the component in `/live-tokens/components`. |
|
|
81
81
|
| `multiple-primary` | Keep the action that completes the main task `primary`. A Button with no `variant` counts as `primary`. Use `secondary` for supporting or related actions and `outline` for unrelated or informational actions. |
|
|
82
82
|
| `danger-without-dialog` | Open a `Dialog` from the danger Button or IconButton and run the action from the Dialog's confirm. The rule fires once per page, when the page imports no Dialog. For other actions, assign emphasis by the action's relationship to the main task. |
|
|
83
|
+
| `native-control` | Replace the native element with the shipped component the message names: Button or IconButton, Input, MenuSelect. |
|
|
84
|
+
| `property-override` | Delete the declaration from the page. Retune the component's token for the whole project at `/live-tokens/components`. |
|
|
85
|
+
| `page-component-paint` | The finding names the page file and the line of the instance whose part painted a value its semantic property never resolves to. Remove the global rule reaching past the component, from `site.css` or the page's own CSS, and retune the component's token for the whole project at `/live-tokens/components`. |
|
|
86
|
+
| `page-text-style` | The finding names the page file and the line of the text element and the nearest bundle it missed. Set the container's text style directly on the text element, one shipped bundle from `npx live-tokens tokens --scale heading` (or `body`, `editorial`, `code`), rather than leaving it to inherit from an ancestor typed for a different role. |
|
|
87
|
+
| `page-contrast` | The finding names the page file and the line of the text element, the surface ancestor, and both computed colors. Pick the text token the surface pairs with, from the Color by role table above. |
|
|
88
|
+
| `page-grid` | The finding names the page file and the line of the section whose edge sits off a column line. Move the edge to the line: place it by page-column numbers per the Grid section of **live-tokens-create-page**, and give a centered section symmetric insets rather than a margin, a width, or a transform. |
|
|
89
|
+
| `page-overflow` | The finding names the page file and the line of the element or the instance that overflows. Give the control its shipped width, remove a fixed width that does not fit its column at the viewport, and set `overflow-x: auto` only on an element deliberately meant to scroll, such as a code block. When the overflow is the page grid itself at a phone width, collapse it: `grid-template-columns: 1fr`, `column-gap: 0`, per the Grid section of **live-tokens-create-page**. |
|
|
83
90
|
| `hardcoded-columns` | `repeat(var(--columns-count), 1fr)` for the page grid. `calc(var(--columns-count) - 2)` for a sub-grid spanning fewer columns. |
|
|
84
91
|
| `site-css-in-main` | Delete the import from `main.ts`. Add it to each page's `<script>`. Page CSS then stays off the editor routes. |
|
|
85
92
|
| `missing-source` | Add `source: 'src/...'` to the route entry. |
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.78.0 — A page proves compliance as rendered
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **A page proves compliance as rendered, not just in source.** `check-page
|
|
8
|
+
--tests` opens each page's own route in a Playwright browser, against the
|
|
9
|
+
consumer's own app, and reports five rule ids no static read of the source
|
|
10
|
+
could: `page-component-paint` (every shipped component instance paints its
|
|
11
|
+
contracted parts from its semantic properties, never from a page-wide or
|
|
12
|
+
`site.css` rule that reaches past them), `page-text-style` (every run of
|
|
13
|
+
text sits in one shipped text style, never inherited from a container typed
|
|
14
|
+
for a different role), `page-contrast` (every text and surface pair the
|
|
15
|
+
page composes meets AA), `page-grid` (every section's edges land on the
|
|
16
|
+
page's own column lines), and `page-overflow` (nothing overflows its
|
|
17
|
+
container, at either viewport). Coverage is reported by page, rule, and
|
|
18
|
+
viewport; a rule that observed nothing on a page reports `inapplicable`
|
|
19
|
+
with a reason, never a silent pass. `live-tokens.testing.ts` gains two
|
|
20
|
+
settings: `pageRoutes`, for a route the app's own route table cannot
|
|
21
|
+
express, and `pageViewports`, to replace the two sizes every rule runs at.
|
|
22
|
+
- **Two static rules close source-level gaps `check-page` left open.**
|
|
23
|
+
`native-control` flags a raw `<button>`, `<input>`, `<select>`, or
|
|
24
|
+
`<textarea>` where a shipped component belongs. `property-override` flags a
|
|
25
|
+
page that declares or sets a component token's name directly, in a style
|
|
26
|
+
block, an inline `style`, a `style:` directive, or `setProperty`, rather
|
|
27
|
+
than retuning the component for the whole project at
|
|
28
|
+
`/live-tokens/components`.
|
|
29
|
+
- **The consumer acceptance gate now covers a page.** `check:smoke-page-tests`
|
|
30
|
+
packs the built package into a fresh project outside the repository,
|
|
31
|
+
scaffolds it with `create`, and proves the documented command against the
|
|
32
|
+
template's own clean page and against the same page under a deliberate
|
|
33
|
+
`site.css` override: the clean page passes, the override fails
|
|
34
|
+
`page-component-paint` at the page file, and source-tree hashes prove the
|
|
35
|
+
isolated run never touched the project's own data. Wired into
|
|
36
|
+
`prepublishOnly` beside the existing component gate. Both gates now carry a
|
|
37
|
+
twenty-minute deadline of their own, naming the step that did not finish.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- **The `create` template's `test:design` runs both suites:**
|
|
42
|
+
`live-tokens check-component --tests && live-tokens check-page --tests`.
|
|
43
|
+
- **The live-tokens-create-page skill's Verify step runs the rendered check.**
|
|
44
|
+
After live-tokens-check-compliance, it runs `check-page --tests --strict`
|
|
45
|
+
on the page it built. The manual line "every control stays inside its
|
|
46
|
+
wrapper" moves to the automated `page-overflow` rule; every other
|
|
47
|
+
editorial line stays.
|
|
48
|
+
- **The `create` template's own Home page collapses to one column below
|
|
49
|
+
768px.** Its `.stub` held a fixed twelve-column span with no phone
|
|
50
|
+
breakpoint, so `page-overflow` failed at 390x844, the defect
|
|
51
|
+
`check:smoke-page-tests` now catches on any page written to that shape.
|
|
52
|
+
The template ships no exclusion mechanism, so the fix is the page's own
|
|
53
|
+
`@media (max-width: 767px)` rule, the same shape every page-defects
|
|
54
|
+
fixture already takes.
|
|
55
|
+
|
|
3
56
|
## 0.77.0 — A consumer component runs the same suites
|
|
4
57
|
|
|
5
58
|
### Added
|
package/bin/check-page.mjs
CHANGED
|
@@ -33,10 +33,24 @@ export const PAGE_RULES = {
|
|
|
33
33
|
'control-size': 'warn',
|
|
34
34
|
'multiple-primary': 'warn',
|
|
35
35
|
'danger-without-dialog': 'warn',
|
|
36
|
+
'native-control': 'warn',
|
|
37
|
+
'property-override': 'warn',
|
|
38
|
+
// `--tests` (bin/contractRunner.mjs's `runPageTests`). Fixed by design
|
|
39
|
+
// decision 9, same reasoning as `check-component`'s own `contract-*` rules:
|
|
40
|
+
// every one is an error, including the setup rules, which `--tests` treats
|
|
41
|
+
// as never-silenceable (see cli.mjs).
|
|
42
|
+
'page-component-paint': 'error',
|
|
43
|
+
'page-text-style': 'error',
|
|
44
|
+
'page-contrast': 'error',
|
|
45
|
+
'page-grid': 'error',
|
|
46
|
+
'page-overflow': 'error',
|
|
47
|
+
'tests-not-installed': 'error',
|
|
48
|
+
'tests-setup': 'error',
|
|
49
|
+
'tests-incomplete': 'error',
|
|
36
50
|
};
|
|
37
51
|
|
|
38
52
|
// Directories that hold the system, not pages built on it.
|
|
39
|
-
const NOT_PAGES = ['src/system', 'src/editor', 'src/lib', 'src/live-tokens'];
|
|
53
|
+
export const NOT_PAGES = ['src/system', 'src/editor', 'src/lib', 'src/live-tokens'];
|
|
40
54
|
|
|
41
55
|
export const COMPONENT_IMPORT =
|
|
42
56
|
/(?:@motion-proto\/live-tokens\/components|[./][^'"]*\/system\/components)\/([A-Za-z0-9]+)\.svelte$/;
|
|
@@ -194,6 +208,61 @@ function tagAttributes(code, start) {
|
|
|
194
208
|
return { attrs, end: tagEnd };
|
|
195
209
|
}
|
|
196
210
|
|
|
211
|
+
// The shipped component that owns each native control's paint.
|
|
212
|
+
const NATIVE_CONTROLS = {
|
|
213
|
+
button: 'Button or IconButton',
|
|
214
|
+
input: 'Input',
|
|
215
|
+
select: 'MenuSelect',
|
|
216
|
+
textarea: 'Input',
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* `{@html ...}` blanked, brace-balanced, so a tag textually present inside a
|
|
221
|
+
* raw HTML string is not mistaken for markup the page authored.
|
|
222
|
+
*/
|
|
223
|
+
function blankHtmlExpressions(code) {
|
|
224
|
+
let out = code;
|
|
225
|
+
const re = /\{@html\b/g;
|
|
226
|
+
let m;
|
|
227
|
+
while ((m = re.exec(out)) !== null) {
|
|
228
|
+
const start = m.index;
|
|
229
|
+
let depth = 0;
|
|
230
|
+
let i = start;
|
|
231
|
+
for (; i < out.length; i++) {
|
|
232
|
+
if (out[i] === '{') depth++;
|
|
233
|
+
else if (out[i] === '}' && --depth === 0) {
|
|
234
|
+
i++;
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
out = out.slice(0, start) + ' '.repeat(i - start) + out.slice(i);
|
|
239
|
+
re.lastIndex = start;
|
|
240
|
+
}
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** A raw `<button>`, `<input>`, `<select>`, or `<textarea>` where a shipped
|
|
245
|
+
component belongs. `<input type="hidden">` paints nothing and is exempt.
|
|
246
|
+
A tag inside `<script>` is markup the page assembles into a string, not
|
|
247
|
+
markup it renders, so the script block is blanked the same way
|
|
248
|
+
`codeRegion` blanks `<style>`, keeping offsets aligned with `code`. */
|
|
249
|
+
function checkNativeControls(code, add) {
|
|
250
|
+
const scan = blankHtmlExpressions(code).replace(/<script[^>]*>[\s\S]*?<\/script>/g, (m) => ' '.repeat(m.length));
|
|
251
|
+
for (const tag of Object.keys(NATIVE_CONTROLS)) {
|
|
252
|
+
for (const m of scan.matchAll(new RegExp(`<${tag}(?=[\\s/>])`, 'g'))) {
|
|
253
|
+
const parsed = tagAttributes(scan, m.index);
|
|
254
|
+
if (tag === 'input' && parsed?.attrs.some((a) => a.name.toLowerCase() === 'type' && a.value?.toLowerCase() === 'hidden')) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
add(
|
|
258
|
+
'native-control',
|
|
259
|
+
m.index,
|
|
260
|
+
`<${tag}> is a native control; use ${NATIVE_CONTROLS[tag]} so it paints from the theme.`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
197
266
|
/**
|
|
198
267
|
* Props a page passes that the component does not declare, values outside a
|
|
199
268
|
* prop's union, and a shipped component the page sizes itself.
|
|
@@ -274,6 +343,22 @@ function checkDestructiveActions(code, imports, add) {
|
|
|
274
343
|
}
|
|
275
344
|
}
|
|
276
345
|
|
|
346
|
+
// Which component's :global(:root) block declared each component token,
|
|
347
|
+
// cached per vocabulary since checkFile runs once per page.
|
|
348
|
+
const tokenOwnersCache = new WeakMap();
|
|
349
|
+
function componentTokenOwners(vocab) {
|
|
350
|
+
let owners = tokenOwnersCache.get(vocab);
|
|
351
|
+
if (owners) return owners;
|
|
352
|
+
owners = new Map();
|
|
353
|
+
for (const comp of vocab.components.values()) {
|
|
354
|
+
for (const name of comp.tokens.keys()) {
|
|
355
|
+
if (!owners.has(name)) owners.set(name, comp.name);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
tokenOwnersCache.set(vocab, owners);
|
|
359
|
+
return owners;
|
|
360
|
+
}
|
|
361
|
+
|
|
277
362
|
/** The object literal enclosing `index`, found by balancing braces outward. */
|
|
278
363
|
function enclosingObject(text, index) {
|
|
279
364
|
let depth = 0;
|
|
@@ -335,6 +420,7 @@ function checkFile(file, text, vocab, root) {
|
|
|
335
420
|
checkComponentUsage(code, imports, add);
|
|
336
421
|
checkPrimaryActions(code, imports, add);
|
|
337
422
|
checkDestructiveActions(code, imports, add);
|
|
423
|
+
checkNativeControls(code, add);
|
|
338
424
|
|
|
339
425
|
for (const m of code.matchAll(/['"](\/live-tokens[^'"]*)['"]\s*:/g)) {
|
|
340
426
|
add('reserved-route', m.index, `route '${m[1]}' is inside the reserved /live-tokens/* namespace`);
|
|
@@ -360,16 +446,52 @@ function checkFile(file, text, vocab, root) {
|
|
|
360
446
|
|
|
361
447
|
// A page may also mint a custom property outside its <style> block — a
|
|
362
448
|
// `style:--x={...}` directive or an el.style.setProperty call — and those are
|
|
363
|
-
// just as declared as one written in CSS.
|
|
364
|
-
|
|
449
|
+
// just as declared as one written in CSS. Each name keeps the earliest site
|
|
450
|
+
// it was declared at, so property-override reports one finding per name.
|
|
451
|
+
const declaredSites = new Map();
|
|
452
|
+
const declareAt = (name, index) => {
|
|
453
|
+
if (!declaredSites.has(name) || index < declaredSites.get(name)) declaredSites.set(name, index);
|
|
454
|
+
};
|
|
365
455
|
const regions = styleRegions(text, file);
|
|
366
|
-
|
|
367
|
-
for (const
|
|
368
|
-
|
|
456
|
+
const styleBlockDeclarations = (regionList, at) => {
|
|
457
|
+
for (const region of regionList) {
|
|
458
|
+
for (const m of neutralise(region.text).matchAll(/(?:^|[;{])\s*(--[a-z0-9-]+)\s*:/gim)) {
|
|
459
|
+
at(m[1], region.offset + m.index);
|
|
460
|
+
}
|
|
369
461
|
}
|
|
370
|
-
}
|
|
462
|
+
};
|
|
463
|
+
styleBlockDeclarations(regions, declareAt);
|
|
371
464
|
for (const m of text.matchAll(/(?:style:|setProperty\(\s*['"`]|['"`])(--[a-z0-9-]+)/g)) {
|
|
372
|
-
|
|
465
|
+
declareAt(m[1], m.index);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// property-override needs a narrower set than declaredSites: the bare-quote
|
|
469
|
+
// alternative above exists only to suppress unknown-token on a name any
|
|
470
|
+
// quoted string mentions, so it also matches a read like
|
|
471
|
+
// getPropertyValue("--x"). A real declaration is a style-block rule, an
|
|
472
|
+
// inline `style="--x: ..."` attribute, a `style:--x=` directive, or a
|
|
473
|
+
// setProperty('--x', ...) call.
|
|
474
|
+
const overrideSites = new Map();
|
|
475
|
+
const overrideAt = (name, index) => {
|
|
476
|
+
if (!overrideSites.has(name) || index < overrideSites.get(name)) overrideSites.set(name, index);
|
|
477
|
+
};
|
|
478
|
+
styleBlockDeclarations(regions, overrideAt);
|
|
479
|
+
if (code !== null) styleBlockDeclarations(inlineStyleRegions(code), overrideAt);
|
|
480
|
+
for (const m of text.matchAll(/(?:style:|setProperty\(\s*['"`])(--[a-z0-9-]+)/g)) {
|
|
481
|
+
overrideAt(m[1], m.index);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// A name the vocabulary already ties to a component is that component's
|
|
485
|
+
// token, so declaring it here is one instance overriding the whole
|
|
486
|
+
// project's retuning surface at /live-tokens/components.
|
|
487
|
+
for (const [name, index] of overrideSites) {
|
|
488
|
+
if (!vocab.componentTokens.has(name)) continue;
|
|
489
|
+
const owner = componentTokenOwners(vocab).get(name) ?? 'a shipped component';
|
|
490
|
+
add(
|
|
491
|
+
'property-override',
|
|
492
|
+
index,
|
|
493
|
+
`${name} overrides ${owner}'s token here instead of the whole project; retune it at /live-tokens/components.`,
|
|
494
|
+
);
|
|
373
495
|
}
|
|
374
496
|
|
|
375
497
|
for (const region of [...regions, ...(code === null ? [] : inlineStyleRegions(code))]) {
|
|
@@ -378,7 +500,7 @@ function checkFile(file, text, vocab, root) {
|
|
|
378
500
|
|
|
379
501
|
for (const m of css.matchAll(/var\(\s*(--[a-z0-9-]+)/g)) {
|
|
380
502
|
const name = m[1];
|
|
381
|
-
if (
|
|
503
|
+
if (declaredSites.has(name) || vocab.knows(name)) continue;
|
|
382
504
|
add(
|
|
383
505
|
'unknown-token',
|
|
384
506
|
at(m.index),
|
package/bin/cli.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
20
20
|
import process from 'node:process';
|
|
21
21
|
import { COMPONENT_RULES, COMPONENT_RULE_FIX, checkComponent, discoverComponents, formatReport } from './check-component.mjs';
|
|
22
22
|
import { PAGE_RULES, checkPages, discoverPages } from './check-page.mjs';
|
|
23
|
+
import { resolvePageTestTargets } from './lib/pageRoutes.mjs';
|
|
23
24
|
import { describeComponents, describeTokens, formatComponents, formatTokens } from './lib/catalogue.mjs';
|
|
24
25
|
import { buildReport, formatReport as formatProjectReport } from './lib/report.mjs';
|
|
25
26
|
import { loadVocabulary } from './lib/tokenVocabulary.mjs';
|
|
@@ -74,10 +75,20 @@ Commands:
|
|
|
74
75
|
@playwright/test, vitest, and happy-dom; a
|
|
75
76
|
missing one is a tests-not-installed finding
|
|
76
77
|
naming the install command
|
|
77
|
-
check-page [paths...]
|
|
78
|
+
check-page [paths...] [--tests]
|
|
79
|
+
Validate pages against the live-tokens-create-page
|
|
78
80
|
contract: catalogue components only, and every CSS
|
|
79
81
|
value a design token. Checks every page under src/
|
|
80
|
-
when given no paths.
|
|
82
|
+
when given no paths. --tests also opens each
|
|
83
|
+
page's own route in the consumer's own app and
|
|
84
|
+
proves, per shipped rule id, that the cascade
|
|
85
|
+
painted every component from its semantic
|
|
86
|
+
properties, every run of text sits in one shipped
|
|
87
|
+
text style, every text/surface pair meets AA,
|
|
88
|
+
sections sit on the page grid, and nothing
|
|
89
|
+
overflows. Needs @playwright/test, vitest, and
|
|
90
|
+
happy-dom; a missing one is a tests-not-installed
|
|
91
|
+
finding naming the install command
|
|
81
92
|
|
|
82
93
|
check-component and check-page also accept:
|
|
83
94
|
--json Machine-readable findings, for a skill to iterate
|
|
@@ -270,7 +281,18 @@ if (command === 'check-page') {
|
|
|
270
281
|
const opts = parseCheckFlags(rest);
|
|
271
282
|
const targets = opts.rest.length > 0 ? opts.rest : discoverPages(process.cwd());
|
|
272
283
|
const { findings, checked } = checkPages(targets, { root: process.cwd() });
|
|
273
|
-
|
|
284
|
+
if (!opts.tests) {
|
|
285
|
+
reportChecks('check-page', findings, checked, PAGE_RULES, opts);
|
|
286
|
+
}
|
|
287
|
+
const { hasHardFailure, runPageTests } = await import('./contractRunner.mjs');
|
|
288
|
+
const pageTargets = resolvePageTestTargets(opts.rest, process.cwd());
|
|
289
|
+
const testOutcome = await runPageTests(pageTargets, { root: process.cwd() });
|
|
290
|
+
const label = 'check-page --tests';
|
|
291
|
+
const allFindings = [...findings, ...testOutcome.findings];
|
|
292
|
+
reportChecks(label, allFindings, Math.max(checked, pageTargets.length), PAGE_RULES, opts, {
|
|
293
|
+
coverage: testOutcome.coverage,
|
|
294
|
+
hardFailure: hasHardFailure(testOutcome.findings),
|
|
295
|
+
});
|
|
274
296
|
}
|
|
275
297
|
|
|
276
298
|
if (command === 'set-colors') {
|