@parity-app/cli 0.3.1 → 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-lVLSuOgh.mjs → src-BryimTPw.mjs} +165 -14
- package/dist/{src-BGF7szJh.mjs → src-BwTWa2_Y.mjs} +1 -1
- package/dist/{src-Aoey5Uq0.mjs → src-CpoDOrn3.mjs} +10 -5
- package/dist/src-CqmC2-Em.mjs +3 -0
- package/dist/{src-eso2QXtH.mjs → src-D85OVBQL.mjs} +1 -1
- package/package.json +1 -1
- package/skills/SKILL.md +3 -2
- package/skills/document.md +9 -3
- package/skills/execute.md +141 -11
- 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-DZKi9702.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,58 @@ 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
|
+
];
|
|
304
|
+
const containerKinds = [
|
|
305
|
+
"screen",
|
|
306
|
+
"component",
|
|
307
|
+
"frame"
|
|
308
|
+
];
|
|
257
309
|
const textTags = [
|
|
258
310
|
"p",
|
|
259
311
|
"span",
|
|
@@ -345,16 +397,97 @@ function parseCanvasDocument(value) {
|
|
|
345
397
|
"containers",
|
|
346
398
|
"notes",
|
|
347
399
|
"modes",
|
|
348
|
-
"detection"
|
|
400
|
+
"detection",
|
|
401
|
+
"objects",
|
|
402
|
+
"links",
|
|
403
|
+
"sources"
|
|
349
404
|
], "");
|
|
350
405
|
return compact({
|
|
351
406
|
version: 3,
|
|
352
407
|
containers: expectArray(document.containers, "/containers").map((item, index) => parseContainer(item, `/containers/${index}`)),
|
|
353
408
|
modes: optional(document.modes, (value) => parseModeSettings(value)),
|
|
354
409
|
notes: optional(document.notes, (value) => expectArray(value, "/notes").map((item, index) => parseNote(item, `/notes/${index}`))),
|
|
355
|
-
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
|
+
};
|
|
356
444
|
});
|
|
357
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
|
+
};
|
|
482
|
+
});
|
|
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
|
+
}
|
|
358
491
|
function parseDetection(value, path) {
|
|
359
492
|
const detection = expectObject(value, path);
|
|
360
493
|
expectKnownKeys(detection, ["include", "exclude"], path);
|
|
@@ -399,7 +532,8 @@ function parseContainer(value, path) {
|
|
|
399
532
|
"view",
|
|
400
533
|
"params",
|
|
401
534
|
"context",
|
|
402
|
-
"frames"
|
|
535
|
+
"frames",
|
|
536
|
+
"size"
|
|
403
537
|
], path);
|
|
404
538
|
const position = expectObject(container.position, `${path}/position`);
|
|
405
539
|
const view = optional(container.view, (v) => {
|
|
@@ -440,10 +574,13 @@ function parseContainer(value, path) {
|
|
|
440
574
|
};
|
|
441
575
|
});
|
|
442
576
|
if (states.length === 0) throw new TypeError(`${path}/states: expected at least one state`);
|
|
577
|
+
const kind = optional(container.kind, (v) => expectOneOf(v, containerKinds, `${path}/kind`));
|
|
578
|
+
if (kind === "frame") checkFrame(container, root, states, path);
|
|
579
|
+
else if (container.size !== void 0) throw new TypeError(`${path}/size: only a frame has a size of its own; size a ${kind ?? "screen"} in frames`);
|
|
443
580
|
return compact({
|
|
444
581
|
id: expectString(container.id, `${path}/id`),
|
|
445
582
|
name: expectString(container.name, `${path}/name`),
|
|
446
|
-
kind
|
|
583
|
+
kind,
|
|
447
584
|
context: optional(container.context, (v) => expectArray(v, `${path}/context`).map((item, index) => {
|
|
448
585
|
const at = `${path}/context/${index}`;
|
|
449
586
|
const wrapper = expectObject(item, at);
|
|
@@ -498,17 +635,10 @@ function parseContainer(value, path) {
|
|
|
498
635
|
}),
|
|
499
636
|
frames: optional(container.frames, (v) => {
|
|
500
637
|
const frames = {};
|
|
501
|
-
for (const [viewport, entry] of Object.entries(expectObject(v, `${path}/frames`))) {
|
|
502
|
-
const at = `${path}/frames/${viewport}`;
|
|
503
|
-
const size = expectObject(entry, at);
|
|
504
|
-
expectKnownKeys(size, ["width", "height"], at);
|
|
505
|
-
frames[viewport] = compact({
|
|
506
|
-
width: optional(size.width, (w) => expectNumber(w, `${at}/width`)),
|
|
507
|
-
height: optional(size.height, (h) => expectNumber(h, `${at}/height`))
|
|
508
|
-
});
|
|
509
|
-
}
|
|
638
|
+
for (const [viewport, entry] of Object.entries(expectObject(v, `${path}/frames`))) frames[viewport] = parseFrameSize(entry, `${path}/frames/${viewport}`);
|
|
510
639
|
return frames;
|
|
511
640
|
}),
|
|
641
|
+
size: optional(container.size, (v) => parseFrameSize(v, `${path}/size`)),
|
|
512
642
|
position: {
|
|
513
643
|
x: expectNumber(position.x, `${path}/position/x`),
|
|
514
644
|
y: expectNumber(position.y, `${path}/position/y`)
|
|
@@ -518,6 +648,26 @@ function parseContainer(value, path) {
|
|
|
518
648
|
view
|
|
519
649
|
});
|
|
520
650
|
}
|
|
651
|
+
function parseFrameSize(value, path) {
|
|
652
|
+
const size = expectObject(value, path);
|
|
653
|
+
expectKnownKeys(size, ["width", "height"], path);
|
|
654
|
+
return compact({
|
|
655
|
+
width: optional(size.width, (w) => expectNumber(w, `${path}/width`)),
|
|
656
|
+
height: optional(size.height, (h) => expectNumber(h, `${path}/height`))
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* What a frame may hold: a layout at its root and one state, drawn as its layers are, with no params, no view (it
|
|
661
|
+
* shows only the primary mode and that state) and no sizes per viewport, since it has one of its own.
|
|
662
|
+
*/
|
|
663
|
+
function checkFrame(container, root, states, path) {
|
|
664
|
+
if (root.type !== "layout") throw new TypeError(`${path}/root: a frame's root is a layout`);
|
|
665
|
+
if (states.length > 1) throw new TypeError(`${path}/states: a frame has one state`);
|
|
666
|
+
if (Object.keys(states[0].overrides ?? {}).length > 0) throw new TypeError(`${path}/states/0/overrides: a frame is drawn as its layers are`);
|
|
667
|
+
if (container.params !== void 0) throw new TypeError(`${path}/params: a frame has no params`);
|
|
668
|
+
if (container.view !== void 0) throw new TypeError(`${path}/view: a frame shows only the primary mode and its one state`);
|
|
669
|
+
if (container.frames !== void 0) throw new TypeError(`${path}/frames: a frame's size is in size, not per viewport`);
|
|
670
|
+
}
|
|
521
671
|
function parseNode(value, path, ids) {
|
|
522
672
|
const node = expectObject(value, path);
|
|
523
673
|
const type = expectOneOf(node.type, [
|
|
@@ -1364,6 +1514,7 @@ function librarySource(library, name, component, path) {
|
|
|
1364
1514
|
library,
|
|
1365
1515
|
...component.group ? { group: component.group } : {},
|
|
1366
1516
|
...component.parents ? { parents: component.parents.map((parent) => `${library}/${parent}`) } : {},
|
|
1517
|
+
...component.parents && component.parentsFrom ? { parentsFrom: component.parentsFrom } : {},
|
|
1367
1518
|
...component.internal ? { internal: true } : {},
|
|
1368
1519
|
...component.visual === false ? { visual: false } : {},
|
|
1369
1520
|
...component.optionalParents ? { optionalParents: component.optionalParents.map((parent) => `${library}/${parent}`) } : {},
|
|
@@ -1403,4 +1554,4 @@ function sampleIds(sample, library) {
|
|
|
1403
1554
|
...sample.children ? { children: sample.children.map(child) } : {}
|
|
1404
1555
|
};
|
|
1405
1556
|
}
|
|
1406
|
-
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/document.md
CHANGED
|
@@ -5,20 +5,26 @@ change, so you never edit the files by hand. This is the shape `GetDesign` and `
|
|
|
5
5
|
|
|
6
6
|
## Designs
|
|
7
7
|
|
|
8
|
-
A design is a screen or a
|
|
8
|
+
A design is a screen, a component or a frame, drawn on the canvas as a board of frames: one frame per state and mode it
|
|
9
|
+
shows.
|
|
9
10
|
|
|
10
11
|
| Field | What it is |
|
|
11
12
|
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
12
13
|
| `id` | Made from the name when the design is created. Stable; use it in paths. |
|
|
13
14
|
| `name` | What people see. |
|
|
14
|
-
| `kind` | `screen` (a page or view)
|
|
15
|
+
| `kind` | `screen` (a page or view), `component` (a reusable part that screens place as instances) or `frame` (a sketch; see below). |
|
|
15
16
|
| `root` | The design itself: one node, usually a layout. |
|
|
16
17
|
| `states` | Situations the whole design can be in, like loading or empty. The first is the base; the others store only what they change. |
|
|
17
18
|
| `params` | A component design's inputs, each with a `type` (`text`, `boolean`, `number`, `value`, `variant`) and a `default`. See Params and bindings. |
|
|
18
|
-
| `frames` | A fixed size per viewport, when
|
|
19
|
+
| `frames` | A screen's or component's fixed size per viewport, when it isn't as wide as the viewport or as tall as its content. |
|
|
20
|
+
| `size` | A frame's own `width` and `height` in pixels. Without a width it's as wide as the primary mode; without a height, as tall as its content. |
|
|
19
21
|
| `view` | How the canvas shows it: the main mode, the modes beside it, the state and the variant options shown. Not part of the design. |
|
|
20
22
|
| `context` | Components a component design is drawn inside on its own board, such as a table around a table row. |
|
|
21
23
|
|
|
24
|
+
A frame is the light kind, for sketching: a layout at its root, drawn once in the project's primary mode at its own
|
|
25
|
+
`size`, with one state and no `params`, `view` or `frames`. No code is written from it. Make it a screen or a component
|
|
26
|
+
to give it more.
|
|
27
|
+
|
|
22
28
|
## Nodes
|
|
23
29
|
|
|
24
30
|
Every node has an `id` (given when it's inserted), and can have a `name`, `hidden`, `style` and `responsive`. Name the
|
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
|
|
@@ -47,8 +61,8 @@ GetModes(design?): { settings, variables, viewports, modes, designs } // modes
|
|
|
47
61
|
Selection(): { design?, node?, mode? } // what the person has selected
|
|
48
62
|
|
|
49
63
|
// Designs
|
|
50
|
-
CreateDesign({ name, kind?, root?, x?, y?, file?, baseState? }): string //
|
|
51
|
-
UpdateDesign(design, { name?, kind?, x?, y?, mode?, showModes?, state?, allStates? }): void
|
|
64
|
+
CreateDesign({ name, kind?, root?, x?, y?, width?, height?, file?, baseState? }): string // the new design's id
|
|
65
|
+
UpdateDesign(design, { name?, kind?, x?, y?, width?, height?, mode?, showModes?, state?, allStates? }): void
|
|
52
66
|
DeleteDesign(design): void
|
|
53
67
|
|
|
54
68
|
// Nodes
|
|
@@ -91,12 +105,15 @@ 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
|
|
97
114
|
|
|
98
|
-
Lists every design with its `id`, `name`, `kind` (`screen` or `
|
|
99
|
-
canvas, the main mode and the modes beside it. Start here to find ids.
|
|
115
|
+
Lists every design with its `id`, `name`, `kind` (`screen`, `component` or `frame`), `states`, and `showing`: the state
|
|
116
|
+
on the canvas, the main mode and the modes beside it. Start here to find ids.
|
|
100
117
|
|
|
101
118
|
### Get
|
|
102
119
|
|
|
@@ -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
|
|
|
@@ -175,11 +249,27 @@ foreground colours. Without `x` and `y` it goes into empty space. `kind: "compon
|
|
|
175
249
|
screens place as instances; most component designs come from `ExtractComponent` instead, because that also gives them
|
|
176
250
|
params.
|
|
177
251
|
|
|
252
|
+
`kind: "frame"` makes a frame, the light kind for sketching an idea before it's a screen or a component: an empty padded
|
|
253
|
+
layout, drawn once at its own `width` and `height` in the project's primary mode. A frame has one state, no params and
|
|
254
|
+
no modes beside it, and no code is written from it, so `AddState`, `showModes` or another `mode` on one fail with
|
|
255
|
+
`invalid_input`. Turn it into a screen or a component when it needs them.
|
|
256
|
+
|
|
257
|
+
```js
|
|
258
|
+
const sketch = CreateDesign({ name: "Sketch", kind: "frame", width: 480, height: 320 })
|
|
259
|
+
Insert(sketch, { type: "text", tag: "h2", text: "Pricing ideas" })
|
|
260
|
+
await Screenshot(sketch)
|
|
261
|
+
UpdateDesign(sketch, { kind: "screen" }) // keeps its layers, and its size in the primary viewport
|
|
262
|
+
```
|
|
263
|
+
|
|
178
264
|
### UpdateDesign
|
|
179
265
|
|
|
180
266
|
Renames or moves a design, or changes how it's shown: `mode` sets the main frame's mode, `showModes` the modes beside
|
|
181
267
|
it, `state` the state on the canvas, and `allStates: true` shows every state in its own row. None of these change the
|
|
182
|
-
design itself.
|
|
268
|
+
design itself. `width` and `height` resize a frame.
|
|
269
|
+
|
|
270
|
+
`kind` turns a design into another kind. Turned into a frame, a design keeps its layers as its board drew them in the
|
|
271
|
+
base state, with its params' values filled in, and drops its other states, the modes beside it, its params and its
|
|
272
|
+
variants; the call's warnings say what went.
|
|
183
273
|
|
|
184
274
|
## Nodes
|
|
185
275
|
|
|
@@ -219,6 +309,25 @@ removes the keys as `null` does.
|
|
|
219
309
|
its design; to move it to another design, `Copy` it there and `Delete` the original. `Copy` gives the copy fresh ids and
|
|
220
310
|
returns the new id. A design's root can't be deleted; use `Replace`.
|
|
221
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
|
+
|
|
222
331
|
### Responsive
|
|
223
332
|
|
|
224
333
|
Patches a node in one viewport: `Responsive("dashboard/Stats", "mobile", { style: { columns: 1 } })`. The viewport is a
|
|
@@ -304,8 +413,29 @@ Frames draw as the canvas does: instances of a library's component designs (from
|
|
|
304
413
|
token values not yet saved show, the person's edits and this call's own `SetTokens` alike, so
|
|
305
414
|
`SetTokens({ primary: "red" }); await Screenshot(id)` shows the new value in the same call.
|
|
306
415
|
|
|
307
|
-
`Measure` lays out one frame and returns its size, whether it overflows sideways, and every node's box
|
|
308
|
-
|
|
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
|
+
|
|
309
439
|
`Accessibility` returns axe-core's findings per node and frame, with the rule, its impact and what's wrong.
|
|
310
440
|
|
|
311
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-DZKi9702.mjs
DELETED