annotate-anything 0.0.1 → 0.1.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.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +238 -3
  3. package/agent/INSTRUCTIONS.md +70 -0
  4. package/dist/annotate-anything.iife.js +164 -0
  5. package/dist/cli.js +785 -0
  6. package/dist/client-file-B1I-YibZ.js +12 -0
  7. package/dist/index.js +1 -0
  8. package/dist/local-W-uyrETY.js +4 -0
  9. package/dist/middleware.js +18 -0
  10. package/dist/next-client.js +19 -0
  11. package/dist/next.js +49 -0
  12. package/dist/node-SB91dcoI.js +614 -0
  13. package/dist/react.js +13 -0
  14. package/dist/src-DTByJ6QG.js +2116 -0
  15. package/dist/types/anchor.d.ts +36 -0
  16. package/dist/types/annotator.d.ts +173 -0
  17. package/dist/types/auto.d.ts +1 -0
  18. package/dist/types/cli.d.ts +1 -0
  19. package/dist/types/icons.d.ts +13 -0
  20. package/dist/types/index.d.ts +19 -0
  21. package/dist/types/local.d.ts +2 -0
  22. package/dist/types/mcp.d.ts +2 -0
  23. package/dist/types/next-client.d.ts +4 -0
  24. package/dist/types/next.d.ts +10 -0
  25. package/dist/types/react.d.ts +4 -0
  26. package/dist/types/scheme.d.ts +2 -0
  27. package/dist/types/server/client-file.d.ts +1 -0
  28. package/dist/types/server/core.d.ts +57 -0
  29. package/dist/types/server/locate.d.ts +2 -0
  30. package/dist/types/server/middleware.d.ts +8 -0
  31. package/dist/types/server/node.d.ts +4 -0
  32. package/dist/types/server/serve.d.ts +9 -0
  33. package/dist/types/server/store.d.ts +43 -0
  34. package/dist/types/server/watcher.d.ts +21 -0
  35. package/dist/types/styles.d.ts +1 -0
  36. package/dist/types/transport.d.ts +23 -0
  37. package/dist/types/types.d.ts +75 -0
  38. package/dist/types/vite.d.ts +20 -0
  39. package/dist/vite.js +108 -0
  40. package/package.json +84 -3
@@ -0,0 +1,2116 @@
1
+ import { t as LOCAL_HOST } from "./local-W-uyrETY.js";
2
+ // src/anchor.ts
3
+ var STATE_CLASS = /^(on|off|open|closed|active|sel|selected|current|hover|hovered|focus|focused|flash|show|shown|hidden|visible|disabled|checked|expanded|collapsed|loading)$|^(is|has)-/;
4
+ var GENERATED_ID = /\d{4,}|[:«»]|^_r_|^radix-|^headlessui-|^react-aria|^mui-|^ember\d/;
5
+ var PLAIN_CLASS = /^[A-Za-z_-][\w-]*$/;
6
+ var DIALOG = "dialog,[role=dialog],[role=alertdialog]";
7
+ var OPEN_DIALOG = "dialog[open],[role=dialog],[role=alertdialog]";
8
+ var DEEP = " >>> ";
9
+ var NEAR = 80;
10
+ function matches(el, sel) {
11
+ try {
12
+ return el.matches(sel);
13
+ } catch {
14
+ return false;
15
+ }
16
+ }
17
+ var isShadow = (n) => n.nodeType === 11 && "host" in n;
18
+ function frameOf(doc) {
19
+ try {
20
+ return doc.defaultView?.frameElement ?? null;
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+ function frameDoc(el) {
26
+ if (el.tagName !== "IFRAME" && el.tagName !== "FRAME") return null;
27
+ try {
28
+ return el.contentDocument;
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+ function composedParent(el) {
34
+ if (el.parentElement) return el.parentElement;
35
+ const root = el.getRootNode();
36
+ if (isShadow(root)) return root.host;
37
+ if (root.nodeType === 9 && el === root.documentElement) return frameOf(root);
38
+ return null;
39
+ }
40
+ function within(container, el) {
41
+ for (let e = el; e; e = composedParent(e)) if (e === container) return true;
42
+ return false;
43
+ }
44
+ function rectOf(el) {
45
+ const r = el.getBoundingClientRect();
46
+ let x = r.left;
47
+ let y = r.top;
48
+ for (let doc = el.ownerDocument, f = frameOf(doc); f && doc !== document; doc = f.ownerDocument, f = frameOf(doc)) {
49
+ const fr = f.getBoundingClientRect();
50
+ x += fr.left + f.clientLeft;
51
+ y += fr.top + f.clientTop;
52
+ }
53
+ return {
54
+ left: x,
55
+ top: y,
56
+ width: r.width,
57
+ height: r.height,
58
+ right: x + r.width,
59
+ bottom: y + r.height
60
+ };
61
+ }
62
+ function rootsOf(el) {
63
+ const roots = [];
64
+ for (let e = el; e;) {
65
+ const root = e.getRootNode();
66
+ if (root === document) break;
67
+ roots.push(root);
68
+ e = isShadow(root) ? root.host : frameOf(root);
69
+ }
70
+ return roots;
71
+ }
72
+ function inDialog(el) {
73
+ for (let e = el; e; e = composedParent(e)) if (matches(e, DIALOG)) return true;
74
+ return false;
75
+ }
76
+ function topLayer(host) {
77
+ let open = [...document.querySelectorAll(OPEN_DIALOG)].filter((el) => el !== host && !host.contains(el) && visible(el));
78
+ const top = open.filter((el) => el.tagName === "DIALOG" && matches(el, ":modal")).at(-1);
79
+ if (top) open = open.filter((el) => top.contains(el));
80
+ const dialog = open.at(-1) ?? null;
81
+ if (!dialog) return {
82
+ parent: document.body,
83
+ dialog: null,
84
+ modal: false
85
+ };
86
+ const headless = dialog.hasAttribute("data-headlessui-state");
87
+ const modal = matches(dialog, ":modal") || dialog.getAttribute("aria-modal") === "true" || headless || document.body.style.pointerEvents === "none";
88
+ return {
89
+ parent: headless ? document.body : dialog,
90
+ dialog,
91
+ modal
92
+ };
93
+ }
94
+ function visible(el) {
95
+ const r = el.getBoundingClientRect();
96
+ return r.width > 0 || r.height > 0;
97
+ }
98
+ function elementAt(x, y, host) {
99
+ let el = null;
100
+ for (const e of document.elementsFromPoint(x, y)) {
101
+ if (e === host || e === document.documentElement || e === document.body) continue;
102
+ el = e;
103
+ break;
104
+ }
105
+ let ox = 0;
106
+ let oy = 0;
107
+ for (let depth = 0; el && depth < 10; depth++) {
108
+ if (el.shadowRoot) {
109
+ const root = el.shadowRoot;
110
+ const inner = root.elementsFromPoint(x - ox, y - oy).find((e) => e.getRootNode() === root);
111
+ if (!inner) break;
112
+ el = inner;
113
+ continue;
114
+ }
115
+ const doc = frameDoc(el);
116
+ if (doc) {
117
+ const fr = el.getBoundingClientRect();
118
+ ox += fr.left + el.clientLeft;
119
+ oy += fr.top + el.clientTop;
120
+ const inner = doc.elementsFromPoint(x - ox, y - oy).find((e) => e !== doc.documentElement && e !== doc.body);
121
+ if (!inner) break;
122
+ el = inner;
123
+ continue;
124
+ }
125
+ break;
126
+ }
127
+ return el;
128
+ }
129
+ function localPath(el, root) {
130
+ const parts = [];
131
+ const doc = root.nodeType === 9 ? root : null;
132
+ let cur = el;
133
+ while (cur && cur !== doc?.body && cur !== doc?.documentElement && parts.length < 12) {
134
+ const tag = cur.tagName.toLowerCase();
135
+ if (cur.id && !GENERATED_ID.test(cur.id)) {
136
+ parts.unshift(tag + "#" + CSS.escape(cur.id));
137
+ break;
138
+ }
139
+ let part = tag;
140
+ const cls = [...cur.classList].filter((c) => PLAIN_CLASS.test(c) && !/\d{3,}/.test(c) && !STATE_CLASS.test(c)).slice(0, 2);
141
+ if (cls.length) part += "." + cls.map((c) => CSS.escape(c)).join(".");
142
+ const parent = cur.parentElement;
143
+ const same = (parent ? [...parent.children] : [...root.children]).filter((c) => c.tagName === cur.tagName);
144
+ if (same.length > 1) part += `:nth-of-type(${same.indexOf(cur) + 1})`;
145
+ parts.unshift(part);
146
+ try {
147
+ const path = parts.join(" > ");
148
+ if (parts.length >= 2 && root.querySelectorAll(path).length === 1 && root.querySelector(path) === el) break;
149
+ } catch {}
150
+ cur = parent;
151
+ }
152
+ return parts.join(" > ") || el.tagName.toLowerCase();
153
+ }
154
+ function cssPath(el) {
155
+ const segs = [];
156
+ for (let cur = el; cur;) {
157
+ const root = cur.getRootNode();
158
+ segs.unshift(localPath(cur, root));
159
+ cur = isShadow(root) ? root.host : root === document ? null : frameOf(root);
160
+ }
161
+ return segs.join(DEEP);
162
+ }
163
+ function textOf(el) {
164
+ return (el.innerText || el.getAttribute("aria-label") || el.getAttribute("title") || el.alt || el.textContent || "").trim().replace(/\s+/g, " ").slice(0, 180);
165
+ }
166
+ function labelFor(el, attrs) {
167
+ const tag = el.tagName.toLowerCase();
168
+ for (const a of attrs) {
169
+ const v = el.getAttribute(a);
170
+ if (v) return `${tag}[${v}]`;
171
+ }
172
+ if (el.id) return tag + "#" + el.id;
173
+ const c = [...el.classList].find((c) => PLAIN_CLASS.test(c));
174
+ return c ? tag + "." + c : tag;
175
+ }
176
+ function stableAttr(el, attrs) {
177
+ for (const a of attrs) {
178
+ const v = el.getAttribute(a);
179
+ if (v) return v;
180
+ }
181
+ }
182
+ function sized(el) {
183
+ if (!el) return false;
184
+ const r = el.getBoundingClientRect();
185
+ return r.width > 0 || r.height > 0;
186
+ }
187
+ function query(root, sel, host) {
188
+ try {
189
+ const el = root.querySelector(sel);
190
+ return el && el !== host && !host.contains(el) ? el : null;
191
+ } catch {
192
+ return null;
193
+ }
194
+ }
195
+ function container(selector) {
196
+ const segs = selector.split(DEEP);
197
+ let root = document;
198
+ for (const seg of segs.slice(0, -1)) {
199
+ let el = null;
200
+ try {
201
+ el = root.querySelector(seg);
202
+ } catch {}
203
+ const next = el ? el.shadowRoot ?? frameDoc(el) : null;
204
+ if (!next) return null;
205
+ root = next;
206
+ }
207
+ return {
208
+ root,
209
+ last: segs.at(-1)
210
+ };
211
+ }
212
+ function find(note, host, attrs) {
213
+ const c = container(note.selector || note.tag);
214
+ if (!c) return null;
215
+ const { root, last } = c;
216
+ const bySelector = note.selector ? query(root, last, host) : null;
217
+ const agrees = (el) => (!note.testId || stableAttr(el, attrs) === note.testId) && (!note.targetText || textOf(el) === note.targetText);
218
+ if (sized(bySelector) && agrees(bySelector)) return bySelector;
219
+ if (note.testId) for (const a of attrs) {
220
+ const el = query(root, `[${a}="${CSS.escape(note.testId)}"]`, host);
221
+ if (sized(el)) return el;
222
+ }
223
+ if (!note.tag) return null;
224
+ const cx = note.bounds.x + note.bounds.width / 2 + note.scroll.x;
225
+ const cy = note.bounds.y + note.bounds.height / 2 + note.scroll.y;
226
+ let byText = null;
227
+ let byTextDist = Infinity;
228
+ let near = null;
229
+ let nearDist = NEAR;
230
+ let candidates = [];
231
+ try {
232
+ candidates = [...root.querySelectorAll(note.tag)];
233
+ } catch {}
234
+ for (const el of candidates) {
235
+ if (host.contains(el) || inDialog(el) !== note.inDialog) continue;
236
+ const r = rectOf(el);
237
+ if (!r.width && !r.height) continue;
238
+ const dist = Math.hypot(r.left + r.width / 2 + scrollX - cx, r.top + r.height / 2 + scrollY - cy);
239
+ if (note.targetText && dist < byTextDist && textOf(el) === note.targetText) {
240
+ byText = el;
241
+ byTextDist = dist;
242
+ }
243
+ if (dist < nearDist) {
244
+ near = el;
245
+ nearDist = dist;
246
+ }
247
+ }
248
+ if (byText) return byText;
249
+ if (sized(bySelector)) return bySelector;
250
+ return near;
251
+ }
252
+ function place(note, el) {
253
+ if (el) {
254
+ const r = rectOf(el);
255
+ if (note.kind === "element") return {
256
+ x: r.left,
257
+ y: r.top,
258
+ width: r.width,
259
+ height: r.height,
260
+ ok: true,
261
+ hidden: false
262
+ };
263
+ if (note.kind === "area" && note.offset) return {
264
+ x: r.left + note.offset.x,
265
+ y: r.top + note.offset.y,
266
+ width: note.offset.width,
267
+ height: note.offset.height,
268
+ ok: true,
269
+ hidden: false
270
+ };
271
+ const b = note.bounds;
272
+ const fx = b.width ? clamp((note.x - b.x) / b.width, 0, 1) : 0;
273
+ const fy = b.height ? clamp((note.y - b.y) / b.height, 0, 1) : 0;
274
+ return {
275
+ x: r.left + fx * r.width,
276
+ y: r.top + fy * r.height,
277
+ width: 0,
278
+ height: 0,
279
+ ok: true,
280
+ hidden: false
281
+ };
282
+ }
283
+ const dx = note.scroll.x - scrollX;
284
+ const dy = note.scroll.y - scrollY;
285
+ const hidden = note.inDialog;
286
+ if (note.kind === "pin") return {
287
+ x: note.x + dx,
288
+ y: note.y + dy,
289
+ width: 0,
290
+ height: 0,
291
+ ok: false,
292
+ hidden
293
+ };
294
+ if (note.kind === "area" && note.offset) return {
295
+ x: note.bounds.x + note.offset.x + dx,
296
+ y: note.bounds.y + note.offset.y + dy,
297
+ width: note.offset.width,
298
+ height: note.offset.height,
299
+ ok: false,
300
+ hidden
301
+ };
302
+ return {
303
+ x: note.bounds.x + dx,
304
+ y: note.bounds.y + dy,
305
+ width: note.bounds.width,
306
+ height: note.bounds.height,
307
+ ok: false,
308
+ hidden
309
+ };
310
+ }
311
+ function scrollableAt(el) {
312
+ while (el && el !== document.body && el !== document.documentElement) {
313
+ const doc = el.ownerDocument;
314
+ if (doc !== document && (el === doc.body || el === doc.documentElement)) {
315
+ const s = doc.scrollingElement;
316
+ if (s && (s.scrollHeight > s.clientHeight || s.scrollWidth > s.clientWidth)) return s;
317
+ el = frameOf(doc);
318
+ continue;
319
+ }
320
+ const s = getComputedStyle(el);
321
+ if (/(auto|scroll)/.test(s.overflowY + s.overflowX) && (el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth)) return el;
322
+ el = composedParent(el);
323
+ }
324
+ return null;
325
+ }
326
+ function containing(start, rect) {
327
+ let el = start;
328
+ while (el) {
329
+ const parent = composedParent(el);
330
+ if (!parent || parent === document.documentElement) break;
331
+ const r = rectOf(el);
332
+ if (r.left <= rect.x + 1 && r.top <= rect.y + 1 && r.right >= rect.x + rect.width - 1 && r.bottom >= rect.y + rect.height - 1) break;
333
+ el = parent;
334
+ }
335
+ return el;
336
+ }
337
+ function clamp(v, lo, hi) {
338
+ return Math.min(Math.max(v, lo), hi);
339
+ }
340
+ // src/icons.ts
341
+ var ICON = {
342
+ pin: "<svg viewBox=\"0 0 24 24\"><path d=\"M12 21s7-6.7 7-12a7 7 0 1 0-14 0c0 5.3 7 12 7 12z\"/><circle cx=\"12\" cy=\"9\" r=\"2.4\"/></svg>",
343
+ element: "<svg viewBox=\"0 0 24 24\"><path d=\"M5 3l14 8-6 2-2 6z\"/></svg>",
344
+ area: "<svg viewBox=\"0 0 24 24\"><rect x=\"4\" y=\"5\" width=\"16\" height=\"14\" rx=\"1.5\" stroke-dasharray=\"3 2.5\"/></svg>",
345
+ list: "<svg viewBox=\"0 0 24 24\"><path d=\"M8 6h12M8 12h12M8 18h12M4 6h.01M4 12h.01M4 18h.01\"/></svg>",
346
+ eye: "<svg viewBox=\"0 0 24 24\"><path d=\"M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7S2 12 2 12z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>",
347
+ eyeOff: "<svg viewBox=\"0 0 24 24\"><path d=\"M3 3l18 18M10.6 5.1A10 10 0 0 1 12 5c6.4 0 10 7 10 7a17 17 0 0 1-3.2 4M6.6 6.6C3.9 8.3 2 12 2 12s3.6 7 10 7a9.6 9.6 0 0 0 5.4-1.6\"/></svg>",
348
+ caret: "<svg viewBox=\"0 0 24 24\"><path d=\"M6 9l6 6 6-6\"/></svg>",
349
+ copy: "<svg viewBox=\"0 0 24 24\"><rect x=\"9\" y=\"9\" width=\"11\" height=\"11\" rx=\"2\"/><path d=\"M5 15H4V6a2 2 0 0 1 2-2h9v1\"/></svg>",
350
+ check: "<svg viewBox=\"0 0 24 24\"><path d=\"M5 12l5 5 9-10\"/></svg>",
351
+ trash: "<svg viewBox=\"0 0 24 24\"><path d=\"M3 6h18M8 6V4h8v2M6 6l1 14h10l1-14M10 11v5M14 11v5\"/></svg>",
352
+ pencil: "<svg viewBox=\"0 0 24 24\"><path d=\"M4 20h4L19 9a2.8 2.8 0 0 0-4-4L4 16v4z\"/><path d=\"M13.5 6.5l4 4\"/></svg>"
353
+ };
354
+ // src/styles.ts
355
+ var LIGHT = {
356
+ ink: "#ffffff",
357
+ "ink-2": "#f1f6fb",
358
+ "ink-hover": "#e3edf6",
359
+ text: "#0b1a28",
360
+ muted: "#556575",
361
+ line: "#d8e4ef",
362
+ edge: "rgba(11,26,40,.09)",
363
+ divider: "#e8eff6",
364
+ hover: "#eef4f9",
365
+ accent: "#0369a1",
366
+ "accent-2": "#4f46e5",
367
+ "on-accent": "#fff",
368
+ sent: "#7c3aed",
369
+ working: "#d97706",
370
+ resolved: "#059669",
371
+ danger: "#be123c",
372
+ "accent-soft": "rgba(3,105,161,.13)",
373
+ "accent-soft-hover": "rgba(3,105,161,.2)",
374
+ shadow: "0 18px 46px rgba(20,30,50,.15)",
375
+ launcher: "#e3edf6",
376
+ "launcher-ring": "rgba(11,26,40,.14)",
377
+ "mark-edge": "#fff",
378
+ radius: "16px",
379
+ font: "13px/1.4 Geist,\"Geist Sans\",Inter,system-ui,-apple-system,sans-serif"
380
+ };
381
+ var DARK = {
382
+ ink: "#1d2630",
383
+ "ink-2": "#27323e",
384
+ "ink-hover": "#323e4c",
385
+ text: "#f1f6fb",
386
+ muted: "#aebbc9",
387
+ line: "#384553",
388
+ edge: "rgba(241,246,251,.09)",
389
+ divider: "#2b3643",
390
+ hover: "#28333f",
391
+ accent: "#38bdf8",
392
+ "accent-2": "#818cf8",
393
+ "on-accent": "#06263a",
394
+ sent: "#b794ff",
395
+ working: "#fbbf24",
396
+ resolved: "#34d399",
397
+ danger: "#fb7185",
398
+ "accent-soft": "rgba(56,189,248,.17)",
399
+ "accent-soft-hover": "rgba(56,189,248,.28)",
400
+ shadow: "0 20px 50px rgba(0,0,0,.5)",
401
+ launcher: "#1d2630",
402
+ "launcher-ring": "rgba(241,246,251,.14)",
403
+ "mark-edge": "#1d2630"
404
+ };
405
+ var tokens = (t) => Object.entries(t).map(([k, v]) => `--aa-${k}:${v}`).join(";");
406
+ var themes = `:host{${tokens(LIGHT)}}:host([data-scheme=dark]){${tokens(DARK)}}`;
407
+ var GRADIENT = "linear-gradient(135deg,var(--aa-accent),var(--aa-accent-2))";
408
+ var css = `
409
+ ${themes}
410
+ .root{all:initial;font:var(--aa-font);color:var(--aa-text);pointer-events:none}
411
+ .root *{box-sizing:border-box;margin:0;padding:0;font:inherit;color:inherit;letter-spacing:normal;text-transform:none;text-align:left;line-height:inherit}
412
+ button,textarea{all:unset;box-sizing:border-box;font:inherit;color:inherit}
413
+ button{cursor:pointer}
414
+ button:focus-visible{outline:2px solid var(--aa-accent);outline-offset:1px}
415
+ [hidden]{display:none!important}
416
+ svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;display:block;flex:none}
417
+ .capture{position:fixed;inset:0;cursor:crosshair;background:transparent;touch-action:none;pointer-events:auto}
418
+ .hover{position:fixed;pointer-events:none;border:1px dashed color-mix(in srgb,var(--aa-accent) 70%,transparent);border-radius:3px}
419
+ .hover.el{border:0;border-radius:6px;background:linear-gradient(var(--aa-accent),var(--aa-accent)) 0 0/14px 2px no-repeat,linear-gradient(var(--aa-accent),var(--aa-accent)) 0 0/2px 14px no-repeat,linear-gradient(var(--aa-accent),var(--aa-accent)) 100% 0/14px 2px no-repeat,linear-gradient(var(--aa-accent),var(--aa-accent)) 100% 0/2px 14px no-repeat,linear-gradient(var(--aa-accent),var(--aa-accent)) 0 100%/14px 2px no-repeat,linear-gradient(var(--aa-accent),var(--aa-accent)) 0 100%/2px 14px no-repeat,linear-gradient(var(--aa-accent),var(--aa-accent)) 100% 100%/14px 2px no-repeat,linear-gradient(var(--aa-accent),var(--aa-accent)) 100% 100%/2px 14px no-repeat,color-mix(in srgb,var(--aa-accent) 7%,transparent);transition:left .12s cubic-bezier(.2,.9,.3,1),top .12s cubic-bezier(.2,.9,.3,1),width .12s cubic-bezier(.2,.9,.3,1),height .12s cubic-bezier(.2,.9,.3,1)}
420
+ .hover.el span{left:16px;border-radius:6px;background:${GRADIENT}}
421
+ .hover span{position:absolute;left:-1px;bottom:100%;margin-bottom:3px;padding:2px 6px;font-size:10.5px;font-weight:600;white-space:nowrap;color:var(--aa-on-accent);background:var(--aa-accent);border-radius:4px}
422
+ .area{position:fixed;pointer-events:none;border:1.5px solid var(--aa-accent);background:color-mix(in srgb,var(--aa-accent) 7%,transparent);border-radius:4px}
423
+ .area.drawing{border-style:dashed}
424
+ .area.sent,.area.working{border-color:var(--aa-sent);background:color-mix(in srgb,var(--aa-sent) 6%,transparent)}
425
+ .area.resolved{border-color:color-mix(in srgb,var(--aa-resolved) 55%,transparent);border-style:dashed;background:transparent}
426
+ .area.detached{opacity:.45;border-style:dashed}
427
+ .mark{position:fixed;width:22px;height:22px;padding:0;border:2px solid var(--aa-mark-edge);border-radius:50%;background:${GRADIENT};color:var(--aa-on-accent);font-size:11px;font-weight:700;line-height:18px;text-align:center;box-shadow:0 1px 5px rgba(0,0,0,.22);transform:translate(-50%,-50%);transition:transform .16s cubic-bezier(.2,1.4,.4,1);pointer-events:auto}
428
+ .mark:hover,.mark.current{transform:translate(-50%,-50%) scale(1.12)}
429
+ .mark.current{box-shadow:0 0 0 3px color-mix(in srgb,var(--aa-accent) 28%,transparent),0 1px 5px rgba(0,0,0,.22)}
430
+ .mark.current{color:var(--aa-on-accent)}
431
+ .mark.d3,.dot.d3{font-size:8px}
432
+ .mark.d4,.dot.d4{font-size:6px}
433
+ .mark.sent{background:linear-gradient(135deg,#7c3aed,#a78bfa);color:#fff}
434
+ .mark.working{background:linear-gradient(135deg,#d97706,#fbbf24);color:#fff}
435
+ .mark.resolved{background:linear-gradient(135deg,#059669,#34d399);color:#fff}
436
+ .mark.detached{opacity:.5;border-style:dashed}
437
+ .mark.sent::after,.mark.working::after{content:"";position:absolute;inset:-5px;border-radius:50%;background:conic-gradient(currentColor,transparent 70%);-webkit-mask:radial-gradient(farthest-side,transparent calc(100% - 2px),#000 calc(100% - 1.5px));mask:radial-gradient(farthest-side,transparent calc(100% - 2px),#000 calc(100% - 1.5px));animation:aa-spin 1.1s linear infinite;pointer-events:none}
438
+ .mark.sent::after{color:var(--aa-sent)}
439
+ .mark.working::after{color:var(--aa-working)}
440
+ .burst{position:fixed;width:22px;height:22px;pointer-events:none}
441
+ .burst::before{content:"";position:absolute;inset:0;border-radius:50%;border:2px solid var(--aa-resolved);animation:aa-ring .75s cubic-bezier(.1,.7,.3,1) forwards}
442
+ .burst::after{content:"";position:absolute;left:10px;top:10px;width:2.5px;height:2.5px;border-radius:50%;animation:aa-sparks .7s cubic-bezier(.1,.7,.3,1) forwards}
443
+ @keyframes aa-ring{from{transform:scale(.7);opacity:1}to{transform:scale(2.5);opacity:0}}
444
+ @keyframes aa-sparks{from{box-shadow:0 -8px var(--aa-resolved),5.6px -5.6px var(--aa-accent),8px 0 var(--aa-resolved),5.6px 5.6px var(--aa-accent),0 8px var(--aa-resolved),-5.6px 5.6px var(--aa-accent),-8px 0 var(--aa-resolved),-5.6px -5.6px var(--aa-accent);opacity:1}to{box-shadow:0 -30px var(--aa-resolved),21px -21px var(--aa-accent),30px 0 var(--aa-resolved),21px 21px var(--aa-accent),0 30px var(--aa-resolved),-21px 21px var(--aa-accent),-30px 0 var(--aa-resolved),-21px -21px var(--aa-accent);opacity:0}}
445
+ @keyframes aa-spin{to{rotate:360deg}}
446
+ @keyframes aa-pop{from{opacity:0;transform:translateY(6px) scale(.97)}}
447
+ .panel,.toolbar{position:fixed;background:var(--aa-ink);border:1px solid var(--aa-edge);border-radius:var(--aa-radius);box-shadow:var(--aa-shadow);pointer-events:auto}
448
+ .panel:not([hidden]),.toolbar:not([hidden]){animation:aa-pop .2s cubic-bezier(.2,1.2,.4,1)}
449
+ .panel{z-index:3;padding:10px}
450
+ .head{display:flex;align-items:center;gap:8px;margin-bottom:8px}
451
+ .chip{flex:none;padding:2px 7px;font-size:11px;font-weight:700;color:var(--aa-on-accent);background:${GRADIENT};border-radius:6px}
452
+ .chip.sent,.dot.sent{background:linear-gradient(120deg,#7c3aed,#9f67ff);color:#fff}
453
+ .chip.working,.dot.working{background:linear-gradient(120deg,#b45309,#d97706);color:#fff}
454
+ .chip.resolved,.dot.resolved{background:linear-gradient(120deg,#059669,#10b981);color:#fff}
455
+ .target{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px;color:var(--aa-muted)}
456
+ .x{color:var(--aa-muted);font-size:18px;line-height:1;padding:0 2px}
457
+ .x:hover{color:var(--aa-text)}
458
+ textarea.input{display:block;width:100%;min-height:84px;resize:vertical;padding:8px 9px;font-size:13px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere;color:var(--aa-text);background:var(--aa-ink-2);border:1px solid var(--aa-line);border-radius:11px;cursor:text}
459
+ textarea.input:focus{border-color:var(--aa-accent);box-shadow:0 0 0 3px var(--aa-accent-soft)}
460
+ textarea.input::placeholder{color:var(--aa-muted)}
461
+ .comment{margin:0 0 8px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere}
462
+ .reply{margin-bottom:8px;padding:8px 9px;line-height:1.45;white-space:pre-wrap;overflow-wrap:anywhere;background:color-mix(in srgb,var(--aa-resolved) 9%,var(--aa-ink));box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--aa-resolved) 35%,transparent);border-radius:10px}
463
+ .reply.progress{background:color-mix(in srgb,var(--aa-working) 9%,var(--aa-ink));box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--aa-working) 35%,transparent)}
464
+ .reply b{display:block;margin-bottom:2px;font-size:10.5px;font-weight:700;color:var(--aa-muted);text-transform:uppercase;letter-spacing:.04em}
465
+ .waiting{display:flex;align-items:flex-start;gap:8px;margin:0 0 8px;padding:8px 9px;font-size:12px;line-height:1.45;color:var(--aa-text);background:var(--aa-ink-2);border-radius:6px;overflow-wrap:anywhere}
466
+ .waiting .spinner{margin-top:2px}
467
+ .waiting span{flex:1;min-width:0}
468
+ .status{display:flex;align-items:center;gap:8px}
469
+ .status>span{flex:1;min-width:0}
470
+ .copy{flex:none;display:inline-flex;align-items:center;gap:4px;margin:-2px 0;padding:3px 7px 3px 6px;font-size:11px;font-weight:600;color:var(--aa-accent);background:var(--aa-accent-soft);border-radius:6px;white-space:nowrap}
471
+ .copy:hover{background:var(--aa-accent-soft-hover)}
472
+ .copy.copied{color:var(--aa-resolved);background:color-mix(in srgb,var(--aa-resolved) 14%,transparent)}
473
+ .copy svg{width:12px;height:12px}
474
+ .foot{display:flex;align-items:center;gap:6px;margin-top:8px}
475
+ .hint{flex:1;font-size:11px;color:var(--aa-muted);text-align:right}
476
+ .hint.nudge{color:var(--aa-accent);font-weight:600}
477
+ .btn{height:30px;padding:0 12px;font-size:12px;font-weight:600;line-height:30px;color:var(--aa-text);background:color-mix(in srgb,var(--aa-text) 9%,transparent);border-radius:9px}
478
+ .btn:hover:not(:disabled){background:color-mix(in srgb,var(--aa-text) 15%,transparent)}
479
+ .panel .btn:not(.primary,.danger){color:var(--aa-accent);background:var(--aa-accent-soft)}
480
+ .panel .btn:not(.primary,.danger):hover:not(:disabled){background:var(--aa-accent-soft-hover)}
481
+ .btn:disabled{opacity:.4;cursor:default}
482
+ .panel .btn.primary,.btn.primary{background:var(--aa-accent);color:var(--aa-on-accent)}
483
+ .panel .btn.primary:hover:not(:disabled),.btn.primary:hover:not(:disabled){background:var(--aa-accent);filter:brightness(1.08)}
484
+ .btn.danger{color:var(--aa-danger);background:color-mix(in srgb,var(--aa-danger) 13%,var(--aa-ink))}
485
+ .btn.danger:hover:not(:disabled){background:color-mix(in srgb,var(--aa-danger) 21%,var(--aa-ink))}
486
+ .btn.trash{display:grid;place-items:center;width:30px;padding:0;color:var(--aa-muted);background:color-mix(in srgb,var(--aa-text) 9%,transparent)}
487
+ .btn.trash svg{width:16px;height:16px}
488
+ .btn.trash:hover:not(:disabled){color:var(--aa-danger);background:color-mix(in srgb,var(--aa-danger) 15%,var(--aa-ink))}
489
+ .toolbar{max-width:calc(100vw - 16px)}
490
+ .tools{display:flex;align-items:center;gap:2px;padding:5px;flex-wrap:wrap}
491
+ .tool{position:relative;display:inline-flex;align-items:center;gap:5px;padding:6px 9px;font-size:12px;font-weight:600;border-radius:8px;white-space:nowrap}
492
+ .tool:hover:not(:disabled){background:var(--aa-hover)}
493
+ .tool.on{color:var(--aa-text);background:linear-gradient(110deg,color-mix(in srgb,var(--aa-accent) 20%,transparent),color-mix(in srgb,var(--aa-accent-2) 20%,transparent))}
494
+ .tool[data-mode].on{background:none}
495
+ .tool[data-mode].on::before{content:"";position:absolute;inset:0;z-index:-1;border-radius:inherit;background:linear-gradient(110deg,color-mix(in srgb,var(--aa-accent) 24%,transparent),color-mix(in srgb,var(--aa-accent-2) 24%,transparent))}
496
+ .toolbar.opening .tools>*{animation:aa-rise .34s cubic-bezier(.2,1.3,.4,1) backwards}
497
+ .toolbar.opening .tools>*:nth-child(2){animation-delay:.03s}
498
+ .toolbar.opening .tools>*:nth-child(3){animation-delay:.06s}
499
+ .toolbar.opening .tools>*:nth-child(4){animation-delay:.09s}
500
+ .toolbar.opening .tools>*:nth-child(5){animation-delay:.12s}
501
+ .toolbar.opening .tools>*:nth-child(6){animation-delay:.15s}
502
+ .toolbar.opening .tools>*:nth-child(7){animation-delay:.18s}
503
+ .toolbar.opening .tools>*:nth-child(8){animation-delay:.21s}
504
+ @keyframes aa-rise{from{opacity:0;transform:translateY(7px)}}
505
+ .tool:disabled{opacity:.4;cursor:default}
506
+ .tool.icon{padding:6px 7px}
507
+ .sep{width:1px;height:18px;margin:0 3px;background:var(--aa-line)}
508
+ .send{position:relative;overflow:hidden;display:inline-flex;align-items:center;gap:6px;margin-left:2px;padding:6px 11px;font-size:12px;font-weight:700;color:var(--aa-accent);background:var(--aa-accent-soft);border-radius:8px;white-space:nowrap}
509
+ .send:not(:disabled)::after{content:"";position:absolute;inset:0;background:linear-gradient(105deg,transparent 35%,color-mix(in srgb,var(--aa-accent) 38%,transparent) 50%,transparent 65%);transform:translateX(-120%);animation:aa-sheen 4.5s ease-in-out infinite;pointer-events:none}
510
+ @keyframes aa-sheen{0%,62%{transform:translateX(-120%)}86%,100%{transform:translateX(120%)}}
511
+ .send:not(:disabled) .count{color:var(--aa-on-accent);background:${GRADIENT};animation:aa-count 2.25s ease-in-out infinite}
512
+ @keyframes aa-count{50%{box-shadow:0 0 0 3px var(--aa-accent-soft)}}
513
+ .send:hover:not(:disabled){background:var(--aa-accent-soft-hover)}
514
+ .send:disabled{opacity:.45;cursor:default}
515
+ .split{display:inline-flex;min-width:0;margin-left:2px}
516
+ .split .send{margin-left:0}
517
+ .split .send:has(+ .more){border-radius:8px 0 0 8px;min-width:0}
518
+ .send.more{padding:6px 7px;border-radius:0 8px 8px 0;border-left:1px solid color-mix(in srgb,var(--aa-accent) 30%,transparent)}
519
+ .send.more svg{width:13px;height:13px}
520
+ .send .to{max-width:160px;overflow:hidden;text-overflow:ellipsis}
521
+ .current{flex:none;color:var(--aa-accent)}
522
+ .current svg{width:14px;height:14px}
523
+ .count{font-variant-numeric:tabular-nums;min-width:17px;padding:0 5px;font-size:10.5px;line-height:17px;text-align:center;color:var(--aa-ink);background:var(--aa-text);border-radius:9px}
524
+ .slot{display:contents}
525
+ .rows{width:0;min-width:100%}
526
+ .toolbar.up:not([hidden]){display:flex;flex-direction:column-reverse}
527
+ .toolbar.up .rows{border-bottom:1px solid var(--aa-divider)}
528
+ .toolbar.up .rows>:first-child{border-top:0}
529
+ .status,.rows>.busy{padding:6px 12px 8px;font-size:11.5px;color:var(--aa-muted);border-top:1px solid var(--aa-divider);overflow-wrap:anywhere}
530
+ .rows>.busy{display:flex;align-items:center;gap:7px}
531
+ .rows>.busy span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
532
+ .spinner{flex:none;width:11px;height:11px;border-radius:50%;border:2px solid color-mix(in srgb,var(--aa-muted) 35%,transparent);border-top-color:var(--aa-text);animation:aa-spin .9s linear infinite}
533
+ .list{list-style:none;padding:4px;max-height:260px;overflow-y:auto;border-top:1px solid var(--aa-divider)}
534
+ .list li{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:7px;cursor:pointer}
535
+ .list li:hover{background:var(--aa-hover)}
536
+ .list li:focus-visible{outline:2px solid var(--aa-accent);outline-offset:-2px}
537
+ .phead{padding:8px 12px 2px;font-size:11px;font-weight:600;color:var(--aa-muted);border-top:1px solid var(--aa-divider)}
538
+ .pick{border-top:0}
539
+ .list li.empty{color:var(--aa-muted);cursor:default}
540
+ .list li.empty:hover{background:none}
541
+ .dot{flex:none;width:20px;height:20px;padding:0;font-size:10.5px;font-weight:700;line-height:20px;text-align:center;color:var(--aa-on-accent);background:${GRADIENT};border-radius:50%}
542
+ .ltext{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
543
+ .lstate{flex:none;max-width:45%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px;color:var(--aa-muted)}
544
+ .lfoot{display:flex;align-items:center;justify-content:flex-end;gap:6px;padding:6px 8px;border-top:1px solid var(--aa-divider)}
545
+ .confirm{display:flex;align-items:center;gap:6px;padding:8px;border-top:1px solid var(--aa-divider)}
546
+ .confirm p{flex:1;font-size:11.5px;color:var(--aa-muted)}
547
+ .launcher{position:fixed;transition:transform .22s cubic-bezier(.2,1.5,.4,1),background .15s,color .15s;left:clamp(8px,calc(var(--fx,1) * 100vw - 64px),calc(100vw - 52px));top:clamp(8px,calc(var(--fy,1) * 100vh - 140px),calc(100vh - 52px));width:44px;height:44px;display:grid;place-items:center;color:var(--aa-text);background:var(--aa-launcher);border:1.5px solid var(--aa-launcher-ring);border-radius:50%;box-shadow:0 6px 18px rgba(0,0,0,.3);cursor:grab;touch-action:none;user-select:none;-webkit-user-select:none;pointer-events:auto}
548
+ .launcher.placed{left:clamp(8px,calc(var(--fx) * 100vw),calc(100vw - 52px));top:clamp(8px,calc(var(--fy) * 100vh),calc(100vh - 52px))}
549
+ .launcher svg{width:19px;height:19px}
550
+ .launcher:hover{transform:scale(1.08);background:color-mix(in srgb,var(--aa-text) 12%,var(--aa-launcher))}
551
+ .launcher.on:hover{background:${GRADIENT}}
552
+ .launcher:active{transform:scale(.93)}
553
+ .launcher:hover svg{animation:aa-wiggle .55s ease-in-out}
554
+ @keyframes aa-wiggle{20%{transform:rotate(-14deg)}50%{transform:rotate(10deg)}75%{transform:rotate(-5deg)}}
555
+ .launcher.on{background:${GRADIENT};border-color:transparent;color:var(--aa-on-accent)}
556
+ .launcher.busy::after{content:"";position:absolute;inset:-5px;border-radius:50%;border:2px solid transparent;border-top-color:var(--aa-accent);animation:aa-spin 1.1s linear infinite}
557
+ .launcher.on.busy::after{border-top-color:var(--aa-accent-2)}
558
+ .badge{position:absolute;top:-5px;right:-5px;min-width:18px;height:18px;padding:0 4px;font-size:10.5px;font-weight:700;line-height:14px;text-align:center;color:var(--aa-on-accent);background:var(--aa-accent);border:2px solid var(--aa-mark-edge);border-radius:9px}
559
+ @media (prefers-reduced-motion:reduce){.mark::after,.launcher.busy::after,.spinner,.send::after,.send .count,.panel,.toolbar,.toolbar *,.launcher svg{animation:none!important}.burst{display:none}.mark,.launcher,.hover{transition:none!important}}
560
+ @media print{.root{display:none!important}}
561
+ `;
562
+ // src/scheme.ts
563
+ var probe;
564
+ function schemeOf(color) {
565
+ if (probe === void 0) probe = document.createElement("canvas").getContext("2d", { willReadFrequently: true });
566
+ if (!probe) return null;
567
+ probe.clearRect(0, 0, 1, 1);
568
+ probe.fillStyle = "rgba(0,0,0,0)";
569
+ probe.fillStyle = color;
570
+ probe.fillRect(0, 0, 1, 1);
571
+ const [r, g, b, a] = probe.getImageData(0, 0, 1, 1).data;
572
+ if (a < 128) return null;
573
+ return .2126 * r + .7152 * g + .0722 * b < 128 ? "dark" : "light";
574
+ }
575
+ function pageScheme(host) {
576
+ const wide = document.elementsFromPoint(innerWidth / 2, innerHeight / 2).filter((el) => {
577
+ if (el === host || host.contains(el)) return false;
578
+ const r = el.getBoundingClientRect();
579
+ return r.width >= innerWidth / 2 && r.height >= innerHeight / 2;
580
+ });
581
+ for (const el of [
582
+ ...wide,
583
+ document.body,
584
+ document.documentElement
585
+ ]) {
586
+ if (!el) continue;
587
+ const found = schemeOf(getComputedStyle(el).backgroundColor);
588
+ if (found) return found;
589
+ }
590
+ const declared = getComputedStyle(document.documentElement).colorScheme;
591
+ if (declared.includes("dark") && !declared.includes("light")) return "dark";
592
+ if (declared.includes("light") && !declared.includes("dark")) return "light";
593
+ return matchMedia("(prefers-color-scheme:dark)").matches ? "dark" : "light";
594
+ }
595
+ // src/transport.ts
596
+ function http(endpoint, extra = {}) {
597
+ const base = endpoint.replace(/\/$/, "");
598
+ async function call(path, body) {
599
+ const res = await fetch(base + path, {
600
+ method: body === void 0 ? "GET" : "POST",
601
+ headers: body === void 0 ? extra : {
602
+ ...extra,
603
+ "Content-Type": "application/json"
604
+ },
605
+ body: body === void 0 ? void 0 : JSON.stringify(body),
606
+ cache: "no-store",
607
+ signal: AbortSignal.timeout(path.endsWith("/submit") ? 12e4 : 2e4)
608
+ });
609
+ if (!res.ok) throw new Error(`${res.status} ${(await res.text()).slice(0, 160)}`);
610
+ return res.json();
611
+ }
612
+ return {
613
+ kind: "http",
614
+ sends: true,
615
+ pull: () => call("/feedback"),
616
+ push: (notes, deleted) => call("/feedback", {
617
+ notes,
618
+ deleted
619
+ }),
620
+ submit: (notes, page, target) => call("/feedback/submit", {
621
+ noteIds: notes.map((n) => n.id),
622
+ ...page,
623
+ target: target?.id,
624
+ agent: target?.label
625
+ }),
626
+ status: async (ids, status) => {
627
+ await call("/feedback/status", {
628
+ ids,
629
+ status
630
+ });
631
+ },
632
+ targets: () => call("/targets").then((r) => Array.isArray(r.targets) ? r.targets : [])
633
+ };
634
+ }
635
+ function clipboard() {
636
+ return {
637
+ ...local(),
638
+ kind: "clipboard",
639
+ sends: false,
640
+ submit: async (notes, page) => {
641
+ await navigator.clipboard.writeText(markdown(notes, page));
642
+ return {
643
+ sent: notes.length,
644
+ watching: false
645
+ };
646
+ }
647
+ };
648
+ }
649
+ function local() {
650
+ const state = (notes, deleted) => ({
651
+ notes,
652
+ deleted,
653
+ watching: false
654
+ });
655
+ return {
656
+ kind: "local",
657
+ sends: false,
658
+ pull: () => Promise.resolve(state([], [])),
659
+ push: (notes, deleted) => Promise.resolve(state(notes, deleted)),
660
+ submit: () => Promise.reject(/* @__PURE__ */ new Error("No agent connection")),
661
+ status: () => Promise.resolve()
662
+ };
663
+ }
664
+ function markdown(notes, page) {
665
+ const lines = [`UI notes on ${page.title || page.url} (${page.url}), viewport ${page.viewport.width}x${page.viewport.height}:`, ""];
666
+ for (const n of notes) {
667
+ lines.push(`#${n.n} ${n.kind} on ${n.route}: ${n.selector || n.tag}`);
668
+ if (n.targetText) lines.push(` element text: "${n.targetText}"`);
669
+ if (n.testId) lines.push(` test id: ${n.testId}`);
670
+ if (Object.keys(n.context).length) lines.push(` context: ${JSON.stringify(n.context)}`);
671
+ lines.push(` ${n.comment.replace(/\n/g, "\n ")}`, "");
672
+ }
673
+ return lines.join("\n").trimEnd();
674
+ }
675
+ // src/annotator.ts
676
+ var PANEL_W = 300;
677
+ var KIND_LABEL = {
678
+ pin: "Pin",
679
+ element: "Element",
680
+ area: "Area"
681
+ };
682
+ var HINT = {
683
+ pin: "Click an exact spot to pin a note · Esc to stop",
684
+ element: "Click an element to note it · Esc to stop",
685
+ area: "Drag a box over an area · Esc to stop"
686
+ };
687
+ var TOOL_TITLE = {
688
+ pin: "Drop a pin on an exact spot",
689
+ element: "Select a whole element",
690
+ area: "Drag a box over an area"
691
+ };
692
+ var LAYOUT_ATTRS = [
693
+ "class",
694
+ "style",
695
+ "hidden",
696
+ "open",
697
+ "aria-hidden",
698
+ "aria-expanded",
699
+ "aria-current",
700
+ "data-state"
701
+ ];
702
+ var esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({
703
+ "&": "&amp;",
704
+ "<": "&lt;",
705
+ ">": "&gt;",
706
+ "\"": "&quot;"
707
+ })[c]);
708
+ var now = () => (/* @__PURE__ */ new Date()).toISOString();
709
+ var digits = (n) => n >= 1e3 ? " d4" : n >= 100 ? " d3" : "";
710
+ var plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
711
+ var clock = (iso) => iso ? new Date(iso).toLocaleTimeString([], {
712
+ hour: "numeric",
713
+ minute: "2-digit"
714
+ }) : "";
715
+ var uuid = () => crypto.randomUUID ? crypto.randomUUID() : Date.now().toString(36) + Math.random().toString(36).slice(2);
716
+ function read(key, fallback) {
717
+ try {
718
+ const raw = localStorage.getItem(key);
719
+ return raw ? JSON.parse(raw) : fallback;
720
+ } catch {
721
+ return fallback;
722
+ }
723
+ }
724
+ function write(key, value) {
725
+ try {
726
+ if (value === null) localStorage.removeItem(key);
727
+ else localStorage.setItem(key, JSON.stringify(value));
728
+ } catch {}
729
+ }
730
+ var Annotator = class {
731
+ options;
732
+ host;
733
+ shadow;
734
+ q;
735
+ layer;
736
+ hoverBox;
737
+ schemeObserver;
738
+ schemeMedia;
739
+ schemeStop;
740
+ schemeTimer = 0;
741
+ lastStatus = /* @__PURE__ */ new Map();
742
+ marksHtml = "";
743
+ barHtml = "";
744
+ toolRect = null;
745
+ drawBox;
746
+ marks;
747
+ panelEl;
748
+ bar;
749
+ launcher;
750
+ transport;
751
+ attrs;
752
+ keys;
753
+ notes = [];
754
+ deleted = [];
755
+ pending = /* @__PURE__ */ new Map();
756
+ watching = false;
757
+ agentFromServer;
758
+ connected = true;
759
+ ready = false;
760
+ open = false;
761
+ mode = null;
762
+ listOpen = false;
763
+ confirming = false;
764
+ picking = null;
765
+ targets = [];
766
+ panel = null;
767
+ drawing = null;
768
+ msg = null;
769
+ intro = null;
770
+ busy = false;
771
+ lastSent;
772
+ delivered = false;
773
+ showMarks;
774
+ route;
775
+ found = /* @__PURE__ */ new Map();
776
+ dirtyAnchors = true;
777
+ modal = null;
778
+ roots = /* @__PURE__ */ new Set();
779
+ dialog = null;
780
+ active = false;
781
+ keysOn = false;
782
+ raf = 0;
783
+ attachRaf = 0;
784
+ msgTimer = 0;
785
+ nudgeTimer = 0;
786
+ pushTimer = 0;
787
+ pollTimer = 0;
788
+ pushing = null;
789
+ modalObserver;
790
+ layoutObserver;
791
+ drag = null;
792
+ destroyed = false;
793
+ constructor(options = {}) {
794
+ this.options = options;
795
+ const scope = options.scope ?? "default";
796
+ this.keys = {
797
+ notes: `annotate-anything:${scope}:notes`,
798
+ pos: "annotate-anything:launcher",
799
+ marks: "annotate-anything:marks",
800
+ target: `annotate-anything:${scope}:target`
801
+ };
802
+ this.attrs = options.stableAttributes ?? [
803
+ "data-testid",
804
+ "data-test",
805
+ "data-qa",
806
+ "data-cy"
807
+ ];
808
+ const t = options.transport ?? (options.endpoint ? "http" : "local");
809
+ this.transport = typeof t === "object" ? t : t === "http" ? http(options.endpoint ?? "/__annotate", options.headers) : t === "clipboard" ? clipboard() : local();
810
+ const saved = read(this.keys.notes, {});
811
+ this.notes = Array.isArray(saved.notes) ? saved.notes : [];
812
+ this.deleted = Array.isArray(saved.deleted) ? saved.deleted : [];
813
+ this.ready = this.transport.kind !== "http";
814
+ this.showMarks = read(this.keys.marks, true);
815
+ this.route = this.routeOf();
816
+ this.host = document.createElement("annotate-anything");
817
+ const hs = this.host.style;
818
+ for (const [k, v] of Object.entries({
819
+ position: "fixed",
820
+ inset: "0",
821
+ width: "100vw",
822
+ height: "100vh",
823
+ margin: "0",
824
+ padding: "0",
825
+ border: "0",
826
+ background: "transparent",
827
+ overflow: "visible",
828
+ "pointer-events": "none",
829
+ "z-index": "2147483000",
830
+ display: "block",
831
+ "max-width": "none",
832
+ "max-height": "none",
833
+ transform: "none",
834
+ filter: "none",
835
+ contain: "none"
836
+ })) hs.setProperty(k, v, "important");
837
+ this.setTheme(options.theme);
838
+ this.syncScheme();
839
+ if ("showPopover" in this.host) this.host.setAttribute("popover", "manual");
840
+ this.shadow = this.host.attachShadow({ mode: "open" });
841
+ this.shadow.innerHTML = `<style>${css}</style><div class="root" part="root">
842
+ <div class="capture" hidden></div>
843
+ <div class="hover" hidden><span></span></div>
844
+ <div class="area drawing" hidden></div>
845
+ <div class="marks"></div>
846
+ <div class="toolbar" role="toolbar" aria-label="Annotate this page" hidden></div>
847
+ <div class="panel" role="dialog" aria-label="Note" hidden></div>
848
+ <button class="launcher" type="button" aria-label="Annotate this page" aria-expanded="false" title="Annotate the UI (drag to move · double-click to reset)">${ICON.pencil}<span class="badge" hidden></span></button>
849
+ </div>`;
850
+ this.q = (s) => this.shadow.querySelector(s);
851
+ this.layer = this.q(".capture");
852
+ this.hoverBox = this.q(".hover");
853
+ this.drawBox = this.q(".drawing");
854
+ this.marks = this.q(".marks");
855
+ this.panelEl = this.q(".panel");
856
+ this.bar = this.q(".toolbar");
857
+ this.launcher = this.q(".launcher");
858
+ for (const el of options.controls ?? []) this.addControl(el);
859
+ this.modalObserver = new MutationObserver((records) => this.onModalMutation(records));
860
+ this.layoutObserver = new MutationObserver(() => {
861
+ this.dirtyAnchors = true;
862
+ this.schedule();
863
+ });
864
+ this.wire();
865
+ this.placeLauncher();
866
+ this.attach();
867
+ this.watchScheme();
868
+ this.modalObserver.observe(document.documentElement, {
869
+ subtree: true,
870
+ childList: true,
871
+ attributes: true,
872
+ attributeFilter: [
873
+ "open",
874
+ "aria-modal",
875
+ "role"
876
+ ]
877
+ });
878
+ this.render();
879
+ this.start();
880
+ }
881
+ addControl(el) {
882
+ el.slot = "controls";
883
+ this.host.appendChild(el);
884
+ this.renderBar();
885
+ }
886
+ syncScheme = () => {
887
+ if (this.destroyed) return;
888
+ const chosen = this.options.colorScheme;
889
+ this.host.dataset.scheme = chosen && chosen !== "auto" ? chosen : pageScheme(this.host);
890
+ };
891
+ watchScheme() {
892
+ const later = () => {
893
+ this.syncScheme();
894
+ clearTimeout(this.schemeTimer);
895
+ this.schemeTimer = window.setTimeout(this.syncScheme, 350);
896
+ };
897
+ this.schemeObserver = new MutationObserver(later);
898
+ const watch = () => {
899
+ for (const el of [document.documentElement, document.body]) if (el) this.schemeObserver.observe(el, {
900
+ attributes: true,
901
+ attributeFilter: [
902
+ "class",
903
+ "style",
904
+ "data-theme",
905
+ "data-mode",
906
+ "data-color-scheme",
907
+ "data-bs-theme"
908
+ ]
909
+ });
910
+ };
911
+ watch();
912
+ this.schemeMedia = matchMedia("(prefers-color-scheme:dark)");
913
+ this.schemeMedia.addEventListener("change", later);
914
+ this.schemeStop = () => {
915
+ this.schemeObserver?.disconnect();
916
+ this.schemeMedia?.removeEventListener("change", later);
917
+ removeEventListener("load", onLoad);
918
+ clearTimeout(this.schemeTimer);
919
+ };
920
+ const onLoad = () => {
921
+ removeEventListener("load", onLoad);
922
+ watch();
923
+ later();
924
+ };
925
+ if (document.readyState !== "complete") addEventListener("load", onLoad);
926
+ later();
927
+ }
928
+ setTheme(theme) {
929
+ for (const [k, v] of Object.entries(theme ?? {})) this.host.style.setProperty(k.startsWith("--") ? k : `--aa-${k}`, v);
930
+ }
931
+ openTool(opts = {}) {
932
+ if (!this.open) this.toggleOpen();
933
+ if (opts.mode && this.mode !== opts.mode) this.setMode(opts.mode);
934
+ this.intro = opts.intro ?? null;
935
+ this.renderBar();
936
+ }
937
+ close() {
938
+ if (this.open) this.toggleOpen();
939
+ }
940
+ destroy() {
941
+ this.destroyed = true;
942
+ this.schemeStop?.();
943
+ this.setActive(false);
944
+ this.setKeys(false);
945
+ this.modalObserver.disconnect();
946
+ clearTimeout(this.pushTimer);
947
+ clearTimeout(this.pollTimer);
948
+ this.host.remove();
949
+ }
950
+ routeOf() {
951
+ if (this.options.route) return this.options.route();
952
+ return location.pathname + (location.hash.startsWith("#/") ? location.hash : "");
953
+ }
954
+ page() {
955
+ return {
956
+ route: this.route,
957
+ url: location.href,
958
+ title: document.title,
959
+ viewport: {
960
+ width: innerWidth,
961
+ height: innerHeight
962
+ }
963
+ };
964
+ }
965
+ save() {
966
+ write(this.keys.notes, {
967
+ notes: this.notes,
968
+ deleted: this.deleted.slice(-2e3)
969
+ });
970
+ }
971
+ changed() {
972
+ this.save();
973
+ this.schedulePush();
974
+ this.render();
975
+ }
976
+ unsent = () => this.notes.filter((n) => n.status === "open" && this.mine(n));
977
+ mine = (n) => !n.origin || n.origin === location.origin;
978
+ done = () => this.notes.filter((n) => n.status === "resolved");
979
+ waiting = () => this.notes.filter((n) => n.status === "sent" || n.status === "working");
980
+ async start() {
981
+ if (this.transport.kind !== "http") return;
982
+ await this.pull();
983
+ if (this.ready) this.schedulePush(0);
984
+ this.poll();
985
+ }
986
+ merge(state) {
987
+ const deleted = /* @__PURE__ */ new Set([...state.deleted, ...this.deleted]);
988
+ const server = new Map(state.notes.map((n) => [n.id, n]));
989
+ const out = [];
990
+ const t = Date.now();
991
+ for (const mine of this.notes) {
992
+ if (deleted.has(mine.id)) continue;
993
+ const theirs = server.get(mine.id);
994
+ server.delete(mine.id);
995
+ if (!theirs) {
996
+ out.push(mine);
997
+ continue;
998
+ }
999
+ const p = this.pending.get(mine.id);
1000
+ if (p && theirs.status === p.status) this.pending.delete(mine.id);
1001
+ if (p && theirs.status !== p.status && t - p.at < 15e3) {
1002
+ out.push(mine);
1003
+ continue;
1004
+ }
1005
+ const newer = Date.parse(theirs.updatedAt) > Date.parse(mine.updatedAt) ? {
1006
+ comment: theirs.comment,
1007
+ updatedAt: theirs.updatedAt
1008
+ } : {};
1009
+ out.push({
1010
+ ...mine,
1011
+ ...newer,
1012
+ n: theirs.n,
1013
+ status: theirs.status,
1014
+ sentAt: theirs.sentAt,
1015
+ pickedUpAt: theirs.pickedUpAt,
1016
+ reply: theirs.reply,
1017
+ resolvedAt: theirs.resolvedAt
1018
+ });
1019
+ }
1020
+ for (const theirs of server.values()) if (!deleted.has(theirs.id) && this.mine(theirs)) out.push(theirs);
1021
+ this.notes = out;
1022
+ this.deleted = [...deleted];
1023
+ this.watching = !!state.watching;
1024
+ this.agentFromServer = state.agent;
1025
+ this.save();
1026
+ }
1027
+ apply(state) {
1028
+ if (this.destroyed) return;
1029
+ const before = this.panel && !this.panel.isNew ? this.notes.find((n) => n.id === this.panel.note.id) : void 0;
1030
+ const was = new Map(this.notes.map((n) => [n.id, n.status]));
1031
+ this.merge(state);
1032
+ const finished = this.notes.filter((n) => n.status === "resolved" && (was.get(n.id) === "sent" || was.get(n.id) === "working"));
1033
+ if (finished.length) this.flash(`${this.who(true)} finished ${finished.length === 1 ? `note ${finished[0].n}` : `notes ${finished.map((n) => n.n).join(", ")}`}.`);
1034
+ this.connected = true;
1035
+ this.ready = true;
1036
+ if (this.panel && !this.panel.isNew) {
1037
+ const fresh = this.notes.find((n) => n.id === this.panel.note.id);
1038
+ if (!fresh) this.closePanel();
1039
+ else if (before && (fresh.status !== before.status || fresh.reply !== before.reply || fresh.pickedUpAt !== before.pickedUpAt) && !this.isDirty()) this.openPanel(fresh, false);
1040
+ else this.panel.note = fresh;
1041
+ }
1042
+ this.render();
1043
+ }
1044
+ async pull() {
1045
+ try {
1046
+ this.apply(await this.transport.pull());
1047
+ } catch {
1048
+ if (this.destroyed) return;
1049
+ this.connected = false;
1050
+ this.ready = true;
1051
+ this.render();
1052
+ }
1053
+ }
1054
+ schedulePush(delay = 300) {
1055
+ if (this.destroyed || this.transport.kind !== "http") return;
1056
+ clearTimeout(this.pushTimer);
1057
+ this.pushTimer = window.setTimeout(() => void this.flush(), delay);
1058
+ }
1059
+ async flush() {
1060
+ if (this.destroyed || this.transport.kind !== "http") return;
1061
+ clearTimeout(this.pushTimer);
1062
+ while (this.pushing) await this.pushing;
1063
+ let failed;
1064
+ this.pushing = (async () => {
1065
+ try {
1066
+ this.apply(await this.transport.push(this.notes, this.deleted.slice(-2e3)));
1067
+ } catch (e) {
1068
+ failed = e.message;
1069
+ if (this.destroyed) return;
1070
+ this.connected = false;
1071
+ this.renderBar();
1072
+ this.schedulePush(15e3);
1073
+ }
1074
+ })();
1075
+ await this.pushing;
1076
+ this.pushing = null;
1077
+ return failed;
1078
+ }
1079
+ poll() {
1080
+ clearTimeout(this.pollTimer);
1081
+ if (this.destroyed || this.transport.kind !== "http") return;
1082
+ const waiting = this.waiting().length > 0;
1083
+ const need = this.open || !!this.panel || waiting || !this.connected;
1084
+ this.pollTimer = window.setTimeout(async () => {
1085
+ if (need && !document.hidden) await this.pull();
1086
+ this.poll();
1087
+ }, waiting ? 2e3 : need ? 5e3 : 15e3);
1088
+ }
1089
+ setStatus(note, status) {
1090
+ note.status = status;
1091
+ note.updatedAt = now();
1092
+ if (status === "open") delete note.sentAt;
1093
+ if (status === "resolved") note.resolvedAt = now();
1094
+ this.pending.set(note.id, {
1095
+ status,
1096
+ at: Date.now()
1097
+ });
1098
+ this.changed();
1099
+ this.transport.status([note.id], status).catch(() => {});
1100
+ }
1101
+ remove(ids) {
1102
+ const gone = new Set(ids);
1103
+ this.notes = this.notes.filter((n) => !gone.has(n.id));
1104
+ this.deleted.push(...ids);
1105
+ for (const id of ids) this.found.delete(id);
1106
+ this.changed();
1107
+ }
1108
+ onModalMutation(records) {
1109
+ if (!this.host.isConnected) return this.attach();
1110
+ if (this.attachRaf || !records.some((r) => r.type === "attributes" || r.addedNodes.length || r.removedNodes.length)) return;
1111
+ this.attachRaf = requestAnimationFrame(() => {
1112
+ this.attachRaf = 0;
1113
+ this.attach();
1114
+ });
1115
+ }
1116
+ attach() {
1117
+ if (this.destroyed || !document.body) return;
1118
+ const { parent, dialog, modal } = topLayer(this.host);
1119
+ const top = modal ? dialog : null;
1120
+ const moved = this.host.parentElement !== parent;
1121
+ if (moved) {
1122
+ if (this.popoverOpen()) this.host.hidePopover();
1123
+ parent.appendChild(this.host);
1124
+ }
1125
+ if (this.host.hasAttribute("popover") && (moved || dialog !== this.dialog || !this.popoverOpen())) try {
1126
+ if (this.popoverOpen()) this.host.hidePopover();
1127
+ this.host.showPopover();
1128
+ } catch {}
1129
+ this.dialog = dialog;
1130
+ if (top !== this.modal) {
1131
+ this.modal = top;
1132
+ this.dirtyAnchors = true;
1133
+ this.schedule();
1134
+ }
1135
+ }
1136
+ popoverOpen() {
1137
+ try {
1138
+ return this.host.matches(":popover-open");
1139
+ } catch {
1140
+ return false;
1141
+ }
1142
+ }
1143
+ setActive(on) {
1144
+ if (on === this.active) return;
1145
+ this.active = on;
1146
+ const f = on ? "addEventListener" : "removeEventListener";
1147
+ window[f]("resize", this.onResize);
1148
+ document[f]("scroll", this.onScroll, { capture: true });
1149
+ document[f]("load", this.onLoaded, { capture: true });
1150
+ window[f]("popstate", this.onRoute);
1151
+ window[f]("hashchange", this.onRoute);
1152
+ window[f]("annotate-anything:route", this.onRoute);
1153
+ document[f]("visibilitychange", this.onVisible);
1154
+ if (on) {
1155
+ patchHistory();
1156
+ this.layoutObserver.observe(document.documentElement, {
1157
+ subtree: true,
1158
+ childList: true,
1159
+ attributes: true,
1160
+ attributeFilter: LAYOUT_ATTRS
1161
+ });
1162
+ } else {
1163
+ this.layoutObserver.disconnect();
1164
+ for (const root of this.roots) if (root.nodeType === 9) {
1165
+ root.removeEventListener("scroll", this.onScroll, { capture: true });
1166
+ root.defaultView?.removeEventListener("resize", this.onScroll);
1167
+ }
1168
+ this.roots.clear();
1169
+ }
1170
+ }
1171
+ setKeys(on) {
1172
+ if (on === this.keysOn) return;
1173
+ this.keysOn = on;
1174
+ if (on) window.addEventListener("keydown", this.onKey, true);
1175
+ else window.removeEventListener("keydown", this.onKey, true);
1176
+ }
1177
+ onResize = () => {
1178
+ this.schedule();
1179
+ if (this.open) this.placeBar();
1180
+ };
1181
+ onScroll = () => this.schedule();
1182
+ onLoaded = () => {
1183
+ this.dirtyAnchors = true;
1184
+ this.schedule();
1185
+ };
1186
+ onVisible = () => {
1187
+ if (!document.hidden && this.transport.kind === "http") this.pull();
1188
+ };
1189
+ onRoute = () => {
1190
+ const r = this.routeOf();
1191
+ if (r === this.route) return;
1192
+ this.route = r;
1193
+ this.dirtyAnchors = true;
1194
+ this.render();
1195
+ };
1196
+ onKey = (e) => {
1197
+ if (e.key !== "Escape" || !(this.open || this.panel)) return;
1198
+ e.preventDefault();
1199
+ e.stopPropagation();
1200
+ if (this.panel) this.dismissPanel();
1201
+ else if (this.picking) {
1202
+ this.picking = null;
1203
+ this.renderBar();
1204
+ } else if (this.confirming) {
1205
+ this.confirming = false;
1206
+ this.renderBar();
1207
+ } else if (this.listOpen) {
1208
+ this.listOpen = false;
1209
+ this.renderBar();
1210
+ } else if (this.mode) this.setMode(this.mode);
1211
+ else this.toggleOpen();
1212
+ };
1213
+ render() {
1214
+ this.setActive(this.open || this.notes.length > 0);
1215
+ this.setKeys(this.open || !!this.panel);
1216
+ this.renderMarks();
1217
+ this.renderBar();
1218
+ this.renderLauncher();
1219
+ this.renderLayer();
1220
+ }
1221
+ schedule() {
1222
+ if (!this.raf) this.raf = requestAnimationFrame(() => this.frame());
1223
+ }
1224
+ frame() {
1225
+ this.raf = 0;
1226
+ if (this.dirtyAnchors) {
1227
+ this.dirtyAnchors = false;
1228
+ this.found.clear();
1229
+ }
1230
+ this.position();
1231
+ }
1232
+ elementFor(note) {
1233
+ let el = this.found.get(note.id);
1234
+ if (el === void 0 || el && !el.isConnected) {
1235
+ el = find(note, this.host, this.attrs);
1236
+ this.found.set(note.id, el);
1237
+ if (el) this.watchRoots(el);
1238
+ }
1239
+ return el;
1240
+ }
1241
+ watchRoots(el) {
1242
+ if (!this.active) return;
1243
+ for (const root of rootsOf(el)) {
1244
+ if (this.roots.has(root)) continue;
1245
+ this.roots.add(root);
1246
+ this.layoutObserver.observe(root, {
1247
+ subtree: true,
1248
+ childList: true,
1249
+ attributes: true,
1250
+ attributeFilter: LAYOUT_ATTRS
1251
+ });
1252
+ if (root.nodeType === 9) {
1253
+ root.addEventListener("scroll", this.onScroll, {
1254
+ capture: true,
1255
+ passive: true
1256
+ });
1257
+ root.defaultView?.addEventListener("resize", this.onScroll);
1258
+ }
1259
+ }
1260
+ }
1261
+ placed(note) {
1262
+ const el = this.elementFor(note);
1263
+ const p = place(note, el);
1264
+ if (this.modal && !(el && within(this.modal, el))) p.hidden = true;
1265
+ return p;
1266
+ }
1267
+ visibleNotes() {
1268
+ if (!this.showMarks || !this.ready) return [];
1269
+ return this.notes.filter((n) => n.route === this.route);
1270
+ }
1271
+ renderMarks() {
1272
+ const shown = this.visibleNotes();
1273
+ const p = this.panel;
1274
+ let html = shown.sort((a, b) => a.n - b.n).map((n) => (n.kind !== "pin" ? `<div class="area ${n.status}" data-area="${esc(n.id)}"></div>` : "") + `<button type="button" class="mark k-${n.kind} ${n.status}${digits(n.n)}" data-id="${esc(n.id)}" title="${esc(n.comment)}" aria-label="Note ${n.n}"><span>${n.status === "resolved" ? "✓" : n.n}</span></button>`).join("");
1275
+ if (p?.isNew) html += (p.note.kind !== "pin" ? "<div class=\"area\" data-area=\"new\"></div>" : "") + `<button type="button" class="mark k-${p.note.kind} current${digits(p.note.n)}" data-id="new" aria-label="New note"><span>${p.note.n}</span></button>`;
1276
+ if (html !== this.marksHtml) {
1277
+ this.marksHtml = html;
1278
+ this.marks.innerHTML = html;
1279
+ }
1280
+ this.position();
1281
+ this.celebrate();
1282
+ }
1283
+ celebrate() {
1284
+ const seen = /* @__PURE__ */ new Map();
1285
+ for (const n of this.notes) {
1286
+ seen.set(n.id, n.status);
1287
+ if (n.status !== "resolved" || !this.lastStatus.has(n.id) || this.lastStatus.get(n.id) === "resolved") continue;
1288
+ const mark = this.marks.querySelector(`[data-id="${CSS.escape(n.id)}"]`);
1289
+ if (!mark || mark.hidden) continue;
1290
+ const r = mark.getBoundingClientRect();
1291
+ const burst = document.createElement("div");
1292
+ burst.className = "burst";
1293
+ burst.style.left = r.left + r.width / 2 - 11 + "px";
1294
+ burst.style.top = r.top + r.height / 2 - 11 + "px";
1295
+ this.marks.parentElement.append(burst);
1296
+ setTimeout(() => burst.remove(), 800);
1297
+ }
1298
+ this.lastStatus = seen;
1299
+ }
1300
+ position() {
1301
+ for (const m of this.marks.querySelectorAll("[data-id]")) {
1302
+ const id = m.dataset.id;
1303
+ const n = id === "new" ? this.panel?.note : this.notes.find((x) => x.id === id);
1304
+ if (!n) continue;
1305
+ const r = id === "new" ? this.newRect(n) : this.placed(n);
1306
+ const area = this.marks.querySelector(`[data-area="${CSS.escape(id)}"]`);
1307
+ m.hidden = r.hidden;
1308
+ m.style.left = r.x + "px";
1309
+ m.style.top = r.y + "px";
1310
+ m.classList.toggle("detached", !r.ok);
1311
+ m.classList.toggle("current", !!this.panel && (id === "new" || this.panel.note.id === n.id));
1312
+ if (area) {
1313
+ area.hidden = r.hidden;
1314
+ Object.assign(area.style, {
1315
+ left: r.x + "px",
1316
+ top: r.y + "px",
1317
+ width: r.width + "px",
1318
+ height: r.height + "px"
1319
+ });
1320
+ area.classList.toggle("detached", !r.ok);
1321
+ }
1322
+ }
1323
+ if (this.panel) this.placePanel();
1324
+ }
1325
+ newRect(n) {
1326
+ const el = this.found.get(n.id) ?? null;
1327
+ return place(n, el && el.isConnected ? el : null);
1328
+ }
1329
+ placePanel() {
1330
+ const p = this.panel;
1331
+ const r = p.isNew ? this.newRect(p.note) : this.placed(p.note);
1332
+ const w = Math.min(PANEL_W, innerWidth - 16);
1333
+ const h = this.panelEl.offsetHeight;
1334
+ let left = r.x + r.width + 14;
1335
+ if (left + w > innerWidth - 8) left = r.x - w - 14;
1336
+ if (left < 8 || left + w > innerWidth - 8) left = Math.max(8, Math.min(r.x, innerWidth - w - 8));
1337
+ let top = Math.min(Math.max(8, r.y - 8), innerHeight - h - 8);
1338
+ if (r.hidden) top = innerHeight / 3;
1339
+ Object.assign(this.panelEl.style, {
1340
+ left: left + "px",
1341
+ top: Math.max(8, top) + "px",
1342
+ width: w + "px"
1343
+ });
1344
+ }
1345
+ statusText() {
1346
+ if (this.msg) return this.msg;
1347
+ if (this.intro) return this.intro;
1348
+ if (this.mode) return HINT[this.mode];
1349
+ const u = this.unsent().length;
1350
+ const t = this.transport.kind;
1351
+ if (t === "http" && !this.connected) return "Saved in this browser · server unreachable, retrying";
1352
+ if (u && t === "clipboard") return `${plural(u, "note")} ready · Copy puts them on your clipboard`;
1353
+ if (u && t === "local") return `${plural(u, "note")} saved in this browser · no agent connection`;
1354
+ if (u) return `${plural(u, "note")} waiting · press Send`;
1355
+ if (t === "local") return "No agent connection · notes stay in this browser";
1356
+ if (t === "clipboard") return "Copy mode · Copy puts your notes on the clipboard to paste into your agent";
1357
+ if (this.needsAgent()) return this.lastSent ? `Sent at ${clock(this.lastSent)} · no agent attached` : "No agent attached";
1358
+ if (this.lastSent) return `Sent at ${clock(this.lastSent)}`;
1359
+ if (this.watching) return `Connected · ${this.who()} is listening`;
1360
+ return this.offers ? "Connected · Send lets you pick an agent" : "Connected · no agent attached yet";
1361
+ }
1362
+ unheard(n) {
1363
+ return n.status === "sent" && !n.pickedUpAt && this.transport.kind === "http" && !this.watching && !this.delivered;
1364
+ }
1365
+ get offers() {
1366
+ return this.targets.length > 0;
1367
+ }
1368
+ lastTarget() {
1369
+ if (this.transport.kind !== "http") return void 0;
1370
+ const id = read(this.keys.target, "");
1371
+ return this.targets.find((t) => t.id === id);
1372
+ }
1373
+ noAgent() {
1374
+ return this.offers ? "no agent has it · open the note to send it to one" : "no agent attached";
1375
+ }
1376
+ watchCmd() {
1377
+ return this.options.watchCommand ?? "npx annotate-anything attach";
1378
+ }
1379
+ agentPrompt() {
1380
+ return `Run \`${this.watchCmd()}\` in this project, in the background if you can, and follow what it prints.`;
1381
+ }
1382
+ needsAgent() {
1383
+ return this.transport.kind === "http" && this.connected && !this.watching && !this.delivered && !this.offers;
1384
+ }
1385
+ copyHtml() {
1386
+ return `<button type="button" class="copy" data-act="copy-prompt" aria-label="Copy prompt for your agent" title="${esc(this.agentPrompt())}">${ICON.copy}<span>Copy prompt</span></button>`;
1387
+ }
1388
+ unheardOnly() {
1389
+ const sent = this.notes.filter((n) => n.status === "sent");
1390
+ return !this.busy && sent.length > 0 && sent.every((n) => this.unheard(n)) && !this.notes.some((n) => n.status === "working");
1391
+ }
1392
+ async copyPrompt(b) {
1393
+ try {
1394
+ await navigator.clipboard.writeText(this.agentPrompt());
1395
+ } catch {
1396
+ this.flash("Your browser blocked copying. Select the prompt and copy it yourself.");
1397
+ return;
1398
+ }
1399
+ const label = b.getAttribute("aria-label");
1400
+ b.innerHTML = `${ICON.check}<span>Copied</span>`;
1401
+ b.classList.add("copied");
1402
+ b.setAttribute("aria-label", "Copied");
1403
+ setTimeout(() => {
1404
+ b.innerHTML = `${ICON.copy}<span>Copy prompt</span>`;
1405
+ b.classList.remove("copied");
1406
+ b.setAttribute("aria-label", label);
1407
+ }, 1500);
1408
+ }
1409
+ busyText() {
1410
+ if (this.busy) return this.transport.kind === "clipboard" ? "Copying…" : `Sending to ${this.who()}…`;
1411
+ const working = this.notes.filter((n) => n.status === "working");
1412
+ if (working.length) {
1413
+ const latest = working.filter((n) => n.reply).sort((a, b) => b.updatedAt > a.updatedAt ? 1 : -1)[0];
1414
+ return `${this.who(true)} is working on ${plural(working.length, "note")}` + (latest?.reply ? ` · ${latest.reply}` : "");
1415
+ }
1416
+ const sent = this.notes.filter((n) => n.status === "sent");
1417
+ if (!sent.length) return "";
1418
+ const picked = sent.filter((n) => n.pickedUpAt);
1419
+ if (picked.length) return `${this.who(true)} picked up ${plural(picked.length, "note")} at ${clock(picked.map((n) => n.pickedUpAt).sort().at(-1))} · starting soon`;
1420
+ const at = sent.map((n) => n.sentAt ?? "").sort().at(-1);
1421
+ return this.watching || this.delivered ? `Sent to ${this.who()} at ${clock(at)} · waiting for ${this.who()} to pick it up` : `Sent at ${clock(at)} · ${this.noAgent()}`;
1422
+ }
1423
+ agentName() {
1424
+ return this.options.agentName ?? this.agentFromServer;
1425
+ }
1426
+ who(start = false) {
1427
+ return this.agentName() ?? (start ? "Your agent" : "your agent");
1428
+ }
1429
+ stateLabel(n) {
1430
+ if (n.status === "open") return "not sent";
1431
+ if (n.status === "resolved") return "done";
1432
+ if (n.status === "working") return `${this.who()} working`;
1433
+ return n.pickedUpAt ? "picked up" : "sent";
1434
+ }
1435
+ slideTool() {
1436
+ const tool = this.bar.querySelector(".tool[data-mode].on");
1437
+ const was = this.toolRect;
1438
+ this.toolRect = tool ? tool.getBoundingClientRect() : null;
1439
+ if (!tool || !was || !this.toolRect || Math.abs(was.left - this.toolRect.left) < 1) return;
1440
+ if (matchMedia("(prefers-reduced-motion:reduce)").matches) return;
1441
+ tool.animate([{
1442
+ transform: `translateX(${was.left - this.toolRect.left}px) scaleX(${was.width / this.toolRect.width})`,
1443
+ transformOrigin: "left"
1444
+ }, {
1445
+ transform: "none",
1446
+ transformOrigin: "left"
1447
+ }], {
1448
+ duration: 240,
1449
+ easing: "cubic-bezier(.2,1.1,.3,1)",
1450
+ pseudoElement: "::before"
1451
+ });
1452
+ }
1453
+ renderBar() {
1454
+ const opening = this.open && this.bar.hidden;
1455
+ this.bar.hidden = !this.open;
1456
+ if (!this.open) {
1457
+ this.toolRect = null;
1458
+ return;
1459
+ }
1460
+ if (opening) {
1461
+ this.bar.classList.add("opening");
1462
+ setTimeout(() => this.bar.classList.remove("opening"), 650);
1463
+ }
1464
+ const u = this.unsent().length;
1465
+ const d = this.done().length;
1466
+ const copy = this.transport.kind === "clipboard";
1467
+ const canSend = u > 0 && !this.busy && (this.transport.sends || copy) && (this.transport.kind !== "http" || this.ready);
1468
+ const tool = (k) => `<button type="button" class="tool${this.mode === k ? " on" : ""}" data-mode="${k}" aria-pressed="${this.mode === k}" title="${TOOL_TITLE[k]}">${ICON[k]}${KIND_LABEL[k]}</button>`;
1469
+ const sorted = [...this.notes].sort((a, b) => a.n - b.n);
1470
+ const to = this.lastTarget();
1471
+ const picker = this.picking ? `<div class="phead">Send to</div><ul class="list pick" aria-label="Send to">${this.picking.length ? this.picking.map((t) => `<li data-target="${esc(t.id)}" tabindex="0"${t.id === to?.id ? " aria-current=\"true\"" : ""}><span class="ltext">${esc(t.label)}</span>${t.detail ? `<span class="lstate">${esc(t.detail)}</span>` : ""}${t.id === to?.id ? `<span class="current">${ICON.check}</span>` : ""}</li>`).join("") : "<li class=\"empty\">Loading agents…</li>"}</ul>` : "";
1472
+ const list = picker ? picker : this.listOpen ? `<ul class="list" aria-label="Notes">${sorted.length ? sorted.map((n) => `<li data-note="${esc(n.id)}" tabindex="0"><span class="dot ${n.status}${digits(n.n)}">${n.status === "resolved" ? "✓" : n.n}</span><span class="ltext">${esc(n.comment)}</span><span class="lstate">${n.route !== this.route ? esc(n.route) + " · " : ""}${esc(this.stateLabel(n))}</span></li>`).join("") : "<li class=\"empty\">No notes yet. Pick Pin, Element or Area.</li>"}</ul>` + (sorted.length ? this.confirming ? `<div class="confirm" role="alertdialog" aria-label="Clear all notes"><p>Delete all ${plural(sorted.length, "note")}? This can't be undone.</p><button type="button" class="btn" data-act="keep">Keep</button><button type="button" class="btn primary" data-act="clear-all">Clear all</button></div>` : "<div class=\"lfoot\"><button type=\"button\" class=\"btn danger\" data-act=\"confirm-clear\">Clear all…</button></div>" : "") : "";
1473
+ const busy = this.busyText();
1474
+ const barHtml = `<div class="tools">${tool("pin")}${tool("element")}${tool("area")}<span class="sep"></span><button type="button" class="tool${this.listOpen ? " on" : ""}" data-act="list" aria-pressed="${this.listOpen}" title="All notes" aria-label="All notes">${ICON.list}${this.notes.length}</button><button type="button" class="tool icon${this.showMarks ? "" : " on"}" data-act="marks" title="${this.showMarks ? "Hide marks" : "Show marks"}" aria-label="${this.showMarks ? "Hide marks" : "Show marks"}">${this.showMarks ? ICON.eye : ICON.eyeOff}</button><span class="sep"></span><button type="button" class="tool" data-act="clear-done" ${d ? "" : "disabled"} title="Remove notes that are done">Clear done<span class="count">${d}</span></button><span class="split"><button type="button" class="send" data-act="send" ${canSend ? "" : "disabled"} title="${copy ? "Copy unsent notes to the clipboard" : to ? `Send unsent notes to ${esc(to.label)}` : this.transport.sends ? "Send unsent notes to your agent" : "No agent connection"}">${copy ? "Copy" : to ? `<span class="to">Send to ${esc(to.label)}</span>` : "Send"}<span class="count">${u}</span></button>` + (to ? `<button type="button" class="send more" data-act="pick" ${canSend ? "" : "disabled"} aria-label="Send to another agent" aria-expanded="${!!this.picking}" title="Send to another agent">${ICON.caret}</button>` : "") + "</span><span class=\"slot\"><slot name=\"controls\"></slot></span></div><div class=\"rows\">" + (busy && !(this.needsAgent() && this.unheardOnly()) ? `<div class="busy" role="status"><i class="spinner"></i><span>${esc(busy)}</span></div>` : "") + `<div class="status"><span role="status">${esc(this.statusText())}</span>${this.needsAgent() ? this.copyHtml() : ""}</div>` + list + `</div>`;
1475
+ if (barHtml !== this.barHtml) {
1476
+ this.barHtml = barHtml;
1477
+ this.bar.innerHTML = barHtml;
1478
+ }
1479
+ this.placeBar();
1480
+ this.slideTool();
1481
+ }
1482
+ launcherXY() {
1483
+ const l = this.launcher;
1484
+ return {
1485
+ x: l.offsetLeft,
1486
+ y: l.offsetTop,
1487
+ w: l.offsetWidth || 44,
1488
+ h: l.offsetHeight || 44
1489
+ };
1490
+ }
1491
+ placeBar() {
1492
+ const { x, y, w, h } = this.launcherXY();
1493
+ const s = this.bar.style;
1494
+ s.top = s.bottom = s.left = s.right = "";
1495
+ const above = y >= innerHeight / 2;
1496
+ this.bar.classList.toggle("up", above);
1497
+ if (above) s.bottom = innerHeight - y + 10 + "px";
1498
+ else s.top = y + h + 10 + "px";
1499
+ const side = x > innerWidth / 2 ? Math.max(8, innerWidth - x - w) : Math.max(8, x);
1500
+ s[x > innerWidth / 2 ? "right" : "left"] = side + "px";
1501
+ s.maxWidth = `calc(100vw - ${side + 8}px)`;
1502
+ }
1503
+ placeLauncher() {
1504
+ const pos = read(this.keys.pos, null);
1505
+ this.launcher.classList.toggle("placed", !!pos);
1506
+ if (pos) {
1507
+ this.launcher.style.setProperty("--fx", String(pos.fx));
1508
+ this.launcher.style.setProperty("--fy", String(pos.fy));
1509
+ } else {
1510
+ this.launcher.style.removeProperty("--fx");
1511
+ this.launcher.style.removeProperty("--fy");
1512
+ }
1513
+ }
1514
+ renderLauncher() {
1515
+ const u = this.unsent().length;
1516
+ const badge = this.launcher.querySelector(".badge");
1517
+ badge.hidden = !(u && !this.open);
1518
+ badge.textContent = String(u);
1519
+ const busy = this.busyText();
1520
+ this.launcher.classList.toggle("on", this.open);
1521
+ this.launcher.classList.toggle("busy", !!busy);
1522
+ this.launcher.setAttribute("aria-expanded", String(this.open));
1523
+ this.launcher.title = (busy ? busy + " · " : "") + "Annotate the UI (drag to move · double-click to reset)";
1524
+ }
1525
+ renderLayer() {
1526
+ const on = !!this.panel || this.open && !!this.mode;
1527
+ this.layer.hidden = !on;
1528
+ if (!on || this.panel) this.hoverBox.hidden = true;
1529
+ }
1530
+ flash(m) {
1531
+ this.msg = m;
1532
+ clearTimeout(this.msgTimer);
1533
+ this.msgTimer = window.setTimeout(() => {
1534
+ this.msg = null;
1535
+ this.renderBar();
1536
+ }, 6e3);
1537
+ this.renderBar();
1538
+ }
1539
+ setMode(m) {
1540
+ this.mode = this.mode === m ? null : m;
1541
+ this.intro = null;
1542
+ this.listOpen = false;
1543
+ this.confirming = false;
1544
+ this.hoverBox.hidden = true;
1545
+ this.render();
1546
+ }
1547
+ toggleOpen() {
1548
+ if (this.panel && this.isDirty()) return this.nudge();
1549
+ this.open = !this.open;
1550
+ if (this.open && this.transport.targets) this.transport.targets().then((t) => {
1551
+ this.targets = t;
1552
+ this.renderBar();
1553
+ }).catch(() => {});
1554
+ if (!this.open) {
1555
+ this.mode = null;
1556
+ this.listOpen = false;
1557
+ this.confirming = false;
1558
+ this.intro = null;
1559
+ if (this.panel) this.closePanel();
1560
+ }
1561
+ this.render();
1562
+ this.poll();
1563
+ }
1564
+ newNote(kind, el, x, y, area) {
1565
+ const r = rectOf(el);
1566
+ const bounds = {
1567
+ x: r.left,
1568
+ y: r.top,
1569
+ width: r.width,
1570
+ height: r.height
1571
+ };
1572
+ const n = Math.max(0, ...this.notes.map((n) => n.n)) + 1;
1573
+ const t = now();
1574
+ let context = {};
1575
+ try {
1576
+ context = this.options.context?.() ?? {};
1577
+ } catch {}
1578
+ return {
1579
+ id: uuid(),
1580
+ n,
1581
+ origin: location.origin,
1582
+ kind,
1583
+ route: this.route,
1584
+ comment: "",
1585
+ createdAt: t,
1586
+ updatedAt: t,
1587
+ status: "open",
1588
+ selector: cssPath(el),
1589
+ testId: stableAttr(el, this.attrs),
1590
+ tag: el.tagName.toLowerCase(),
1591
+ targetText: textOf(el),
1592
+ x,
1593
+ y,
1594
+ bounds,
1595
+ offset: area ? {
1596
+ x: area.x - r.left,
1597
+ y: area.y - r.top,
1598
+ width: area.width,
1599
+ height: area.height
1600
+ } : void 0,
1601
+ scroll: {
1602
+ x: scrollX,
1603
+ y: scrollY
1604
+ },
1605
+ viewport: {
1606
+ width: innerWidth,
1607
+ height: innerHeight
1608
+ },
1609
+ inDialog: inDialog(el),
1610
+ context
1611
+ };
1612
+ }
1613
+ openPanel(note, isNew, el) {
1614
+ this.listOpen = false;
1615
+ this.confirming = false;
1616
+ this.hoverBox.hidden = true;
1617
+ this.panel = {
1618
+ note,
1619
+ isNew
1620
+ };
1621
+ if (el) {
1622
+ this.found.set(note.id, el);
1623
+ this.watchRoots(el);
1624
+ }
1625
+ const n = note;
1626
+ const head = `<div class="head"><span class="chip ${n.status}">#${n.n} · ${KIND_LABEL[n.kind]}</span><span class="target" title="${esc(n.selector)}">${esc(n.tag || "page")}${n.targetText ? " · “" + esc(n.targetText.slice(0, 60)) + "”" : ""}</span><button type="button" class="x" data-act="close" aria-label="Close">×</button></div>`;
1627
+ if (n.status === "open") this.panelEl.innerHTML = head + `<textarea class="input" rows="4" placeholder="What should change here?" aria-label="Note">${esc(n.comment)}</textarea><div class="foot">${isNew ? "" : `<button type="button" class="btn trash danger" data-act="delete" aria-label="Delete note" title="Delete note">${ICON.trash}</button>`}<span class="hint">⌘↵ to save</span><button type="button" class="btn primary" data-act="save">Save</button></div>`;
1628
+ else {
1629
+ const progress = n.status === "working" ? n.reply || `${this.who(true)} is working on this.` : n.status === "sent" ? n.pickedUpAt ? `${this.who(true)} picked this up at ${clock(n.pickedUpAt)}.` : this.unheard(n) ? this.offers ? `Sent at ${clock(n.sentAt)}. No agent has it yet.` : `Sent at ${clock(n.sentAt)}. No agent is attached yet.` : `Sent to ${this.who()} at ${clock(n.sentAt)}, waiting for ${this.who()} to pick it up.` : "";
1630
+ this.panelEl.innerHTML = head + `<p class="comment">${esc(n.comment)}</p>` + (n.reply && n.status === "resolved" ? `<div class="reply"><b>${esc(this.agentName() ?? "Agent")}</b>${esc(n.reply)}</div>` : "") + (progress ? `<div class="waiting"><i class="spinner"></i><span role="status">${esc(progress)}</span>${this.unheard(n) && !this.offers ? this.copyHtml() : ""}</div>` : "") + `<div class="foot"><button type="button" class="btn trash danger" data-act="delete" aria-label="Delete note" title="Delete note">${ICON.trash}</button><span class="hint"></span>${n.status === "resolved" ? "<button type=\"button\" class=\"btn\" data-act=\"reopen\">Reopen</button>" : this.offers && this.unheard(n) ? "<button type=\"button\" class=\"btn primary\" data-act=\"resend\">Send to an agent</button>" : "<button type=\"button\" class=\"btn\" data-act=\"done\">Mark done</button>"}</div>`;
1631
+ }
1632
+ this.panelEl.hidden = false;
1633
+ this.syncSave();
1634
+ this.render();
1635
+ const ta = this.panelEl.querySelector("textarea");
1636
+ if (ta) {
1637
+ const f = () => {
1638
+ ta.focus();
1639
+ ta.setSelectionRange(ta.value.length, ta.value.length);
1640
+ };
1641
+ f();
1642
+ setTimeout(f, 0);
1643
+ }
1644
+ }
1645
+ closePanel() {
1646
+ if (this.panel?.isNew) this.found.delete(this.panel.note.id);
1647
+ this.panel = null;
1648
+ this.panelEl.hidden = true;
1649
+ this.panelEl.innerHTML = "";
1650
+ this.render();
1651
+ }
1652
+ panelText() {
1653
+ return this.panelEl.querySelector("textarea")?.value.trim() ?? "";
1654
+ }
1655
+ isDirty() {
1656
+ const p = this.panel;
1657
+ return !!p && p.note.status === "open" && this.panelText() !== "" && this.panelText() !== p.note.comment;
1658
+ }
1659
+ syncSave() {
1660
+ const b = this.panelEl.querySelector("[data-act=\"save\"]");
1661
+ if (b) b.disabled = !this.panelText();
1662
+ }
1663
+ commitPanel() {
1664
+ const p = this.panel;
1665
+ if (!p) return;
1666
+ const text = this.panelText();
1667
+ if (!text) return this.closePanel();
1668
+ if (p.isNew) {
1669
+ const el = this.found.get(p.note.id);
1670
+ this.notes.push({
1671
+ ...p.note,
1672
+ comment: text,
1673
+ updatedAt: now()
1674
+ });
1675
+ this.panel = null;
1676
+ if (el) this.found.set(p.note.id, el);
1677
+ this.flash(`Note ${p.note.n} saved. ${this.transport.kind === "clipboard" ? "Copy" : "Send"} when you're done.`);
1678
+ } else if (text !== p.note.comment) {
1679
+ const n = this.notes.find((x) => x.id === p.note.id);
1680
+ if (n) {
1681
+ n.comment = text;
1682
+ n.updatedAt = now();
1683
+ }
1684
+ }
1685
+ this.panel = null;
1686
+ this.panelEl.hidden = true;
1687
+ this.panelEl.innerHTML = "";
1688
+ this.changed();
1689
+ }
1690
+ dismissPanel() {
1691
+ if (this.isDirty()) this.commitPanel();
1692
+ else this.closePanel();
1693
+ }
1694
+ nudge() {
1695
+ const h = this.panelEl.querySelector(".hint");
1696
+ if (h) {
1697
+ h.textContent = "Save or press Esc first";
1698
+ h.classList.add("nudge");
1699
+ clearTimeout(this.nudgeTimer);
1700
+ this.nudgeTimer = window.setTimeout(() => {
1701
+ h.textContent = "⌘↵ to save";
1702
+ h.classList.remove("nudge");
1703
+ }, 1600);
1704
+ }
1705
+ this.panelEl.querySelector("textarea")?.focus();
1706
+ }
1707
+ panelAction(a) {
1708
+ const p = this.panel;
1709
+ if (a === "close") return this.dismissPanel();
1710
+ if (a === "save") return this.commitPanel();
1711
+ if (!p) return;
1712
+ const note = this.notes.find((n) => n.id === p.note.id);
1713
+ if (a === "delete") {
1714
+ this.closePanel();
1715
+ if (!p.isNew) this.remove([p.note.id]);
1716
+ return;
1717
+ }
1718
+ if (!note) return;
1719
+ if (a === "resend") {
1720
+ this.closePanel();
1721
+ this.setStatus(note, "open");
1722
+ this.pick();
1723
+ return;
1724
+ }
1725
+ if (a === "done" || a === "reopen") {
1726
+ this.setStatus(note, a === "done" ? "resolved" : "open");
1727
+ if (a === "reopen") this.openPanel(note, false);
1728
+ else this.closePanel();
1729
+ }
1730
+ }
1731
+ async focusNote(n) {
1732
+ if (n.route !== this.route) {
1733
+ await (this.options.navigate ?? defaultNavigate)(n.route);
1734
+ this.onRoute();
1735
+ await new Promise((r) => setTimeout(r, 400));
1736
+ }
1737
+ this.found.delete(n.id);
1738
+ const el = this.elementFor(n);
1739
+ if (el) el.scrollIntoView({
1740
+ behavior: "smooth",
1741
+ block: "center"
1742
+ });
1743
+ setTimeout(() => {
1744
+ const fresh = this.notes.find((x) => x.id === n.id);
1745
+ if (fresh) this.openPanel(fresh, false);
1746
+ }, el ? 350 : 0);
1747
+ }
1748
+ async pick() {
1749
+ if (this.picking) {
1750
+ this.picking = null;
1751
+ return this.renderBar();
1752
+ }
1753
+ if (!this.transport.targets) return this.send();
1754
+ this.listOpen = this.confirming = false;
1755
+ const last = read(this.keys.target, "");
1756
+ const order = (list) => [...list].sort((a, b) => Number(b.id === last) - Number(a.id === last));
1757
+ const focusRow = (id) => (this.bar.querySelector(`li[data-target="${CSS.escape(id ?? "")}"]`) ?? this.bar.querySelector("li[data-target]"))?.focus();
1758
+ this.picking = order(this.targets);
1759
+ this.renderBar();
1760
+ focusRow();
1761
+ let targets = this.targets;
1762
+ try {
1763
+ targets = await this.transport.targets();
1764
+ } catch {}
1765
+ if (!this.picking) return;
1766
+ this.targets = targets;
1767
+ if (!targets.length) {
1768
+ this.picking = null;
1769
+ return this.send();
1770
+ }
1771
+ const focused = this.shadow.activeElement?.dataset?.target;
1772
+ this.picking = order(targets);
1773
+ this.renderBar();
1774
+ focusRow(focused);
1775
+ }
1776
+ async send(target) {
1777
+ if (this.isDirty()) this.commitPanel();
1778
+ const notes = this.unsent();
1779
+ if (!notes.length || this.busy) return;
1780
+ this.busy = true;
1781
+ this.render();
1782
+ try {
1783
+ if (this.transport.kind === "http") {
1784
+ const failed = await this.flush();
1785
+ if (failed) throw new Error(failed);
1786
+ }
1787
+ const res = await this.transport.submit(notes, this.page(), target);
1788
+ if (this.transport.kind === "http" && !res.submission) {
1789
+ this.busy = false;
1790
+ this.flash("Those notes were already sent.");
1791
+ this.pull();
1792
+ return;
1793
+ }
1794
+ this.busy = false;
1795
+ if (this.transport.kind === "clipboard") this.flash(`Copied ${plural(notes.length, "note")} to the clipboard. Paste them into your agent.`);
1796
+ else {
1797
+ const t = res.submission?.submittedAt ?? now();
1798
+ const ids = new Set(res.submission?.noteIds ?? notes.map((n) => n.id));
1799
+ for (const n of this.notes) if (ids.has(n.id) && n.status === "open") {
1800
+ n.status = "sent";
1801
+ n.sentAt = t;
1802
+ }
1803
+ this.lastSent = t;
1804
+ this.watching = res.watching;
1805
+ this.delivered = !!res.delivery?.ok;
1806
+ if (res.agent) this.agentFromServer = res.agent;
1807
+ this.save();
1808
+ const d = res.delivery;
1809
+ const via = d ? d.ok ? ` to ${d.to}${d.detail ? ` (${d.detail})` : ""}` : `. ${d.to} didn't take it: ${d.detail ?? "unknown error"}` : "";
1810
+ this.flash(res.delivery?.ok || res.watching ? `Sent ${plural(res.sent, "note")}${via || ` to ${this.who()}`}.` : `Sent ${plural(res.sent, "note")}${via}. No agent is attached yet.`);
1811
+ }
1812
+ } catch (e) {
1813
+ this.busy = false;
1814
+ this.flash(`${this.transport.kind === "clipboard" ? "Copy" : "Send"} failed: ${e.message}. Your notes are still here.`);
1815
+ }
1816
+ this.render();
1817
+ this.poll();
1818
+ }
1819
+ wire() {
1820
+ const layer = this.layer;
1821
+ layer.addEventListener("mousedown", (e) => e.preventDefault());
1822
+ layer.addEventListener("pointermove", (e) => {
1823
+ if (this.panel) return;
1824
+ if (this.drawing) {
1825
+ this.drawing.x1 = e.clientX;
1826
+ this.drawing.y1 = e.clientY;
1827
+ const r = this.drawRect();
1828
+ Object.assign(this.drawBox.style, {
1829
+ left: r.x + "px",
1830
+ top: r.y + "px",
1831
+ width: r.width + "px",
1832
+ height: r.height + "px"
1833
+ });
1834
+ this.drawBox.hidden = false;
1835
+ return;
1836
+ }
1837
+ if (this.mode === "area" || e.pointerType === "touch") {
1838
+ this.hoverBox.hidden = true;
1839
+ return;
1840
+ }
1841
+ const el = elementAt(e.clientX, e.clientY, this.host);
1842
+ if (!el) {
1843
+ this.hoverBox.hidden = true;
1844
+ return;
1845
+ }
1846
+ const r = rectOf(el);
1847
+ Object.assign(this.hoverBox.style, {
1848
+ left: r.left + "px",
1849
+ top: r.top + "px",
1850
+ width: r.width + "px",
1851
+ height: r.height + "px"
1852
+ });
1853
+ this.hoverBox.classList.toggle("el", this.mode === "element");
1854
+ this.hoverBox.firstElementChild.textContent = labelFor(el, this.attrs);
1855
+ this.hoverBox.hidden = false;
1856
+ });
1857
+ layer.addEventListener("pointerleave", () => this.hoverBox.hidden = true);
1858
+ layer.addEventListener("pointerdown", (e) => {
1859
+ if (e.button !== 0) return;
1860
+ e.preventDefault();
1861
+ if (this.panel) {
1862
+ if (this.isDirty()) this.nudge();
1863
+ else this.closePanel();
1864
+ return;
1865
+ }
1866
+ if (this.mode === "area") {
1867
+ this.drawing = {
1868
+ x0: e.clientX,
1869
+ y0: e.clientY,
1870
+ x1: e.clientX,
1871
+ y1: e.clientY
1872
+ };
1873
+ layer.setPointerCapture(e.pointerId);
1874
+ return;
1875
+ }
1876
+ const el = elementAt(e.clientX, e.clientY, this.host);
1877
+ if (!el) {
1878
+ this.flash("Nothing here to note · click on part of the page");
1879
+ return;
1880
+ }
1881
+ const r = rectOf(el);
1882
+ if (this.mode === "pin") this.openPanel(this.newNote("pin", el, e.clientX, e.clientY), true, el);
1883
+ else if (this.mode === "element") this.openPanel(this.newNote("element", el, r.left, r.top), true, el);
1884
+ });
1885
+ layer.addEventListener("pointerup", () => {
1886
+ if (!this.drawing) return;
1887
+ const rect = this.drawRect();
1888
+ this.drawing = null;
1889
+ this.drawBox.hidden = true;
1890
+ if (rect.width < 12 || rect.height < 12) return;
1891
+ const el = containing(elementAt(rect.x + rect.width / 2, rect.y + rect.height / 2, this.host), rect);
1892
+ if (!el) return;
1893
+ this.openPanel(this.newNote("area", el, rect.x, rect.y, rect), true, el);
1894
+ });
1895
+ layer.addEventListener("wheel", (e) => {
1896
+ const target = scrollableAt(elementAt(e.clientX, e.clientY, this.host));
1897
+ if (target) target.scrollBy(e.deltaX, e.deltaY);
1898
+ else scrollBy(e.deltaX, e.deltaY);
1899
+ }, { passive: true });
1900
+ this.marks.addEventListener("click", (e) => {
1901
+ const m = e.target.closest("[data-id]");
1902
+ if (!m || m.dataset.id === "new") return;
1903
+ const n = this.notes.find((x) => x.id === m.dataset.id);
1904
+ if (!n) return;
1905
+ if (this.panel && this.isDirty()) return this.nudge();
1906
+ this.openPanel(n, false);
1907
+ });
1908
+ this.bar.addEventListener("click", (e) => {
1909
+ const b = e.target.closest("button,li[data-note],li[data-target]");
1910
+ if (!b || b.disabled || !this.bar.contains(b)) return;
1911
+ if (b.dataset.mode) return this.setMode(b.dataset.mode);
1912
+ if (b.dataset.note) {
1913
+ const n = this.notes.find((x) => x.id === b.dataset.note);
1914
+ if (n) this.focusNote(n);
1915
+ return;
1916
+ }
1917
+ if (b.dataset.target !== void 0) {
1918
+ const t = this.picking?.find((x) => x.id === b.dataset.target);
1919
+ this.picking = null;
1920
+ if (t) {
1921
+ write(this.keys.target, t.id);
1922
+ this.send(t);
1923
+ } else this.renderBar();
1924
+ return;
1925
+ }
1926
+ const a = b.dataset.act;
1927
+ if (a === "copy-prompt") return void this.copyPrompt(b);
1928
+ if (a === "list") {
1929
+ this.listOpen = !this.listOpen;
1930
+ this.confirming = false;
1931
+ this.renderBar();
1932
+ } else if (a === "marks") {
1933
+ this.showMarks = !this.showMarks;
1934
+ write(this.keys.marks, this.showMarks);
1935
+ this.render();
1936
+ } else if (a === "clear-done") {
1937
+ const d = this.done();
1938
+ if (d.length) {
1939
+ this.remove(d.map((n) => n.id));
1940
+ this.flash(`Cleared ${plural(d.length, "finished note")}.`);
1941
+ }
1942
+ } else if (a === "confirm-clear") {
1943
+ this.confirming = true;
1944
+ this.renderBar();
1945
+ } else if (a === "keep") {
1946
+ this.confirming = false;
1947
+ this.renderBar();
1948
+ } else if (a === "clear-all") {
1949
+ const all = this.notes.map((n) => n.id);
1950
+ this.confirming = false;
1951
+ if (this.panel) this.closePanel();
1952
+ this.remove(all);
1953
+ this.flash(`Cleared ${plural(all.length, "note")}.`);
1954
+ } else if (a === "send") {
1955
+ const t = this.lastTarget();
1956
+ t ? this.send(t) : this.pick();
1957
+ } else if (a === "pick") this.pick();
1958
+ });
1959
+ this.bar.addEventListener("keydown", (e) => {
1960
+ const li = e.target.closest("li[data-note],li[data-target]");
1961
+ if (li && (e.key === "Enter" || e.key === " ")) {
1962
+ e.preventDefault();
1963
+ li.click();
1964
+ }
1965
+ });
1966
+ this.panelEl.addEventListener("click", (e) => {
1967
+ const b = e.target.closest("[data-act]");
1968
+ if (b?.dataset.act === "copy-prompt") return void this.copyPrompt(b);
1969
+ if (b && !b.disabled) this.panelAction(b.dataset.act);
1970
+ });
1971
+ this.panelEl.addEventListener("input", () => this.syncSave());
1972
+ const root = this.q(".root");
1973
+ const fromControls = (e) => e.composedPath().some((t) => t instanceof HTMLSlotElement);
1974
+ root.addEventListener("keydown", (e) => {
1975
+ if (fromControls(e)) return;
1976
+ const k = e;
1977
+ if (k.key === "Enter" && (k.metaKey || k.ctrlKey) && k.target.tagName === "TEXTAREA") {
1978
+ k.preventDefault();
1979
+ this.commitPanel();
1980
+ }
1981
+ if (k.key !== "Escape") e.stopPropagation();
1982
+ });
1983
+ for (const t of [
1984
+ "click",
1985
+ "dblclick",
1986
+ "mousedown",
1987
+ "mouseup",
1988
+ "pointerdown",
1989
+ "pointerup",
1990
+ "contextmenu",
1991
+ "keyup",
1992
+ "keypress",
1993
+ "input",
1994
+ "focusin",
1995
+ "focusout"
1996
+ ]) root.addEventListener(t, (e) => {
1997
+ if (!fromControls(e)) e.stopPropagation();
1998
+ });
1999
+ const launcher = this.launcher;
2000
+ launcher.addEventListener("pointerdown", (e) => {
2001
+ if (e.button !== 0) return;
2002
+ const { x, y } = this.launcherXY();
2003
+ this.drag = {
2004
+ sx: e.clientX,
2005
+ sy: e.clientY,
2006
+ bx: x,
2007
+ by: y,
2008
+ moved: false
2009
+ };
2010
+ launcher.setPointerCapture(e.pointerId);
2011
+ });
2012
+ launcher.addEventListener("pointermove", (e) => {
2013
+ const d = this.drag;
2014
+ if (!d) return;
2015
+ const dx = e.clientX - d.sx;
2016
+ const dy = e.clientY - d.sy;
2017
+ if (!d.moved && Math.hypot(dx, dy) < 4) return;
2018
+ d.moved = true;
2019
+ launcher.classList.add("placed");
2020
+ launcher.style.setProperty("--fx", String((d.bx + dx) / innerWidth));
2021
+ launcher.style.setProperty("--fy", String((d.by + dy) / innerHeight));
2022
+ if (this.open) this.placeBar();
2023
+ });
2024
+ launcher.addEventListener("pointerup", () => {
2025
+ const d = this.drag;
2026
+ if (!d) return;
2027
+ this.drag = null;
2028
+ if (d.moved) {
2029
+ const { x, y } = this.launcherXY();
2030
+ write(this.keys.pos, {
2031
+ fx: x / innerWidth,
2032
+ fy: y / innerHeight
2033
+ });
2034
+ this.placeLauncher();
2035
+ } else this.toggleOpen();
2036
+ });
2037
+ launcher.addEventListener("pointercancel", () => this.drag = null);
2038
+ launcher.addEventListener("dblclick", () => {
2039
+ write(this.keys.pos, null);
2040
+ this.placeLauncher();
2041
+ if (this.open) this.placeBar();
2042
+ });
2043
+ }
2044
+ drawRect() {
2045
+ const d = this.drawing;
2046
+ return {
2047
+ x: Math.min(d.x0, d.x1),
2048
+ y: Math.min(d.y0, d.y1),
2049
+ width: Math.abs(d.x1 - d.x0),
2050
+ height: Math.abs(d.y1 - d.y0)
2051
+ };
2052
+ }
2053
+ };
2054
+ function defaultNavigate(route) {
2055
+ history.pushState(null, "", route);
2056
+ dispatchEvent(new PopStateEvent("popstate", { state: null }));
2057
+ }
2058
+ var patched = false;
2059
+ function patchHistory() {
2060
+ if (patched) return;
2061
+ patched = true;
2062
+ for (const name of ["pushState", "replaceState"]) {
2063
+ const original = history[name];
2064
+ history[name] = function(...args) {
2065
+ const result = original.apply(this, args);
2066
+ dispatchEvent(new Event("annotate-anything:route"));
2067
+ return result;
2068
+ };
2069
+ }
2070
+ }
2071
+ // src/index.ts
2072
+ var current = null;
2073
+ function annotate(options = {}) {
2074
+ if (current) return current.handle;
2075
+ const allowed = options.enabled ?? LOCAL_HOST.test(location.hostname);
2076
+ const noop = {
2077
+ open() {},
2078
+ close() {},
2079
+ addControl() {},
2080
+ setTheme() {},
2081
+ destroy() {}
2082
+ };
2083
+ if (!allowed) {
2084
+ console.info("annotate-anything: not starting outside local development. Pass { enabled: true } to turn it on here.");
2085
+ return noop;
2086
+ }
2087
+ const state = {
2088
+ annotator: null,
2089
+ handle: noop
2090
+ };
2091
+ const queue = [];
2092
+ let pendingOpen;
2093
+ state.handle = {
2094
+ open: (o) => state.annotator ? state.annotator.openTool(o) : pendingOpen = o ?? {},
2095
+ close: () => state.annotator?.close(),
2096
+ addControl: (el) => state.annotator ? state.annotator.addControl(el) : queue.push(el),
2097
+ setTheme: (t) => state.annotator ? state.annotator.setTheme(t) : options.theme = t,
2098
+ destroy: () => {
2099
+ state.annotator?.destroy();
2100
+ current = null;
2101
+ }
2102
+ };
2103
+ const boot = () => {
2104
+ if (current !== state) return;
2105
+ state.annotator = new Annotator({
2106
+ ...options,
2107
+ controls: [...options.controls ?? [], ...queue]
2108
+ });
2109
+ if (pendingOpen) state.annotator.openTool(pendingOpen);
2110
+ };
2111
+ current = state;
2112
+ if (document.body) boot();
2113
+ else document.addEventListener("DOMContentLoaded", boot, { once: true });
2114
+ return state.handle;
2115
+ }
2116
+ export { markdown as a, local as i, clipboard as n, http as r, annotate as t };