@parity-app/cli 0.0.0-stage → 0.0.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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -2
  3. package/dist/bin.mjs +831 -0
  4. package/dist/client-DsTLQ2lT.mjs +286 -0
  5. package/dist/in-app.json +10 -0
  6. package/dist/index.d.mts +160 -0
  7. package/dist/index.mjs +1 -0
  8. package/dist/libraries/aria.lib.par +14516 -0
  9. package/dist/libraries/baseui.lib.par +8298 -0
  10. package/dist/libraries/buildkit-baseui.edits.json +3 -0
  11. package/dist/libraries/chakra.lib.par +539186 -0
  12. package/dist/libraries/fluent-icons.lib.par +2674 -0
  13. package/dist/libraries/fluent.lib.par +33703 -0
  14. package/dist/libraries/heroicons.lib.par +33 -0
  15. package/dist/libraries/hugeicons.lib.par +708 -0
  16. package/dist/libraries/lucide.lib.par +2123 -0
  17. package/dist/libraries/luma.lib.par +16362 -0
  18. package/dist/libraries/lyra.lib.par +16362 -0
  19. package/dist/libraries/maia.lib.par +16362 -0
  20. package/dist/libraries/mantine.lib.par +32935 -0
  21. package/dist/libraries/mira.lib.par +16362 -0
  22. package/dist/libraries/mui.lib.par +9817 -0
  23. package/dist/libraries/nova.lib.par +16363 -0
  24. package/dist/libraries/phosphor.lib.par +3060 -0
  25. package/dist/libraries/radix.lib.par +6890 -0
  26. package/dist/libraries/rhea.lib.par +16362 -0
  27. package/dist/libraries/sera.lib.par +16362 -0
  28. package/dist/libraries/tabler.lib.par +5325 -0
  29. package/dist/libraries/vega.lib.par +16362 -0
  30. package/dist/published-BtL8RM6W.mjs +1 -0
  31. package/dist/published-CdXoUgfD.mjs +200 -0
  32. package/dist/src-C8DCd2a1.mjs +1 -0
  33. package/dist/src-CSeISXoZ.mjs +1298 -0
  34. package/dist/src-WcAfq6g1.mjs +2 -0
  35. package/dist/src-rlZNC2MU.mjs +4690 -0
  36. package/dist/src-uPmBVSsB.mjs +53 -0
  37. package/package.json +50 -3
  38. package/skills/SKILL.md +96 -0
  39. package/skills/document.md +166 -0
  40. package/skills/execute.md +276 -0
  41. package/skills/guide/checking.md +56 -0
  42. package/skills/guide/components.md +48 -0
  43. package/skills/guide/design-system.md +52 -0
  44. package/skills/guide/mobile.md +32 -0
  45. package/skills/guide/states-and-modes.md +55 -0
  46. package/skills/guide/web-app.md +43 -0
@@ -0,0 +1,53 @@
1
+ import { p as parseLibraryManifest, u as libraryManifestExtension } from "./src-CSeISXoZ.mjs";
2
+ import { createRequire } from "node:module";
3
+ import { readFile, readdir } from "node:fs/promises";
4
+ import { dirname, join } from "node:path";
5
+ import { existsSync } from "node:fs";
6
+ import semver from "semver";
7
+ // ../registry/src/index.ts
8
+ const require = createRequire(import.meta.url);
9
+ /**
10
+ * The folder the registry's files are in. The published CLI bundles this module and ships the files beside it, where
11
+ * the package can't be resolved. Otherwise they're found through the package's own `package.json`, so it's the same
12
+ * whether this module runs from source or bundled into the app.
13
+ */
14
+ function registryDir() {
15
+ const bundled = join(import.meta.dirname, "libraries");
16
+ if (existsSync(bundled)) return bundled;
17
+ return join(dirname(require.resolve("@parity-app/registry/package.json")), "libraries");
18
+ }
19
+ let cache;
20
+ /** Every file in the registry, read once. One that doesn't parse is skipped with a warning. */
21
+ function registryEntries(dir = registryDir()) {
22
+ if (dir !== registryDir()) return readEntries(dir);
23
+ cache ??= readEntries(dir);
24
+ return cache;
25
+ }
26
+ async function readEntries(dir) {
27
+ const files = (await readdir(dir).catch(() => [])).filter((file) => file.endsWith(libraryManifestExtension)).sort();
28
+ const entries = [];
29
+ for (const file of files) {
30
+ const path = join(dir, file);
31
+ try {
32
+ const manifest = parseLibraryManifest(JSON.parse(await readFile(path, "utf8")));
33
+ if (!manifest.package) throw new TypeError("/package: a registry file names the package it describes");
34
+ entries.push({
35
+ file,
36
+ path,
37
+ manifest
38
+ });
39
+ } catch (error) {
40
+ console.warn(`Skipping registry file ${file}: ${error instanceof Error ? error.message : String(error)}`);
41
+ }
42
+ }
43
+ return entries;
44
+ }
45
+ /** Whether a manifest describes this version of its package. A manifest without a range covers every version. */
46
+ function covers(manifest, version) {
47
+ const range = manifest.package?.range;
48
+ if (!range) return true;
49
+ if (!version) return false;
50
+ const parsed = semver.coerce(version, { includePrerelease: true });
51
+ return parsed !== null && semver.satisfies(parsed, range, { includePrerelease: true });
52
+ }
53
+ export { registryDir as n, registryEntries as r, covers as t };
package/package.json CHANGED
@@ -1,6 +1,53 @@
1
1
  {
2
2
  "name": "@parity-app/cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "description": "Drive the Parity app from a terminal or an agent: read state, run edits, take screenshots, serve MCP.",
7
+ "keywords": [
8
+ "parity",
9
+ "design",
10
+ "components",
11
+ "mcp",
12
+ "cli"
13
+ ],
14
+ "homepage": "https://github.com/betterapps-hq/parity/tree/main/packages/cli",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/betterapps-hq/parity.git",
18
+ "directory": "packages/cli"
19
+ },
20
+ "bugs": "https://github.com/betterapps-hq/parity/issues",
21
+ "engines": {
22
+ "node": ">=24"
23
+ },
24
+ "bin": {
25
+ "parity": "./dist/bin.mjs"
26
+ },
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.mts",
30
+ "default": "./dist/index.mjs"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "skills"
36
+ ],
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "dependencies": {
41
+ "semver": "^7.8.5",
42
+ "typescript": "6.0.3"
43
+ },
44
+ "devDependencies": {
45
+ "@parity-app/core": "0.0.0",
46
+ "@parity-app/libgen": "0.0.0",
47
+ "@parity-app/registry": "0.0.0"
48
+ },
49
+ "scripts": {
50
+ "build": "vp pack",
51
+ "try-packed": "node scripts/try-packed.ts"
52
+ }
6
53
  }
@@ -0,0 +1,96 @@
1
+ # Parity
2
+
3
+ Parity is a design canvas that draws a project's real components, from its own source, styled only with its own tokens.
4
+ A design here isn't a picture of the app: every button, card and table on the canvas is the project's component,
5
+ rendered by its builder. What you design is what the code can build, and the files are the source of truth.
6
+
7
+ Read this file first. Read `execute.md` and `document.md` before your first change; read a guide when the task calls for
8
+ it.
9
+
10
+ ## Files
11
+
12
+ | Read | When |
13
+ | --------------------------- | -------------------------------------------------------------------------------------- |
14
+ | `execute.md` | Before any change: the functions a snippet can call, with examples. Required. |
15
+ | `document.md` | Before any change: designs, nodes, styles, states and responsive patches. Required. |
16
+ | `guide/components.md` | Using the project's components, making component designs, instances and params, icons. |
17
+ | `guide/states-and-modes.md` | Loading, empty, error and other states; themes, viewports and locales. |
18
+ | `guide/checking.md` | Screenshots, layout problems, accessibility, the console, and trying a design. |
19
+ | `guide/design-system.md` | Composing screens from the project's components and tokens. |
20
+ | `guide/web-app.md` | Designing web app screens: dashboards, settings, lists, forms. |
21
+ | `guide/mobile.md` | Designing for phones and small viewports. |
22
+
23
+ ## Tools
24
+
25
+ The same tools are MCP tools and `parity` commands in a terminal. They act on a tab, which is one open project; the
26
+ active tab is the default.
27
+
28
+ | MCP | CLI | What it does |
29
+ | --------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
30
+ | `get_app_state` | `parity state` | Designs, the selection, modes, components and token names, and what Parity detected (builder and config, package manager, installed versions). Start here. |
31
+ | `execute` | `parity exec` | Runs a JavaScript snippet against the designs. All changes land as one undo step. |
32
+ | `screenshot` | `parity screenshot <design>` | Renders a design in its states and modes, offscreen, and returns the images. |
33
+ | `find_problems` | `parity problems <design>` | Render errors, overflow, clipping and unknown components, per frame. |
34
+ | `check_accessibility` | `parity a11y <design>` | axe-core against WCAG 2.2 AA, per frame, on the layers that break each rule. |
35
+ | `get_console` | `parity console` | What the project's own code wrote to the console, on the canvas and offscreen. |
36
+ | `get_tabs` | `parity tabs` | The open projects. |
37
+ | `open_project` | `parity open <folder>` | Opens a project in a new tab. |
38
+ | `read_skill` | `parity skill [file]` | This skill. |
39
+
40
+ In a terminal, `parity tokens`, `parity components` and `parity props <Component>` read what designs can use without
41
+ writing a snippet. `parity components` also says which of the app's exports aren't components and why, which components
42
+ fail to draw on the canvas and why (often a missing provider, which a decorator adds), and what the person listed or
43
+ took off the list by hand.
44
+
45
+ When a library's components, tokens or icons don't show, `parity lib <package> --root <project>` describes the package
46
+ in a `.lib.par`: its components, tokens and the providers its frames need. It writes `./<id>.lib.par` in the current
47
+ folder, or prints with `--out -`. With no package it describes the app at `--root` into the `.lib.par` at its root,
48
+ keeping the designs there, so ask before running that in someone's project.
49
+
50
+ ## How to work
51
+
52
+ 1. **Get your bearings.** Call `get_app_state`. Note the designs, which one is selected (the person usually means that
53
+ one), the modes, and the components. Designs, states and nodes have ids; use them.
54
+ 2. **Look before you change.** Read the design with `Get("design-id", { depth: 3 })` in `execute`, and take a screenshot
55
+ of what's there. Find the components you need with `Components()` and their props with `Props()`.
56
+ 3. **Change it in one snippet.** Build or edit the design with `Insert`, `Update`, `Replace`, `Move` and the rest. A
57
+ snippet either applies whole, as one step the person can undo, or changes nothing. Name the layouts you make.
58
+ 4. **Cover every state.** A screen that loads has a loading state, a list has an empty one, a form has an error one. Add
59
+ them with `AddState`, as changes to the base design, not as copies.
60
+ 5. **Check every frame.** Screenshot the design in all its states and the modes it shows (`state: "all"`,
61
+ `mode: "shown"`). Run `find_problems` and `check_accessibility` too, and read the warnings `execute` returns: they're
62
+ usually a token that doesn't exist or a prop the component doesn't take. Fix what you find, then check again.
63
+ 6. **Say what you did.** Tell the person what changed and what you couldn't verify. Ask them to try anything interactive
64
+ themselves with the Interact tool (I), which hands their mouse and keyboard to the real components.
65
+
66
+ Your changes to designs are on the canvas but not saved: the person saves (Ctrl+S) or undoes them, as with their own
67
+ edits. Don't say they're saved. Token values from `SetTokens` are the same: they show at once, and go into the project's
68
+ stylesheet when the person saves.
69
+
70
+ ## Rules
71
+
72
+ - **Use the project's components.** `Components()` lists them; library components are namespaced, as in `nova/Button`,
73
+ and icons too, as in `lucide/Heart`. A name that isn't in the list renders as a placeholder. Don't rebuild a button,
74
+ card or input out of layouts and text when the project has one.
75
+ - **Use the project's tokens.** Colours, radii, shadows, fonts, sizes, leading and tracking name a token, such as
76
+ `muted-foreground`, `radius-lg` or `text-sm`, without the `--`. Lengths are steps of the spacing scale: `4` is four
77
+ spacing units (1rem in a default Tailwind project). `GetTokens()` lists them. Never write a raw colour or pixel value.
78
+ - **Prefer semantic colours** (`background`, `foreground`, `card`, `muted`, `muted-foreground`, `primary`, `border`,
79
+ `destructive`) over palette ones (`color-blue-500`). They change with the theme; palette colours don't.
80
+ - **Layout is auto layout.** A `layout` node is a flex box (or a grid with `display: "grid"`); children fill, hug or
81
+ take a fixed length. Avoid absolute positioning unless something truly overlaps, such as a badge on an avatar.
82
+ - **One design, every viewport.** Don't copy a screen for mobile. Patch it with `Responsive(node, "mobile", patch)`:
83
+ hide the sidebar, stack the columns, shrink the padding.
84
+ - **Real content.** Write the words the screen would show: names, numbers, dates and messages that fit the product.
85
+ Lorem ipsum hides layout problems.
86
+ - **Reuse what repeats.** When a row, card or list item appears more than twice, make it a component design with
87
+ `ExtractComponent`, so each copy is an instance with its own content.
88
+ - **Leave the canvas tidy.** New designs go into empty space (`FindEmptySpace`); `Overlaps()` tells you if boards
89
+ collide.
90
+
91
+ ## When something fails
92
+
93
+ `execute` returns an `editId` when a snippet throws, and nothing changes. Fix it with `editId` and `edits` (exact find
94
+ and replace on the snippet) instead of sending it all again. The error names the line. Common causes: a node path that
95
+ matches two designs (write it as `design/node`), a node property that doesn't exist, and a state change to something
96
+ states can't change (only `hidden`, `text`, `props` and `style`).
@@ -0,0 +1,166 @@
1
+ # Designs and nodes
2
+
3
+ A project's designs live in `.par` files at its root, as JSON. You change them through `execute`, which validates every
4
+ change, so you never edit the files by hand. This is the shape `GetDesign` and `Get` return and `Insert` takes.
5
+
6
+ ## Designs
7
+
8
+ A design is a screen or a component, drawn on the canvas as a board of frames: one frame per state and mode it shows.
9
+
10
+ | Field | What it is |
11
+ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
12
+ | `id` | Made from the name when the design is created. Stable; use it in paths. |
13
+ | `name` | What people see. |
14
+ | `kind` | `screen` (a page or view) or `component` (a reusable part that screens place as instances). |
15
+ | `root` | The design itself: one node, usually a layout. |
16
+ | `states` | Situations the whole design can be in, like loading or empty. The first is the base; the others store only what they change. |
17
+ | `params` | A component design's inputs, each with a `type` (`text`, `boolean`, `number`, `value`, `variant`) and a `default`. See Params and bindings. |
18
+ | `frames` | A fixed size per viewport, when the design isn't as wide as the viewport or as tall as its content. |
19
+ | `view` | How the canvas shows it: the main mode, the modes beside it, the state and the variant options shown. Not part of the design. |
20
+ | `context` | Components a component design is drawn inside on its own board, such as a table around a table row. |
21
+
22
+ ## Nodes
23
+
24
+ Every node has an `id` (given when it's inserted), and can have a `name`, `hidden`, `style` and `responsive`. Name the
25
+ layouts that group things ("Header", "Stats", "Actions"): the layers panel, state changes and paths all read better.
26
+
27
+ ### `layout`
28
+
29
+ A box that lays out its `children`: a flex box by default, or a grid. Most structure is layouts.
30
+
31
+ ```json
32
+ { "type": "layout", "name": "Actions", "style": { "direction": "row", "gap": 2, "justify": "end" }, "children": [] }
33
+ ```
34
+
35
+ ### `component`
36
+
37
+ One of the project's components. `component` is a name from `Components()`; `props` are its props; `slots` hold child
38
+ nodes, where `children` is what goes between its tags and any other slot becomes a prop that takes nodes.
39
+
40
+ ```json
41
+ {
42
+ "type": "component",
43
+ "component": "Button",
44
+ "props": { "variant": "outline", "size": "sm" },
45
+ "slots": {
46
+ "children": [
47
+ { "type": "component", "component": "lucide/Download" },
48
+ { "type": "text", "text": "Export" }
49
+ ]
50
+ }
51
+ }
52
+ ```
53
+
54
+ Text that's only a component's label can be its `children` prop instead: `"props": { "children": "Export" }`.
55
+
56
+ ### `instance`
57
+
58
+ A copy of one of the project's component designs. `design` is the component design's id, `args` give its params values
59
+ (an option's id for a variant), and `state` picks which of its states this copy shows.
60
+
61
+ ```json
62
+ { "type": "instance", "design": "stat-card", "args": { "label": "Active users", "value": "1,284" } }
63
+ ```
64
+
65
+ `overrides` holds what this copy changes inside the component, keyed by the inner node's id, or `nested/inner` for a
66
+ node inside a nested instance. Each is a patch like a state's (`hidden`, `text`, `props`, `style`). Everything the copy
67
+ doesn't override keeps following the component.
68
+
69
+ ```json
70
+ { "type": "instance", "design": "stat-card", "overrides": { "trend": { "hidden": true } } }
71
+ ```
72
+
73
+ A state's patches can reach inside an instance the same way, keyed `instance/inner`, so one copy can change in one
74
+ state.
75
+
76
+ ### `text`
77
+
78
+ Words. `tag` is `p` (the default), `span`, `h1` to `h4` or `strong`, and sets what the text is, not how it looks; size
79
+ and weight come from `style`.
80
+
81
+ ```json
82
+ { "type": "text", "tag": "h2", "text": "Recent activity", "style": { "size": "text-lg", "weight": 600 } }
83
+ ```
84
+
85
+ ### `image`
86
+
87
+ A picture: `src` is a path the project serves (`/images/team.jpg`) or a URL; `alt` describes it, or is empty when it's
88
+ decoration; `fit` is `cover` (the default), `contain` or `fill`; `credit` names its maker when a photo asks for it.
89
+
90
+ ## Style
91
+
92
+ Every value comes from the project. Lengths are steps of the spacing scale or the name of a length token; colours and
93
+ type name a token from `GetTokens()`, without the `--`.
94
+
95
+ | Key | Takes |
96
+ | ------------------------------------------------ | ------------------------------------------------------------------------- |
97
+ | `display` | `flex` (the default) or `grid` |
98
+ | `direction` | `column` (the default) or `row` |
99
+ | `columns`, `rows` | A grid's column or row count; all the same size |
100
+ | `gap`, `rowGap` | A length |
101
+ | `padding`, `margin` | A length, `[vertical, horizontal]` or `[top, right, bottom, left]` |
102
+ | `align` | Children across: `start`, `center`, `end`, `stretch` |
103
+ | `justify` | Children along: `start`, `center`, `end`, `between`, `around`, `evenly` |
104
+ | `wrap` | `true` to wrap a row |
105
+ | `width`, `height` | A length, `fill` (take the room the parent gives) or `hug` (fit content) |
106
+ | `minWidth`, `maxWidth`, `minHeight`, `maxHeight` | A length |
107
+ | `grow` | `true` to fill along the parent's direction |
108
+ | `self` | This node across its parent, overriding the parent's `align` |
109
+ | `colSpan`, `rowSpan` | Grid cells to span |
110
+ | `position`, `top`, `right`, `bottom`, `left` | `absolute`, placed against the parent's edges by lengths |
111
+ | `background`, `foreground`, `border` | A colour token |
112
+ | `borderWidth` | Pixels: 0, 1, 2, 4 or 8 |
113
+ | `borderStyle` | `solid`, `dashed` or `dotted` |
114
+ | `radius`, `shadow` | A radius or shadow token, such as `radius-lg` or `shadow-sm` |
115
+ | `opacity` | Percent, 0 to 100 |
116
+ | `overflow` | `visible`, `hidden` or `scroll` |
117
+ | `font`, `size`, `leading`, `tracking` | A token, such as `font-mono`, `text-sm`, `leading-tight`, `tracking-wide` |
118
+ | `weight` | A number: 400, 500, 600, 700 |
119
+ | `textAlign` | `start`, `center`, `end` or `justify` |
120
+ | `transform`, `decoration`, `italic` | `uppercase` and the like, `underline` or `line-through`, `true` |
121
+ | `lines` | Lines of text before an ellipsis |
122
+
123
+ Type set on a layout applies to the text inside it. A component's own look comes from its props, so reach for a variant
124
+ (`variant: "secondary"`) before styling a component from outside.
125
+
126
+ ## States
127
+
128
+ A state's `overrides` map node ids to patches of `hidden`, `text`, `props` and `style`. A node a state doesn't mention
129
+ looks as it does in the base. States win over responsive patches.
130
+
131
+ ## Responsive
132
+
133
+ A node's `responsive` maps a viewport option's id (such as `tablet` or `mobile`) to a patch of the same four things,
134
+ applied in every mode on that viewport:
135
+
136
+ ```json
137
+ { "name": "Sidebar", "type": "instance", "design": "app-sidebar", "responsive": { "mobile": { "hidden": true } } }
138
+ ```
139
+
140
+ ## Params and bindings
141
+
142
+ Inside a component design, a node's `bind` takes its values from the component's params, so each instance shows its own:
143
+ `bind.text` names a text param, `bind.props` maps a prop to a param, and `bind.visible` names a boolean param that shows
144
+ or hides the node. `ExtractComponent` sets all of this up from the occurrences it replaces.
145
+
146
+ ```json
147
+ { "type": "text", "text": "Revenue", "bind": { "text": "label" } }
148
+ ```
149
+
150
+ A **variant** is a param whose `options` each patch the component design, the way a state does: a size or a tone the
151
+ instance chooses. Its `default` is an option's id, else the first option. An instance picks one in its `args`, and
152
+ `view.variants` picks which one the component's own board shows. Patches apply in this order: the viewport's, the
153
+ variant options', the state's, then the instance's overrides.
154
+
155
+ ```json
156
+ "params": {
157
+ "size": {
158
+ "type": "variant",
159
+ "default": "medium",
160
+ "options": [
161
+ { "id": "medium", "name": "Medium" },
162
+ { "id": "small", "name": "Small", "overrides": { "card": { "style": { "padding": 3 } } } }
163
+ ]
164
+ }
165
+ }
166
+ ```
@@ -0,0 +1,276 @@
1
+ # The `execute` tool
2
+
3
+ `execute` runs a JavaScript snippet against a working copy of one tab's designs. The functions below are globals.
4
+ `await` works at the top level. When the snippet finishes, every change lands on the canvas as one step the person can
5
+ undo; when it throws, nothing changes and you get an `editId` to fix it with.
6
+
7
+ - Pass `description`: 3 to 8 words the person sees while it runs, such as "Add a pricing section".
8
+ - `Print(...)` (or `console.log`) is how you see values. Objects print as JSON.
9
+ - Values assigned without `const` or `let` carry over to your next snippet in the same tab.
10
+ - A snippet has 5 seconds of computing time. Rendering (screenshots and checks) doesn't count against it.
11
+ - To retry a failed snippet, send `editId` with `edits`, each an exact `{ find, replace }` on the snippet text (`all` to
12
+ replace every match).
13
+
14
+ ## API
15
+
16
+ ```ts
17
+ // Reading
18
+ Designs(): DesignSummary[] // every design: id, name, kind, states, what it shows, size
19
+ GetDesign(design): Design // a whole design, as in document.md
20
+ Get(path, { depth?, state? }?): Node // a design's root or a node; depth cuts children off with "…"
21
+ Get(path?, visit): any[] // walks nodes, collecting what visit returns
22
+ Components(): string[] // the components designs can use
23
+ Props(component, { all? }?): PropsInfo // a component's props, from its source
24
+ GetTokens({ category?, source?, search? }?): Token[]
25
+ GetModes(): { settings, modes } // the project's modes, and each resolved
26
+ Selection(): { design?, node?, mode? } // what the person has selected
27
+
28
+ // Designs
29
+ CreateDesign({ name, kind?, root?, x?, y?, file?, baseState? }): string // returns the new design's id
30
+ UpdateDesign(design, { name?, kind?, x?, y?, mode?, showModes?, state?, allStates? }): void
31
+ DeleteDesign(design): void
32
+
33
+ // Nodes
34
+ Insert(parent, node, { index?, slot? }?): string // returns the new node's id
35
+ Update(path, change, { state? }?): void // change: name, text, tag, hidden, props, args, style, component
36
+ Replace(path, node): string
37
+ Move(path, parent, { index?, slot? }?): void // within one design
38
+ Copy(path, parent, { index?, slot?, name? }?): string
39
+ Delete(path): void
40
+ Responsive(path, viewport, patch | null): void // a patch for one viewport, or null to remove it
41
+
42
+ // States
43
+ AddState(design, { name, changes? }): string // changes keyed by node id or name
44
+ UpdateState(design, state, { name?, changes? }): void
45
+ DeleteState(design, state): void
46
+
47
+ // Components
48
+ FindRepeats(design): Repeat[] // parts that repeat with the same structure
49
+ ExtractComponent(design, nodes, { name, params?, file? }): string
50
+
51
+ // Modes and tokens
52
+ SetModes(settings): void // replaces the project's modes
53
+ SetTokens({ name: value | { theme: value } }): void
54
+
55
+ // Checking
56
+ Screenshot(design, { state?, mode?, scale? }?): number
57
+ Measure(design, { state?, mode? }?): Layout
58
+ Problems(design, { state?, mode? }?): Problem[]
59
+ Accessibility(design, { state?, mode? }?): Finding[]
60
+
61
+ // The canvas
62
+ FindEmptySpace({ width, height, direction?, near?, padding? }): { x, y }
63
+ Overlaps(design?): Overlap[]
64
+ Print(...values): void
65
+ ```
66
+
67
+ ## Paths
68
+
69
+ A design is named by its id or its name. A node is named by `design/node`, where `node` is the node's id or its name, or
70
+ by its id alone when only one design has that id. Names aren't unique, so prefer ids once you have them; `Insert`,
71
+ `Copy` and `CreateDesign` return them, and nodes you create with a `name` are listed under "Created" in the result.
72
+
73
+ ## Reading
74
+
75
+ ### Designs
76
+
77
+ Lists every design with its `id`, `name`, `kind` (`screen` or `component`), `states`, and `showing`: the state on the
78
+ canvas, the main mode and the modes beside it. Start here to find ids.
79
+
80
+ ### Get
81
+
82
+ `Get("dashboard", { depth: 2 })` returns the design's root with two levels of children; deeper ones show as `"…"`. Read
83
+ a branch with a node path, as in `Get("dashboard/Stats", { depth: 2 })`. With `state`, the result shows the nodes as
84
+ that state changes them.
85
+
86
+ `Get(path, visit)` walks the nodes under `path`, or every design without one, and returns what `visit` returns for each.
87
+ `visit(node, { design, path, depth, index, parent, skipChildren })`. Return `undefined` to collect nothing.
88
+
89
+ ```js
90
+ // Every Button in the project, with where it is.
91
+ Print(Get((node, at) => (node.type === "component" && node.component === "Button" ? at.path : undefined)))
92
+ ```
93
+
94
+ ### Components and Props
95
+
96
+ `Components()` is everything a `component` node can name. `Props("Button")` returns the props found in the component's
97
+ source: variants with their options (`variant`, `size`), its own props, and with `{ all: true }` the attributes it
98
+ passes to its element. A prop that isn't listed, or a variant value outside its options, comes back as a warning.
99
+
100
+ ### GetTokens
101
+
102
+ Each token has a `name`, a `category` (`color`, `font-size`, `radius`, `shadow`, `spacing`, `font`, `font-weight`,
103
+ `leading`, `tracking` and others), a `source` (`semantic` for the project's own variables, `theme` for its styling
104
+ framework's scale) and its `values` per theme. Filter with `GetTokens({ category: "color", source: "semantic" })` or
105
+ `GetTokens({ search: "radius" })`.
106
+
107
+ ### Selection
108
+
109
+ What the person has selected: the design's id, and the node and mode when they picked one. "This", "here" and "the
110
+ selected one" usually mean it.
111
+
112
+ ## Designs
113
+
114
+ ### CreateDesign
115
+
116
+ Makes a design and returns its id. Without `root` it starts as a page layout with padding, a gap and the background and
117
+ foreground colours. Without `x` and `y` it goes into empty space. `kind: "component"` makes a component design, which
118
+ screens place as instances; most component designs come from `ExtractComponent` instead, because that also gives them
119
+ params.
120
+
121
+ ### UpdateDesign
122
+
123
+ Renames or moves a design, or changes how it's shown: `mode` sets the main frame's mode, `showModes` the modes beside
124
+ it, `state` the state on the canvas, and `allStates: true` shows every state in its own row. None of these change the
125
+ design itself.
126
+
127
+ ## Nodes
128
+
129
+ ### Insert
130
+
131
+ Adds a node, with everything inside it, as the last child of `parent` or at `index`. Into a component node, it goes into
132
+ the `children` slot unless `slot` names another. Every node gets a fresh id; don't pass ids. `parent` can be a design,
133
+ meaning its root.
134
+
135
+ ```js
136
+ const form = Insert("settings/Content", {
137
+ type: "layout",
138
+ name: "Profile form",
139
+ style: { gap: 4, maxWidth: 120 },
140
+ children: [
141
+ { type: "component", component: "Label", props: { htmlFor: "name", children: "Name" } },
142
+ { type: "component", component: "Input", props: { id: "name", defaultValue: "Avery Chen" } },
143
+ { type: "component", component: "Button", props: { children: "Save changes" }, style: { self: "start" } }
144
+ ]
145
+ })
146
+ ```
147
+
148
+ ### Update
149
+
150
+ Changes a node. `change` takes `name`, `text`, `tag`, `hidden`, `props`, `args` (an instance's values), `style` and
151
+ `component`. `props`, `args` and `style` merge with what's there; set a key to `null` to remove it.
152
+
153
+ With `{ state: "loading" }` the change belongs to that state instead of the base design. A state can change only
154
+ `hidden`, `text`, `props` and `style`.
155
+
156
+ ### Replace, Move, Copy and Delete
157
+
158
+ `Replace` swaps a node for a new one in the same place and returns its id. `Move` reorders or reparents a node within
159
+ its design; to move it to another design, `Copy` it there and `Delete` the original. `Copy` gives the copy fresh ids and
160
+ returns the new id. A design's root can't be deleted; use `Replace`.
161
+
162
+ ### Responsive
163
+
164
+ Patches a node in one viewport: `Responsive("dashboard/Stats", "mobile", { style: { columns: 1 } })`. The viewport is a
165
+ viewport option's id from the modes (such as `desktop`, `tablet` and `mobile`), not a mode's id. Patches take `hidden`,
166
+ `text`, `props` and `style`, and merge with the viewport's earlier patch. `null` removes it.
167
+
168
+ ## States
169
+
170
+ A state is a situation the whole design is in, like a story: loading, empty, error, signed out. It stores only what
171
+ differs from the base. `changes` maps node ids or names to patches (`hidden`, `text`, `props`, `style`); `null` clears a
172
+ node's patch. The first state is the base.
173
+
174
+ ```js
175
+ AddState("team-members", {
176
+ name: "Empty",
177
+ changes: {
178
+ "Members table": { hidden: true },
179
+ "Empty state": { hidden: false },
180
+ seats: { props: { children: "1 of 10 seats used" } }
181
+ }
182
+ })
183
+ ```
184
+
185
+ Put the parts only some states show into the base design with `hidden: true`, then show them in those states. A text
186
+ node's words are its `text`; a component's are usually its `children` prop.
187
+
188
+ ## Components
189
+
190
+ ### FindRepeats
191
+
192
+ Returns parts of a design that repeat with the same structure: each has the root ids of its occurrences (`nodes`), a
193
+ starting `name`, and the `params`, which are what differs between occurrences (texts, props, whether a node shows).
194
+
195
+ ### ExtractComponent
196
+
197
+ Turns occurrences into a component design and replaces each with an instance that keeps its own content as args. Returns
198
+ the component's id. `params` renames the params found, by the name they were given. It refuses when the result wouldn't
199
+ render the same in every state and viewport.
200
+
201
+ Look at what `FindRepeats` found before extracting, because the params' names are only a start:
202
+
203
+ ```js
204
+ repeats = FindRepeats("team-members")
205
+ Print(repeats)
206
+ ```
207
+
208
+ Then, in the next snippet, extract with names that say what each param is:
209
+
210
+ ```js
211
+ const [rows] = repeats
212
+ ExtractComponent("team-members", rows.nodes, { name: "Member row", params: { [rows.params[0].name]: "name" } })
213
+ ```
214
+
215
+ ## Modes and tokens
216
+
217
+ `SetModes(settings)` replaces the project's modes: take `GetModes().settings`, change it, and pass it back. Modes are
218
+ shared by every design.
219
+
220
+ `SetTokens` changes token values in the project's stylesheets: `SetTokens({ primary: "oklch(0.55 0.2 260)" })` for every
221
+ theme, or `SetTokens({ primary: { default: "…", dark: "…" } })` per theme. Every frame shows the new values at once, and
222
+ saving writes them into the project's code, so do it only when the person asks to change the design system, not to style
223
+ one design. They're undone with the call's other changes.
224
+
225
+ ## Checking
226
+
227
+ `Screenshot` renders frames offscreen at their real widths and returns how many it took; the images come back with the
228
+ result. `state` and `mode` take an id, a list, or `"all"`; `mode` also takes `"shown"`, the modes the design shows. The
229
+ defaults are the state on the canvas and the main mode.
230
+
231
+ `Measure` lays out one frame and returns its size, whether it overflows sideways, and every node's box (`clipped` when a
232
+ parent cuts it off). `Problems` checks frames for render errors, overflow, clipping and unknown components.
233
+ `Accessibility` returns axe-core's findings per node and frame, with the rule, its impact and what's wrong.
234
+
235
+ ## The canvas
236
+
237
+ `FindEmptySpace({ width, height })` finds room for a new board, to the right of the others unless `direction: "below"`,
238
+ or next to the design named in `near`. `Overlaps()` lists boards that collide, titles included; move one with
239
+ `UpdateDesign(design, { x, y })`.
240
+
241
+ ## Examples
242
+
243
+ A new screen, checked in every state and mode:
244
+
245
+ ```js
246
+ const id = CreateDesign({ name: "Billing" })
247
+ Insert(id, { type: "text", tag: "h1", text: "Billing", style: { size: "text-2xl", weight: 600 } })
248
+ Insert(id, {
249
+ type: "component",
250
+ component: "Card",
251
+ name: "Plan",
252
+ slots: {
253
+ children: [
254
+ {
255
+ type: "component",
256
+ component: "CardHeader",
257
+ slots: {
258
+ children: [
259
+ { type: "component", component: "CardTitle", props: { children: "Pro plan" } },
260
+ { type: "component", component: "CardDescription", props: { children: "$24 per seat, billed monthly" } }
261
+ ]
262
+ }
263
+ }
264
+ ]
265
+ }
266
+ })
267
+ await Screenshot(id, { state: "all", mode: "shown" })
268
+ Print(await Problems(id, { state: "all", mode: "shown" }))
269
+ ```
270
+
271
+ A loading state that swaps content for skeletons:
272
+
273
+ ```js
274
+ Update("dashboard/Stats loading", { hidden: true })
275
+ AddState("dashboard", { name: "Loading", changes: { Stats: { hidden: true }, "Stats loading": { hidden: false } } })
276
+ ```