@ingcreators/annot-product-docs 0.2.0 → 0.4.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/index.js CHANGED
@@ -1,338 +1,84 @@
1
- import { mkdir as e, readFile as t, readdir as n, writeFile as r } from "node:fs/promises";
2
- import { dirname as i, join as a, relative as o, resolve as s } from "node:path";
3
- import { test as c } from "@ingcreators/annot-playwright";
4
- import l from "js-yaml";
1
+ import { annotSourceResolvers as e, playwrightYamlToElementTree as t, test as n } from "@ingcreators/annot-playwright";
2
+ import { mkdir as r, readFile as i, readdir as a, writeFile as o } from "node:fs/promises";
3
+ import s, { dump as c, load as l } from "js-yaml";
5
4
  import u from "remark-frontmatter";
6
5
  import d from "remark-mdx";
7
6
  import f from "remark-parse";
8
- import { unified as p } from "unified";
9
- import { visit as m } from "unist-util-visit";
10
- import { z as h } from "zod";
11
- //#region src/resolver.ts
12
- function g(e) {
13
- let t = [], n = e.split(/\r?\n/), r = [];
14
- for (let e of n) {
15
- if (!e.trim()) continue;
16
- let n = e.length - e.replace(/^\s+/, "").length, i = Math.floor(n / 2), a = e.match(/^\s*-\s+([a-z]+)(?:\s+"([^"]*?)")?/);
17
- if (!a) continue;
18
- let o = a[1] ?? "", s = a[2] ?? "";
19
- for (; r.length > 0 && (r[r.length - 1]?.depth ?? -1) >= i;) r.pop();
20
- let c = r.map((e) => ({
21
- role: e.role,
22
- name: e.name
23
- })), l = e.match(/\[ref=(e\d+)\]/);
24
- l && t.push({
25
- role: o,
26
- name: s,
27
- ref: l[1] ?? "",
28
- depth: i,
29
- ancestors: c
30
- }), /:\s*$/.test(e) && r.push({
31
- depth: i,
32
- role: o,
33
- name: s
34
- });
35
- }
36
- return t;
37
- }
38
- async function _(e, t, n) {
39
- let r = te(t.filter((t) => t.role === e.role && t.name === e.name), e.under);
40
- if (r.length === 0) return ne(e, t);
41
- if (r.length > 1) return {
42
- ok: !1,
43
- kind: "ambiguous",
44
- reason: `Multiple elements match role="${e.role}" name="${e.name}". Use \`under\` to disambiguate.`,
45
- candidates: r.map((e) => `[ref=${e.ref}]`)
46
- };
47
- let i = r[0], a = v(n, e), o = await a.count();
48
- return o === 0 ? {
49
- ok: !1,
50
- kind: "live-mismatch",
51
- reason: `Snapshot says element exists but live locator failed: role="${e.role}" name="${e.name}".`
52
- } : o > 1 ? {
53
- ok: !1,
54
- kind: "ambiguous",
55
- reason: `Live locator returns ${o} elements for role="${e.role}" name="${e.name}".`
56
- } : {
57
- ok: !0,
58
- locator: a,
59
- entry: i
60
- };
61
- }
62
- async function ee(e, t, n) {
63
- let r = [];
64
- for (let i of e) r.push({
65
- overlay: i,
66
- result: await _(i.match, t, n)
67
- });
68
- return r;
69
- }
70
- function v(e, t) {
71
- let n = e.getByRole(t.role, {
72
- name: t.name,
73
- exact: !0
74
- });
75
- return t.under && (n = e.getByRole(t.under.role, {
76
- name: t.under.name,
77
- exact: !0
78
- }).getByRole(t.role, {
79
- name: t.name,
80
- exact: !0
81
- })), n;
82
- }
83
- function te(e, t) {
84
- return t ? e.filter((e) => e.ancestors.some((e) => e.role === t.role && e.name === t.name)) : e;
85
- }
86
- function ne(e, t) {
87
- let n = t.filter((t) => t.name === e.name);
88
- if (n.length === 1 && n[0].role !== e.role) return {
89
- ok: !1,
90
- kind: "role-changed",
91
- reason: `Element with name="${e.name}" exists but has role="${n[0].role}", not "${e.role}".`,
92
- suggestion: n[0].role
93
- };
94
- let r = t.filter((t) => t.role === e.role);
95
- if (r.length > 0) {
96
- let t = re(e.name, r.map((e) => e.name));
97
- if (t) return {
98
- ok: !1,
99
- kind: "renamed",
100
- reason: `No element with role="${e.role}" name="${e.name}". Closest match: name="${t}".`,
101
- suggestion: t
102
- };
103
- }
104
- return {
105
- ok: !1,
106
- kind: "not-found",
107
- reason: `No element with role="${e.role}" name="${e.name}".`
108
- };
109
- }
110
- function re(e, t) {
111
- if (t.length === 0) return null;
112
- let n = t[0], r = y(e, n);
113
- for (let i of t.slice(1)) {
114
- let t = y(e, i);
115
- t > r && (n = i, r = t);
116
- }
117
- return r > .3 ? n : null;
118
- }
119
- function y(e, t) {
120
- if (!e || !t) return 0;
121
- let n = (e) => {
122
- let t = /* @__PURE__ */ new Set();
123
- for (let n = 0; n < e.length - 2; n++) t.add(e.slice(n, n + 3));
124
- return t;
125
- }, r = n(e), i = n(t);
126
- if (r.size === 0 || i.size === 0) return 0;
127
- let a = 0;
128
- for (let e of r) i.has(e) && a++;
129
- return a / Math.max(r.size, i.size);
130
- }
131
- //#endregion
132
- //#region src/drift.ts
133
- function b(e) {
134
- let { screen: t, liveSnapshot: n, storedAttributesYaml: r, freshAttributesYaml: i } = e, a = [], o = /* @__PURE__ */ new Set();
135
- for (let e of t.overlays) {
136
- let r = n.filter((t) => t.role === e.match.role && t.name === e.match.name);
137
- if (r.length > 1) {
138
- a.push({
139
- severity: "error",
140
- kind: "duplicated",
141
- screenId: t.id,
142
- message: `Multiple elements match role="${e.match.role}" name="${e.match.name}". Use \`under\` to disambiguate.`,
143
- match: e.match
144
- });
145
- continue;
146
- }
147
- if (r.length === 1) continue;
148
- let i = n.filter((t) => t.name === e.match.name);
149
- if (i.length === 1 && i[0].role !== e.match.role) {
150
- let n = i[0];
151
- o.add(`${n.role}|${n.name}`), a.push({
152
- severity: "warning",
153
- kind: "role-changed",
154
- screenId: t.id,
155
- message: `Element with name="${e.match.name}" exists but has role="${n.role}", not "${e.match.role}".`,
156
- match: e.match,
157
- suggestion: { role: n.role }
158
- });
159
- continue;
160
- }
161
- let s = n.filter((t) => t.role === e.match.role);
162
- if (s.length > 0) {
163
- let n = se(e.match.name, s.map((e) => e.name));
164
- if (n) {
165
- o.add(`${e.match.role}|${n}`), a.push({
166
- severity: "warning",
167
- kind: "renamed",
168
- screenId: t.id,
169
- message: `No element with role="${e.match.role}" name="${e.match.name}". Closest match: name="${n}".`,
170
- match: e.match,
171
- suggestion: { name: n }
172
- });
173
- continue;
174
- }
175
- }
176
- a.push({
177
- severity: "error",
178
- kind: "removed",
179
- screenId: t.id,
180
- message: `No element with role="${e.match.role}" name="${e.match.name}".`,
181
- match: e.match
182
- });
183
- }
184
- let s = new Set(t.overlays.map((e) => `${e.match.role}|${e.match.name}`));
185
- for (let e of n) {
186
- if (!oe(e.role)) continue;
187
- let n = `${e.role}|${e.name}`;
188
- s.has(n) || o.has(n) || a.push({
189
- severity: "warning",
190
- kind: "added",
191
- screenId: t.id,
192
- message: `New ${e.role} "${e.name}" on the page has no <Overlay>.`,
193
- suggestion: {
194
- role: e.role,
195
- name: e.name
196
- }
197
- });
198
- }
199
- return r !== void 0 && i !== void 0 && C(r) !== C(i) && a.push({
200
- severity: "info",
201
- kind: "attribute-drift",
202
- screenId: t.id,
203
- message: "`annot:attributes` block no longer matches live element attributes. Run `annot docs sync` to update."
204
- }), a;
205
- }
206
- function ie(e) {
207
- return b({
208
- screen: e.screen,
209
- liveSnapshot: g(e.liveSnapshotYaml),
210
- storedAttributesYaml: e.storedAttributesYaml,
211
- freshAttributesYaml: e.freshAttributesYaml
212
- });
213
- }
214
- function x(e) {
215
- let t = 0, n = 0, r = 0;
216
- for (let i of e) i.severity === "error" ? t++ : i.severity === "warning" ? n++ : r++;
217
- return {
218
- errors: t,
219
- warnings: n,
220
- infos: r
221
- };
222
- }
223
- var ae = new Set([
224
- "button",
225
- "checkbox",
226
- "combobox",
227
- "link",
228
- "menuitem",
229
- "menuitemcheckbox",
230
- "menuitemradio",
231
- "option",
232
- "radio",
233
- "searchbox",
234
- "slider",
235
- "spinbutton",
236
- "switch",
237
- "tab",
238
- "textbox",
239
- "treeitem"
240
- ]);
241
- function oe(e) {
242
- return ae.has(e);
243
- }
244
- function se(e, t) {
245
- if (t.length === 0) return null;
246
- let n = t[0], r = S(e, n);
247
- for (let i of t.slice(1)) {
248
- let t = S(e, i);
249
- t > r && (n = i, r = t);
250
- }
251
- return r > .3 ? n : null;
252
- }
253
- function S(e, t) {
254
- if (!e || !t) return 0;
255
- let n = (e) => {
256
- let t = /* @__PURE__ */ new Set();
257
- for (let n = 0; n < e.length - 2; n++) t.add(e.slice(n, n + 3));
258
- return t;
259
- }, r = n(e), i = n(t);
260
- if (r.size === 0 || i.size === 0) return 0;
261
- let a = 0;
262
- for (let e of r) i.has(e) && a++;
263
- return a / Math.max(r.size, i.size);
264
- }
265
- function C(e) {
266
- return e.split(/\r?\n/).map((e) => e.trimEnd()).filter((e) => e.length > 0).join("\n");
267
- }
268
- function w(e) {
269
- return e.overlays.length > 0;
270
- }
271
- function T(e) {
272
- return e.filter(w);
273
- }
274
- //#endregion
7
+ import { unified as ee } from "unified";
8
+ import { visit as te } from "unist-util-visit";
9
+ import { z as p } from "zod";
10
+ import { basename as ne, dirname as m, isAbsolute as h, join as re, relative as g, resolve as _ } from "node:path";
11
+ import { bboxAnnotationsToSvg as v } from "@ingcreators/annot-annotator";
12
+ import { findByMatch as ie, walkTree as ae, writeElementTreePng as oe } from "@ingcreators/annot-core";
275
13
  //#region src/config.ts
276
- var E = h.enum([
14
+ var se = p.enum([
277
15
  "cover",
278
16
  "history",
279
17
  "list",
280
18
  "screen",
281
19
  "reference"
282
- ]), ce = h.object({
283
- book: h.string().optional(),
284
- sheet: h.string().optional(),
285
- sheets: h.record(h.string(), h.string()).optional(),
286
- role: E.optional(),
287
- order: h.number().optional()
288
- }).strict().refine((e) => !(e.sheet && e.sheets), "Specify either `xlsx.sheet` (single sheet) or `xlsx.sheets` (multi-sheet), not both."), le = h.record(h.string(), h.unknown()), D = h.object({
289
- id: h.string().min(1, "`annot.id` is required."),
290
- title: h.string().optional(),
291
- purpose: h.string().optional(),
20
+ ]), ce = p.object({
21
+ book: p.string().optional(),
22
+ sheet: p.string().optional(),
23
+ sheets: p.record(p.string(), p.string()).optional(),
24
+ role: se.optional(),
25
+ order: p.number().optional()
26
+ }).strict().refine((e) => !(e.sheet && e.sheets), "Specify either `xlsx.sheet` (single sheet) or `xlsx.sheets` (multi-sheet), not both."), le = p.record(p.string(), p.unknown()), ue = p.object({
27
+ id: p.string().min(1, "`annot.id` is required."),
28
+ title: p.string().optional(),
29
+ purpose: p.string().optional(),
292
30
  meta: le.optional(),
293
31
  xlsx: ce.optional()
294
- }).strict(), ue = h.object({
295
- template: h.string().optional(),
296
- templateSheets: h.object({
297
- cover: h.string().optional(),
298
- history: h.string().optional(),
299
- list: h.string().optional(),
300
- screen: h.string().optional(),
301
- reference: h.string().optional()
302
- }).strict().optional()
303
- }).strict(), O = h.object({
304
- meta: h.record(h.string(), h.unknown()).optional(),
305
- xlsx: h.object({
306
- defaultBook: h.string().optional(),
307
- books: h.record(h.string(), ue).optional()
32
+ }).strict(), de = p.object({
33
+ template: p.string().optional(),
34
+ templateSheets: p.object({
35
+ cover: p.string().optional(),
36
+ history: p.string().optional(),
37
+ list: p.string().optional(),
38
+ screen: p.string().optional(),
39
+ reference: p.string().optional()
308
40
  }).strict().optional()
41
+ }).strict(), fe = p.object({
42
+ embedMode: p.enum([
43
+ "newTab",
44
+ "inline",
45
+ "disabled"
46
+ ]).optional(),
47
+ cloudUrl: p.string().url("editor.cloudUrl must be an absolute URL").optional()
48
+ }).strict(), pe = p.object({
49
+ meta: p.record(p.string(), p.unknown()).optional(),
50
+ xlsx: p.object({
51
+ defaultBook: p.string().optional(),
52
+ books: p.record(p.string(), de).optional()
53
+ }).strict().optional(),
54
+ editor: fe.optional()
309
55
  }).strict();
310
- function de(e) {
311
- let t = O.safeParse(e);
56
+ function me(e) {
57
+ let t = pe.safeParse(e);
312
58
  if (!t.success) {
313
59
  let e = t.error.issues.map((e) => ` - ${e.path.length ? e.path.join(".") + ": " : ""}${e.message}`).join("\n");
314
60
  throw Error(`Invalid \`annot-docs.config.ts\`:\n${e}`);
315
61
  }
316
62
  return t.data;
317
63
  }
318
- function fe(e) {
64
+ function he(e) {
319
65
  return (e.xlsx?.role ?? "screen") === "screen";
320
66
  }
321
67
  //#endregion
322
68
  //#region src/mdx.ts
323
- var k = "annot:snapshot", pe = "/annot:snapshot", A = "annot:attributes", me = "/annot:attributes", he = /^\s*\/\*\s*([^\s*]+)\s*([\s\S]*?)\*\/\s*$/;
324
- async function j(e) {
325
- return M(await t(e, "utf8"), { filePath: e });
69
+ var ge = "annot:snapshot", _e = "/annot:snapshot", ve = "annot:attributes", ye = "/annot:attributes", be = /^\s*\/\*\s*([^\s*]+)\s*([\s\S]*?)\*\/\s*$/;
70
+ async function y(e) {
71
+ return xe(await i(e, "utf8"), { filePath: e });
326
72
  }
327
- function M(e, t = {}) {
328
- let n = p().use(f).use(u, ["yaml"]).use(d).parse(e), r = N(n, t.filePath);
73
+ function xe(e, t = {}) {
74
+ let n = ee().use(f).use(u, ["yaml"]).use(d).parse(e), r = Se(n, t.filePath);
329
75
  if (!r) return null;
330
76
  let i = [], a = [], o = [], s = [], c = {};
331
- return m(n, (t) => {
77
+ return te(n, (t) => {
332
78
  switch (t.type) {
333
79
  case "mdxJsxFlowElement":
334
80
  case "mdxJsxTextElement":
335
- F(t, e, {
81
+ we(t, e, {
336
82
  screens: i,
337
83
  transitions: a,
338
84
  history: o,
@@ -341,7 +87,7 @@ function M(e, t = {}) {
341
87
  return;
342
88
  case "mdxFlowExpression":
343
89
  case "mdxTextExpression":
344
- _e(t, c);
90
+ je(t, c);
345
91
  return;
346
92
  }
347
93
  }), {
@@ -354,113 +100,127 @@ function M(e, t = {}) {
354
100
  source: e
355
101
  };
356
102
  }
357
- function N(e, t) {
103
+ function Se(e, t) {
358
104
  let n = e.children.find((e) => e.type === "yaml");
359
105
  if (!n || !n.value) return null;
360
- let r = l.load(n.value);
106
+ let r = s.load(n.value);
361
107
  if (!r || typeof r != "object") return null;
362
108
  let i = r.annot;
363
109
  if (!i || typeof i != "object") return null;
364
- let a = D.safeParse(i);
110
+ let a = ue.safeParse(i);
365
111
  if (!a.success) {
366
112
  let e = t ? ` in ${t}` : "";
367
- throw Error(`Invalid \`annot:\` frontmatter${e}:\n${P(a.error)}`);
113
+ throw Error(`Invalid \`annot:\` frontmatter${e}:\n${Ce(a.error)}`);
368
114
  }
369
115
  return a.data;
370
116
  }
371
- function P(e) {
372
- return e.issues.map((e) => ` - ${e.path.length ? e.path.join(".") + ": " : ""}${e.message}`).join("\n");
117
+ function Ce(e) {
118
+ return e.issues.map((e) => ` - ${e.path.length ? e.path.map(String).join(".") + ": " : ""}${e.message}`).join("\n");
373
119
  }
374
- function F(e, t, n) {
120
+ function we(e, t, n) {
375
121
  let r = e;
376
122
  switch (r.name) {
377
123
  case "Screen":
378
- n.screens.push(I(r, t));
124
+ n.screens.push(Te(r, t));
379
125
  return;
380
126
  case "Transition":
381
- n.transitions.push(R(r, t));
127
+ n.transitions.push(Oe(r, t));
382
128
  return;
383
129
  case "HistoryEntry":
384
- n.history.push(ge(r, t));
130
+ n.history.push(ke(r, t));
385
131
  return;
386
132
  case "ScreenList":
387
- n.screenLists.push(z(r));
133
+ n.screenLists.push(Ae(r));
388
134
  return;
389
135
  }
390
136
  }
391
- function I(e, t) {
392
- let n = B(e), r = [], i = (n) => {
393
- let a = n;
394
- if (a.name === "Overlay") {
395
- r.push(L(a, t));
137
+ function Te(e, t) {
138
+ let n = b(e), r = [], i = [], a = (n) => {
139
+ let o = n;
140
+ if (o.name === "Overlay") {
141
+ r.push(De(o, t));
142
+ return;
143
+ }
144
+ if (o.name === "AnnotCallout") {
145
+ i.push(Ee(o, t));
396
146
  return;
397
147
  }
398
- if (!(a.name === "Screen" && a !== e)) for (let e of a.children ?? []) i(e);
148
+ if (!(o.name === "Screen" && o !== e)) for (let e of o.children ?? []) a(e);
149
+ };
150
+ for (let t of e.children ?? []) a(t);
151
+ let o = x(n.id);
152
+ if (!o) throw Error("<Screen> requires an `id` prop.");
153
+ let s = x(n.annotations), c = {
154
+ id: o,
155
+ src: x(n.src),
156
+ overlays: r,
157
+ callouts: i
399
158
  };
400
- for (let t of e.children ?? []) i(t);
401
- let a = V(n.id);
402
- if (!a) throw Error("<Screen> requires an `id` prop.");
159
+ return s && (c.annotations = s), c;
160
+ }
161
+ function Ee(e, t) {
162
+ let n = x(b(e).for);
163
+ if (!n) throw Error("<AnnotCallout> requires a `for=\"<overlay id>\"` prop.");
403
164
  return {
404
- id: a,
405
- src: V(n.src),
406
- overlays: r
165
+ for: n,
166
+ body: C(e, t)
407
167
  };
408
168
  }
409
- function L(e, t) {
410
- let n = B(e), r = n.match;
411
- if (!H(r)) throw Error("<Overlay> requires a `match` prop with at least `{ role, name }`.");
169
+ function De(e, t) {
170
+ let n = b(e), r = n.match;
171
+ if (!S(r)) throw Error("<Overlay> requires a `match` prop with at least `{ role, name }`.");
412
172
  return {
413
173
  match: r,
414
- intent: V(n.intent),
415
- number: ye(n.number),
416
- body: U(e, t)
174
+ intent: x(n.intent),
175
+ number: Ne(n.number),
176
+ body: C(e, t)
417
177
  };
418
178
  }
419
- function R(e, t) {
420
- let n = B(e), r = n.trigger;
421
- if (!H(r)) throw Error("<Transition> requires a `trigger` prop with at least `{ role, name }`.");
179
+ function Oe(e, t) {
180
+ let n = b(e), r = n.trigger;
181
+ if (!S(r)) throw Error("<Transition> requires a `trigger` prop with at least `{ role, name }`.");
422
182
  return {
423
183
  trigger: r,
424
- on: V(n.on),
425
- to: V(n.to),
426
- body: U(e, t)
184
+ on: x(n.on),
185
+ to: x(n.to),
186
+ body: C(e, t)
427
187
  };
428
188
  }
429
- function ge(e, t) {
430
- let n = B(e), r = V(n.version) ?? "", i = V(n.date) ?? "", a = V(n.author) ?? "";
189
+ function ke(e, t) {
190
+ let n = b(e), r = x(n.version) ?? "", i = x(n.date) ?? "", a = x(n.author) ?? "";
431
191
  if (!r || !i) throw Error("<HistoryEntry> requires `version` and `date` props.");
432
192
  return {
433
193
  version: r,
434
194
  date: i,
435
195
  author: a,
436
- body: U(e, t)
196
+ body: C(e, t)
437
197
  };
438
198
  }
439
- function z(e) {
440
- let t = B(e);
199
+ function Ae(e) {
200
+ let t = b(e);
441
201
  return {
442
- book: V(t.book),
443
- sort: V(t.sort)
202
+ book: x(t.book),
203
+ sort: x(t.sort)
444
204
  };
445
205
  }
446
- function _e(e, t) {
206
+ function je(e, t) {
447
207
  let n = e.value;
448
208
  if (typeof n != "string") return;
449
- let r = n.match(he);
209
+ let r = n.match(be);
450
210
  if (!r) return;
451
211
  let i = (r[1] ?? "").trim(), a = (r[2] ?? "").trim();
452
212
  switch (i) {
453
- case k:
213
+ case ge:
454
214
  t.snapshot = a;
455
215
  return;
456
- case A:
216
+ case ve:
457
217
  t.attributes = a;
458
218
  return;
459
- case pe:
460
- case me: return;
219
+ case _e:
220
+ case ye: return;
461
221
  }
462
222
  }
463
- function B(e) {
223
+ function b(e) {
464
224
  let t = {};
465
225
  for (let n of e.attributes ?? []) {
466
226
  let e = n;
@@ -474,11 +234,11 @@ function B(e) {
474
234
  continue;
475
235
  }
476
236
  let r = e.value;
477
- r.type === "mdxJsxAttributeValueExpression" && typeof r.value == "string" && (t[e.name] = ve(r.value));
237
+ r.type === "mdxJsxAttributeValueExpression" && typeof r.value == "string" && (t[e.name] = Me(r.value));
478
238
  }
479
239
  return t;
480
240
  }
481
- function ve(e) {
241
+ function Me(e) {
482
242
  let t = e.trim();
483
243
  if (t) {
484
244
  if (t === "true") return !0;
@@ -509,18 +269,18 @@ function ve(e) {
509
269
  return t;
510
270
  }
511
271
  }
512
- function V(e) {
272
+ function x(e) {
513
273
  return typeof e == "string" ? e : void 0;
514
274
  }
515
- function ye(e) {
275
+ function Ne(e) {
516
276
  return typeof e == "number" ? e : void 0;
517
277
  }
518
- function H(e) {
278
+ function S(e) {
519
279
  if (!e || typeof e != "object") return !1;
520
280
  let t = e;
521
- return !(typeof t.role != "string" || typeof t.name != "string" || t.under !== void 0 && !H(t.under));
281
+ return !(typeof t.role != "string" || typeof t.name != "string" || t.under !== void 0 && !S(t.under));
522
282
  }
523
- function U(e, t) {
283
+ function C(e, t) {
524
284
  let n = e.position;
525
285
  if (!n) return "";
526
286
  let r = t.slice(n.start.offset, n.end.offset);
@@ -528,24 +288,24 @@ function U(e, t) {
528
288
  let i = r.indexOf(">"), a = r.lastIndexOf("</");
529
289
  return i < 0 || a < 0 || a <= i ? "" : r.slice(i + 1, a).trim();
530
290
  }
531
- function W(e, t) {
291
+ function w(e, t) {
532
292
  let n = e;
533
- return t.snapshot !== void 0 && (n = G(n, k, t.snapshot)), t.attributes !== void 0 && (n = G(n, A, t.attributes)), n;
293
+ return t.snapshot !== void 0 && (n = T(n, ge, t.snapshot)), t.attributes !== void 0 && (n = T(n, ve, t.attributes)), n;
534
294
  }
535
- function G(e, t, n) {
536
- let r = new RegExp(String.raw`\{\s*/\*\s*` + xe(t) + String.raw`\s*[\s\S]*?\*/\s*\}`, "m"), i = be(t, n);
295
+ function T(e, t, n) {
296
+ let r = new RegExp(String.raw`\{\s*/\*\s*` + Fe(t) + String.raw`\s*[\s\S]*?\*/\s*\}`, "m"), i = Pe(t, n);
537
297
  return r.test(e) ? e.replace(r, i) : `${e.trimEnd()}\n\n${i}\n`;
538
298
  }
539
- function be(e, t) {
299
+ function Pe(e, t) {
540
300
  let n = t.trim();
541
301
  return n ? `{/* ${e}\n${n}\n*/}` : `{/* ${e} */}`;
542
302
  }
543
- function xe(e) {
303
+ function Fe(e) {
544
304
  return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
545
305
  }
546
306
  //#endregion
547
307
  //#region src/fixture.ts
548
- var K = [
308
+ var E = [
549
309
  "type",
550
310
  "required",
551
311
  "placeholder",
@@ -561,24 +321,34 @@ var K = [
561
321
  "aria-required",
562
322
  "aria-disabled",
563
323
  "aria-readonly"
564
- ], Se = c.extend({ screen: async ({ page: e }, t) => {
565
- await t({ capture: (t) => q(e, t) });
566
- } });
567
- async function q(e, t) {
568
- let n = await j(t.mdxPath);
569
- if (!n) throw Error(`captureScreen: ${t.mdxPath} has no \`annot:\` frontmatter — refusing to write.`);
570
- let i = n.screens.find((e) => e.id === t.id);
571
- if (!i) throw Error(`captureScreen: ${t.mdxPath} has no <Screen id="${t.id}"> block.`);
572
- let a = await (t.rootLocator ?? e.locator("body")).ariaSnapshot({
324
+ ], Ie = n.extend({
325
+ productDocs: async ({ page: e }, t) => {
326
+ await t({ sync: (t) => D(e, t) });
327
+ },
328
+ screen: async ({ page: e }, t) => {
329
+ let n = (t) => D(e, t);
330
+ await t({
331
+ sync: n,
332
+ capture: n
333
+ });
334
+ }
335
+ });
336
+ async function D(e, t) {
337
+ let n = await y(t.mdxPath);
338
+ if (!n) throw Error(`syncProductDocs: ${t.mdxPath} has no \`annot:\` frontmatter — refusing to write.`);
339
+ let r = n.screens.find((e) => e.id === t.id);
340
+ if (!r) throw Error(`syncProductDocs: ${t.mdxPath} has no <Screen id="${t.id}"> block.`);
341
+ let i = await (t.rootLocator ?? e.locator("body")).ariaSnapshot({
573
342
  mode: "ai",
574
343
  boxes: !0
575
- }), o = await J(e, i.overlays, t.attributeWhitelist ?? K), s = W(n.source, {
576
- snapshot: a.trim(),
577
- attributes: o
344
+ }), a = await O(e, r.overlays, t.attributeWhitelist ?? E), s = w(n.source, {
345
+ snapshot: i.trim(),
346
+ attributes: a
578
347
  });
579
- await r(t.mdxPath, s, "utf8");
348
+ await o(t.mdxPath, s, "utf8");
580
349
  }
581
- async function J(e, t, n) {
350
+ var Le = D;
351
+ async function O(e, t, n) {
582
352
  let r = [];
583
353
  for (let i of t) {
584
354
  let t = e.getByRole(i.match.role, {
@@ -602,112 +372,1577 @@ async function J(e, t, n) {
602
372
  return r.join("\n");
603
373
  }
604
374
  //#endregion
605
- //#region src/cli.ts
606
- async function Ce(e, t = {}) {
607
- let n = t.stdout ?? ((e) => process.stdout.write(`${e}\n`)), r = t.stderr ?? ((e) => process.stderr.write(`${e}\n`)), i = t.cwd ?? process.cwd(), [, , a, ...o] = e;
608
- if (a !== "init" && a !== "sync" && a !== "lint") return r(we), +!!a;
609
- try {
610
- switch (a) {
611
- case "init": return await Te(o, {
612
- cwd: i,
613
- stdout: n,
614
- stderr: r
615
- });
616
- case "sync": return await ke(o, {
617
- ...t,
618
- cwd: i,
619
- stdout: n,
620
- stderr: r
621
- });
622
- case "lint": return await Ae(o, {
623
- ...t,
624
- cwd: i,
625
- stdout: n,
626
- stderr: r
627
- });
628
- }
629
- } catch (e) {
630
- return r(`annot docs ${a}: ${e.message}`), 1;
631
- }
375
+ //#region src/mdx-annotations.ts
376
+ function k(e) {
377
+ let t = [];
378
+ for (let n of e.split(/\r?\n/)) {
379
+ if (!n.trim()) continue;
380
+ let e = n.match(/^\s*-\s+([a-z]+)(?:\s+"([^"]*?)")?/);
381
+ if (!e) continue;
382
+ let r = e[1] ?? "", i = e[2] ?? "", a = n.match(/\[ref=(e\d+)\]/)?.[1], o = n.match(/\[box=(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)\]/);
383
+ !a || !o || t.push({
384
+ role: r,
385
+ name: i,
386
+ ref: a,
387
+ box: {
388
+ x: Number.parseFloat(o[1] ?? "0"),
389
+ y: Number.parseFloat(o[2] ?? "0"),
390
+ width: Number.parseFloat(o[3] ?? "0"),
391
+ height: Number.parseFloat(o[4] ?? "0")
392
+ }
393
+ });
394
+ }
395
+ return t;
396
+ }
397
+ function Re(e) {
398
+ let t = [];
399
+ return ae(e, (e) => {
400
+ e.bbox && e.name && t.push({
401
+ role: e.role,
402
+ name: e.name,
403
+ ref: e.ref,
404
+ box: e.bbox
405
+ });
406
+ }), t;
407
+ }
408
+ function ze(e, t, n) {
409
+ let r = [], i = 1;
410
+ for (let a of e) {
411
+ let e = t.find((e) => e.role === a.match.role && e.name === a.match.name);
412
+ if (!e) continue;
413
+ let o = a.number ?? i++;
414
+ r.push({
415
+ type: "numberedBadge",
416
+ bbox: e.box,
417
+ number: o,
418
+ placement: "auto",
419
+ imageWidth: n.width,
420
+ imageHeight: n.height,
421
+ intent: a.intent === "action" ? "warning" : a.intent === "required" ? "error" : "info"
422
+ });
423
+ }
424
+ return r;
425
+ }
426
+ function Be(e, t, n) {
427
+ let r = [];
428
+ for (let i of e) {
429
+ let e = t.find((e) => e.role === i.match.role && e.name === i.match.name);
430
+ e && r.push({
431
+ type: "numberedBadge",
432
+ bbox: e.box,
433
+ number: i.number,
434
+ placement: "auto",
435
+ imageWidth: n.width,
436
+ imageHeight: n.height,
437
+ intent: i.intent === "action" ? "warning" : i.intent === "required" ? "error" : "info"
438
+ });
439
+ }
440
+ return r;
441
+ }
442
+ async function Ve(e) {
443
+ let t = e.cwd ?? process.cwd(), n = await y(h(e.mdxPath) ? e.mdxPath : _(t, e.mdxPath));
444
+ if (!n) throw Error(`resolveMdxAnnotations: ${e.mdxPath} has no \`annot:\` frontmatter — cannot resolve.`);
445
+ let r = n.screens.find((t) => t.id === e.screenId);
446
+ if (!r) throw Error(`resolveMdxAnnotations: ${e.mdxPath} has no <Screen id="${e.screenId}"> block.`);
447
+ let i = k(n.commentBlocks.snapshot ?? "");
448
+ return ze(r.overlays, i, e.dims);
449
+ }
450
+ function He(e) {
451
+ return `<svg xmlns="http://www.w3.org/2000/svg">${v(e)}</svg>`;
452
+ }
453
+ function Ue(e, t, n) {
454
+ let r = [];
455
+ for (let i of e) {
456
+ let e = Ge(i, t, n);
457
+ e && r.push(e);
458
+ }
459
+ return r;
460
+ }
461
+ function We(e, t) {
462
+ let n = [];
463
+ for (let r of e) {
464
+ if (r.kind !== "redact" || r.style !== "mosaic" && r.style !== "blur") continue;
465
+ let e = null;
466
+ if (r.match) {
467
+ let n = j(t, r.match);
468
+ if (!n) continue;
469
+ e = n.box;
470
+ } else r.bbox && (e = r.bbox);
471
+ if (!e) continue;
472
+ let i = {
473
+ bbox: e,
474
+ style: r.style
475
+ };
476
+ r.color !== void 0 && (i.color = r.color), n.push(i);
477
+ }
478
+ return n;
479
+ }
480
+ function Ge(e, t, n) {
481
+ switch (e.kind) {
482
+ case "rect": return Ke(e, t);
483
+ case "circle": return qe(e, t);
484
+ case "arrow": return Je(e, t);
485
+ case "text": return Xe(e, t);
486
+ case "callout": return Qe(e, t);
487
+ case "freehand": return $e(e);
488
+ case "redact": return tt(e, t);
489
+ case "focusMask": return nt(e, t, n);
490
+ }
491
+ }
492
+ function Ke(e, t) {
493
+ let n = null;
494
+ if (e.match) {
495
+ let r = j(t, e.match);
496
+ if (!r) return null;
497
+ n = r.box;
498
+ } else if (e.coversElements) {
499
+ let r = [];
500
+ for (let n of e.coversElements) {
501
+ let e = j(t, n);
502
+ if (!e) return null;
503
+ r.push(e.box);
504
+ }
505
+ n = it(r);
506
+ } else e.bbox && (n = e.bbox);
507
+ return n ? N({
508
+ type: "rect",
509
+ bbox: n
510
+ }, e) : null;
511
+ }
512
+ function qe(e, t) {
513
+ if (e.match) {
514
+ let n = j(t, e.match);
515
+ return n ? N({
516
+ type: "circle",
517
+ center: rt(n.box),
518
+ radius: e.radius ?? Math.max(n.box.width, n.box.height) / 2
519
+ }, e) : null;
520
+ }
521
+ return e.center && e.radius !== void 0 ? N({
522
+ type: "circle",
523
+ center: e.center,
524
+ radius: e.radius
525
+ }, e) : null;
526
+ }
527
+ function Je(e, t) {
528
+ let n = Ye(e.from, t);
529
+ if (!n) return null;
530
+ let r = Ye(e.to, t);
531
+ return r ? N({
532
+ type: "arrow",
533
+ from: n,
534
+ to: r
535
+ }, e) : null;
536
+ }
537
+ function Ye(e, t) {
538
+ if ("point" in e) return e.point;
539
+ let n = j(t, e.match);
540
+ return n ? rt(n.box) : null;
541
+ }
542
+ var A = 8;
543
+ function Xe(e, t) {
544
+ let n, r;
545
+ if (e.anchor) {
546
+ let i = j(t, e.anchor.match);
547
+ if (!i) return null;
548
+ let a = Ze(i.box, e.anchor.position ?? "above");
549
+ n = a.at, r = a.anchor;
550
+ } else if (e.at) n = e.at, r = "start";
551
+ else return null;
552
+ let i = {
553
+ type: "text",
554
+ at: n,
555
+ content: e.text,
556
+ anchor: r
557
+ };
558
+ return e.fontSize !== void 0 && (i.fontSize = e.fontSize), N(i, e);
559
+ }
560
+ function Ze(e, t) {
561
+ let n = e.x + e.width / 2, r = e.y + e.height / 2;
562
+ switch (t) {
563
+ case "above": return {
564
+ at: {
565
+ x: n,
566
+ y: e.y - A
567
+ },
568
+ anchor: "middle"
569
+ };
570
+ case "below": return {
571
+ at: {
572
+ x: n,
573
+ y: e.y + e.height + A
574
+ },
575
+ anchor: "middle"
576
+ };
577
+ case "left": return {
578
+ at: {
579
+ x: e.x - A,
580
+ y: r
581
+ },
582
+ anchor: "end"
583
+ };
584
+ case "right": return {
585
+ at: {
586
+ x: e.x + e.width + A,
587
+ y: r
588
+ },
589
+ anchor: "start"
590
+ };
591
+ case "center": return {
592
+ at: {
593
+ x: n,
594
+ y: r
595
+ },
596
+ anchor: "middle"
597
+ };
598
+ }
599
+ }
600
+ function Qe(e, t) {
601
+ let n = null;
602
+ if ("match" in e.target) {
603
+ let r = j(t, e.target.match);
604
+ if (!r) return null;
605
+ n = r.box;
606
+ } else n = e.target.bbox;
607
+ return N({
608
+ type: "callout",
609
+ at: e.at,
610
+ targetBbox: n,
611
+ content: e.text
612
+ }, e);
613
+ }
614
+ function $e(e) {
615
+ return N({
616
+ type: "freehand",
617
+ path: e.path
618
+ }, e);
619
+ }
620
+ var et = "#222222";
621
+ function tt(e, t) {
622
+ if (e.style === "mosaic" || e.style === "blur") return null;
623
+ let n = null;
624
+ if (e.match) {
625
+ let r = j(t, e.match);
626
+ if (!r) return null;
627
+ n = r.box;
628
+ } else e.bbox && (n = e.bbox);
629
+ return n ? {
630
+ type: "rect",
631
+ bbox: n,
632
+ fill: e.fill ?? et,
633
+ stroke: e.stroke ?? "none",
634
+ strokeWidth: e.strokeWidth ?? 0,
635
+ ...e.intent ? { intent: M(e.intent) } : {}
636
+ } : null;
637
+ }
638
+ function nt(e, t, n) {
639
+ let r = null;
640
+ if ("match" in e.cutout) {
641
+ let n = j(t, e.cutout.match);
642
+ if (!n) return null;
643
+ let i = e.cutout.padding ?? 0;
644
+ r = at(n.box, i);
645
+ } else r = e.cutout.bbox;
646
+ let i = {
647
+ type: "focusMask",
648
+ cutout: r,
649
+ imageWidth: n.width,
650
+ imageHeight: n.height
651
+ };
652
+ return e.dimColor !== void 0 && (i.dimColor = e.dimColor), N(i, e);
653
+ }
654
+ function j(e, t) {
655
+ return e.find((e) => e.role === t.role && e.name === t.name);
656
+ }
657
+ function rt(e) {
658
+ return {
659
+ x: e.x + e.width / 2,
660
+ y: e.y + e.height / 2
661
+ };
662
+ }
663
+ function it(e) {
664
+ let t = Math.min(...e.map((e) => e.x)), n = Math.min(...e.map((e) => e.y)), r = Math.max(...e.map((e) => e.x + e.width)), i = Math.max(...e.map((e) => e.y + e.height));
665
+ return {
666
+ x: t,
667
+ y: n,
668
+ width: r - t,
669
+ height: i - n
670
+ };
671
+ }
672
+ function at(e, t) {
673
+ return t <= 0 ? e : {
674
+ x: e.x - t,
675
+ y: e.y - t,
676
+ width: e.width + t * 2,
677
+ height: e.height + t * 2
678
+ };
679
+ }
680
+ function M(e) {
681
+ if (e !== void 0) switch (e) {
682
+ case "required": return "error";
683
+ case "action": return "warning";
684
+ case "info":
685
+ case "warning":
686
+ case "error":
687
+ case "success":
688
+ case "neutral": return e;
689
+ }
690
+ }
691
+ function N(e, t) {
692
+ let n = M(t.intent), r = { ...e };
693
+ return n !== void 0 && !("intent" in r) && (r.intent = n), n !== void 0 && r.intent === void 0 && (r.intent = n), t.stroke !== void 0 && (r.stroke = t.stroke), t.strokeWidth !== void 0 && (r.strokeWidth = t.strokeWidth), t.fill !== void 0 && (r.fill = t.fill), t.color !== void 0 && (r.color = t.color), r;
694
+ }
695
+ function ot(e) {
696
+ return e.length === 0 ? st() : `<svg xmlns="http://www.w3.org/2000/svg">${v(e)}</svg>`;
697
+ }
698
+ function st() {
699
+ return "<svg xmlns=\"http://www.w3.org/2000/svg\"></svg>";
700
+ }
701
+ //#endregion
702
+ //#region src/playwright-screenshot-hook.ts
703
+ var ct = Symbol.for("@ingcreators/annot-product-docs:mdx-resolver"), lt = async ({ annot: e, page: t }) => {
704
+ if (!e.mdx) return null;
705
+ let { id: n, path: r } = e.mdx;
706
+ return {
707
+ prepare: () => D(t, {
708
+ id: n,
709
+ mdxPath: r
710
+ }),
711
+ resolveAnnotations: (e) => Ve({
712
+ mdxPath: r,
713
+ screenId: n,
714
+ dims: e
715
+ })
716
+ };
717
+ }, ut = lt;
718
+ ut[ct] || (ut[ct] = !0, e.push(lt));
719
+ //#endregion
720
+ //#region src/annotations-yaml.ts
721
+ var dt = 1, P = [
722
+ "rect",
723
+ "circle",
724
+ "arrow",
725
+ "text",
726
+ "callout",
727
+ "freehand",
728
+ "redact",
729
+ "focusMask"
730
+ ], F = class extends Error {
731
+ source;
732
+ constructor(e, t) {
733
+ super(e), this.source = t, this.name = "AnnotationsYamlError";
734
+ }
735
+ };
736
+ function ft(e) {
737
+ let t;
738
+ try {
739
+ t = l(e);
740
+ } catch (t) {
741
+ throw new F(`Failed to parse annotations yaml: ${t.message}`, e);
742
+ }
743
+ if (!t || typeof t != "object") throw new F("Annotations yaml must be a mapping", e);
744
+ let n = t, r = n.version;
745
+ if (r !== 1) throw new F(`Unsupported annotations yaml version: expected 1, got ${JSON.stringify(r)}`, e);
746
+ let i = n.overlays;
747
+ if (!Array.isArray(i)) throw new F("Annotations yaml `overlays` must be an array", e);
748
+ let a = {
749
+ version: 1,
750
+ overlays: i.map((e, t) => mt(e, t))
751
+ }, o = n.annotations;
752
+ if (o !== void 0) {
753
+ if (!Array.isArray(o)) throw new F("Annotations yaml `annotations` must be an array", e);
754
+ let t = o.map((e, t) => _t(e, t));
755
+ t.length > 0 && (a.annotations = t);
756
+ }
757
+ let s = n.meta;
758
+ if (s && typeof s == "object" && !Array.isArray(s)) {
759
+ let e = {};
760
+ for (let [t, n] of Object.entries(s)) typeof n == "string" && (e[t] = n);
761
+ Object.keys(e).length > 0 && (a.meta = e);
762
+ }
763
+ return a;
764
+ }
765
+ function pt(e) {
766
+ for (let t of e.overlays) {
767
+ if (typeof t.id != "string" || t.id.length === 0) throw new F(`Overlay entry missing id: ${JSON.stringify(t)}`);
768
+ if (!t.match || typeof t.match.role != "string" || typeof t.match.name != "string") throw new F(`Overlay entry ${t.id}: match must have string role + name`);
769
+ if (typeof t.number != "number" || !Number.isFinite(t.number)) throw new F(`Overlay entry ${t.id}: number must be a finite number`);
770
+ }
771
+ if (e.annotations) for (let t of e.annotations) jt(t);
772
+ let t = {
773
+ version: e.version,
774
+ overlays: e.overlays.map(gt)
775
+ };
776
+ return e.annotations && e.annotations.length > 0 && (t.annotations = e.annotations.map(Ot)), e.meta && Object.keys(e.meta).length > 0 && (t.meta = { ...e.meta }), c(t, {
777
+ lineWidth: -1,
778
+ noRefs: !0,
779
+ sortKeys: !1
780
+ });
781
+ }
782
+ function mt(e, t) {
783
+ if (!e || typeof e != "object") throw new F(`Overlay #${t}: not a mapping`);
784
+ let n = e, r = n.id;
785
+ if (typeof r != "string" || r.length === 0) throw new F(`Overlay #${t}: missing or empty id`);
786
+ let i = n.kind;
787
+ if (i !== "numberedBadge") throw new F(`Overlay ${r}: unsupported kind ${JSON.stringify(i)} (expected "numberedBadge")`);
788
+ let a = ht(n.match, r), o = n.number;
789
+ if (typeof o != "number" || !Number.isFinite(o)) throw new F(`Overlay ${r}: number must be a finite number`);
790
+ let s = {
791
+ id: r,
792
+ kind: "numberedBadge",
793
+ match: a,
794
+ number: o
795
+ }, c = n.intent;
796
+ return typeof c == "string" && (s.intent = c), s;
797
+ }
798
+ function ht(e, t) {
799
+ if (!e || typeof e != "object") throw new F(`Overlay ${t}: match must be a mapping`);
800
+ let n = e, r = n.role, i = n.name;
801
+ if (typeof r != "string" || typeof i != "string") throw new F(`Overlay ${t}: match.role and match.name must both be strings`);
802
+ let a = {
803
+ role: r,
804
+ name: i
805
+ };
806
+ return n.under !== void 0 && (a.under = ht(n.under, t)), a;
807
+ }
808
+ function gt(e) {
809
+ let t = {
810
+ id: e.id,
811
+ kind: e.kind,
812
+ match: I(e.match)
813
+ };
814
+ return e.intent !== void 0 && (t.intent = e.intent), t.number = e.number, t;
815
+ }
816
+ function I(e) {
817
+ let t = {
818
+ role: e.role,
819
+ name: e.name
820
+ };
821
+ return e.under && (t.under = I(e.under)), t;
822
+ }
823
+ function _t(e, t) {
824
+ if (!e || typeof e != "object") throw new F(`Annotation #${t}: not a mapping`);
825
+ let n = e, r = n.id;
826
+ if (typeof r != "string" || r.length === 0) throw new F(`Annotation #${t}: missing or empty id`);
827
+ let i = n.kind;
828
+ if (typeof i != "string" || !P.includes(i)) throw new F(`Annotation ${r}: unsupported kind ${JSON.stringify(i)} (expected one of ${P.join(", ")})`);
829
+ switch (i) {
830
+ case "rect": return vt(r, n);
831
+ case "circle": return yt(r, n);
832
+ case "arrow": return bt(r, n);
833
+ case "text": return St(r, n);
834
+ case "callout": return Ct(r, n);
835
+ case "freehand": return wt(r, n);
836
+ case "redact": return Tt(r, n);
837
+ case "focusMask": return Et(r, n);
838
+ }
839
+ }
840
+ function vt(e, t) {
841
+ let n = t.match !== void 0, r = t.coversElements !== void 0, i = t.bbox !== void 0;
842
+ B(e, "rect", {
843
+ match: n,
844
+ coversElements: r,
845
+ bbox: i
846
+ });
847
+ let a = {
848
+ id: e,
849
+ kind: "rect"
850
+ };
851
+ return n && (a.match = L(e, t.match)), r && (a.coversElements = Dt(e, t.coversElements)), i && (a.bbox = z(e, t.bbox)), V(t, a), a;
852
+ }
853
+ function yt(e, t) {
854
+ let n = t.match !== void 0, r = t.center !== void 0;
855
+ B(e, "circle", {
856
+ match: n,
857
+ center: r
858
+ });
859
+ let i = {
860
+ id: e,
861
+ kind: "circle"
862
+ };
863
+ if (n && (i.match = L(e, t.match)), r) {
864
+ i.center = R(e, t.center);
865
+ let n = t.radius;
866
+ if (typeof n != "number" || !Number.isFinite(n) || n < 0) throw new F(`Annotation ${e}: circle with \`center\` requires a finite non-negative \`radius\``);
867
+ i.radius = n;
868
+ } else {
869
+ let n = t.radius;
870
+ if (n !== void 0) {
871
+ if (typeof n != "number" || !Number.isFinite(n) || n < 0) throw new F(`Annotation ${e}: circle \`radius\` must be a finite non-negative number`);
872
+ i.radius = n;
873
+ }
874
+ }
875
+ return V(t, i), i;
876
+ }
877
+ function bt(e, t) {
878
+ let n = {
879
+ id: e,
880
+ kind: "arrow",
881
+ from: xt(e, "from", t.from),
882
+ to: xt(e, "to", t.to)
883
+ };
884
+ return V(t, n), n;
885
+ }
886
+ function xt(e, t, n) {
887
+ if (!n || typeof n != "object") throw new F(`Annotation ${e}: arrow.${t} must be a mapping with \`match\` or \`point\``);
888
+ let r = n, i = r.match !== void 0;
889
+ if (i === (r.point !== void 0)) throw new F(`Annotation ${e}: arrow.${t} requires exactly one of \`match\` / \`point\``);
890
+ return i ? { match: L(e, r.match) } : { point: R(e, r.point) };
891
+ }
892
+ function St(e, t) {
893
+ let n = t.text;
894
+ if (typeof n != "string" || n.length === 0) throw new F(`Annotation ${e}: text requires a non-empty \`text\` string`);
895
+ let r = t.anchor !== void 0, i = t.at !== void 0;
896
+ B(e, "text", {
897
+ anchor: r,
898
+ at: i
899
+ });
900
+ let a = {
901
+ id: e,
902
+ kind: "text",
903
+ text: n
904
+ };
905
+ if (r) {
906
+ let n = t.anchor;
907
+ if (!n || typeof n != "object") throw new F(`Annotation ${e}: text.anchor must be a mapping`);
908
+ let r = n, i = { match: L(e, r.match) }, o = r.position;
909
+ if (o !== void 0) {
910
+ if (typeof o != "string" || ![
911
+ "above",
912
+ "below",
913
+ "left",
914
+ "right",
915
+ "center"
916
+ ].includes(o)) throw new F(`Annotation ${e}: text.anchor.position must be one of above / below / left / right / center`);
917
+ i.position = o;
918
+ }
919
+ a.anchor = i;
920
+ }
921
+ i && (a.at = R(e, t.at));
922
+ let o = t.fontSize;
923
+ if (o !== void 0) {
924
+ if (typeof o != "number" || !Number.isFinite(o) || o <= 0) throw new F(`Annotation ${e}: text.fontSize must be a finite positive number`);
925
+ a.fontSize = o;
926
+ }
927
+ return V(t, a), a;
928
+ }
929
+ function Ct(e, t) {
930
+ let n = t.text;
931
+ if (typeof n != "string" || n.length === 0) throw new F(`Annotation ${e}: callout requires a non-empty \`text\` string`);
932
+ let r = t.target;
933
+ if (!r || typeof r != "object") throw new F(`Annotation ${e}: callout.target must be a mapping with \`match\` or \`bbox\``);
934
+ let i = r, a = i.match !== void 0;
935
+ if (a === (i.bbox !== void 0)) throw new F(`Annotation ${e}: callout.target requires exactly one of \`match\` / \`bbox\``);
936
+ let o = a ? { match: L(e, i.match) } : { bbox: z(e, i.bbox) }, s = t.at;
937
+ if (s === void 0) throw new F(`Annotation ${e}: callout requires an \`at\` point`);
938
+ let c = {
939
+ id: e,
940
+ kind: "callout",
941
+ text: n,
942
+ target: o,
943
+ at: R(e, s)
944
+ };
945
+ return V(t, c), c;
946
+ }
947
+ function wt(e, t) {
948
+ let n = t.path;
949
+ if (typeof n != "string" || n.length === 0) throw new F(`Annotation ${e}: freehand requires a non-empty \`path\` string`);
950
+ let r = {
951
+ id: e,
952
+ kind: "freehand",
953
+ path: n
954
+ };
955
+ return V(t, r), r;
956
+ }
957
+ function Tt(e, t) {
958
+ let n = t.match !== void 0, r = t.bbox !== void 0;
959
+ B(e, "redact", {
960
+ match: n,
961
+ bbox: r
962
+ });
963
+ let i = {
964
+ id: e,
965
+ kind: "redact"
966
+ };
967
+ n && (i.match = L(e, t.match)), r && (i.bbox = z(e, t.bbox));
968
+ let a = t.style;
969
+ if (a !== void 0) {
970
+ if (a !== "solid" && a !== "mosaic" && a !== "blur") throw new F(`Annotation ${e}: redact.style must be one of "solid" / "mosaic" / "blur"`);
971
+ i.style = a;
972
+ }
973
+ return V(t, i), i;
974
+ }
975
+ function Et(e, t) {
976
+ let n = t.cutout;
977
+ if (!n || typeof n != "object") throw new F(`Annotation ${e}: focusMask.cutout must be a mapping with \`match\` or \`bbox\``);
978
+ let r = n, i = r.match !== void 0;
979
+ if (i === (r.bbox !== void 0)) throw new F(`Annotation ${e}: focusMask.cutout requires exactly one of \`match\` / \`bbox\``);
980
+ let a;
981
+ if (i) {
982
+ let t = L(e, r.match), n = r.padding;
983
+ if (n !== void 0) {
984
+ if (typeof n != "number" || !Number.isFinite(n) || n < 0) throw new F(`Annotation ${e}: focusMask.cutout.padding must be a finite non-negative number`);
985
+ a = {
986
+ match: t,
987
+ padding: n
988
+ };
989
+ } else a = { match: t };
990
+ } else a = { bbox: z(e, r.bbox) };
991
+ let o = {
992
+ id: e,
993
+ kind: "focusMask",
994
+ cutout: a
995
+ }, s = t.dimColor;
996
+ if (s !== void 0) {
997
+ if (typeof s != "string") throw new F(`Annotation ${e}: focusMask.dimColor must be a string`);
998
+ o.dimColor = s;
999
+ }
1000
+ return V(t, o), o;
1001
+ }
1002
+ function L(e, t) {
1003
+ if (!t || typeof t != "object") throw new F(`Annotation ${e}: match must be a mapping`);
1004
+ let n = t, r = n.role, i = n.name;
1005
+ if (typeof r != "string" || typeof i != "string") throw new F(`Annotation ${e}: match.role and match.name must both be strings`);
1006
+ let a = {
1007
+ role: r,
1008
+ name: i
1009
+ };
1010
+ return n.under !== void 0 && (a.under = L(e, n.under)), a;
1011
+ }
1012
+ function Dt(e, t) {
1013
+ if (!Array.isArray(t) || t.length === 0) throw new F(`Annotation ${e}: coversElements must be a non-empty array of match objects`);
1014
+ return t.map((t) => L(e, t));
1015
+ }
1016
+ function R(e, t) {
1017
+ if (!t || typeof t != "object") throw new F(`Annotation ${e}: point must be a mapping`);
1018
+ let n = t, r = n.x, i = n.y;
1019
+ if (typeof r != "number" || !Number.isFinite(r) || typeof i != "number" || !Number.isFinite(i)) throw new F(`Annotation ${e}: point.x and point.y must be finite numbers`);
1020
+ return {
1021
+ x: r,
1022
+ y: i
1023
+ };
1024
+ }
1025
+ function z(e, t) {
1026
+ if (!t || typeof t != "object") throw new F(`Annotation ${e}: bbox must be a mapping`);
1027
+ let n = t, r = n.x, i = n.y, a = n.width, o = n.height;
1028
+ if (typeof r != "number" || !Number.isFinite(r) || typeof i != "number" || !Number.isFinite(i) || typeof a != "number" || !Number.isFinite(a) || a < 0 || typeof o != "number" || !Number.isFinite(o) || o < 0) throw new F(`Annotation ${e}: bbox requires finite numeric x / y + non-negative width / height`);
1029
+ return {
1030
+ x: r,
1031
+ y: i,
1032
+ width: a,
1033
+ height: o
1034
+ };
1035
+ }
1036
+ function B(e, t, n) {
1037
+ if (Object.entries(n).filter(([, e]) => e).length !== 1) throw new F(`Annotation ${e}: ${t} requires exactly one of ${Object.keys(n).join(" / ")}`);
1038
+ }
1039
+ function V(e, t) {
1040
+ let n = e.intent;
1041
+ typeof n == "string" && (t.intent = n);
1042
+ let r = e.stroke;
1043
+ typeof r == "string" && (t.stroke = r);
1044
+ let i = e.strokeWidth;
1045
+ typeof i == "number" && Number.isFinite(i) && i >= 0 && (t.strokeWidth = i);
1046
+ let a = e.fill;
1047
+ typeof a == "string" && (t.fill = a);
1048
+ let o = e.color;
1049
+ typeof o == "string" && (t.color = o);
1050
+ }
1051
+ function Ot(e) {
1052
+ let t = {
1053
+ id: e.id,
1054
+ kind: e.kind
1055
+ };
1056
+ switch (e.kind) {
1057
+ case "rect":
1058
+ e.match && (t.match = I(e.match)), e.coversElements && (t.coversElements = e.coversElements.map(I)), e.bbox && (t.bbox = H(e.bbox));
1059
+ break;
1060
+ case "circle":
1061
+ e.match && (t.match = I(e.match)), e.center && (t.center = U(e.center)), e.radius !== void 0 && (t.radius = e.radius);
1062
+ break;
1063
+ case "arrow":
1064
+ t.from = kt(e.from), t.to = kt(e.to);
1065
+ break;
1066
+ case "text":
1067
+ if (t.text = e.text, e.anchor) {
1068
+ let n = { match: I(e.anchor.match) };
1069
+ e.anchor.position && (n.position = e.anchor.position), t.anchor = n;
1070
+ }
1071
+ e.at && (t.at = U(e.at)), e.fontSize !== void 0 && (t.fontSize = e.fontSize);
1072
+ break;
1073
+ case "callout":
1074
+ t.text = e.text, "match" in e.target ? t.target = { match: I(e.target.match) } : t.target = { bbox: H(e.target.bbox) }, t.at = U(e.at);
1075
+ break;
1076
+ case "freehand":
1077
+ t.path = e.path;
1078
+ break;
1079
+ case "redact":
1080
+ e.match && (t.match = I(e.match)), e.bbox && (t.bbox = H(e.bbox)), e.style !== void 0 && (t.style = e.style);
1081
+ break;
1082
+ case "focusMask":
1083
+ if ("match" in e.cutout) {
1084
+ let n = { match: I(e.cutout.match) };
1085
+ e.cutout.padding !== void 0 && (n.padding = e.cutout.padding), t.cutout = n;
1086
+ } else t.cutout = { bbox: H(e.cutout.bbox) };
1087
+ e.dimColor !== void 0 && (t.dimColor = e.dimColor);
1088
+ break;
1089
+ }
1090
+ return At(t, e), t;
1091
+ }
1092
+ function kt(e) {
1093
+ return "match" in e ? { match: I(e.match) } : { point: U(e.point) };
1094
+ }
1095
+ function H(e) {
1096
+ return {
1097
+ x: e.x,
1098
+ y: e.y,
1099
+ width: e.width,
1100
+ height: e.height
1101
+ };
1102
+ }
1103
+ function U(e) {
1104
+ return {
1105
+ x: e.x,
1106
+ y: e.y
1107
+ };
1108
+ }
1109
+ function At(e, t) {
1110
+ t.intent !== void 0 && (e.intent = t.intent), t.stroke !== void 0 && (e.stroke = t.stroke), t.strokeWidth !== void 0 && (e.strokeWidth = t.strokeWidth), t.fill !== void 0 && (e.fill = t.fill), t.color !== void 0 && (e.color = t.color);
1111
+ }
1112
+ function jt(e) {
1113
+ if (typeof e.id != "string" || e.id.length === 0) throw new F(`Annotation entry missing id: ${JSON.stringify(e)}`);
1114
+ if (!P.includes(e.kind)) throw new F(`Annotation ${e.id}: unknown kind ${JSON.stringify(e.kind)}`);
1115
+ switch (e.kind) {
1116
+ case "rect":
1117
+ if ([
1118
+ e.match !== void 0,
1119
+ e.coversElements !== void 0,
1120
+ e.bbox !== void 0
1121
+ ].filter(Boolean).length !== 1) throw new F(`Annotation ${e.id}: rect requires exactly one of match / coversElements / bbox`);
1122
+ if (e.coversElements && e.coversElements.length === 0) throw new F(`Annotation ${e.id}: rect.coversElements must be non-empty`);
1123
+ break;
1124
+ case "circle":
1125
+ if ([e.match !== void 0, e.center !== void 0].filter(Boolean).length !== 1) throw new F(`Annotation ${e.id}: circle requires exactly one of match / center`);
1126
+ if (e.center && (e.radius === void 0 || !Number.isFinite(e.radius) || e.radius < 0)) throw new F(`Annotation ${e.id}: circle with center requires a finite non-negative radius`);
1127
+ break;
1128
+ case "arrow":
1129
+ if (!e.from || !e.to) throw new F(`Annotation ${e.id}: arrow requires both from + to`);
1130
+ break;
1131
+ case "text":
1132
+ if (typeof e.text != "string" || e.text.length === 0) throw new F(`Annotation ${e.id}: text requires non-empty \`text\``);
1133
+ if ([e.anchor !== void 0, e.at !== void 0].filter(Boolean).length !== 1) throw new F(`Annotation ${e.id}: text requires exactly one of anchor / at`);
1134
+ break;
1135
+ case "callout":
1136
+ if (typeof e.text != "string" || e.text.length === 0) throw new F(`Annotation ${e.id}: callout requires non-empty \`text\``);
1137
+ if (!e.target || !e.at) throw new F(`Annotation ${e.id}: callout requires target + at`);
1138
+ break;
1139
+ case "freehand":
1140
+ if (typeof e.path != "string" || e.path.length === 0) throw new F(`Annotation ${e.id}: freehand requires non-empty \`path\``);
1141
+ break;
1142
+ case "redact":
1143
+ if ([e.match !== void 0, e.bbox !== void 0].filter(Boolean).length !== 1) throw new F(`Annotation ${e.id}: redact requires exactly one of match / bbox`);
1144
+ if (e.style !== void 0 && e.style !== "solid" && e.style !== "mosaic" && e.style !== "blur") throw new F(`Annotation ${e.id}: redact.style must be one of "solid" / "mosaic" / "blur"`);
1145
+ break;
1146
+ case "focusMask":
1147
+ if (!e.cutout) throw new F(`Annotation ${e.id}: focusMask requires cutout`);
1148
+ break;
1149
+ }
1150
+ }
1151
+ //#endregion
1152
+ //#region src/deprecation.ts
1153
+ var W = /* @__PURE__ */ new Set();
1154
+ function Mt(e, t = (e) => console.warn(e)) {
1155
+ let n = `${e.mdxPath}\0${e.screenId}`;
1156
+ W.has(n) || (W.add(n), t(Nt(e)));
1157
+ }
1158
+ function Nt(e) {
1159
+ return `[annot-docs] DEPRECATED: ${e.mdxPath} screen "${e.screenId}" uses ${e.overlayCount} inline <Overlay> block(s). Run \`annot docs migrate-overlays-to-annotations\` to extract them into a \`.annotations.yaml\` file + <AnnotCallout for=…> JSX. The inline <Overlay> form is supported during a deprecation window; see OQ-08 in docs/plans/living-spec-authoring-roadmap.md for the removal schedule.`;
1160
+ }
1161
+ function Pt() {
1162
+ W.clear();
1163
+ }
1164
+ //#endregion
1165
+ //#region src/resolver.ts
1166
+ function G(e) {
1167
+ let t = [], n = e.split(/\r?\n/), r = [];
1168
+ for (let e of n) {
1169
+ if (!e.trim()) continue;
1170
+ let n = e.length - e.replace(/^\s+/, "").length, i = Math.floor(n / 2), a = e.match(/^\s*-\s+([a-z]+)(?:\s+"([^"]*?)")?/);
1171
+ if (!a) continue;
1172
+ let o = a[1] ?? "", s = a[2] ?? "";
1173
+ for (; r.length > 0 && (r[r.length - 1]?.depth ?? -1) >= i;) r.pop();
1174
+ let c = r.map((e) => ({
1175
+ role: e.role,
1176
+ name: e.name
1177
+ })), l = e.match(/\[ref=(e\d+)\]/);
1178
+ l && t.push({
1179
+ role: o,
1180
+ name: s,
1181
+ ref: l[1] ?? "",
1182
+ depth: i,
1183
+ ancestors: c
1184
+ }), /:\s*$/.test(e) && r.push({
1185
+ depth: i,
1186
+ role: o,
1187
+ name: s
1188
+ });
1189
+ }
1190
+ return t;
1191
+ }
1192
+ async function Ft(e, t, n) {
1193
+ let r = Rt(t.filter((t) => t.role === e.role && t.name === e.name), e.under);
1194
+ if (r.length === 0) return zt(e, t);
1195
+ if (r.length > 1) return {
1196
+ ok: !1,
1197
+ kind: "ambiguous",
1198
+ reason: `Multiple elements match role="${e.role}" name="${e.name}". Use \`under\` to disambiguate.`,
1199
+ candidates: r.map((e) => `[ref=${e.ref}]`)
1200
+ };
1201
+ let i = r[0], a = Lt(n, e), o = await a.count();
1202
+ return o === 0 ? {
1203
+ ok: !1,
1204
+ kind: "live-mismatch",
1205
+ reason: `Snapshot says element exists but live locator failed: role="${e.role}" name="${e.name}".`
1206
+ } : o > 1 ? {
1207
+ ok: !1,
1208
+ kind: "ambiguous",
1209
+ reason: `Live locator returns ${o} elements for role="${e.role}" name="${e.name}".`
1210
+ } : {
1211
+ ok: !0,
1212
+ locator: a,
1213
+ entry: i
1214
+ };
1215
+ }
1216
+ async function It(e, t, n) {
1217
+ let r = [];
1218
+ for (let i of e) r.push({
1219
+ overlay: i,
1220
+ result: await Ft(i.match, t, n)
1221
+ });
1222
+ return r;
1223
+ }
1224
+ function Lt(e, t) {
1225
+ let n = e.getByRole(t.role, {
1226
+ name: t.name,
1227
+ exact: !0
1228
+ });
1229
+ return t.under && (n = e.getByRole(t.under.role, {
1230
+ name: t.under.name,
1231
+ exact: !0
1232
+ }).getByRole(t.role, {
1233
+ name: t.name,
1234
+ exact: !0
1235
+ })), n;
1236
+ }
1237
+ function Rt(e, t) {
1238
+ return t ? e.filter((e) => e.ancestors.some((e) => e.role === t.role && e.name === t.name)) : e;
1239
+ }
1240
+ function zt(e, t) {
1241
+ let n = t.filter((t) => t.name === e.name);
1242
+ if (n.length === 1 && n[0].role !== e.role) return {
1243
+ ok: !1,
1244
+ kind: "role-changed",
1245
+ reason: `Element with name="${e.name}" exists but has role="${n[0].role}", not "${e.role}".`,
1246
+ suggestion: n[0].role
1247
+ };
1248
+ let r = t.filter((t) => t.role === e.role);
1249
+ if (r.length > 0) {
1250
+ let t = Bt(e.name, r.map((e) => e.name));
1251
+ if (t) return {
1252
+ ok: !1,
1253
+ kind: "renamed",
1254
+ reason: `No element with role="${e.role}" name="${e.name}". Closest match: name="${t}".`,
1255
+ suggestion: t
1256
+ };
1257
+ }
1258
+ return {
1259
+ ok: !1,
1260
+ kind: "not-found",
1261
+ reason: `No element with role="${e.role}" name="${e.name}".`
1262
+ };
1263
+ }
1264
+ function Bt(e, t) {
1265
+ if (t.length === 0) return null;
1266
+ let n = t[0], r = Vt(e, n);
1267
+ for (let i of t.slice(1)) {
1268
+ let t = Vt(e, i);
1269
+ t > r && (n = i, r = t);
1270
+ }
1271
+ return r > .3 ? n : null;
1272
+ }
1273
+ function Vt(e, t) {
1274
+ if (!e || !t) return 0;
1275
+ let n = (e) => {
1276
+ let t = /* @__PURE__ */ new Set();
1277
+ for (let n = 0; n < e.length - 2; n++) t.add(e.slice(n, n + 3));
1278
+ return t;
1279
+ }, r = n(e), i = n(t);
1280
+ if (r.size === 0 || i.size === 0) return 0;
1281
+ let a = 0;
1282
+ for (let e of r) i.has(e) && a++;
1283
+ return a / Math.max(r.size, i.size);
1284
+ }
1285
+ //#endregion
1286
+ //#region src/drift.ts
1287
+ function K(e) {
1288
+ let { screen: t, liveSnapshot: n, storedAttributesYaml: r, freshAttributesYaml: i, yamlOverlays: a, yamlAnnotations: o } = e, s = [], c = a ?? t.overlays, l = /* @__PURE__ */ new Set();
1289
+ for (let e of c) {
1290
+ let r = n.filter((t) => t.role === e.match.role && t.name === e.match.name);
1291
+ if (r.length > 1) {
1292
+ s.push({
1293
+ severity: "error",
1294
+ kind: "duplicated",
1295
+ screenId: t.id,
1296
+ message: `Multiple elements match role="${e.match.role}" name="${e.match.name}". Use \`under\` to disambiguate.`,
1297
+ match: e.match
1298
+ });
1299
+ continue;
1300
+ }
1301
+ if (r.length === 1) continue;
1302
+ let i = n.filter((t) => t.name === e.match.name);
1303
+ if (i.length === 1 && i[0].role !== e.match.role) {
1304
+ let n = i[0];
1305
+ l.add(`${n.role}|${n.name}`), s.push({
1306
+ severity: "warning",
1307
+ kind: "role-changed",
1308
+ screenId: t.id,
1309
+ message: `Element with name="${e.match.name}" exists but has role="${n.role}", not "${e.match.role}".`,
1310
+ match: e.match,
1311
+ suggestion: { role: n.role }
1312
+ });
1313
+ continue;
1314
+ }
1315
+ let a = n.filter((t) => t.role === e.match.role);
1316
+ if (a.length > 0) {
1317
+ let n = qt(e.match.name, a.map((e) => e.name));
1318
+ if (n) {
1319
+ l.add(`${e.match.role}|${n}`), s.push({
1320
+ severity: "warning",
1321
+ kind: "renamed",
1322
+ screenId: t.id,
1323
+ message: `No element with role="${e.match.role}" name="${e.match.name}". Closest match: name="${n}".`,
1324
+ match: e.match,
1325
+ suggestion: { name: n }
1326
+ });
1327
+ continue;
1328
+ }
1329
+ }
1330
+ s.push({
1331
+ severity: "error",
1332
+ kind: "removed",
1333
+ screenId: t.id,
1334
+ message: `No element with role="${e.match.role}" name="${e.match.name}".`,
1335
+ match: e.match
1336
+ });
1337
+ }
1338
+ let u = new Set(c.map((e) => `${e.match.role}|${e.match.name}`));
1339
+ for (let e of n) {
1340
+ if (!Kt(e.role)) continue;
1341
+ let n = `${e.role}|${e.name}`;
1342
+ u.has(n) || l.has(n) || s.push({
1343
+ severity: "warning",
1344
+ kind: "added",
1345
+ screenId: t.id,
1346
+ message: `New ${e.role} "${e.name}" on the page has no <Overlay>.`,
1347
+ suggestion: {
1348
+ role: e.role,
1349
+ name: e.name
1350
+ }
1351
+ });
1352
+ }
1353
+ if (o !== void 0) for (let e of o) for (let r of Zt(e)) {
1354
+ let i = n.filter((e) => e.role === r.role && e.name === r.name);
1355
+ if (i.length > 1) {
1356
+ s.push({
1357
+ severity: "error",
1358
+ kind: "duplicated",
1359
+ screenId: t.id,
1360
+ message: `Annotation ${e.id}: multiple elements match role="${r.role}" name="${r.name}". Use \`under\` to disambiguate.`,
1361
+ match: r
1362
+ });
1363
+ continue;
1364
+ }
1365
+ if (i.length === 1) continue;
1366
+ let a = n.filter((e) => e.name === r.name);
1367
+ if (a.length === 1 && a[0].role !== r.role) {
1368
+ let n = a[0];
1369
+ l.add(`${n.role}|${n.name}`), s.push({
1370
+ severity: "warning",
1371
+ kind: "role-changed",
1372
+ screenId: t.id,
1373
+ message: `Annotation ${e.id}: element with name="${r.name}" exists but has role="${n.role}", not "${r.role}".`,
1374
+ match: r,
1375
+ suggestion: { role: n.role }
1376
+ });
1377
+ continue;
1378
+ }
1379
+ let o = n.filter((e) => e.role === r.role);
1380
+ if (o.length > 0) {
1381
+ let n = qt(r.name, o.map((e) => e.name));
1382
+ if (n) {
1383
+ l.add(`${r.role}|${n}`), s.push({
1384
+ severity: "warning",
1385
+ kind: "renamed",
1386
+ screenId: t.id,
1387
+ message: `Annotation ${e.id}: no element with role="${r.role}" name="${r.name}". Closest match: name="${n}".`,
1388
+ match: r,
1389
+ suggestion: { name: n }
1390
+ });
1391
+ continue;
1392
+ }
1393
+ }
1394
+ s.push({
1395
+ severity: "error",
1396
+ kind: "removed",
1397
+ screenId: t.id,
1398
+ message: `Annotation ${e.id}: no element with role="${r.role}" name="${r.name}".`,
1399
+ match: r
1400
+ });
1401
+ }
1402
+ if (a !== void 0) {
1403
+ let e = new Set(a.map((e) => e.id)), n = new Set(t.callouts.map((e) => e.for));
1404
+ for (let e of a) n.has(e.id) || s.push({
1405
+ severity: "warning",
1406
+ kind: "description-missing",
1407
+ screenId: t.id,
1408
+ message: `annotations yaml has \`overlays[].id="${e.id}"\` but no <AnnotCallout for="${e.id}"> in MDX.`,
1409
+ match: e.match
1410
+ });
1411
+ for (let n of t.callouts) e.has(n.for) || s.push({
1412
+ severity: "error",
1413
+ kind: "description-orphan",
1414
+ screenId: t.id,
1415
+ message: `<AnnotCallout for="${n.for}"> has no matching entry in annotations yaml \`overlays[]\`.`
1416
+ });
1417
+ }
1418
+ return r !== void 0 && i !== void 0 && Yt(r) !== Yt(i) && s.push({
1419
+ severity: "info",
1420
+ kind: "attribute-drift",
1421
+ screenId: t.id,
1422
+ message: "`annot:attributes` block no longer matches live element attributes. Run `annot docs sync` to update."
1423
+ }), s;
1424
+ }
1425
+ function Ht(e) {
1426
+ return K({
1427
+ screen: e.screen,
1428
+ liveSnapshot: G(e.liveSnapshotYaml),
1429
+ storedAttributesYaml: e.storedAttributesYaml,
1430
+ freshAttributesYaml: e.freshAttributesYaml,
1431
+ yamlOverlays: e.yamlOverlays,
1432
+ yamlAnnotations: e.yamlAnnotations
1433
+ });
1434
+ }
1435
+ function Ut(e) {
1436
+ let t = [];
1437
+ return ae(e, (e, n) => {
1438
+ !e.role || !e.name || t.push({
1439
+ role: e.role,
1440
+ name: e.name,
1441
+ ref: e.ref,
1442
+ depth: n.length,
1443
+ ancestors: n.filter((e) => typeof e.name == "string").map((e) => ({
1444
+ role: e.role,
1445
+ name: e.name
1446
+ }))
1447
+ });
1448
+ }), t;
1449
+ }
1450
+ function Wt(e) {
1451
+ return K({
1452
+ screen: e.screen,
1453
+ liveSnapshot: Ut(e.liveElementTree),
1454
+ storedAttributesYaml: e.storedAttributesYaml,
1455
+ freshAttributesYaml: e.freshAttributesYaml,
1456
+ yamlOverlays: e.yamlOverlays,
1457
+ yamlAnnotations: e.yamlAnnotations
1458
+ });
1459
+ }
1460
+ function q(e) {
1461
+ let t = 0, n = 0, r = 0;
1462
+ for (let i of e) i.severity === "error" ? t++ : i.severity === "warning" ? n++ : r++;
1463
+ return {
1464
+ errors: t,
1465
+ warnings: n,
1466
+ infos: r
1467
+ };
1468
+ }
1469
+ var Gt = /* @__PURE__ */ new Set([
1470
+ "button",
1471
+ "checkbox",
1472
+ "combobox",
1473
+ "link",
1474
+ "menuitem",
1475
+ "menuitemcheckbox",
1476
+ "menuitemradio",
1477
+ "option",
1478
+ "radio",
1479
+ "searchbox",
1480
+ "slider",
1481
+ "spinbutton",
1482
+ "switch",
1483
+ "tab",
1484
+ "textbox",
1485
+ "treeitem"
1486
+ ]);
1487
+ function Kt(e) {
1488
+ return Gt.has(e);
1489
+ }
1490
+ function qt(e, t) {
1491
+ if (t.length === 0) return null;
1492
+ let n = t[0], r = Jt(e, n);
1493
+ for (let i of t.slice(1)) {
1494
+ let t = Jt(e, i);
1495
+ t > r && (n = i, r = t);
1496
+ }
1497
+ return r > .3 ? n : null;
1498
+ }
1499
+ function Jt(e, t) {
1500
+ if (!e || !t) return 0;
1501
+ let n = (e) => {
1502
+ let t = /* @__PURE__ */ new Set();
1503
+ for (let n = 0; n < e.length - 2; n++) t.add(e.slice(n, n + 3));
1504
+ return t;
1505
+ }, r = n(e), i = n(t);
1506
+ if (r.size === 0 || i.size === 0) return 0;
1507
+ let a = 0;
1508
+ for (let e of r) i.has(e) && a++;
1509
+ return a / Math.max(r.size, i.size);
1510
+ }
1511
+ function Yt(e) {
1512
+ return e.split(/\r?\n/).map((e) => e.trimEnd()).filter((e) => e.length > 0).join("\n");
1513
+ }
1514
+ function Xt(e) {
1515
+ return e.overlays.length > 0 || e.annotations !== void 0 || e.callouts.length > 0;
1516
+ }
1517
+ function J(e) {
1518
+ return e.filter(Xt);
1519
+ }
1520
+ function Zt(e) {
1521
+ switch (e.kind) {
1522
+ case "rect": return e.match ? [e.match] : e.coversElements ? [...e.coversElements] : [];
1523
+ case "circle": return e.match ? [e.match] : [];
1524
+ case "arrow": {
1525
+ let t = [];
1526
+ return "match" in e.from && t.push(e.from.match), "match" in e.to && t.push(e.to.match), t;
1527
+ }
1528
+ case "text": return e.anchor ? [e.anchor.match] : [];
1529
+ case "callout": return "match" in e.target ? [e.target.match] : [];
1530
+ case "freehand": return [];
1531
+ case "redact": return e.match ? [e.match] : [];
1532
+ case "focusMask": return "match" in e.cutout ? [e.cutout.match] : [];
1533
+ }
1534
+ }
1535
+ //#endregion
1536
+ //#region src/migrate-overlays-to-annotations.ts
1537
+ async function Qt(e, t = {}) {
1538
+ let n = await y(e);
1539
+ if (!n) return {
1540
+ mdxPath: e,
1541
+ screens: [],
1542
+ mdxRewritten: !1
1543
+ };
1544
+ let i = [], a = [];
1545
+ for (let s of n.screens) {
1546
+ if (s.annotations !== void 0) {
1547
+ a.push({
1548
+ id: s.id,
1549
+ overlayCount: 0,
1550
+ yamlWritten: !1,
1551
+ skipReason: "already-migrated"
1552
+ });
1553
+ continue;
1554
+ }
1555
+ if (s.overlays.length === 0) {
1556
+ a.push({
1557
+ id: s.id,
1558
+ overlayCount: 0,
1559
+ yamlWritten: !1,
1560
+ skipReason: "no-overlays"
1561
+ });
1562
+ continue;
1563
+ }
1564
+ if (!s.src) {
1565
+ a.push({
1566
+ id: s.id,
1567
+ overlayCount: s.overlays.length,
1568
+ yamlWritten: !1,
1569
+ skipReason: "no-src"
1570
+ });
1571
+ continue;
1572
+ }
1573
+ let c = tn({
1574
+ mdxPath: e,
1575
+ screenId: s.id,
1576
+ src: s.src,
1577
+ override: t.yamlPathFor
1578
+ }), l = nn(m(e), c), u = an(n.source, s), d = rn(n.source, s, l);
1579
+ if (!d || u.length !== s.overlays.length) throw Error(`migrateOverlaysToAnnotationsFile: ${e} screen "${s.id}" position data incomplete — cannot rewrite safely.`);
1580
+ i.push(d, ...u.map((e) => e.edit));
1581
+ let f = $t(s.overlays);
1582
+ a.push({
1583
+ id: s.id,
1584
+ yamlPath: c,
1585
+ yamlWritten: !1,
1586
+ overlayCount: s.overlays.length
1587
+ }), t.dryRun || (await r(m(c), { recursive: !0 }), await o(c, pt(f), "utf8"), a[a.length - 1].yamlWritten = !0);
1588
+ }
1589
+ let s = !1;
1590
+ if (i.length > 0 && !t.dryRun) {
1591
+ let t = en(n.source, i);
1592
+ t !== n.source && (await o(e, t, "utf8"), s = !0);
1593
+ }
1594
+ return {
1595
+ mdxPath: e,
1596
+ screens: a,
1597
+ mdxRewritten: s
1598
+ };
1599
+ }
1600
+ function $t(e) {
1601
+ return {
1602
+ version: 1,
1603
+ overlays: e.map((e, t) => {
1604
+ let n = {
1605
+ id: `o${t + 1}`,
1606
+ kind: "numberedBadge",
1607
+ match: e.match,
1608
+ number: e.number ?? t + 1
1609
+ };
1610
+ return e.intent && (n.intent = e.intent), n;
1611
+ }),
1612
+ meta: { generator: "annot-docs migrate-overlays-to-annotations@1" }
1613
+ };
1614
+ }
1615
+ function en(e, t) {
1616
+ let n = [...t].sort((e, t) => t.start - e.start), r = e;
1617
+ for (let e of n) r = r.slice(0, e.start) + e.replacement + r.slice(e.end);
1618
+ return r;
1619
+ }
1620
+ function tn(e) {
1621
+ if (e.override) return e.override({
1622
+ mdxPath: e.mdxPath,
1623
+ screenId: e.screenId,
1624
+ src: e.src
1625
+ });
1626
+ let t = h(e.src) ? e.src : _(m(e.mdxPath), e.src), n = ne(t).replace(/\.(png|jpe?g|webp|gif|svg)$/i, "");
1627
+ return _(m(t), `${n}.annotations.yaml`);
1628
+ }
1629
+ function nn(e, t) {
1630
+ let n = g(e, t).split(/[\\/]/).join("/");
1631
+ return n.startsWith(".") || n.startsWith("/") ? n : `./${n}`;
1632
+ }
1633
+ function rn(e, t, n) {
1634
+ let r = new RegExp(String.raw`<Screen\b[^>]*\bid=(?:"${Y(t.id)}"|'${Y(t.id)}'|\{["']${Y(t.id)}["']\})[^>]*?(/?)>`, "m").exec(e);
1635
+ if (!r || r.index === void 0) return null;
1636
+ let i = r.index, a = i + r[0].length, o = r[1] === "/" ? a - 2 : a - 1, s = e.slice(i, o);
1637
+ return {
1638
+ start: o,
1639
+ end: o,
1640
+ replacement: `${/\s$/.test(s) ? "" : " "}annotations="${n}"`
1641
+ };
1642
+ }
1643
+ function an(e, t) {
1644
+ let n = new RegExp(String.raw`<Screen\b[^>]*\bid=(?:"${Y(t.id)}"|'${Y(t.id)}'|\{["']${Y(t.id)}["']\})[^>]*?>`, "m").exec(e);
1645
+ if (!n || n.index === void 0) return [];
1646
+ let r = n.index + n[0].length, i = e.indexOf("</Screen>", r);
1647
+ if (i === -1) return [];
1648
+ let a = i, o = [], s = r;
1649
+ for (let n = 0; n < t.overlays.length; n++) {
1650
+ let r = t.overlays[n], i = e.indexOf("<Overlay", s);
1651
+ if (i === -1 || i >= a) return o;
1652
+ let c = e.indexOf("</Overlay>", i);
1653
+ if (c === -1 || c >= a) return o;
1654
+ let l = c + 10, u = e.indexOf(">", i);
1655
+ if (u === -1 || u >= c) return o;
1656
+ let d = e.slice(u + 1, c), f = `<AnnotCallout for="${`o${n + 1}`}">${d}</AnnotCallout>`;
1657
+ o.push({
1658
+ overlay: r,
1659
+ edit: {
1660
+ start: i,
1661
+ end: l,
1662
+ replacement: f
1663
+ }
1664
+ }), s = l;
1665
+ }
1666
+ return o;
1667
+ }
1668
+ function Y(e) {
1669
+ return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1670
+ }
1671
+ //#endregion
1672
+ //#region src/migrate-to-element-tree.ts
1673
+ var on = [
1674
+ "type",
1675
+ "required",
1676
+ "placeholder",
1677
+ "name",
1678
+ "id",
1679
+ "href",
1680
+ "aria-checked",
1681
+ "aria-expanded",
1682
+ "aria-selected",
1683
+ "aria-disabled",
1684
+ "aria-invalid"
1685
+ ];
1686
+ async function sn(e, t = {}) {
1687
+ let n = await y(e);
1688
+ if (!n) return {
1689
+ mdxPath: e,
1690
+ screens: [],
1691
+ mdxRewritten: !1
1692
+ };
1693
+ let r = n.commentBlocks.snapshot?.trim() ?? "", a = n.commentBlocks.attributes?.trim() ?? "", s = [];
1694
+ for (let c of n.screens) {
1695
+ if (!r) {
1696
+ s.push({
1697
+ id: c.id,
1698
+ xmpWritten: !1,
1699
+ skipReason: "no-snapshot"
1700
+ });
1701
+ continue;
1702
+ }
1703
+ if (!c.src) {
1704
+ s.push({
1705
+ id: c.id,
1706
+ xmpWritten: !1,
1707
+ skipReason: "no-src"
1708
+ });
1709
+ continue;
1710
+ }
1711
+ let n = un(e, c.src);
1712
+ if (!n) {
1713
+ s.push({
1714
+ id: c.id,
1715
+ xmpWritten: !1,
1716
+ skipReason: "no-src"
1717
+ });
1718
+ continue;
1719
+ }
1720
+ let l = cn({
1721
+ snapshotYaml: r,
1722
+ attributesYaml: a,
1723
+ whitelist: t.attributeWhitelist ?? on
1724
+ });
1725
+ if (t.dryRun) {
1726
+ s.push({
1727
+ id: c.id,
1728
+ pngPath: n,
1729
+ xmpWritten: !0
1730
+ });
1731
+ continue;
1732
+ }
1733
+ await o(n, oe(await i(n), l)), s.push({
1734
+ id: c.id,
1735
+ pngPath: n,
1736
+ xmpWritten: !0
1737
+ });
1738
+ }
1739
+ let c = !!(n.commentBlocks.snapshot || n.commentBlocks.attributes), l = !1;
1740
+ if (c && !t.dryRun) {
1741
+ let t = dn(n.source);
1742
+ t !== n.source && (await o(e, t, "utf8"), l = !0);
1743
+ }
1744
+ return {
1745
+ mdxPath: e,
1746
+ screens: s,
1747
+ mdxRewritten: l
1748
+ };
1749
+ }
1750
+ function cn(e) {
1751
+ let n = t({
1752
+ yaml: e.snapshotYaml,
1753
+ viewport: {
1754
+ width: 0,
1755
+ height: 0,
1756
+ scale: 1
1757
+ },
1758
+ agent: "annot-docs-migrate-to-element-tree@1",
1759
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
1760
+ }), r = ln(e.attributesYaml, e.whitelist);
1761
+ for (let e of r) {
1762
+ let t = ie(n, {
1763
+ role: e.role,
1764
+ name: e.name
1765
+ });
1766
+ if (t.length !== 1) continue;
1767
+ let r = t[0];
1768
+ Object.keys(e.attributes).length !== 0 && (r.attributes = {
1769
+ ...r.attributes ?? {},
1770
+ ...e.attributes
1771
+ });
1772
+ }
1773
+ return n;
1774
+ }
1775
+ function ln(e, t) {
1776
+ let n = [];
1777
+ if (!e.trim()) return n;
1778
+ let r = new Set(t), i = e.split(/\r?\n/), a = null, o = /^([a-z]+)\s+"([^"]*)"\s*:\s*$/;
1779
+ for (let e of i) {
1780
+ let t = e.trim();
1781
+ if (!t) continue;
1782
+ let i = t.match(o);
1783
+ if (i) {
1784
+ a = {
1785
+ role: i[1] ?? "",
1786
+ name: i[2] ?? "",
1787
+ attributes: {}
1788
+ }, n.push(a);
1789
+ continue;
1790
+ }
1791
+ if (!a) continue;
1792
+ let s = t.indexOf(":");
1793
+ if (s <= 0) continue;
1794
+ let c = t.slice(0, s).trim(), l = t.slice(s + 1).trim();
1795
+ r.has(c) && ((l.startsWith("\"") && l.endsWith("\"") || l.startsWith("'") && l.endsWith("'")) && (l = l.slice(1, -1)), a.attributes[c] = l);
1796
+ }
1797
+ return n;
1798
+ }
1799
+ function un(e, t) {
1800
+ return /^[a-z][a-z0-9+.-]*:/i.test(t) ? null : h(t) ? t : _(m(e), t);
1801
+ }
1802
+ function dn(e) {
1803
+ let t = e;
1804
+ return t = fn(t, "annot:snapshot"), t = fn(t, "annot:attributes"), t;
1805
+ }
1806
+ function fn(e, t) {
1807
+ let n = `{/* ${t}`, r = e.indexOf(n);
1808
+ if (r === -1) return e;
1809
+ let i = e.indexOf("*/}", r);
1810
+ if (i === -1) return e;
1811
+ let a = i + 3, o = e[a] === "\n" ? a + 1 : a;
1812
+ return e.slice(0, r) + e.slice(o);
1813
+ }
1814
+ //#endregion
1815
+ //#region src/cli.ts
1816
+ async function pn(e, t = {}) {
1817
+ let n = t.stdout ?? ((e) => process.stdout.write(`${e}\n`)), r = t.stderr ?? ((e) => process.stderr.write(`${e}\n`)), i = t.cwd ?? process.cwd(), [, , a, ...o] = e;
1818
+ if (a !== "init" && a !== "sync" && a !== "lint" && a !== "migrate-to-element-tree" && a !== "migrate-overlays-to-annotations") return r(mn), +!!a;
1819
+ try {
1820
+ switch (a) {
1821
+ case "init": return await hn(o, {
1822
+ cwd: i,
1823
+ stdout: n,
1824
+ stderr: r
1825
+ });
1826
+ case "sync": return await yn(o, {
1827
+ ...t,
1828
+ cwd: i,
1829
+ stdout: n,
1830
+ stderr: r
1831
+ });
1832
+ case "lint": return await bn(o, {
1833
+ ...t,
1834
+ cwd: i,
1835
+ stdout: n,
1836
+ stderr: r
1837
+ });
1838
+ case "migrate-to-element-tree": return await Sn(o, {
1839
+ cwd: i,
1840
+ stdout: n,
1841
+ stderr: r
1842
+ });
1843
+ case "migrate-overlays-to-annotations": return await Cn(o, {
1844
+ cwd: i,
1845
+ stdout: n,
1846
+ stderr: r
1847
+ });
1848
+ }
1849
+ } catch (e) {
1850
+ return r(`annot docs ${a}: ${e.message}`), 1;
1851
+ }
632
1852
  }
633
- var we = [
1853
+ var mn = [
634
1854
  "annot docs <command> [options]",
635
1855
  "",
636
1856
  "Commands:",
637
1857
  " init Scaffold annot-docs.config.ts + sample files",
638
1858
  " sync --url <baseUrl> Re-capture snapshot + attrs into every annot MDX",
639
1859
  " lint --url <baseUrl> Report drift between annot MDXs and the live page",
1860
+ " migrate-to-element-tree Convert legacy annot:snapshot / annot:attributes",
1861
+ " MDX blocks into PNG XMP annot:elementTree chunks",
1862
+ " (one-time, per Phase 1g of",
1863
+ " docs/plans/living-spec-authoring-roadmap.md)",
1864
+ " migrate-overlays-to-annotations",
1865
+ " Extract inline <Overlay> JSX into",
1866
+ " .annotations.yaml files and rewrite the MDX to use",
1867
+ " <Screen annotations> + <AnnotCallout for> (one-time,",
1868
+ " per Phase 2d of",
1869
+ " docs/plans/living-spec-authoring-roadmap.md)",
640
1870
  "",
641
1871
  "Options:",
642
1872
  " --url <baseUrl> Base URL Playwright navigates to (sync / lint)",
643
1873
  " --root <dir> Override MDX search root (default: docs/)",
1874
+ " --dry-run migrate-to-element-tree only — report what would",
1875
+ " be changed without writing back",
1876
+ " --check-descriptions lint only — also validate <AnnotCallout for> IDs",
1877
+ " against the screen's annotations yaml overlays[]",
1878
+ " (Phase 2c of living-spec-authoring-roadmap.md)",
644
1879
  " --help Show this help"
645
1880
  ].join("\n");
646
- async function Te(t, n) {
647
- let { cwd: a, stdout: o } = n, c = [
1881
+ async function hn(e, t) {
1882
+ let { cwd: n, stdout: i } = t, a = [
648
1883
  {
649
1884
  path: "annot-docs.config.ts",
650
- content: Ee
1885
+ content: gn
651
1886
  },
652
1887
  {
653
1888
  path: "tests/docs/example.spec.ts",
654
- content: De
1889
+ content: _n
655
1890
  },
656
1891
  {
657
1892
  path: "docs/books/example/SC-001-login.mdx",
658
- content: Oe
1893
+ content: vn
659
1894
  }
660
- ], l = 0;
661
- for (let t of c) {
662
- let n = s(a, t.path);
1895
+ ], s = 0;
1896
+ for (let e of a) {
1897
+ let t = _(n, e.path);
663
1898
  try {
664
- await e(i(n), { recursive: !0 }), await r(n, t.content, { flag: "wx" }), o(` created ${t.path}`), l++;
665
- } catch (e) {
666
- if (e.code === "EEXIST") o(` exists ${t.path} (skipped)`);
667
- else throw e;
1899
+ await r(m(t), { recursive: !0 }), await o(t, e.content, { flag: "wx" }), i(` created ${e.path}`), s++;
1900
+ } catch (t) {
1901
+ if (t.code === "EEXIST") i(` exists ${e.path} (skipped)`);
1902
+ else throw t;
668
1903
  }
669
1904
  }
670
- return o(l === 0 ? "annot docs init: nothing to do (all targets exist)" : `annot docs init: wrote ${l} file(s)`), 0;
1905
+ return i(s === 0 ? "annot docs init: nothing to do (all targets exist)" : `annot docs init: wrote ${s} file(s)`), 0;
671
1906
  }
672
- var Ee = "// Living product docs config for annot.\n// See https://github.com/ingcreators/annot/blob/main/docs/plans/living-product-docs.md\nimport { defineConfig } from \"@ingcreators/annot-product-docs\";\n\nexport default defineConfig({\n meta: {\n projectName: \"Example\",\n },\n xlsx: {\n defaultBook: \"Screen spec\",\n books: {\n \"Screen spec\": {\n // Drop your customer-supplied template here (Phase 3) and\n // the Excel adapter will fill it. Until then the OSS\n // default layout applies.\n // template: \"./templates/customer-screen-spec.xlsx\",\n },\n },\n },\n});\n", De = "// Tour file — runs through every screen and refreshes the\n// matching MDX's annot:snapshot / annot:attributes blocks.\n//\n// Run with: pnpm playwright test tests/docs/\n\nimport { test } from \"@ingcreators/annot-product-docs\";\n\ntest.describe.configure({ mode: \"serial\" });\n\ntest(\"login flow\", async ({ page, screen }) => {\n await page.goto(\"/login\");\n await screen.capture({\n id: \"login\",\n mdxPath: \"docs/books/example/SC-001-login.mdx\",\n });\n});\n", Oe = "---\nannot:\n id: SC-001\n title: Login screen\n meta:\n author: TODO\n xlsx:\n book: Screen spec\n sheet: SC-001 Login\n role: screen\n order: 100\n---\n\nimport { Screen, Overlay } from \"@ingcreators/annot-product-docs-astro\";\n\n# Login screen\n\nEnter your credentials to access the system.\n\n<Screen id=\"login\" src=\"./shots/login.png\">\n\n<Overlay match={{ role: \"textbox\", name: \"Email\" }} intent=\"required\" number={1}>\n**Email** — Enter your registered email address.\n</Overlay>\n\n<Overlay match={{ role: \"button\", name: \"Sign in\" }} intent=\"action\" number={2}>\nClick to sign in.\n</Overlay>\n\n</Screen>\n";
673
- async function ke(e, t) {
1907
+ var gn = "// Living product docs config for annot.\n// See https://github.com/ingcreators/annot/blob/main/docs/plans/living-product-docs.md\nimport { defineConfig } from \"@ingcreators/annot-product-docs\";\n\nexport default defineConfig({\n meta: {\n projectName: \"Example\",\n },\n xlsx: {\n defaultBook: \"Screen spec\",\n books: {\n \"Screen spec\": {\n // Drop your customer-supplied template here (Phase 3) and\n // the Excel adapter will fill it. Until then the OSS\n // default layout applies.\n // template: \"./templates/customer-screen-spec.xlsx\",\n },\n },\n },\n});\n", _n = "// Tour file — runs through every screen and refreshes the\n// matching MDX's annot:snapshot / annot:attributes blocks.\n//\n// Run with: pnpm playwright test tests/docs/\n\nimport { test } from \"@ingcreators/annot-product-docs\";\n\ntest.describe.configure({ mode: \"serial\" });\n\ntest(\"login flow\", async ({ page, productDocs }) => {\n await page.goto(\"/login\");\n await productDocs.sync({\n id: \"login\",\n mdxPath: \"docs/books/example/SC-001-login.mdx\",\n });\n});\n", vn = "---\nannot:\n id: SC-001\n title: Login screen\n meta:\n author: TODO\n xlsx:\n book: Screen spec\n sheet: SC-001 Login\n role: screen\n order: 100\n---\n\nimport { Screen, Overlay } from \"@ingcreators/annot-product-docs-astro\";\n\n# Login screen\n\nEnter your credentials to access the system.\n\n<Screen id=\"login\" src=\"./shots/login.png\">\n\n<Overlay match={{ role: \"textbox\", name: \"Email\" }} intent=\"required\" number={1}>\n**Email** — Enter your registered email address.\n</Overlay>\n\n<Overlay match={{ role: \"button\", name: \"Sign in\" }} intent=\"action\" number={2}>\nClick to sign in.\n</Overlay>\n\n</Screen>\n";
1908
+ async function yn(e, t) {
674
1909
  let { cwd: n, stdout: r, stderr: i } = t, a = Z(e);
675
1910
  if (!a.url) return i("annot docs sync: --url <baseUrl> is required."), 1;
676
- let c = await $(await Q(s(n, a.root ?? "docs")));
677
- if (c.length === 0) return r(`annot docs sync: no MDX files with \`annot:\` frontmatter under ${a.root ?? "docs"}.`), 0;
678
- r(`annot docs sync: ${c.length} MDX file(s) → ${a.url}`);
679
- let { newPage: l, close: u } = await Y(a.url, t);
1911
+ let o = await $(await Q(_(n, a.root ?? "docs")));
1912
+ if (o.length === 0) return r(`annot docs sync: no MDX files with \`annot:\` frontmatter under ${a.root ?? "docs"}.`), 0;
1913
+ r(`annot docs sync: ${o.length} MDX file(s) → ${a.url}`);
1914
+ let { newPage: s, close: c } = await X(a.url, t);
680
1915
  try {
681
- for (let e of c) {
682
- let t = await j(e);
1916
+ for (let e of o) {
1917
+ let t = await y(e);
683
1918
  if (!t) continue;
684
- let i = T(t.screens);
1919
+ let i = J(t.screens);
685
1920
  if (i.length === 0) {
686
- r(` skip ${o(n, e)} (no <Screen> blocks)`);
1921
+ r(` skip ${g(n, e)} (no <Screen> blocks)`);
687
1922
  continue;
688
1923
  }
689
1924
  for (let t of i) {
690
- let { page: i, dispose: a } = await l(t.src ?? "/");
1925
+ let { page: i, dispose: a } = await s(t.src ?? "/");
691
1926
  try {
692
- await q(i, {
1927
+ await D(i, {
693
1928
  id: t.id,
694
1929
  mdxPath: e
695
- }), r(` synced ${o(n, e)} (screen=${t.id})`);
1930
+ }), r(` synced ${g(n, e)} (screen=${t.id})`);
696
1931
  } finally {
697
1932
  await a();
698
1933
  }
699
1934
  }
700
1935
  }
701
1936
  } finally {
702
- await u();
1937
+ await c();
703
1938
  }
704
1939
  return 0;
705
1940
  }
706
- async function Ae(e, t) {
1941
+ async function bn(e, t) {
707
1942
  let { cwd: n, stdout: r, stderr: i } = t, a = Z(e);
708
1943
  if (!a.url) return i("annot docs lint: --url <baseUrl> is required."), 1;
709
- let c = await $(await Q(s(n, a.root ?? "docs")));
710
- if (c.length === 0) return a.json ? r(JSON.stringify({
1944
+ let o = await $(await Q(_(n, a.root ?? "docs")));
1945
+ if (o.length === 0) return a.json ? r(JSON.stringify({
711
1946
  findings: [],
712
1947
  summary: {
713
1948
  errors: 0,
@@ -715,34 +1950,41 @@ async function Ae(e, t) {
715
1950
  infos: 0
716
1951
  }
717
1952
  })) : r(`annot docs lint: no MDX files with \`annot:\` frontmatter under ${a.root ?? "docs"}.`), 0;
718
- a.json || r(`annot docs lint: ${c.length} MDX file(s) → ${a.url}`);
719
- let l = [], u = /* @__PURE__ */ new Set(), { newPage: d, close: f } = await Y(a.url, t);
1953
+ a.json || r(`annot docs lint: ${o.length} MDX file(s) → ${a.url}`);
1954
+ let s = [], c = /* @__PURE__ */ new Set(), { newPage: l, close: u } = await X(a.url, t);
720
1955
  try {
721
- for (let e of c) {
722
- let t = await j(e);
723
- if (t) for (let r of T(t.screens)) {
724
- let { page: t, dispose: i } = await d(r.src ?? "/");
1956
+ for (let e of o) {
1957
+ let t = await y(e);
1958
+ if (t) for (let r of J(t.screens)) {
1959
+ let { page: t, dispose: o } = await l(r.src ?? "/");
725
1960
  try {
726
- let i = b({
1961
+ let o = G(await t.locator("body").ariaSnapshot({ mode: "ai" })), l = a.checkDescriptions ? await xn(r, e, i) : void 0;
1962
+ r.overlays.length > 0 && r.annotations === void 0 && Mt({
1963
+ mdxPath: e,
1964
+ screenId: r.id,
1965
+ overlayCount: r.overlays.length
1966
+ }, i);
1967
+ let u = K({
727
1968
  screen: r,
728
- liveSnapshot: g(await t.locator("body").ariaSnapshot({ mode: "ai" }))
1969
+ liveSnapshot: o,
1970
+ yamlOverlays: l
729
1971
  });
730
- for (let t of i) l.push({
731
- file: o(n, e),
1972
+ for (let t of u) s.push({
1973
+ file: g(n, e),
732
1974
  finding: t
733
- }), u.add(e);
1975
+ }), c.add(e);
734
1976
  } finally {
735
- await i();
1977
+ await o();
736
1978
  }
737
1979
  }
738
1980
  }
739
1981
  } finally {
740
- await f();
1982
+ await u();
741
1983
  }
742
1984
  if (a.json) {
743
- let e = x(l.map((e) => e.finding));
1985
+ let e = q(s.map((e) => e.finding));
744
1986
  r(JSON.stringify({
745
- findings: l.map(({ file: e, finding: t }) => ({
1987
+ findings: s.map(({ file: e, finding: t }) => ({
746
1988
  file: e,
747
1989
  severity: t.severity,
748
1990
  kind: t.kind,
@@ -754,23 +1996,23 @@ async function Ae(e, t) {
754
1996
  summary: e
755
1997
  }));
756
1998
  } else {
757
- for (let { file: e, finding: t } of l) r(`${je(t.severity)} ${e} [${t.screenId}] ${t.kind}: ${t.message}`);
758
- let e = x(l.map((e) => e.finding));
1999
+ for (let { file: e, finding: t } of s) r(`${Tn(t.severity)} ${e} [${t.screenId}] ${t.kind}: ${t.message}`);
2000
+ let e = q(s.map((e) => e.finding));
759
2001
  r(`annot docs lint: ${e.errors} error(s), ${e.warnings} warning(s), ${e.infos} info(s).`);
760
2002
  }
761
- let p = 0;
762
- if (a.fix && u.size > 0) {
763
- let { newPage: e, close: n } = await Y(a.url, t);
2003
+ let d = 0;
2004
+ if (a.fix && c.size > 0) {
2005
+ let { newPage: e, close: n } = await X(a.url, t);
764
2006
  try {
765
- for (let t of u) {
766
- let n = await j(t);
767
- if (n) for (let r of T(n.screens)) {
2007
+ for (let t of c) {
2008
+ let n = await y(t);
2009
+ if (n) for (let r of J(n.screens)) {
768
2010
  let { page: n, dispose: i } = await e(r.src ?? "/");
769
2011
  try {
770
- await q(n, {
2012
+ await D(n, {
771
2013
  id: r.id,
772
2014
  mdxPath: t
773
- }), p++;
2015
+ }), d++;
774
2016
  } finally {
775
2017
  await i();
776
2018
  }
@@ -779,15 +2021,25 @@ async function Ae(e, t) {
779
2021
  } finally {
780
2022
  await n();
781
2023
  }
782
- a.json || r(`annot docs lint --fix: refreshed snapshot/attributes for ${p} screen(s).`);
2024
+ a.json || r(`annot docs lint --fix: refreshed snapshot/attributes for ${d} screen(s).`);
783
2025
  }
784
- let m = x(l.map((e) => e.finding));
785
- return a.ci ? +(m.errors + m.warnings > 0) : +(m.errors > 0);
2026
+ let f = q(s.map((e) => e.finding));
2027
+ return a.ci ? +(f.errors + f.warnings > 0) : +(f.errors > 0);
786
2028
  }
787
- async function Y(e, t) {
2029
+ async function xn(e, t, n) {
2030
+ if (!e.annotations) return;
2031
+ let r = h(e.annotations) ? e.annotations : _(m(t), e.annotations);
2032
+ try {
2033
+ return ft(await i(r, "utf8")).overlays;
2034
+ } catch (e) {
2035
+ n(`annot docs lint --check-descriptions: failed to load ${r} (${e.message}). Skipping description cross-refs for this screen.`);
2036
+ return;
2037
+ }
2038
+ }
2039
+ async function X(e, t) {
788
2040
  if (t.newPage) return {
789
2041
  async newPage(n) {
790
- let { page: r, close: i } = await t.newPage(X(e, n));
2042
+ let { page: r, close: i } = await t.newPage(wn(e, n));
791
2043
  return {
792
2044
  page: r,
793
2045
  dispose: i
@@ -799,7 +2051,7 @@ async function Y(e, t) {
799
2051
  return {
800
2052
  async newPage(t) {
801
2053
  let n = await r.newContext(), i = await n.newPage();
802
- return await i.goto(X(e, t)), {
2054
+ return await i.goto(wn(e, t)), {
803
2055
  page: i,
804
2056
  dispose: async () => {
805
2057
  await n.close();
@@ -811,33 +2063,61 @@ async function Y(e, t) {
811
2063
  }
812
2064
  };
813
2065
  }
814
- function X(e, t) {
2066
+ async function Sn(e, t) {
2067
+ let { cwd: n, stdout: r, stderr: i } = t, a = Z(e), o = await $(await Q(_(n, a.root ?? "docs")));
2068
+ if (o.length === 0) return r(`annot docs migrate-to-element-tree: no MDX files with \`annot:\` frontmatter under ${a.root ?? "docs"}.`), 0;
2069
+ r(`annot docs migrate-to-element-tree: ${o.length} MDX file(s)${a.dryRun ? " (dry run)" : ""}`);
2070
+ let s = 0, c = 0, l = 0;
2071
+ for (let e of o) try {
2072
+ let t = await sn(e, { dryRun: a.dryRun });
2073
+ for (let i of t.screens) i.xmpWritten ? (s++, r(` xmp ${g(n, e)} (screen=${i.id})`)) : i.skipReason && (l++, r(` skip ${g(n, e)} (screen=${i.id}, ${i.skipReason})`));
2074
+ t.mdxRewritten && (c++, r(` rewrote ${g(n, e)} (legacy blocks stripped)`));
2075
+ } catch (t) {
2076
+ return i(`annot docs migrate-to-element-tree: ${g(n, e)}: ${t.message}`), 1;
2077
+ }
2078
+ return r(`annot docs migrate-to-element-tree: ${s} XMP write(s), ${c} MDX rewrite(s), ${l} skip(s).`), 0;
2079
+ }
2080
+ async function Cn(e, t) {
2081
+ let { cwd: n, stdout: r, stderr: i } = t, a = Z(e), o = await $(await Q(_(n, a.root ?? "docs")));
2082
+ if (o.length === 0) return r(`annot docs migrate-overlays-to-annotations: no MDX files with \`annot:\` frontmatter under ${a.root ?? "docs"}.`), 0;
2083
+ r(`annot docs migrate-overlays-to-annotations: ${o.length} MDX file(s)${a.dryRun ? " (dry run)" : ""}`);
2084
+ let s = 0, c = 0, l = 0;
2085
+ for (let e of o) try {
2086
+ let t = await Qt(e, { dryRun: a.dryRun });
2087
+ for (let i of t.screens) i.yamlPath && !i.skipReason ? (s++, r(` yaml ${g(n, e)} (screen=${i.id}, overlays=${i.overlayCount}) → ${g(n, i.yamlPath)}`)) : i.skipReason && (l++, r(` skip ${g(n, e)} (screen=${i.id}, ${i.skipReason})`));
2088
+ t.mdxRewritten && (c++, r(` rewrote ${g(n, e)} (Overlay → AnnotCallout)`));
2089
+ } catch (t) {
2090
+ return i(`annot docs migrate-overlays-to-annotations: ${g(n, e)}: ${t.message}`), 1;
2091
+ }
2092
+ return r(`annot docs migrate-overlays-to-annotations: ${s} yaml write(s), ${c} MDX rewrite(s), ${l} skip(s).`), 0;
2093
+ }
2094
+ function wn(e, t) {
815
2095
  return /^https?:\/\//.test(t) ? t : t.startsWith("/") ? e.replace(/\/$/, "") + t : e;
816
2096
  }
817
2097
  function Z(e) {
818
2098
  let t = {};
819
2099
  for (let n = 0; n < e.length; n++) {
820
2100
  let r = e[n];
821
- r === "--url" || r === "-u" ? t.url = e[++n] : r === "--root" || r === "-r" ? t.root = e[++n] : r === "--json" ? t.json = !0 : r === "--ci" ? t.ci = !0 : r === "--fix" && (t.fix = !0);
2101
+ r === "--url" || r === "-u" ? t.url = e[++n] : r === "--root" || r === "-r" ? t.root = e[++n] : r === "--json" ? t.json = !0 : r === "--ci" ? t.ci = !0 : r === "--fix" ? t.fix = !0 : r === "--dry-run" ? t.dryRun = !0 : r === "--check-descriptions" && (t.checkDescriptions = !0);
822
2102
  }
823
2103
  return t;
824
2104
  }
825
2105
  async function Q(e) {
826
- let t = [], r = [e];
827
- for (; r.length > 0;) {
828
- let e = r.pop(), i;
2106
+ let t = [], n = [e];
2107
+ for (; n.length > 0;) {
2108
+ let e = n.pop(), r;
829
2109
  try {
830
- i = await n(e, { withFileTypes: !0 });
2110
+ r = await a(e, { withFileTypes: !0 });
831
2111
  } catch (e) {
832
2112
  if (e.code === "ENOENT") continue;
833
2113
  throw e;
834
2114
  }
835
- for (let n of i) {
836
- let i = a(e, n.name);
837
- if (n.isDirectory()) {
838
- if (n.name === "node_modules" || n.name.startsWith(".")) continue;
839
- r.push(i);
840
- } else n.isFile() && n.name.endsWith(".mdx") && t.push(i);
2115
+ for (let i of r) {
2116
+ let r = re(e, i.name);
2117
+ if (i.isDirectory()) {
2118
+ if (i.name === "node_modules" || i.name.startsWith(".")) continue;
2119
+ n.push(r);
2120
+ } else i.isFile() && i.name.endsWith(".mdx") && t.push(r);
841
2121
  }
842
2122
  }
843
2123
  return t.sort();
@@ -845,11 +2125,11 @@ async function Q(e) {
845
2125
  async function $(e) {
846
2126
  let t = [];
847
2127
  for (let n of e) try {
848
- await j(n) && t.push(n);
2128
+ await y(n) && t.push(n);
849
2129
  } catch {}
850
2130
  return t;
851
2131
  }
852
- function je(e) {
2132
+ function Tn(e) {
853
2133
  switch (e) {
854
2134
  case "error": return "ERROR ";
855
2135
  case "warning": return "WARN ";
@@ -857,4 +2137,4 @@ function je(e) {
857
2137
  }
858
2138
  }
859
2139
  //#endregion
860
- export { K as DEFAULT_ATTR_WHITELIST, O as annotDocsConfigSchema, D as annotFrontmatterSchema, q as captureScreen, J as collectAttributesYaml, de as defineConfig, b as detectDrift, ie as detectDriftFromYaml, $ as filterAnnotMdxFiles, w as isLintableScreen, fe as isScreenRole, T as lintableScreens, Ce as main, M as parseMdx, j as parseMdxFile, g as parseSnapshot, _ as resolveMatch, ee as resolveOverlays, x as summariseDrift, Se as test, W as updateCommentBlocks, Q as walkMdx };
2140
+ export { dt as ANNOTATIONS_YAML_VERSION, P as ANNOTATION_KINDS, F as AnnotationsYamlError, E as DEFAULT_ATTR_WHITELIST, Pt as _resetLegacyOverlayDedupForTests, pe as annotDocsConfigSchema, ue as annotFrontmatterSchema, $t as buildAnnotationsFile, ze as buildBadgeAnnotations, Be as buildBadgeAnnotationsFromYaml, We as buildRasterRedactRegionsFromYaml, Ue as buildShapeAnnotationsFromYaml, Le as captureScreen, O as collectAttributesYaml, Zt as collectMatchKeysFromAnnotation, me as defineConfig, K as detectDrift, Wt as detectDriftFromElementTree, Ht as detectDriftFromYaml, Re as elementTreeToBoxedEntries, Ut as elementTreeToSnapshotEntries, st as emptyAnnotationsSvg, $ as filterAnnotMdxFiles, Nt as formatLegacyOverlayWarning, Xt as isLintableScreen, he as isScreenRole, J as lintableScreens, pn as main, Qt as migrateOverlaysToAnnotationsFile, ft as parseAnnotationsYaml, xe as parseMdx, y as parseMdxFile, G as parseSnapshot, k as parseSnapshotBoxes, Ft as resolveMatch, Ve as resolveMdxAnnotations, It as resolveOverlays, pt as serializeAnnotationsYaml, q as summariseDrift, He as svgFromBadges, ot as svgFromBboxAnnotations, D as syncProductDocs, Ie as test, w as updateCommentBlocks, Q as walkMdx, Mt as warnLegacyOverlay };