@parity-app/cli 0.4.0 → 0.5.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/dist/bin.mjs +254 -34
- package/dist/{client-Cf66-n3R.mjs → client-DgpoM3e7.mjs} +84 -12
- package/dist/index.d.mts +97 -7
- package/dist/index.mjs +1 -1
- package/dist/{src-CYEKdXm_.mjs → src-BryimTPw.mjs} +132 -3
- package/dist/{src-CJUvccJO.mjs → src-BwTWa2_Y.mjs} +1 -1
- package/dist/{src-DaCYZuwb.mjs → src-CpoDOrn3.mjs} +10 -5
- package/dist/src-CqmC2-Em.mjs +3 -0
- package/dist/{src-y3hdgbK2.mjs → src-D85OVBQL.mjs} +1 -1
- package/package.json +1 -1
- package/skills/SKILL.md +3 -2
- package/skills/execute.md +120 -6
- package/skills/guide/checking.md +4 -2
- package/skills/guide/components.md +3 -1
- package/skills/guide/states-and-modes.md +14 -0
- package/dist/src-DgI96EpD.mjs +0 -3
package/dist/bin.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { _ as errorDetails, a as describeExec, c as describeProps, f as setCaller, g as errorCodes, h as closest, i as describeConsole, l as describeStatus, m as CodedError, n as describeAccessibility, o as describeModes, p as mcpCallerName, r as describeComponents, s as describeProblems, t as call, u as describeTokens, v as readErrorDetails } from "./client-
|
|
2
|
+
import { _ as errorDetails, a as describeExec, c as describeProps, f as setCaller, g as errorCodes, h as closest, i as describeConsole, l as describeStatus, m as CodedError, n as describeAccessibility, o as describeModes, p as mcpCallerName, r as describeComponents, s as describeProblems, t as call, u as describeTokens, v as readErrorDetails } from "./client-DgpoM3e7.mjs";
|
|
3
3
|
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, join, normalize, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
@@ -99,6 +99,120 @@ function structuredExec(result) {
|
|
|
99
99
|
screenshots: result.screenshots.map(({ png: _png, ...shot }) => shot)
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
|
+
const screenshotSchema = {
|
|
103
|
+
type: "object",
|
|
104
|
+
properties: {
|
|
105
|
+
path: {
|
|
106
|
+
type: "string",
|
|
107
|
+
description: "Where the PNG was saved."
|
|
108
|
+
},
|
|
109
|
+
design: { type: "string" },
|
|
110
|
+
state: { type: "string" },
|
|
111
|
+
mode: { type: "string" }
|
|
112
|
+
},
|
|
113
|
+
required: [
|
|
114
|
+
"path",
|
|
115
|
+
"design",
|
|
116
|
+
"state",
|
|
117
|
+
"mode"
|
|
118
|
+
]
|
|
119
|
+
};
|
|
120
|
+
const strings$1 = {
|
|
121
|
+
type: "array",
|
|
122
|
+
items: { type: "string" }
|
|
123
|
+
};
|
|
124
|
+
const discardedSchema = {
|
|
125
|
+
type: "array",
|
|
126
|
+
description: "Patches each Replace threw away with the node it swapped out. Present only when there were some; reapply what's still wanted to the replacement with Responsive, Update with a state, or UpdateState.",
|
|
127
|
+
items: {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: {
|
|
130
|
+
design: { type: "string" },
|
|
131
|
+
replaced: {
|
|
132
|
+
type: "string",
|
|
133
|
+
description: "The node Replace swapped out."
|
|
134
|
+
},
|
|
135
|
+
replacement: {
|
|
136
|
+
type: "string",
|
|
137
|
+
description: "The node that took its place."
|
|
138
|
+
},
|
|
139
|
+
nodes: {
|
|
140
|
+
type: "array",
|
|
141
|
+
items: {
|
|
142
|
+
type: "object",
|
|
143
|
+
properties: {
|
|
144
|
+
id: {
|
|
145
|
+
type: "string",
|
|
146
|
+
description: "The old node's id, or instance/inner path."
|
|
147
|
+
},
|
|
148
|
+
name: { type: "string" },
|
|
149
|
+
viewports: {
|
|
150
|
+
...strings$1,
|
|
151
|
+
description: "Viewport ids it had responsive patches for."
|
|
152
|
+
},
|
|
153
|
+
states: {
|
|
154
|
+
...strings$1,
|
|
155
|
+
description: "State ids it had patches in."
|
|
156
|
+
},
|
|
157
|
+
variants: {
|
|
158
|
+
...strings$1,
|
|
159
|
+
description: "Variant options it had patches in, as param=option."
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
required: ["id"]
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
required: [
|
|
167
|
+
"design",
|
|
168
|
+
"replaced",
|
|
169
|
+
"replacement",
|
|
170
|
+
"nodes"
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const warningGroupsSchema = {
|
|
175
|
+
type: "array",
|
|
176
|
+
description: "The warnings as data, in the same order: each finding once, with how many places and which. Errors come first, then warnings, then suggestions.",
|
|
177
|
+
items: {
|
|
178
|
+
type: "object",
|
|
179
|
+
properties: {
|
|
180
|
+
category: {
|
|
181
|
+
type: "string",
|
|
182
|
+
description: "Stable: render_failed, unknown_component, unknown_component_design, unknown_prop, invalid_option, not_a_token, not_a_length_token, overlap, kind_changed or unnamed_layout."
|
|
183
|
+
},
|
|
184
|
+
severity: {
|
|
185
|
+
type: "string",
|
|
186
|
+
enum: [
|
|
187
|
+
"error",
|
|
188
|
+
"warning",
|
|
189
|
+
"suggestion"
|
|
190
|
+
],
|
|
191
|
+
description: "error: a render failed, so a check or screenshot is missing. suggestion: worth doing, not wrong."
|
|
192
|
+
},
|
|
193
|
+
message: {
|
|
194
|
+
type: "string",
|
|
195
|
+
description: "What's wrong and what to do about it."
|
|
196
|
+
},
|
|
197
|
+
count: {
|
|
198
|
+
type: "integer",
|
|
199
|
+
minimum: 1,
|
|
200
|
+
description: "How many places it was found."
|
|
201
|
+
},
|
|
202
|
+
at: {
|
|
203
|
+
...strings$1,
|
|
204
|
+
description: "Every place: a node's design/node path, or a frame. Empty for a whole board."
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
required: [
|
|
208
|
+
"category",
|
|
209
|
+
"severity",
|
|
210
|
+
"message",
|
|
211
|
+
"count",
|
|
212
|
+
"at"
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
};
|
|
102
216
|
/** Execute's and screenshot's structured result. */
|
|
103
217
|
const execOutputSchema = {
|
|
104
218
|
type: "object",
|
|
@@ -114,34 +228,19 @@ const execOutputSchema = {
|
|
|
114
228
|
description: "Lines from Print."
|
|
115
229
|
},
|
|
116
230
|
warnings: {
|
|
117
|
-
|
|
118
|
-
|
|
231
|
+
...strings$1,
|
|
232
|
+
description: "One line per warning group, places first."
|
|
119
233
|
},
|
|
234
|
+
warningGroups: warningGroupsSchema,
|
|
120
235
|
created: {
|
|
121
236
|
type: "object",
|
|
122
237
|
additionalProperties: { type: "string" },
|
|
123
238
|
description: "Names of designs and nodes created, mapped to their ids."
|
|
124
239
|
},
|
|
240
|
+
discarded: discardedSchema,
|
|
125
241
|
screenshots: {
|
|
126
242
|
type: "array",
|
|
127
|
-
items:
|
|
128
|
-
type: "object",
|
|
129
|
-
properties: {
|
|
130
|
-
path: {
|
|
131
|
-
type: "string",
|
|
132
|
-
description: "Where the PNG was saved."
|
|
133
|
-
},
|
|
134
|
-
design: { type: "string" },
|
|
135
|
-
state: { type: "string" },
|
|
136
|
-
mode: { type: "string" }
|
|
137
|
-
},
|
|
138
|
-
required: [
|
|
139
|
-
"path",
|
|
140
|
-
"design",
|
|
141
|
-
"state",
|
|
142
|
-
"mode"
|
|
143
|
-
]
|
|
144
|
-
}
|
|
243
|
+
items: screenshotSchema
|
|
145
244
|
},
|
|
146
245
|
error: operationErrorSchema
|
|
147
246
|
},
|
|
@@ -287,8 +386,8 @@ const executeOperation = {
|
|
|
287
386
|
*/
|
|
288
387
|
async function runCheck(library, options) {
|
|
289
388
|
if (!library) throw new Error("parity lib check <library> [--tab t] [--mode m]");
|
|
290
|
-
const { applyLibraryEdits, componentNode, generateId, librarySource, parseLibraryEdits, withLibraryContext } = await import("./src-
|
|
291
|
-
const { registryDir, registryEntries } = await import("./src-
|
|
389
|
+
const { applyLibraryEdits, componentNode, generateId, librarySource, parseLibraryEdits, withLibraryContext } = await import("./src-CqmC2-Em.mjs");
|
|
390
|
+
const { registryDir, registryEntries } = await import("./src-BwTWa2_Y.mjs");
|
|
292
391
|
const entry = (await registryEntries()).find((item) => item.manifest.id === library);
|
|
293
392
|
if (!entry) throw new Error(`The registry has no library "${library}"`);
|
|
294
393
|
let manifest = entry.manifest;
|
|
@@ -416,8 +515,8 @@ function describeCheck(report) {
|
|
|
416
515
|
* because it reads the project's files and its own copy of TypeScript.
|
|
417
516
|
*/
|
|
418
517
|
async function runLib(packageName, options) {
|
|
419
|
-
const { appManifestPath, generateLibrary, manifestText, mergeManifest, reviseLibrary } = await import("./src-
|
|
420
|
-
const { covers, registryDir, registryEntries } = await import("./src-
|
|
518
|
+
const { appManifestPath, generateLibrary, manifestText, mergeManifest, reviseLibrary } = await import("./src-CpoDOrn3.mjs");
|
|
519
|
+
const { covers, registryDir, registryEntries } = await import("./src-BwTWa2_Y.mjs");
|
|
421
520
|
const spec = packageName === void 0 ? void 0 : packageSpec(packageName);
|
|
422
521
|
let root = resolve(options.root ?? ".");
|
|
423
522
|
if (spec && (spec.version !== void 0 || options.registry && !options.root)) {
|
|
@@ -428,7 +527,7 @@ async function runLib(packageName, options) {
|
|
|
428
527
|
console.error(`Installing ${spec.name} ${version} on its own…`);
|
|
429
528
|
root = await installedVersion(spec.name, version);
|
|
430
529
|
}
|
|
431
|
-
const { parseLibraryEdits } = await import("./src-
|
|
530
|
+
const { parseLibraryEdits } = await import("./src-CpoDOrn3.mjs");
|
|
432
531
|
const trialEdits = options.edits ? parseLibraryEdits(JSON.parse(await readFile(resolve(options.edits), "utf8"))) : void 0;
|
|
433
532
|
const recordedId = options.registry && spec ? options.id ?? (await registryEntries()).find((entry) => entry.manifest.package?.name === spec.name)?.manifest.id : void 0;
|
|
434
533
|
const appErrors = recordedId ? await readAppErrors(registryDir(), recordedId) : void 0;
|
|
@@ -778,6 +877,13 @@ const frameSchema = {
|
|
|
778
877
|
state: string,
|
|
779
878
|
mode: string
|
|
780
879
|
};
|
|
880
|
+
const number = { type: "number" };
|
|
881
|
+
const boxSchema = object({
|
|
882
|
+
x: number,
|
|
883
|
+
y: number,
|
|
884
|
+
width: number,
|
|
885
|
+
height: number
|
|
886
|
+
});
|
|
781
887
|
const designSchema = object({
|
|
782
888
|
id: string,
|
|
783
889
|
name: string,
|
|
@@ -899,9 +1005,44 @@ const problemsSchema = object({
|
|
|
899
1005
|
type: "array",
|
|
900
1006
|
items: object({
|
|
901
1007
|
...frameSchema,
|
|
1008
|
+
kind: {
|
|
1009
|
+
type: "string",
|
|
1010
|
+
enum: [
|
|
1011
|
+
"render-error",
|
|
1012
|
+
"unknown-component",
|
|
1013
|
+
"content-wider",
|
|
1014
|
+
"frame-overflow",
|
|
1015
|
+
"clipped"
|
|
1016
|
+
]
|
|
1017
|
+
},
|
|
902
1018
|
problem: string,
|
|
903
|
-
node: string
|
|
904
|
-
|
|
1019
|
+
node: string,
|
|
1020
|
+
name: string,
|
|
1021
|
+
box: boxSchema,
|
|
1022
|
+
container: object({
|
|
1023
|
+
node: string,
|
|
1024
|
+
name: string,
|
|
1025
|
+
box: boxSchema
|
|
1026
|
+
}, ["node", "name"]),
|
|
1027
|
+
overflow: object({
|
|
1028
|
+
left: number,
|
|
1029
|
+
right: number,
|
|
1030
|
+
top: number,
|
|
1031
|
+
bottom: number
|
|
1032
|
+
}, [
|
|
1033
|
+
"left",
|
|
1034
|
+
"right",
|
|
1035
|
+
"top",
|
|
1036
|
+
"bottom"
|
|
1037
|
+
])
|
|
1038
|
+
}, [
|
|
1039
|
+
"kind",
|
|
1040
|
+
"node",
|
|
1041
|
+
"name",
|
|
1042
|
+
"box",
|
|
1043
|
+
"container",
|
|
1044
|
+
"overflow"
|
|
1045
|
+
])
|
|
905
1046
|
},
|
|
906
1047
|
frames: integer,
|
|
907
1048
|
warnings: strings
|
|
@@ -1002,8 +1143,86 @@ const propsSchema = object({
|
|
|
1002
1143
|
"default",
|
|
1003
1144
|
"required"
|
|
1004
1145
|
])
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1146
|
+
},
|
|
1147
|
+
composition: object({
|
|
1148
|
+
role: {
|
|
1149
|
+
type: "string",
|
|
1150
|
+
enum: [
|
|
1151
|
+
"provider",
|
|
1152
|
+
"wrapper",
|
|
1153
|
+
"component"
|
|
1154
|
+
],
|
|
1155
|
+
description: "provider draws nothing and gives parts inside it context; wrapper draws and holds other parts."
|
|
1156
|
+
},
|
|
1157
|
+
parents: object({
|
|
1158
|
+
status: {
|
|
1159
|
+
type: "string",
|
|
1160
|
+
enum: [
|
|
1161
|
+
"listed",
|
|
1162
|
+
"none",
|
|
1163
|
+
"unknown"
|
|
1164
|
+
],
|
|
1165
|
+
description: "listed: insert it inside chain; none: it needs nothing; unknown: nothing says, see reason."
|
|
1166
|
+
},
|
|
1167
|
+
chain: {
|
|
1168
|
+
type: "array",
|
|
1169
|
+
description: "Outermost first.",
|
|
1170
|
+
items: object({
|
|
1171
|
+
component: string,
|
|
1172
|
+
optional: boolean_
|
|
1173
|
+
}, ["optional"])
|
|
1174
|
+
},
|
|
1175
|
+
from: {
|
|
1176
|
+
...string,
|
|
1177
|
+
description: "What said so, such as the library's examples or the error it throws."
|
|
1178
|
+
},
|
|
1179
|
+
reason: string
|
|
1180
|
+
}, [
|
|
1181
|
+
"chain",
|
|
1182
|
+
"from",
|
|
1183
|
+
"reason"
|
|
1184
|
+
]),
|
|
1185
|
+
holds: {
|
|
1186
|
+
...strings,
|
|
1187
|
+
description: "Parts that go directly inside it."
|
|
1188
|
+
},
|
|
1189
|
+
slots: {
|
|
1190
|
+
type: "array",
|
|
1191
|
+
items: object({
|
|
1192
|
+
name: string,
|
|
1193
|
+
key: string,
|
|
1194
|
+
kind: {
|
|
1195
|
+
type: "string",
|
|
1196
|
+
enum: [
|
|
1197
|
+
"children",
|
|
1198
|
+
"element",
|
|
1199
|
+
"render"
|
|
1200
|
+
]
|
|
1201
|
+
}
|
|
1202
|
+
})
|
|
1203
|
+
},
|
|
1204
|
+
example: object({
|
|
1205
|
+
node: {
|
|
1206
|
+
type: "object",
|
|
1207
|
+
description: "A node Insert takes as it is: the component inside its parents."
|
|
1208
|
+
},
|
|
1209
|
+
status: {
|
|
1210
|
+
type: "string",
|
|
1211
|
+
enum: [
|
|
1212
|
+
"drawn",
|
|
1213
|
+
"failing",
|
|
1214
|
+
"untried"
|
|
1215
|
+
],
|
|
1216
|
+
description: "Whether the component has drawn on the canvas."
|
|
1217
|
+
},
|
|
1218
|
+
reason: string
|
|
1219
|
+
}, ["reason"])
|
|
1220
|
+
})
|
|
1221
|
+
}, [
|
|
1222
|
+
"path",
|
|
1223
|
+
"import",
|
|
1224
|
+
"composition"
|
|
1225
|
+
]);
|
|
1007
1226
|
const viewportSchema = object({
|
|
1008
1227
|
id: string,
|
|
1009
1228
|
name: string,
|
|
@@ -1334,13 +1553,13 @@ const tools = [
|
|
|
1334
1553
|
}
|
|
1335
1554
|
},
|
|
1336
1555
|
operationTool("screenshot", "Capture a design in any of its states and modes, rendered offscreen at real widths. state and mode take an id, a list, or \"all\"; mode also takes \"shown\".", operations.screenshot, (result) => [text(describeExec(result)), ...result.screenshots.map((shot) => image(shot.png))]),
|
|
1337
|
-
operationTool("find_problems", "Find layout problems in a design, rendered offscreen in every state and the modes it shows unless you narrow it: render errors,
|
|
1338
|
-
operationTool("check_accessibility", "Check a design's accessibility with axe-core against WCAG 2.2 AA, rendered offscreen in every state and the modes it shows unless you narrow it. Lists each rule broken, worst first, with the layers that break it (ids you can pass to execute), the frames, what's wrong and a link on how to fix it. state and mode take an id, a list, or \"all\"; mode also takes \"shown\". Automated checks find some issues, not all.", operations.accessibility, (result) => [text(describeAccessibility(result))]),
|
|
1556
|
+
operationTool("find_problems", "Find layout problems in a design, rendered offscreen in every state and the modes it shows unless you narrow it: render errors, nodes that stick out of the frame, nodes cut off by an ancestor that clips, and components that aren't the project's. Each layout finding names the node and what it overflows, with both boxes and how far past each edge. Run it after changes, with screenshot. state and mode take an id, a list, or \"all\"; mode also takes \"shown\".", operations.problems, (result) => [text(describeProblems(result))]),
|
|
1557
|
+
operationTool("check_accessibility", "Check a design's accessibility with axe-core against WCAG 2.2 AA, rendered offscreen in every state and the modes it shows unless you narrow it. Lists each rule broken, worst first, with the layers that break it (ids you can pass to execute), the frames, what's wrong and a link on how to fix it. A contrast failure names the color token to set as style.foreground so it passes in every frame checked, when one does. state and mode take an id, a list, or \"all\"; mode also takes \"shown\". Automated checks find some issues, not all.", operations.accessibility, (result) => [text(describeAccessibility(result))]),
|
|
1339
1558
|
operationTool("get_console", "Read what the project's own code wrote to the console: errors, warnings and logs from its components on the canvas, including while the person interacts with them, and from offscreen renders such as screenshots. Oldest first; the same message several times in a row is counted once. clear: true empties it after reading, so the next read shows only what's new.", operations.console, (result) => [text(describeConsole(result))]),
|
|
1340
1559
|
operationTool("open_project", "Open a project folder in a new Parity tab, or switch to it if it's already open.", operations.open, json),
|
|
1341
1560
|
operationTool("get_tokens", "Read tokens filtered by category, source or name, with values in every theme.", operations.tokens, (result) => [text(describeTokens(result))]),
|
|
1342
1561
|
operationTool("get_components", "List project components, libraries, icon sets and exports that were left out or failed to render.", operations.components, (result) => [text(describeComponents(result))]),
|
|
1343
|
-
operationTool("get_props", "Read a component's props from source. all includes attributes it passes through.", operations.props, (result) => [text(describeProps(result))]),
|
|
1562
|
+
operationTool("get_props", "Read a component's props from source, and its composition: the parents or provider it goes inside, the parts it holds, its slots and an example node to insert. all includes attributes it passes through.", operations.props, (result) => [text(describeProps(result))]),
|
|
1344
1563
|
{
|
|
1345
1564
|
name: "read_skill",
|
|
1346
1565
|
description: "Read the Parity design skill or a file it references.",
|
|
@@ -1465,7 +1684,8 @@ const usage = `parity: drive the Parity app from a terminal or an agent
|
|
|
1465
1684
|
unless --limit says
|
|
1466
1685
|
parity components [--tab t] the components designs can use, by library
|
|
1467
1686
|
parity props <Component> [--all] [--tab t]
|
|
1468
|
-
a component's props from its source
|
|
1687
|
+
a component's props from its source, then what it goes inside and holds,
|
|
1688
|
+
with an example to insert; --all adds the attributes it passes on
|
|
1469
1689
|
parity lib [package] [--root dir] [--out file|-] [--registry] [--id id] [--name n] [--range r] [--json]
|
|
1470
1690
|
[--only new|all|A,B] [--edits file] [--no-examples] [--no-tags]
|
|
1471
1691
|
write a .lib.par: a package's components, tokens, breakpoints, icons and
|
|
@@ -468,10 +468,45 @@ function describeExec(result) {
|
|
|
468
468
|
if (result.output.length > 0) lines.push("", "Output:", ...result.output);
|
|
469
469
|
const created = Object.entries(result.created);
|
|
470
470
|
if (created.length > 0) lines.push("", "Created:", ...created.map(([name, id]) => ` ${name} = ${id}`));
|
|
471
|
-
|
|
471
|
+
lines.push(...describeWarnings(result));
|
|
472
|
+
if (result.discarded?.length) {
|
|
473
|
+
lines.push("", "Discarded by Replace (reapply what you still want to the new node):");
|
|
474
|
+
for (const { design, replaced, replacement, nodes } of result.discarded) {
|
|
475
|
+
lines.push(` ${design}/${replaced}, now ${replacement}:`);
|
|
476
|
+
for (const node of nodes) {
|
|
477
|
+
const where = [
|
|
478
|
+
node.viewports && `viewports ${node.viewports.join(", ")}`,
|
|
479
|
+
node.states && `states ${node.states.join(", ")}`,
|
|
480
|
+
node.variants && `variants ${node.variants.join(", ")}`
|
|
481
|
+
].filter(Boolean);
|
|
482
|
+
lines.push(` ${node.name ? `${node.name} (${node.id})` : node.id}: ${where.join("; ")}`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
472
486
|
if (result.screenshots.length > 0) lines.push("", "Screenshots:", ...result.screenshots.map((shot) => ` ${shot.design} / ${shot.state} / ${shot.mode}: ${shot.path}`));
|
|
473
487
|
return lines.join("\n");
|
|
474
488
|
}
|
|
489
|
+
const warningHeadings = {
|
|
490
|
+
error: "Render failures (these frames weren't checked):",
|
|
491
|
+
warning: "Warnings (fix these next):",
|
|
492
|
+
suggestion: "Suggestions:"
|
|
493
|
+
};
|
|
494
|
+
/**
|
|
495
|
+
* The warnings under a heading for each severity, so a failed render doesn't read like a naming tip. The lines are
|
|
496
|
+
* the app's, one per group in the groups' order; an app older than PAR-460 sends no groups, so they're all warnings.
|
|
497
|
+
*/
|
|
498
|
+
function describeWarnings(result) {
|
|
499
|
+
const lines = [];
|
|
500
|
+
for (const severity of [
|
|
501
|
+
"error",
|
|
502
|
+
"warning",
|
|
503
|
+
"suggestion"
|
|
504
|
+
]) {
|
|
505
|
+
const matching = result.warnings.filter((_, index) => (result.warningGroups?.[index]?.severity ?? "warning") === severity);
|
|
506
|
+
if (matching.length > 0) lines.push("", warningHeadings[severity], ...matching.map((warning) => ` ${warning}`));
|
|
507
|
+
}
|
|
508
|
+
return lines;
|
|
509
|
+
}
|
|
475
510
|
/** Where a tab's edits stand: the revision, what's applied and undoable, what's on disk, and what failed to write. */
|
|
476
511
|
function describeStatus(result) {
|
|
477
512
|
const { tab, revision, unsaved, undo, redo, save, errors } = result;
|
|
@@ -563,6 +598,7 @@ function describeAccessibility(result) {
|
|
|
563
598
|
name: finding.name ?? "The design",
|
|
564
599
|
nodes: finding.node ? [finding.node] : [],
|
|
565
600
|
detail: finding.detail,
|
|
601
|
+
...fixLine(finding) ? { fix: fixLine(finding) } : {},
|
|
566
602
|
frames: []
|
|
567
603
|
};
|
|
568
604
|
layer.frames.push({
|
|
@@ -586,6 +622,7 @@ function describeAccessibility(result) {
|
|
|
586
622
|
const where = layer.frames.length === frames && frames > 1 ? "every frame" : frameList(layer.frames);
|
|
587
623
|
const ids = layer.nodes.length > 0 ? ` (${layer.nodes.join(", ")})` : "";
|
|
588
624
|
lines.push(` ${layer.name}${ids}, in ${where}: ${layer.detail}`);
|
|
625
|
+
if (layer.fix) lines.push(` ${layer.fix}`);
|
|
589
626
|
}
|
|
590
627
|
lines.push(` How to fix it: ${helpUrl}`);
|
|
591
628
|
}
|
|
@@ -593,12 +630,19 @@ function describeAccessibility(result) {
|
|
|
593
630
|
lines.push("", "Automated checks find some issues, not all. Try the design with a keyboard and a screen reader too.");
|
|
594
631
|
return lines.join("\n");
|
|
595
632
|
}
|
|
633
|
+
/** A contrast failure's fix in a sentence, naming the style to set so it can be passed to execute. */
|
|
634
|
+
function fixLine(finding) {
|
|
635
|
+
const fix = finding.contrast?.fix;
|
|
636
|
+
if (fix === "none") return "Fix: none of the project's color tokens pass on this background; change the background.";
|
|
637
|
+
return fix && `Fix: set style.foreground to "${fix.token}", which reaches ${fix.ratio}:1 in every frame checked.`;
|
|
638
|
+
}
|
|
596
639
|
/** A readable account of the layout problems in a design's frames, each once with the frames it's in. */
|
|
597
640
|
function describeProblems(result) {
|
|
598
641
|
const { design, problems, frames } = result;
|
|
599
642
|
const checked = `${design.name} (${design.id}), ${frames === 1 ? "1 frame" : `${frames} frames`}`;
|
|
600
643
|
const grouped = /* @__PURE__ */ new Map();
|
|
601
|
-
for (const { problem, node, state, mode } of problems) {
|
|
644
|
+
for (const { problem, node: id, name, state, mode } of problems) {
|
|
645
|
+
const node = id && name ? `${name} (${id})` : id;
|
|
602
646
|
const key = `${node ?? ""}\n${problem}`;
|
|
603
647
|
const entry = grouped.get(key) ?? {
|
|
604
648
|
problem,
|
|
@@ -671,19 +715,47 @@ function describeComponents({ components, iconLibraries = [], leftOut = [], draw
|
|
|
671
715
|
}
|
|
672
716
|
return lines.join("\n").trimEnd();
|
|
673
717
|
}
|
|
674
|
-
/** A component's props, with each variant's options and default. */
|
|
718
|
+
/** A component's props, with each variant's options and default, then what it needs around it and holds. */
|
|
675
719
|
function describeProps(result) {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
720
|
+
const lines = [];
|
|
721
|
+
if (!result.known) lines.push(`${result.component}'s props haven't been read from its source yet. Try again in a moment.`);
|
|
722
|
+
else {
|
|
723
|
+
const imported = result.import && result.import !== result.path ? `, imported from "${result.import}"` : "";
|
|
724
|
+
lines.push(`${result.component}, from ${result.path}${imported}:`);
|
|
725
|
+
for (const prop of result.props) {
|
|
726
|
+
const options = prop.options ? `: ${prop.options.map((option) => JSON.stringify(option)).join(", ")}` : "";
|
|
727
|
+
const fallback = prop.default === void 0 ? "" : `, default ${JSON.stringify(prop.default)}`;
|
|
728
|
+
lines.push(` ${prop.name} (${prop.kind}${prop.required ? ", required" : ""})${options}${fallback}`);
|
|
729
|
+
}
|
|
730
|
+
if (result.props.length === 0) lines.push(" No props of its own. --all lists the attributes it passes on.");
|
|
731
|
+
}
|
|
732
|
+
if (result.composition) lines.push("", ...describeComposition(result.composition));
|
|
685
733
|
return lines.join("\n");
|
|
686
734
|
}
|
|
735
|
+
const roles = {
|
|
736
|
+
provider: "a provider, which draws nothing and gives the parts inside it their context",
|
|
737
|
+
wrapper: "a wrapper, which other parts go inside",
|
|
738
|
+
component: "a component"
|
|
739
|
+
};
|
|
740
|
+
const exampleStatus = {
|
|
741
|
+
drawn: "drawn on the canvas",
|
|
742
|
+
failing: "failing on the canvas",
|
|
743
|
+
untried: "not drawn yet"
|
|
744
|
+
};
|
|
745
|
+
/** What a component needs around it, what it holds, its slots, and its example as JSON for `Insert`. */
|
|
746
|
+
function describeComposition({ role, parents, holds, slots, example }) {
|
|
747
|
+
const lines = [`It's ${roles[role]}.`];
|
|
748
|
+
if (parents.status === "listed") {
|
|
749
|
+
const chain = parents.chain.map((parent) => parent.optional ? `${parent.component} (optional)` : parent.component);
|
|
750
|
+
lines.push(`Goes inside, outermost first: ${chain.join(" > ")}${parents.from ? ` (from its ${parents.from})` : ""}`);
|
|
751
|
+
} else if (parents.status === "none") lines.push("Needs nothing around it.");
|
|
752
|
+
else lines.push(`What it needs around it is unknown: ${parents.reason}`);
|
|
753
|
+
if (holds.length > 0) lines.push(`Holds: ${holds.join(", ")}`);
|
|
754
|
+
if (slots.length > 0) lines.push(`Slots: ${slots.map((slot) => `${slot.key} (${slot.kind})`).join(", ")}`);
|
|
755
|
+
const reason = example.reason ? `: ${example.reason}` : "";
|
|
756
|
+
lines.push(`Example for Insert, ${exampleStatus[example.status]}${reason}:`, ` ${JSON.stringify(example.node)}`);
|
|
757
|
+
return lines;
|
|
758
|
+
}
|
|
687
759
|
/** Frames as state and mode ids, naming a mode once when they share it: "default, error in desktop-light". */
|
|
688
760
|
function frameList(frames) {
|
|
689
761
|
const modes = [...new Set(frames.map((frame) => frame.mode))];
|
package/dist/index.d.mts
CHANGED
|
@@ -63,15 +63,42 @@ interface Screenshot {
|
|
|
63
63
|
state: string;
|
|
64
64
|
mode: string;
|
|
65
65
|
}
|
|
66
|
+
/** What one `Replace` threw away: the patches keyed to the old node and what was inside it (PAR-455). */
|
|
67
|
+
interface Discarded {
|
|
68
|
+
design: string;
|
|
69
|
+
replaced: string;
|
|
70
|
+
replacement: string;
|
|
71
|
+
nodes: {
|
|
72
|
+
id: string;
|
|
73
|
+
name?: string;
|
|
74
|
+
viewports?: string[];
|
|
75
|
+
states?: string[];
|
|
76
|
+
variants?: string[];
|
|
77
|
+
}[];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Findings with the same category and message, once, with every place they were found (PAR-460). The app's
|
|
81
|
+
* `warnings` are these as lines, in the same order.
|
|
82
|
+
*/
|
|
83
|
+
interface WarningGroup {
|
|
84
|
+
category: string;
|
|
85
|
+
severity: "error" | "warning" | "suggestion";
|
|
86
|
+
message: string;
|
|
87
|
+
count: number;
|
|
88
|
+
at: string[];
|
|
89
|
+
}
|
|
66
90
|
/** The canonical result. Structured output is this without the screenshots' image data. */
|
|
67
91
|
interface ExecResponse {
|
|
68
92
|
ok: boolean;
|
|
69
93
|
applied: boolean;
|
|
70
94
|
output: string[];
|
|
71
95
|
warnings: string[];
|
|
96
|
+
warningGroups?: WarningGroup[];
|
|
72
97
|
/** Set when the call failed, with the editId to retry a snippet by. */
|
|
73
98
|
error?: OperationError;
|
|
74
99
|
created: Record<string, string>;
|
|
100
|
+
/** Set when a Replace threw away responsive, state or variant patches. */
|
|
101
|
+
discarded?: Discarded[];
|
|
75
102
|
screenshots: Screenshot[];
|
|
76
103
|
}
|
|
77
104
|
/**
|
|
@@ -225,6 +252,13 @@ export interface AccessibilityFinding {
|
|
|
225
252
|
node?: string;
|
|
226
253
|
name?: string;
|
|
227
254
|
detail: string;
|
|
255
|
+
/** For a contrast failure: the token to color the layer's text with, or `"none"` when none of the project's pass. */
|
|
256
|
+
contrast?: {
|
|
257
|
+
fix?: {
|
|
258
|
+
token: string;
|
|
259
|
+
ratio: number;
|
|
260
|
+
} | "none";
|
|
261
|
+
};
|
|
228
262
|
}
|
|
229
263
|
export interface AccessibilityResponse {
|
|
230
264
|
tab: {
|
|
@@ -253,15 +287,41 @@ export interface ProblemsResponse {
|
|
|
253
287
|
id: string;
|
|
254
288
|
name: string;
|
|
255
289
|
};
|
|
256
|
-
problems:
|
|
257
|
-
state: string;
|
|
258
|
-
mode: string;
|
|
259
|
-
problem: string;
|
|
260
|
-
node?: string;
|
|
261
|
-
}[];
|
|
290
|
+
problems: Problem[];
|
|
262
291
|
frames: number;
|
|
263
292
|
warnings: string[];
|
|
264
293
|
}
|
|
294
|
+
/** A box in a frame, in CSS pixels from its top left corner. */
|
|
295
|
+
export interface FrameBox {
|
|
296
|
+
x: number;
|
|
297
|
+
y: number;
|
|
298
|
+
width: number;
|
|
299
|
+
height: number;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* One problem in one frame. Layout problems (`frame-overflow`, `clipped`) name the node, what it overflows (the node
|
|
303
|
+
* that clips it, or the frame, which has none), both boxes, and how far past each edge it reaches.
|
|
304
|
+
*/
|
|
305
|
+
export interface Problem {
|
|
306
|
+
state: string;
|
|
307
|
+
mode: string;
|
|
308
|
+
kind?: "render-error" | "unknown-component" | "content-wider" | "frame-overflow" | "clipped";
|
|
309
|
+
problem: string;
|
|
310
|
+
node?: string;
|
|
311
|
+
name?: string;
|
|
312
|
+
box?: FrameBox;
|
|
313
|
+
container?: {
|
|
314
|
+
node?: string;
|
|
315
|
+
name?: string;
|
|
316
|
+
box: FrameBox;
|
|
317
|
+
};
|
|
318
|
+
overflow?: {
|
|
319
|
+
left?: number;
|
|
320
|
+
right?: number;
|
|
321
|
+
top?: number;
|
|
322
|
+
bottom?: number;
|
|
323
|
+
};
|
|
324
|
+
}
|
|
265
325
|
/** A readable account of the layout problems in a design's frames, each once with the frames it's in. */
|
|
266
326
|
export declare function describeProblems(result: ProblemsResponse): string;
|
|
267
327
|
export interface TokenInfo {
|
|
@@ -325,8 +385,38 @@ export interface PropsResponse {
|
|
|
325
385
|
default?: string | number | boolean | null;
|
|
326
386
|
required?: boolean;
|
|
327
387
|
}[];
|
|
388
|
+
/** What it needs around it and holds inside it, read from its library's metadata (PAR-454). */
|
|
389
|
+
composition?: Composition;
|
|
390
|
+
}
|
|
391
|
+
/** A component's composition, as `@parity-app/editor`'s `componentComposition` reads it. */
|
|
392
|
+
export interface Composition {
|
|
393
|
+
role: "provider" | "wrapper" | "component";
|
|
394
|
+
parents: {
|
|
395
|
+
status: "listed";
|
|
396
|
+
chain: {
|
|
397
|
+
component: string;
|
|
398
|
+
optional?: true;
|
|
399
|
+
}[];
|
|
400
|
+
from?: string;
|
|
401
|
+
} | {
|
|
402
|
+
status: "none";
|
|
403
|
+
} | {
|
|
404
|
+
status: "unknown";
|
|
405
|
+
reason: string;
|
|
406
|
+
};
|
|
407
|
+
holds: string[];
|
|
408
|
+
slots: {
|
|
409
|
+
name: string;
|
|
410
|
+
key: string;
|
|
411
|
+
kind: "children" | "element" | "render";
|
|
412
|
+
}[];
|
|
413
|
+
example: {
|
|
414
|
+
node: unknown;
|
|
415
|
+
status: "drawn" | "failing" | "untried";
|
|
416
|
+
reason?: string;
|
|
417
|
+
};
|
|
328
418
|
}
|
|
329
|
-
/** A component's props, with each variant's options and default. */
|
|
419
|
+
/** A component's props, with each variant's options and default, then what it needs around it and holds. */
|
|
330
420
|
export declare function describeProps(result: PropsResponse): string;
|
|
331
421
|
export interface ConsoleEntry {
|
|
332
422
|
level: "info" | "warning" | "error" | "debug";
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as call, n as describeAccessibility, r as describeComponents, i as describeConsole, a as describeExec, o as describeModes, s as describeProblems, c as describeProps, l as describeStatus, u as describeTokens, d as registry, f as setCaller } from "./client-
|
|
1
|
+
export { t as call, n as describeAccessibility, r as describeComponents, i as describeConsole, a as describeExec, o as describeModes, s as describeProblems, c as describeProps, l as describeStatus, u as describeTokens, d as registry, f as setCaller } from "./client-DgpoM3e7.mjs";
|
|
@@ -254,6 +254,53 @@ function generateId(taken, length = 5) {
|
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
256
|
// ../core/src/document.ts
|
|
257
|
+
const canvasObjectKinds = [
|
|
258
|
+
"table",
|
|
259
|
+
"operation",
|
|
260
|
+
"route"
|
|
261
|
+
];
|
|
262
|
+
const linkEndKinds = [
|
|
263
|
+
"screen",
|
|
264
|
+
"component",
|
|
265
|
+
...canvasObjectKinds
|
|
266
|
+
];
|
|
267
|
+
/** Each type of link, and the kinds it may join, from and to. */
|
|
268
|
+
const linkTypes = {
|
|
269
|
+
/** A screen shown at a route's URL. */
|
|
270
|
+
shows: {
|
|
271
|
+
from: ["screen"],
|
|
272
|
+
to: ["route"]
|
|
273
|
+
},
|
|
274
|
+
/** A screen, route or component that calls an operation. */
|
|
275
|
+
calls: {
|
|
276
|
+
from: [
|
|
277
|
+
"screen",
|
|
278
|
+
"route",
|
|
279
|
+
"component"
|
|
280
|
+
],
|
|
281
|
+
to: ["operation"]
|
|
282
|
+
},
|
|
283
|
+
/** An operation that reads a table. */
|
|
284
|
+
reads: {
|
|
285
|
+
from: ["operation"],
|
|
286
|
+
to: ["table"]
|
|
287
|
+
},
|
|
288
|
+
/** An operation that writes to a table. */
|
|
289
|
+
writes: {
|
|
290
|
+
from: ["operation"],
|
|
291
|
+
to: ["table"]
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
/** Whether a link of this type may join these two kinds. */
|
|
295
|
+
function linkAllowed(type, from, to) {
|
|
296
|
+
const allowed = linkTypes[type];
|
|
297
|
+
return allowed.from.includes(from) && allowed.to.includes(to);
|
|
298
|
+
}
|
|
299
|
+
const lenses = [
|
|
300
|
+
"data",
|
|
301
|
+
"api",
|
|
302
|
+
"routes"
|
|
303
|
+
];
|
|
257
304
|
const containerKinds = [
|
|
258
305
|
"screen",
|
|
259
306
|
"component",
|
|
@@ -350,16 +397,97 @@ function parseCanvasDocument(value) {
|
|
|
350
397
|
"containers",
|
|
351
398
|
"notes",
|
|
352
399
|
"modes",
|
|
353
|
-
"detection"
|
|
400
|
+
"detection",
|
|
401
|
+
"objects",
|
|
402
|
+
"links",
|
|
403
|
+
"sources"
|
|
354
404
|
], "");
|
|
355
405
|
return compact({
|
|
356
406
|
version: 3,
|
|
357
407
|
containers: expectArray(document.containers, "/containers").map((item, index) => parseContainer(item, `/containers/${index}`)),
|
|
358
408
|
modes: optional(document.modes, (value) => parseModeSettings(value)),
|
|
359
409
|
notes: optional(document.notes, (value) => expectArray(value, "/notes").map((item, index) => parseNote(item, `/notes/${index}`))),
|
|
360
|
-
detection: optional(document.detection, (value) => parseDetection(value, "/detection"))
|
|
410
|
+
detection: optional(document.detection, (value) => parseDetection(value, "/detection")),
|
|
411
|
+
objects: optional(document.objects, (value) => parseObjects(value, "/objects")),
|
|
412
|
+
links: optional(document.links, (value) => parseLinks(value, "/links")),
|
|
413
|
+
sources: optional(document.sources, (value) => parseSources(value, "/sources"))
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
function parsePosition(value, path) {
|
|
417
|
+
const position = expectObject(value, path);
|
|
418
|
+
expectKnownKeys(position, ["x", "y"], path);
|
|
419
|
+
return {
|
|
420
|
+
x: expectNumber(position.x, `${path}/x`),
|
|
421
|
+
y: expectNumber(position.y, `${path}/y`)
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
function parseObjects(value, path) {
|
|
425
|
+
const placed = /* @__PURE__ */ new Set();
|
|
426
|
+
return expectArray(value, path).map((item, index) => {
|
|
427
|
+
const at = `${path}/${index}`;
|
|
428
|
+
const object = expectObject(item, at);
|
|
429
|
+
expectKnownKeys(object, [
|
|
430
|
+
"kind",
|
|
431
|
+
"ref",
|
|
432
|
+
"position"
|
|
433
|
+
], at);
|
|
434
|
+
const kind = expectOneOf(object.kind, canvasObjectKinds, `${at}/kind`);
|
|
435
|
+
const ref = expectString(object.ref, `${at}/ref`);
|
|
436
|
+
const key = JSON.stringify([kind, ref]);
|
|
437
|
+
if (placed.has(key)) throw new TypeError(`${at}: the ${kind} "${ref}" is already on the canvas`);
|
|
438
|
+
placed.add(key);
|
|
439
|
+
return {
|
|
440
|
+
kind,
|
|
441
|
+
ref,
|
|
442
|
+
position: parsePosition(object.position, `${at}/position`)
|
|
443
|
+
};
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
function parseLinkEnd(value, path) {
|
|
447
|
+
const end = expectObject(value, path);
|
|
448
|
+
expectKnownKeys(end, ["kind", "ref"], path);
|
|
449
|
+
return {
|
|
450
|
+
kind: expectOneOf(end.kind, linkEndKinds, `${path}/kind`),
|
|
451
|
+
ref: expectString(end.ref, `${path}/ref`)
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function parseLinks(value, path) {
|
|
455
|
+
const ids = /* @__PURE__ */ new Set();
|
|
456
|
+
const types = Object.keys(linkTypes);
|
|
457
|
+
return expectArray(value, path).map((item, index) => {
|
|
458
|
+
const at = `${path}/${index}`;
|
|
459
|
+
const link = expectObject(item, at);
|
|
460
|
+
expectKnownKeys(link, [
|
|
461
|
+
"id",
|
|
462
|
+
"type",
|
|
463
|
+
"from",
|
|
464
|
+
"to"
|
|
465
|
+
], at);
|
|
466
|
+
const id = expectString(link.id, `${at}/id`);
|
|
467
|
+
if (ids.has(id)) throw new TypeError(`${at}/id: duplicate link id "${id}"`);
|
|
468
|
+
ids.add(id);
|
|
469
|
+
const type = expectOneOf(link.type, types, `${at}/type`);
|
|
470
|
+
const from = parseLinkEnd(link.from, `${at}/from`);
|
|
471
|
+
const to = parseLinkEnd(link.to, `${at}/to`);
|
|
472
|
+
if (!linkAllowed(type, from.kind, to.kind)) {
|
|
473
|
+
const { from: froms, to: tos } = linkTypes[type];
|
|
474
|
+
throw new TypeError(`${at}: a ${type} link goes from ${froms.join(" or ")} to ${tos.join(" or ")}, not ${from.kind} to ${to.kind}`);
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
id,
|
|
478
|
+
type,
|
|
479
|
+
from,
|
|
480
|
+
to
|
|
481
|
+
};
|
|
361
482
|
});
|
|
362
483
|
}
|
|
484
|
+
function parseSources(value, path) {
|
|
485
|
+
const sources = expectObject(value, path);
|
|
486
|
+
expectKnownKeys(sources, lenses, path);
|
|
487
|
+
const chosen = {};
|
|
488
|
+
for (const lens of lenses) if (sources[lens] !== void 0) chosen[lens] = expectString(sources[lens], `${path}/${lens}`);
|
|
489
|
+
return chosen;
|
|
490
|
+
}
|
|
363
491
|
function parseDetection(value, path) {
|
|
364
492
|
const detection = expectObject(value, path);
|
|
365
493
|
expectKnownKeys(detection, ["include", "exclude"], path);
|
|
@@ -1386,6 +1514,7 @@ function librarySource(library, name, component, path) {
|
|
|
1386
1514
|
library,
|
|
1387
1515
|
...component.group ? { group: component.group } : {},
|
|
1388
1516
|
...component.parents ? { parents: component.parents.map((parent) => `${library}/${parent}`) } : {},
|
|
1517
|
+
...component.parents && component.parentsFrom ? { parentsFrom: component.parentsFrom } : {},
|
|
1389
1518
|
...component.internal ? { internal: true } : {},
|
|
1390
1519
|
...component.visual === false ? { visual: false } : {},
|
|
1391
1520
|
...component.optionalParents ? { optionalParents: component.optionalParents.map((parent) => `${library}/${parent}`) } : {},
|
|
@@ -1425,4 +1554,4 @@ function sampleIds(sample, library) {
|
|
|
1425
1554
|
...sample.children ? { children: sample.children.map(child) } : {}
|
|
1426
1555
|
};
|
|
1427
1556
|
}
|
|
1428
|
-
export {
|
|
1557
|
+
export { deviceKinds as A, parseCanvasDocument as C, collectCustomProperties as D, parseModeSettings as E, viewportHeightProperty as O, paramTypes as S, generateId as T, imageFits as _, applyLibraryEdits as a, linkEndKinds as b, isSampleElement as c, parentSignals as d, parseLibraryEdits as f, containerKinds as g, canvasObjectKinds as h, withLibraryContext as i, lengthToPixels as j, viewportWidthProperty as k, isSampleRender as l, renderSlotOf as m, librarySource as n, componentEvidence as o, parseLibraryManifest as p, sampleIds as r, isMadeValue as s, componentNode as t, libraryManifestExtension as u, lenses as v, styleKeys as w, linkTypes as x, linkAllowed as y };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as covers, n as registryDir, r as registryEntries } from "./src-
|
|
1
|
+
export { t as covers, n as registryDir, r as registryEntries } from "./src-D85OVBQL.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "./src-
|
|
1
|
+
import { D as collectCustomProperties, a as applyLibraryEdits, c as isSampleElement, f as parseLibraryEdits, j as lengthToPixels, l as isSampleRender, m as renderSlotOf, p as parseLibraryManifest, s as isMadeValue, u as libraryManifestExtension } from "./src-BryimTPw.mjs";
|
|
2
|
+
import "./src-D85OVBQL.mjs";
|
|
3
3
|
import { a as publishedVersions, n as packageFile, o as repositoryFiles, r as publishedSource } from "./published-CdXoUgfD.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { mkdir, readFile, readdir, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
@@ -601,11 +601,12 @@ function listTheme(selector) {
|
|
|
601
601
|
const [only] = names;
|
|
602
602
|
return names.size === 1 ? only : void 0;
|
|
603
603
|
}
|
|
604
|
-
const simpleSelector = /^(?:(?<root>html|:root|:host)(?![\w(-])|\.(?<className>[\w-]+)|\[\s*[\w-]
|
|
604
|
+
const simpleSelector = /^(?:(?<root>html|:root|:host)(?![\w(-])|\.(?<className>[\w-]+)|\[\s*(?<attribute>[\w-]+)\s*(?:=\s*(?:"(?<double>[^"]*)"|'(?<single>[^']*)'|(?<bare>[\w-]+))\s*(?:[is]\s*)?)?\]|(?<functional>:(?:not|where|is|host)\())/;
|
|
605
605
|
/** The theme one compound selector (no combinators) names, or undefined when it isn't a theme's. */
|
|
606
606
|
function compoundTheme(part) {
|
|
607
607
|
let root = false;
|
|
608
608
|
const names = [];
|
|
609
|
+
let attribute;
|
|
609
610
|
let rest = part;
|
|
610
611
|
while (rest.length > 0) {
|
|
611
612
|
const match = simpleSelector.exec(rest);
|
|
@@ -623,11 +624,14 @@ function compoundTheme(part) {
|
|
|
623
624
|
if (!nested) return void 0;
|
|
624
625
|
root ||= nested.root;
|
|
625
626
|
if (nested.name !== void 0) names.push(nested.name);
|
|
627
|
+
attribute ??= nested.attribute;
|
|
626
628
|
continue;
|
|
627
629
|
}
|
|
628
630
|
rest = rest.slice(match[0].length);
|
|
629
631
|
if (groups.root) root = true;
|
|
630
|
-
const
|
|
632
|
+
const value = groups.double ?? groups.single ?? groups.bare;
|
|
633
|
+
if (value !== void 0) attribute = groups.attribute;
|
|
634
|
+
const name = groups.className ?? value;
|
|
631
635
|
if (name !== void 0) names.push(name);
|
|
632
636
|
}
|
|
633
637
|
if (names.length > 1) return void 0;
|
|
@@ -635,7 +639,8 @@ function compoundTheme(part) {
|
|
|
635
639
|
if (!root && names.length === 0) return void 0;
|
|
636
640
|
return {
|
|
637
641
|
root: root || names[0] === "light",
|
|
638
|
-
...name === void 0 ? {} : { name }
|
|
642
|
+
...name === void 0 ? {} : { name },
|
|
643
|
+
...name !== void 0 && attribute !== void 0 ? { attribute } : {}
|
|
639
644
|
};
|
|
640
645
|
}
|
|
641
646
|
function matchingParen(text, open) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { p as parseLibraryManifest, u as libraryManifestExtension } from "./src-
|
|
1
|
+
import { p as parseLibraryManifest, u as libraryManifestExtension } from "./src-BryimTPw.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";
|
package/package.json
CHANGED
package/skills/SKILL.md
CHANGED
|
@@ -39,7 +39,7 @@ project, pass `tab: "active"` (`--tab active`) and read `get_app_state` for the
|
|
|
39
39
|
| `get_console` | `parity console` | What the project's own code wrote to the console, on the canvas and offscreen. |
|
|
40
40
|
| `get_tokens` | `parity tokens` | Tokens filtered by category, source or name, with values in every theme. |
|
|
41
41
|
| `get_components` | `parity components` | Components, libraries, icon sets and exports left out or failing to render. |
|
|
42
|
-
| `get_props` | `parity props <Component>` | Props from source; `all`
|
|
42
|
+
| `get_props` | `parity props <Component>` | Props from source, then what it goes inside and holds, its slots and an example to insert; `all` adds passed-through attributes. |
|
|
43
43
|
| `get_tabs` | `parity tabs` | The open projects. |
|
|
44
44
|
| `open_project` | `parity open <folder>` | Opens a project in a new tab. |
|
|
45
45
|
| `read_skill` | `parity skill [file]` | This skill. |
|
|
@@ -97,7 +97,8 @@ keeping the designs there, so ask before running that in someone's project.
|
|
|
97
97
|
them with `AddState`, as changes to the base design, not as copies.
|
|
98
98
|
5. **Check every frame.** Screenshot the design in all its states and the modes it shows (`state: "all"`,
|
|
99
99
|
`mode: "shown"`). Run `find_problems` and `check_accessibility` too, and read the warnings `execute` returns: they're
|
|
100
|
-
usually a token that doesn't exist or a prop the component doesn't take.
|
|
100
|
+
usually a token that doesn't exist or a prop the component doesn't take. A render failure comes first and means a
|
|
101
|
+
frame wasn't checked. Fix what you find, then check again.
|
|
101
102
|
6. **Say what you did.** Tell the person what changed and what you couldn't verify. Ask them to try anything interactive
|
|
102
103
|
themselves with the Interact tool (I), which hands their mouse and keyboard to the real components.
|
|
103
104
|
|
package/skills/execute.md
CHANGED
|
@@ -29,6 +29,20 @@ held.
|
|
|
29
29
|
patches with a nonempty `find`, string `replace` (which can be empty), and optional boolean `all`. Invalid requests
|
|
30
30
|
are rejected before reaching the app.
|
|
31
31
|
|
|
32
|
+
### Warnings
|
|
33
|
+
|
|
34
|
+
A call that applies can still return warnings. Each finding comes once, with every place it was found. The text puts
|
|
35
|
+
them under "Render failures", "Warnings" and "Suggestions". The structured result's `warningGroups` gives each one's
|
|
36
|
+
`category`, `severity` (`error`, `warning` or `suggestion`), `message`, `count` and `at`, which lists the places as
|
|
37
|
+
`design/node` paths or frames. A render failure means a screenshot or check is missing for that frame, so look into it
|
|
38
|
+
first. `unnamed_layout` is a suggestion for layouts this call added: give each one a name with `Update`.
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
// Unnamed rows come back as one suggestion, such as 12 places (team-members/k3x9a, … and 7 more). Name them:
|
|
42
|
+
const rows = Get("team-members", (node, at) => (node.type === "layout" && !node.name ? at.path : undefined))
|
|
43
|
+
rows.forEach((path, index) => Update(path, { name: `Member row ${index + 1}` }))
|
|
44
|
+
```
|
|
45
|
+
|
|
32
46
|
## API
|
|
33
47
|
|
|
34
48
|
```ts
|
|
@@ -91,6 +105,9 @@ A design is named by its id or its name. A node is named by `design/node`, where
|
|
|
91
105
|
by its id alone when only one design has that id. Names aren't unique, so prefer ids once you have them; `Insert`,
|
|
92
106
|
`Copy` and `CreateDesign` return them, and nodes you create with a `name` are listed under "Created" in the result.
|
|
93
107
|
|
|
108
|
+
A state is named by its id only, as `Designs()` lists it and `AddState` returns it. A state's name in its place fails
|
|
109
|
+
with `unknown_state`, and the error names the id it belongs to.
|
|
110
|
+
|
|
94
111
|
## Reading
|
|
95
112
|
|
|
96
113
|
### Designs
|
|
@@ -131,8 +148,58 @@ Print(Get((node, at) => (node.type === "component" && node.component === "Button
|
|
|
131
148
|
### Components and Props
|
|
132
149
|
|
|
133
150
|
`Components()` is everything a `component` node can name. `Props("Button")` returns the props found in the component's
|
|
134
|
-
source: variants with their options (`variant`, `size`), its own props,
|
|
135
|
-
|
|
151
|
+
source: variants with their options (`variant`, `size`), its own props, including those a library's component adds to
|
|
152
|
+
its element's (Nova's `isActive`), and with `{ all: true }` the attributes it passes to its element. A prop that isn't
|
|
153
|
+
listed, or a variant value outside its options, comes back as a warning.
|
|
154
|
+
|
|
155
|
+
Its `composition` says what it needs around it and holds, before you insert it:
|
|
156
|
+
|
|
157
|
+
- `role` is `provider` for one that draws nothing and gives the parts inside it their context (`SidebarProvider`),
|
|
158
|
+
`wrapper` for one other parts go inside (`Sidebar`), and `component` otherwise.
|
|
159
|
+
- `parents` is `listed`, with the `chain` it goes inside, outermost first, and where its library learned that; `none`;
|
|
160
|
+
or `unknown`, with the `reason`, such as a project component that hasn't drawn yet or an error asking for a provider.
|
|
161
|
+
An `optional` parent is left out when the ones outside it are already there.
|
|
162
|
+
- `holds` lists the parts that go directly inside it, and `slots` the `key` each slot's layers go under.
|
|
163
|
+
- `example.node` is the component inside its parents with its sample, as the Assets panel inserts it and the Components
|
|
164
|
+
panel draws it. `example.status` is `drawn` once the component has drawn on the canvas, `failing` with a `reason`, or
|
|
165
|
+
`untried`.
|
|
166
|
+
|
|
167
|
+
```js
|
|
168
|
+
// A sidebar goes inside its provider: insert the example, then fill it in.
|
|
169
|
+
const { composition } = Props("nova/Sidebar")
|
|
170
|
+
Print(composition.parents.chain.map((parent) => parent.component))
|
|
171
|
+
const sidebar = Insert("settings/Content", composition.example.node)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Writing the layers yourself works the same way. A provider draws nothing of its own, so the Components panel leaves it
|
|
175
|
+
out, but `Components()` lists it, since the parts inside it throw without it. A part placed without its provider shows
|
|
176
|
+
in `Problems` as a render error that names it.
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
// A Nova sidebar, inside the provider its parts need.
|
|
180
|
+
const part = (component, props, children) => ({ type: "component", component, props, slots: { children } })
|
|
181
|
+
const item = (label, isActive) =>
|
|
182
|
+
part("nova/SidebarMenuItem", {}, [
|
|
183
|
+
{ type: "component", component: "nova/SidebarMenuButton", props: { isActive, children: label } }
|
|
184
|
+
])
|
|
185
|
+
CreateDesign({
|
|
186
|
+
name: "App sidebar",
|
|
187
|
+
root: {
|
|
188
|
+
type: "layout",
|
|
189
|
+
name: "Frame",
|
|
190
|
+
style: { width: 280, height: 400 },
|
|
191
|
+
children: [
|
|
192
|
+
part("nova/SidebarProvider", {}, [
|
|
193
|
+
part("nova/Sidebar", { collapsible: "none" }, [
|
|
194
|
+
part("nova/SidebarContent", {}, [
|
|
195
|
+
part("nova/SidebarMenu", {}, [item("Dashboard", true), item("Members", false)])
|
|
196
|
+
])
|
|
197
|
+
])
|
|
198
|
+
])
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
```
|
|
136
203
|
|
|
137
204
|
### Icons
|
|
138
205
|
|
|
@@ -158,8 +225,15 @@ Insert("dashboard/Toolbar", {
|
|
|
158
225
|
|
|
159
226
|
Each token has a `name`, a `category` (`color`, `font-size`, `radius`, `shadow`, `spacing`, `font`, `font-weight`,
|
|
160
227
|
`leading`, `tracking` and others), a `source` (`semantic` for the project's own variables, `theme` for its styling
|
|
161
|
-
framework's scale) and its `values` per theme. Filter with
|
|
162
|
-
`GetTokens({ search: "radius" })`.
|
|
228
|
+
framework's scale, `keyword` for a framework keyword the style fields also take) and its `values` per theme. Filter with
|
|
229
|
+
`GetTokens({ category: "color", source: "semantic" })` or `GetTokens({ search: "radius" })`.
|
|
230
|
+
|
|
231
|
+
For a circle or a pill, use `radius-full`, Tailwind's `rounded-full`. It's listed under `radius` with
|
|
232
|
+
`source: "keyword"`, and frames draw it as round as the box allows, at any size:
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
Update("avatar", { style: { width: 10, height: 10, radius: "radius-full" } })
|
|
236
|
+
```
|
|
163
237
|
|
|
164
238
|
### Selection
|
|
165
239
|
|
|
@@ -235,6 +309,25 @@ removes the keys as `null` does.
|
|
|
235
309
|
its design; to move it to another design, `Copy` it there and `Delete` the original. `Copy` gives the copy fresh ids and
|
|
236
310
|
returns the new id. A design's root can't be deleted; use `Replace`.
|
|
237
311
|
|
|
312
|
+
The new node and everything in it get fresh ids, so `Replace` throws away the patches on the old node and what was
|
|
313
|
+
inside it: its `Responsive` patches, its changes in every state and its variant options' patches. Replacing a design's
|
|
314
|
+
root clears every state's changes. To keep a node's patches, change it with `Update` instead. The result lists what went
|
|
315
|
+
under "Discarded by Replace" (`discarded` in structured output), with each node's viewports, states and variants:
|
|
316
|
+
|
|
317
|
+
```text
|
|
318
|
+
Discarded by Replace (reapply what you still want to the new node):
|
|
319
|
+
dashboard/stats, now k3x9a:
|
|
320
|
+
Stats (stats): viewports mobile
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Put back the ones you still want on the new ids, in the same call or the next. A state's changes go back with
|
|
324
|
+
`UpdateState(design, state, { changes: { [id]: … } })`.
|
|
325
|
+
|
|
326
|
+
```js
|
|
327
|
+
const stats = Replace("dashboard/Stats", { type: "layout", name: "Stats", style: { direction: "row", gap: 16 } })
|
|
328
|
+
Responsive(stats, "mobile", { style: { direction: "column" } })
|
|
329
|
+
```
|
|
330
|
+
|
|
238
331
|
### Responsive
|
|
239
332
|
|
|
240
333
|
Patches a node in one viewport: `Responsive("dashboard/Stats", "mobile", { style: { columns: 1 } })`. The viewport is a
|
|
@@ -320,8 +413,29 @@ Frames draw as the canvas does: instances of a library's component designs (from
|
|
|
320
413
|
token values not yet saved show, the person's edits and this call's own `SetTokens` alike, so
|
|
321
414
|
`SetTokens({ primary: "red" }); await Screenshot(id)` shows the new value in the same call.
|
|
322
415
|
|
|
323
|
-
`Measure` lays out one frame and returns its size, whether it overflows sideways, and every node's box
|
|
324
|
-
|
|
416
|
+
`Measure` lays out one frame and returns its state, mode and size, whether it overflows sideways, and every node's box
|
|
417
|
+
with the node it's drawn inside (`parent`); a node an ancestor cuts off has `clipped` and that ancestor's box in `clip`.
|
|
418
|
+
`Problems` checks frames for render errors, overflow, clipping and unknown components. Each layout problem has its
|
|
419
|
+
`kind`: `frame-overflow` for a node that sticks out of the frame, by its position or its width, or `clipped` for one an
|
|
420
|
+
ancestor that hides overflow cuts off. It names the `node`, its `box`, the `container` it overflows (the clipping node,
|
|
421
|
+
or the frame, which has no `node`) with that box, and the pixels past each edge in `overflow`. A node past a parent that
|
|
422
|
+
doesn't clip, inside the frame, shows, so it isn't a problem; children going no further than a reported parent are left
|
|
423
|
+
out.
|
|
424
|
+
|
|
425
|
+
```js
|
|
426
|
+
const id = CreateDesign({ name: "Checkout" })
|
|
427
|
+
Insert(id, { type: "component", component: "Button", name: "Pay", props: { children: "Pay" } })
|
|
428
|
+
// Each one, such as { kind: "frame-overflow", node: "pay", name: "Pay", box: { x: 340, y: 100, width: 60, height: 36 },
|
|
429
|
+
// container: { box: { x: 0, y: 0, width: 390, height: 600 } }, overflow: { right: 10 }, problem: "at x 340, …" }
|
|
430
|
+
for (const problem of await Problems(id, { mode: "shown" })) Print(problem.kind, problem.name, problem.overflow)
|
|
431
|
+
const layout = await Measure(id)
|
|
432
|
+
Print(
|
|
433
|
+
layout.state,
|
|
434
|
+
layout.mode,
|
|
435
|
+
layout.nodes.map((node) => `${node.id} in ${node.parent}`)
|
|
436
|
+
)
|
|
437
|
+
```
|
|
438
|
+
|
|
325
439
|
`Accessibility` returns axe-core's findings per node and frame, with the rule, its impact and what's wrong.
|
|
326
440
|
|
|
327
441
|
These frames are drawn in the same window as the canvas's: as wide as the frame, and as tall as the design's fixed
|
package/skills/guide/checking.md
CHANGED
|
@@ -15,8 +15,10 @@ small details.
|
|
|
15
15
|
|
|
16
16
|
- **Render errors.** A component threw. Usually a prop with the wrong type, or a part used outside its parent (a
|
|
17
17
|
`TabsTrigger` without `Tabs`). `parity components` lists the components failing anywhere on the canvas, with why.
|
|
18
|
-
- **Overflow.**
|
|
19
|
-
|
|
18
|
+
- **Overflow.** A node that sticks out of the frame, by its width or where it's placed, usually a row that needs to wrap
|
|
19
|
+
or stack on mobile. The finding gives the node, its box, the frame's, and how many pixels past which edge.
|
|
20
|
+
- **Clipping.** A node cut off by an ancestor with `overflow: hidden` or a fixed size, named in the finding with its
|
|
21
|
+
box. A node that only spills past a parent that doesn't clip still shows, so it isn't listed.
|
|
20
22
|
- **Unknown components.** A name that isn't in `Components()`; it draws as a placeholder.
|
|
21
23
|
|
|
22
24
|
`Measure` gives every node's box in one frame, for checking alignment or sizes by numbers.
|
|
@@ -17,7 +17,9 @@ instance follows.
|
|
|
17
17
|
- Read its props with `Props("Badge")`. Variants and sizes come with their options; use those values exactly. A prop the
|
|
18
18
|
component doesn't declare comes back as a warning.
|
|
19
19
|
- Compose the parts the way the library intends: `Tabs` holds a `TabsList` of `TabsTrigger`s and one `TabsContent` per
|
|
20
|
-
trigger; `Table` holds `TableHeader` and `TableBody`, each with `TableRow`s of cells.
|
|
20
|
+
trigger; `Table` holds `TableHeader` and `TableBody`, each with `TableRow`s of cells. `Props("nova/Sidebar")` says
|
|
21
|
+
what a part goes inside in `composition.parents`, here the `SidebarProvider` it needs, and what goes inside it in
|
|
22
|
+
`composition.holds`. Start from `composition.example.node`, which already has its parents, when they aren't there.
|
|
21
23
|
- Put children where they go. `slots.children` is what goes between the component's tags. Other slots, when a component
|
|
22
24
|
takes nodes as props, are named after the prop.
|
|
23
25
|
|
|
@@ -22,6 +22,20 @@ only its changes, so editing the base updates every state.
|
|
|
22
22
|
|
|
23
23
|
Name states for the situation ("No members yet", "Invite failed"), not the change ("Table hidden").
|
|
24
24
|
|
|
25
|
+
Every call that takes a state takes its id, never its name: `UpdateState`, `DeleteState`, `Update`'s and `Get`'s
|
|
26
|
+
`state`, `UpdateDesign`'s `state` and the checks. `AddState` returns the id, and `Designs()` lists each design's states
|
|
27
|
+
with their ids and names. Names can repeat and change, so an id made from a name stops matching it after a rename. A
|
|
28
|
+
name passed by mistake fails with `unknown_state`, naming the id it belongs to, or every id when states share the name.
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
AddState("team-members", { name: "No members yet", changes: { "Members table": { hidden: true } } })
|
|
32
|
+
// Later, find a state's id by its name in Designs(), then pass the id.
|
|
33
|
+
const states = Designs().find((design) => design.id === "team-members").states // [{ id, name }]
|
|
34
|
+
const empty = states.find((state) => state.name === "No members yet").id
|
|
35
|
+
UpdateState("team-members", empty, { name: "No members" })
|
|
36
|
+
await Screenshot("team-members", { state: empty })
|
|
37
|
+
```
|
|
38
|
+
|
|
25
39
|
## Modes
|
|
26
40
|
|
|
27
41
|
Modes are the project's, shared by every design. Each mode picks one option of each variable:
|
package/dist/src-DgI96EpD.mjs
DELETED