@parity-app/cli 0.5.0 → 0.6.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 CHANGED
@@ -1,5 +1,6 @@
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-DgpoM3e7.mjs";
2
+ import { a as errorCodes, i as closest, o as errorDetails, r as CodedError, s as readErrorDetails } from "./schema-BpGSCLEu.mjs";
3
+ import { a as describeExec, c as describeProps, f as setCaller, i as describeConsole, l as describeStatus, n as describeAccessibility, o as describeModes, p as mcpCallerName, r as describeComponents, s as describeProblems, t as call, u as describeTokens } from "./client-CoW-KcnT.mjs";
3
4
  import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
4
5
  import { dirname, join, normalize, relative, resolve, sep } from "node:path";
5
6
  import { parseArgs } from "node:util";
@@ -179,7 +180,7 @@ const warningGroupsSchema = {
179
180
  properties: {
180
181
  category: {
181
182
  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
+ description: "Stable: render_failed, unknown_component, unknown_component_design, unknown_prop, invalid_option, not_a_token, not_a_length_token, overlap, title_clearance, canvas_overlaps, kind_changed or unnamed_layout. overlap and title_clearance cover boards this call created, changed or moved: overlap when two boards collide, title_clearance when they're clear but one's title covers the other. canvas_overlaps counts the rest of the canvas's; call Overlaps() to check every board."
183
184
  },
184
185
  severity: {
185
186
  type: "string",
@@ -386,8 +387,8 @@ const executeOperation = {
386
387
  */
387
388
  async function runCheck(library, options) {
388
389
  if (!library) throw new Error("parity lib check <library> [--tab t] [--mode m]");
389
- const { applyLibraryEdits, componentNode, generateId, librarySource, parseLibraryEdits, withLibraryContext } = await import("./src-CqmC2-Em.mjs");
390
- const { registryDir, registryEntries } = await import("./src-BwTWa2_Y.mjs");
390
+ const { applyLibraryEdits, componentNode, generateId, librarySource, parseLibraryEdits, withLibraryContext } = await import("./src-p0vFxRBz.mjs");
391
+ const { registryDir, registryEntries } = await import("./src-Cr8pfFJE.mjs");
391
392
  const entry = (await registryEntries()).find((item) => item.manifest.id === library);
392
393
  if (!entry) throw new Error(`The registry has no library "${library}"`);
393
394
  let manifest = entry.manifest;
@@ -515,8 +516,8 @@ function describeCheck(report) {
515
516
  * because it reads the project's files and its own copy of TypeScript.
516
517
  */
517
518
  async function runLib(packageName, options) {
518
- const { appManifestPath, generateLibrary, manifestText, mergeManifest, reviseLibrary } = await import("./src-CpoDOrn3.mjs");
519
- const { covers, registryDir, registryEntries } = await import("./src-BwTWa2_Y.mjs");
519
+ const { appManifestPath, generateLibrary, manifestText, mergeManifest, reviseLibrary } = await import("./src-Zm_PYFFH.mjs");
520
+ const { covers, registryDir, registryEntries } = await import("./src-Cr8pfFJE.mjs");
520
521
  const spec = packageName === void 0 ? void 0 : packageSpec(packageName);
521
522
  let root = resolve(options.root ?? ".");
522
523
  if (spec && (spec.version !== void 0 || options.registry && !options.root)) {
@@ -527,7 +528,7 @@ async function runLib(packageName, options) {
527
528
  console.error(`Installing ${spec.name} ${version} on its own…`);
528
529
  root = await installedVersion(spec.name, version);
529
530
  }
530
- const { parseLibraryEdits } = await import("./src-CpoDOrn3.mjs");
531
+ const { parseLibraryEdits } = await import("./src-Zm_PYFFH.mjs");
531
532
  const trialEdits = options.edits ? parseLibraryEdits(JSON.parse(await readFile(resolve(options.edits), "utf8"))) : void 0;
532
533
  const recordedId = options.registry && spec ? options.id ?? (await registryEntries()).find((entry) => entry.manifest.package?.name === spec.name)?.manifest.id : void 0;
533
534
  const appErrors = recordedId ? await readAppErrors(registryDir(), recordedId) : void 0;
@@ -921,10 +922,29 @@ const stateSchema = object({
921
922
  },
922
923
  overlaps: {
923
924
  type: "array",
925
+ description: "Every pair of boards on the canvas that's too close, as execute's Overlaps() lists them. A pair with titleOnly doesn't collide: the lower board's title covers the upper one.",
924
926
  items: object({
925
927
  designs: strings,
926
- names: strings
927
- })
928
+ names: strings,
929
+ width: {
930
+ ...number,
931
+ description: "How far they share, title band included."
932
+ },
933
+ height: number,
934
+ titleOnly: {
935
+ ...boolean_,
936
+ description: "Only the lower board's title covers the other; the boards are clear."
937
+ },
938
+ estimated: {
939
+ ...boolean_,
940
+ description: "One isn't drawn yet, so its size is estimated from its frames. Absent: both measured."
941
+ }
942
+ }, [
943
+ "width",
944
+ "height",
945
+ "titleOnly",
946
+ "estimated"
947
+ ])
928
948
  },
929
949
  modes: {
930
950
  type: "array",
@@ -1,35 +1,38 @@
1
+ import { c as unknownReference, n as nonBlankString, r as CodedError, s as readErrorDetails, t as jsonText } from "./schema-BpGSCLEu.mjs";
1
2
  import { join } from "node:path";
2
3
  import { randomUUID } from "node:crypto";
3
4
  import { readFileSync, readdirSync, realpathSync, rmSync } from "node:fs";
4
5
  import { homedir } from "node:os";
6
+ import { z } from "zod";
7
+ // ../agent/src/routing.ts
8
+ /**
9
+ * Which running copy of Parity an agent's call goes to (PAR-514). Several copies can run at once, each with its own
10
+ * projects open: the person's app, a dev session, `pnpm perf`. Agents care about the project, not the copy, so every
11
+ * call is sent to the copy that has its project open. The CLI decides, once, with these rules, and sends that copy the
12
+ * exact tab it chose; the app matches a tab reference with the same `matchTab`. Nothing here touches files or the
13
+ * network, so the app's renderer can use it too.
14
+ */
15
+ /** How to reach one running copy: what it writes to its registry entry. A number an entry lacks reads as 0. */
16
+ const copyEntry = z.object({
17
+ version: z.number().catch(0),
18
+ url: z.string(),
19
+ token: z.string(),
20
+ /** The app's main process, which names the copy and its entry. A service the app restarts is the same copy. */
21
+ app: z.number(),
22
+ /** When the copy started, in milliseconds since the epoch. A project open in two copies goes to the older one. */
23
+ startedAt: z.number().catch(0)
24
+ });
25
+ /** An entry's text. The file from before the registry names the app's main process `pid`. */
26
+ const entryText = jsonText(copyEntry.extend({
27
+ app: z.number().optional().catch(void 0),
28
+ pid: z.number().optional().catch(void 0)
29
+ }).transform(({ app, pid, ...entry }) => ({
30
+ ...entry,
31
+ app: app ?? pid ?? 0
32
+ })));
5
33
  /** Reads a registry entry's text, or nothing when it isn't one. A file from before the registry stands for its copy. */
6
34
  function readCopyEntry(text) {
7
- if (text === void 0) return void 0;
8
- let value;
9
- try {
10
- value = JSON.parse(text);
11
- } catch {
12
- return;
13
- }
14
- if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
15
- const fields = new Map(Object.entries(value));
16
- const [version, url, token, app, pid, startedAt] = [
17
- "version",
18
- "url",
19
- "token",
20
- "app",
21
- "pid",
22
- "startedAt"
23
- ].map((key) => fields.get(key));
24
- if (typeof url !== "string" || typeof token !== "string") return void 0;
25
- const number = (field) => typeof field === "number" ? field : void 0;
26
- return {
27
- version: number(version) ?? 0,
28
- url,
29
- token,
30
- app: number(app) ?? number(pid) ?? 0,
31
- startedAt: number(startedAt) ?? 0
32
- };
35
+ return entryText.safeParse(text).data;
33
36
  }
34
37
  /**
35
38
  * A project folder as routing compares it: forward slashes, no trailing slash, and on Windows (a drive letter or a
@@ -160,15 +163,21 @@ function byAge(a, b) {
160
163
  return a.entry.startedAt - b.entry.startedAt || a.entry.app - b.entry.app;
161
164
  }
162
165
  // ../agent/src/registry.ts
166
+ /** The settings that place the registry. One that's blank counts as unset. */
167
+ const registrySettings = z.object({
168
+ PARITY_AGENT_FILE: nonBlankString.optional().catch(void 0),
169
+ PARITY_AGENT_DIR: nonBlankString.optional().catch(void 0)
170
+ });
163
171
  /** The registry these settings name: `PARITY_AGENT_FILE`, else `PARITY_AGENT_DIR`, else `~/.parity/agents`. */
164
172
  function agentRegistry(env, home) {
165
- if (env.PARITY_AGENT_FILE) return {
173
+ const { PARITY_AGENT_FILE: file, PARITY_AGENT_DIR: folder } = registrySettings.parse(env);
174
+ if (file) return {
166
175
  kind: "file",
167
- path: env.PARITY_AGENT_FILE
176
+ path: file
168
177
  };
169
178
  return {
170
179
  kind: "folder",
171
- path: env.PARITY_AGENT_DIR || join(home, ".parity", "agents")
180
+ path: folder ?? join(home, ".parity", "agents")
172
181
  };
173
182
  }
174
183
  /** The file before the registry, where an app from before it says how to reach it. */
@@ -221,105 +230,10 @@ function readText(path) {
221
230
  return;
222
231
  }
223
232
  }
224
- // ../core/src/errors.ts
225
- /**
226
- * Why an agent's call failed, as a stable code it can act on without reading the message. The CLI and MCP publish
227
- * these; the app throws them where it knows the cause.
228
- */
229
- const errorCodes = [
230
- "invalid_input",
231
- "not_running",
232
- "not_answering",
233
- "unknown_tab",
234
- "tab_not_ready",
235
- "unknown_design",
236
- "unknown_node",
237
- "ambiguous_reference",
238
- "unknown_state",
239
- "unknown_mode",
240
- "unknown_viewport",
241
- "unknown_component",
242
- "unknown_icon_set",
243
- "unknown_edit_id",
244
- "conflict",
245
- "execution_failed",
246
- "app_error"
247
- ];
248
- /** An error with a stable code, the field it's about and what to use instead. */
249
- var CodedError = class extends Error {
250
- details;
251
- constructor(message, details) {
252
- super(message);
253
- this.name = "CodedError";
254
- this.details = {
255
- ...details,
256
- retryable: details.retryable ?? false
257
- };
258
- }
259
- };
260
- /** An unknown reference, naming the valid ones closest to what was asked for, up to `alternativeLimit`. */
261
- function unknownReference(code, message, reference, candidates, field) {
262
- return new CodedError(message, {
263
- code,
264
- ...field === void 0 ? {} : { field },
265
- alternatives: closest(reference, candidates)
266
- });
267
- }
268
- /**
269
- * Candidates sharing text with the reference first, ignoring case, then the rest by how few single-character edits
270
- * away they are, so a typo's intended id makes the cut; ties keep their own order. All of them when they fit.
271
- */
272
- function closest(reference, candidates, limit = 20) {
273
- const unique = [...new Set(candidates)];
274
- if (unique.length <= limit) return unique;
275
- const wanted = reference.toLowerCase();
276
- const ranked = unique.map((candidate, index) => {
277
- const lower = candidate.toLowerCase();
278
- const near = wanted.length > 0 && (lower.includes(wanted) || wanted.includes(lower));
279
- return {
280
- candidate,
281
- index,
282
- near: near ? 0 : 1,
283
- distance: near ? 0 : editDistance(wanted, lower)
284
- };
285
- });
286
- ranked.sort((a, b) => a.near - b.near || a.distance - b.distance || a.index - b.index);
287
- return ranked.slice(0, limit).map(({ candidate }) => candidate);
288
- }
289
- /** How many single-character insertions, deletions or substitutions turn one string into the other. */
290
- function editDistance(a, b) {
291
- let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
292
- for (let i = 1; i <= a.length; i++) {
293
- const row = [i];
294
- for (let j = 1; j <= b.length; j++) row[j] = Math.min(previous[j] + 1, row[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
295
- previous = row;
296
- }
297
- return previous[b.length];
298
- }
299
- /** A thrown value's details, when it carries them. */
300
- function errorDetails(error) {
301
- return error instanceof CodedError ? error.details : void 0;
302
- }
303
- /** Details that came from another process, checked before they're trusted. Anything malformed reads as none. */
304
- function readErrorDetails(value) {
305
- if (!value || typeof value !== "object") return void 0;
306
- const code = "code" in value ? value.code : void 0;
307
- const known = errorCodes.find((candidate) => candidate === code);
308
- if (!known) return void 0;
309
- const retryable = "retryable" in value && value.retryable === true;
310
- const field = "field" in value && typeof value.field === "string" && value.field ? value.field : void 0;
311
- const alternatives = "alternatives" in value && Array.isArray(value.alternatives) ? value.alternatives.filter((item) => typeof item === "string").slice(0, 20) : void 0;
312
- return {
313
- code: known,
314
- retryable,
315
- ...field === void 0 ? {} : { field },
316
- ...alternatives === void 0 ? {} : { alternatives }
317
- };
318
- }
319
233
  // src/caller.ts
320
234
  /** Declared caller names are displayed verbatim apart from surrounding whitespace. */
321
235
  function declaredName(value) {
322
- return typeof value === "string" ? value.trim() || void 0 : void 0;
236
+ return nonBlankString.safeParse(value).data;
323
237
  }
324
238
  /** Reserve the session suffix inside the desktop boundary's 80-character caller-id limit. */
325
239
  function sessionCallerId(name, session) {
@@ -405,10 +319,13 @@ async function runningCopies() {
405
319
  report
406
320
  }] : [];
407
321
  }))).flat();
408
- if (entries.length > 0 && copies.length === 0) throw new CodedError(`Parity isn't answering at ${entries.map((entry) => entry.url).join(", ")}. Is the app still running?`, {
409
- code: "not_answering",
410
- retryable: true
411
- });
322
+ if (entries.length > 0 && copies.length === 0) {
323
+ const where = entries.map((entry) => entry.url).join(", ");
324
+ throw new CodedError(`Parity isn't answering at ${where}. Is the app still running?`, {
325
+ code: "not_answering",
326
+ retryable: true
327
+ });
328
+ }
412
329
  if (copies.length === 0) throw new CodedError(`Parity isn't running (nothing in ${registry.path}). Start the app, then try again.`, {
413
330
  code: "not_running",
414
331
  retryable: true
@@ -793,4 +710,4 @@ function scriptPath(url) {
793
710
  return url || "an unknown script";
794
711
  }
795
712
  }
796
- export { errorDetails as _, describeExec as a, describeProps as c, registry as d, setCaller as f, errorCodes as g, closest as h, describeConsole as i, describeStatus as l, CodedError as m, describeAccessibility as n, describeModes as o, mcpCallerName as p, describeComponents as r, describeProblems as s, call as t, describeTokens as u, readErrorDetails as v, unknownReference as y };
713
+ export { describeExec as a, describeProps as c, registry as d, setCaller as f, describeConsole as i, describeStatus as l, describeAccessibility as n, describeModes as o, mcpCallerName as p, describeComponents as r, describeProblems as s, call as t, describeTokens as u };
package/dist/index.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import { z } from "zod";
1
2
  /**
2
3
  * Where running copies of Parity say how to reach them (PAR-514): one entry per copy, named by its main process, in
3
4
  * `~/.parity/agents`. Each copy writes and removes only its own. The app and the CLI both go through here, so the
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-DgpoM3e7.mjs";
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-CoW-KcnT.mjs";
@@ -0,0 +1,113 @@
1
+ import { z } from "zod";
2
+ // ../core/src/errors.ts
3
+ /**
4
+ * Why an agent's call failed, as a stable code it can act on without reading the message. The CLI and MCP publish
5
+ * these; the app throws them where it knows the cause.
6
+ */
7
+ const errorCodes = [
8
+ "invalid_input",
9
+ "not_running",
10
+ "not_answering",
11
+ "unknown_tab",
12
+ "tab_not_ready",
13
+ "unknown_design",
14
+ "unknown_node",
15
+ "ambiguous_reference",
16
+ "unknown_state",
17
+ "unknown_mode",
18
+ "unknown_viewport",
19
+ "unknown_component",
20
+ "unknown_icon_set",
21
+ "unknown_edit_id",
22
+ "conflict",
23
+ "execution_failed",
24
+ "app_error"
25
+ ];
26
+ /** Details as another process sends them. Each field that's malformed falls back on its own; only the code must hold. */
27
+ const errorDetailsSchema = z.object({
28
+ code: z.enum(errorCodes),
29
+ /** Whether sending the same request again, unchanged, can succeed. */
30
+ retryable: z.boolean().catch(false),
31
+ /** The request field at fault, as a path: `design`, `state`, `edits/0/find`. */
32
+ field: z.string().min(1).optional().catch(void 0),
33
+ /** Valid references to use instead of an unknown or ambiguous one, at most `alternativeLimit` of them. */
34
+ alternatives: z.array(z.unknown()).transform((items) => items.filter((item) => typeof item === "string").slice(0, 20)).optional().catch(void 0)
35
+ });
36
+ /** An error with a stable code, the field it's about and what to use instead. */
37
+ var CodedError = class extends Error {
38
+ details;
39
+ constructor(message, details) {
40
+ super(message);
41
+ this.name = "CodedError";
42
+ this.details = {
43
+ ...details,
44
+ retryable: details.retryable ?? false
45
+ };
46
+ }
47
+ };
48
+ /** An unknown reference, naming the valid ones closest to what was asked for, up to `alternativeLimit`. */
49
+ function unknownReference(code, message, reference, candidates, field) {
50
+ return new CodedError(message, {
51
+ code,
52
+ ...field === void 0 ? {} : { field },
53
+ alternatives: closest(reference, candidates)
54
+ });
55
+ }
56
+ /**
57
+ * Candidates sharing text with the reference first, ignoring case, then the rest by how few single-character edits
58
+ * away they are, so a typo's intended id makes the cut; ties keep their own order. All of them when they fit.
59
+ */
60
+ function closest(reference, candidates, limit = 20) {
61
+ const unique = [...new Set(candidates)];
62
+ if (unique.length <= limit) return unique;
63
+ const wanted = reference.toLowerCase();
64
+ const ranked = unique.map((candidate, index) => {
65
+ const lower = candidate.toLowerCase();
66
+ const near = wanted.length > 0 && (lower.includes(wanted) || wanted.includes(lower));
67
+ return {
68
+ candidate,
69
+ index,
70
+ near: near ? 0 : 1,
71
+ distance: near ? 0 : editDistance(wanted, lower)
72
+ };
73
+ });
74
+ ranked.sort((a, b) => a.near - b.near || a.distance - b.distance || a.index - b.index);
75
+ return ranked.slice(0, limit).map(({ candidate }) => candidate);
76
+ }
77
+ /** How many single-character insertions, deletions or substitutions turn one string into the other. */
78
+ function editDistance(a, b) {
79
+ let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
80
+ for (let i = 1; i <= a.length; i++) {
81
+ const row = [i];
82
+ for (let j = 1; j <= b.length; j++) row[j] = Math.min(previous[j] + 1, row[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
83
+ previous = row;
84
+ }
85
+ return previous[b.length];
86
+ }
87
+ /** A thrown value's details, when it carries them. */
88
+ function errorDetails(error) {
89
+ return error instanceof CodedError ? error.details : void 0;
90
+ }
91
+ /** Details that came from another process, checked before they're trusted. Anything malformed reads as none. */
92
+ function readErrorDetails(value) {
93
+ return errorDetailsSchema.safeParse(value).data;
94
+ }
95
+ z.record(z.string(), z.unknown());
96
+ /** Text with something besides whitespace, trimmed. */
97
+ const nonBlankString = z.string().trim().min(1, "can't be blank");
98
+ z.number();
99
+ /** JSON text holding what `schema` checks. Text that isn't JSON fails as `expected JSON`. */
100
+ function jsonText(schema) {
101
+ return z.string().transform((text, context) => {
102
+ try {
103
+ return JSON.parse(text);
104
+ } catch {
105
+ context.addIssue({
106
+ code: "custom",
107
+ message: "expected JSON"
108
+ });
109
+ return z.NEVER;
110
+ }
111
+ }).pipe(schema);
112
+ }
113
+ export { errorCodes as a, unknownReference as c, closest as i, nonBlankString as n, errorDetails as o, CodedError as r, readErrorDetails as s, jsonText as t };
@@ -1,4 +1,4 @@
1
- import { p as parseLibraryManifest, u as libraryManifestExtension } from "./src-BryimTPw.mjs";
1
+ import { p as parseLibraryManifest, u as libraryManifestExtension } from "./src-C4faIpux.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { readFile, readdir } from "node:fs/promises";
4
4
  import { dirname, join } from "node:path";
@@ -1,3 +1,13 @@
1
+ import "./schema-BpGSCLEu.mjs";
2
+ import { z } from "zod";
3
+ // ../core/src/adapters.ts
4
+ const packageVersions = z.record(z.string(), z.string());
5
+ z.looseObject({
6
+ name: z.string().optional(),
7
+ dependencies: packageVersions.optional(),
8
+ devDependencies: packageVersions.optional(),
9
+ scripts: z.record(z.string(), z.string()).optional()
10
+ });
1
11
  // ../core/src/devices.ts
2
12
  const deviceKinds = [
3
13
  "phone",
@@ -1 +1 @@
1
- export { t as covers, n as registryDir, r as registryEntries } from "./src-D85OVBQL.mjs";
1
+ export { t as covers, n as registryDir, r as registryEntries } from "./src-5CrSA_RX.mjs";
@@ -1,5 +1,5 @@
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";
1
+ import { D as collectCustomProperties, a as applyLibraryEdits, c as isSampleElement, f as parseLibraryEdits, j as lengthToPixels, l as isSampleRender, m as renderSlotOf, p as parseLibraryManifest, s as isMadeValue, u as libraryManifestExtension } from "./src-C4faIpux.mjs";
2
+ import "./src-5CrSA_RX.mjs";
3
3
  import { a as publishedVersions, n as packageFile, o as repositoryFiles, r as publishedSource } from "./published-CdXoUgfD.mjs";
4
4
  import { createRequire } from "node:module";
5
5
  import { mkdir, readFile, readdir, realpath, rm, stat, writeFile } from "node:fs/promises";
@@ -1,3 +1,3 @@
1
- import "./client-DgpoM3e7.mjs";
2
- export { a as applyLibraryEdits, t as componentNode, T as generateId, n as librarySource, f as parseLibraryEdits, i as withLibraryContext } from "./src-BryimTPw.mjs";
3
- import "./src-CpoDOrn3.mjs";
1
+ import "./schema-BpGSCLEu.mjs";
2
+ export { a as applyLibraryEdits, t as componentNode, T as generateId, n as librarySource, f as parseLibraryEdits, i as withLibraryContext } from "./src-C4faIpux.mjs";
3
+ import "./src-Zm_PYFFH.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parity-app/cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Drive the Parity app from a terminal or an agent: read state, run edits, take screenshots, serve MCP.",
@@ -44,7 +44,8 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "semver": "^7.8.5",
47
- "typescript": "6.0.3"
47
+ "typescript": "6.0.3",
48
+ "zod": "4.6.7"
48
49
  },
49
50
  "devDependencies": {
50
51
  "@parity-app/agent": "0.0.0",
package/skills/SKILL.md CHANGED
@@ -132,8 +132,9 @@ parity status --summary
132
132
  Lorem ipsum hides layout problems.
133
133
  - **Reuse what repeats.** When a row, card or list item appears more than twice, make it a component design with
134
134
  `ExtractComponent`, so each copy is an instance with its own content.
135
- - **Leave the canvas tidy.** New designs go into empty space (`FindEmptySpace`); `Overlaps()` tells you if boards
136
- collide, sizing boards the canvas hasn't drawn yet from their frames (`estimated: true`).
135
+ - **Leave the canvas tidy.** New designs go into empty space (`FindEmptySpace`). A call warns only about the boards it
136
+ moves; `Overlaps()` checks every board, telling collisions from a title too close (`titleOnly`) and boards sized from
137
+ their frames because the canvas hasn't drawn them (`estimated: true`).
137
138
 
138
139
  ## When something fails
139
140
 
package/skills/execute.md CHANGED
@@ -89,7 +89,7 @@ SetTokens({ name: value | { theme: value } }): void
89
89
 
90
90
  // Checking
91
91
  Screenshot(design, { state?, mode?, scale? }?): number
92
- Measure(design, { state?, mode? }?): Layout
92
+ Measure(design, { state?, mode? }?): Layout // one frame: one state id, one mode id
93
93
  Problems(design, { state?, mode? }?): Problem[]
94
94
  Accessibility(design, { state?, mode? }?): Finding[]
95
95
 
@@ -436,6 +436,21 @@ Print(
436
436
  )
437
437
  ```
438
438
 
439
+ `Measure` takes one state id and one mode id, because it returns one frame's layout. A list, `"all"` or `"shown"` fails
440
+ with `invalid_input`, naming the ids it takes. To measure several frames, call it once for each; every layout says which
441
+ state and mode it drew:
442
+
443
+ ```js
444
+ const signup = Designs().find((design) => design.id === "signup")
445
+ const layouts = []
446
+ for (const state of signup.states) {
447
+ for (const mode of [signup.showing.mode, ...signup.showing.beside]) {
448
+ layouts.push(await Measure("signup", { state: state.id, mode }))
449
+ }
450
+ }
451
+ for (const layout of layouts) Print(`${layout.state} in ${layout.mode}: ${layout.width} × ${layout.height}`)
452
+ ```
453
+
439
454
  `Accessibility` returns axe-core's findings per node and frame, with the rule, its impact and what's wrong.
440
455
 
441
456
  These frames are drawn in the same window as the canvas's: as wide as the frame, and as tall as the design's fixed
@@ -444,12 +459,22 @@ size checks measure the same in `Measure` and `Screenshot` as on the canvas, eve
444
459
 
445
460
  ## The canvas
446
461
 
447
- `FindEmptySpace({ width, height })` finds room for a new board, to the right of the others unless `direction: "below"`,
448
- or next to the design named in `near`. `Overlaps()` lists boards that collide, titles included; move one with
449
- `UpdateDesign(design, { x, y })`. A board is as big as the canvas last drew it. One it hasn't drawn yet is sized from
450
- its frames, laid out as the canvas lays them out: each shown mode's frame size (`frames` on the design, else the mode's
451
- width), companions shrunk to 640 wide, and a frame without a fixed height as tall as its window. Those overlaps say
452
- `estimated: true`.
462
+ `FindEmptySpace({ width, height })` finds room for a board that size, to the right of the others unless
463
+ `direction: "below"`, or next to the design named in `near`. Its default `padding` of 240 leaves room for the board's
464
+ title. Give it the size the board will be: `Designs()` has a drawn board's `width` and `height`.
465
+
466
+ `Overlaps()` checks every board on the canvas and lists each pair that's too close, in one of two kinds:
467
+
468
+ - **A collision:** the boards themselves overlap.
469
+ - **Title clearance:** `titleOnly: true`. The boards are clear, but the lower one starts less than 160 below the upper
470
+ one, so the title the canvas draws above it covers the upper board.
471
+
472
+ `width` and `height` are the area they share, title band included. Move one with `UpdateDesign(design, { x, y })`.
473
+
474
+ A board is as big as the canvas last drew it, so its size is measured. One it hasn't drawn yet is sized from its frames,
475
+ laid out as the canvas lays them out: each shown mode's frame size (`frames` on the design, else the mode's width),
476
+ companions shrunk to 640 wide, and a frame without a fixed height as tall as its window. A pair with an estimated board
477
+ says `estimated: true`; without it, both sizes are measured.
453
478
 
454
479
  ```js
455
480
  // With the default modes: a 1280 main frame, the dark one beside it at half size and the 390 phone, 2505 wide in all.
@@ -459,6 +484,20 @@ CreateDesign({ name: "Settings", x: 2200, y: 0 })
459
484
  Print(Overlaps(dashboard)) // Dashboard and Settings, 305 across, estimated: true
460
485
  ```
461
486
 
487
+ An execute call's own warnings cover only the boards it creates, changes or moves: `overlap` for a collision and
488
+ `title_clearance` for a title too close. Other crowded boards are counted in one `canvas_overlaps` suggestion, so a call
489
+ that changes nothing, or only reads, doesn't make a crowded canvas look clear. To audit the canvas, call `Overlaps()`:
490
+
491
+ ```js
492
+ for (const { names, titleOnly, estimated } of Overlaps()) {
493
+ const kind = titleOnly ? "a title covers a board" : "the boards collide"
494
+ Print(`${names.join(" and ")}: ${kind}, ${estimated ? "estimated" : "measured"}`)
495
+ }
496
+ // Room below Settings for a board 1400 wide and 1000 tall, and its title above it.
497
+ const room = FindEmptySpace({ width: 1400, height: 1000, near: "settings", direction: "below" })
498
+ CreateDesign({ name: "Plans", x: room.x, y: room.y })
499
+ ```
500
+
462
501
  ## Examples
463
502
 
464
503
  A new screen, checked in every state and mode:
@@ -21,7 +21,8 @@ small details.
21
21
  box. A node that only spills past a parent that doesn't clip still shows, so it isn't listed.
22
22
  - **Unknown components.** A name that isn't in `Components()`; it draws as a placeholder.
23
23
 
24
- `Measure` gives every node's box in one frame, for checking alignment or sizes by numbers.
24
+ `Measure` gives every node's box in one frame, for checking alignment or sizes by numbers. It takes one state and one
25
+ mode; to compare frames, call it for each, as the execute skill shows.
25
26
 
26
27
  ## Accessibility
27
28