@pasko70/pibo 1.4.1 → 1.4.3

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 (132) hide show
  1. package/README.md +183 -183
  2. package/context/codex-base-prompt.md +148 -148
  3. package/context/compute-worker.md +23 -23
  4. package/context/pibo-compaction-prompt.md +100 -100
  5. package/context/pibo-native-tooling.md +18 -18
  6. package/context/pibo-system-prompt.md +77 -77
  7. package/dist/apps/chat/agent-store.js +82 -82
  8. package/dist/apps/chat/data/project-service.js +166 -166
  9. package/dist/apps/chat/data/read-state-service.js +18 -18
  10. package/dist/apps/chat/data/timeline-query-service.js +8 -8
  11. package/dist/apps/chat/model-catalog.js +5 -1
  12. package/dist/apps/chat/static-assets.js +853 -853
  13. package/dist/apps/chat/workflow-persistence.js +255 -255
  14. package/dist/apps/chat-ui/assets/{dist-7YaJd19a.js → dist-B2BEpL7n.js} +1 -1
  15. package/dist/apps/chat-ui/assets/{dist-CiYeO8nN.js → dist-BAGS_xkV.js} +1 -1
  16. package/dist/apps/chat-ui/assets/{dist-BIvPnn_C.js → dist-BAXNalar.js} +1 -1
  17. package/dist/apps/chat-ui/assets/{dist-CjSI6y5z.js → dist-BwUvs6Ph.js} +1 -1
  18. package/dist/apps/chat-ui/assets/{dist-Ubstha8t.js → dist-C0zsJ8II.js} +1 -1
  19. package/dist/apps/chat-ui/assets/{dist-KXCMNKIL.js → dist-C3PnEkhb.js} +1 -1
  20. package/dist/apps/chat-ui/assets/{dist-CVQU42Fn.js → dist-CYPL-B2Z.js} +1 -1
  21. package/dist/apps/chat-ui/assets/{dist-BbNE72h8.js → dist-CiDSXgtg.js} +1 -1
  22. package/dist/apps/chat-ui/assets/{dist-D7TCkoFT.js → dist-DnACFKyO.js} +1 -1
  23. package/dist/apps/chat-ui/assets/{dist-ChSZNqKE.js → dist-ZB1-ui2y.js} +1 -1
  24. package/dist/apps/chat-ui/assets/{dist-8Noo5eCN.js → dist-vKlxFkTa.js} +1 -1
  25. package/dist/apps/chat-ui/assets/{index-CmxtUVG1.js → index-0x7tuTNX.js} +3 -3
  26. package/dist/apps/chat-ui/assets/{index-C25VYnyb.css → index-B-qaya1G.css} +1 -1
  27. package/dist/apps/chat-ui/index.html +18 -18
  28. package/dist/apps/chat-ui/manifest.webmanifest +25 -25
  29. package/dist/apps/chat-ui/sw.js +41 -41
  30. package/dist/apps/chat-vscode-web/index.html +12 -12
  31. package/dist/apps/cli-ui/cliSessionsCommand.js +23 -23
  32. package/dist/apps/context-files-ui/index.html +11 -11
  33. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  34. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.4.2.vsix +0 -0
  35. package/dist/apps/vscode-artifacts/{pibo-vscode-ext-1.4.1.vsix → pibo-vscode-ext-1.4.3.vsix} +0 -0
  36. package/dist/bin/pibo.js +0 -0
  37. package/dist/bin/rg.js +0 -0
  38. package/dist/cli.js +35 -35
  39. package/dist/compute/cli.js +54 -54
  40. package/dist/core/runtime.js +2 -0
  41. package/dist/core/session-router.js +1 -0
  42. package/dist/cron/cli.js +15 -15
  43. package/dist/cron/store.js +49 -49
  44. package/dist/data/cli.js +23 -23
  45. package/dist/data/event-log.js +23 -23
  46. package/dist/data/message-store.js +20 -20
  47. package/dist/data/navigation-store.js +9 -9
  48. package/dist/data/observation-store.js +4 -4
  49. package/dist/data/payload-store.js +17 -17
  50. package/dist/data/schema.js +429 -429
  51. package/dist/data/session-store.js +4 -4
  52. package/dist/data/telemetry-queries.js +54 -54
  53. package/dist/data/telemetry.js +156 -156
  54. package/dist/debug/events.js +12 -12
  55. package/dist/debug/failures.js +6 -6
  56. package/dist/debug/index.js +207 -207
  57. package/dist/debug/messages.js +6 -6
  58. package/dist/debug/pty.js +124 -124
  59. package/dist/debug/session.js +29 -29
  60. package/dist/debug/tools.js +5 -5
  61. package/dist/debug/web-snapshot-browser-scripts.js +294 -294
  62. package/dist/debug/web-streaming-browser-library.js +925 -925
  63. package/dist/debug/web-streaming-browser-scripts.js +232 -232
  64. package/dist/debug/web-streaming-provider-telemetry.js +4 -4
  65. package/dist/debug/web.js +93 -93
  66. package/dist/gateway/cli.js +19 -19
  67. package/dist/mcp/config-command.js +53 -53
  68. package/dist/mcp/errors.js +1 -1
  69. package/dist/mcp/index.js +21 -21
  70. package/dist/mcp/output.js +47 -7
  71. package/dist/mcp/registry.js +11 -11
  72. package/dist/pi-packages/cli.js +11 -11
  73. package/dist/plugins/context-files-store.js +110 -110
  74. package/dist/plugins/context-files.js +4 -4
  75. package/dist/providers/glm.js +59 -0
  76. package/dist/ralph/cli.js +18 -18
  77. package/dist/ralph/templates.js +140 -140
  78. package/dist/reliability/store.js +226 -226
  79. package/dist/sessions/pibo-data-store.js +16 -16
  80. package/dist/sessions/sqlite-store.js +53 -53
  81. package/dist/setup/cli.js +58 -58
  82. package/dist/tools/agent-browser-wrapper.js +80 -80
  83. package/dist/tools/browser-use-cdp.js +12 -12
  84. package/dist/tools/browser-use-wrapper.js +762 -762
  85. package/dist/tools/guides.js +455 -455
  86. package/dist/tools/index.js +99 -99
  87. package/dist/tools/runtime/node-worker-source.js +205 -205
  88. package/dist/tools/runtime/python-worker-source.js +177 -177
  89. package/dist/vscode/cli.js +9 -9
  90. package/dist/web-annotations/cdp.js +900 -900
  91. package/dist/web-annotations/store.js +96 -96
  92. package/docs/README.md +23 -23
  93. package/docs/ops/install-developer-host.md +112 -112
  94. package/docs/ops/install-user-host.md +96 -96
  95. package/docs/ops/upgrade-user-to-developer-host.md +69 -69
  96. package/docs/ops/vscode-extension-release.md +160 -160
  97. package/package.json +93 -93
  98. package/skills/builtin/pi-agent-harness/SKILL.md +319 -319
  99. package/skills/builtin/pi-agent-harness/agents/openai.yaml +4 -4
  100. package/skills/builtin/pibo-docker-system/SKILL.md +170 -170
  101. package/skills/builtin/pibo-spec-writing/SKILL.md +330 -330
  102. package/skills/builtin/prd/SKILL.md +143 -143
  103. package/skills/builtin/ralph-loop/SKILL.md +359 -359
  104. package/skills/builtin/ralph-prd-json/SKILL.md +123 -123
  105. package/skills/builtin/skill-creator/LICENSE.txt +201 -201
  106. package/skills/builtin/skill-creator/SKILL.md +513 -513
  107. package/skills/builtin/skill-creator/agents/analyzer.md +274 -274
  108. package/skills/builtin/skill-creator/agents/comparator.md +202 -202
  109. package/skills/builtin/skill-creator/agents/grader.md +223 -223
  110. package/skills/builtin/skill-creator/assets/eval_review.html +146 -146
  111. package/skills/builtin/skill-creator/eval-viewer/generate_review.py +471 -471
  112. package/skills/builtin/skill-creator/eval-viewer/viewer.html +1325 -1325
  113. package/skills/builtin/skill-creator/references/schemas.md +430 -430
  114. package/skills/builtin/skill-creator/scripts/aggregate_benchmark.py +401 -401
  115. package/skills/builtin/skill-creator/scripts/generate_report.py +326 -326
  116. package/skills/builtin/skill-creator/scripts/improve_description.py +247 -247
  117. package/skills/builtin/skill-creator/scripts/package_skill.py +136 -136
  118. package/skills/builtin/skill-creator/scripts/quick_validate.py +102 -102
  119. package/skills/builtin/skill-creator/scripts/run_eval.py +310 -310
  120. package/skills/builtin/skill-creator/scripts/run_loop.py +328 -328
  121. package/skills/builtin/skill-creator/scripts/utils.py +47 -47
  122. package/skills/builtin/web-annotations/SKILL.md +93 -93
  123. package/src/mcp/LICENSE.mcp-cli +21 -21
  124. package/dist/apps/vscode-artifacts/pibo-vscode-1.3.0.vsix +0 -0
  125. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.3.3.vsix +0 -0
  126. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.3.4.vsix +0 -0
  127. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.3.5.vsix +0 -0
  128. package/dist/core/shared-app.js +0 -17
  129. package/dist/data/final-app-space-cutover-migration.js +0 -728
  130. package/dist/data/shared-app-migration.js +0 -757
  131. package/dist/session-ui/ownerViewModel.js +0 -27
  132. package/dist/shared-app.js +0 -4
@@ -194,914 +194,914 @@ function createOverlaySubmissionToken() {
194
194
  return randomBytes(24).toString("base64url");
195
195
  }
196
196
  function buildDocumentReadyExpression() {
197
- return String.raw `new Promise((resolve) => {
198
- if (document.documentElement) {
199
- resolve(true);
200
- return;
201
- }
202
- const done = () => resolve(Boolean(document.documentElement));
203
- document.addEventListener("DOMContentLoaded", done, { once: true });
204
- setTimeout(done, 2000);
197
+ return String.raw `new Promise((resolve) => {
198
+ if (document.documentElement) {
199
+ resolve(true);
200
+ return;
201
+ }
202
+ const done = () => resolve(Boolean(document.documentElement));
203
+ document.addEventListener("DOMContentLoaded", done, { once: true });
204
+ setTimeout(done, 2000);
205
205
  })`;
206
206
  }
207
207
  function buildInjectExpression(config) {
208
208
  return buildWebAnnotationOverlayScript(JSON.stringify(config));
209
209
  }
210
210
  export function buildWebAnnotationOverlayScript(configExpression = "window.__piboWebAnnotationConfig") {
211
- return String.raw `(() => {
212
- const config = (${configExpression});
213
- if (!config || !config.bindingId || !config.bindingToken) throw new Error("Pibo Web Annotation overlay config is missing bindingId or bindingToken");
214
- const rootId = "pibo-web-annotation-overlay";
215
- const outlineId = "pibo-web-annotation-outline";
216
- const shortcutStorageKey = "pibo.chat.shortcuts.webAnnotationsToggle";
217
- const overlayStateEventName = "pibo:web-annotation-overlay-state";
218
- const overlayStateStoragePrefix = "pibo.chat.webAnnotations.overlay.";
219
- const defaultAnnotationShortcut = "Alt+Shift+A";
220
- const previous = window.__piboWebAnnotations;
221
- if (previous && typeof previous.remove === "function") previous.remove();
222
-
223
- const caps = {
224
- note: 2000,
225
- text: 500,
226
- htmlHint: 400,
227
- classSummary: 240,
228
- accessibility: 240,
229
- selector: 500,
230
- domPath: 700,
231
- sourceRaw: 800,
232
- };
233
- const state = {
234
- active: false,
235
- mode: "element",
236
- hovered: null,
237
- popup: null,
238
- lastHoverAt: 0,
239
- pendingFrame: 0,
240
- dragMoved: false,
241
- toolbarExpanded: false,
242
- shortcut: parseShortcut(config.annotationShortcut || readStoredShortcut() || defaultAnnotationShortcut),
243
- submissions: [],
244
- lastError: null,
245
- };
246
-
247
- function publishOverlayState(reason, installed) {
248
- const piboSessionId = String(config.piboSessionId || "");
249
- const detail = {
250
- bindingId: String(config.bindingId || ""),
251
- piboSessionId,
252
- installed: installed !== false,
253
- active: installed !== false && Boolean(state.active),
254
- toolbarExpanded: installed !== false && Boolean(state.toolbarExpanded),
255
- mode: state.mode,
256
- reason: String(reason || "state"),
257
- updatedAt: new Date().toISOString(),
258
- };
259
- if (piboSessionId) {
260
- try { window.localStorage && window.localStorage.setItem(overlayStateStoragePrefix + piboSessionId, JSON.stringify(detail)); } catch {}
261
- }
262
- try { window.dispatchEvent(new CustomEvent(overlayStateEventName, { detail })); } catch {}
263
- }
264
-
265
- function onPageHide() {
266
- state.active = false;
267
- publishOverlayState("pagehide", false);
268
- }
269
-
270
- const host = document.createElement("div");
271
- host.id = rootId;
272
- host.setAttribute("data-pibo-web-annotation-binding", config.bindingId);
273
- host.style.cssText = "position:fixed;right:16px;bottom:calc(16px + env(safe-area-inset-bottom));z-index:2147483647;pointer-events:auto;color-scheme:light dark";
274
- const shadow = host.attachShadow ? host.attachShadow({ mode: "open" }) : host;
275
- const style = document.createElement("style");
276
- style.textContent = [
277
- ":host{all:initial;font:13px system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}",
278
- "@keyframes pibo-wa-attention{0%{transform:scale(.72);opacity:.45;box-shadow:0 0 0 0 rgba(56,189,248,.7)}18%{transform:scale(1.22,.82);opacity:1}34%{transform:scale(.9,1.12)}52%{transform:scale(1.08,.94);box-shadow:0 0 0 12px rgba(56,189,248,.18)}72%{transform:scale(.98,1.02)}100%{transform:scale(1);opacity:1;box-shadow:0 12px 32px rgba(0,0,0,.36),0 0 0 0 rgba(56,189,248,0)}}",
279
- ".pibo-wa-panel{position:relative;width:50px;min-height:50px;color:#fff;font:13px system-ui,sans-serif;touch-action:none}",
280
- ".pibo-wa-main,.pibo-wa-button{box-sizing:border-box;width:48px;height:48px;display:inline-flex;align-items:center;justify-content:center;border:1px solid rgba(148,163,184,.5);background:#111827;color:#dbeafe;border-radius:14px;box-shadow:0 12px 32px rgba(0,0,0,.36);cursor:pointer;user-select:none;line-height:0;padding:0;transition:background .12s ease,border-color .12s ease,color .12s ease,transform .12s ease;-webkit-tap-highlight-color:transparent}",
281
- ".pibo-wa-main{border-color:#38bdf8;background:linear-gradient(180deg,#102334,#0f172a);color:#7dd3fc;cursor:grab}",
282
- ".pibo-wa-main:active{cursor:grabbing}",
283
- ".pibo-wa-main.pibo-wa-attention{animation:pibo-wa-attention .78s cubic-bezier(.2,.8,.2,1) both}",
284
- "@media (prefers-reduced-motion:reduce){.pibo-wa-main.pibo-wa-attention{animation:none;box-shadow:0 0 0 3px rgba(56,189,248,.6),0 12px 32px rgba(0,0,0,.36)}}",
285
- ".pibo-wa-main:hover,.pibo-wa-button:hover{border-color:#7dd3fc;color:#e0f2fe;background:#172033;transform:translateY(-1px)}",
286
- ".pibo-wa-icon{width:22px;height:22px;display:block;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;flex:0 0 auto}",
287
- ".pibo-wa-actions{position:absolute;right:-2px;bottom:42px;display:flex;flex-direction:column;gap:7px;padding:0 2px 12px 2px;opacity:0;pointer-events:none;transform:translateY(6px);transition:opacity .12s ease,transform .12s ease}",
288
- ".pibo-wa-actions::before{content:'';position:absolute;inset:0 -8px -12px -8px;z-index:-1}",
289
- ".pibo-wa-panel:hover .pibo-wa-actions,.pibo-wa-panel:focus-within .pibo-wa-actions,.pibo-wa-panel.pibo-wa-active .pibo-wa-actions,.pibo-wa-panel.pibo-wa-expanded .pibo-wa-actions{opacity:1;pointer-events:auto;transform:translateY(0)}",
290
- "@media (hover:none),(pointer:coarse){.pibo-wa-panel{width:54px;min-height:54px}.pibo-wa-main,.pibo-wa-button{width:52px;height:52px;border-radius:16px}.pibo-wa-actions,.pibo-wa-panel:hover .pibo-wa-actions,.pibo-wa-panel:focus-within .pibo-wa-actions,.pibo-wa-panel.pibo-wa-active .pibo-wa-actions{right:-1px;bottom:48px;gap:8px;opacity:0;pointer-events:none;transform:translateY(6px)}.pibo-wa-panel.pibo-wa-expanded .pibo-wa-actions{opacity:1;pointer-events:auto;transform:none}.pibo-wa-main:hover,.pibo-wa-button:hover{transform:none}}",
291
- ".pibo-wa-button{background:#1f2937}",
292
- ".pibo-wa-button[aria-pressed='true']{background:#2563eb;border-color:#93c5fd;color:#fff}",
293
- ".pibo-wa-button-danger{background:#3f1418;border-color:#7f1d1d;color:#fecaca}",
294
- ".pibo-wa-button-danger:hover{border-color:#ef4444;color:#fff;background:#7f1d1d}",
295
- ".pibo-wa-status{display:none}",
296
- ".pibo-wa-popup{box-sizing:border-box;position:fixed;z-index:2147483647;width:min(320px,calc(100vw - 24px));max-height:calc(100svh - 24px);overflow:auto;background:#fff;color:#111827;border:1px solid #9ca3af;border-radius:12px;padding:10px;box-shadow:0 14px 36px rgba(0,0,0,.35);font:13px system-ui,sans-serif;overscroll-behavior:contain}",
297
- ".pibo-wa-row{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:8px}",
298
- ".pibo-wa-popup textarea{box-sizing:border-box;width:100%;min-height:76px;margin:8px 0;border:1px solid #9ca3af;border-radius:8px;padding:8px;font:13px system-ui,sans-serif;color:#111827;background:#fff}",
299
- "@media (max-width:480px){.pibo-wa-popup{width:calc(100vw - 24px);padding:12px}.pibo-wa-popup textarea{min-height:96px;font-size:16px}.pibo-wa-row .pibo-wa-button{width:auto;min-width:92px;height:40px;border-radius:10px;padding:0 12px;line-height:1}}",
300
- ".pibo-wa-popup-label{font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
301
- ].join("");
302
- const panel = document.createElement("div");
303
- panel.className = "pibo-wa-panel";
304
- const actions = document.createElement("div");
305
- actions.className = "pibo-wa-actions";
306
- const toggle = button(iconSvg("edit"), "Toggle element annotation mode");
307
- const pin = button(iconSvg("arrow-down-to-dot"), "Mark a point instead of an element");
308
- const stop = button(iconSvg("x"), "Remove the Pibo annotation overlay");
309
- stop.className += " pibo-wa-button-danger";
310
- const main = document.createElement("button");
311
- main.type = "button";
312
- main.className = "pibo-wa-main";
313
- main.innerHTML = iconSvg("book-a");
314
- main.classList.add("pibo-wa-attention");
315
- main.setAttribute("aria-label", shortcutTitle("Enable element annotation mode"));
316
- main.title = shortcutTitle("Enable element annotation mode");
317
- const status = document.createElement("div");
318
- status.className = "pibo-wa-status";
319
- actions.append(toggle, pin, stop);
320
- panel.append(actions, main, status);
321
- shadow.append(style, panel);
322
- document.documentElement.appendChild(host);
323
-
324
- const outline = document.createElement("div");
325
- outline.id = outlineId;
326
- outline.style.cssText = "position:fixed;z-index:2147483646;pointer-events:none;border:2px solid #2563eb;background:rgba(37,99,235,.10);box-shadow:0 0 0 9999px rgba(37,99,235,.04);display:none;border-radius:3px";
327
- document.documentElement.appendChild(outline);
328
-
329
- main.addEventListener("pointerdown", startDrag);
330
- main.addEventListener("click", () => {
331
- if (state.dragMoved) {
332
- state.dragMoved = false;
333
- return;
334
- }
335
- if (isCoarsePointer()) {
336
- state.toolbarExpanded = !state.toolbarExpanded;
337
- updateUi();
338
- publishOverlayState("toolbar-toggle");
339
- return;
340
- }
341
- state.active = true;
342
- state.mode = "element";
343
- closePopup();
344
- updateUi();
345
- publishOverlayState("main-button");
346
- });
347
- main.addEventListener("animationend", () => main.classList.remove("pibo-wa-attention"));
348
- toggle.addEventListener("click", () => {
349
- state.active = !state.active;
350
- state.mode = "element";
351
- state.toolbarExpanded = !state.active;
352
- closePopup();
353
- updateUi();
354
- publishOverlayState("toggle");
355
- });
356
- pin.addEventListener("click", () => {
357
- state.active = true;
358
- state.mode = state.mode === "pin" ? "element" : "pin";
359
- state.toolbarExpanded = false;
360
- closePopup();
361
- updateUi();
362
- publishOverlayState("pin-toggle");
363
- });
364
- stop.addEventListener("click", () => api.remove());
365
-
366
- function button(content, aria) {
367
- const el = document.createElement("button");
368
- el.type = "button";
369
- el.className = "pibo-wa-button";
370
- el.innerHTML = content;
371
- el.setAttribute("aria-label", aria);
372
- el.title = aria;
373
- return el;
374
- }
375
-
376
- function iconSvg(name) {
377
- const attrs = "class=\"pibo-wa-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\"";
378
- if (name === "book-a") return "<svg " + attrs + "><path d=\"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20\"/><path d=\"m8 13 4-7 4 7\"/><path d=\"M9.1 11h5.7\"/></svg>";
379
- if (name === "arrow-down-to-dot") return "<svg " + attrs + "><path d=\"M12 2v14\"/><path d=\"m19 9-7 7-7-7\"/><circle cx=\"12\" cy=\"21\" r=\"1\"/></svg>";
380
- if (name === "x") return "<svg " + attrs + "><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg>";
381
- return "<svg " + attrs + "><path d=\"M12 20h9\"/><path d=\"M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z\"/></svg>";
382
- }
383
-
384
- function startDrag(event) {
385
- if (event.button !== undefined && event.button !== 0) return;
386
- const startX = event.clientX;
387
- const startY = event.clientY;
388
- const rect = host.getBoundingClientRect();
389
- const originLeft = rect.left;
390
- const originTop = rect.top;
391
- state.dragMoved = false;
392
- if (main.setPointerCapture) main.setPointerCapture(event.pointerId);
393
- const onMove = (nextEvent) => {
394
- const dx = nextEvent.clientX - startX;
395
- const dy = nextEvent.clientY - startY;
396
- if (!state.dragMoved && Math.hypot(dx, dy) < 4) return;
397
- state.dragMoved = true;
398
- nextEvent.preventDefault();
399
- const viewport = viewportMetrics();
400
- const left = clamp(originLeft + dx, viewport.left + 8, viewport.left + viewport.width - rect.width - 8);
401
- const top = clamp(originTop + dy, viewport.top + 8, viewport.top + viewport.height - rect.height - 8);
402
- host.style.left = left + "px";
403
- host.style.top = top + "px";
404
- host.style.right = "auto";
405
- host.style.bottom = "auto";
406
- };
407
- const onUp = () => {
408
- window.removeEventListener("pointermove", onMove, true);
409
- window.removeEventListener("pointerup", onUp, true);
410
- window.removeEventListener("pointercancel", onUp, true);
411
- ensureHostInViewport();
412
- };
413
- window.addEventListener("pointermove", onMove, true);
414
- window.addEventListener("pointerup", onUp, true);
415
- window.addEventListener("pointercancel", onUp, true);
416
- }
417
-
418
- function updateUi(message) {
419
- const title = isCoarsePointer() ? (state.toolbarExpanded ? "Hide annotation controls" : "Show annotation controls") : shortcutTitle("Enable element annotation mode");
420
- main.setAttribute("aria-label", title);
421
- main.setAttribute("aria-expanded", state.toolbarExpanded ? "true" : "false");
422
- main.title = title;
423
- toggle.title = shortcutTitle("Toggle element annotation mode");
424
- toggle.setAttribute("aria-label", shortcutTitle("Toggle element annotation mode"));
425
- toggle.setAttribute("aria-pressed", state.active && state.mode === "element" ? "true" : "false");
426
- pin.setAttribute("aria-pressed", state.active && state.mode === "pin" ? "true" : "false");
427
- panel.classList.toggle("pibo-wa-active", state.active);
428
- panel.classList.toggle("pibo-wa-expanded", state.toolbarExpanded);
429
- outline.style.display = state.active && state.mode === "element" && state.hovered ? "block" : "none";
430
- status.textContent = message || (state.active ? (state.mode === "pin" ? "Pin mode: click anywhere to mark a point." : "Element mode: hover and click a visible target.") : "Inactive: page clicks and typing pass through normally.");
431
- }
432
-
433
- function isCoarsePointer() {
434
- return Boolean(window.matchMedia && window.matchMedia("(hover: none), (pointer: coarse)").matches);
435
- }
436
-
437
- function readStoredShortcut() {
438
- try { return window.localStorage && window.localStorage.getItem(shortcutStorageKey); } catch { return ""; }
439
- }
440
-
441
- function parseShortcut(value) {
442
- const label = String(value || defaultAnnotationShortcut).trim() || defaultAnnotationShortcut;
443
- const parts = label.split("+").map((part) => part.trim()).filter(Boolean);
444
- const shortcut = { alt: false, ctrl: false, meta: false, shift: false, key: "a", label: defaultAnnotationShortcut };
445
- for (const part of parts) {
446
- const lower = part.toLowerCase();
447
- if (lower === "alt" || lower === "option") shortcut.alt = true;
448
- else if (lower === "ctrl" || lower === "control") shortcut.ctrl = true;
449
- else if (lower === "cmd" || lower === "command" || lower === "meta") shortcut.meta = true;
450
- else if (lower === "shift") shortcut.shift = true;
451
- else shortcut.key = normalizeShortcutKey(part);
452
- }
453
- shortcut.label = shortcutLabel(shortcut);
454
- return shortcut;
455
- }
456
-
457
- function normalizeShortcutKey(value) {
458
- const key = String(value || "a").trim();
459
- if (key === " ") return "Space";
460
- if (key.length === 1) return key.toLowerCase();
461
- return key.toLowerCase() === "space" ? "Space" : key;
462
- }
463
-
464
- function shortcutLabel(shortcut) {
465
- const parts = [];
466
- if (shortcut.ctrl) parts.push("Ctrl");
467
- if (shortcut.alt) parts.push("Alt");
468
- if (shortcut.shift) parts.push("Shift");
469
- if (shortcut.meta) parts.push("Meta");
470
- parts.push(shortcut.key.length === 1 ? shortcut.key.toUpperCase() : shortcut.key);
471
- return parts.join("+");
472
- }
473
-
474
- function shortcutTitle(label) {
475
- return label + " (" + state.shortcut.label + ")";
476
- }
477
-
478
- function matchesShortcut(event) {
479
- const key = normalizeShortcutKey(event.key || "");
480
- const expected = String(state.shortcut.key);
481
- const keyMatches = key.toLowerCase() === expected.toLowerCase()
482
- || (expected.length === 1 && event.code === "Key" + expected.toUpperCase());
483
- return keyMatches
484
- && Boolean(event.altKey) === state.shortcut.alt
485
- && Boolean(event.ctrlKey) === state.shortcut.ctrl
486
- && Boolean(event.metaKey) === state.shortcut.meta
487
- && Boolean(event.shiftKey) === state.shortcut.shift;
488
- }
489
-
490
- function setShortcut(value) {
491
- state.shortcut = parseShortcut(value || readStoredShortcut() || defaultAnnotationShortcut);
492
- updateUi();
493
- }
494
-
495
- function onKeyDown(event) {
496
- if (event.defaultPrevented || event.repeat || !matchesShortcut(event)) return;
497
- event.preventDefault();
498
- event.stopPropagation();
499
- state.active = !(state.active && state.mode === "element");
500
- state.mode = "element";
501
- closePopup();
502
- updateUi(state.active ? "Element annotation mode enabled from shortcut." : "Annotation mode disabled from shortcut.");
503
- publishOverlayState("shortcut");
504
- }
505
-
506
- function onShortcutChanged(event) {
507
- setShortcut(event && event.detail && event.detail.shortcut);
508
- }
509
-
510
- function onMouseMove(event) {
511
- if (!state.active || state.mode !== "element" || isOverlayEvent(event)) return;
512
- const now = Date.now();
513
- if (now - state.lastHoverAt < 80) return;
514
- state.lastHoverAt = now;
515
- if (state.pendingFrame) cancelAnimationFrame(state.pendingFrame);
516
- state.pendingFrame = requestAnimationFrame(() => {
517
- state.pendingFrame = 0;
518
- const target = eventTargetElement(event);
519
- if (!target) {
520
- clearHover();
521
- return;
522
- }
523
- const rect = target.getBoundingClientRect();
524
- if (!isUsableRect(rect)) {
525
- clearHover();
526
- return;
527
- }
528
- state.hovered = target;
529
- outline.style.left = rect.left + "px";
530
- outline.style.top = rect.top + "px";
531
- outline.style.width = rect.width + "px";
532
- outline.style.height = rect.height + "px";
533
- outline.style.display = "block";
534
- });
535
- }
536
-
537
- function onClick(event) {
538
- if (!state.active || isOverlayEvent(event)) return;
539
- if (state.mode === "pin") {
540
- event.preventDefault();
541
- event.stopPropagation();
542
- openNote("pin", buildPinTarget(event), { x: event.clientX, y: event.clientY });
543
- return;
544
- }
545
- const target = eventTargetElement(event);
546
- if (!target || !isUsableRect(target.getBoundingClientRect())) return;
547
- event.preventDefault();
548
- event.stopPropagation();
549
- state.hovered = target;
550
- openNote("element", buildElementTarget(target), rectAnchor(target.getBoundingClientRect()));
551
- }
552
-
553
- function isOverlayEvent(event) {
554
- const path = typeof event.composedPath === "function" ? event.composedPath() : [];
555
- return path.includes(host) || path.includes(panel) || (state.popup && path.includes(state.popup));
556
- }
557
-
558
- function eventTargetElement(event) {
559
- const path = typeof event.composedPath === "function" ? event.composedPath() : [];
560
- for (const item of path) {
561
- if (item && item.nodeType === 1 && item !== host && item !== outline && item !== document.documentElement && item !== document.body) return item;
562
- }
563
- const target = event.target;
564
- return target && target.nodeType === 1 ? target : null;
565
- }
566
-
567
- function clearHover() {
568
- state.hovered = null;
569
- outline.style.display = "none";
570
- }
571
-
572
- function rectAnchor(rect) {
573
- const viewport = viewportMetrics();
574
- return {
575
- x: clamp(rect.left, viewport.left + 12, viewport.left + viewport.width - 12),
576
- y: clamp(rect.bottom + 8, viewport.top + 12, viewport.top + viewport.height - 12),
577
- };
578
- }
579
-
580
- function viewportMetrics() {
581
- const visual = window.visualViewport;
582
- return {
583
- left: visual && Number.isFinite(visual.offsetLeft) ? visual.offsetLeft : 0,
584
- top: visual && Number.isFinite(visual.offsetTop) ? visual.offsetTop : 0,
585
- width: Math.max(0, visual && Number.isFinite(visual.width) ? visual.width : (window.innerWidth || document.documentElement.clientWidth || 0)),
586
- height: Math.max(0, visual && Number.isFinite(visual.height) ? visual.height : (window.innerHeight || document.documentElement.clientHeight || 0)),
587
- };
588
- }
589
-
590
- function clamp(value, min, max) {
591
- const upper = Math.max(min, max);
592
- return Math.max(min, Math.min(upper, value));
593
- }
594
-
595
- function positionPopup(popup, anchor) {
596
- const viewport = viewportMetrics();
597
- const margin = 12;
598
- const rect = popup.getBoundingClientRect();
599
- const width = Math.min(rect.width || 320, Math.max(0, viewport.width - margin * 2));
600
- const height = Math.min(rect.height || 180, Math.max(0, viewport.height - margin * 2));
601
- popup.style.left = clamp(anchor.x, viewport.left + margin, viewport.left + viewport.width - width - margin) + "px";
602
- popup.style.top = clamp(anchor.y, viewport.top + margin, viewport.top + viewport.height - height - margin) + "px";
603
- }
604
-
605
- function ensureHostInViewport() {
606
- const rect = host.getBoundingClientRect();
607
- if (!rect.width || !rect.height) return;
608
- const viewport = viewportMetrics();
609
- const left = clamp(rect.left, viewport.left + 8, viewport.left + viewport.width - rect.width - 8);
610
- const top = clamp(rect.top, viewport.top + 8, viewport.top + viewport.height - rect.height - 8);
611
- if (Math.abs(left - rect.left) > 1 || Math.abs(top - rect.top) > 1) {
612
- host.style.left = left + "px";
613
- host.style.top = top + "px";
614
- host.style.right = "auto";
615
- host.style.bottom = "auto";
616
- }
617
- }
618
-
619
- function onViewportChange() {
620
- ensureHostInViewport();
621
- if (state.popup && state.popup.__piboAnchor) positionPopup(state.popup, state.popup.__piboAnchor);
622
- }
623
-
624
- function isUsableRect(rect) {
625
- return rect && rect.width > 1 && rect.height > 1 && rect.bottom >= 0 && rect.right >= 0 && rect.left <= window.innerWidth && rect.top <= window.innerHeight;
626
- }
627
-
628
- function openNote(kind, target, anchor) {
629
- closePopup();
630
- clearHover();
631
- const popup = document.createElement("div");
632
- popup.className = "pibo-wa-popup";
633
- popup.__piboAnchor = anchor;
634
- const label = document.createElement("div");
635
- label.className = "pibo-wa-popup-label";
636
- label.textContent = kind === "pin" ? "Pin annotation" : (target.label || target.selector || "Element annotation");
637
- const textarea = document.createElement("textarea");
638
- textarea.placeholder = "What should the agent know about this target?";
639
- textarea.maxLength = caps.note;
640
- const actions = document.createElement("div");
641
- actions.className = "pibo-wa-row";
642
- const submit = button("Submit", "Submit annotation");
643
- const cancel = button("Cancel", "Cancel annotation");
644
- actions.append(submit, cancel);
645
- popup.append(label, textarea, actions);
646
- shadow.append(popup);
647
- state.popup = popup;
648
- state.active = false;
649
- positionPopup(popup, anchor);
650
- updateUi("Add a note, submit, or cancel.");
651
- textarea.focus({ preventScroll: true });
652
- cancel.addEventListener("click", () => {
653
- closePopup();
654
- state.active = true;
655
- updateUi("Cancelled. Annotation mode is active.");
656
- });
657
- submit.addEventListener("click", async () => {
658
- const note = cap(textarea.value.trim(), caps.note);
659
- if (!note) {
660
- updateUi("Note is required before submitting.");
661
- textarea.focus();
662
- return;
663
- }
664
- submit.disabled = true;
665
- const payload = buildPayload(kind, target, note);
666
- state.submissions.push(payload);
667
- try {
668
- const response = await fetch(apiUrl() + "/submissions", {
669
- method: "POST",
670
- mode: "cors",
671
- headers: { "content-type": "application/json" },
672
- body: JSON.stringify(payload),
673
- });
674
- if (!response.ok) throw new Error("HTTP " + response.status);
675
- const data = await response.json().catch(() => ({}));
676
- state.lastError = null;
677
- try { window.dispatchEvent(new CustomEvent("pibo:web-annotation-saved", { detail: data })); } catch {}
678
- closePopup();
679
- state.active = true;
680
- updateUi(data && data.annotation && data.annotation.id ? "Saved " + data.annotation.id + "." : "Saved annotation.");
681
- } catch (error) {
682
- state.lastError = error && error.message ? error.message : String(error);
683
- submit.disabled = false;
684
- updateUi("Submission failed: " + cap(state.lastError, 120));
685
- }
686
- });
687
- }
688
-
689
- function closePopup() {
690
- if (state.popup) state.popup.remove();
691
- state.popup = null;
692
- }
693
-
694
- function apiUrl() {
695
- const base = config.apiBaseUrl || window.location.origin;
696
- return base.replace(/\/$/, "") + "/api/web-annotations";
697
- }
698
-
699
- function buildPayload(kind, target, note) {
700
- return {
701
- bindingId: config.bindingId,
702
- bindingToken: config.bindingToken,
703
- note,
704
- url: cap(window.location.href, 2000),
705
- title: cap(document.title || "", 200),
706
- targetKind: kind,
707
- viewport: {
708
- width: Math.max(0, Math.round(window.innerWidth || document.documentElement.clientWidth || 0)),
709
- height: Math.max(0, Math.round(window.innerHeight || document.documentElement.clientHeight || 0)),
710
- devicePixelRatio: Number(window.devicePixelRatio || 1),
711
- },
712
- target,
713
- };
714
- }
715
-
716
- function buildPinTarget(event) {
717
- return boundedTarget({
718
- kind: "pin",
719
- label: "pin " + Math.round(event.clientX) + "," + Math.round(event.clientY),
720
- position: { x: Math.round(event.clientX), y: Math.round(event.clientY) },
721
- sourceHints: [{ kind: "dom-fallback", confidence: "low", id: "pin", raw: { url: cap(location.pathname, 200) } }],
722
- });
723
- }
724
-
725
- function buildElementTarget(element) {
726
- const rect = element.getBoundingClientRect();
727
- const selector = bestSelector(element);
728
- const selectedText = getSelectedText();
729
- const tagName = (element.tagName || "").toLowerCase();
730
- const target = {
731
- kind: "element",
732
- label: cap(labelFor(element), caps.text),
733
- selector,
734
- domPath: cap(domPath(element, false), caps.domPath),
735
- fullDomPath: cap(domPath(element, true), caps.domPath),
736
- tagName,
737
- stableId: stableId(element),
738
- classSummary: cap(classSummary(element), caps.classSummary),
739
- text: cap((element.innerText || element.textContent || "").replace(/\s+/g, " ").trim(), caps.text),
740
- selectedText: selectedText || undefined,
741
- htmlHint: cap(openingTagHint(element), caps.htmlHint),
742
- accessibility: accessibilityHint(element),
743
- boundingBox: { x: Math.round(rect.left), y: Math.round(rect.top), width: Math.round(rect.width), height: Math.round(rect.height) },
744
- sourceHints: sourceHints(element, selector),
745
- };
746
- if (tagName === "iframe") {
747
- target.accessibility = { ...(target.accessibility || {}), description: "Iframe contents are unavailable to the top-level annotation overlay when cross-origin." };
748
- }
749
- return boundedTarget(target);
750
- }
751
-
752
- function boundedTarget(target) {
753
- if (target.selector) target.selector = cap(target.selector, caps.selector);
754
- if (target.domPath) target.domPath = cap(target.domPath, caps.domPath);
755
- if (target.fullDomPath) target.fullDomPath = cap(target.fullDomPath, caps.domPath);
756
- if (target.text) target.text = cap(target.text, caps.text);
757
- if (target.selectedText) target.selectedText = cap(target.selectedText, caps.text);
758
- if (target.htmlHint) target.htmlHint = cap(target.htmlHint, caps.htmlHint);
759
- if (target.classSummary) target.classSummary = cap(target.classSummary, caps.classSummary);
760
- if (target.accessibility && target.accessibility.description) target.accessibility.description = cap(target.accessibility.description, caps.accessibility);
761
- if (Array.isArray(target.sourceHints)) target.sourceHints = target.sourceHints.slice(0, 8).map(boundSourceHint);
762
- return target;
763
- }
764
-
765
- function boundSourceHint(hint) {
766
- const next = { ...hint };
767
- if (next.id) next.id = cap(String(next.id), 160);
768
- if (next.file) next.file = cap(String(next.file), 300);
769
- if (next.component) next.component = cap(String(next.component), 160);
770
- if (Array.isArray(next.componentPath)) next.componentPath = next.componentPath.slice(0, 12).map((item) => cap(String(item), 120));
771
- if (next.raw) next.raw = truncateRaw(next.raw);
772
- return next;
773
- }
774
-
775
- function truncateRaw(raw) {
776
- try {
777
- return JSON.parse(cap(JSON.stringify(raw), caps.sourceRaw));
778
- } catch {
779
- return { truncated: true };
780
- }
781
- }
782
-
783
- function cap(value, max) {
784
- if (value === undefined || value === null) return undefined;
785
- const text = String(value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
786
- return text.length > max ? text.slice(0, max) : text;
787
- }
788
-
789
- function getSelectedText() {
790
- const selection = window.getSelection && window.getSelection();
791
- const text = selection ? selection.toString().replace(/\s+/g, " ").trim() : "";
792
- return text ? cap(text, caps.text) : undefined;
793
- }
794
-
795
- function cssEscape(value) {
796
- if (window.CSS && typeof window.CSS.escape === "function") return window.CSS.escape(value);
797
- return String(value).replace(/[^a-zA-Z0-9_-]/g, (char) => "\\" + char);
798
- }
799
-
800
- function stableId(element) {
801
- const attrs = ["data-pibo-id", "data-pibo-component", "data-pibo-debug", "data-shared-terminal-card", "data-shared-status-field", "data-pibo-markdown-node", "data-testid", "data-test-id", "data-cy", "data-qa", "data-locatorjs-id", "id", "aria-label"];
802
- let current = element;
803
- while (current && current.nodeType === 1) {
804
- for (const attr of attrs) {
805
- const value = current.getAttribute && current.getAttribute(attr);
806
- if (value && value.trim()) return attr + ":" + cap(value.trim(), 160);
807
- }
808
- const terminalRow = current.getAttribute && current.getAttribute("data-pibo-terminal-row");
809
- if (terminalRow) {
810
- const rowId = current.getAttribute("data-row-id") || current.getAttribute("data-row-kind") || terminalRow;
811
- return "data-pibo-terminal-row:" + cap(rowId, 160);
812
- }
813
- current = current.parentElement || (current.getRootNode && current.getRootNode().host) || null;
814
- }
815
- return undefined;
816
- }
817
-
818
- function bestSelector(element) {
819
- const direct = stableSelectorPart(element);
820
- if (direct && direct.unique) return direct.selector;
821
- const chain = [];
822
- let current = element;
823
- while (current && current.nodeType === 1 && current !== document.documentElement) {
824
- const part = stableSelectorPart(current);
825
- chain.unshift(part.selector);
826
- const combined = chain.join(" > ");
827
- if (safeQueryCount(combined) === 1) return combined;
828
- current = current.parentElement || (current.getRootNode && current.getRootNode().host) || null;
829
- }
830
- return chain.length ? chain.join(" > ") : undefined;
831
- }
832
-
833
- function stableSelectorPart(element) {
834
- const tag = (element.tagName || "*").toLowerCase();
835
- const id = element.getAttribute && element.getAttribute("id");
836
- if (id) {
837
- const selector = "#" + cssEscape(id);
838
- return { selector, unique: safeQueryCount(selector) === 1 };
839
- }
840
- for (const attr of ["data-pibo-id", "data-pibo-component", "data-pibo-debug", "data-shared-terminal-card", "data-shared-status-field", "data-pibo-markdown-node", "data-testid", "data-test-id", "data-cy", "data-qa", "data-locatorjs-id", "aria-label"]) {
841
- const value = element.getAttribute && element.getAttribute(attr);
842
- if (value) {
843
- const selector = tag + "[" + attr + "=\"" + String(value).replace(/\\/g, "\\\\").replace(/\"/g, "\\\"") + "\"]";
844
- return { selector, unique: safeQueryCount(selector) === 1 };
845
- }
846
- }
847
- if (element.getAttribute && element.getAttribute("data-pibo-terminal-row")) {
848
- const rowId = element.getAttribute("data-row-id");
849
- if (rowId) {
850
- const selector = tag + "[data-pibo-terminal-row=\"true\"][data-row-id=\"" + String(rowId).replace(/\\/g, "\\\\").replace(/\"/g, "\\\"") + "\"]";
851
- return { selector, unique: safeQueryCount(selector) === 1 };
852
- }
853
- return { selector: tag + "[data-pibo-terminal-row=\"true\"]", unique: false };
854
- }
855
- return { selector: tag + ":nth-of-type(" + nthOfType(element) + ")", unique: false };
856
- }
857
-
858
- function safeQueryCount(selector) {
859
- try { return document.querySelectorAll(selector).length; } catch { return 0; }
860
- }
861
-
862
- function nthOfType(element) {
863
- let index = 1;
864
- let sibling = element.previousElementSibling;
865
- while (sibling) {
866
- if (sibling.tagName === element.tagName) index += 1;
867
- sibling = sibling.previousElementSibling;
868
- }
869
- return index;
870
- }
871
-
872
- function domPath(element, full) {
873
- const parts = [];
874
- let current = element;
875
- while (current && current.nodeType === 1) {
876
- const tag = (current.tagName || "").toLowerCase();
877
- if (!tag) break;
878
- const id = current.getAttribute && current.getAttribute("id");
879
- const testId = current.getAttribute && (current.getAttribute("data-pibo-id") || current.getAttribute("data-pibo-component") || current.getAttribute("data-pibo-debug") || current.getAttribute("data-shared-terminal-card") || current.getAttribute("data-shared-status-field") || current.getAttribute("data-pibo-markdown-node") || current.getAttribute("data-testid") || current.getAttribute("data-test-id"));
880
- let part = tag;
881
- if (id) part += "#" + id;
882
- else if (testId) part += "[" + testId + "]";
883
- else if (current.getAttribute && current.getAttribute("data-pibo-terminal-row")) part += "[terminal-row:" + (current.getAttribute("data-row-kind") || "true") + "]";
884
- else part += ":nth-of-type(" + nthOfType(current) + ")";
885
- parts.unshift(part);
886
- if (!full && (id || testId || current.getAttribute && current.getAttribute("data-pibo-terminal-row") || parts.length >= 5)) break;
887
- current = current.parentElement || (current.getRootNode && current.getRootNode().host) || null;
888
- }
889
- return parts.join(" > ");
890
- }
891
-
892
- function classSummary(element) {
893
- const classes = Array.from(element.classList || []).filter((name) => !/[a-f0-9]{7,}/i.test(name)).slice(0, 12);
894
- return classes.join(" ") || undefined;
895
- }
896
-
897
- function openingTagHint(element) {
898
- const clone = element.cloneNode(false);
899
- const html = clone.outerHTML || "";
900
- return html.replace(/><\/[\s\S]*$/, ">");
901
- }
902
-
903
- function labelFor(element) {
904
- const aria = element.getAttribute && (element.getAttribute("aria-label") || element.getAttribute("title") || element.getAttribute("alt"));
905
- const text = (element.innerText || element.textContent || "").replace(/\s+/g, " ").trim();
906
- const tag = (element.tagName || "element").toLowerCase();
907
- return aria ? tag + " \"" + aria + "\"" : (text ? tag + " \"" + text.slice(0, 80) + "\"" : tag);
908
- }
909
-
910
- function accessibilityHint(element) {
911
- const focusable = Boolean(element.matches && element.matches("a[href],button,input,textarea,select,[tabindex]"));
912
- const role = element.getAttribute && element.getAttribute("role") || undefined;
913
- const ariaLabel = element.getAttribute && element.getAttribute("aria-label") || undefined;
914
- return { role, name: cap(ariaLabel || element.getAttribute && element.getAttribute("title") || undefined, 160), ariaLabel: cap(ariaLabel, 160), focusable };
915
- }
916
-
917
- function sourceHints(element, selector) {
918
- const hints = [];
919
- const explicit = explicitSourceHints(element);
920
- hints.push(...explicit);
921
- const locator = locatorHint(element);
922
- if (locator && !hasEquivalentHint(hints, locator)) hints.push(locator);
923
- const react = reactHint(element);
924
- if (react && !hasEquivalentHint(hints, react)) hints.push(react);
925
- if (!hints.length) hints.push({ kind: "dom-fallback", confidence: "low", id: selector || domPath(element, false), raw: { tagName: (element.tagName || "").toLowerCase() } });
926
- return hints;
927
- }
928
-
929
- function explicitSourceHints(element) {
930
- const hints = [];
931
- const deferredTestHints = [];
932
- let current = element;
933
- while (current && current.nodeType === 1 && hints.length < 8) {
934
- const tagName = (current.tagName || "").toLowerCase();
935
- const piboId = current.getAttribute && current.getAttribute("data-pibo-id");
936
- if (piboId) pushHint(hints, { kind: "pibo-id", confidence: "high", id: piboId, raw: { tagName } });
937
- const component = current.getAttribute && current.getAttribute("data-pibo-component");
938
- if (component) pushHint(hints, { kind: "pibo-component", confidence: "high", id: component, component, raw: collectPiboRaw(current) });
939
- const debug = current.getAttribute && current.getAttribute("data-pibo-debug");
940
- if (debug) pushHint(hints, { kind: "pibo-debug", confidence: "high", id: debug, component, raw: collectPiboRaw(current) });
941
- const markdownNode = current.getAttribute && current.getAttribute("data-pibo-markdown-node");
942
- if (markdownNode) pushHint(hints, { kind: "pibo-markdown", confidence: "high", id: markdownNode, component: component || "MarkdownRenderer", raw: collectPiboRaw(current) });
943
- const sharedCard = current.getAttribute && current.getAttribute("data-shared-terminal-card");
944
- if (sharedCard) pushHint(hints, { kind: "pibo-shared-card", confidence: "high", id: sharedCard, component: component || "TerminalCard", raw: collectPiboRaw(current) });
945
- const sharedField = current.getAttribute && current.getAttribute("data-shared-status-field");
946
- if (sharedField) pushHint(hints, { kind: "pibo-shared-card", confidence: "high", id: "status-field:" + sharedField, component: component || "TerminalStatusCard", raw: collectPiboRaw(current) });
947
- if (current.getAttribute && current.getAttribute("data-pibo-terminal-row")) {
948
- pushHint(hints, { kind: "pibo-terminal-row", confidence: "high", id: current.getAttribute("data-row-id") || current.getAttribute("data-row-kind") || "terminal-row", component: component || "TerminalRow", raw: collectPiboRaw(current) });
949
- }
950
- const testId = current.getAttribute && (current.getAttribute("data-testid") || current.getAttribute("data-test-id") || current.getAttribute("data-cy") || current.getAttribute("data-qa"));
951
- if (testId) {
952
- const testHint = { kind: "test-id", confidence: "high", id: testId, raw: { tagName } };
953
- if (/^virtuoso/i.test(testId) || /virtuoso/i.test(testId)) deferredTestHints.push(testHint);
954
- else pushHint(hints, testHint);
955
- }
956
- const locatorId = current.getAttribute && current.getAttribute("data-locatorjs-id");
957
- if (locatorId) pushHint(hints, { kind: "locatorjs", confidence: "high", id: locatorId, raw: collectLocatorRaw(current) });
958
- current = current.parentElement || (current.getRootNode && current.getRootNode().host) || null;
959
- }
960
- if (!hints.length) deferredTestHints.slice(0, 2).forEach((hint) => pushHint(hints, hint));
961
- return hints;
962
- }
963
-
964
- function pushHint(hints, hint) {
965
- if (!hint || !hint.kind) return;
966
- if (hasEquivalentHint(hints, hint)) return;
967
- hints.push(hint);
968
- }
969
-
970
- function hasEquivalentHint(hints, hint) {
971
- return hints.some((existing) => existing.kind === hint.kind && (existing.id || "") === (hint.id || "") && (existing.component || "") === (hint.component || ""));
972
- }
973
-
974
- function collectPiboRaw(element) {
975
- const raw = { tagName: (element.tagName || "").toLowerCase() };
976
- for (const attr of ["data-pibo-id", "data-pibo-component", "data-pibo-debug", "data-pibo-session-id", "data-pibo-title", "data-pibo-selected", "data-pibo-state", "data-pibo-terminal-row", "data-pibo-markdown-node", "data-pibo-markdown-kind", "data-shared-terminal-card", "data-shared-status-field", "data-row-id", "data-row-kind", "data-row-status", "data-trace-node-id", "data-event-id", "data-run-id", "data-order-source", "data-order-stream-id", "data-order-frame-index"]) {
977
- const value = element.getAttribute && element.getAttribute(attr);
978
- if (value) raw[attr.replace(/^data-/, "").replace(/-([a-z])/g, (_, char) => char.toUpperCase())] = cap(value, 300);
979
- }
980
- return raw;
981
- }
982
-
983
- function locatorHint(element) {
984
- let current = element;
985
- while (current && current.nodeType === 1) {
986
- const raw = collectLocatorRaw(current);
987
- const file = raw.file || raw.sourceFile || raw.source;
988
- const component = raw.component || raw.componentName;
989
- if (file || component || raw.line || raw.column) {
990
- return { kind: "locatorjs", confidence: "high", id: raw.id, file, line: numberOrUndefined(raw.line), column: numberOrUndefined(raw.column), component, raw };
991
- }
992
- current = current.parentElement || (current.getRootNode && current.getRootNode().host) || null;
993
- }
994
- return undefined;
995
- }
996
-
997
- function collectLocatorRaw(element) {
998
- const names = ["data-locatorjs-id", "data-locatorjs-source", "data-locatorjs-file", "data-locatorjs-line", "data-locatorjs-column", "data-locatorjs-component", "data-source", "data-source-file", "data-source-line", "data-source-column", "data-component", "data-component-path"];
999
- const raw = {};
1000
- for (const name of names) {
1001
- const value = element.getAttribute && element.getAttribute(name);
1002
- if (value) raw[name.replace(/^data-/, "").replace(/-([a-z])/g, (_, char) => char.toUpperCase())] = cap(value, 300);
1003
- }
1004
- return raw;
1005
- }
1006
-
1007
- function reactHint(element) {
1008
- let current = element;
1009
- while (current && current.nodeType === 1) {
1010
- const key = Object.keys(current).find((name) => name.startsWith("__reactFiber$") || name.startsWith("__reactInternalInstance$"));
1011
- let fiber = key ? current[key] : undefined;
1012
- const names = [];
1013
- let source;
1014
- let guard = 0;
1015
- while (fiber && guard < 20) {
1016
- const type = fiber.elementType || fiber.type;
1017
- const name = typeof type === "function" ? (type.displayName || type.name) : (typeof type === "string" ? type : undefined);
1018
- if (name) names.push(name);
1019
- if (!source && fiber._debugSource) source = fiber._debugSource;
1020
- fiber = fiber.return;
1021
- guard += 1;
1022
- }
1023
- if (names.length || source) {
1024
- return {
1025
- kind: source ? "jsx-source" : "react-fiber",
1026
- confidence: "medium",
1027
- file: source && source.fileName,
1028
- line: source && source.lineNumber,
1029
- column: source && source.columnNumber,
1030
- component: names[0],
1031
- componentPath: names.slice(0, 12),
1032
- raw: source ? { source } : { components: names.slice(0, 12) },
1033
- };
1034
- }
1035
- current = current.parentElement || null;
1036
- }
1037
- return undefined;
1038
- }
1039
-
1040
- function numberOrUndefined(value) {
1041
- const parsed = Number(value);
1042
- return Number.isFinite(parsed) ? parsed : undefined;
1043
- }
1044
-
1045
- document.addEventListener("mousemove", onMouseMove, true);
1046
- document.addEventListener("click", onClick, true);
1047
- document.addEventListener("keydown", onKeyDown, true);
1048
- window.addEventListener("pibo:web-annotation-shortcut-changed", onShortcutChanged);
1049
- window.addEventListener("pagehide", onPageHide);
1050
- window.addEventListener("resize", onViewportChange);
1051
- if (window.visualViewport) {
1052
- window.visualViewport.addEventListener("resize", onViewportChange);
1053
- window.visualViewport.addEventListener("scroll", onViewportChange);
1054
- }
1055
- updateUi();
1056
-
1057
- const api = {
1058
- bindingId: config.bindingId,
1059
- piboSessionId: config.piboSessionId,
1060
- injectedAt: new Date().toISOString(),
1061
- setActive(value) {
1062
- state.active = Boolean(value);
1063
- updateUi();
1064
- publishOverlayState("set-active");
1065
- },
1066
- setMode(value) {
1067
- state.mode = value === "pin" ? "pin" : "element";
1068
- state.active = true;
1069
- updateUi();
1070
- publishOverlayState("set-mode");
1071
- },
1072
- setShortcut(value) { setShortcut(value); },
1073
- getState() { return { active: state.active, toolbarExpanded: state.toolbarExpanded, mode: state.mode, shortcut: state.shortcut.label, submissions: state.submissions.slice(), lastError: state.lastError }; },
1074
- remove() {
1075
- state.active = false;
1076
- publishOverlayState("removed", false);
1077
- document.removeEventListener("mousemove", onMouseMove, true);
1078
- document.removeEventListener("click", onClick, true);
1079
- document.removeEventListener("keydown", onKeyDown, true);
1080
- window.removeEventListener("pibo:web-annotation-shortcut-changed", onShortcutChanged);
1081
- window.removeEventListener("pagehide", onPageHide);
1082
- window.removeEventListener("resize", onViewportChange);
1083
- if (window.visualViewport) {
1084
- window.visualViewport.removeEventListener("resize", onViewportChange);
1085
- window.visualViewport.removeEventListener("scroll", onViewportChange);
1086
- }
1087
- if (state.pendingFrame) cancelAnimationFrame(state.pendingFrame);
1088
- closePopup();
1089
- outline.remove();
1090
- host.remove();
1091
- if (window.__piboWebAnnotations && window.__piboWebAnnotations.bindingId === config.bindingId) delete window.__piboWebAnnotations;
1092
- },
1093
- };
1094
- window.__piboWebAnnotations = api;
1095
- publishOverlayState("installed");
1096
- return { ok: true, url: location.href, title: document.title || "" };
211
+ return String.raw `(() => {
212
+ const config = (${configExpression});
213
+ if (!config || !config.bindingId || !config.bindingToken) throw new Error("Pibo Web Annotation overlay config is missing bindingId or bindingToken");
214
+ const rootId = "pibo-web-annotation-overlay";
215
+ const outlineId = "pibo-web-annotation-outline";
216
+ const shortcutStorageKey = "pibo.chat.shortcuts.webAnnotationsToggle";
217
+ const overlayStateEventName = "pibo:web-annotation-overlay-state";
218
+ const overlayStateStoragePrefix = "pibo.chat.webAnnotations.overlay.";
219
+ const defaultAnnotationShortcut = "Alt+Shift+A";
220
+ const previous = window.__piboWebAnnotations;
221
+ if (previous && typeof previous.remove === "function") previous.remove();
222
+
223
+ const caps = {
224
+ note: 2000,
225
+ text: 500,
226
+ htmlHint: 400,
227
+ classSummary: 240,
228
+ accessibility: 240,
229
+ selector: 500,
230
+ domPath: 700,
231
+ sourceRaw: 800,
232
+ };
233
+ const state = {
234
+ active: false,
235
+ mode: "element",
236
+ hovered: null,
237
+ popup: null,
238
+ lastHoverAt: 0,
239
+ pendingFrame: 0,
240
+ dragMoved: false,
241
+ toolbarExpanded: false,
242
+ shortcut: parseShortcut(config.annotationShortcut || readStoredShortcut() || defaultAnnotationShortcut),
243
+ submissions: [],
244
+ lastError: null,
245
+ };
246
+
247
+ function publishOverlayState(reason, installed) {
248
+ const piboSessionId = String(config.piboSessionId || "");
249
+ const detail = {
250
+ bindingId: String(config.bindingId || ""),
251
+ piboSessionId,
252
+ installed: installed !== false,
253
+ active: installed !== false && Boolean(state.active),
254
+ toolbarExpanded: installed !== false && Boolean(state.toolbarExpanded),
255
+ mode: state.mode,
256
+ reason: String(reason || "state"),
257
+ updatedAt: new Date().toISOString(),
258
+ };
259
+ if (piboSessionId) {
260
+ try { window.localStorage && window.localStorage.setItem(overlayStateStoragePrefix + piboSessionId, JSON.stringify(detail)); } catch {}
261
+ }
262
+ try { window.dispatchEvent(new CustomEvent(overlayStateEventName, { detail })); } catch {}
263
+ }
264
+
265
+ function onPageHide() {
266
+ state.active = false;
267
+ publishOverlayState("pagehide", false);
268
+ }
269
+
270
+ const host = document.createElement("div");
271
+ host.id = rootId;
272
+ host.setAttribute("data-pibo-web-annotation-binding", config.bindingId);
273
+ host.style.cssText = "position:fixed;right:16px;bottom:calc(16px + env(safe-area-inset-bottom));z-index:2147483647;pointer-events:auto;color-scheme:light dark";
274
+ const shadow = host.attachShadow ? host.attachShadow({ mode: "open" }) : host;
275
+ const style = document.createElement("style");
276
+ style.textContent = [
277
+ ":host{all:initial;font:13px system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}",
278
+ "@keyframes pibo-wa-attention{0%{transform:scale(.72);opacity:.45;box-shadow:0 0 0 0 rgba(56,189,248,.7)}18%{transform:scale(1.22,.82);opacity:1}34%{transform:scale(.9,1.12)}52%{transform:scale(1.08,.94);box-shadow:0 0 0 12px rgba(56,189,248,.18)}72%{transform:scale(.98,1.02)}100%{transform:scale(1);opacity:1;box-shadow:0 12px 32px rgba(0,0,0,.36),0 0 0 0 rgba(56,189,248,0)}}",
279
+ ".pibo-wa-panel{position:relative;width:50px;min-height:50px;color:#fff;font:13px system-ui,sans-serif;touch-action:none}",
280
+ ".pibo-wa-main,.pibo-wa-button{box-sizing:border-box;width:48px;height:48px;display:inline-flex;align-items:center;justify-content:center;border:1px solid rgba(148,163,184,.5);background:#111827;color:#dbeafe;border-radius:14px;box-shadow:0 12px 32px rgba(0,0,0,.36);cursor:pointer;user-select:none;line-height:0;padding:0;transition:background .12s ease,border-color .12s ease,color .12s ease,transform .12s ease;-webkit-tap-highlight-color:transparent}",
281
+ ".pibo-wa-main{border-color:#38bdf8;background:linear-gradient(180deg,#102334,#0f172a);color:#7dd3fc;cursor:grab}",
282
+ ".pibo-wa-main:active{cursor:grabbing}",
283
+ ".pibo-wa-main.pibo-wa-attention{animation:pibo-wa-attention .78s cubic-bezier(.2,.8,.2,1) both}",
284
+ "@media (prefers-reduced-motion:reduce){.pibo-wa-main.pibo-wa-attention{animation:none;box-shadow:0 0 0 3px rgba(56,189,248,.6),0 12px 32px rgba(0,0,0,.36)}}",
285
+ ".pibo-wa-main:hover,.pibo-wa-button:hover{border-color:#7dd3fc;color:#e0f2fe;background:#172033;transform:translateY(-1px)}",
286
+ ".pibo-wa-icon{width:22px;height:22px;display:block;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;flex:0 0 auto}",
287
+ ".pibo-wa-actions{position:absolute;right:-2px;bottom:42px;display:flex;flex-direction:column;gap:7px;padding:0 2px 12px 2px;opacity:0;pointer-events:none;transform:translateY(6px);transition:opacity .12s ease,transform .12s ease}",
288
+ ".pibo-wa-actions::before{content:'';position:absolute;inset:0 -8px -12px -8px;z-index:-1}",
289
+ ".pibo-wa-panel:hover .pibo-wa-actions,.pibo-wa-panel:focus-within .pibo-wa-actions,.pibo-wa-panel.pibo-wa-active .pibo-wa-actions,.pibo-wa-panel.pibo-wa-expanded .pibo-wa-actions{opacity:1;pointer-events:auto;transform:translateY(0)}",
290
+ "@media (hover:none),(pointer:coarse){.pibo-wa-panel{width:54px;min-height:54px}.pibo-wa-main,.pibo-wa-button{width:52px;height:52px;border-radius:16px}.pibo-wa-actions,.pibo-wa-panel:hover .pibo-wa-actions,.pibo-wa-panel:focus-within .pibo-wa-actions,.pibo-wa-panel.pibo-wa-active .pibo-wa-actions{right:-1px;bottom:48px;gap:8px;opacity:0;pointer-events:none;transform:translateY(6px)}.pibo-wa-panel.pibo-wa-expanded .pibo-wa-actions{opacity:1;pointer-events:auto;transform:none}.pibo-wa-main:hover,.pibo-wa-button:hover{transform:none}}",
291
+ ".pibo-wa-button{background:#1f2937}",
292
+ ".pibo-wa-button[aria-pressed='true']{background:#2563eb;border-color:#93c5fd;color:#fff}",
293
+ ".pibo-wa-button-danger{background:#3f1418;border-color:#7f1d1d;color:#fecaca}",
294
+ ".pibo-wa-button-danger:hover{border-color:#ef4444;color:#fff;background:#7f1d1d}",
295
+ ".pibo-wa-status{display:none}",
296
+ ".pibo-wa-popup{box-sizing:border-box;position:fixed;z-index:2147483647;width:min(320px,calc(100vw - 24px));max-height:calc(100svh - 24px);overflow:auto;background:#fff;color:#111827;border:1px solid #9ca3af;border-radius:12px;padding:10px;box-shadow:0 14px 36px rgba(0,0,0,.35);font:13px system-ui,sans-serif;overscroll-behavior:contain}",
297
+ ".pibo-wa-row{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:8px}",
298
+ ".pibo-wa-popup textarea{box-sizing:border-box;width:100%;min-height:76px;margin:8px 0;border:1px solid #9ca3af;border-radius:8px;padding:8px;font:13px system-ui,sans-serif;color:#111827;background:#fff}",
299
+ "@media (max-width:480px){.pibo-wa-popup{width:calc(100vw - 24px);padding:12px}.pibo-wa-popup textarea{min-height:96px;font-size:16px}.pibo-wa-row .pibo-wa-button{width:auto;min-width:92px;height:40px;border-radius:10px;padding:0 12px;line-height:1}}",
300
+ ".pibo-wa-popup-label{font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
301
+ ].join("");
302
+ const panel = document.createElement("div");
303
+ panel.className = "pibo-wa-panel";
304
+ const actions = document.createElement("div");
305
+ actions.className = "pibo-wa-actions";
306
+ const toggle = button(iconSvg("edit"), "Toggle element annotation mode");
307
+ const pin = button(iconSvg("arrow-down-to-dot"), "Mark a point instead of an element");
308
+ const stop = button(iconSvg("x"), "Remove the Pibo annotation overlay");
309
+ stop.className += " pibo-wa-button-danger";
310
+ const main = document.createElement("button");
311
+ main.type = "button";
312
+ main.className = "pibo-wa-main";
313
+ main.innerHTML = iconSvg("book-a");
314
+ main.classList.add("pibo-wa-attention");
315
+ main.setAttribute("aria-label", shortcutTitle("Enable element annotation mode"));
316
+ main.title = shortcutTitle("Enable element annotation mode");
317
+ const status = document.createElement("div");
318
+ status.className = "pibo-wa-status";
319
+ actions.append(toggle, pin, stop);
320
+ panel.append(actions, main, status);
321
+ shadow.append(style, panel);
322
+ document.documentElement.appendChild(host);
323
+
324
+ const outline = document.createElement("div");
325
+ outline.id = outlineId;
326
+ outline.style.cssText = "position:fixed;z-index:2147483646;pointer-events:none;border:2px solid #2563eb;background:rgba(37,99,235,.10);box-shadow:0 0 0 9999px rgba(37,99,235,.04);display:none;border-radius:3px";
327
+ document.documentElement.appendChild(outline);
328
+
329
+ main.addEventListener("pointerdown", startDrag);
330
+ main.addEventListener("click", () => {
331
+ if (state.dragMoved) {
332
+ state.dragMoved = false;
333
+ return;
334
+ }
335
+ if (isCoarsePointer()) {
336
+ state.toolbarExpanded = !state.toolbarExpanded;
337
+ updateUi();
338
+ publishOverlayState("toolbar-toggle");
339
+ return;
340
+ }
341
+ state.active = true;
342
+ state.mode = "element";
343
+ closePopup();
344
+ updateUi();
345
+ publishOverlayState("main-button");
346
+ });
347
+ main.addEventListener("animationend", () => main.classList.remove("pibo-wa-attention"));
348
+ toggle.addEventListener("click", () => {
349
+ state.active = !state.active;
350
+ state.mode = "element";
351
+ state.toolbarExpanded = !state.active;
352
+ closePopup();
353
+ updateUi();
354
+ publishOverlayState("toggle");
355
+ });
356
+ pin.addEventListener("click", () => {
357
+ state.active = true;
358
+ state.mode = state.mode === "pin" ? "element" : "pin";
359
+ state.toolbarExpanded = false;
360
+ closePopup();
361
+ updateUi();
362
+ publishOverlayState("pin-toggle");
363
+ });
364
+ stop.addEventListener("click", () => api.remove());
365
+
366
+ function button(content, aria) {
367
+ const el = document.createElement("button");
368
+ el.type = "button";
369
+ el.className = "pibo-wa-button";
370
+ el.innerHTML = content;
371
+ el.setAttribute("aria-label", aria);
372
+ el.title = aria;
373
+ return el;
374
+ }
375
+
376
+ function iconSvg(name) {
377
+ const attrs = "class=\"pibo-wa-icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\"";
378
+ if (name === "book-a") return "<svg " + attrs + "><path d=\"M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20\"/><path d=\"m8 13 4-7 4 7\"/><path d=\"M9.1 11h5.7\"/></svg>";
379
+ if (name === "arrow-down-to-dot") return "<svg " + attrs + "><path d=\"M12 2v14\"/><path d=\"m19 9-7 7-7-7\"/><circle cx=\"12\" cy=\"21\" r=\"1\"/></svg>";
380
+ if (name === "x") return "<svg " + attrs + "><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg>";
381
+ return "<svg " + attrs + "><path d=\"M12 20h9\"/><path d=\"M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z\"/></svg>";
382
+ }
383
+
384
+ function startDrag(event) {
385
+ if (event.button !== undefined && event.button !== 0) return;
386
+ const startX = event.clientX;
387
+ const startY = event.clientY;
388
+ const rect = host.getBoundingClientRect();
389
+ const originLeft = rect.left;
390
+ const originTop = rect.top;
391
+ state.dragMoved = false;
392
+ if (main.setPointerCapture) main.setPointerCapture(event.pointerId);
393
+ const onMove = (nextEvent) => {
394
+ const dx = nextEvent.clientX - startX;
395
+ const dy = nextEvent.clientY - startY;
396
+ if (!state.dragMoved && Math.hypot(dx, dy) < 4) return;
397
+ state.dragMoved = true;
398
+ nextEvent.preventDefault();
399
+ const viewport = viewportMetrics();
400
+ const left = clamp(originLeft + dx, viewport.left + 8, viewport.left + viewport.width - rect.width - 8);
401
+ const top = clamp(originTop + dy, viewport.top + 8, viewport.top + viewport.height - rect.height - 8);
402
+ host.style.left = left + "px";
403
+ host.style.top = top + "px";
404
+ host.style.right = "auto";
405
+ host.style.bottom = "auto";
406
+ };
407
+ const onUp = () => {
408
+ window.removeEventListener("pointermove", onMove, true);
409
+ window.removeEventListener("pointerup", onUp, true);
410
+ window.removeEventListener("pointercancel", onUp, true);
411
+ ensureHostInViewport();
412
+ };
413
+ window.addEventListener("pointermove", onMove, true);
414
+ window.addEventListener("pointerup", onUp, true);
415
+ window.addEventListener("pointercancel", onUp, true);
416
+ }
417
+
418
+ function updateUi(message) {
419
+ const title = isCoarsePointer() ? (state.toolbarExpanded ? "Hide annotation controls" : "Show annotation controls") : shortcutTitle("Enable element annotation mode");
420
+ main.setAttribute("aria-label", title);
421
+ main.setAttribute("aria-expanded", state.toolbarExpanded ? "true" : "false");
422
+ main.title = title;
423
+ toggle.title = shortcutTitle("Toggle element annotation mode");
424
+ toggle.setAttribute("aria-label", shortcutTitle("Toggle element annotation mode"));
425
+ toggle.setAttribute("aria-pressed", state.active && state.mode === "element" ? "true" : "false");
426
+ pin.setAttribute("aria-pressed", state.active && state.mode === "pin" ? "true" : "false");
427
+ panel.classList.toggle("pibo-wa-active", state.active);
428
+ panel.classList.toggle("pibo-wa-expanded", state.toolbarExpanded);
429
+ outline.style.display = state.active && state.mode === "element" && state.hovered ? "block" : "none";
430
+ status.textContent = message || (state.active ? (state.mode === "pin" ? "Pin mode: click anywhere to mark a point." : "Element mode: hover and click a visible target.") : "Inactive: page clicks and typing pass through normally.");
431
+ }
432
+
433
+ function isCoarsePointer() {
434
+ return Boolean(window.matchMedia && window.matchMedia("(hover: none), (pointer: coarse)").matches);
435
+ }
436
+
437
+ function readStoredShortcut() {
438
+ try { return window.localStorage && window.localStorage.getItem(shortcutStorageKey); } catch { return ""; }
439
+ }
440
+
441
+ function parseShortcut(value) {
442
+ const label = String(value || defaultAnnotationShortcut).trim() || defaultAnnotationShortcut;
443
+ const parts = label.split("+").map((part) => part.trim()).filter(Boolean);
444
+ const shortcut = { alt: false, ctrl: false, meta: false, shift: false, key: "a", label: defaultAnnotationShortcut };
445
+ for (const part of parts) {
446
+ const lower = part.toLowerCase();
447
+ if (lower === "alt" || lower === "option") shortcut.alt = true;
448
+ else if (lower === "ctrl" || lower === "control") shortcut.ctrl = true;
449
+ else if (lower === "cmd" || lower === "command" || lower === "meta") shortcut.meta = true;
450
+ else if (lower === "shift") shortcut.shift = true;
451
+ else shortcut.key = normalizeShortcutKey(part);
452
+ }
453
+ shortcut.label = shortcutLabel(shortcut);
454
+ return shortcut;
455
+ }
456
+
457
+ function normalizeShortcutKey(value) {
458
+ const key = String(value || "a").trim();
459
+ if (key === " ") return "Space";
460
+ if (key.length === 1) return key.toLowerCase();
461
+ return key.toLowerCase() === "space" ? "Space" : key;
462
+ }
463
+
464
+ function shortcutLabel(shortcut) {
465
+ const parts = [];
466
+ if (shortcut.ctrl) parts.push("Ctrl");
467
+ if (shortcut.alt) parts.push("Alt");
468
+ if (shortcut.shift) parts.push("Shift");
469
+ if (shortcut.meta) parts.push("Meta");
470
+ parts.push(shortcut.key.length === 1 ? shortcut.key.toUpperCase() : shortcut.key);
471
+ return parts.join("+");
472
+ }
473
+
474
+ function shortcutTitle(label) {
475
+ return label + " (" + state.shortcut.label + ")";
476
+ }
477
+
478
+ function matchesShortcut(event) {
479
+ const key = normalizeShortcutKey(event.key || "");
480
+ const expected = String(state.shortcut.key);
481
+ const keyMatches = key.toLowerCase() === expected.toLowerCase()
482
+ || (expected.length === 1 && event.code === "Key" + expected.toUpperCase());
483
+ return keyMatches
484
+ && Boolean(event.altKey) === state.shortcut.alt
485
+ && Boolean(event.ctrlKey) === state.shortcut.ctrl
486
+ && Boolean(event.metaKey) === state.shortcut.meta
487
+ && Boolean(event.shiftKey) === state.shortcut.shift;
488
+ }
489
+
490
+ function setShortcut(value) {
491
+ state.shortcut = parseShortcut(value || readStoredShortcut() || defaultAnnotationShortcut);
492
+ updateUi();
493
+ }
494
+
495
+ function onKeyDown(event) {
496
+ if (event.defaultPrevented || event.repeat || !matchesShortcut(event)) return;
497
+ event.preventDefault();
498
+ event.stopPropagation();
499
+ state.active = !(state.active && state.mode === "element");
500
+ state.mode = "element";
501
+ closePopup();
502
+ updateUi(state.active ? "Element annotation mode enabled from shortcut." : "Annotation mode disabled from shortcut.");
503
+ publishOverlayState("shortcut");
504
+ }
505
+
506
+ function onShortcutChanged(event) {
507
+ setShortcut(event && event.detail && event.detail.shortcut);
508
+ }
509
+
510
+ function onMouseMove(event) {
511
+ if (!state.active || state.mode !== "element" || isOverlayEvent(event)) return;
512
+ const now = Date.now();
513
+ if (now - state.lastHoverAt < 80) return;
514
+ state.lastHoverAt = now;
515
+ if (state.pendingFrame) cancelAnimationFrame(state.pendingFrame);
516
+ state.pendingFrame = requestAnimationFrame(() => {
517
+ state.pendingFrame = 0;
518
+ const target = eventTargetElement(event);
519
+ if (!target) {
520
+ clearHover();
521
+ return;
522
+ }
523
+ const rect = target.getBoundingClientRect();
524
+ if (!isUsableRect(rect)) {
525
+ clearHover();
526
+ return;
527
+ }
528
+ state.hovered = target;
529
+ outline.style.left = rect.left + "px";
530
+ outline.style.top = rect.top + "px";
531
+ outline.style.width = rect.width + "px";
532
+ outline.style.height = rect.height + "px";
533
+ outline.style.display = "block";
534
+ });
535
+ }
536
+
537
+ function onClick(event) {
538
+ if (!state.active || isOverlayEvent(event)) return;
539
+ if (state.mode === "pin") {
540
+ event.preventDefault();
541
+ event.stopPropagation();
542
+ openNote("pin", buildPinTarget(event), { x: event.clientX, y: event.clientY });
543
+ return;
544
+ }
545
+ const target = eventTargetElement(event);
546
+ if (!target || !isUsableRect(target.getBoundingClientRect())) return;
547
+ event.preventDefault();
548
+ event.stopPropagation();
549
+ state.hovered = target;
550
+ openNote("element", buildElementTarget(target), rectAnchor(target.getBoundingClientRect()));
551
+ }
552
+
553
+ function isOverlayEvent(event) {
554
+ const path = typeof event.composedPath === "function" ? event.composedPath() : [];
555
+ return path.includes(host) || path.includes(panel) || (state.popup && path.includes(state.popup));
556
+ }
557
+
558
+ function eventTargetElement(event) {
559
+ const path = typeof event.composedPath === "function" ? event.composedPath() : [];
560
+ for (const item of path) {
561
+ if (item && item.nodeType === 1 && item !== host && item !== outline && item !== document.documentElement && item !== document.body) return item;
562
+ }
563
+ const target = event.target;
564
+ return target && target.nodeType === 1 ? target : null;
565
+ }
566
+
567
+ function clearHover() {
568
+ state.hovered = null;
569
+ outline.style.display = "none";
570
+ }
571
+
572
+ function rectAnchor(rect) {
573
+ const viewport = viewportMetrics();
574
+ return {
575
+ x: clamp(rect.left, viewport.left + 12, viewport.left + viewport.width - 12),
576
+ y: clamp(rect.bottom + 8, viewport.top + 12, viewport.top + viewport.height - 12),
577
+ };
578
+ }
579
+
580
+ function viewportMetrics() {
581
+ const visual = window.visualViewport;
582
+ return {
583
+ left: visual && Number.isFinite(visual.offsetLeft) ? visual.offsetLeft : 0,
584
+ top: visual && Number.isFinite(visual.offsetTop) ? visual.offsetTop : 0,
585
+ width: Math.max(0, visual && Number.isFinite(visual.width) ? visual.width : (window.innerWidth || document.documentElement.clientWidth || 0)),
586
+ height: Math.max(0, visual && Number.isFinite(visual.height) ? visual.height : (window.innerHeight || document.documentElement.clientHeight || 0)),
587
+ };
588
+ }
589
+
590
+ function clamp(value, min, max) {
591
+ const upper = Math.max(min, max);
592
+ return Math.max(min, Math.min(upper, value));
593
+ }
594
+
595
+ function positionPopup(popup, anchor) {
596
+ const viewport = viewportMetrics();
597
+ const margin = 12;
598
+ const rect = popup.getBoundingClientRect();
599
+ const width = Math.min(rect.width || 320, Math.max(0, viewport.width - margin * 2));
600
+ const height = Math.min(rect.height || 180, Math.max(0, viewport.height - margin * 2));
601
+ popup.style.left = clamp(anchor.x, viewport.left + margin, viewport.left + viewport.width - width - margin) + "px";
602
+ popup.style.top = clamp(anchor.y, viewport.top + margin, viewport.top + viewport.height - height - margin) + "px";
603
+ }
604
+
605
+ function ensureHostInViewport() {
606
+ const rect = host.getBoundingClientRect();
607
+ if (!rect.width || !rect.height) return;
608
+ const viewport = viewportMetrics();
609
+ const left = clamp(rect.left, viewport.left + 8, viewport.left + viewport.width - rect.width - 8);
610
+ const top = clamp(rect.top, viewport.top + 8, viewport.top + viewport.height - rect.height - 8);
611
+ if (Math.abs(left - rect.left) > 1 || Math.abs(top - rect.top) > 1) {
612
+ host.style.left = left + "px";
613
+ host.style.top = top + "px";
614
+ host.style.right = "auto";
615
+ host.style.bottom = "auto";
616
+ }
617
+ }
618
+
619
+ function onViewportChange() {
620
+ ensureHostInViewport();
621
+ if (state.popup && state.popup.__piboAnchor) positionPopup(state.popup, state.popup.__piboAnchor);
622
+ }
623
+
624
+ function isUsableRect(rect) {
625
+ return rect && rect.width > 1 && rect.height > 1 && rect.bottom >= 0 && rect.right >= 0 && rect.left <= window.innerWidth && rect.top <= window.innerHeight;
626
+ }
627
+
628
+ function openNote(kind, target, anchor) {
629
+ closePopup();
630
+ clearHover();
631
+ const popup = document.createElement("div");
632
+ popup.className = "pibo-wa-popup";
633
+ popup.__piboAnchor = anchor;
634
+ const label = document.createElement("div");
635
+ label.className = "pibo-wa-popup-label";
636
+ label.textContent = kind === "pin" ? "Pin annotation" : (target.label || target.selector || "Element annotation");
637
+ const textarea = document.createElement("textarea");
638
+ textarea.placeholder = "What should the agent know about this target?";
639
+ textarea.maxLength = caps.note;
640
+ const actions = document.createElement("div");
641
+ actions.className = "pibo-wa-row";
642
+ const submit = button("Submit", "Submit annotation");
643
+ const cancel = button("Cancel", "Cancel annotation");
644
+ actions.append(submit, cancel);
645
+ popup.append(label, textarea, actions);
646
+ shadow.append(popup);
647
+ state.popup = popup;
648
+ state.active = false;
649
+ positionPopup(popup, anchor);
650
+ updateUi("Add a note, submit, or cancel.");
651
+ textarea.focus({ preventScroll: true });
652
+ cancel.addEventListener("click", () => {
653
+ closePopup();
654
+ state.active = true;
655
+ updateUi("Cancelled. Annotation mode is active.");
656
+ });
657
+ submit.addEventListener("click", async () => {
658
+ const note = cap(textarea.value.trim(), caps.note);
659
+ if (!note) {
660
+ updateUi("Note is required before submitting.");
661
+ textarea.focus();
662
+ return;
663
+ }
664
+ submit.disabled = true;
665
+ const payload = buildPayload(kind, target, note);
666
+ state.submissions.push(payload);
667
+ try {
668
+ const response = await fetch(apiUrl() + "/submissions", {
669
+ method: "POST",
670
+ mode: "cors",
671
+ headers: { "content-type": "application/json" },
672
+ body: JSON.stringify(payload),
673
+ });
674
+ if (!response.ok) throw new Error("HTTP " + response.status);
675
+ const data = await response.json().catch(() => ({}));
676
+ state.lastError = null;
677
+ try { window.dispatchEvent(new CustomEvent("pibo:web-annotation-saved", { detail: data })); } catch {}
678
+ closePopup();
679
+ state.active = true;
680
+ updateUi(data && data.annotation && data.annotation.id ? "Saved " + data.annotation.id + "." : "Saved annotation.");
681
+ } catch (error) {
682
+ state.lastError = error && error.message ? error.message : String(error);
683
+ submit.disabled = false;
684
+ updateUi("Submission failed: " + cap(state.lastError, 120));
685
+ }
686
+ });
687
+ }
688
+
689
+ function closePopup() {
690
+ if (state.popup) state.popup.remove();
691
+ state.popup = null;
692
+ }
693
+
694
+ function apiUrl() {
695
+ const base = config.apiBaseUrl || window.location.origin;
696
+ return base.replace(/\/$/, "") + "/api/web-annotations";
697
+ }
698
+
699
+ function buildPayload(kind, target, note) {
700
+ return {
701
+ bindingId: config.bindingId,
702
+ bindingToken: config.bindingToken,
703
+ note,
704
+ url: cap(window.location.href, 2000),
705
+ title: cap(document.title || "", 200),
706
+ targetKind: kind,
707
+ viewport: {
708
+ width: Math.max(0, Math.round(window.innerWidth || document.documentElement.clientWidth || 0)),
709
+ height: Math.max(0, Math.round(window.innerHeight || document.documentElement.clientHeight || 0)),
710
+ devicePixelRatio: Number(window.devicePixelRatio || 1),
711
+ },
712
+ target,
713
+ };
714
+ }
715
+
716
+ function buildPinTarget(event) {
717
+ return boundedTarget({
718
+ kind: "pin",
719
+ label: "pin " + Math.round(event.clientX) + "," + Math.round(event.clientY),
720
+ position: { x: Math.round(event.clientX), y: Math.round(event.clientY) },
721
+ sourceHints: [{ kind: "dom-fallback", confidence: "low", id: "pin", raw: { url: cap(location.pathname, 200) } }],
722
+ });
723
+ }
724
+
725
+ function buildElementTarget(element) {
726
+ const rect = element.getBoundingClientRect();
727
+ const selector = bestSelector(element);
728
+ const selectedText = getSelectedText();
729
+ const tagName = (element.tagName || "").toLowerCase();
730
+ const target = {
731
+ kind: "element",
732
+ label: cap(labelFor(element), caps.text),
733
+ selector,
734
+ domPath: cap(domPath(element, false), caps.domPath),
735
+ fullDomPath: cap(domPath(element, true), caps.domPath),
736
+ tagName,
737
+ stableId: stableId(element),
738
+ classSummary: cap(classSummary(element), caps.classSummary),
739
+ text: cap((element.innerText || element.textContent || "").replace(/\s+/g, " ").trim(), caps.text),
740
+ selectedText: selectedText || undefined,
741
+ htmlHint: cap(openingTagHint(element), caps.htmlHint),
742
+ accessibility: accessibilityHint(element),
743
+ boundingBox: { x: Math.round(rect.left), y: Math.round(rect.top), width: Math.round(rect.width), height: Math.round(rect.height) },
744
+ sourceHints: sourceHints(element, selector),
745
+ };
746
+ if (tagName === "iframe") {
747
+ target.accessibility = { ...(target.accessibility || {}), description: "Iframe contents are unavailable to the top-level annotation overlay when cross-origin." };
748
+ }
749
+ return boundedTarget(target);
750
+ }
751
+
752
+ function boundedTarget(target) {
753
+ if (target.selector) target.selector = cap(target.selector, caps.selector);
754
+ if (target.domPath) target.domPath = cap(target.domPath, caps.domPath);
755
+ if (target.fullDomPath) target.fullDomPath = cap(target.fullDomPath, caps.domPath);
756
+ if (target.text) target.text = cap(target.text, caps.text);
757
+ if (target.selectedText) target.selectedText = cap(target.selectedText, caps.text);
758
+ if (target.htmlHint) target.htmlHint = cap(target.htmlHint, caps.htmlHint);
759
+ if (target.classSummary) target.classSummary = cap(target.classSummary, caps.classSummary);
760
+ if (target.accessibility && target.accessibility.description) target.accessibility.description = cap(target.accessibility.description, caps.accessibility);
761
+ if (Array.isArray(target.sourceHints)) target.sourceHints = target.sourceHints.slice(0, 8).map(boundSourceHint);
762
+ return target;
763
+ }
764
+
765
+ function boundSourceHint(hint) {
766
+ const next = { ...hint };
767
+ if (next.id) next.id = cap(String(next.id), 160);
768
+ if (next.file) next.file = cap(String(next.file), 300);
769
+ if (next.component) next.component = cap(String(next.component), 160);
770
+ if (Array.isArray(next.componentPath)) next.componentPath = next.componentPath.slice(0, 12).map((item) => cap(String(item), 120));
771
+ if (next.raw) next.raw = truncateRaw(next.raw);
772
+ return next;
773
+ }
774
+
775
+ function truncateRaw(raw) {
776
+ try {
777
+ return JSON.parse(cap(JSON.stringify(raw), caps.sourceRaw));
778
+ } catch {
779
+ return { truncated: true };
780
+ }
781
+ }
782
+
783
+ function cap(value, max) {
784
+ if (value === undefined || value === null) return undefined;
785
+ const text = String(value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
786
+ return text.length > max ? text.slice(0, max) : text;
787
+ }
788
+
789
+ function getSelectedText() {
790
+ const selection = window.getSelection && window.getSelection();
791
+ const text = selection ? selection.toString().replace(/\s+/g, " ").trim() : "";
792
+ return text ? cap(text, caps.text) : undefined;
793
+ }
794
+
795
+ function cssEscape(value) {
796
+ if (window.CSS && typeof window.CSS.escape === "function") return window.CSS.escape(value);
797
+ return String(value).replace(/[^a-zA-Z0-9_-]/g, (char) => "\\" + char);
798
+ }
799
+
800
+ function stableId(element) {
801
+ const attrs = ["data-pibo-id", "data-pibo-component", "data-pibo-debug", "data-shared-terminal-card", "data-shared-status-field", "data-pibo-markdown-node", "data-testid", "data-test-id", "data-cy", "data-qa", "data-locatorjs-id", "id", "aria-label"];
802
+ let current = element;
803
+ while (current && current.nodeType === 1) {
804
+ for (const attr of attrs) {
805
+ const value = current.getAttribute && current.getAttribute(attr);
806
+ if (value && value.trim()) return attr + ":" + cap(value.trim(), 160);
807
+ }
808
+ const terminalRow = current.getAttribute && current.getAttribute("data-pibo-terminal-row");
809
+ if (terminalRow) {
810
+ const rowId = current.getAttribute("data-row-id") || current.getAttribute("data-row-kind") || terminalRow;
811
+ return "data-pibo-terminal-row:" + cap(rowId, 160);
812
+ }
813
+ current = current.parentElement || (current.getRootNode && current.getRootNode().host) || null;
814
+ }
815
+ return undefined;
816
+ }
817
+
818
+ function bestSelector(element) {
819
+ const direct = stableSelectorPart(element);
820
+ if (direct && direct.unique) return direct.selector;
821
+ const chain = [];
822
+ let current = element;
823
+ while (current && current.nodeType === 1 && current !== document.documentElement) {
824
+ const part = stableSelectorPart(current);
825
+ chain.unshift(part.selector);
826
+ const combined = chain.join(" > ");
827
+ if (safeQueryCount(combined) === 1) return combined;
828
+ current = current.parentElement || (current.getRootNode && current.getRootNode().host) || null;
829
+ }
830
+ return chain.length ? chain.join(" > ") : undefined;
831
+ }
832
+
833
+ function stableSelectorPart(element) {
834
+ const tag = (element.tagName || "*").toLowerCase();
835
+ const id = element.getAttribute && element.getAttribute("id");
836
+ if (id) {
837
+ const selector = "#" + cssEscape(id);
838
+ return { selector, unique: safeQueryCount(selector) === 1 };
839
+ }
840
+ for (const attr of ["data-pibo-id", "data-pibo-component", "data-pibo-debug", "data-shared-terminal-card", "data-shared-status-field", "data-pibo-markdown-node", "data-testid", "data-test-id", "data-cy", "data-qa", "data-locatorjs-id", "aria-label"]) {
841
+ const value = element.getAttribute && element.getAttribute(attr);
842
+ if (value) {
843
+ const selector = tag + "[" + attr + "=\"" + String(value).replace(/\\/g, "\\\\").replace(/\"/g, "\\\"") + "\"]";
844
+ return { selector, unique: safeQueryCount(selector) === 1 };
845
+ }
846
+ }
847
+ if (element.getAttribute && element.getAttribute("data-pibo-terminal-row")) {
848
+ const rowId = element.getAttribute("data-row-id");
849
+ if (rowId) {
850
+ const selector = tag + "[data-pibo-terminal-row=\"true\"][data-row-id=\"" + String(rowId).replace(/\\/g, "\\\\").replace(/\"/g, "\\\"") + "\"]";
851
+ return { selector, unique: safeQueryCount(selector) === 1 };
852
+ }
853
+ return { selector: tag + "[data-pibo-terminal-row=\"true\"]", unique: false };
854
+ }
855
+ return { selector: tag + ":nth-of-type(" + nthOfType(element) + ")", unique: false };
856
+ }
857
+
858
+ function safeQueryCount(selector) {
859
+ try { return document.querySelectorAll(selector).length; } catch { return 0; }
860
+ }
861
+
862
+ function nthOfType(element) {
863
+ let index = 1;
864
+ let sibling = element.previousElementSibling;
865
+ while (sibling) {
866
+ if (sibling.tagName === element.tagName) index += 1;
867
+ sibling = sibling.previousElementSibling;
868
+ }
869
+ return index;
870
+ }
871
+
872
+ function domPath(element, full) {
873
+ const parts = [];
874
+ let current = element;
875
+ while (current && current.nodeType === 1) {
876
+ const tag = (current.tagName || "").toLowerCase();
877
+ if (!tag) break;
878
+ const id = current.getAttribute && current.getAttribute("id");
879
+ const testId = current.getAttribute && (current.getAttribute("data-pibo-id") || current.getAttribute("data-pibo-component") || current.getAttribute("data-pibo-debug") || current.getAttribute("data-shared-terminal-card") || current.getAttribute("data-shared-status-field") || current.getAttribute("data-pibo-markdown-node") || current.getAttribute("data-testid") || current.getAttribute("data-test-id"));
880
+ let part = tag;
881
+ if (id) part += "#" + id;
882
+ else if (testId) part += "[" + testId + "]";
883
+ else if (current.getAttribute && current.getAttribute("data-pibo-terminal-row")) part += "[terminal-row:" + (current.getAttribute("data-row-kind") || "true") + "]";
884
+ else part += ":nth-of-type(" + nthOfType(current) + ")";
885
+ parts.unshift(part);
886
+ if (!full && (id || testId || current.getAttribute && current.getAttribute("data-pibo-terminal-row") || parts.length >= 5)) break;
887
+ current = current.parentElement || (current.getRootNode && current.getRootNode().host) || null;
888
+ }
889
+ return parts.join(" > ");
890
+ }
891
+
892
+ function classSummary(element) {
893
+ const classes = Array.from(element.classList || []).filter((name) => !/[a-f0-9]{7,}/i.test(name)).slice(0, 12);
894
+ return classes.join(" ") || undefined;
895
+ }
896
+
897
+ function openingTagHint(element) {
898
+ const clone = element.cloneNode(false);
899
+ const html = clone.outerHTML || "";
900
+ return html.replace(/><\/[\s\S]*$/, ">");
901
+ }
902
+
903
+ function labelFor(element) {
904
+ const aria = element.getAttribute && (element.getAttribute("aria-label") || element.getAttribute("title") || element.getAttribute("alt"));
905
+ const text = (element.innerText || element.textContent || "").replace(/\s+/g, " ").trim();
906
+ const tag = (element.tagName || "element").toLowerCase();
907
+ return aria ? tag + " \"" + aria + "\"" : (text ? tag + " \"" + text.slice(0, 80) + "\"" : tag);
908
+ }
909
+
910
+ function accessibilityHint(element) {
911
+ const focusable = Boolean(element.matches && element.matches("a[href],button,input,textarea,select,[tabindex]"));
912
+ const role = element.getAttribute && element.getAttribute("role") || undefined;
913
+ const ariaLabel = element.getAttribute && element.getAttribute("aria-label") || undefined;
914
+ return { role, name: cap(ariaLabel || element.getAttribute && element.getAttribute("title") || undefined, 160), ariaLabel: cap(ariaLabel, 160), focusable };
915
+ }
916
+
917
+ function sourceHints(element, selector) {
918
+ const hints = [];
919
+ const explicit = explicitSourceHints(element);
920
+ hints.push(...explicit);
921
+ const locator = locatorHint(element);
922
+ if (locator && !hasEquivalentHint(hints, locator)) hints.push(locator);
923
+ const react = reactHint(element);
924
+ if (react && !hasEquivalentHint(hints, react)) hints.push(react);
925
+ if (!hints.length) hints.push({ kind: "dom-fallback", confidence: "low", id: selector || domPath(element, false), raw: { tagName: (element.tagName || "").toLowerCase() } });
926
+ return hints;
927
+ }
928
+
929
+ function explicitSourceHints(element) {
930
+ const hints = [];
931
+ const deferredTestHints = [];
932
+ let current = element;
933
+ while (current && current.nodeType === 1 && hints.length < 8) {
934
+ const tagName = (current.tagName || "").toLowerCase();
935
+ const piboId = current.getAttribute && current.getAttribute("data-pibo-id");
936
+ if (piboId) pushHint(hints, { kind: "pibo-id", confidence: "high", id: piboId, raw: { tagName } });
937
+ const component = current.getAttribute && current.getAttribute("data-pibo-component");
938
+ if (component) pushHint(hints, { kind: "pibo-component", confidence: "high", id: component, component, raw: collectPiboRaw(current) });
939
+ const debug = current.getAttribute && current.getAttribute("data-pibo-debug");
940
+ if (debug) pushHint(hints, { kind: "pibo-debug", confidence: "high", id: debug, component, raw: collectPiboRaw(current) });
941
+ const markdownNode = current.getAttribute && current.getAttribute("data-pibo-markdown-node");
942
+ if (markdownNode) pushHint(hints, { kind: "pibo-markdown", confidence: "high", id: markdownNode, component: component || "MarkdownRenderer", raw: collectPiboRaw(current) });
943
+ const sharedCard = current.getAttribute && current.getAttribute("data-shared-terminal-card");
944
+ if (sharedCard) pushHint(hints, { kind: "pibo-shared-card", confidence: "high", id: sharedCard, component: component || "TerminalCard", raw: collectPiboRaw(current) });
945
+ const sharedField = current.getAttribute && current.getAttribute("data-shared-status-field");
946
+ if (sharedField) pushHint(hints, { kind: "pibo-shared-card", confidence: "high", id: "status-field:" + sharedField, component: component || "TerminalStatusCard", raw: collectPiboRaw(current) });
947
+ if (current.getAttribute && current.getAttribute("data-pibo-terminal-row")) {
948
+ pushHint(hints, { kind: "pibo-terminal-row", confidence: "high", id: current.getAttribute("data-row-id") || current.getAttribute("data-row-kind") || "terminal-row", component: component || "TerminalRow", raw: collectPiboRaw(current) });
949
+ }
950
+ const testId = current.getAttribute && (current.getAttribute("data-testid") || current.getAttribute("data-test-id") || current.getAttribute("data-cy") || current.getAttribute("data-qa"));
951
+ if (testId) {
952
+ const testHint = { kind: "test-id", confidence: "high", id: testId, raw: { tagName } };
953
+ if (/^virtuoso/i.test(testId) || /virtuoso/i.test(testId)) deferredTestHints.push(testHint);
954
+ else pushHint(hints, testHint);
955
+ }
956
+ const locatorId = current.getAttribute && current.getAttribute("data-locatorjs-id");
957
+ if (locatorId) pushHint(hints, { kind: "locatorjs", confidence: "high", id: locatorId, raw: collectLocatorRaw(current) });
958
+ current = current.parentElement || (current.getRootNode && current.getRootNode().host) || null;
959
+ }
960
+ if (!hints.length) deferredTestHints.slice(0, 2).forEach((hint) => pushHint(hints, hint));
961
+ return hints;
962
+ }
963
+
964
+ function pushHint(hints, hint) {
965
+ if (!hint || !hint.kind) return;
966
+ if (hasEquivalentHint(hints, hint)) return;
967
+ hints.push(hint);
968
+ }
969
+
970
+ function hasEquivalentHint(hints, hint) {
971
+ return hints.some((existing) => existing.kind === hint.kind && (existing.id || "") === (hint.id || "") && (existing.component || "") === (hint.component || ""));
972
+ }
973
+
974
+ function collectPiboRaw(element) {
975
+ const raw = { tagName: (element.tagName || "").toLowerCase() };
976
+ for (const attr of ["data-pibo-id", "data-pibo-component", "data-pibo-debug", "data-pibo-session-id", "data-pibo-title", "data-pibo-selected", "data-pibo-state", "data-pibo-terminal-row", "data-pibo-markdown-node", "data-pibo-markdown-kind", "data-shared-terminal-card", "data-shared-status-field", "data-row-id", "data-row-kind", "data-row-status", "data-trace-node-id", "data-event-id", "data-run-id", "data-order-source", "data-order-stream-id", "data-order-frame-index"]) {
977
+ const value = element.getAttribute && element.getAttribute(attr);
978
+ if (value) raw[attr.replace(/^data-/, "").replace(/-([a-z])/g, (_, char) => char.toUpperCase())] = cap(value, 300);
979
+ }
980
+ return raw;
981
+ }
982
+
983
+ function locatorHint(element) {
984
+ let current = element;
985
+ while (current && current.nodeType === 1) {
986
+ const raw = collectLocatorRaw(current);
987
+ const file = raw.file || raw.sourceFile || raw.source;
988
+ const component = raw.component || raw.componentName;
989
+ if (file || component || raw.line || raw.column) {
990
+ return { kind: "locatorjs", confidence: "high", id: raw.id, file, line: numberOrUndefined(raw.line), column: numberOrUndefined(raw.column), component, raw };
991
+ }
992
+ current = current.parentElement || (current.getRootNode && current.getRootNode().host) || null;
993
+ }
994
+ return undefined;
995
+ }
996
+
997
+ function collectLocatorRaw(element) {
998
+ const names = ["data-locatorjs-id", "data-locatorjs-source", "data-locatorjs-file", "data-locatorjs-line", "data-locatorjs-column", "data-locatorjs-component", "data-source", "data-source-file", "data-source-line", "data-source-column", "data-component", "data-component-path"];
999
+ const raw = {};
1000
+ for (const name of names) {
1001
+ const value = element.getAttribute && element.getAttribute(name);
1002
+ if (value) raw[name.replace(/^data-/, "").replace(/-([a-z])/g, (_, char) => char.toUpperCase())] = cap(value, 300);
1003
+ }
1004
+ return raw;
1005
+ }
1006
+
1007
+ function reactHint(element) {
1008
+ let current = element;
1009
+ while (current && current.nodeType === 1) {
1010
+ const key = Object.keys(current).find((name) => name.startsWith("__reactFiber$") || name.startsWith("__reactInternalInstance$"));
1011
+ let fiber = key ? current[key] : undefined;
1012
+ const names = [];
1013
+ let source;
1014
+ let guard = 0;
1015
+ while (fiber && guard < 20) {
1016
+ const type = fiber.elementType || fiber.type;
1017
+ const name = typeof type === "function" ? (type.displayName || type.name) : (typeof type === "string" ? type : undefined);
1018
+ if (name) names.push(name);
1019
+ if (!source && fiber._debugSource) source = fiber._debugSource;
1020
+ fiber = fiber.return;
1021
+ guard += 1;
1022
+ }
1023
+ if (names.length || source) {
1024
+ return {
1025
+ kind: source ? "jsx-source" : "react-fiber",
1026
+ confidence: "medium",
1027
+ file: source && source.fileName,
1028
+ line: source && source.lineNumber,
1029
+ column: source && source.columnNumber,
1030
+ component: names[0],
1031
+ componentPath: names.slice(0, 12),
1032
+ raw: source ? { source } : { components: names.slice(0, 12) },
1033
+ };
1034
+ }
1035
+ current = current.parentElement || null;
1036
+ }
1037
+ return undefined;
1038
+ }
1039
+
1040
+ function numberOrUndefined(value) {
1041
+ const parsed = Number(value);
1042
+ return Number.isFinite(parsed) ? parsed : undefined;
1043
+ }
1044
+
1045
+ document.addEventListener("mousemove", onMouseMove, true);
1046
+ document.addEventListener("click", onClick, true);
1047
+ document.addEventListener("keydown", onKeyDown, true);
1048
+ window.addEventListener("pibo:web-annotation-shortcut-changed", onShortcutChanged);
1049
+ window.addEventListener("pagehide", onPageHide);
1050
+ window.addEventListener("resize", onViewportChange);
1051
+ if (window.visualViewport) {
1052
+ window.visualViewport.addEventListener("resize", onViewportChange);
1053
+ window.visualViewport.addEventListener("scroll", onViewportChange);
1054
+ }
1055
+ updateUi();
1056
+
1057
+ const api = {
1058
+ bindingId: config.bindingId,
1059
+ piboSessionId: config.piboSessionId,
1060
+ injectedAt: new Date().toISOString(),
1061
+ setActive(value) {
1062
+ state.active = Boolean(value);
1063
+ updateUi();
1064
+ publishOverlayState("set-active");
1065
+ },
1066
+ setMode(value) {
1067
+ state.mode = value === "pin" ? "pin" : "element";
1068
+ state.active = true;
1069
+ updateUi();
1070
+ publishOverlayState("set-mode");
1071
+ },
1072
+ setShortcut(value) { setShortcut(value); },
1073
+ getState() { return { active: state.active, toolbarExpanded: state.toolbarExpanded, mode: state.mode, shortcut: state.shortcut.label, submissions: state.submissions.slice(), lastError: state.lastError }; },
1074
+ remove() {
1075
+ state.active = false;
1076
+ publishOverlayState("removed", false);
1077
+ document.removeEventListener("mousemove", onMouseMove, true);
1078
+ document.removeEventListener("click", onClick, true);
1079
+ document.removeEventListener("keydown", onKeyDown, true);
1080
+ window.removeEventListener("pibo:web-annotation-shortcut-changed", onShortcutChanged);
1081
+ window.removeEventListener("pagehide", onPageHide);
1082
+ window.removeEventListener("resize", onViewportChange);
1083
+ if (window.visualViewport) {
1084
+ window.visualViewport.removeEventListener("resize", onViewportChange);
1085
+ window.visualViewport.removeEventListener("scroll", onViewportChange);
1086
+ }
1087
+ if (state.pendingFrame) cancelAnimationFrame(state.pendingFrame);
1088
+ closePopup();
1089
+ outline.remove();
1090
+ host.remove();
1091
+ if (window.__piboWebAnnotations && window.__piboWebAnnotations.bindingId === config.bindingId) delete window.__piboWebAnnotations;
1092
+ },
1093
+ };
1094
+ window.__piboWebAnnotations = api;
1095
+ publishOverlayState("installed");
1096
+ return { ok: true, url: location.href, title: document.title || "" };
1097
1097
  })()`;
1098
1098
  }
1099
1099
  function buildStopExpression() {
1100
- return `(() => {
1101
- const previous = window.__piboWebAnnotations;
1102
- if (previous && typeof previous.remove === "function") previous.remove();
1103
- const root = document.getElementById("pibo-web-annotation-overlay");
1104
- if (root) root.remove();
1105
- return { ok: true };
1100
+ return `(() => {
1101
+ const previous = window.__piboWebAnnotations;
1102
+ if (previous && typeof previous.remove === "function") previous.remove();
1103
+ const root = document.getElementById("pibo-web-annotation-overlay");
1104
+ if (root) root.remove();
1105
+ return { ok: true };
1106
1106
  })()`;
1107
1107
  }