@parity-app/cli 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -1
- package/dist/bin.mjs +1132 -272
- package/dist/client-Cf66-n3R.mjs +724 -0
- package/dist/index.d.mts +211 -17
- package/dist/index.mjs +1 -1
- package/dist/libraries/aria.lib.par +5 -5
- package/dist/libraries/baseui.lib.par +25 -25
- package/dist/libraries/chakra.lib.par +17 -17
- package/dist/libraries/fluent.lib.par +27 -27
- package/dist/libraries/luma.lib.par +20 -20
- package/dist/libraries/lyra.lib.par +20 -20
- package/dist/libraries/maia.lib.par +20 -20
- package/dist/libraries/mantine.lib.par +14 -14
- package/dist/libraries/mira.lib.par +20 -20
- package/dist/libraries/mui.lib.par +5 -5
- package/dist/libraries/nova.lib.par +20 -20
- package/dist/libraries/radix.lib.par +11 -11
- package/dist/libraries/rhea.lib.par +20 -20
- package/dist/libraries/sera.lib.par +20 -20
- package/dist/libraries/vega.lib.par +20 -20
- package/dist/{src-8MhDV_sA.mjs → src-Aoey5Uq0.mjs} +498 -207
- package/dist/{src-DYi23Ypb.mjs → src-BGF7szJh.mjs} +1 -1
- package/dist/{src-FF8eY6EI.mjs → src-DZKi9702.mjs} +3 -2
- package/dist/{src-DXZqtm6z.mjs → src-eso2QXtH.mjs} +1 -1
- package/dist/{src-D9EMN48r.mjs → src-lVLSuOgh.mjs} +138 -38
- package/package.json +8 -6
- package/skills/SKILL.md +61 -14
- package/skills/document.md +11 -1
- package/skills/execute.md +82 -13
- package/skills/guide/states-and-modes.md +5 -3
- package/LICENSE +0 -21
- package/dist/client-DsTLQ2lT.mjs +0 -286
|
@@ -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-eso2QXtH.mjs";
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import "./client-Cf66-n3R.mjs";
|
|
2
|
+
export { a as applyLibraryEdits, t as componentNode, y as generateId, n as librarySource, f as parseLibraryEdits, i as withLibraryContext } from "./src-lVLSuOgh.mjs";
|
|
3
|
+
import "./src-Aoey5Uq0.mjs";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { p as parseLibraryManifest, u as libraryManifestExtension } from "./src-
|
|
1
|
+
import { p as parseLibraryManifest, u as libraryManifestExtension } from "./src-lVLSuOgh.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { readFile, readdir } from "node:fs/promises";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
// ../core/src/css.ts
|
|
2
|
-
/**
|
|
3
|
-
* Custom properties holding 1% of a frame's viewport width and height, which viewport units are rewritten to: in a frame,
|
|
4
|
-
* `100vh` is the height of the screen the frame stands for, not of Parity's window.
|
|
5
|
-
*/
|
|
6
|
-
const viewportWidthProperty = "--canvas-vw";
|
|
7
|
-
const viewportHeightProperty = "--canvas-vh";
|
|
8
|
-
new RegExp(String.raw`"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|url\([^)]*\)|var\((?:${viewportWidthProperty}|${viewportHeightProperty}),[^)]*\)`, "g");
|
|
9
1
|
// ../core/src/devices.ts
|
|
10
2
|
const deviceKinds = [
|
|
11
3
|
"phone",
|
|
@@ -24,6 +16,110 @@ function lengthToPixels(value) {
|
|
|
24
16
|
if (!Number.isFinite(number)) return void 0;
|
|
25
17
|
return match[2] === "rem" || match[2] === "em" ? number * 16 : number;
|
|
26
18
|
}
|
|
19
|
+
// ../core/src/css.ts
|
|
20
|
+
/**
|
|
21
|
+
* Custom properties holding 1% of a frame's viewport width and height, which viewport units are rewritten to: in a frame,
|
|
22
|
+
* `100vh` is the height of the screen the frame stands for, not of Parity's window.
|
|
23
|
+
*/
|
|
24
|
+
const viewportWidthProperty = "--canvas-vw";
|
|
25
|
+
const viewportHeightProperty = "--canvas-vh";
|
|
26
|
+
/** Every custom property declaration in a stylesheet, in source order. */
|
|
27
|
+
function collectCustomProperties(css) {
|
|
28
|
+
const declarations = [];
|
|
29
|
+
const visit = (nodes, selector, atRules) => {
|
|
30
|
+
for (const node of nodes) if (node.kind === "block") {
|
|
31
|
+
const prelude = node.prelude.trim();
|
|
32
|
+
const atRule = /^@([\w-]+)\s*([\s\S]*)$/.exec(prelude);
|
|
33
|
+
if (atRule) visit(node.children, selector, [...atRules, {
|
|
34
|
+
name: atRule[1].toLowerCase(),
|
|
35
|
+
prelude: atRule[2].trim()
|
|
36
|
+
}]);
|
|
37
|
+
else visit(node.children, prelude, atRules);
|
|
38
|
+
} else if (selector !== void 0 || atRules.length > 0) for (const match of node.text.matchAll(/--([\w-]+)\s*:\s*([^;]+)/g)) declarations.push({
|
|
39
|
+
selector: selector ?? "",
|
|
40
|
+
atRules,
|
|
41
|
+
name: match[1],
|
|
42
|
+
value: match[2].trim()
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
visit(parse(css), void 0, []);
|
|
46
|
+
return declarations;
|
|
47
|
+
}
|
|
48
|
+
function parse(css) {
|
|
49
|
+
let index = 0;
|
|
50
|
+
function parseLevel() {
|
|
51
|
+
const nodes = [];
|
|
52
|
+
let buffer = "";
|
|
53
|
+
let afterLastSemicolon = 0;
|
|
54
|
+
let parens = 0;
|
|
55
|
+
while (index < css.length) {
|
|
56
|
+
const char = css[index];
|
|
57
|
+
if (char === "/" && css[index + 1] === "*") {
|
|
58
|
+
const end = css.indexOf("*/", index + 2);
|
|
59
|
+
index = end === -1 ? css.length : end + 2;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (char === "\\") {
|
|
63
|
+
buffer += css.slice(index, index + 2);
|
|
64
|
+
index += 2;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (char === "\"" || char === "'") {
|
|
68
|
+
const end = findStringEnd(css, index);
|
|
69
|
+
buffer += css.slice(index, end);
|
|
70
|
+
index = end;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
index++;
|
|
74
|
+
if (char === "(") parens++;
|
|
75
|
+
if (char === ")") parens = Math.max(0, parens - 1);
|
|
76
|
+
if (parens > 0) {
|
|
77
|
+
buffer += char;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (char === ";") {
|
|
81
|
+
buffer += char;
|
|
82
|
+
afterLastSemicolon = buffer.length;
|
|
83
|
+
} else if (char === "{") {
|
|
84
|
+
const text = buffer.slice(0, afterLastSemicolon);
|
|
85
|
+
if (text) nodes.push({
|
|
86
|
+
kind: "text",
|
|
87
|
+
text
|
|
88
|
+
});
|
|
89
|
+
nodes.push({
|
|
90
|
+
kind: "block",
|
|
91
|
+
prelude: buffer.slice(afterLastSemicolon),
|
|
92
|
+
children: parseLevel()
|
|
93
|
+
});
|
|
94
|
+
buffer = "";
|
|
95
|
+
afterLastSemicolon = 0;
|
|
96
|
+
} else if (char === "}") {
|
|
97
|
+
if (buffer) nodes.push({
|
|
98
|
+
kind: "text",
|
|
99
|
+
text: buffer
|
|
100
|
+
});
|
|
101
|
+
return nodes;
|
|
102
|
+
} else buffer += char;
|
|
103
|
+
}
|
|
104
|
+
if (buffer) nodes.push({
|
|
105
|
+
kind: "text",
|
|
106
|
+
text: buffer
|
|
107
|
+
});
|
|
108
|
+
return nodes;
|
|
109
|
+
}
|
|
110
|
+
const nodes = [];
|
|
111
|
+
while (index < css.length) nodes.push(...parseLevel());
|
|
112
|
+
return nodes;
|
|
113
|
+
}
|
|
114
|
+
function findStringEnd(css, start) {
|
|
115
|
+
const quote = css[start];
|
|
116
|
+
let index = start + 1;
|
|
117
|
+
while (index < css.length) if (css[index] === "\\") index += 2;
|
|
118
|
+
else if (css[index] === quote) return index + 1;
|
|
119
|
+
else index++;
|
|
120
|
+
return css.length;
|
|
121
|
+
}
|
|
122
|
+
new RegExp(String.raw`"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|url\([^)]*\)|var\((?:${viewportWidthProperty}|${viewportHeightProperty}),[^)]*\)`, "g");
|
|
27
123
|
// ../core/src/validate.ts
|
|
28
124
|
function expectObject(value, path) {
|
|
29
125
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError(`${path || "/"}: expected an object`);
|
|
@@ -270,10 +366,7 @@ function parseDetection(value, path) {
|
|
|
270
366
|
export: expectString(ref.export, `${at}/${index}/export`)
|
|
271
367
|
};
|
|
272
368
|
});
|
|
273
|
-
return compact({
|
|
274
|
-
include: optional(detection.include, (list) => refs(list, `${path}/include`)),
|
|
275
|
-
exclude: optional(detection.exclude, (list) => refs(list, `${path}/exclude`))
|
|
276
|
-
});
|
|
369
|
+
return compact({ include: optional(detection.include, (list) => refs(list, `${path}/include`)) });
|
|
277
370
|
}
|
|
278
371
|
function parseNote(value, path) {
|
|
279
372
|
const note = expectObject(value, path);
|
|
@@ -575,13 +668,24 @@ function parsePatch(value, path) {
|
|
|
575
668
|
"hidden",
|
|
576
669
|
"text",
|
|
577
670
|
"props",
|
|
578
|
-
"style"
|
|
671
|
+
"style",
|
|
672
|
+
"unset"
|
|
579
673
|
], path);
|
|
580
674
|
return compact({
|
|
581
675
|
hidden: optional(patch.hidden, (v) => expectBoolean(v, `${path}/hidden`)),
|
|
582
676
|
text: optional(patch.text, (v) => expectString(v, `${path}/text`)),
|
|
583
677
|
props: optional(patch.props, (v) => expectObject(v, `${path}/props`)),
|
|
584
|
-
style: optional(patch.style, (v) => parseStyle(v, `${path}/style`))
|
|
678
|
+
style: optional(patch.style, (v) => parseStyle(v, `${path}/style`)),
|
|
679
|
+
unset: optional(patch.unset, (v) => parseUnset(v, `${path}/unset`))
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
function parseUnset(value, path) {
|
|
683
|
+
const unset = expectObject(value, path);
|
|
684
|
+
expectKnownKeys(unset, ["props", "style"], path);
|
|
685
|
+
const keys = (key, item) => optional(unset[key], (v) => expectArray(v, `${path}/${key}`).map((entry, index) => item(entry, `${path}/${key}/${index}`)));
|
|
686
|
+
return compact({
|
|
687
|
+
props: keys("props", expectString),
|
|
688
|
+
style: keys("style", (v, where) => expectOneOf(v, styleKeys, where))
|
|
585
689
|
});
|
|
586
690
|
}
|
|
587
691
|
function parseStyle(value, path) {
|
|
@@ -729,7 +833,7 @@ const decoratorKeys = [
|
|
|
729
833
|
const libraryManifestExtension = ".lib.par";
|
|
730
834
|
function parseLibraryManifest(value) {
|
|
731
835
|
const manifest = expectObject(value, "");
|
|
732
|
-
|
|
836
|
+
expectKnownKeys(manifest, [
|
|
733
837
|
"$schema",
|
|
734
838
|
"version",
|
|
735
839
|
"id",
|
|
@@ -738,13 +842,16 @@ function parseLibraryManifest(value) {
|
|
|
738
842
|
"styles",
|
|
739
843
|
"breakpoints",
|
|
740
844
|
"tokens",
|
|
741
|
-
"canvas"
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
845
|
+
"canvas",
|
|
846
|
+
"package",
|
|
847
|
+
"components",
|
|
848
|
+
"icons",
|
|
849
|
+
"decorators",
|
|
850
|
+
"edits"
|
|
851
|
+
], "");
|
|
852
|
+
if (manifest.version !== 3) throw new TypeError("/version: expected 3");
|
|
746
853
|
return compact({
|
|
747
|
-
version:
|
|
854
|
+
version: 3,
|
|
748
855
|
id: optional(manifest.id, (v) => expectString(v, "/id")),
|
|
749
856
|
name: expectString(manifest.name, "/name"),
|
|
750
857
|
description: optional(manifest.description, (v) => expectString(v, "/description")),
|
|
@@ -1074,7 +1181,7 @@ function expectStringRecord(value, path) {
|
|
|
1074
1181
|
const named = /* @__PURE__ */ new WeakSet();
|
|
1075
1182
|
/**
|
|
1076
1183
|
* A new node for a component, ready to show something: required props get their default or first option, and a
|
|
1077
|
-
* component that takes children gets its own name as text, so it isn't an empty box.
|
|
1184
|
+
* component that takes children gets its own name as text, so it isn't an empty box, unless `given` has its own.
|
|
1078
1185
|
*/
|
|
1079
1186
|
function componentNode(component, info, taken, given = {}) {
|
|
1080
1187
|
const props = { ...given };
|
|
@@ -1086,7 +1193,7 @@ function componentNode(component, info, taken, given = {}) {
|
|
|
1086
1193
|
else if (prop.kind === "number") props[prop.name] = 0;
|
|
1087
1194
|
else if (prop.kind === "string") props[prop.name] = prop.name;
|
|
1088
1195
|
}
|
|
1089
|
-
const labelled = info?.props.some((prop) => prop.name === "children" && prop.kind === "node");
|
|
1196
|
+
const labelled = !("children" in given) && info?.props.some((prop) => prop.name === "children" && prop.kind === "node");
|
|
1090
1197
|
if (labelled) props.children = component.split("/").at(-1) ?? component;
|
|
1091
1198
|
const node = {
|
|
1092
1199
|
type: "component",
|
|
@@ -1247,10 +1354,10 @@ function withLibraryContext(node, sources, around, taken) {
|
|
|
1247
1354
|
}
|
|
1248
1355
|
}
|
|
1249
1356
|
/**
|
|
1250
|
-
* A library file's component as the canvas inserts it,
|
|
1251
|
-
*
|
|
1357
|
+
* A library file's component as the canvas inserts it, with its parents and sample named by component id.
|
|
1358
|
+
* Discovery has already rewritten module imports for the name the project installed the package under.
|
|
1252
1359
|
*/
|
|
1253
|
-
function librarySource(library, name, component, path
|
|
1360
|
+
function librarySource(library, name, component, path) {
|
|
1254
1361
|
return {
|
|
1255
1362
|
path,
|
|
1256
1363
|
export: name,
|
|
@@ -1261,14 +1368,13 @@ function librarySource(library, name, component, path, imported = (specifier) =>
|
|
|
1261
1368
|
...component.visual === false ? { visual: false } : {},
|
|
1262
1369
|
...component.optionalParents ? { optionalParents: component.optionalParents.map((parent) => `${library}/${parent}`) } : {},
|
|
1263
1370
|
...component.parentParts ? { parentParts: true } : {},
|
|
1264
|
-
...component.sample ? { sample: sampleIds(component.sample, library
|
|
1371
|
+
...component.sample ? { sample: sampleIds(component.sample, library) } : {}
|
|
1265
1372
|
};
|
|
1266
1373
|
}
|
|
1267
1374
|
/**
|
|
1268
|
-
* A sample with its children's components named by id, as `nova/AccordionItem`; HTML elements keep their tag.
|
|
1269
|
-
* package makes (`{ $call: { import: "@chakra-ui/react", ... } }`) imports it by the name the project depends on.
|
|
1375
|
+
* A sample with its children's components named by id, as `nova/AccordionItem`; HTML elements keep their tag.
|
|
1270
1376
|
*/
|
|
1271
|
-
function sampleIds(sample, library
|
|
1377
|
+
function sampleIds(sample, library) {
|
|
1272
1378
|
const child = (node) => {
|
|
1273
1379
|
if (typeof node === "string") return node;
|
|
1274
1380
|
if (isSampleRender(node)) return {
|
|
@@ -1288,13 +1394,7 @@ function sampleIds(sample, library, imported = (specifier) => specifier) {
|
|
|
1288
1394
|
$render: item.$render.map(child)
|
|
1289
1395
|
};
|
|
1290
1396
|
if (isSampleElement(item)) return { $element: item.$element.map(child) };
|
|
1291
|
-
return
|
|
1292
|
-
...item,
|
|
1293
|
-
$call: {
|
|
1294
|
-
...item.$call,
|
|
1295
|
-
import: imported(item.$call.import)
|
|
1296
|
-
}
|
|
1297
|
-
} : item;
|
|
1397
|
+
return item;
|
|
1298
1398
|
};
|
|
1299
1399
|
const propIds = (props) => Object.fromEntries(Object.entries(props).map(([key, item]) => [key, propId(item)]));
|
|
1300
1400
|
return {
|
|
@@ -1303,4 +1403,4 @@ function sampleIds(sample, library, imported = (specifier) => specifier) {
|
|
|
1303
1403
|
...sample.children ? { children: sample.children.map(child) } : {}
|
|
1304
1404
|
};
|
|
1305
1405
|
}
|
|
1306
|
-
export {
|
|
1406
|
+
export { viewportWidthProperty as C, viewportHeightProperty as S, lengthToPixels as T, parseCanvasDocument as _, applyLibraryEdits as a, parseModeSettings as b, isSampleElement as c, parentSignals as d, parseLibraryEdits as f, paramTypes as g, imageFits 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, styleKeys as v, deviceKinds as w, collectCustomProperties as x, generateId as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parity-app/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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.",
|
|
@@ -34,6 +34,11 @@
|
|
|
34
34
|
"dist",
|
|
35
35
|
"skills"
|
|
36
36
|
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "vp pack",
|
|
39
|
+
"prepack": "vp pack",
|
|
40
|
+
"try-packed": "node scripts/try-packed.ts"
|
|
41
|
+
},
|
|
37
42
|
"publishConfig": {
|
|
38
43
|
"access": "public"
|
|
39
44
|
},
|
|
@@ -42,12 +47,9 @@
|
|
|
42
47
|
"typescript": "6.0.3"
|
|
43
48
|
},
|
|
44
49
|
"devDependencies": {
|
|
50
|
+
"@parity-app/agent": "0.0.0",
|
|
45
51
|
"@parity-app/core": "0.0.0",
|
|
46
52
|
"@parity-app/libgen": "0.0.0",
|
|
47
53
|
"@parity-app/registry": "0.0.0"
|
|
48
|
-
},
|
|
49
|
-
"scripts": {
|
|
50
|
-
"build": "vp pack",
|
|
51
|
-
"try-packed": "node scripts/try-packed.ts"
|
|
52
54
|
}
|
|
53
|
-
}
|
|
55
|
+
}
|
package/skills/SKILL.md
CHANGED
|
@@ -22,25 +22,62 @@ it.
|
|
|
22
22
|
|
|
23
23
|
## Tools
|
|
24
24
|
|
|
25
|
-
The same tools are MCP tools and `parity` commands in a terminal. They act on a tab, which is one open project
|
|
26
|
-
|
|
25
|
+
The same tools are MCP tools and `parity` commands in a terminal. They act on a tab, which is one open project. Without
|
|
26
|
+
`tab`, they act on the open project your working folder is in, and otherwise on `active`: what the person is looking at,
|
|
27
|
+
the active tab of the Parity window they used last. When the person says "this" or "what I've selected" without naming a
|
|
28
|
+
project, pass `tab: "active"` (`--tab active`) and read `get_app_state` for the selection.
|
|
27
29
|
|
|
28
30
|
| MCP | CLI | What it does |
|
|
29
31
|
| --------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
30
32
|
| `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
|
-
| `
|
|
33
|
+
| `get_status` | `parity status` | The revision, whether anything is unsaved and which files and tokens, the last save, and undo and redo; `summary` names them. |
|
|
34
|
+
| `get_modes` | `parity modes [design]` | Viewports (the ids `Responsive` takes), modes (the ids renders take) and what each design shows; a design adds its frames. |
|
|
35
|
+
| `execute` | `parity exec` | Runs a JavaScript snippet against the designs. Its changes land as one undo step, merged per design with edits made meanwhile. |
|
|
32
36
|
| `screenshot` | `parity screenshot <design>` | Renders a design in its states and modes, offscreen, and returns the images. |
|
|
33
37
|
| `find_problems` | `parity problems <design>` | Render errors, overflow, clipping and unknown components, per frame. |
|
|
34
38
|
| `check_accessibility` | `parity a11y <design>` | axe-core against WCAG 2.2 AA, per frame, on the layers that break each rule. |
|
|
35
39
|
| `get_console` | `parity console` | What the project's own code wrote to the console, on the canvas and offscreen. |
|
|
40
|
+
| `get_tokens` | `parity tokens` | Tokens filtered by category, source or name, with values in every theme. |
|
|
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. |
|
|
36
43
|
| `get_tabs` | `parity tabs` | The open projects. |
|
|
37
44
|
| `open_project` | `parity open <folder>` | Opens a project in a new tab. |
|
|
38
45
|
| `read_skill` | `parity skill [file]` | This skill. |
|
|
39
46
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
The token, component and prop tools read what designs can use without writing a snippet in either transport.
|
|
48
|
+
`parity components` also says which of the app's exports aren't components and why, which components fail to draw on the
|
|
49
|
+
canvas and why (often a missing provider, which a decorator adds), and what the person listed or took off the list by
|
|
50
|
+
hand.
|
|
51
|
+
|
|
52
|
+
CLI commands and MCP tools share validation and defaults. Screenshot scale defaults to 1 and accepts 0.25 to 3. Audits
|
|
53
|
+
check every state and shown mode unless narrowed; a single-element selector list is equivalent to a string. Screenshot
|
|
54
|
+
failures and audit findings exit with 1 in the CLI and set MCP `isError`, with details retained. `get_console` defaults
|
|
55
|
+
`clear` to false; `get_props` defaults `all` to false. MCP project paths must be absolute; the CLI resolves relative
|
|
56
|
+
folders from its working directory. Skill reading is local and needs no running app.
|
|
57
|
+
|
|
58
|
+
Several copies of Parity can run at once, each with its own projects open. Each call goes to the copy that has its
|
|
59
|
+
project open, so naming the project is enough; `get_tabs` lists every copy's projects. A project open in two copies goes
|
|
60
|
+
to the one that has been running longest. A copy kept apart with `PARITY_AGENT_FILE` is reached only through that file:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
parity tabs # every project open in any copy
|
|
64
|
+
parity state --tab active # what the person is looking at, and their selection
|
|
65
|
+
PARITY_AGENT_FILE=copy/agent.json pnpm dev & # a copy of Parity kept apart
|
|
66
|
+
PARITY_AGENT_FILE=copy/agent.json parity tabs # just that copy
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Every tool returns its result as data too: MCP `structuredContent`, or `--json` in the CLI. Read that rather than the
|
|
70
|
+
text. A failure has `error.code` to branch on, `retryable` (send it again unchanged), `path` (the input at fault) and
|
|
71
|
+
`alternatives` (valid ids for one it didn't know), so a mistyped id is fixed in one retry:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
parity problems button --state loading --json
|
|
75
|
+
# {"error": {"code": "unknown_state", "path": "/state", "alternatives": ["default", "outline", "destructive", "disabled"], ...}}
|
|
76
|
+
parity problems button --state disabled --json
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`get_tokens` returns the first 100 tokens and `get_console` the newest 50 messages, with `total` and `truncated`; narrow
|
|
80
|
+
the read or raise `limit` when `truncated` is true.
|
|
44
81
|
|
|
45
82
|
When a library's components, tokens or icons don't show, `parity lib <package> --root <project>` describes the package
|
|
46
83
|
in a `.lib.par`: its components, tokens and the providers its frames need. It writes `./<id>.lib.par` in the current
|
|
@@ -54,7 +91,8 @@ keeping the designs there, so ask before running that in someone's project.
|
|
|
54
91
|
2. **Look before you change.** Read the design with `Get("design-id", { depth: 3 })` in `execute`, and take a screenshot
|
|
55
92
|
of what's there. Find the components you need with `Components()` and their props with `Props()`.
|
|
56
93
|
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.
|
|
94
|
+
snippet either applies whole, as one step the person can undo, or changes nothing. Name the layouts you make. If it
|
|
95
|
+
fails because the person changed a design it changes, run it again by its `editId`.
|
|
58
96
|
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
97
|
them with `AddState`, as changes to the base design, not as copies.
|
|
60
98
|
5. **Check every frame.** Screenshot the design in all its states and the modes it shows (`state: "all"`,
|
|
@@ -65,7 +103,15 @@ keeping the designs there, so ask before running that in someone's project.
|
|
|
65
103
|
|
|
66
104
|
Your changes to designs are on the canvas but not saved: the person saves (Ctrl+S) or undoes them, as with their own
|
|
67
105
|
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.
|
|
106
|
+
stylesheet when the person saves. `get_status` says what's saved and what isn't, and its `revision` (also in
|
|
107
|
+
`get_app_state`) stays the same until something changes, so compare it before reading the board again:
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
parity status --summary
|
|
111
|
+
# shadcn-vite is at revision 7c612c04:1.
|
|
112
|
+
# 1 file isn't saved. They wait for the person to save; agents can't.
|
|
113
|
+
# shadcn-vite.par: changed button
|
|
114
|
+
```
|
|
69
115
|
|
|
70
116
|
## Rules
|
|
71
117
|
|
|
@@ -86,11 +132,12 @@ stylesheet when the person saves.
|
|
|
86
132
|
- **Reuse what repeats.** When a row, card or list item appears more than twice, make it a component design with
|
|
87
133
|
`ExtractComponent`, so each copy is an instance with its own content.
|
|
88
134
|
- **Leave the canvas tidy.** New designs go into empty space (`FindEmptySpace`); `Overlaps()` tells you if boards
|
|
89
|
-
collide.
|
|
135
|
+
collide, sizing boards the canvas hasn't drawn yet from their frames (`estimated: true`).
|
|
90
136
|
|
|
91
137
|
## When something fails
|
|
92
138
|
|
|
93
|
-
`execute` returns an `editId` when a snippet throws, and nothing changes
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
139
|
+
`execute` returns an `editId` when a snippet throws, and nothing changes; `error.code` says why, and
|
|
140
|
+
`error.alternatives` names the valid ids when a design, state, mode or component wasn't found. Fix it with `editId` and
|
|
141
|
+
`edits` (exact find and replace on the snippet) instead of sending it all again. The error names the line. Common
|
|
142
|
+
causes: a node path that matches two designs (write it as `design/node`), a node property that doesn't exist, and a
|
|
143
|
+
state change to something states can't change (only `hidden`, `text`, `props` and `style`).
|
package/skills/document.md
CHANGED
|
@@ -132,6 +132,15 @@ Type set on a layout applies to the text inside it. A component's own look comes
|
|
|
132
132
|
A state's `overrides` map node ids to patches of `hidden`, `text`, `props` and `style`. A node a state doesn't mention
|
|
133
133
|
looks as it does in the base. States win over responsive patches.
|
|
134
134
|
|
|
135
|
+
A patch can also take values away with `unset`, which lists prop names and style keys to drop from what's under it, so a
|
|
136
|
+
state can turn off a prop the base sets or leave a layout without the base's gap:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{ "fields": { "style": { "padding": 4 }, "unset": { "style": ["gap"], "props": ["disabled"] } } }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
A key a patch sets isn't also in its `unset`. Taking away an instance's param value gives the param its default.
|
|
143
|
+
|
|
135
144
|
## Responsive
|
|
136
145
|
|
|
137
146
|
A node's `responsive` maps a viewport option's id (such as `tablet` or `mobile`) to a patch of the same four things,
|
|
@@ -154,7 +163,8 @@ or hides the node. `ExtractComponent` sets all of this up from the occurrences i
|
|
|
154
163
|
A **variant** is a param whose `options` each patch the component design, the way a state does: a size or a tone the
|
|
155
164
|
instance chooses. Its `default` is an option's id, else the first option. An instance picks one in its `args`, and
|
|
156
165
|
`view.variants` picks which one the component's own board shows. Patches apply in this order: the viewport's, the
|
|
157
|
-
variant options', the state's, then the instance's overrides.
|
|
166
|
+
variant options', the state's, then the instance's overrides. `Get` returns the stored node, with the text it was given;
|
|
167
|
+
`Get(path, { state })` returns it as the board shows it, the param's value filled in and `bind` kept (see execute.md).
|
|
158
168
|
|
|
159
169
|
```json
|
|
160
170
|
"params": {
|
package/skills/execute.md
CHANGED
|
@@ -4,12 +4,30 @@
|
|
|
4
4
|
`await` works at the top level. When the snippet finishes, every change lands on the canvas as one step the person can
|
|
5
5
|
undo; when it throws, nothing changes and you get an `editId` to fix it with.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
The person, and other agents, can change the tab while your snippet runs. Only the designs your snippet changed, created
|
|
8
|
+
or deleted are applied, over what's there by then, so their edits to other designs, even in the same file, stay. If a
|
|
9
|
+
design you changed was changed or deleted in the meantime, or the modes were and you set them, nothing is applied and
|
|
10
|
+
the call fails naming it:
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
Failed, nothing changed: Not applied: since this call read the tab, "Pricing" (app.par) was changed in the editor, and
|
|
14
|
+
this call changes it too. Run the call again to work from what's there now.
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Run it again with its `editId` and no edits (`parity exec --edit-id <id>`, or `execute` with just `editId`): the snippet
|
|
18
|
+
reads the tab afresh, so it works from their change. Read the design again first if your snippet depends on what it
|
|
19
|
+
held.
|
|
20
|
+
|
|
21
|
+
- Prefer a `description` of 3 to 8 words, such as "Add a pricing section". CLI `exec` calls it `--label`. Both default
|
|
22
|
+
to "Edit designs" if omitted.
|
|
8
23
|
- `Print(...)` (or `console.log`) is how you see values. Objects print as JSON.
|
|
9
24
|
- Values assigned without `const` or `let` carry over to your next snippet in the same tab.
|
|
10
25
|
- A snippet has 5 seconds of computing time. Rendering (screenshots and checks) doesn't count against it.
|
|
11
26
|
- To retry a failed snippet, send `editId` with `edits`, each an exact `{ find, replace }` on the snippet text (`all` to
|
|
12
|
-
replace every match).
|
|
27
|
+
replace every match). Omitted edits default to an empty list. Do not send a new snippet with `editId`.
|
|
28
|
+
- Both CLI and MCP validate the same request: a nonblank snippet or `editId`, nonblank label and tab when supplied, and
|
|
29
|
+
patches with a nonempty `find`, string `replace` (which can be empty), and optional boolean `all`. Invalid requests
|
|
30
|
+
are rejected before reaching the app.
|
|
13
31
|
|
|
14
32
|
## API
|
|
15
33
|
|
|
@@ -17,14 +35,15 @@ undo; when it throws, nothing changes and you get an `editId` to fix it with.
|
|
|
17
35
|
// Reading
|
|
18
36
|
Designs(): DesignSummary[] // every design: id, name, kind, states, what it shows, size
|
|
19
37
|
GetDesign(design): Design // a whole design, as in document.md
|
|
20
|
-
Get(path, { depth?, state? }?): Node
|
|
38
|
+
Get(path, { depth?, state?, mode? }?): Node // a design's root or a node; depth cuts children off with "…"
|
|
21
39
|
Get(path?, visit): any[] // walks nodes, collecting what visit returns
|
|
40
|
+
Get(path, visit, { state?, mode? }): any[] // walks nodes as a frame draws them
|
|
22
41
|
Components(): string[] // the components designs can use
|
|
23
42
|
Props(component, { all? }?): PropsInfo // a component's props, from its source
|
|
24
43
|
Icons(): IconSet[] // the project's icon sets, with their styles
|
|
25
44
|
Icons(set, { search?, style?, limit? }): Icon[] // async: a set's icons, as component nodes name them
|
|
26
45
|
GetTokens({ category?, source?, search? }?): Token[]
|
|
27
|
-
GetModes(): { settings, modes }
|
|
46
|
+
GetModes(design?): { settings, variables, viewports, modes, designs } // modes resolved; what each design shows
|
|
28
47
|
Selection(): { design?, node?, mode? } // what the person has selected
|
|
29
48
|
|
|
30
49
|
// Designs
|
|
@@ -34,7 +53,7 @@ DeleteDesign(design): void
|
|
|
34
53
|
|
|
35
54
|
// Nodes
|
|
36
55
|
Insert(parent, node, { index?, slot? }?): string // returns the new node's id
|
|
37
|
-
Update(path, change, { state? }?): void // change: name, text, tag, hidden, props, args, style, component
|
|
56
|
+
Update(path, change, { state? }?): void // change: name, text, tag, hidden, props, args, style, unset, component
|
|
38
57
|
Replace(path, node): string
|
|
39
58
|
Move(path, parent, { index?, slot? }?): void // within one design
|
|
40
59
|
Copy(path, parent, { index?, slot?, name? }?): string
|
|
@@ -82,11 +101,27 @@ canvas, the main mode and the modes beside it. Start here to find ids.
|
|
|
82
101
|
### Get
|
|
83
102
|
|
|
84
103
|
`Get("dashboard", { depth: 2 })` returns the design's root with two levels of children; deeper ones show as `"…"`. Read
|
|
85
|
-
a branch with a node path, as in `Get("dashboard/Stats", { depth: 2 })`.
|
|
86
|
-
|
|
104
|
+
a branch with a node path, as in `Get("dashboard/Stats", { depth: 2 })`. Without `state` or `mode`, Get returns the
|
|
105
|
+
nodes as stored, which is what `Update` and the other edits change.
|
|
106
|
+
|
|
107
|
+
With `state`, `mode` or both, Get returns the nodes as that frame of the board draws them: the design's variant options
|
|
108
|
+
(`view.variants`), its params' defaults filled into bound values, its responsive patches for the mode, then the state's
|
|
109
|
+
patches. `mode` is a mode id from `GetModes()`, as Screenshot takes. A state alone is read in the design's main mode,
|
|
110
|
+
and a mode alone in the state the design shows. A bound value comes back filled in, with its `bind` kept so you can tell
|
|
111
|
+
it comes from a param. An instance comes back as its own node with the args it's drawn with; read what's inside it by
|
|
112
|
+
its path, `design/instance/inner`, which only works with `state` or `mode`.
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
// A component whose title is bound to its "title" param: stored, then as the board shows it.
|
|
116
|
+
Print(Get("card/title").text) // "Title"
|
|
117
|
+
Print(Get("card/title", { state: "base" }).text) // "Card title", the param's default
|
|
118
|
+
// A copy of the card on a screen, inside the instance, in a state and on mobile.
|
|
119
|
+
Print(Get("profile/person/title", { state: "selected", mode: "mobile-light" }).text) // "Selected"
|
|
120
|
+
```
|
|
87
121
|
|
|
88
122
|
`Get(path, visit)` walks the nodes under `path`, or every design without one, and returns what `visit` returns for each.
|
|
89
|
-
`visit(node, { design, path, depth, index, parent, skipChildren })`. Return `undefined` to collect nothing.
|
|
123
|
+
`visit(node, { design, path, depth, index, parent, skipChildren })`. Return `undefined` to collect nothing. Given a
|
|
124
|
+
path, `Get(path, visit, { state, mode })` visits the nodes as that frame draws them, as above.
|
|
90
125
|
|
|
91
126
|
```js
|
|
92
127
|
// Every Button in the project, with where it is.
|
|
@@ -169,11 +204,14 @@ const form = Insert("settings/Content", {
|
|
|
169
204
|
|
|
170
205
|
### Update
|
|
171
206
|
|
|
172
|
-
Changes a node. `change` takes `name`, `text`, `tag`, `hidden`, `props`, `args` (an instance's values), `style`
|
|
173
|
-
`component`. `props`, `args` and `style` merge with what's there; set a key to `null` to remove it.
|
|
207
|
+
Changes a node. `change` takes `name`, `text`, `tag`, `hidden`, `props`, `args` (an instance's values), `style`, `unset`
|
|
208
|
+
and `component`. `props`, `args` and `style` merge with what's there; set a key to `null` to remove it.
|
|
174
209
|
|
|
175
|
-
With `{ state: "loading" }` the change belongs to that state instead of the base design
|
|
176
|
-
`hidden`, `text`, `props` and `
|
|
210
|
+
With `{ state: "loading" }` the change belongs to that state instead of the base design, and `null` takes back the
|
|
211
|
+
state's change to that key. A state can change only `hidden`, `text`, `props`, `style` and `unset`. To turn off in one
|
|
212
|
+
state what the base sets, list it in `unset`:
|
|
213
|
+
`Update(button, { unset: { props: ["disabled"], style: ["gap"] } }, { state: "ready" })`. In the base design, `unset`
|
|
214
|
+
removes the keys as `null` does.
|
|
177
215
|
|
|
178
216
|
### Replace, Move, Copy and Delete
|
|
179
217
|
|
|
@@ -239,6 +277,18 @@ ExtractComponent("team-members", rows.nodes, { name: "Member row", params: { [ro
|
|
|
239
277
|
`SetModes(settings)` replaces the project's modes: take `GetModes().settings`, change it, and pass it back. Modes are
|
|
240
278
|
shared by every design.
|
|
241
279
|
|
|
280
|
+
`GetModes()` also gives `viewports`, the ids `Responsive` takes, each with its width and window height; `modes`, the ids
|
|
281
|
+
`Screenshot` and the checks take, each with its `values` (one option per variable), width, window height, classes and
|
|
282
|
+
attributes; and `designs`, each design's `state` (or `states` when it shows every state), `main` mode and `beside`
|
|
283
|
+
modes. `GetModes("dashboard")` gives just that design, with each frame's width, fixed `height` if it has one, and
|
|
284
|
+
`window`.
|
|
285
|
+
|
|
286
|
+
```js
|
|
287
|
+
const { viewports, designs } = GetModes("dashboard")
|
|
288
|
+
Print(viewports.map((viewport) => viewport.id)) // ["desktop", "tablet", "mobile"]: for Responsive
|
|
289
|
+
Print(designs[0].frames.map((frame) => `${frame.mode} ${frame.window.width}x${frame.window.height}`))
|
|
290
|
+
```
|
|
291
|
+
|
|
242
292
|
`SetTokens` changes token values in the project's stylesheets: `SetTokens({ primary: "oklch(0.55 0.2 260)" })` for every
|
|
243
293
|
theme, or `SetTokens({ primary: { default: "…", dark: "…" } })` per theme. Every frame shows the new values at once, and
|
|
244
294
|
saving writes them into the project's code, so do it only when the person asks to change the design system, not to style
|
|
@@ -250,15 +300,34 @@ one design. They're undone with the call's other changes.
|
|
|
250
300
|
result. `state` and `mode` take an id, a list, or `"all"`; `mode` also takes `"shown"`, the modes the design shows. The
|
|
251
301
|
defaults are the state on the canvas and the main mode.
|
|
252
302
|
|
|
303
|
+
Frames draw as the canvas does: instances of a library's component designs (from its `.lib.par`) draw that design, and
|
|
304
|
+
token values not yet saved show, the person's edits and this call's own `SetTokens` alike, so
|
|
305
|
+
`SetTokens({ primary: "red" }); await Screenshot(id)` shows the new value in the same call.
|
|
306
|
+
|
|
253
307
|
`Measure` lays out one frame and returns its size, whether it overflows sideways, and every node's box (`clipped` when a
|
|
254
308
|
parent cuts it off). `Problems` checks frames for render errors, overflow, clipping and unknown components.
|
|
255
309
|
`Accessibility` returns axe-core's findings per node and frame, with the rule, its impact and what's wrong.
|
|
256
310
|
|
|
311
|
+
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
|
|
312
|
+
height for the viewport, else the viewport's screen height, else the device nearest its width. So `100vh` and window
|
|
313
|
+
size checks measure the same in `Measure` and `Screenshot` as on the canvas, even when the frame grows past that height.
|
|
314
|
+
|
|
257
315
|
## The canvas
|
|
258
316
|
|
|
259
317
|
`FindEmptySpace({ width, height })` finds room for a new board, to the right of the others unless `direction: "below"`,
|
|
260
318
|
or next to the design named in `near`. `Overlaps()` lists boards that collide, titles included; move one with
|
|
261
|
-
`UpdateDesign(design, { x, y })`.
|
|
319
|
+
`UpdateDesign(design, { x, y })`. A board is as big as the canvas last drew it. One it hasn't drawn yet is sized from
|
|
320
|
+
its frames, laid out as the canvas lays them out: each shown mode's frame size (`frames` on the design, else the mode's
|
|
321
|
+
width), companions shrunk to 640 wide, and a frame without a fixed height as tall as its window. Those overlaps say
|
|
322
|
+
`estimated: true`.
|
|
323
|
+
|
|
324
|
+
```js
|
|
325
|
+
// With the default modes: a 1280 main frame, the dark one beside it at half size and the 390 phone, 2505 wide in all.
|
|
326
|
+
const dashboard = CreateDesign({ name: "Dashboard", x: 0, y: 0 })
|
|
327
|
+
UpdateDesign(dashboard, { showModes: ["desktop-dark", "mobile-light"] })
|
|
328
|
+
CreateDesign({ name: "Settings", x: 2200, y: 0 })
|
|
329
|
+
Print(Overlaps(dashboard)) // Dashboard and Settings, 305 across, estimated: true
|
|
330
|
+
```
|
|
262
331
|
|
|
263
332
|
## Examples
|
|
264
333
|
|
|
@@ -30,9 +30,11 @@ Modes are the project's, shared by every design. Each mode picks one option of e
|
|
|
30
30
|
- **Theme:** a class on the frame, such as `dark`, that switches the project's tokens.
|
|
31
31
|
- **Locale:** `lang` and `dir` attributes, for languages and right-to-left scripts.
|
|
32
32
|
|
|
33
|
-
`
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
`get_modes` (`parity modes`) lists them, and `get_modes` with a design gives its frames: run it before a responsive
|
|
34
|
+
change, since the ids `Responsive` takes are viewports (`mobile`) and the ids screenshots take are modes
|
|
35
|
+
(`mobile-light`). `GetModes()` gives the same in a snippet. A design shows its main mode and, beside it, any others you
|
|
36
|
+
choose with `UpdateDesign(design, { showModes: ["desktop-dark", "mobile-light"] })`. Show the modes that matter for the
|
|
37
|
+
design: a dashboard in desktop and mobile, a component in light and dark.
|
|
36
38
|
|
|
37
39
|
## Viewports and responsive patches
|
|
38
40
|
|