@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.
@@ -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 { viewportHeightProperty as C, lengthToPixels as E, collectCustomProperties as S, deviceKinds as T, paramTypes as _, applyLibraryEdits as a, generateId as b, isSampleElement as c, parentSignals as d, parseLibraryEdits as f, imageFits as g, containerKinds as h, withLibraryContext as i, 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, parseCanvasDocument as v, viewportWidthProperty as w, parseModeSettings as x, styleKeys as y };
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-y3hdgbK2.mjs";
1
+ export { t as covers, n as registryDir, r as registryEntries } from "./src-5CrSA_RX.mjs";
@@ -1,5 +1,5 @@
1
- import { E as lengthToPixels, S as collectCustomProperties, a as applyLibraryEdits, c as isSampleElement, f as parseLibraryEdits, l as isSampleRender, m as renderSlotOf, p as parseLibraryManifest, s as isMadeValue, u as libraryManifestExtension } from "./src-CYEKdXm_.mjs";
2
- import "./src-y3hdgbK2.mjs";
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-]+\s*(?:=\s*(?:"(?<double>[^"]*)"|'(?<single>[^']*)'|(?<bare>[\w-]+))\s*(?:[is]\s*)?)?\]|(?<functional>:(?:not|where|is|host)\())/;
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 name = groups.className ?? groups.double ?? groups.single ?? groups.bare;
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) {
@@ -0,0 +1,3 @@
1
+ import "./schema-BpGSCLEu.mjs";
2
+ export { a as applyLibraryEdits, t as componentNode, T as generateId, n as librarySource, f as parseLibraryEdits, i as withLibraryContext } from "./src-C4faIpux.mjs";
3
+ import "./src-Zm_PYFFH.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parity-app/cli",
3
- "version": "0.4.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` includes passed-through attributes. |
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. Fix what you find, then check again.
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, and with `{ all: true }` the attributes it
135
- passes to its element. A prop that isn't listed, or a variant value outside its options, comes back as a warning.
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 `GetTokens({ category: "color", source: "semantic" })` or
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 (`clipped` when a
324
- parent cuts it off). `Problems` checks frames for render errors, overflow, clipping and unknown components.
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
@@ -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.** Content wider than the frame, usually a row that needs to wrap or stack on mobile.
19
- - **Clipping.** A node cut off by a parent with `overflow: hidden` or a fixed size.
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:
@@ -1,3 +0,0 @@
1
- import "./client-Cf66-n3R.mjs";
2
- export { a as applyLibraryEdits, t as componentNode, b as generateId, n as librarySource, f as parseLibraryEdits, i as withLibraryContext } from "./src-CYEKdXm_.mjs";
3
- import "./src-DaCYZuwb.mjs";