copilot-tap-extension 2.0.7 → 2.0.9

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 (58) hide show
  1. package/README.md +4 -1
  2. package/SOUL.md +51 -0
  3. package/bin/install.mjs +7 -1
  4. package/dist/copilot-instructions.md +15 -0
  5. package/dist/extension.mjs +823 -29
  6. package/dist/skills/tap-goal/SKILL.md +13 -2
  7. package/dist/skills/tap-loop/SKILL.md +6 -0
  8. package/dist/skills/tap-monitor/SKILL.md +19 -3
  9. package/dist/skills/tap-orchestrate/SKILL.md +81 -0
  10. package/dist/version.json +1 -1
  11. package/docs/adr/0001-persistent-config-default-ownership.md +33 -0
  12. package/docs/adr/0002-local-provider-gateway-runtime-security.md +36 -0
  13. package/docs/adr/0003-emitter-delivery-lifecycle.md +68 -0
  14. package/docs/adr/0004-persistent-config-canonical-streams.md +86 -0
  15. package/docs/adr/0005-provider-sdk-push-and-dynamic-tools.md +48 -0
  16. package/docs/adr/0006-command-emitter-cwd-workspace-boundary.md +46 -0
  17. package/docs/adr/0007-runtime-session-workspace-context.md +62 -0
  18. package/docs/evals.md +41 -0
  19. package/docs/evolution-of-tap-icon.html +989 -0
  20. package/docs/providers.md +242 -0
  21. package/docs/recipes/adaptive-agent.md +303 -0
  22. package/docs/recipes/agent-brainstorm/100-extension-ideas.md +288 -0
  23. package/docs/recipes/agent-brainstorm/deep-ideas.md +216 -0
  24. package/docs/recipes/ambient-guardian.md +314 -0
  25. package/docs/recipes/browser-bridge.md +162 -0
  26. package/docs/recipes/codex-goals-for-tap-goal.md +136 -0
  27. package/docs/recipes/copilot-sdk-canvas.md +147 -0
  28. package/docs/recipes/deferred-cognition.md +310 -0
  29. package/docs/recipes/provider-integration-patterns.md +93 -0
  30. package/docs/recipes/provider-interface-advanced.md +1364 -0
  31. package/docs/recipes/provider-interface-core-profile.md +568 -0
  32. package/docs/recipes/tap-control-plane-roadmap.md +60 -0
  33. package/docs/recipes/universal-tool-gateway.md +202 -0
  34. package/docs/reference.md +229 -0
  35. package/docs/use-cases.md +348 -0
  36. package/package.json +4 -1
  37. package/providers/detour/README.md +84 -0
  38. package/providers/detour/bridge.js +219 -0
  39. package/providers/detour/index.mjs +322 -0
  40. package/providers/detour/package-lock.json +577 -0
  41. package/providers/detour/package.json +19 -0
  42. package/providers/detour/scripts/build.mjs +31 -0
  43. package/providers/detour/src/bridge.js +256 -0
  44. package/providers/detour/src/contracts.js +40 -0
  45. package/providers/detour/src/inspector.js +260 -0
  46. package/providers/detour/src/inspector.test.mjs +53 -0
  47. package/providers/detour/src/panel.js +465 -0
  48. package/providers/detour/src/provider-core.js +233 -0
  49. package/providers/detour/src/provider-core.test.mjs +185 -0
  50. package/providers/detour/src/react-context-core.js +143 -0
  51. package/providers/detour/src/react-context.js +44 -0
  52. package/providers/detour/src/react-context.test.mjs +41 -0
  53. package/providers/templates/README.md +23 -0
  54. package/providers/templates/ci-review-provider.mjs +46 -0
  55. package/providers/templates/detour-workflow-provider.mjs +41 -0
  56. package/providers/templates/jira-github-provider.mjs +42 -0
  57. package/providers/templates/provider-utils.mjs +45 -0
  58. package/providers/templates/sast-triage-provider.mjs +51 -0
@@ -0,0 +1,465 @@
1
+ /**
2
+ * Detour Context Panel — floating UI for element inspection, annotations, and agent chat.
3
+ *
4
+ * Uses regular DOM with prefixed classes (__dp-*) for isolation.
5
+ * No Shadow DOM — simpler, more compatible.
6
+ */
7
+
8
+ import { deepElementFromPoint, extractElementContext, getElementDisplayName, buildSelector, generateAnnotationMarkdown } from "./inspector.js";
9
+ import { isReactDetected } from "./react-context.js";
10
+ import { DETAIL_LEVELS, DETAIL_LEVEL_OPTIONS, INTENT_TOKENS, INTENT_OPTIONS, MESSAGE_TYPES } from "./contracts.js";
11
+
12
+ export function createPanel(bridge) {
13
+ let detailLevel = DETAIL_LEVELS.STANDARD;
14
+ let annotations = [];
15
+ let pickerActive = false;
16
+ let panelOpen = false;
17
+ let hoveredElement = null;
18
+ let highlightOverlay = null;
19
+ let labelOverlay = null;
20
+ let pickerAbort = null;
21
+ let activePopup = null;
22
+ const markers = new Map();
23
+
24
+ // ── Inject styles ─────────────────────────────────────────────────────
25
+ const styleEl = document.createElement("style");
26
+ styleEl.textContent = `
27
+ .__dp-fab {
28
+ all: initial; position: fixed !important; bottom: 16px !important; right: 16px !important;
29
+ width: 44px !important; height: 44px !important; border-radius: 50% !important;
30
+ background: linear-gradient(135deg, #667eea, #764ba2) !important;
31
+ color: #fff !important; border: none !important; cursor: pointer !important;
32
+ display: flex !important; align-items: center !important; justify-content: center !important;
33
+ font-size: 20px !important; box-shadow: 0 4px 16px rgba(0,0,0,.3) !important;
34
+ z-index: 2147483647 !important; font-family: system-ui !important;
35
+ transition: transform .2s !important;
36
+ }
37
+ .__dp-fab:hover { transform: scale(1.1) !important; }
38
+ .__dp-fab.active { background: linear-gradient(135deg, #e53e3e, #dd6b20) !important; }
39
+
40
+ .__dp-panel {
41
+ all: initial; position: fixed !important; bottom: 72px !important; right: 16px !important;
42
+ width: 360px !important; max-height: 500px !important;
43
+ background: #1a1a2e !important; color: #e0e0e0 !important;
44
+ border-radius: 12px !important; overflow: hidden !important;
45
+ box-shadow: 0 8px 40px rgba(0,0,0,.5) !important;
46
+ font: 13px/1.4 -apple-system, system-ui, sans-serif !important;
47
+ z-index: 2147483647 !important; display: flex !important; flex-direction: column !important;
48
+ }
49
+ .__dp-panel.hidden { display: none !important; }
50
+
51
+ .__dp-header {
52
+ padding: 12px 16px !important; background: #16213e !important;
53
+ display: flex !important; align-items: center !important; justify-content: space-between !important;
54
+ border-bottom: 1px solid #2a2a4a !important;
55
+ }
56
+ .__dp-title { font: 600 14px system-ui !important; color: #fff !important; }
57
+ .__dp-react { color: #61dafb !important; font-size: 11px !important; margin-left: 6px !important; }
58
+ .__dp-status { font-size: 11px !important; color: #8888aa !important; }
59
+
60
+ .__dp-toolbar {
61
+ padding: 8px 12px !important; display: flex !important; gap: 6px !important;
62
+ align-items: center !important; border-bottom: 1px solid #2a2a4a !important;
63
+ }
64
+ .__dp-btn {
65
+ all: initial; padding: 5px 10px !important; border-radius: 6px !important;
66
+ border: 1px solid #3a3a5a !important; background: #2a2a4a !important;
67
+ color: #ccc !important; cursor: pointer !important; font: 12px system-ui !important;
68
+ }
69
+ .__dp-btn:hover { background: #3a3a6a !important; }
70
+ .__dp-btn.active { background: #667eea !important; color: #fff !important; border-color: #667eea !important; }
71
+ .__dp-btn.danger { background: #e53e3e !important; border-color: #e53e3e !important; color: #fff !important; }
72
+ .__dp-select {
73
+ all: initial; padding: 4px 6px !important; border-radius: 6px !important;
74
+ border: 1px solid #3a3a5a !important; background: #2a2a4a !important;
75
+ color: #ccc !important; font: 12px system-ui !important; cursor: pointer !important;
76
+ }
77
+
78
+ .__dp-list {
79
+ flex: 1 !important; overflow-y: auto !important; padding: 8px 12px !important;
80
+ max-height: 260px !important; min-height: 40px !important;
81
+ }
82
+ .__dp-ann {
83
+ padding: 8px 10px !important; margin-bottom: 6px !important;
84
+ background: #2a2a4a !important; border-radius: 8px !important;
85
+ border-left: 3px solid #667eea !important;
86
+ }
87
+ .__dp-ann-head { display: flex !important; justify-content: space-between !important; align-items: center !important; }
88
+ .__dp-ann-name { font: 600 12px system-ui !important; color: #fff !important; }
89
+ .__dp-ann-intent {
90
+ font: 10px system-ui !important; padding: 2px 6px !important; border-radius: 4px !important;
91
+ background: #3a3a5a !important; color: #aaa !important; text-transform: uppercase !important;
92
+ }
93
+ .__dp-ann-intent.fix { background: #e53e3e33 !important; color: #fc8181 !important; }
94
+ .__dp-ann-intent.change { background: #ecc94b33 !important; color: #ecc94b !important; }
95
+ .__dp-ann-intent.question { background: #667eea33 !important; color: #a3bffa !important; }
96
+ .__dp-ann-comment { font: 12px system-ui !important; color: #aaa !important; margin-top: 4px !important; }
97
+ .__dp-ann-del {
98
+ all: initial; color: #666 !important; cursor: pointer !important;
99
+ font: 14px system-ui !important; padding: 2px 4px !important; margin-left: 4px !important;
100
+ }
101
+ .__dp-ann-del:hover { color: #e53e3e !important; }
102
+ .__dp-empty { padding: 20px !important; text-align: center !important; color: #666 !important; font: 12px system-ui !important; }
103
+
104
+ .__dp-chat {
105
+ border-top: 1px solid #2a2a4a !important; padding: 8px 12px !important;
106
+ display: flex !important; gap: 6px !important;
107
+ }
108
+ .__dp-input {
109
+ all: initial; flex: 1 !important; padding: 8px 10px !important; border-radius: 8px !important;
110
+ border: 1px solid #3a3a5a !important; background: #2a2a4a !important; color: #e0e0e0 !important;
111
+ font: 13px system-ui !important;
112
+ }
113
+ .__dp-input:focus { border-color: #667eea !important; outline: none !important; }
114
+ .__dp-send {
115
+ all: initial; padding: 8px 14px !important; border-radius: 8px !important;
116
+ background: #667eea !important; color: #fff !important; cursor: pointer !important;
117
+ font: 600 13px system-ui !important; white-space: nowrap !important;
118
+ }
119
+ .__dp-send:hover { background: #5a6fd6 !important; }
120
+
121
+ .__dp-popup {
122
+ all: initial; position: fixed !important; z-index: 2147483647 !important;
123
+ width: 300px !important; padding: 12px !important; border-radius: 10px !important;
124
+ background: #1a1a2e !important; border: 1px solid #3a3a5a !important;
125
+ box-shadow: 0 8px 32px rgba(0,0,0,.5) !important; font-family: system-ui !important;
126
+ }
127
+ .__dp-popup-name { font: 600 13px system-ui !important; color: #fff !important; margin-bottom: 8px !important; }
128
+ .__dp-popup-ta {
129
+ all: initial; display: block !important; width: 100% !important; height: 60px !important;
130
+ padding: 8px !important; border-radius: 6px !important; border: 1px solid #3a3a5a !important;
131
+ background: #2a2a4a !important; color: #e0e0e0 !important; font: 12px system-ui !important;
132
+ resize: vertical !important; box-sizing: border-box !important;
133
+ }
134
+ .__dp-popup-ta:focus { border-color: #667eea !important; outline: none !important; }
135
+ .__dp-popup-intents { display: flex !important; gap: 4px !important; margin: 8px 0 !important; }
136
+ .__dp-intent-btn {
137
+ all: initial; padding: 3px 8px !important; border-radius: 4px !important;
138
+ border: 1px solid #3a3a5a !important; background: #2a2a4a !important;
139
+ color: #aaa !important; cursor: pointer !important; font: 11px system-ui !important;
140
+ }
141
+ .__dp-intent-btn.selected { border-color: #667eea !important; color: #fff !important; background: #667eea33 !important; }
142
+ .__dp-popup-actions { display: flex !important; gap: 6px !important; justify-content: flex-end !important; margin-top: 8px !important; }
143
+
144
+ .__dp-highlight {
145
+ position: fixed !important; pointer-events: none !important; z-index: 2147483645 !important;
146
+ border: 2px solid #667eea !important; border-radius: 3px !important;
147
+ background: rgba(102,126,234,.08) !important; transition: all .1s !important;
148
+ }
149
+ .__dp-label {
150
+ position: fixed !important; pointer-events: none !important; z-index: 2147483646 !important;
151
+ padding: 4px 8px !important; border-radius: 4px !important;
152
+ background: #1a1a2e !important; color: #e0e0e0 !important; font: 600 11px system-ui !important;
153
+ box-shadow: 0 2px 8px rgba(0,0,0,.4) !important; white-space: nowrap !important;
154
+ }
155
+ .__dp-marker {
156
+ position: absolute !important; width: 22px !important; height: 22px !important;
157
+ border-radius: 50% !important; background: #667eea !important; color: #fff !important;
158
+ display: flex !important; align-items: center !important; justify-content: center !important;
159
+ font: 700 11px system-ui !important; z-index: 2147483646 !important;
160
+ cursor: pointer !important; box-shadow: 0 2px 8px rgba(0,0,0,.4) !important;
161
+ }
162
+ `;
163
+
164
+ // ── Build DOM ─────────────────────────────────────────────────────────
165
+ const fab = document.createElement("button");
166
+ fab.className = "__dp-fab";
167
+ fab.textContent = "⚡";
168
+ fab.title = "Detour Context Panel";
169
+
170
+ const panel = document.createElement("div");
171
+ panel.className = "__dp-panel hidden";
172
+ const detailLevelOptionsHtml = DETAIL_LEVEL_OPTIONS
173
+ .map(({ value, label }) => `<option value="${value}"${value === DETAIL_LEVELS.STANDARD ? " selected" : ""}>${label}</option>`)
174
+ .join("");
175
+ panel.innerHTML = `
176
+ <div class="__dp-header">
177
+ <div><span class="__dp-title">⚡ Detour</span><span class="__dp-react"></span></div>
178
+ <span class="__dp-status">connected</span>
179
+ </div>
180
+ <div class="__dp-toolbar">
181
+ <button class="__dp-btn" data-action="pick">🎯 Pick</button>
182
+ <select class="__dp-select" data-action="detail">
183
+ ${detailLevelOptionsHtml}
184
+ </select>
185
+ <button class="__dp-btn" data-action="submit">📤 Send All</button>
186
+ <button class="__dp-btn danger" data-action="clear" style="margin-left:auto">Clear</button>
187
+ </div>
188
+ <div class="__dp-list"></div>
189
+ <div class="__dp-chat">
190
+ <input class="__dp-input" placeholder="Message the agent..." />
191
+ <button class="__dp-send" data-action="send">Send</button>
192
+ </div>
193
+ `;
194
+
195
+ // ── Refs ──────────────────────────────────────────────────────────────
196
+ const listEl = panel.querySelector(".__dp-list");
197
+ const chatInput = panel.querySelector(".__dp-input");
198
+ const reactBadge = panel.querySelector(".__dp-react");
199
+
200
+ // ── Event delegation on panel (single handler, no per-button wiring) ─
201
+ panel.addEventListener("click", (e) => {
202
+ e.stopPropagation();
203
+ const action = e.target.closest("[data-action]")?.dataset.action;
204
+ const delBtn = e.target.closest("[data-del]");
205
+ if (action === "pick") startPicker();
206
+ else if (action === "submit") submitAll();
207
+ else if (action === "clear") clearAll();
208
+ else if (action === "send") sendChat();
209
+ else if (delBtn) removeAnnotation(delBtn.dataset.del);
210
+ }, true);
211
+
212
+ panel.addEventListener("mousedown", (e) => e.stopPropagation(), true);
213
+ panel.addEventListener("pointerdown", (e) => e.stopPropagation(), true);
214
+ panel.addEventListener("keydown", (e) => {
215
+ e.stopPropagation();
216
+ if (e.key === "Enter" && !e.shiftKey && document.activeElement === chatInput) {
217
+ e.preventDefault();
218
+ sendChat();
219
+ }
220
+ }, true);
221
+
222
+ panel.querySelector(".__dp-select").addEventListener("change", (e) => {
223
+ e.stopPropagation();
224
+ detailLevel = e.target.value;
225
+ }, true);
226
+
227
+ fab.addEventListener("click", (e) => { e.stopPropagation(); togglePanel(); }, true);
228
+ fab.addEventListener("mousedown", (e) => e.stopPropagation(), true);
229
+
230
+ // ── Panel toggle ──────────────────────────────────────────────────────
231
+ function togglePanel() {
232
+ panelOpen = !panelOpen;
233
+ panel.classList.toggle("hidden", !panelOpen);
234
+ fab.classList.toggle("active", panelOpen);
235
+ if (panelOpen) reactBadge.textContent = isReactDetected() ? "⚛ React" : "";
236
+ if (!panelOpen) { stopPicker(); removeAnnotationPopup(); }
237
+ }
238
+
239
+ // ── Annotations ───────────────────────────────────────────────────────
240
+ function addAnnotation(element, context, comment, intent, rect) {
241
+ const annotation = {
242
+ id: "ann-" + Date.now(),
243
+ context, comment: comment || "", intent,
244
+ selector: context?.selector || buildSelector(element),
245
+ timestamp: new Date().toISOString(),
246
+ anchorRect: { x: rect.x, y: rect.y + window.scrollY },
247
+ anchorText: (element.textContent || "").slice(0, 50),
248
+ };
249
+ annotations.push(annotation);
250
+ renderAnnotations();
251
+ placeMarker(annotation);
252
+ bridge.sendMessage(MESSAGE_TYPES.PAGE_ANNOTATE, { annotation });
253
+ }
254
+
255
+ function removeAnnotation(id) {
256
+ annotations = annotations.filter((a) => a.id !== id);
257
+ removeMarker(id);
258
+ renderAnnotations();
259
+ }
260
+
261
+ function clearAll() {
262
+ annotations = [];
263
+ for (const [id] of markers) removeMarker(id);
264
+ renderAnnotations();
265
+ }
266
+
267
+ function renderAnnotations() {
268
+ if (annotations.length === 0) {
269
+ listEl.innerHTML = '<div class="__dp-empty">Click 🎯 Pick to annotate elements</div>';
270
+ return;
271
+ }
272
+ listEl.innerHTML = annotations.map((ann, i) => `
273
+ <div class="__dp-ann">
274
+ <div class="__dp-ann-head">
275
+ <span class="__dp-ann-name">${i + 1}. ${esc(ann.context?.displayName || ann.selector || "Element")}</span>
276
+ <span>
277
+ <span class="__dp-ann-intent ${ann.intent}">${ann.intent}</span>
278
+ <button class="__dp-ann-del" data-del="${ann.id}" title="Remove">×</button>
279
+ </span>
280
+ </div>
281
+ ${ann.comment ? `<div class="__dp-ann-comment">${esc(ann.comment)}</div>` : ""}
282
+ </div>
283
+ `).join("");
284
+ }
285
+
286
+ // ── Page markers ──────────────────────────────────────────────────────
287
+ function placeMarker(ann) {
288
+ const m = document.createElement("div");
289
+ m.className = "__dp-marker";
290
+ m.textContent = annotations.indexOf(ann) + 1;
291
+ m.title = ann.comment || ann.intent;
292
+ m.style.left = ann.anchorRect.x + "px";
293
+ m.style.top = ann.anchorRect.y + "px";
294
+ document.body.appendChild(m);
295
+ markers.set(ann.id, m);
296
+ }
297
+
298
+ function removeMarker(id) {
299
+ const m = markers.get(id);
300
+ if (m && m.parentNode) m.remove();
301
+ markers.delete(id);
302
+ }
303
+
304
+ // ── Annotation popup ─────────────────────────────────────────────────
305
+ function createAnnotationPopup(element, rect) {
306
+ removeAnnotationPopup();
307
+ const ctx = extractElementContext(element, detailLevel);
308
+ const displayName = ctx?.displayName || element.tagName.toLowerCase();
309
+
310
+ const popup = document.createElement("div");
311
+ popup.className = "__dp-popup";
312
+ popup.style.left = Math.min(rect.right + 8, window.innerWidth - 320) + "px";
313
+ popup.style.top = Math.min(rect.top, window.innerHeight - 240) + "px";
314
+ const intentButtonsHtml = INTENT_OPTIONS
315
+ .map(({ value, icon, label }) => `<button class="__dp-intent-btn${value === INTENT_TOKENS.FIX ? " selected" : ""}" data-intent="${value}">${icon} ${label}</button>`)
316
+ .join("");
317
+ popup.innerHTML = `
318
+ <div class="__dp-popup-name">${esc(displayName)}</div>
319
+ <textarea class="__dp-popup-ta" placeholder="What's the feedback?"></textarea>
320
+ <div class="__dp-popup-intents">
321
+ ${intentButtonsHtml}
322
+ </div>
323
+ <div class="__dp-popup-actions">
324
+ <button class="__dp-btn" data-popup="cancel">Cancel</button>
325
+ <button class="__dp-btn active" data-popup="save">Add Note</button>
326
+ </div>
327
+ `;
328
+
329
+ let selectedIntent = INTENT_TOKENS.FIX;
330
+
331
+ popup.addEventListener("click", (e) => {
332
+ e.stopPropagation();
333
+ const intentBtn = e.target.closest("[data-intent]");
334
+ if (intentBtn) {
335
+ popup.querySelectorAll(".__dp-intent-btn").forEach((b) => b.classList.remove("selected"));
336
+ intentBtn.classList.add("selected");
337
+ selectedIntent = intentBtn.dataset.intent;
338
+ }
339
+ const popupAction = e.target.closest("[data-popup]")?.dataset.popup;
340
+ if (popupAction === "cancel") removeAnnotationPopup();
341
+ if (popupAction === "save") {
342
+ addAnnotation(element, ctx, popup.querySelector("textarea").value.trim(), selectedIntent, rect);
343
+ removeAnnotationPopup();
344
+ }
345
+ }, true);
346
+ popup.addEventListener("mousedown", (e) => e.stopPropagation(), true);
347
+ popup.addEventListener("keydown", (e) => e.stopPropagation(), true);
348
+
349
+ document.body.appendChild(popup);
350
+ activePopup = popup;
351
+ setTimeout(() => popup.querySelector("textarea").focus(), 50);
352
+ }
353
+
354
+ function removeAnnotationPopup() {
355
+ if (activePopup) { activePopup.remove(); activePopup = null; }
356
+ }
357
+
358
+ // ── Element picker ────────────────────────────────────────────────────
359
+ function startPicker() {
360
+ if (pickerActive) { stopPicker(); return; }
361
+ pickerActive = true;
362
+ panel.querySelector("[data-action=pick]").classList.add("active");
363
+ pickerAbort = new AbortController();
364
+ const signal = pickerAbort.signal;
365
+
366
+ highlightOverlay = document.createElement("div");
367
+ highlightOverlay.className = "__dp-highlight";
368
+ highlightOverlay.style.display = "none";
369
+ document.body.appendChild(highlightOverlay);
370
+
371
+ labelOverlay = document.createElement("div");
372
+ labelOverlay.className = "__dp-label";
373
+ labelOverlay.style.display = "none";
374
+ document.body.appendChild(labelOverlay);
375
+
376
+ document.addEventListener("mousemove", onPickerMove, { capture: true, signal });
377
+ document.addEventListener("click", onPickerClick, { capture: true, signal });
378
+ document.addEventListener("keydown", (e) => { if (e.key === "Escape") { stopPicker(); e.preventDefault(); } }, { capture: true, signal });
379
+ }
380
+
381
+ function stopPicker() {
382
+ pickerActive = false;
383
+ const pickBtn = panel.querySelector("[data-action=pick]");
384
+ if (pickBtn) pickBtn.classList.remove("active");
385
+ if (pickerAbort) { pickerAbort.abort(); pickerAbort = null; }
386
+ if (highlightOverlay) { highlightOverlay.remove(); highlightOverlay = null; }
387
+ if (labelOverlay) { labelOverlay.remove(); labelOverlay = null; }
388
+ hoveredElement = null;
389
+ }
390
+
391
+ function onPickerMove(e) {
392
+ const el = deepElementFromPoint(e.clientX, e.clientY);
393
+ if (!el || el.closest(".__dp-panel, .__dp-fab, .__dp-popup, .__dp-marker")) {
394
+ if (highlightOverlay) highlightOverlay.style.display = "none";
395
+ if (labelOverlay) labelOverlay.style.display = "none";
396
+ hoveredElement = null;
397
+ return;
398
+ }
399
+ hoveredElement = el;
400
+ const rect = el.getBoundingClientRect();
401
+ Object.assign(highlightOverlay.style, {
402
+ display: "block",
403
+ left: rect.left + "px", top: rect.top + "px",
404
+ width: rect.width + "px", height: rect.height + "px",
405
+ });
406
+ labelOverlay.textContent = getElementDisplayName(el);
407
+ Object.assign(labelOverlay.style, {
408
+ display: "block",
409
+ left: Math.min(e.clientX + 12, window.innerWidth - 200) + "px",
410
+ top: Math.max(e.clientY - 28, 4) + "px",
411
+ });
412
+ }
413
+
414
+ function onPickerClick(e) {
415
+ if (!hoveredElement) return;
416
+ e.preventDefault();
417
+ e.stopPropagation();
418
+ const rect = hoveredElement.getBoundingClientRect();
419
+ createAnnotationPopup(hoveredElement, rect);
420
+ stopPicker();
421
+ }
422
+
423
+ // ── Chat / Send ───────────────────────────────────────────────────────
424
+ function submitAll() {
425
+ if (annotations.length === 0) return;
426
+ const markdown = generateAnnotationMarkdown(annotations, detailLevel);
427
+ bridge.sendMessage(MESSAGE_TYPES.PAGE_CONTEXT, { markdown, annotations, detailLevel });
428
+ }
429
+
430
+ function sendChat() {
431
+ const msg = chatInput.value.trim();
432
+ if (!msg) return;
433
+ if (annotations.length > 0) {
434
+ const markdown = generateAnnotationMarkdown(annotations, detailLevel);
435
+ bridge.sendMessage(MESSAGE_TYPES.PAGE_CONTEXT, { message: msg, markdown, annotations, detailLevel });
436
+ } else {
437
+ bridge.sendMessage(MESSAGE_TYPES.PAGE_MESSAGE, { message: msg });
438
+ }
439
+ chatInput.value = "";
440
+ }
441
+
442
+ function showAgentReply(message) {
443
+ console.log("[Detour] Agent:", message);
444
+ }
445
+
446
+ // ── Mount / unmount ───────────────────────────────────────────────────
447
+ function mount() {
448
+ const target = document.body || document.documentElement;
449
+ target.appendChild(styleEl);
450
+ target.appendChild(fab);
451
+ target.appendChild(panel);
452
+ renderAnnotations();
453
+ }
454
+
455
+ function unmount() {
456
+ stopPicker(); removeAnnotationPopup(); clearAll();
457
+ styleEl.remove(); fab.remove(); panel.remove();
458
+ }
459
+
460
+ function esc(s) {
461
+ return String(s || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
462
+ }
463
+
464
+ return { mount, unmount, showAgentReply, togglePanel };
465
+ }