@pasko70/pibo 1.9.13 → 1.10.1

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 (163) 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-profiles.js +2 -2
  8. package/dist/apps/chat/agent-store.js +263 -250
  9. package/dist/apps/chat/chat-request-normalizers.js +17 -0
  10. package/dist/apps/chat/data/chat-data-mappers.js +2 -0
  11. package/dist/apps/chat/data/project-service.js +168 -168
  12. package/dist/apps/chat/data/read-state-service.js +18 -18
  13. package/dist/apps/chat/data/session-query-service.js +25 -25
  14. package/dist/apps/chat/data/timeline-query-service.js +19 -19
  15. package/dist/apps/chat/loop-api.js +181 -0
  16. package/dist/apps/chat/static-assets.js +854 -854
  17. package/dist/apps/chat/web-app.js +36 -16
  18. package/dist/apps/chat/workflow-manual-trigger-runtime.js +149 -46
  19. package/dist/apps/chat/workflow-persistence.js +255 -255
  20. package/dist/apps/chat-ui/assets/{dist-BZ2eTC4f.js → dist-3Sts0Afa.js} +1 -1
  21. package/dist/apps/chat-ui/assets/{dist-BHa-kcGl.js → dist-4NlLjLs7.js} +1 -1
  22. package/dist/apps/chat-ui/assets/{dist-yCYNNb5d.js → dist-BA6mhAec.js} +1 -1
  23. package/dist/apps/chat-ui/assets/{dist-D811wJeV.js → dist-BPl-fzRB.js} +1 -1
  24. package/dist/apps/chat-ui/assets/{dist-Dq4GxJi3.js → dist-BPnn9e2B.js} +1 -1
  25. package/dist/apps/chat-ui/assets/{dist-CzE6k3F3.js → dist-BpfBSMzK.js} +1 -1
  26. package/dist/apps/chat-ui/assets/{dist-CnVsqwSG.js → dist-Cvx5M97R.js} +1 -1
  27. package/dist/apps/chat-ui/assets/{dist-WyXdYl-w.js → dist-DnXWNQRm.js} +1 -1
  28. package/dist/apps/chat-ui/assets/{dist-BNMu92bb.js → dist-hU-2oAb6.js} +1 -1
  29. package/dist/apps/chat-ui/assets/{dist-BCB6zezO.js → dist-r31x5Fcc.js} +1 -1
  30. package/dist/apps/chat-ui/assets/{dist-HqTN67dc.js → dist-uF6UXzI7.js} +1 -1
  31. package/dist/apps/chat-ui/assets/index-CaTGYBOS.js +173 -0
  32. package/dist/apps/chat-ui/assets/{index-C0x9nEcf.css → index-al8DeEjA.css} +1 -1
  33. package/dist/apps/chat-ui/index.html +18 -18
  34. package/dist/apps/chat-ui/manifest.webmanifest +25 -25
  35. package/dist/apps/chat-ui/sw.js +47 -47
  36. package/dist/apps/chat-vscode-web/assets/index-CK4SMZuu.js +41 -0
  37. package/dist/apps/chat-vscode-web/index.html +13 -13
  38. package/dist/apps/cli-ui/cliSessionsCommand.js +23 -23
  39. package/dist/apps/context-files-ui/index.html +11 -11
  40. package/dist/cli-session/localSessionSource.js +34 -12
  41. package/dist/cli.js +61 -44
  42. package/dist/compute/cli.js +54 -54
  43. package/dist/compute/resource-health.js +35 -2
  44. package/dist/core/events.js +6 -1
  45. package/dist/core/routed-session.js +97 -5
  46. package/dist/core/runtime.js +6 -1
  47. package/dist/core/session-router.js +27 -8
  48. package/dist/cron/cli.js +15 -15
  49. package/dist/cron/store.js +49 -49
  50. package/dist/data/cli.js +23 -23
  51. package/dist/data/event-log.js +23 -23
  52. package/dist/data/ingest-service.js +3 -1
  53. package/dist/data/message-store.js +27 -27
  54. package/dist/data/navigation-store.js +9 -9
  55. package/dist/data/observation-store.js +4 -4
  56. package/dist/data/payload-store.js +17 -17
  57. package/dist/data/schema.js +433 -433
  58. package/dist/data/session-store.js +4 -4
  59. package/dist/data/telemetry-queries.js +54 -54
  60. package/dist/data/telemetry.js +197 -197
  61. package/dist/debug/events.js +12 -12
  62. package/dist/debug/failures.js +6 -6
  63. package/dist/debug/index.js +231 -227
  64. package/dist/debug/messages.js +6 -6
  65. package/dist/debug/pty.js +124 -124
  66. package/dist/debug/session.js +29 -29
  67. package/dist/debug/tools.js +5 -5
  68. package/dist/debug/trace.js +42 -7
  69. package/dist/debug/web-snapshot-browser-scripts.js +294 -294
  70. package/dist/debug/web-streaming-browser-library.js +925 -925
  71. package/dist/debug/web-streaming-browser-scripts.js +232 -232
  72. package/dist/debug/web-streaming-provider-telemetry.js +4 -4
  73. package/dist/debug/web.js +93 -93
  74. package/dist/gateway/cli.js +19 -19
  75. package/dist/gateway/server.js +4 -2
  76. package/dist/gateway/web.js +2 -2
  77. package/dist/index.js +1 -0
  78. package/dist/loops/accounting.js +19 -0
  79. package/dist/loops/channel.js +8 -0
  80. package/dist/loops/cli.js +224 -0
  81. package/dist/loops/plugin.js +16 -0
  82. package/dist/loops/prompts.js +86 -0
  83. package/dist/loops/service.js +446 -0
  84. package/dist/loops/stopping.js +170 -0
  85. package/dist/loops/store.js +642 -0
  86. package/dist/loops/templates.js +232 -0
  87. package/dist/loops/tools.js +184 -0
  88. package/dist/loops/types.js +1 -0
  89. package/dist/mcp/config-command.js +53 -53
  90. package/dist/mcp/index.js +21 -21
  91. package/dist/mcp/registry.js +11 -11
  92. package/dist/pi-packages/cli.js +11 -11
  93. package/dist/plugins/builtin.js +8 -0
  94. package/dist/plugins/context-files-store.js +110 -110
  95. package/dist/plugins/context-files.js +4 -4
  96. package/dist/plugins/registry.js +23 -12
  97. package/dist/ralph/cli.js +18 -18
  98. package/dist/ralph/templates.js +140 -140
  99. package/dist/reliability/store.js +256 -229
  100. package/dist/runs/lifecycle.js +59 -0
  101. package/dist/runs/registry.js +47 -1
  102. package/dist/runs/tools.js +31 -13
  103. package/dist/session-ui/terminalRows.js +66 -2
  104. package/dist/sessions/pibo-data-store.js +16 -16
  105. package/dist/sessions/sqlite-store.js +53 -53
  106. package/dist/setup/cli.js +58 -58
  107. package/dist/shared/trace-async-agent-runs.js +4 -4
  108. package/dist/shared/trace-event-projection.js +59 -19
  109. package/dist/shared/trace-nodes.js +5 -0
  110. package/dist/shared/trace-page-merge.js +15 -0
  111. package/dist/shared/trace-run-notifications.js +3 -1
  112. package/dist/signals/projector.js +6 -2
  113. package/dist/tools/agent-browser-wrapper.js +80 -80
  114. package/dist/tools/browser-pool.js +50 -0
  115. package/dist/tools/browser-use-cdp.js +12 -12
  116. package/dist/tools/browser-use-leases.js +12 -8
  117. package/dist/tools/browser-use-wrapper.js +762 -762
  118. package/dist/tools/guides.js +596 -538
  119. package/dist/tools/index.js +123 -99
  120. package/dist/tools/registry.js +21 -3
  121. package/dist/tools/runtime/node-worker-source.js +205 -205
  122. package/dist/tools/runtime/python-worker-source.js +177 -177
  123. package/dist/vscode/cli.js +9 -9
  124. package/dist/web-annotations/cdp.js +900 -900
  125. package/dist/web-annotations/store.js +96 -96
  126. package/docs/README.md +23 -23
  127. package/docs/ops/install-developer-host.md +112 -112
  128. package/docs/ops/install-user-host.md +96 -96
  129. package/docs/ops/upgrade-user-to-developer-host.md +69 -69
  130. package/docs/ops/vscode-extension-release.md +160 -160
  131. package/package.json +99 -95
  132. package/skills/builtin/graphify/SKILL.md +52 -52
  133. package/skills/builtin/loop/SKILL.md +78 -0
  134. package/skills/builtin/pi-agent-harness/SKILL.md +319 -319
  135. package/skills/builtin/pi-agent-harness/agents/openai.yaml +4 -4
  136. package/skills/builtin/pibo-docker-system/SKILL.md +170 -170
  137. package/skills/builtin/pibo-spec-writing/SKILL.md +330 -330
  138. package/skills/builtin/prd/SKILL.md +143 -143
  139. package/skills/builtin/ralph-loop/SKILL.md +361 -359
  140. package/skills/builtin/ralph-prd-json/SKILL.md +123 -123
  141. package/skills/builtin/skill-creator/LICENSE.txt +201 -201
  142. package/skills/builtin/skill-creator/SKILL.md +513 -513
  143. package/skills/builtin/skill-creator/agents/analyzer.md +274 -274
  144. package/skills/builtin/skill-creator/agents/comparator.md +202 -202
  145. package/skills/builtin/skill-creator/agents/grader.md +223 -223
  146. package/skills/builtin/skill-creator/assets/eval_review.html +146 -146
  147. package/skills/builtin/skill-creator/eval-viewer/generate_review.py +471 -471
  148. package/skills/builtin/skill-creator/eval-viewer/viewer.html +1325 -1325
  149. package/skills/builtin/skill-creator/references/schemas.md +430 -430
  150. package/skills/builtin/skill-creator/scripts/aggregate_benchmark.py +401 -401
  151. package/skills/builtin/skill-creator/scripts/generate_report.py +326 -326
  152. package/skills/builtin/skill-creator/scripts/improve_description.py +247 -247
  153. package/skills/builtin/skill-creator/scripts/package_skill.py +136 -136
  154. package/skills/builtin/skill-creator/scripts/quick_validate.py +102 -102
  155. package/skills/builtin/skill-creator/scripts/run_eval.py +310 -310
  156. package/skills/builtin/skill-creator/scripts/run_loop.py +328 -328
  157. package/skills/builtin/skill-creator/scripts/utils.py +47 -47
  158. package/skills/builtin/web-annotations/SKILL.md +93 -93
  159. package/src/mcp/LICENSE.mcp-cli +21 -21
  160. package/dist/apps/chat-ui/assets/index-DwHJfmiF.js +0 -173
  161. package/dist/apps/chat-vscode-web/assets/index-BAMxIaI_.js +0 -41
  162. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  163. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.9.13.vsix +0 -0
@@ -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
  }