@parity-app/cli 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.mjs +255 -34
- package/dist/{client-Cf66-n3R.mjs → client-CoW-KcnT.mjs} +130 -141
- package/dist/index.d.mts +98 -7
- package/dist/index.mjs +1 -1
- package/dist/schema-BpGSCLEu.mjs +113 -0
- package/dist/{src-y3hdgbK2.mjs → src-5CrSA_RX.mjs} +1 -1
- package/dist/{src-CYEKdXm_.mjs → src-C4faIpux.mjs} +142 -3
- package/dist/{src-CJUvccJO.mjs → src-Cr8pfFJE.mjs} +1 -1
- package/dist/{src-DaCYZuwb.mjs → src-Zm_PYFFH.mjs} +10 -5
- package/dist/src-p0vFxRBz.mjs +3 -0
- package/package.json +3 -2
- package/skills/SKILL.md +3 -2
- package/skills/execute.md +136 -7
- package/skills/guide/checking.md +6 -3
- package/skills/guide/components.md +3 -1
- package/skills/guide/states-and-modes.md +14 -0
- package/dist/src-DgI96EpD.mjs +0 -3
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import "./schema-BpGSCLEu.mjs";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
// ../core/src/adapters.ts
|
|
4
|
+
const packageVersions = z.record(z.string(), z.string());
|
|
5
|
+
z.looseObject({
|
|
6
|
+
name: z.string().optional(),
|
|
7
|
+
dependencies: packageVersions.optional(),
|
|
8
|
+
devDependencies: packageVersions.optional(),
|
|
9
|
+
scripts: z.record(z.string(), z.string()).optional()
|
|
10
|
+
});
|
|
1
11
|
// ../core/src/devices.ts
|
|
2
12
|
const deviceKinds = [
|
|
3
13
|
"phone",
|
|
@@ -254,6 +264,53 @@ function generateId(taken, length = 5) {
|
|
|
254
264
|
}
|
|
255
265
|
}
|
|
256
266
|
// ../core/src/document.ts
|
|
267
|
+
const canvasObjectKinds = [
|
|
268
|
+
"table",
|
|
269
|
+
"operation",
|
|
270
|
+
"route"
|
|
271
|
+
];
|
|
272
|
+
const linkEndKinds = [
|
|
273
|
+
"screen",
|
|
274
|
+
"component",
|
|
275
|
+
...canvasObjectKinds
|
|
276
|
+
];
|
|
277
|
+
/** Each type of link, and the kinds it may join, from and to. */
|
|
278
|
+
const linkTypes = {
|
|
279
|
+
/** A screen shown at a route's URL. */
|
|
280
|
+
shows: {
|
|
281
|
+
from: ["screen"],
|
|
282
|
+
to: ["route"]
|
|
283
|
+
},
|
|
284
|
+
/** A screen, route or component that calls an operation. */
|
|
285
|
+
calls: {
|
|
286
|
+
from: [
|
|
287
|
+
"screen",
|
|
288
|
+
"route",
|
|
289
|
+
"component"
|
|
290
|
+
],
|
|
291
|
+
to: ["operation"]
|
|
292
|
+
},
|
|
293
|
+
/** An operation that reads a table. */
|
|
294
|
+
reads: {
|
|
295
|
+
from: ["operation"],
|
|
296
|
+
to: ["table"]
|
|
297
|
+
},
|
|
298
|
+
/** An operation that writes to a table. */
|
|
299
|
+
writes: {
|
|
300
|
+
from: ["operation"],
|
|
301
|
+
to: ["table"]
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
/** Whether a link of this type may join these two kinds. */
|
|
305
|
+
function linkAllowed(type, from, to) {
|
|
306
|
+
const allowed = linkTypes[type];
|
|
307
|
+
return allowed.from.includes(from) && allowed.to.includes(to);
|
|
308
|
+
}
|
|
309
|
+
const lenses = [
|
|
310
|
+
"data",
|
|
311
|
+
"api",
|
|
312
|
+
"routes"
|
|
313
|
+
];
|
|
257
314
|
const containerKinds = [
|
|
258
315
|
"screen",
|
|
259
316
|
"component",
|
|
@@ -350,16 +407,97 @@ function parseCanvasDocument(value) {
|
|
|
350
407
|
"containers",
|
|
351
408
|
"notes",
|
|
352
409
|
"modes",
|
|
353
|
-
"detection"
|
|
410
|
+
"detection",
|
|
411
|
+
"objects",
|
|
412
|
+
"links",
|
|
413
|
+
"sources"
|
|
354
414
|
], "");
|
|
355
415
|
return compact({
|
|
356
416
|
version: 3,
|
|
357
417
|
containers: expectArray(document.containers, "/containers").map((item, index) => parseContainer(item, `/containers/${index}`)),
|
|
358
418
|
modes: optional(document.modes, (value) => parseModeSettings(value)),
|
|
359
419
|
notes: optional(document.notes, (value) => expectArray(value, "/notes").map((item, index) => parseNote(item, `/notes/${index}`))),
|
|
360
|
-
detection: optional(document.detection, (value) => parseDetection(value, "/detection"))
|
|
420
|
+
detection: optional(document.detection, (value) => parseDetection(value, "/detection")),
|
|
421
|
+
objects: optional(document.objects, (value) => parseObjects(value, "/objects")),
|
|
422
|
+
links: optional(document.links, (value) => parseLinks(value, "/links")),
|
|
423
|
+
sources: optional(document.sources, (value) => parseSources(value, "/sources"))
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
function parsePosition(value, path) {
|
|
427
|
+
const position = expectObject(value, path);
|
|
428
|
+
expectKnownKeys(position, ["x", "y"], path);
|
|
429
|
+
return {
|
|
430
|
+
x: expectNumber(position.x, `${path}/x`),
|
|
431
|
+
y: expectNumber(position.y, `${path}/y`)
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
function parseObjects(value, path) {
|
|
435
|
+
const placed = /* @__PURE__ */ new Set();
|
|
436
|
+
return expectArray(value, path).map((item, index) => {
|
|
437
|
+
const at = `${path}/${index}`;
|
|
438
|
+
const object = expectObject(item, at);
|
|
439
|
+
expectKnownKeys(object, [
|
|
440
|
+
"kind",
|
|
441
|
+
"ref",
|
|
442
|
+
"position"
|
|
443
|
+
], at);
|
|
444
|
+
const kind = expectOneOf(object.kind, canvasObjectKinds, `${at}/kind`);
|
|
445
|
+
const ref = expectString(object.ref, `${at}/ref`);
|
|
446
|
+
const key = JSON.stringify([kind, ref]);
|
|
447
|
+
if (placed.has(key)) throw new TypeError(`${at}: the ${kind} "${ref}" is already on the canvas`);
|
|
448
|
+
placed.add(key);
|
|
449
|
+
return {
|
|
450
|
+
kind,
|
|
451
|
+
ref,
|
|
452
|
+
position: parsePosition(object.position, `${at}/position`)
|
|
453
|
+
};
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
function parseLinkEnd(value, path) {
|
|
457
|
+
const end = expectObject(value, path);
|
|
458
|
+
expectKnownKeys(end, ["kind", "ref"], path);
|
|
459
|
+
return {
|
|
460
|
+
kind: expectOneOf(end.kind, linkEndKinds, `${path}/kind`),
|
|
461
|
+
ref: expectString(end.ref, `${path}/ref`)
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
function parseLinks(value, path) {
|
|
465
|
+
const ids = /* @__PURE__ */ new Set();
|
|
466
|
+
const types = Object.keys(linkTypes);
|
|
467
|
+
return expectArray(value, path).map((item, index) => {
|
|
468
|
+
const at = `${path}/${index}`;
|
|
469
|
+
const link = expectObject(item, at);
|
|
470
|
+
expectKnownKeys(link, [
|
|
471
|
+
"id",
|
|
472
|
+
"type",
|
|
473
|
+
"from",
|
|
474
|
+
"to"
|
|
475
|
+
], at);
|
|
476
|
+
const id = expectString(link.id, `${at}/id`);
|
|
477
|
+
if (ids.has(id)) throw new TypeError(`${at}/id: duplicate link id "${id}"`);
|
|
478
|
+
ids.add(id);
|
|
479
|
+
const type = expectOneOf(link.type, types, `${at}/type`);
|
|
480
|
+
const from = parseLinkEnd(link.from, `${at}/from`);
|
|
481
|
+
const to = parseLinkEnd(link.to, `${at}/to`);
|
|
482
|
+
if (!linkAllowed(type, from.kind, to.kind)) {
|
|
483
|
+
const { from: froms, to: tos } = linkTypes[type];
|
|
484
|
+
throw new TypeError(`${at}: a ${type} link goes from ${froms.join(" or ")} to ${tos.join(" or ")}, not ${from.kind} to ${to.kind}`);
|
|
485
|
+
}
|
|
486
|
+
return {
|
|
487
|
+
id,
|
|
488
|
+
type,
|
|
489
|
+
from,
|
|
490
|
+
to
|
|
491
|
+
};
|
|
361
492
|
});
|
|
362
493
|
}
|
|
494
|
+
function parseSources(value, path) {
|
|
495
|
+
const sources = expectObject(value, path);
|
|
496
|
+
expectKnownKeys(sources, lenses, path);
|
|
497
|
+
const chosen = {};
|
|
498
|
+
for (const lens of lenses) if (sources[lens] !== void 0) chosen[lens] = expectString(sources[lens], `${path}/${lens}`);
|
|
499
|
+
return chosen;
|
|
500
|
+
}
|
|
363
501
|
function parseDetection(value, path) {
|
|
364
502
|
const detection = expectObject(value, path);
|
|
365
503
|
expectKnownKeys(detection, ["include", "exclude"], path);
|
|
@@ -1386,6 +1524,7 @@ function librarySource(library, name, component, path) {
|
|
|
1386
1524
|
library,
|
|
1387
1525
|
...component.group ? { group: component.group } : {},
|
|
1388
1526
|
...component.parents ? { parents: component.parents.map((parent) => `${library}/${parent}`) } : {},
|
|
1527
|
+
...component.parents && component.parentsFrom ? { parentsFrom: component.parentsFrom } : {},
|
|
1389
1528
|
...component.internal ? { internal: true } : {},
|
|
1390
1529
|
...component.visual === false ? { visual: false } : {},
|
|
1391
1530
|
...component.optionalParents ? { optionalParents: component.optionalParents.map((parent) => `${library}/${parent}`) } : {},
|
|
@@ -1425,4 +1564,4 @@ function sampleIds(sample, library) {
|
|
|
1425
1564
|
...sample.children ? { children: sample.children.map(child) } : {}
|
|
1426
1565
|
};
|
|
1427
1566
|
}
|
|
1428
|
-
export {
|
|
1567
|
+
export { deviceKinds as A, parseCanvasDocument as C, collectCustomProperties as D, parseModeSettings as E, viewportHeightProperty as O, paramTypes as S, generateId as T, imageFits as _, applyLibraryEdits as a, linkEndKinds as b, isSampleElement as c, parentSignals as d, parseLibraryEdits as f, containerKinds as g, canvasObjectKinds as h, withLibraryContext as i, lengthToPixels as j, viewportWidthProperty as k, isSampleRender as l, renderSlotOf as m, librarySource as n, componentEvidence as o, parseLibraryManifest as p, sampleIds as r, isMadeValue as s, componentNode as t, libraryManifestExtension as u, lenses as v, styleKeys as w, linkTypes as x, linkAllowed as y };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as covers, n as registryDir, r as registryEntries } from "./src-
|
|
1
|
+
export { t as covers, n as registryDir, r as registryEntries } from "./src-5CrSA_RX.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "./src-
|
|
1
|
+
import { D as collectCustomProperties, a as applyLibraryEdits, c as isSampleElement, f as parseLibraryEdits, j as lengthToPixels, l as isSampleRender, m as renderSlotOf, p as parseLibraryManifest, s as isMadeValue, u as libraryManifestExtension } from "./src-C4faIpux.mjs";
|
|
2
|
+
import "./src-5CrSA_RX.mjs";
|
|
3
3
|
import { a as publishedVersions, n as packageFile, o as repositoryFiles, r as publishedSource } from "./published-CdXoUgfD.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { mkdir, readFile, readdir, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
@@ -601,11 +601,12 @@ function listTheme(selector) {
|
|
|
601
601
|
const [only] = names;
|
|
602
602
|
return names.size === 1 ? only : void 0;
|
|
603
603
|
}
|
|
604
|
-
const simpleSelector = /^(?:(?<root>html|:root|:host)(?![\w(-])|\.(?<className>[\w-]+)|\[\s*[\w-]
|
|
604
|
+
const simpleSelector = /^(?:(?<root>html|:root|:host)(?![\w(-])|\.(?<className>[\w-]+)|\[\s*(?<attribute>[\w-]+)\s*(?:=\s*(?:"(?<double>[^"]*)"|'(?<single>[^']*)'|(?<bare>[\w-]+))\s*(?:[is]\s*)?)?\]|(?<functional>:(?:not|where|is|host)\())/;
|
|
605
605
|
/** The theme one compound selector (no combinators) names, or undefined when it isn't a theme's. */
|
|
606
606
|
function compoundTheme(part) {
|
|
607
607
|
let root = false;
|
|
608
608
|
const names = [];
|
|
609
|
+
let attribute;
|
|
609
610
|
let rest = part;
|
|
610
611
|
while (rest.length > 0) {
|
|
611
612
|
const match = simpleSelector.exec(rest);
|
|
@@ -623,11 +624,14 @@ function compoundTheme(part) {
|
|
|
623
624
|
if (!nested) return void 0;
|
|
624
625
|
root ||= nested.root;
|
|
625
626
|
if (nested.name !== void 0) names.push(nested.name);
|
|
627
|
+
attribute ??= nested.attribute;
|
|
626
628
|
continue;
|
|
627
629
|
}
|
|
628
630
|
rest = rest.slice(match[0].length);
|
|
629
631
|
if (groups.root) root = true;
|
|
630
|
-
const
|
|
632
|
+
const value = groups.double ?? groups.single ?? groups.bare;
|
|
633
|
+
if (value !== void 0) attribute = groups.attribute;
|
|
634
|
+
const name = groups.className ?? value;
|
|
631
635
|
if (name !== void 0) names.push(name);
|
|
632
636
|
}
|
|
633
637
|
if (names.length > 1) return void 0;
|
|
@@ -635,7 +639,8 @@ function compoundTheme(part) {
|
|
|
635
639
|
if (!root && names.length === 0) return void 0;
|
|
636
640
|
return {
|
|
637
641
|
root: root || names[0] === "light",
|
|
638
|
-
...name === void 0 ? {} : { name }
|
|
642
|
+
...name === void 0 ? {} : { name },
|
|
643
|
+
...name !== void 0 && attribute !== void 0 ? { attribute } : {}
|
|
639
644
|
};
|
|
640
645
|
}
|
|
641
646
|
function matchingParen(text, open) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parity-app/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Drive the Parity app from a terminal or an agent: read state, run edits, take screenshots, serve MCP.",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"semver": "^7.8.5",
|
|
47
|
-
"typescript": "6.0.3"
|
|
47
|
+
"typescript": "6.0.3",
|
|
48
|
+
"zod": "4.6.7"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@parity-app/agent": "0.0.0",
|
package/skills/SKILL.md
CHANGED
|
@@ -39,7 +39,7 @@ project, pass `tab: "active"` (`--tab active`) and read `get_app_state` for the
|
|
|
39
39
|
| `get_console` | `parity console` | What the project's own code wrote to the console, on the canvas and offscreen. |
|
|
40
40
|
| `get_tokens` | `parity tokens` | Tokens filtered by category, source or name, with values in every theme. |
|
|
41
41
|
| `get_components` | `parity components` | Components, libraries, icon sets and exports left out or failing to render. |
|
|
42
|
-
| `get_props` | `parity props <Component>` | Props from source; `all`
|
|
42
|
+
| `get_props` | `parity props <Component>` | Props from source, then what it goes inside and holds, its slots and an example to insert; `all` adds passed-through attributes. |
|
|
43
43
|
| `get_tabs` | `parity tabs` | The open projects. |
|
|
44
44
|
| `open_project` | `parity open <folder>` | Opens a project in a new tab. |
|
|
45
45
|
| `read_skill` | `parity skill [file]` | This skill. |
|
|
@@ -97,7 +97,8 @@ keeping the designs there, so ask before running that in someone's project.
|
|
|
97
97
|
them with `AddState`, as changes to the base design, not as copies.
|
|
98
98
|
5. **Check every frame.** Screenshot the design in all its states and the modes it shows (`state: "all"`,
|
|
99
99
|
`mode: "shown"`). Run `find_problems` and `check_accessibility` too, and read the warnings `execute` returns: they're
|
|
100
|
-
usually a token that doesn't exist or a prop the component doesn't take.
|
|
100
|
+
usually a token that doesn't exist or a prop the component doesn't take. A render failure comes first and means a
|
|
101
|
+
frame wasn't checked. Fix what you find, then check again.
|
|
101
102
|
6. **Say what you did.** Tell the person what changed and what you couldn't verify. Ask them to try anything interactive
|
|
102
103
|
themselves with the Interact tool (I), which hands their mouse and keyboard to the real components.
|
|
103
104
|
|
package/skills/execute.md
CHANGED
|
@@ -29,6 +29,20 @@ held.
|
|
|
29
29
|
patches with a nonempty `find`, string `replace` (which can be empty), and optional boolean `all`. Invalid requests
|
|
30
30
|
are rejected before reaching the app.
|
|
31
31
|
|
|
32
|
+
### Warnings
|
|
33
|
+
|
|
34
|
+
A call that applies can still return warnings. Each finding comes once, with every place it was found. The text puts
|
|
35
|
+
them under "Render failures", "Warnings" and "Suggestions". The structured result's `warningGroups` gives each one's
|
|
36
|
+
`category`, `severity` (`error`, `warning` or `suggestion`), `message`, `count` and `at`, which lists the places as
|
|
37
|
+
`design/node` paths or frames. A render failure means a screenshot or check is missing for that frame, so look into it
|
|
38
|
+
first. `unnamed_layout` is a suggestion for layouts this call added: give each one a name with `Update`.
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
// Unnamed rows come back as one suggestion, such as 12 places (team-members/k3x9a, … and 7 more). Name them:
|
|
42
|
+
const rows = Get("team-members", (node, at) => (node.type === "layout" && !node.name ? at.path : undefined))
|
|
43
|
+
rows.forEach((path, index) => Update(path, { name: `Member row ${index + 1}` }))
|
|
44
|
+
```
|
|
45
|
+
|
|
32
46
|
## API
|
|
33
47
|
|
|
34
48
|
```ts
|
|
@@ -75,7 +89,7 @@ SetTokens({ name: value | { theme: value } }): void
|
|
|
75
89
|
|
|
76
90
|
// Checking
|
|
77
91
|
Screenshot(design, { state?, mode?, scale? }?): number
|
|
78
|
-
Measure(design, { state?, mode? }?): Layout
|
|
92
|
+
Measure(design, { state?, mode? }?): Layout // one frame: one state id, one mode id
|
|
79
93
|
Problems(design, { state?, mode? }?): Problem[]
|
|
80
94
|
Accessibility(design, { state?, mode? }?): Finding[]
|
|
81
95
|
|
|
@@ -91,6 +105,9 @@ A design is named by its id or its name. A node is named by `design/node`, where
|
|
|
91
105
|
by its id alone when only one design has that id. Names aren't unique, so prefer ids once you have them; `Insert`,
|
|
92
106
|
`Copy` and `CreateDesign` return them, and nodes you create with a `name` are listed under "Created" in the result.
|
|
93
107
|
|
|
108
|
+
A state is named by its id only, as `Designs()` lists it and `AddState` returns it. A state's name in its place fails
|
|
109
|
+
with `unknown_state`, and the error names the id it belongs to.
|
|
110
|
+
|
|
94
111
|
## Reading
|
|
95
112
|
|
|
96
113
|
### Designs
|
|
@@ -131,8 +148,58 @@ Print(Get((node, at) => (node.type === "component" && node.component === "Button
|
|
|
131
148
|
### Components and Props
|
|
132
149
|
|
|
133
150
|
`Components()` is everything a `component` node can name. `Props("Button")` returns the props found in the component's
|
|
134
|
-
source: variants with their options (`variant`, `size`), its own props,
|
|
135
|
-
|
|
151
|
+
source: variants with their options (`variant`, `size`), its own props, including those a library's component adds to
|
|
152
|
+
its element's (Nova's `isActive`), and with `{ all: true }` the attributes it passes to its element. A prop that isn't
|
|
153
|
+
listed, or a variant value outside its options, comes back as a warning.
|
|
154
|
+
|
|
155
|
+
Its `composition` says what it needs around it and holds, before you insert it:
|
|
156
|
+
|
|
157
|
+
- `role` is `provider` for one that draws nothing and gives the parts inside it their context (`SidebarProvider`),
|
|
158
|
+
`wrapper` for one other parts go inside (`Sidebar`), and `component` otherwise.
|
|
159
|
+
- `parents` is `listed`, with the `chain` it goes inside, outermost first, and where its library learned that; `none`;
|
|
160
|
+
or `unknown`, with the `reason`, such as a project component that hasn't drawn yet or an error asking for a provider.
|
|
161
|
+
An `optional` parent is left out when the ones outside it are already there.
|
|
162
|
+
- `holds` lists the parts that go directly inside it, and `slots` the `key` each slot's layers go under.
|
|
163
|
+
- `example.node` is the component inside its parents with its sample, as the Assets panel inserts it and the Components
|
|
164
|
+
panel draws it. `example.status` is `drawn` once the component has drawn on the canvas, `failing` with a `reason`, or
|
|
165
|
+
`untried`.
|
|
166
|
+
|
|
167
|
+
```js
|
|
168
|
+
// A sidebar goes inside its provider: insert the example, then fill it in.
|
|
169
|
+
const { composition } = Props("nova/Sidebar")
|
|
170
|
+
Print(composition.parents.chain.map((parent) => parent.component))
|
|
171
|
+
const sidebar = Insert("settings/Content", composition.example.node)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Writing the layers yourself works the same way. A provider draws nothing of its own, so the Components panel leaves it
|
|
175
|
+
out, but `Components()` lists it, since the parts inside it throw without it. A part placed without its provider shows
|
|
176
|
+
in `Problems` as a render error that names it.
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
// A Nova sidebar, inside the provider its parts need.
|
|
180
|
+
const part = (component, props, children) => ({ type: "component", component, props, slots: { children } })
|
|
181
|
+
const item = (label, isActive) =>
|
|
182
|
+
part("nova/SidebarMenuItem", {}, [
|
|
183
|
+
{ type: "component", component: "nova/SidebarMenuButton", props: { isActive, children: label } }
|
|
184
|
+
])
|
|
185
|
+
CreateDesign({
|
|
186
|
+
name: "App sidebar",
|
|
187
|
+
root: {
|
|
188
|
+
type: "layout",
|
|
189
|
+
name: "Frame",
|
|
190
|
+
style: { width: 280, height: 400 },
|
|
191
|
+
children: [
|
|
192
|
+
part("nova/SidebarProvider", {}, [
|
|
193
|
+
part("nova/Sidebar", { collapsible: "none" }, [
|
|
194
|
+
part("nova/SidebarContent", {}, [
|
|
195
|
+
part("nova/SidebarMenu", {}, [item("Dashboard", true), item("Members", false)])
|
|
196
|
+
])
|
|
197
|
+
])
|
|
198
|
+
])
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
```
|
|
136
203
|
|
|
137
204
|
### Icons
|
|
138
205
|
|
|
@@ -158,8 +225,15 @@ Insert("dashboard/Toolbar", {
|
|
|
158
225
|
|
|
159
226
|
Each token has a `name`, a `category` (`color`, `font-size`, `radius`, `shadow`, `spacing`, `font`, `font-weight`,
|
|
160
227
|
`leading`, `tracking` and others), a `source` (`semantic` for the project's own variables, `theme` for its styling
|
|
161
|
-
framework's scale) and its `values` per theme. Filter with
|
|
162
|
-
`GetTokens({ search: "radius" })`.
|
|
228
|
+
framework's scale, `keyword` for a framework keyword the style fields also take) and its `values` per theme. Filter with
|
|
229
|
+
`GetTokens({ category: "color", source: "semantic" })` or `GetTokens({ search: "radius" })`.
|
|
230
|
+
|
|
231
|
+
For a circle or a pill, use `radius-full`, Tailwind's `rounded-full`. It's listed under `radius` with
|
|
232
|
+
`source: "keyword"`, and frames draw it as round as the box allows, at any size:
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
Update("avatar", { style: { width: 10, height: 10, radius: "radius-full" } })
|
|
236
|
+
```
|
|
163
237
|
|
|
164
238
|
### Selection
|
|
165
239
|
|
|
@@ -235,6 +309,25 @@ removes the keys as `null` does.
|
|
|
235
309
|
its design; to move it to another design, `Copy` it there and `Delete` the original. `Copy` gives the copy fresh ids and
|
|
236
310
|
returns the new id. A design's root can't be deleted; use `Replace`.
|
|
237
311
|
|
|
312
|
+
The new node and everything in it get fresh ids, so `Replace` throws away the patches on the old node and what was
|
|
313
|
+
inside it: its `Responsive` patches, its changes in every state and its variant options' patches. Replacing a design's
|
|
314
|
+
root clears every state's changes. To keep a node's patches, change it with `Update` instead. The result lists what went
|
|
315
|
+
under "Discarded by Replace" (`discarded` in structured output), with each node's viewports, states and variants:
|
|
316
|
+
|
|
317
|
+
```text
|
|
318
|
+
Discarded by Replace (reapply what you still want to the new node):
|
|
319
|
+
dashboard/stats, now k3x9a:
|
|
320
|
+
Stats (stats): viewports mobile
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Put back the ones you still want on the new ids, in the same call or the next. A state's changes go back with
|
|
324
|
+
`UpdateState(design, state, { changes: { [id]: … } })`.
|
|
325
|
+
|
|
326
|
+
```js
|
|
327
|
+
const stats = Replace("dashboard/Stats", { type: "layout", name: "Stats", style: { direction: "row", gap: 16 } })
|
|
328
|
+
Responsive(stats, "mobile", { style: { direction: "column" } })
|
|
329
|
+
```
|
|
330
|
+
|
|
238
331
|
### Responsive
|
|
239
332
|
|
|
240
333
|
Patches a node in one viewport: `Responsive("dashboard/Stats", "mobile", { style: { columns: 1 } })`. The viewport is a
|
|
@@ -320,8 +413,44 @@ Frames draw as the canvas does: instances of a library's component designs (from
|
|
|
320
413
|
token values not yet saved show, the person's edits and this call's own `SetTokens` alike, so
|
|
321
414
|
`SetTokens({ primary: "red" }); await Screenshot(id)` shows the new value in the same call.
|
|
322
415
|
|
|
323
|
-
`Measure` lays out one frame and returns its size, whether it overflows sideways, and every node's box
|
|
324
|
-
|
|
416
|
+
`Measure` lays out one frame and returns its state, mode and size, whether it overflows sideways, and every node's box
|
|
417
|
+
with the node it's drawn inside (`parent`); a node an ancestor cuts off has `clipped` and that ancestor's box in `clip`.
|
|
418
|
+
`Problems` checks frames for render errors, overflow, clipping and unknown components. Each layout problem has its
|
|
419
|
+
`kind`: `frame-overflow` for a node that sticks out of the frame, by its position or its width, or `clipped` for one an
|
|
420
|
+
ancestor that hides overflow cuts off. It names the `node`, its `box`, the `container` it overflows (the clipping node,
|
|
421
|
+
or the frame, which has no `node`) with that box, and the pixels past each edge in `overflow`. A node past a parent that
|
|
422
|
+
doesn't clip, inside the frame, shows, so it isn't a problem; children going no further than a reported parent are left
|
|
423
|
+
out.
|
|
424
|
+
|
|
425
|
+
```js
|
|
426
|
+
const id = CreateDesign({ name: "Checkout" })
|
|
427
|
+
Insert(id, { type: "component", component: "Button", name: "Pay", props: { children: "Pay" } })
|
|
428
|
+
// Each one, such as { kind: "frame-overflow", node: "pay", name: "Pay", box: { x: 340, y: 100, width: 60, height: 36 },
|
|
429
|
+
// container: { box: { x: 0, y: 0, width: 390, height: 600 } }, overflow: { right: 10 }, problem: "at x 340, …" }
|
|
430
|
+
for (const problem of await Problems(id, { mode: "shown" })) Print(problem.kind, problem.name, problem.overflow)
|
|
431
|
+
const layout = await Measure(id)
|
|
432
|
+
Print(
|
|
433
|
+
layout.state,
|
|
434
|
+
layout.mode,
|
|
435
|
+
layout.nodes.map((node) => `${node.id} in ${node.parent}`)
|
|
436
|
+
)
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
`Measure` takes one state id and one mode id, because it returns one frame's layout. A list, `"all"` or `"shown"` fails
|
|
440
|
+
with `invalid_input`, naming the ids it takes. To measure several frames, call it once for each; every layout says which
|
|
441
|
+
state and mode it drew:
|
|
442
|
+
|
|
443
|
+
```js
|
|
444
|
+
const signup = Designs().find((design) => design.id === "signup")
|
|
445
|
+
const layouts = []
|
|
446
|
+
for (const state of signup.states) {
|
|
447
|
+
for (const mode of [signup.showing.mode, ...signup.showing.beside]) {
|
|
448
|
+
layouts.push(await Measure("signup", { state: state.id, mode }))
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
for (const layout of layouts) Print(`${layout.state} in ${layout.mode}: ${layout.width} × ${layout.height}`)
|
|
452
|
+
```
|
|
453
|
+
|
|
325
454
|
`Accessibility` returns axe-core's findings per node and frame, with the rule, its impact and what's wrong.
|
|
326
455
|
|
|
327
456
|
These frames are drawn in the same window as the canvas's: as wide as the frame, and as tall as the design's fixed
|
package/skills/guide/checking.md
CHANGED
|
@@ -15,11 +15,14 @@ small details.
|
|
|
15
15
|
|
|
16
16
|
- **Render errors.** A component threw. Usually a prop with the wrong type, or a part used outside its parent (a
|
|
17
17
|
`TabsTrigger` without `Tabs`). `parity components` lists the components failing anywhere on the canvas, with why.
|
|
18
|
-
- **Overflow.**
|
|
19
|
-
|
|
18
|
+
- **Overflow.** A node that sticks out of the frame, by its width or where it's placed, usually a row that needs to wrap
|
|
19
|
+
or stack on mobile. The finding gives the node, its box, the frame's, and how many pixels past which edge.
|
|
20
|
+
- **Clipping.** A node cut off by an ancestor with `overflow: hidden` or a fixed size, named in the finding with its
|
|
21
|
+
box. A node that only spills past a parent that doesn't clip still shows, so it isn't listed.
|
|
20
22
|
- **Unknown components.** A name that isn't in `Components()`; it draws as a placeholder.
|
|
21
23
|
|
|
22
|
-
`Measure` gives every node's box in one frame, for checking alignment or sizes by numbers.
|
|
24
|
+
`Measure` gives every node's box in one frame, for checking alignment or sizes by numbers. It takes one state and one
|
|
25
|
+
mode; to compare frames, call it for each, as the execute skill shows.
|
|
23
26
|
|
|
24
27
|
## Accessibility
|
|
25
28
|
|
|
@@ -17,7 +17,9 @@ instance follows.
|
|
|
17
17
|
- Read its props with `Props("Badge")`. Variants and sizes come with their options; use those values exactly. A prop the
|
|
18
18
|
component doesn't declare comes back as a warning.
|
|
19
19
|
- Compose the parts the way the library intends: `Tabs` holds a `TabsList` of `TabsTrigger`s and one `TabsContent` per
|
|
20
|
-
trigger; `Table` holds `TableHeader` and `TableBody`, each with `TableRow`s of cells.
|
|
20
|
+
trigger; `Table` holds `TableHeader` and `TableBody`, each with `TableRow`s of cells. `Props("nova/Sidebar")` says
|
|
21
|
+
what a part goes inside in `composition.parents`, here the `SidebarProvider` it needs, and what goes inside it in
|
|
22
|
+
`composition.holds`. Start from `composition.example.node`, which already has its parents, when they aren't there.
|
|
21
23
|
- Put children where they go. `slots.children` is what goes between the component's tags. Other slots, when a component
|
|
22
24
|
takes nodes as props, are named after the prop.
|
|
23
25
|
|
|
@@ -22,6 +22,20 @@ only its changes, so editing the base updates every state.
|
|
|
22
22
|
|
|
23
23
|
Name states for the situation ("No members yet", "Invite failed"), not the change ("Table hidden").
|
|
24
24
|
|
|
25
|
+
Every call that takes a state takes its id, never its name: `UpdateState`, `DeleteState`, `Update`'s and `Get`'s
|
|
26
|
+
`state`, `UpdateDesign`'s `state` and the checks. `AddState` returns the id, and `Designs()` lists each design's states
|
|
27
|
+
with their ids and names. Names can repeat and change, so an id made from a name stops matching it after a rename. A
|
|
28
|
+
name passed by mistake fails with `unknown_state`, naming the id it belongs to, or every id when states share the name.
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
AddState("team-members", { name: "No members yet", changes: { "Members table": { hidden: true } } })
|
|
32
|
+
// Later, find a state's id by its name in Designs(), then pass the id.
|
|
33
|
+
const states = Designs().find((design) => design.id === "team-members").states // [{ id, name }]
|
|
34
|
+
const empty = states.find((state) => state.name === "No members yet").id
|
|
35
|
+
UpdateState("team-members", empty, { name: "No members" })
|
|
36
|
+
await Screenshot("team-members", { state: empty })
|
|
37
|
+
```
|
|
38
|
+
|
|
25
39
|
## Modes
|
|
26
40
|
|
|
27
41
|
Modes are the project's, shared by every design. Each mode picks one option of each variable:
|
package/dist/src-DgI96EpD.mjs
DELETED