@weavetab/mcp 2.5.0-beta.5 → 2.5.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.
- package/AGENTS.md +49 -49
- package/ARCHITECTURE.md +590 -590
- package/README.md +216 -100
- package/SECURITY.md +207 -207
- package/TROUBLESHOOTING.md +373 -365
- package/dist/anticaptcha/detector.js +395 -395
- package/dist/anticaptcha/solver.js +130 -130
- package/dist/cdp/bridge.js +73 -73
- package/dist/cdp/connector.js +46 -22
- package/dist/cdp/extension.js +34 -34
- package/dist/cdp/helpers.js +174 -174
- package/dist/cdp/pruner.js +195 -195
- package/dist/cdp/ref_cache.js +66 -66
- package/dist/cdp/security.js +10 -10
- package/dist/cdp/verify.js +57 -57
- package/dist/cdp/walker.js +563 -525
- package/dist/cli/plugin.js +26 -3
- package/dist/config/loader.d.ts +0 -3
- package/dist/config/loader.js +0 -9
- package/dist/index.js +0 -4
- package/dist/intelligence/intent.js +81 -81
- package/dist/intelligence/sessionMemory.js +2 -2
- package/dist/overlay/ghost.js +22 -22
- package/dist/overlay/injector.js +89 -89
- package/dist/plugin/extension_bridge.js +54 -54
- package/dist/plugin/interface.d.ts +7 -0
- package/dist/plugin/loader.js +3 -4
- package/dist/plugin/schema.d.ts +11 -0
- package/dist/plugin/schema.js +59 -0
- package/dist/security/guard.js +18 -18
- package/dist/sensors/actionability.js +167 -167
- package/dist/sensors/guard.js +29 -29
- package/dist/sensors/network.js +15 -17
- package/dist/server.d.ts +1 -1
- package/dist/server.js +173 -144
- package/dist/state/delta.js +6 -7
- package/dist/tools/automation.js +85 -85
- package/dist/tools/canvas.js +48 -25
- package/dist/tools/checkpoint.js +94 -35
- package/dist/tools/click.js +52 -41
- package/dist/tools/clipboard.js +80 -74
- package/dist/tools/design_extract.js +441 -441
- package/dist/tools/dialog.js +13 -13
- package/dist/tools/emulation.d.ts +2 -0
- package/dist/tools/emulation.js +7 -0
- package/dist/tools/fill.js +344 -347
- package/dist/tools/find.js +78 -52
- package/dist/tools/highlight.js +67 -67
- package/dist/tools/inspect.js +54 -25
- package/dist/tools/key.js +42 -35
- package/dist/tools/navigate.d.ts +1 -1
- package/dist/tools/navigate.js +35 -35
- package/dist/tools/office/parse.js +62 -18
- package/dist/tools/pdf.d.ts +9 -0
- package/dist/tools/pdf.js +7 -4
- package/dist/tools/performance.d.ts +11 -4
- package/dist/tools/performance.js +94 -42
- package/dist/tools/performance_test.d.ts +2 -2
- package/dist/tools/performance_test.js +69 -69
- package/dist/tools/pointer.js +116 -116
- package/dist/tools/read.js +99 -67
- package/dist/tools/recording.js +9 -0
- package/dist/tools/scrape.js +146 -146
- package/dist/tools/screenshot.d.ts +2 -0
- package/dist/tools/screenshot.js +46 -4
- package/dist/tools/scroll.d.ts +3 -1
- package/dist/tools/scroll.js +13 -15
- package/dist/tools/select.d.ts +3 -0
- package/dist/tools/select.js +131 -124
- package/dist/tools/snapshot.js +8 -6
- package/dist/tools/storage.d.ts +5 -1
- package/dist/tools/storage.js +16 -0
- package/dist/tools/tabs.d.ts +7 -5
- package/dist/tools/tabs.js +79 -26
- package/dist/tools/thought.js +8 -8
- package/dist/tools/type.js +274 -274
- package/dist/tools/type_secret.js +23 -23
- package/dist/tools/upload.js +3 -3
- package/dist/tools/video_compiler.js +9 -0
- package/dist/tools/wait.js +35 -35
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/DOCUMENTATION.md +583 -585
- package/docs/PLUGINS.md +139 -121
- package/docs/TOOLS.md +3 -4
- package/docs/architecture/blind-injection.md +83 -83
- package/docs/architecture/overview.md +49 -49
- package/docs/configuration/configuration.md +39 -27
- package/docs/configuration/folders.md +62 -55
- package/docs/desktop-automation/electron-apps.md +225 -214
- package/docs/get-started/installation.md +1 -1
- package/docs/get-started/introduction.md +60 -60
- package/docs/get-started/quickstart.md +2 -2
- package/docs/get-started/setup-wizard.md +1 -1
- package/docs/intelligence/site-profiles.md +151 -151
- package/docs/tools/tools.md +582 -631
- package/docs/troubleshooting/trouble-loop.md +31 -31
- package/ext-dist/background.js +415 -415
- package/ext-dist/content.js +2527 -2527
- package/ext-dist/dashboard.html +583 -583
- package/ext-dist/hud/hud.js +3 -3
- package/ext-dist/hud/index.html +20 -20
- package/ext-dist/manifest.json +73 -73
- package/ext-dist/styles.css +223 -223
- package/mcp.json +796 -805
- package/package.json +8 -8
- package/server.json +23 -23
- package/smithery.yaml +7 -11
- package/dist/cdp/walker.test.d.ts +0 -2
- package/dist/cdp/walker.test.js +0 -172
- package/dist/intelligence/sessionMemory.test.d.ts +0 -2
- package/dist/intelligence/sessionMemory.test.js +0 -62
- package/dist/intelligence/trail.test.d.ts +0 -2
- package/dist/intelligence/trail.test.js +0 -110
- package/dist/state/cache.test.d.ts +0 -2
- package/dist/state/cache.test.js +0 -76
- package/dist/state/delta.test.d.ts +0 -2
- package/dist/state/delta.test.js +0 -279
- package/dist/state/groups.d.ts +0 -15
- package/dist/state/groups.js +0 -80
- package/dist/tools/accessibility.d.ts +0 -14
- package/dist/tools/accessibility.js +0 -32
- package/dist/tools/ask.d.ts +0 -14
- package/dist/tools/ask.js +0 -49
- package/dist/tools/click_and_wait.d.ts +0 -33
- package/dist/tools/click_and_wait.js +0 -149
- package/dist/tools/drag.d.ts +0 -15
- package/dist/tools/drag.js +0 -29
- package/dist/tools/geo.d.ts +0 -19
- package/dist/tools/geo.js +0 -29
- package/dist/tools/github/analyze.d.ts +0 -4
- package/dist/tools/github/analyze.js +0 -113
- package/dist/tools/github/cache.d.ts +0 -21
- package/dist/tools/github/cache.js +0 -28
- package/dist/tools/github/index.d.ts +0 -19
- package/dist/tools/github/index.js +0 -40
- package/dist/tools/github/issues.d.ts +0 -9
- package/dist/tools/github/issues.js +0 -88
- package/dist/tools/github/pr.d.ts +0 -5
- package/dist/tools/github/pr.js +0 -88
- package/dist/tools/github/rateLimiter.d.ts +0 -4
- package/dist/tools/github/rateLimiter.js +0 -46
- package/dist/tools/github/read.d.ts +0 -34
- package/dist/tools/github/read.js +0 -74
- package/dist/tools/github/repoContext.d.ts +0 -20
- package/dist/tools/github/repoContext.js +0 -129
- package/dist/tools/hover.d.ts +0 -19
- package/dist/tools/hover.js +0 -57
- package/dist/tools/integration.test.d.ts +0 -2
- package/dist/tools/integration.test.js +0 -96
- package/dist/tools/macro_compiler.d.ts +0 -7
- package/dist/tools/macro_compiler.js +0 -82
- package/dist/tools/mouse.d.ts +0 -21
- package/dist/tools/mouse.js +0 -62
- package/dist/tools/response.d.ts +0 -129
- package/dist/tools/response.js +0 -5
- package/dist/tools/wait_for.d.ts +0 -23
- package/dist/tools/wait_for.js +0 -30
- package/dist/tools/windows.d.ts +0 -22
- package/dist/tools/windows.js +0 -57
package/ext-dist/content.js
CHANGED
|
@@ -1,2527 +1,2527 @@
|
|
|
1
|
-
const browserAPI = typeof browser !== 'undefined' ? browser : chrome;
|
|
2
|
-
// Register this tab with background (skip about:blank and chrome:// pages)
|
|
3
|
-
if (location.href !== 'about:blank' && !location.protocol.startsWith('chrome')) {
|
|
4
|
-
try { browserAPI.runtime.sendMessage({ type: 'REGISTER_TAB' }).catch(function(){}); } catch(e) {}
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
// ── STABLE EXTENSION MARKER ────────────────────────────────────────
|
|
8
|
-
// Unconditionally injected at document root so MCP server can detect the extension reliably.
|
|
9
|
-
if (!document.getElementById('wt-extension-marker')) {
|
|
10
|
-
const marker = document.createElement('meta');
|
|
11
|
-
marker.id = 'wt-extension-marker';
|
|
12
|
-
marker.setAttribute('name', 'weavetab-extension');
|
|
13
|
-
if (document.head) {
|
|
14
|
-
document.head.appendChild(marker);
|
|
15
|
-
} else if (document.documentElement) {
|
|
16
|
-
document.documentElement.appendChild(marker);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// ── SAFE MESSAGE BRIDGE ────────────────────────────────────────
|
|
21
|
-
// Wraps browserAPI.runtime.sendMessage to survive "Extension context invalidated"
|
|
22
|
-
// errors that occur when the extension is reloaded/updated mid-session.
|
|
23
|
-
var __wt_extAlive = true;
|
|
24
|
-
function __wt_safeSendMessage(msg) {
|
|
25
|
-
try {
|
|
26
|
-
if (!browserAPI.runtime || !browserAPI.runtime.id) { __wt_extAlive = false; return; }
|
|
27
|
-
browserAPI.runtime.sendMessage(msg).catch(function(){ __wt_extAlive = false; });
|
|
28
|
-
} catch(e) {
|
|
29
|
-
__wt_extAlive = false;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function __wt_safeAppendToBody(el) {
|
|
34
|
-
if (document.body) {
|
|
35
|
-
document.body.appendChild(el);
|
|
36
|
-
} else {
|
|
37
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
38
|
-
if (document.body) document.body.appendChild(el);
|
|
39
|
-
}, { once: true });
|
|
40
|
-
// Backup for SPAs that might fire DOMContentLoaded early or clear body
|
|
41
|
-
const obs = new MutationObserver((m, observer) => {
|
|
42
|
-
if (document.body) {
|
|
43
|
-
if (!el.parentNode || !document.body.contains(el)) document.body.appendChild(el);
|
|
44
|
-
observer.disconnect();
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
obs.observe(document.documentElement, { childList: true });
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ── HUD MOUNT LAYER (Phase 1: Closed Shadow DOM + Iframe Architecture) ─────
|
|
52
|
-
// Reload-proof, agent-invisible, real-time visual system
|
|
53
|
-
(function mountHUD() {
|
|
54
|
-
// Guard: exit gracefully on chrome:// pages (content scripts blocked)
|
|
55
|
-
if (location.protocol.startsWith('chrome')) return;
|
|
56
|
-
|
|
57
|
-
let host = null;
|
|
58
|
-
let shadow = null;
|
|
59
|
-
let iframe = null;
|
|
60
|
-
let hudState = {};
|
|
61
|
-
|
|
62
|
-
// Create mount point with closed shadow DOM
|
|
63
|
-
function createMount() {
|
|
64
|
-
if (host && document.documentElement.contains(host)) return;
|
|
65
|
-
|
|
66
|
-
host = document.createElement('weavetab-host');
|
|
67
|
-
host.setAttribute('data-wt-internal', 'true');
|
|
68
|
-
host.setAttribute('aria-hidden', 'true');
|
|
69
|
-
host.style.cssText = 'all:initial !important;position:fixed !important;left:0 !important;top:0 !important;width:0 !important;height:0 !important;z-index:2147483647 !important;pointer-events:none !important;background:transparent !important;opacity:1 !important;transform:none !important;filter:none !important;';
|
|
70
|
-
__wt_safeAppendToBody(host);
|
|
71
|
-
|
|
72
|
-
// Closed shadow root — ref lives ONLY here, never on window
|
|
73
|
-
shadow = host.attachShadow({ mode: 'closed' });
|
|
74
|
-
|
|
75
|
-
iframe = document.createElement('iframe');
|
|
76
|
-
iframe.src = browserAPI.runtime.getURL('hud/index.html');
|
|
77
|
-
iframe.id = 'wt-hud-iframe';
|
|
78
|
-
iframe.setAttribute('allowtransparency', 'true');
|
|
79
|
-
// FIX: Start hidden. Expand only after receiving state to prevent white flash.
|
|
80
|
-
Object.assign(iframe.style, {
|
|
81
|
-
position: 'fixed',
|
|
82
|
-
top: '0',
|
|
83
|
-
left: '0',
|
|
84
|
-
width: '0',
|
|
85
|
-
height: '0',
|
|
86
|
-
border: 'none',
|
|
87
|
-
background: 'transparent',
|
|
88
|
-
pointerEvents: 'none',
|
|
89
|
-
zIndex: '2147483647',
|
|
90
|
-
visibility: 'hidden',
|
|
91
|
-
opacity: '0',
|
|
92
|
-
colorScheme: 'light dark'
|
|
93
|
-
});
|
|
94
|
-
shadow.appendChild(iframe);
|
|
95
|
-
|
|
96
|
-
// REHYDRATE: send state to iframe, but DO NOT make visible yet to avoid white flash.
|
|
97
|
-
iframe.onload = function() {
|
|
98
|
-
browserAPI.storage.local.get('wt-hud-state', function(result) {
|
|
99
|
-
hudState = result['wt-hud-state'] || {};
|
|
100
|
-
if (iframe.contentWindow) {
|
|
101
|
-
iframe.contentWindow.postMessage({ type: 'REHYDRATE', state: hudState }, browserAPI.runtime.getURL(''));
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
// Listen for HUD state changes from the iframe to toggle visibility
|
|
107
|
-
window.addEventListener('message', function(e) {
|
|
108
|
-
if (e.data && e.data.type === 'HUD_STATE_CHANGED' && e.source === iframe.contentWindow) {
|
|
109
|
-
const visible = e.data.state && e.data.state.hudVisible !== false;
|
|
110
|
-
if (visible) {
|
|
111
|
-
iframe.style.width = '100vw';
|
|
112
|
-
iframe.style.height = '100vh';
|
|
113
|
-
iframe.style.visibility = 'visible';
|
|
114
|
-
iframe.style.opacity = '1';
|
|
115
|
-
} else {
|
|
116
|
-
iframe.style.width = '0';
|
|
117
|
-
iframe.style.height = '0';
|
|
118
|
-
iframe.style.visibility = 'hidden';
|
|
119
|
-
iframe.style.opacity = '0';
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Navigation guard: re-mount if host is removed (SPA/bfcache safe)
|
|
126
|
-
const observer = new MutationObserver(function() {
|
|
127
|
-
if (!document.documentElement.contains(host)) {
|
|
128
|
-
createMount();
|
|
129
|
-
}
|
|
130
|
-
if (window.__wt_visualComponents && window.__wt_visualComponents.cursor && !document.getElementById('__Weavetab_cursor_host')) {
|
|
131
|
-
if (typeof __wt_recoverVisuals === 'function') __wt_recoverVisuals();
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
observer.observe(document.documentElement, { childList: true });
|
|
135
|
-
|
|
136
|
-
// Real-time event relays (Agent only)
|
|
137
|
-
// Removed physical mousemove, click, and keydown listeners to prevent lag and interference with agent visuals.
|
|
138
|
-
|
|
139
|
-
let scrollTicking = false;
|
|
140
|
-
document.addEventListener('scroll', function() {
|
|
141
|
-
if (!scrollTicking && iframe && iframe.contentWindow) {
|
|
142
|
-
window.requestAnimationFrame(function() {
|
|
143
|
-
iframe.contentWindow.postMessage({
|
|
144
|
-
type: 'RT_SCROLL',
|
|
145
|
-
y: window.scrollY,
|
|
146
|
-
ratio: window.scrollY / document.body.scrollHeight
|
|
147
|
-
}, '*');
|
|
148
|
-
scrollTicking = false;
|
|
149
|
-
});
|
|
150
|
-
scrollTicking = true;
|
|
151
|
-
}
|
|
152
|
-
}, { passive: true });
|
|
153
|
-
|
|
154
|
-
// ── MASTER VISUALS TOGGLE SYSTEM ───────────────────────────
|
|
155
|
-
let __wt_masterVisualsEnabled = true;
|
|
156
|
-
|
|
157
|
-
function __wt_applyMasterVisuals(enabled) {
|
|
158
|
-
__wt_masterVisualsEnabled = !!enabled;
|
|
159
|
-
window.__wt_masterVisualsEnabled = __wt_masterVisualsEnabled;
|
|
160
|
-
|
|
161
|
-
const cursorHost = document.getElementById('__Weavetab_cursor_host');
|
|
162
|
-
if (cursorHost) {
|
|
163
|
-
cursorHost.style.display = __wt_masterVisualsEnabled ? 'block' : 'none';
|
|
164
|
-
}
|
|
165
|
-
if (typeof __weaveCursor !== 'undefined' && __weaveCursor) {
|
|
166
|
-
__weaveCursor.setVisible(__wt_masterVisualsEnabled);
|
|
167
|
-
if (__weaveCursor._cursor) {
|
|
168
|
-
__weaveCursor._cursor.style.display = __wt_masterVisualsEnabled ? 'block' : 'none';
|
|
169
|
-
} else if (__weaveCursor._canvas) {
|
|
170
|
-
__weaveCursor._canvas.style.display = __wt_masterVisualsEnabled ? 'block' : 'none';
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const hudEl = document.getElementById('Weavetab-hud');
|
|
175
|
-
if (hudEl) {
|
|
176
|
-
hudEl.style.display = 'none';
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const hudHost = document.querySelector('weavetab-host');
|
|
180
|
-
if (hudHost) {
|
|
181
|
-
hudHost.style.display = __wt_masterVisualsEnabled ? 'block' : 'none';
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const indicatorEl = document.getElementById('wt-controlled-indicator') || document.querySelector('.wt-controlled-indicator') || document.getElementById('__Weavetab_controlled_indicator');
|
|
185
|
-
if (indicatorEl) {
|
|
186
|
-
indicatorEl.style.display = __wt_masterVisualsEnabled ? 'block' : 'none';
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (!__wt_masterVisualsEnabled) {
|
|
190
|
-
if (typeof clearXRay === 'function') clearXRay();
|
|
191
|
-
document.querySelectorAll('.Weavetab-ripple, .wt-xray-spotlight, .wt-bubble, .__Weavetab_xray').forEach(function(el){
|
|
192
|
-
el.remove();
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
try {
|
|
198
|
-
browserAPI.storage.local.get(['wt_master_visuals'], function(res) {
|
|
199
|
-
if (res && res.wt_master_visuals === false) {
|
|
200
|
-
__wt_applyMasterVisuals(false);
|
|
201
|
-
} else {
|
|
202
|
-
__wt_applyMasterVisuals(true);
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
browserAPI.storage.onChanged.addListener(function(changes, area) {
|
|
207
|
-
if (area === 'local' && changes.wt_master_visuals !== undefined) {
|
|
208
|
-
__wt_applyMasterVisuals(changes.wt_master_visuals.newValue !== false);
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
} catch(e) {}
|
|
212
|
-
|
|
213
|
-
// Ghost CDP cursor relay from background
|
|
214
|
-
browserAPI.runtime.onMessage.addListener(function(msg) {
|
|
215
|
-
if (msg.type === 'WT_TOGGLE_MASTER_VISUALS') {
|
|
216
|
-
__wt_applyMasterVisuals(msg.enabled);
|
|
217
|
-
}
|
|
218
|
-
if (msg.type === 'GHOST_CURSOR_MOVE') {
|
|
219
|
-
if (typeof msg.x === 'number' && typeof msg.y === 'number') {
|
|
220
|
-
moveCursor(msg.x, msg.y, msg.phase);
|
|
221
|
-
}
|
|
222
|
-
if (iframe && iframe.contentWindow) {
|
|
223
|
-
iframe.contentWindow.postMessage({ type: 'RT_CURSOR_MOVE', x: msg.x, y: msg.y, phase: msg.phase }, '*');
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
if (msg.type === 'TOOL_CALL_EVENT' && iframe && iframe.contentWindow) {
|
|
227
|
-
iframe.contentWindow.postMessage({ type: 'TOOL_CALL_EVENT', payload: msg.payload }, '*');
|
|
228
|
-
}
|
|
229
|
-
// Scripted pill toggle — relay to HUD iframe
|
|
230
|
-
if (msg.type === 'WT_SCRIPTED_PILL_TOGGLE' && iframe && iframe.contentWindow) {
|
|
231
|
-
iframe.contentWindow.postMessage({ type: 'WT_SCRIPTED_PILL_TOGGLE', enabled: msg.enabled }, '*');
|
|
232
|
-
}
|
|
233
|
-
// Handle agent thoughts in middle-down HUD
|
|
234
|
-
if (msg.type === 'WT_THOUGHT' || msg.type === 'Weavetab_THOUGHT') {
|
|
235
|
-
__wt_showThought(msg.text, msg);
|
|
236
|
-
}
|
|
237
|
-
if (msg.type === 'WT_CLEAR_THOUGHT' || msg.type === 'Weavetab_CLEAR_THOUGHT') {
|
|
238
|
-
__wt_removeThought();
|
|
239
|
-
}
|
|
240
|
-
// Handle incoming user notes from the extension
|
|
241
|
-
if (msg.type === 'WT_USER_NOTE_INJECT' || msg.type === 'WT_USER_NOTE') {
|
|
242
|
-
window.__wt_chat_queue = window.__wt_chat_queue || [];
|
|
243
|
-
let formattedMsg = msg.text || '';
|
|
244
|
-
if (msg.snipe) {
|
|
245
|
-
formattedMsg = `[USER AREA ANNOTATION ATTACHED (${msg.snipe.mode})]: ` + formattedMsg;
|
|
246
|
-
}
|
|
247
|
-
window.__wt_chat_queue.push(formattedMsg);
|
|
248
|
-
|
|
249
|
-
// Unblock pending ask_bridge polling loops immediately
|
|
250
|
-
window.__wt_ask_bridge = window.__wt_ask_bridge || {};
|
|
251
|
-
if (window.__wt_ask_bridge.resolve) {
|
|
252
|
-
window.__wt_ask_bridge.resolve({
|
|
253
|
-
success: true,
|
|
254
|
-
response: formattedMsg,
|
|
255
|
-
status: 'answered'
|
|
256
|
-
});
|
|
257
|
-
window.__wt_ask_bridge = {};
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Display on-page glassmorphism notification banner if master visuals are enabled
|
|
261
|
-
if (window.__wt_masterVisualsEnabled !== false) {
|
|
262
|
-
__wt_showUserNoteVisualBanner(formattedMsg);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
function __wt_showUserNoteVisualBanner(text) {
|
|
268
|
-
let banner = document.getElementById('wt-user-note-banner');
|
|
269
|
-
if (!banner) {
|
|
270
|
-
banner = document.createElement('div');
|
|
271
|
-
banner.id = 'wt-user-note-banner';
|
|
272
|
-
banner.setAttribute('data-wt-ignore', 'true');
|
|
273
|
-
banner.style.cssText = `
|
|
274
|
-
position: fixed; top: 16px; right: 20px; z-index: 2147483647;
|
|
275
|
-
background: rgba(24, 24, 27, 0.94); backdrop-filter: blur(12px);
|
|
276
|
-
border: 1px solid #38bdf8; color: #f4f4f5; font-weight: 500;
|
|
277
|
-
font-size: 11px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
278
|
-
padding: 8px 16px; border-radius: 20px;
|
|
279
|
-
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6), 0 0 14px rgba(56, 189, 248, 0.4);
|
|
280
|
-
display: flex; align-items: center; gap: 8px;
|
|
281
|
-
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
282
|
-
opacity: 0; transform: translateY(-10px); pointer-events: none;
|
|
283
|
-
`;
|
|
284
|
-
document.documentElement.appendChild(banner);
|
|
285
|
-
}
|
|
286
|
-
const cleanText = text.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').slice(0, 90);
|
|
287
|
-
banner.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#38bdf8" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg><span style="color:#38bdf8;font-weight:600;">Message Received:</span> <span>${cleanText}</span>`;
|
|
288
|
-
requestAnimationFrame(() => {
|
|
289
|
-
banner.style.opacity = '1';
|
|
290
|
-
banner.style.transform = 'translateY(0)';
|
|
291
|
-
});
|
|
292
|
-
setTimeout(() => {
|
|
293
|
-
banner.style.opacity = '0';
|
|
294
|
-
banner.style.transform = 'translateY(-10px)';
|
|
295
|
-
setTimeout(() => banner.remove(), 300);
|
|
296
|
-
}, 4000);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// State persistence bridge: listen for state changes from iframe
|
|
300
|
-
window.addEventListener('message', function(e) {
|
|
301
|
-
// Forward relevant events to the extension background
|
|
302
|
-
if (e.data && (
|
|
303
|
-
e.data.type === 'WT_TOOL_CALL' ||
|
|
304
|
-
e.data.type === 'WT_PLAN_UPDATE' ||
|
|
305
|
-
e.data.type === 'WT_THOUGHT' ||
|
|
306
|
-
e.data.type === 'TOOL_CALL_EVENT' ||
|
|
307
|
-
e.data.type === 'Weavetab_CONFIG' ||
|
|
308
|
-
e.data.type === 'WT_CONFIG_UPDATE'
|
|
309
|
-
)) {
|
|
310
|
-
__wt_safeSendMessage(e.data);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (e.origin !== browserAPI.runtime.getURL('').slice(0, -1)) return;
|
|
315
|
-
if (e.data.type === 'HUD_STATE_CHANGED') {
|
|
316
|
-
hudState = { ...hudState, ...e.data.state };
|
|
317
|
-
browserAPI.storage.local.set({ 'wt-hud-state': hudState });
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
// Listen for toggle command from background
|
|
322
|
-
browserAPI.runtime.onMessage.addListener(function(msg) {
|
|
323
|
-
console.log('[Weavetab Content] Received message:', msg.type);
|
|
324
|
-
if (msg.type === 'TOGGLE_HUD' && iframe && iframe.contentWindow) {
|
|
325
|
-
iframe.contentWindow.postMessage({ type: 'TOGGLE_HUD' }, '*');
|
|
326
|
-
}
|
|
327
|
-
if (msg.type === 'TOGGLE_DEBUG' && iframe && iframe.contentWindow) {
|
|
328
|
-
console.log('[Weavetab Content] Forwarding TOGGLE_DEBUG to iframe');
|
|
329
|
-
iframe.contentWindow.postMessage({ type: 'TOGGLE_DEBUG' }, '*');
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
// Initial mount
|
|
334
|
-
createMount();
|
|
335
|
-
})();
|
|
336
|
-
|
|
337
|
-
// ── UNIFIED VISUAL SYSTEM INITIALIZATION ─────────────────────────
|
|
338
|
-
// Ensures all visual components initialize in correct order and persist across reloads
|
|
339
|
-
window.__wt_visualsReady = false;
|
|
340
|
-
window.__wt_visualComponents = {
|
|
341
|
-
cursor: false,
|
|
342
|
-
feedback: false,
|
|
343
|
-
debug: false
|
|
344
|
-
};
|
|
345
|
-
window.__wt_zIndexCounter = 2147483640; // Start near max z-index
|
|
346
|
-
|
|
347
|
-
function __wt_initVisuals() {
|
|
348
|
-
if (window.__wt_visualsReady) return;
|
|
349
|
-
|
|
350
|
-
// Initialize cursor first
|
|
351
|
-
if (!window.__wt_visualComponents.cursor && window.__weaveCursor_instance) {
|
|
352
|
-
window.__wt_visualComponents.cursor = true;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Check for WeaveTab OS panel (todo list + chat)
|
|
356
|
-
if (!window.__wt_visualComponents.feedback && document.getElementById('wt-main-container')) {
|
|
357
|
-
window.__wt_visualComponents.feedback = true;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Check for debug overlay
|
|
361
|
-
if (!window.__wt_visualComponents.debug && document.getElementById('wt-debug-overlay')) {
|
|
362
|
-
window.__wt_visualComponents.debug = true;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Mark as ready when cursor and panel are initialized
|
|
366
|
-
if (window.__wt_visualComponents.cursor && window.__wt_visualComponents.feedback) {
|
|
367
|
-
window.__wt_visualsReady = true;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Health check every 5 seconds to recover missing components
|
|
372
|
-
setInterval(function() {
|
|
373
|
-
__wt_initVisuals();
|
|
374
|
-
__wt_recoverVisuals();
|
|
375
|
-
}, 5000);
|
|
376
|
-
|
|
377
|
-
// Visual recovery - detect and fix missing/broken components
|
|
378
|
-
function __wt_recoverVisuals() {
|
|
379
|
-
// Recover cursor if missing
|
|
380
|
-
if (window.__wt_visualComponents && window.__wt_visualComponents.cursor && !document.getElementById('__Weavetab_cursor_host')) {
|
|
381
|
-
console.warn('[Weavetab] Cursor host missing, reinitializing...');
|
|
382
|
-
if (window.__weaveCursor_instance && window.__weaveCursor_instance.destroy) {
|
|
383
|
-
window.__weaveCursor_instance.destroy();
|
|
384
|
-
}
|
|
385
|
-
__weaveCursor = new WeaveCursor();
|
|
386
|
-
window.__weaveCursor_instance = __weaveCursor;
|
|
387
|
-
window.__wt_visualComponents.cursor = true;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Recover WeaveTab OS panel if missing
|
|
391
|
-
if (window.__wt_visualComponents.feedback && !document.getElementById('wt-main-container')) {
|
|
392
|
-
console.warn('[Weavetab] WeaveTab OS panel missing, this is handled by page reload');
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Fix z-index conflicts
|
|
396
|
-
var wtElements = document.querySelectorAll('[data-wt-ignore]');
|
|
397
|
-
wtElements.forEach(function(el) {
|
|
398
|
-
if (el.style.zIndex && parseInt(el.style.zIndex) < 2147483640) {
|
|
399
|
-
el.style.zIndex = ++window.__wt_zIndexCounter;
|
|
400
|
-
}
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// ── STALE ELEMENT CLEANUP ─────────────────────────────────────
|
|
405
|
-
// Removes old UI elements from previous server/extension sessions.
|
|
406
|
-
// Observer stays active for the full page lifetime (SPA-safe).
|
|
407
|
-
(function cleanupOldUI() {
|
|
408
|
-
var OLD_IDS = [
|
|
409
|
-
'wt-ghost-cursor','wt-ghost-bubble','wt-ask-panel','wt-cur-canvas',
|
|
410
|
-
'wt-ask-container','wt-msg-panel','wt-ask-title','wt-msg-bar','wt-msg-pill'
|
|
411
|
-
];
|
|
412
|
-
function removeOld() {
|
|
413
|
-
for (var i = 0; i < OLD_IDS.length; i++) {
|
|
414
|
-
var el = document.getElementById(OLD_IDS[i]);
|
|
415
|
-
if (el) el.remove();
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
removeOld();
|
|
419
|
-
// Persistent observer — never disconnects so SPA re-renders stay clean
|
|
420
|
-
var obs = new MutationObserver(function() { removeOld(); });
|
|
421
|
-
if (document.documentElement) {
|
|
422
|
-
obs.observe(document.documentElement, { childList: true, subtree: true });
|
|
423
|
-
} else {
|
|
424
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
425
|
-
if (document.documentElement) obs.observe(document.documentElement, { childList: true, subtree: true });
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
})();
|
|
429
|
-
|
|
430
|
-
// ── UI DESIGN SYSTEM (inject once, styles.css handles visuals) ─
|
|
431
|
-
(function injectWeavetabUI() {
|
|
432
|
-
if (document.getElementById('wt-ui-styles')) return;
|
|
433
|
-
// Guard marker so we only run once
|
|
434
|
-
const m = document.createElement('meta');
|
|
435
|
-
m.id = 'wt-ui-styles';
|
|
436
|
-
(document.head || document.documentElement || document.body).appendChild(m);
|
|
437
|
-
})();
|
|
438
|
-
|
|
439
|
-
// ── THOUGHT BUBBLE ─────────────────────────────────────────────
|
|
440
|
-
let __wt_activeBubble = null;
|
|
441
|
-
let __wt_bubbleTimer = null;
|
|
442
|
-
let __wt_typewriterTimer = null;
|
|
443
|
-
let __wt_feedbackSticky = false;
|
|
444
|
-
let __wt_thoughtBirth = 0;
|
|
445
|
-
let __wt_thoughtQueue = [];
|
|
446
|
-
let __wt_lastRemoveTime = 0;
|
|
447
|
-
let __wt_toolActive = false;
|
|
448
|
-
|
|
449
|
-
function __wt_fadeOutBubble(callback) {
|
|
450
|
-
if (!__wt_activeBubble) { if (callback) callback(); return; }
|
|
451
|
-
var bub = __wt_activeBubble;
|
|
452
|
-
bub.style.transition = 'opacity 0.15s ease-out, transform 0.15s ease-out';
|
|
453
|
-
bub.style.opacity = '0';
|
|
454
|
-
bub.style.transform = 'translateX(-50%) translateY(6px)';
|
|
455
|
-
setTimeout(function() {
|
|
456
|
-
bub.remove();
|
|
457
|
-
if (callback) callback();
|
|
458
|
-
}, 150);
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
function __wt_destroyBubble() {
|
|
462
|
-
if (__wt_bubbleTimer) { clearTimeout(__wt_bubbleTimer); __wt_bubbleTimer = null; }
|
|
463
|
-
if (__wt_typewriterTimer) { clearInterval(__wt_typewriterTimer); __wt_typewriterTimer = null; }
|
|
464
|
-
var current = document.querySelector('.wt-bubble[data-wt-thought]');
|
|
465
|
-
if (current) {
|
|
466
|
-
current.style.transition = 'opacity 0.15s ease-out, transform 0.15s ease-out';
|
|
467
|
-
current.style.opacity = '0';
|
|
468
|
-
current.style.transform = 'translateX(-50%) translateY(6px)';
|
|
469
|
-
setTimeout(function() {
|
|
470
|
-
if (current && current.parentNode) current.remove();
|
|
471
|
-
__wt_activeBubble = null;
|
|
472
|
-
}, 150);
|
|
473
|
-
} else {
|
|
474
|
-
__wt_activeBubble = null;
|
|
475
|
-
}
|
|
476
|
-
__wt_lastRemoveTime = Date.now();
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// ── MIDDLE-DOWN THOUGHT & PRESENCE HUD ─────────────────────────────
|
|
480
|
-
let __wt_thoughtHudEl = null;
|
|
481
|
-
let __wt_thoughtHudTimer = null;
|
|
482
|
-
let __wt_thoughtHudCountdown = null;
|
|
483
|
-
|
|
484
|
-
function __wt_highlightAnchor(anchor) {
|
|
485
|
-
if (!anchor) return;
|
|
486
|
-
try {
|
|
487
|
-
let sel = anchor;
|
|
488
|
-
if (anchor.startsWith('w:')) {
|
|
489
|
-
const stripped = anchor.slice(2);
|
|
490
|
-
sel = `[data-wt-id="${stripped}"]`;
|
|
491
|
-
}
|
|
492
|
-
const el = document.querySelector(sel) || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(sel) : null);
|
|
493
|
-
if (!el) return;
|
|
494
|
-
const r = el.getBoundingClientRect();
|
|
495
|
-
if (r.width === 0 && r.height === 0) return;
|
|
496
|
-
|
|
497
|
-
const ring = document.createElement('div');
|
|
498
|
-
ring.setAttribute('data-wt-ignore', 'true');
|
|
499
|
-
ring.style.cssText = `position:fixed;left:${r.left}px;top:${r.top}px;width:${r.width}px;height:${r.height}px;border:1.5px solid #38bdf8;box-shadow:0 0 16px rgba(56,189,248,0.5);border-radius:4px;z-index:2147483646;pointer-events:none;animation:wt-consider-pulse 1.2s ease-in-out infinite;`;
|
|
500
|
-
(document.getElementById('Weavetab-root')?.shadowRoot || document.body || document.documentElement).appendChild(ring);
|
|
501
|
-
setTimeout(() => {
|
|
502
|
-
ring.style.transition = 'opacity 0.25s ease-out';
|
|
503
|
-
ring.style.opacity = '0';
|
|
504
|
-
setTimeout(() => ring.remove(), 260);
|
|
505
|
-
}, 2400);
|
|
506
|
-
} catch(e) {}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
function __wt_removeThought() {
|
|
510
|
-
if (__wt_thoughtHudTimer) {
|
|
511
|
-
clearTimeout(__wt_thoughtHudTimer);
|
|
512
|
-
__wt_thoughtHudTimer = null;
|
|
513
|
-
}
|
|
514
|
-
if (__wt_thoughtHudCountdown) {
|
|
515
|
-
clearInterval(__wt_thoughtHudCountdown);
|
|
516
|
-
__wt_thoughtHudCountdown = null;
|
|
517
|
-
}
|
|
518
|
-
if (__wt_thoughtHudEl) {
|
|
519
|
-
const el = __wt_thoughtHudEl;
|
|
520
|
-
__wt_thoughtHudEl = null;
|
|
521
|
-
el.style.opacity = '0';
|
|
522
|
-
el.style.transform = 'translateX(-50%) translateY(10px)';
|
|
523
|
-
setTimeout(() => {
|
|
524
|
-
if (el.parentNode) el.remove();
|
|
525
|
-
}, 220);
|
|
526
|
-
}
|
|
527
|
-
__wt_feedbackSticky = false;
|
|
528
|
-
clearTimeout(window.__wt_feedbackStickyTimer);
|
|
529
|
-
__wt_destroyBubble();
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
function __wt_showThought(text, options) {
|
|
533
|
-
if (typeof __wt_masterVisualsEnabled !== 'undefined' && !__wt_masterVisualsEnabled) return;
|
|
534
|
-
if (!text || typeof text !== 'string') return;
|
|
535
|
-
|
|
536
|
-
const opts = options || {};
|
|
537
|
-
const isQuestion = opts.action === 'question' || opts.kind === 'question' || !!opts.ask;
|
|
538
|
-
const anchor = opts.anchor || '';
|
|
539
|
-
|
|
540
|
-
// Contextual visual anchor highlight
|
|
541
|
-
if (anchor) {
|
|
542
|
-
__wt_highlightAnchor(anchor);
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// Clear pending timers
|
|
546
|
-
if (__wt_thoughtHudTimer) {
|
|
547
|
-
clearTimeout(__wt_thoughtHudTimer);
|
|
548
|
-
__wt_thoughtHudTimer = null;
|
|
549
|
-
}
|
|
550
|
-
if (__wt_thoughtHudCountdown) {
|
|
551
|
-
clearInterval(__wt_thoughtHudCountdown);
|
|
552
|
-
__wt_thoughtHudCountdown = null;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
let hud = __wt_thoughtHudEl;
|
|
556
|
-
if (!hud || !hud.parentNode) {
|
|
557
|
-
hud = document.createElement('div');
|
|
558
|
-
hud.id = '__wt_thought_hud';
|
|
559
|
-
hud.setAttribute('data-wt-ignore', 'true');
|
|
560
|
-
hud.style.cssText = `
|
|
561
|
-
position: fixed;
|
|
562
|
-
bottom: 24px;
|
|
563
|
-
left: 50%;
|
|
564
|
-
transform: translateX(-50%) translateY(8px);
|
|
565
|
-
z-index: 2147483647;
|
|
566
|
-
width: 320px;
|
|
567
|
-
max-width: calc(100vw - 32px);
|
|
568
|
-
background: #0b0d12;
|
|
569
|
-
border: 1px solid #1e222b;
|
|
570
|
-
border-radius: 4px;
|
|
571
|
-
padding: 10px 12px;
|
|
572
|
-
box-shadow: 0 10px 30px rgba(0,0,0,0.8), 0 0 1px rgba(255,255,255,0.08);
|
|
573
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Inter", sans-serif;
|
|
574
|
-
color: #f3f4f6;
|
|
575
|
-
pointer-events: auto;
|
|
576
|
-
opacity: 0;
|
|
577
|
-
transition: opacity 0.18s cubic-bezier(0.16, 1, 0.3, 1), transform 0.18s cubic-bezier(0.16, 1, 0.3, 1);
|
|
578
|
-
box-sizing: border-box;
|
|
579
|
-
`;
|
|
580
|
-
(document.getElementById('Weavetab-root')?.shadowRoot || document.body || document.documentElement).appendChild(hud);
|
|
581
|
-
__wt_thoughtHudEl = hud;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
const cleanText = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
585
|
-
const anchorBadge = anchor ? `<span style="color:#2b70ff;font-size:10px;font-family:monospace;font-weight:600;margin-left:4px;">@${anchor.replace(/&/g,'&')}</span>` : '';
|
|
586
|
-
|
|
587
|
-
if (isQuestion) {
|
|
588
|
-
const timeoutSec = (opts.timeout || (opts.ask && opts.ask.timeout) || 20);
|
|
589
|
-
hud.innerHTML = `
|
|
590
|
-
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:8px;">
|
|
591
|
-
<div style="font-size:12px;line-height:1.4;color:#ffffff;font-weight:500;word-break:break-word;">
|
|
592
|
-
${cleanText}${anchorBadge}
|
|
593
|
-
</div>
|
|
594
|
-
<button id="wt-hud-close" title="Dismiss" style="background:transparent;border:none;color:#555d6d;font-size:12px;cursor:pointer;padding:0 2px;line-height:1;margin-top:1px;">✕</button>
|
|
595
|
-
</div>
|
|
596
|
-
<div style="display:flex;align-items:center;gap:6px;">
|
|
597
|
-
<input id="wt-hud-input" type="text" placeholder="Type answer..." style="height:28px;background:#14161f;border:1px solid #252a36;border-radius:3px;padding:0 8px;font-size:12px;color:#fff;outline:none;flex:1;min-width:0;caret-color:#2b70ff;font-family:inherit;box-sizing:border-box;" />
|
|
598
|
-
<button id="wt-hud-submit-btn" title="Send" style="width:28px;height:28px;background:#2b70ff;border:none;border-radius:3px;display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0;padding:0;box-sizing:border-box;">
|
|
599
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
|
600
|
-
</button>
|
|
601
|
-
<span id="wt-hud-countdown" style="font-size:10px;color:#64748b;font-family:monospace;margin-left:2px;min-width:20px;text-align:right;">${timeoutSec}s</span>
|
|
602
|
-
</div>
|
|
603
|
-
`;
|
|
604
|
-
} else {
|
|
605
|
-
// Feedback / observation mode — ultra compact single row
|
|
606
|
-
hud.innerHTML = `
|
|
607
|
-
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;">
|
|
608
|
-
<div style="display:flex;align-items:center;gap:7px;min-width:0;">
|
|
609
|
-
<span style="display:inline-block;width:5px;height:5px;border-radius:50%;background:#2b70ff;box-shadow:0 0 6px #2b70ff;flex-shrink:0;"></span>
|
|
610
|
-
<span style="font-size:12px;line-height:1.4;color:#f3f4f6;word-break:break-word;font-weight:450;">${cleanText}${anchorBadge}</span>
|
|
611
|
-
</div>
|
|
612
|
-
<button id="wt-hud-close" title="Dismiss" style="background:transparent;border:none;color:#555d6d;font-size:12px;cursor:pointer;padding:0 2px;line-height:1;flex-shrink:0;">✕</button>
|
|
613
|
-
</div>
|
|
614
|
-
`;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// Animate in
|
|
618
|
-
requestAnimationFrame(() => {
|
|
619
|
-
if (hud) {
|
|
620
|
-
hud.style.opacity = '1';
|
|
621
|
-
hud.style.transform = 'translateX(-50%) translateY(0)';
|
|
622
|
-
}
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
// Wire close button
|
|
626
|
-
const closeBtn = hud.querySelector('#wt-hud-close');
|
|
627
|
-
if (closeBtn) {
|
|
628
|
-
closeBtn.addEventListener('click', () => {
|
|
629
|
-
__wt_removeThought();
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
if (isQuestion) {
|
|
634
|
-
let askBridge = document.getElementById('__wt_ask_bridge');
|
|
635
|
-
if (!askBridge) {
|
|
636
|
-
askBridge = document.createElement('div');
|
|
637
|
-
askBridge.id = '__wt_ask_bridge';
|
|
638
|
-
askBridge.style.display = 'none';
|
|
639
|
-
document.body.appendChild(askBridge);
|
|
640
|
-
}
|
|
641
|
-
askBridge.dataset.status = 'pending';
|
|
642
|
-
askBridge.dataset.result = '';
|
|
643
|
-
|
|
644
|
-
const input = hud.querySelector('#wt-hud-input');
|
|
645
|
-
const submitBtn = hud.querySelector('#wt-hud-submit-btn');
|
|
646
|
-
const countdownSpan = hud.querySelector('#wt-hud-countdown');
|
|
647
|
-
|
|
648
|
-
let remaining = opts.timeout || (opts.ask && opts.ask.timeout) || 20;
|
|
649
|
-
|
|
650
|
-
__wt_thoughtHudCountdown = setInterval(() => {
|
|
651
|
-
remaining--;
|
|
652
|
-
if (countdownSpan) countdownSpan.textContent = `${remaining}s`;
|
|
653
|
-
if (remaining <= 0) {
|
|
654
|
-
clearInterval(__wt_thoughtHudCountdown);
|
|
655
|
-
__wt_thoughtHudCountdown = null;
|
|
656
|
-
if (askBridge) {
|
|
657
|
-
askBridge.dataset.status = 'timeout';
|
|
658
|
-
askBridge.dataset.result = '';
|
|
659
|
-
}
|
|
660
|
-
__wt_removeThought();
|
|
661
|
-
}
|
|
662
|
-
}, 1000);
|
|
663
|
-
|
|
664
|
-
const submit = () => {
|
|
665
|
-
const val = input ? input.value.trim() : '';
|
|
666
|
-
if (askBridge) {
|
|
667
|
-
askBridge.dataset.status = 'submitted';
|
|
668
|
-
askBridge.dataset.result = val;
|
|
669
|
-
}
|
|
670
|
-
if (window.__wt_ask_bridge && window.__wt_ask_bridge.resolve) {
|
|
671
|
-
window.__wt_ask_bridge.resolve({ success: true, response: val, status: 'answered' });
|
|
672
|
-
window.__wt_ask_bridge = {};
|
|
673
|
-
}
|
|
674
|
-
__wt_removeThought();
|
|
675
|
-
};
|
|
676
|
-
|
|
677
|
-
if (submitBtn) submitBtn.addEventListener('click', submit);
|
|
678
|
-
if (input) {
|
|
679
|
-
input.addEventListener('focus', () => { input.style.borderColor = '#2b70ff'; });
|
|
680
|
-
input.addEventListener('blur', () => { input.style.borderColor = '#252a36'; });
|
|
681
|
-
input.addEventListener('keydown', (e) => {
|
|
682
|
-
if (e.key === 'Enter') submit();
|
|
683
|
-
if (e.key === 'Escape') __wt_removeThought();
|
|
684
|
-
});
|
|
685
|
-
setTimeout(() => { if (input) input.focus(); }, 80);
|
|
686
|
-
}
|
|
687
|
-
} else {
|
|
688
|
-
// Auto-fade timer for feedback
|
|
689
|
-
const autoDuration = Math.max(3800, Math.min(8000, text.length * 50));
|
|
690
|
-
__wt_thoughtHudTimer = setTimeout(() => {
|
|
691
|
-
__wt_removeThought();
|
|
692
|
-
}, autoDuration);
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
function __wt_renderThought(text, options) {
|
|
697
|
-
__wt_showThought(text, options);
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
function __wt_processThoughtQueue() {
|
|
701
|
-
if (__wt_thoughtQueue.length > 0) {
|
|
702
|
-
var next = __wt_thoughtQueue.shift();
|
|
703
|
-
setTimeout(function() { __wt_showThought(next.text, next.options); }, 200);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
function __wt_showQuestion(text, inputType, timeoutSeconds) {
|
|
708
|
-
__wt_showThought(text, { action: 'question', timeout: timeoutSeconds || 20 });
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
function __wt_removeAsk() {
|
|
712
|
-
__wt_removeThought();
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
// ── SHARED OVERLAY CONTAINER ────────────────────────────────────
|
|
716
|
-
const __wt_shadowStyles = `
|
|
717
|
-
.Weavetab-ripple {
|
|
718
|
-
width: 40px; height: 40px; margin: -20px 0 0 -20px; border-radius: 50%;
|
|
719
|
-
background: rgba(79,142,247,0.35); border: 2px solid #4F8EF7;
|
|
720
|
-
animation: __wt_ripple 0.55s ease-out forwards;
|
|
721
|
-
}
|
|
722
|
-
@keyframes __wt_ripple { from { transform: scale(0.3); opacity: 1; } to { transform: scale(2.2); opacity: 0; } }
|
|
723
|
-
@keyframes __wt_xray_pulse {
|
|
724
|
-
0% { opacity: 0; transform: scale(0.94); }
|
|
725
|
-
18% { opacity: 1; transform: scale(1.02); }
|
|
726
|
-
70% { opacity: 0.85; transform: scale(1); }
|
|
727
|
-
100% { opacity: 0; transform: scale(0.97); }
|
|
728
|
-
}
|
|
729
|
-
`;
|
|
730
|
-
|
|
731
|
-
function getWeavetabRoot() {
|
|
732
|
-
let host = document.getElementById('Weavetab-root');
|
|
733
|
-
if (!host) {
|
|
734
|
-
host = document.createElement('div');
|
|
735
|
-
host.id = 'Weavetab-root';
|
|
736
|
-
host.setAttribute('data-wt-ignore', '');
|
|
737
|
-
Object.assign(host.style, {
|
|
738
|
-
position: 'fixed', left: '0', top: '0',
|
|
739
|
-
width: '0', height: '0',
|
|
740
|
-
zIndex: '2147483640', pointerEvents: 'none',
|
|
741
|
-
});
|
|
742
|
-
var root = document.documentElement || document.body;
|
|
743
|
-
if (root) root.appendChild(host);
|
|
744
|
-
}
|
|
745
|
-
// Reuse existing shadow root (open mode, set by CDP injector or ourselves)
|
|
746
|
-
if (host.shadowRoot) {
|
|
747
|
-
return host.shadowRoot;
|
|
748
|
-
}
|
|
749
|
-
// Guard against NotSupportedError when another context already attached a closed shadow root
|
|
750
|
-
try {
|
|
751
|
-
const sr = host.attachShadow({ mode: 'open' });
|
|
752
|
-
const style = document.createElement('style');
|
|
753
|
-
style.textContent = __wt_shadowStyles;
|
|
754
|
-
sr.appendChild(style);
|
|
755
|
-
return sr;
|
|
756
|
-
} catch (e) {
|
|
757
|
-
// FIX: fallback must be invisible and non-blocking so it can't
|
|
758
|
-
// cover page content or cause opacity bleeding on sites like example.com.
|
|
759
|
-
const fallback = document.createElement('div');
|
|
760
|
-
fallback.setAttribute('data-wt-ignore', '');
|
|
761
|
-
fallback.style.cssText = 'position:fixed;left:0;top:0;width:0;height:0;pointer-events:none;overflow:visible;z-index:2147483640;';
|
|
762
|
-
__wt_safeAppendToBody(fallback);
|
|
763
|
-
return fallback;
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// ── COORDINATE ORACLE ───────────────────────────────────────────
|
|
768
|
-
function shadowQuery(selector) {
|
|
769
|
-
function searchRoot(root) {
|
|
770
|
-
const found = root.querySelector(selector);
|
|
771
|
-
if (found) return found;
|
|
772
|
-
|
|
773
|
-
for (const el of root.querySelectorAll('*')) {
|
|
774
|
-
if (el.shadowRoot) {
|
|
775
|
-
const result = searchRoot(el.shadowRoot);
|
|
776
|
-
if (result) return result;
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
return null;
|
|
780
|
-
}
|
|
781
|
-
return searchRoot(document);
|
|
782
|
-
}
|
|
783
|
-
window.__Weavetab = {
|
|
784
|
-
version: '2.5.0',
|
|
785
|
-
suppressVisuals: false,
|
|
786
|
-
shadowQuery: shadowQuery,
|
|
787
|
-
|
|
788
|
-
// ── CDP Co-Processor Helpers ──────────────────────────────────────────────
|
|
789
|
-
// Call these via Runtime.evaluate instead of doing expensive CDP-side work.
|
|
790
|
-
// All return plain JSON-serializable values.
|
|
791
|
-
|
|
792
|
-
/** Scroll position, viewport size, and page dimensions in one call. */
|
|
793
|
-
getScrollInfo: function() {
|
|
794
|
-
var vv = window.visualViewport || {};
|
|
795
|
-
var body = document.body || document.documentElement;
|
|
796
|
-
return {
|
|
797
|
-
scrollX: Math.round(window.scrollX),
|
|
798
|
-
scrollY: Math.round(window.scrollY),
|
|
799
|
-
viewportW: Math.round(vv.width || window.innerWidth),
|
|
800
|
-
viewportH: Math.round(vv.height || window.innerHeight),
|
|
801
|
-
pageW: Math.round(body.scrollWidth),
|
|
802
|
-
pageH: Math.round(body.scrollHeight),
|
|
803
|
-
maxScrollX: Math.round(Math.max(0, body.scrollWidth - window.innerWidth)),
|
|
804
|
-
maxScrollY: Math.round(Math.max(0, body.scrollHeight - window.innerHeight)),
|
|
805
|
-
scrollRatio: body.scrollHeight > 0 ? +(window.scrollY / body.scrollHeight).toFixed(3) : 0
|
|
806
|
-
};
|
|
807
|
-
},
|
|
808
|
-
|
|
809
|
-
/**
|
|
810
|
-
* Wait until no DOM mutations occur for `quietMs` milliseconds.
|
|
811
|
-
* Resolves to true when stable, false on timeout (default 5000ms).
|
|
812
|
-
* CDP calls this and awaits — replaces dumb sleep() calls.
|
|
813
|
-
*/
|
|
814
|
-
waitForStable: function(quietMs, timeoutMs) {
|
|
815
|
-
quietMs = quietMs || 300;
|
|
816
|
-
timeoutMs = timeoutMs || 5000;
|
|
817
|
-
return new Promise(function(resolve) {
|
|
818
|
-
var timer = null;
|
|
819
|
-
var deadline = setTimeout(function() {
|
|
820
|
-
obs.disconnect();
|
|
821
|
-
resolve(false);
|
|
822
|
-
}, timeoutMs);
|
|
823
|
-
function reset() {
|
|
824
|
-
if (timer) clearTimeout(timer);
|
|
825
|
-
timer = setTimeout(function() {
|
|
826
|
-
clearTimeout(deadline);
|
|
827
|
-
obs.disconnect();
|
|
828
|
-
resolve(true);
|
|
829
|
-
}, quietMs);
|
|
830
|
-
}
|
|
831
|
-
var obs = new MutationObserver(reset);
|
|
832
|
-
obs.observe(document.documentElement, { childList: true, subtree: true, attributes: true, characterData: true });
|
|
833
|
-
reset(); // start the quiet timer immediately
|
|
834
|
-
});
|
|
835
|
-
},
|
|
836
|
-
|
|
837
|
-
/**
|
|
838
|
-
* Resolve a data-wt-id directly from the live index — much faster than
|
|
839
|
-
* doing a full accessibility tree walk.
|
|
840
|
-
* Returns { id, x, y, rect, tag, label, viewport, isVisible } or null.
|
|
841
|
-
*/
|
|
842
|
-
getElementByWtId: function(id) {
|
|
843
|
-
var idx = window.__wt_liveIndex;
|
|
844
|
-
if (!idx) return null;
|
|
845
|
-
var entry = null;
|
|
846
|
-
for (var i = 0; i < idx.length; i++) {
|
|
847
|
-
if (idx[i].id === String(id)) { entry = idx[i]; break; }
|
|
848
|
-
}
|
|
849
|
-
if (!entry) return null;
|
|
850
|
-
// Refresh coordinates from DOM if element still exists
|
|
851
|
-
var el = document.querySelector('[data-wt-id="' + entry.id + '"]');
|
|
852
|
-
if (el) {
|
|
853
|
-
var r = el.getBoundingClientRect();
|
|
854
|
-
entry.x = Math.round(r.left + r.width / 2);
|
|
855
|
-
entry.y = Math.round(r.top + r.height / 2);
|
|
856
|
-
entry.rect = { left: Math.round(r.left), top: Math.round(r.top), width: Math.round(r.width), height: Math.round(r.height) };
|
|
857
|
-
}
|
|
858
|
-
return entry;
|
|
859
|
-
},
|
|
860
|
-
|
|
861
|
-
/**
|
|
862
|
-
* Dump all form field current values — lets agent verify form state cheaply.
|
|
863
|
-
* Returns array of { name, id, type, value, tag }.
|
|
864
|
-
*/
|
|
865
|
-
getFormValues: function() {
|
|
866
|
-
var results = [];
|
|
867
|
-
var els = document.querySelectorAll('input, select, textarea');
|
|
868
|
-
for (var i = 0; i < els.length; i++) {
|
|
869
|
-
var el = els[i];
|
|
870
|
-
if (el.hasAttribute('data-wt-ignore')) continue;
|
|
871
|
-
var val = el.type === 'checkbox' || el.type === 'radio' ? el.checked : el.value;
|
|
872
|
-
results.push({
|
|
873
|
-
name: el.name || el.id || null,
|
|
874
|
-
id: el.id || null,
|
|
875
|
-
wtId: el.getAttribute('data-wt-id') || null,
|
|
876
|
-
type: el.type || el.tagName.toLowerCase(),
|
|
877
|
-
tag: el.tagName.toLowerCase(),
|
|
878
|
-
value: val,
|
|
879
|
-
placeholder: el.placeholder || null
|
|
880
|
-
});
|
|
881
|
-
}
|
|
882
|
-
return results;
|
|
883
|
-
},
|
|
884
|
-
|
|
885
|
-
/**
|
|
886
|
-
* Check visibility of a selector — uses the live IntersectionObserver data
|
|
887
|
-
* maintained by the MutationObserver. Much faster than a full DOM query.
|
|
888
|
-
* Returns { found, visible, occluded, x, y, rect } or null.
|
|
889
|
-
*/
|
|
890
|
-
checkVisible: function(selector) {
|
|
891
|
-
var el = document.querySelector(selector) || shadowQuery(selector);
|
|
892
|
-
if (!el) return { found: false };
|
|
893
|
-
var r = el.getBoundingClientRect();
|
|
894
|
-
var vv = window.visualViewport || {};
|
|
895
|
-
var vw = vv.width || window.innerWidth;
|
|
896
|
-
var vh = vv.height || window.innerHeight;
|
|
897
|
-
var inViewport = r.width > 0 && r.height > 0 && r.top < vh && r.bottom > 0 && r.left < vw && r.right > 0;
|
|
898
|
-
var topEl = document.elementFromPoint(r.left + r.width / 2, r.top + r.height / 2);
|
|
899
|
-
var occluded = topEl !== el && !el.contains(topEl);
|
|
900
|
-
return {
|
|
901
|
-
found: true,
|
|
902
|
-
visible: inViewport,
|
|
903
|
-
occluded: occluded,
|
|
904
|
-
x: Math.round(r.left + r.width / 2),
|
|
905
|
-
y: Math.round(r.top + r.height / 2),
|
|
906
|
-
rect: { left: Math.round(r.left), top: Math.round(r.top), width: Math.round(r.width), height: Math.round(r.height) }
|
|
907
|
-
};
|
|
908
|
-
},
|
|
909
|
-
|
|
910
|
-
/**
|
|
911
|
-
* Return the live element index, optionally filtered.
|
|
912
|
-
* filter: 'visible' | 'all' (default 'visible')
|
|
913
|
-
* Much faster than browser_map for simple lookups.
|
|
914
|
-
*/
|
|
915
|
-
getLiveIndex: function(filter) {
|
|
916
|
-
var idx = window.__wt_liveIndex || [];
|
|
917
|
-
if (filter === 'all') return idx;
|
|
918
|
-
// Default: visible viewport only
|
|
919
|
-
return idx.filter(function(e) { return e.viewport === 'visible' || e.isVisible; });
|
|
920
|
-
},
|
|
921
|
-
|
|
922
|
-
/**
|
|
923
|
-
* Pop and return the next user feedback message from the queue.
|
|
924
|
-
* MCP calls this between tool calls to check for user interrupts.
|
|
925
|
-
* Returns { text, ts, tabId } or null if queue is empty.
|
|
926
|
-
* Non-destructive — call popUserFeedback() to consume, peekUserFeedback() to inspect.
|
|
927
|
-
*/
|
|
928
|
-
popUserFeedback: function() {
|
|
929
|
-
// Read from localStorage queue (written by submitFeedback + persisted by background)
|
|
930
|
-
try {
|
|
931
|
-
var raw = localStorage.getItem('__wt_chat_queue_persist');
|
|
932
|
-
if (!raw) return null;
|
|
933
|
-
var arr = JSON.parse(raw);
|
|
934
|
-
if (!arr || !arr.length) return null;
|
|
935
|
-
var item = arr.shift();
|
|
936
|
-
localStorage.setItem('__wt_chat_queue_persist', JSON.stringify(arr));
|
|
937
|
-
return item;
|
|
938
|
-
} catch(e) { return null; }
|
|
939
|
-
},
|
|
940
|
-
|
|
941
|
-
/**
|
|
942
|
-
* Check if a blocking dialog (alert/confirm/prompt) is pending on this tab.
|
|
943
|
-
* Agent should check this before clicking if it suspects a dialog might block.
|
|
944
|
-
* Returns { pending: bool, ts: number } — reads from extension storage via background relay.
|
|
945
|
-
*/
|
|
946
|
-
getAlertState: function() {
|
|
947
|
-
// Sync check via a DOM marker set by alert wrapper
|
|
948
|
-
return { pending: !!(window.__wt_alertPending), ts: Date.now() };
|
|
949
|
-
},
|
|
950
|
-
};
|
|
951
|
-
window.__Weavetab_shadowQuery = shadowQuery;
|
|
952
|
-
|
|
953
|
-
function isOccluded(el) {
|
|
954
|
-
const rect = el.getBoundingClientRect();
|
|
955
|
-
const cx = rect.left + rect.width / 2;
|
|
956
|
-
const cy = rect.top + rect.height / 2;
|
|
957
|
-
const topEl = document.elementFromPoint(cx, cy);
|
|
958
|
-
return topEl !== el && !el.contains(topEl);
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
window.__Weavetab_getCoords = function(selector) {
|
|
962
|
-
const el = document.querySelector(selector) || shadowQuery(selector);
|
|
963
|
-
|
|
964
|
-
if (!el) return null;
|
|
965
|
-
|
|
966
|
-
const rect = el.getBoundingClientRect();
|
|
967
|
-
const vv = window.visualViewport;
|
|
968
|
-
|
|
969
|
-
return {
|
|
970
|
-
x: rect.left + rect.width / 2 + vv.offsetLeft,
|
|
971
|
-
y: rect.top + rect.height / 2 + vv.offsetTop,
|
|
972
|
-
raw: { left: rect.left, top: rect.top, w: rect.width, h: rect.height },
|
|
973
|
-
occluded: isOccluded(el),
|
|
974
|
-
visible: rect.width > 0 && rect.height > 0
|
|
975
|
-
&& rect.top < vv.height && rect.bottom > 0
|
|
976
|
-
};
|
|
977
|
-
};
|
|
978
|
-
|
|
979
|
-
function getStableSelector(el) {
|
|
980
|
-
if (el.id) return `#${el.id}`;
|
|
981
|
-
const path = [];
|
|
982
|
-
while (el.parentNode) {
|
|
983
|
-
let index = 1;
|
|
984
|
-
let sibling = el.previousElementSibling;
|
|
985
|
-
while (sibling) {
|
|
986
|
-
if (sibling.tagName === el.tagName) index++;
|
|
987
|
-
sibling = sibling.previousElementSibling;
|
|
988
|
-
}
|
|
989
|
-
path.unshift(`${el.tagName.toLowerCase()}:nth-of-type(${index})`);
|
|
990
|
-
el = el.parentNode;
|
|
991
|
-
}
|
|
992
|
-
return path.join(' > ');
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
window.__Weavetab_fullMap = function() {
|
|
996
|
-
const map = [];
|
|
997
|
-
let idCounter = 1;
|
|
998
|
-
function crawl(root, depth = 0) {
|
|
999
|
-
for (const el of root.querySelectorAll(
|
|
1000
|
-
'a,button,input,select,textarea,[role],[onclick],[tabindex]'
|
|
1001
|
-
)) {
|
|
1002
|
-
if (el.hasAttribute('data-wt-ignore')) continue;
|
|
1003
|
-
const rect = el.getBoundingClientRect();
|
|
1004
|
-
// Always include form elements (input/textarea/select/button) regardless of rect size
|
|
1005
|
-
// — canvas-heavy pages can occlude them and getBoundingClientRect returns 0
|
|
1006
|
-
const isEssential = ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'].includes(el.tagName);
|
|
1007
|
-
if (rect.width === 0 && !isEssential) continue;
|
|
1008
|
-
let id = el.getAttribute('data-wt-id');
|
|
1009
|
-
if (!id) {
|
|
1010
|
-
id = String(idCounter++);
|
|
1011
|
-
el.setAttribute('data-wt-id', id);
|
|
1012
|
-
}
|
|
1013
|
-
map.push({
|
|
1014
|
-
id: id,
|
|
1015
|
-
tag: el.tagName.toLowerCase(),
|
|
1016
|
-
text: (el.innerText || el.value || el.getAttribute('aria-label') || '').trim().slice(0,80),
|
|
1017
|
-
role: el.getAttribute('role'),
|
|
1018
|
-
selector: `[data-wt-id="${id}"]`,
|
|
1019
|
-
coords: { x: rect.left + rect.width/2, y: rect.top + rect.height/2 },
|
|
1020
|
-
inShadow: depth > 0
|
|
1021
|
-
});
|
|
1022
|
-
if (el.shadowRoot) crawl(el.shadowRoot, depth + 1);
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
crawl(document);
|
|
1026
|
-
return map;
|
|
1027
|
-
};
|
|
1028
|
-
|
|
1029
|
-
// ── CONSOLE BUFFERING ───────────────────────────────────────────
|
|
1030
|
-
const __consoleBuffer = [];
|
|
1031
|
-
let __consoleCursor = 0;
|
|
1032
|
-
|
|
1033
|
-
['log','warn','error','info','debug'].forEach(level => {
|
|
1034
|
-
const orig = console[level];
|
|
1035
|
-
console[level] = (...args) => {
|
|
1036
|
-
__consoleBuffer.push({ level, time: Date.now(), text: args.join(' ') });
|
|
1037
|
-
orig.apply(console, args);
|
|
1038
|
-
};
|
|
1039
|
-
});
|
|
1040
|
-
|
|
1041
|
-
window.__Weavetab_readConsole = function({ since = 'last', level = null } = {}) {
|
|
1042
|
-
const from = since === 'last' ? __consoleCursor : 0;
|
|
1043
|
-
const entries = __consoleBuffer.slice(from);
|
|
1044
|
-
__consoleCursor = __consoleBuffer.length;
|
|
1045
|
-
return level ? entries.filter(e => e.level === level) : entries;
|
|
1046
|
-
};
|
|
1047
|
-
|
|
1048
|
-
// ── BLUE SVG WEAVE CURSOR ───────────────────────────────────────
|
|
1049
|
-
// Shadow-DOM-isolated, hardware-accelerated SVG cursor with smooth interpolation
|
|
1050
|
-
class WeaveCursor {
|
|
1051
|
-
constructor() {
|
|
1052
|
-
// Attach a shadow host that sits above all page content
|
|
1053
|
-
this._host = document.createElement('div');
|
|
1054
|
-
this._host.id = '__Weavetab_cursor_host';
|
|
1055
|
-
this._host.setAttribute('data-wt-ignore', 'true');
|
|
1056
|
-
Object.assign(this._host.style, {
|
|
1057
|
-
position: 'fixed',
|
|
1058
|
-
top: '0',
|
|
1059
|
-
left: '0',
|
|
1060
|
-
width: '0',
|
|
1061
|
-
height: '0',
|
|
1062
|
-
zIndex: '2147483647',
|
|
1063
|
-
pointerEvents: 'none',
|
|
1064
|
-
overflow: 'visible',
|
|
1065
|
-
});
|
|
1066
|
-
|
|
1067
|
-
const shadow = this._host.attachShadow({ mode: 'closed' });
|
|
1068
|
-
|
|
1069
|
-
// Liquid trail canvas inside shadow root (zero DOM reflows)
|
|
1070
|
-
this._trailCanvas = document.createElement('canvas');
|
|
1071
|
-
this._trailCanvas.setAttribute('data-wt-ignore', 'true');
|
|
1072
|
-
Object.assign(this._trailCanvas.style, {
|
|
1073
|
-
position: 'fixed',
|
|
1074
|
-
top: '0',
|
|
1075
|
-
left: '0',
|
|
1076
|
-
width: '100vw',
|
|
1077
|
-
height: '100vh',
|
|
1078
|
-
pointerEvents: 'none',
|
|
1079
|
-
zIndex: '2147483645',
|
|
1080
|
-
overflow: 'visible',
|
|
1081
|
-
});
|
|
1082
|
-
this._trailCanvas.width = typeof window !== 'undefined' && window.innerWidth ? window.innerWidth : 1920;
|
|
1083
|
-
this._trailCanvas.height = typeof window !== 'undefined' && window.innerHeight ? window.innerHeight : 1080;
|
|
1084
|
-
shadow.appendChild(this._trailCanvas);
|
|
1085
|
-
this._trailCtx = this._trailCanvas.getContext('2d');
|
|
1086
|
-
this._trailPoints = [];
|
|
1087
|
-
this._lastTrailRecord = 0;
|
|
1088
|
-
|
|
1089
|
-
window.addEventListener('resize', () => {
|
|
1090
|
-
if (this._trailCanvas) {
|
|
1091
|
-
this._trailCanvas.width = window.innerWidth;
|
|
1092
|
-
this._trailCanvas.height = window.innerHeight;
|
|
1093
|
-
}
|
|
1094
|
-
});
|
|
1095
|
-
|
|
1096
|
-
// Hotspot aligned with the top-left pointer tip (3.0, 5.0 in 32x32 viewBox)
|
|
1097
|
-
this._hotX = 3.0;
|
|
1098
|
-
this._hotY = 5.0;
|
|
1099
|
-
this._scale = 0.4;
|
|
1100
|
-
|
|
1101
|
-
const initX = typeof window !== 'undefined' && window.innerWidth ? Math.round(window.innerWidth / 2) : 640;
|
|
1102
|
-
const initY = typeof window !== 'undefined' && window.innerHeight ? Math.round(window.innerHeight / 2) : 400;
|
|
1103
|
-
|
|
1104
|
-
// Exact vector SVG cursor matching user design
|
|
1105
|
-
this._cursor = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
1106
|
-
this._cursor.setAttribute('class', 'cursor');
|
|
1107
|
-
this._cursor.setAttribute('viewBox', '0 0 32 32');
|
|
1108
|
-
this._cursor.setAttribute('width', '32');
|
|
1109
|
-
this._cursor.setAttribute('height', '32');
|
|
1110
|
-
this._cursor.setAttribute('aria-hidden', 'true');
|
|
1111
|
-
Object.assign(this._cursor.style, {
|
|
1112
|
-
position: 'fixed',
|
|
1113
|
-
top: '0',
|
|
1114
|
-
left: '0',
|
|
1115
|
-
width: '32px',
|
|
1116
|
-
height: '32px',
|
|
1117
|
-
display: 'block',
|
|
1118
|
-
transformOrigin: `${this._hotX}px ${this._hotY}px`,
|
|
1119
|
-
transform: `translate3d(${initX - this._hotX}px, ${initY - this._hotY}px, 0) scale(${this._scale})`,
|
|
1120
|
-
overflow: 'visible',
|
|
1121
|
-
pointerEvents: 'none',
|
|
1122
|
-
willChange: 'transform',
|
|
1123
|
-
transition: 'opacity 0.2s ease-out',
|
|
1124
|
-
opacity: '1',
|
|
1125
|
-
zIndex: '2147483647',
|
|
1126
|
-
filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.35))',
|
|
1127
|
-
});
|
|
1128
|
-
|
|
1129
|
-
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
1130
|
-
path.setAttribute('class', 'cursor-shape');
|
|
1131
|
-
path.setAttribute('d', 'M 6.1,4.7 C 4.5,4.1 3.0,5.0 3.0,6.7 C 3.0,7.1 3.1,7.5 3.3,8.0 L 10.2,27.0 C 10.8,28.7 12.0,29.6 13.6,29.5 C 15.1,29.4 16.0,28.5 16.5,27.0 L 19.4,18.8 L 25.9,17.0 C 27.7,16.5 28.9,15.2 28.9,13.6 C 28.9,12.1 28.0,10.9 26.4,10.3 Z');
|
|
1132
|
-
path.setAttribute('fill', '#1268ff');
|
|
1133
|
-
path.setAttribute('stroke', '#ffffff');
|
|
1134
|
-
path.setAttribute('stroke-width', '3.1');
|
|
1135
|
-
path.setAttribute('stroke-linejoin', 'round');
|
|
1136
|
-
path.setAttribute('stroke-linecap', 'round');
|
|
1137
|
-
|
|
1138
|
-
this._cursor.appendChild(path);
|
|
1139
|
-
shadow.appendChild(this._cursor);
|
|
1140
|
-
|
|
1141
|
-
// Alias _canvas to _cursor for backward compatibility
|
|
1142
|
-
this._canvas = this._cursor;
|
|
1143
|
-
|
|
1144
|
-
this._x = initX;
|
|
1145
|
-
this._y = initY;
|
|
1146
|
-
this._targetX = initX;
|
|
1147
|
-
this._targetY = initY;
|
|
1148
|
-
this._lerpX = initX;
|
|
1149
|
-
this._lerpY = initY;
|
|
1150
|
-
this._state = 'idle';
|
|
1151
|
-
this._visible = true;
|
|
1152
|
-
this._animFrame = null;
|
|
1153
|
-
this._lastTime = performance.now();
|
|
1154
|
-
this._waypointQueue = [];
|
|
1155
|
-
this._playingQueue = false;
|
|
1156
|
-
|
|
1157
|
-
__wt_safeAppendToBody(this._host);
|
|
1158
|
-
this._startLoop();
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
destroy() {
|
|
1162
|
-
if (this._animFrame) {
|
|
1163
|
-
cancelAnimationFrame(this._animFrame);
|
|
1164
|
-
this._animFrame = null;
|
|
1165
|
-
}
|
|
1166
|
-
if (this._host && this._host.parentNode) {
|
|
1167
|
-
this._host.parentNode.removeChild(this._host);
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
setState(state) {
|
|
1172
|
-
this._state = state || 'idle';
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
setVisible(visible) {
|
|
1176
|
-
if (typeof __wt_masterVisualsEnabled !== 'undefined' && !__wt_masterVisualsEnabled) {
|
|
1177
|
-
this._visible = false;
|
|
1178
|
-
this._cursor.style.opacity = '0';
|
|
1179
|
-
if (this._trailCtx && this._trailCanvas) {
|
|
1180
|
-
this._trailCtx.clearRect(0, 0, this._trailCanvas.width, this._trailCanvas.height);
|
|
1181
|
-
}
|
|
1182
|
-
return;
|
|
1183
|
-
}
|
|
1184
|
-
this._visible = visible;
|
|
1185
|
-
this._cursor.style.opacity = visible ? '1' : '0';
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
moveTo(x, y) {
|
|
1189
|
-
if (typeof __wt_masterVisualsEnabled !== 'undefined' && !__wt_masterVisualsEnabled) return;
|
|
1190
|
-
if (typeof x !== 'number' || typeof y !== 'number' || isNaN(x) || isNaN(y)) return;
|
|
1191
|
-
this._targetX = x;
|
|
1192
|
-
this._targetY = y;
|
|
1193
|
-
if (this._lerpX === -9999) {
|
|
1194
|
-
this._lerpX = x;
|
|
1195
|
-
this._lerpY = y;
|
|
1196
|
-
}
|
|
1197
|
-
if (!this._visible) {
|
|
1198
|
-
this._visible = true;
|
|
1199
|
-
this._cursor.style.opacity = '1';
|
|
1200
|
-
}
|
|
1201
|
-
if (!this._animFrame) {
|
|
1202
|
-
this._startLoop();
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
setWaypoints(waypoints, durationMs) {
|
|
1207
|
-
if (typeof __wt_masterVisualsEnabled !== 'undefined' && !__wt_masterVisualsEnabled) return;
|
|
1208
|
-
if (!waypoints || !waypoints.length) return;
|
|
1209
|
-
this._waypointQueue = waypoints;
|
|
1210
|
-
this._playingQueue = true;
|
|
1211
|
-
this._waypointStartTime = performance.now();
|
|
1212
|
-
this._waypointDuration = durationMs || (waypoints.length * 16);
|
|
1213
|
-
if (waypoints.length > 0 && this._lerpX === -9999) {
|
|
1214
|
-
this._lerpX = waypoints[0].x;
|
|
1215
|
-
this._lerpY = waypoints[0].y;
|
|
1216
|
-
}
|
|
1217
|
-
if (!this._visible) {
|
|
1218
|
-
this._visible = true;
|
|
1219
|
-
this._cursor.style.opacity = '1';
|
|
1220
|
-
}
|
|
1221
|
-
if (!this._animFrame) this._startLoop();
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
_startLoop() {
|
|
1225
|
-
this._lastTime = performance.now();
|
|
1226
|
-
const draw = (currentTime) => {
|
|
1227
|
-
const dt = Math.min((currentTime - this._lastTime) / 1000, 0.1);
|
|
1228
|
-
this._lastTime = currentTime;
|
|
1229
|
-
|
|
1230
|
-
if (this._playingQueue && this._waypointQueue.length > 0) {
|
|
1231
|
-
const elapsed = currentTime - this._waypointStartTime;
|
|
1232
|
-
const progress = Math.max(0, Math.min(elapsed / (this._waypointDuration || 1), 1));
|
|
1233
|
-
const totalSegments = this._waypointQueue.length - 1;
|
|
1234
|
-
|
|
1235
|
-
if (totalSegments <= 0) {
|
|
1236
|
-
this._targetX = this._waypointQueue[0].x;
|
|
1237
|
-
this._targetY = this._waypointQueue[0].y;
|
|
1238
|
-
} else {
|
|
1239
|
-
const floatIdx = progress * totalSegments;
|
|
1240
|
-
const i0 = Math.floor(floatIdx);
|
|
1241
|
-
const i1 = Math.min(i0 + 1, totalSegments);
|
|
1242
|
-
const t = floatIdx - i0;
|
|
1243
|
-
|
|
1244
|
-
const p0 = this._waypointQueue[i0];
|
|
1245
|
-
const p1 = this._waypointQueue[i1];
|
|
1246
|
-
if (p0 && p1) {
|
|
1247
|
-
this._targetX = p0.x + (p1.x - p0.x) * t;
|
|
1248
|
-
this._targetY = p0.y + (p1.y - p0.y) * t;
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
if (progress >= 1) {
|
|
1253
|
-
this._playingQueue = false;
|
|
1254
|
-
this._waypointQueue = [];
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
// Smooth exponential lerp (k = 24 for fluid human-smooth tracking)
|
|
1259
|
-
const lerpFactor = 1 - Math.exp(-24 * dt);
|
|
1260
|
-
if (this._targetX !== -9999) {
|
|
1261
|
-
this._lerpX += (this._targetX - this._lerpX) * lerpFactor;
|
|
1262
|
-
this._lerpY += (this._targetY - this._lerpY) * lerpFactor;
|
|
1263
|
-
if (Math.abs(this._targetX - this._lerpX) < 0.1) this._lerpX = this._targetX;
|
|
1264
|
-
if (Math.abs(this._targetY - this._lerpY) < 0.1) this._lerpY = this._targetY;
|
|
1265
|
-
|
|
1266
|
-
// "always show in browser" - stays visible once loaded and active
|
|
1267
|
-
if (typeof __wt_masterVisualsEnabled === 'undefined' || __wt_masterVisualsEnabled) {
|
|
1268
|
-
if (!this._visible) {
|
|
1269
|
-
this._visible = true;
|
|
1270
|
-
this._cursor.style.opacity = '1';
|
|
1271
|
-
}
|
|
1272
|
-
} else {
|
|
1273
|
-
if (this._visible) {
|
|
1274
|
-
this._visible = false;
|
|
1275
|
-
this._cursor.style.opacity = '0';
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
this._render(currentTime);
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
this._animFrame = requestAnimationFrame(draw);
|
|
1283
|
-
};
|
|
1284
|
-
if (this._animFrame) cancelAnimationFrame(this._animFrame);
|
|
1285
|
-
this._animFrame = requestAnimationFrame(draw);
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
_render(currentTime) {
|
|
1289
|
-
if (typeof __wt_masterVisualsEnabled !== 'undefined' && !__wt_masterVisualsEnabled) {
|
|
1290
|
-
this._cursor.style.opacity = '0';
|
|
1291
|
-
if (this._trailCtx && this._trailCanvas) {
|
|
1292
|
-
this._trailCtx.clearRect(0, 0, this._trailCanvas.width, this._trailCanvas.height);
|
|
1293
|
-
}
|
|
1294
|
-
return;
|
|
1295
|
-
}
|
|
1296
|
-
const x = this._lerpX;
|
|
1297
|
-
const y = this._lerpY;
|
|
1298
|
-
if (x === -9999 || y === -9999) return;
|
|
1299
|
-
|
|
1300
|
-
const now = currentTime || performance.now();
|
|
1301
|
-
|
|
1302
|
-
// 1. Trail decay tracking
|
|
1303
|
-
const dx = this._targetX - this._lerpX;
|
|
1304
|
-
const dy = this._targetY - this._lerpY;
|
|
1305
|
-
const moveDist = Math.sqrt(dx * dx + dy * dy);
|
|
1306
|
-
|
|
1307
|
-
if (moveDist > 1.8 && now - this._lastTrailRecord > 22) {
|
|
1308
|
-
this._trailPoints.push({ x, y, birth: now });
|
|
1309
|
-
this._lastTrailRecord = now;
|
|
1310
|
-
if (this._trailPoints.length > 14) this._trailPoints.shift();
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
// Render decaying trail
|
|
1314
|
-
if (this._trailCtx && this._trailCanvas) {
|
|
1315
|
-
this._trailCtx.clearRect(0, 0, this._trailCanvas.width, this._trailCanvas.height);
|
|
1316
|
-
for (let i = this._trailPoints.length - 1; i >= 0; i--) {
|
|
1317
|
-
const pt = this._trailPoints[i];
|
|
1318
|
-
const age = (now - pt.birth) / 260;
|
|
1319
|
-
if (age >= 1) {
|
|
1320
|
-
this._trailPoints.splice(i, 1);
|
|
1321
|
-
continue;
|
|
1322
|
-
}
|
|
1323
|
-
const alpha = (1 - age) * 0.45;
|
|
1324
|
-
const radius = Math.max(1, (1 - age) * 2.8);
|
|
1325
|
-
this._trailCtx.beginPath();
|
|
1326
|
-
this._trailCtx.arc(pt.x, pt.y, radius, 0, Math.PI * 2);
|
|
1327
|
-
this._trailCtx.fillStyle = `rgba(18, 104, 255, ${alpha})`;
|
|
1328
|
-
this._trailCtx.shadowColor = '#1268ff';
|
|
1329
|
-
this._trailCtx.shadowBlur = 4;
|
|
1330
|
-
this._trailCtx.fill();
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
// 2. Cursor scale and tactile micro-physics
|
|
1335
|
-
let targetScale = this._scale;
|
|
1336
|
-
if (this._state === 'clicking') {
|
|
1337
|
-
targetScale = this._scale * 0.82; // Tactile compression on click
|
|
1338
|
-
} else if (this._state === 'hover') {
|
|
1339
|
-
targetScale = this._scale * 1.08; // Interactive focus
|
|
1340
|
-
} else if (this._state === 'idle') {
|
|
1341
|
-
targetScale = this._scale + Math.sin(now * 0.003) * 0.012; // Alive breathing oscillation
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
// 3. Dynamic velocity tilt (natural motion lag)
|
|
1345
|
-
const tilt = Math.max(-5, Math.min(5, dx * 0.06));
|
|
1346
|
-
|
|
1347
|
-
this._cursor.style.transform = `translate3d(${x - this._hotX}px, ${y - this._hotY}px, 0) rotate(${tilt}deg) scale(${targetScale})`;
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
// Instantiate the active cursor
|
|
1352
|
-
let __weaveCursor = new WeaveCursor();
|
|
1353
|
-
window.__weaveCursor_instance = __weaveCursor;
|
|
1354
|
-
|
|
1355
|
-
function moveCursor(x, y, state) {
|
|
1356
|
-
if (typeof __wt_masterVisualsEnabled !== 'undefined' && !__wt_masterVisualsEnabled) return;
|
|
1357
|
-
if (__weaveCursor && typeof __weaveCursor.moveTo === 'function') {
|
|
1358
|
-
if (typeof x === 'number' && typeof y === 'number' && x >= 0 && y >= 0) {
|
|
1359
|
-
__weaveCursor.moveTo(x, y);
|
|
1360
|
-
}
|
|
1361
|
-
if (state) __weaveCursor.setState(state);
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
// Enhanced twin action ripples radiating from click target
|
|
1366
|
-
function ripple(x, y) {
|
|
1367
|
-
if (typeof __wt_masterVisualsEnabled !== 'undefined' && !__wt_masterVisualsEnabled) return;
|
|
1368
|
-
const host = document.createElement('div');
|
|
1369
|
-
host.className = 'Weavetab-ripple-host';
|
|
1370
|
-
host.setAttribute('data-wt-ignore', 'true');
|
|
1371
|
-
host.style.cssText = `position:fixed;left:${x}px;top:${y}px;pointer-events:none;z-index:2147483646;transform:translate(-50%, -50%);`;
|
|
1372
|
-
|
|
1373
|
-
const w1 = document.createElement('div');
|
|
1374
|
-
w1.style.cssText = 'position:absolute;top:50%;left:50%;width:8px;height:8px;margin:-4px 0 0 -4px;border:2px solid #1268ff;border-radius:50%;box-shadow:0 0 12px rgba(18,104,255,0.7);animation:__wt_ripple_expand 0.42s cubic-bezier(0.1, 0.8, 0.3, 1) forwards;pointer-events:none;';
|
|
1375
|
-
|
|
1376
|
-
const w2 = document.createElement('div');
|
|
1377
|
-
w2.style.cssText = 'position:absolute;top:50%;left:50%;width:8px;height:8px;margin:-4px 0 0 -4px;border:1.5px solid #60a5fa;border-radius:50%;animation:__wt_ripple_expand 0.52s 0.08s cubic-bezier(0.1, 0.8, 0.3, 1) forwards;pointer-events:none;';
|
|
1378
|
-
|
|
1379
|
-
host.appendChild(w1);
|
|
1380
|
-
host.appendChild(w2);
|
|
1381
|
-
(document.getElementById('Weavetab-root')?.shadowRoot || document.body || document.documentElement).appendChild(host);
|
|
1382
|
-
setTimeout(() => host.remove(), 620);
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
// ── X-RAY SPOTLIGHT OVERLAY (Phase 5.2) ────────────────────────
|
|
1386
|
-
// Flashes glowing halos around top interactive elements when the
|
|
1387
|
-
// semantic map is scanned (GET_SEMANTIC_MAP message).
|
|
1388
|
-
let __xrayActive = false;
|
|
1389
|
-
const __xrayOverlays = [];
|
|
1390
|
-
|
|
1391
|
-
function clearXRay() {
|
|
1392
|
-
for (const el of __xrayOverlays) el.remove();
|
|
1393
|
-
__xrayOverlays.length = 0;
|
|
1394
|
-
__xrayActive = false;
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
function activateXRay(elements) {
|
|
1398
|
-
clearXRay();
|
|
1399
|
-
__xrayActive = true;
|
|
1400
|
-
|
|
1401
|
-
// Colour spectrum for visual richness
|
|
1402
|
-
const colours = [
|
|
1403
|
-
'#4F8EF7', '#7C3AED', '#10B981', '#F59E0B',
|
|
1404
|
-
'#EF4444', '#06B6D4', '#F97316', '#8B5CF6',
|
|
1405
|
-
];
|
|
1406
|
-
|
|
1407
|
-
// Architecture change: Use live index for visual overlay
|
|
1408
|
-
// The live index maintains up-to-date coordinates and visibility info
|
|
1409
|
-
const liveIndex = window.__wt_liveIndex || [];
|
|
1410
|
-
const top = elements.slice(0, 12); // highlight top 12 nodes
|
|
1411
|
-
|
|
1412
|
-
top.forEach((item, i) => {
|
|
1413
|
-
// Try to get element from live index first for accurate coordinates
|
|
1414
|
-
const liveEntry = liveIndex.find(e => e.id === item.id || e.selector === item.selector);
|
|
1415
|
-
|
|
1416
|
-
let rect;
|
|
1417
|
-
let el;
|
|
1418
|
-
|
|
1419
|
-
if (liveEntry && liveEntry.rect) {
|
|
1420
|
-
// Use coordinates from live index (more accurate, already computed)
|
|
1421
|
-
rect = liveEntry.rect;
|
|
1422
|
-
el = document.querySelector(item.selector) || shadowQuery(item.selector);
|
|
1423
|
-
} else {
|
|
1424
|
-
// Fallback to querying DOM
|
|
1425
|
-
el = document.querySelector(item.selector) || shadowQuery(item.selector);
|
|
1426
|
-
if (!el) return;
|
|
1427
|
-
rect = el.getBoundingClientRect();
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
if (rect.width === 0 || rect.height === 0) return;
|
|
1431
|
-
|
|
1432
|
-
const colour = colours[i % colours.length];
|
|
1433
|
-
const halo = document.createElement('div');
|
|
1434
|
-
halo.className = '__Weavetab_xray';
|
|
1435
|
-
halo.setAttribute('data-wt-ignore', '');
|
|
1436
|
-
|
|
1437
|
-
Object.assign(halo.style, {
|
|
1438
|
-
position: 'fixed',
|
|
1439
|
-
left: `${rect.left - 4}px`,
|
|
1440
|
-
top: `${rect.top - 4}px`,
|
|
1441
|
-
width: `${rect.width + 8}px`,
|
|
1442
|
-
height: `${rect.height + 8}px`,
|
|
1443
|
-
border: `2px solid ${colour}`,
|
|
1444
|
-
borderRadius: '6px',
|
|
1445
|
-
boxShadow: `0 0 12px 4px ${colour}88, inset 0 0 8px ${colour}33`,
|
|
1446
|
-
background: `${colour}0d`,
|
|
1447
|
-
pointerEvents: 'none',
|
|
1448
|
-
zIndex: '2147483645',
|
|
1449
|
-
animation: '__wt_xray_pulse 1.4s ease-in-out forwards',
|
|
1450
|
-
animationDelay: `${i * 55}ms`,
|
|
1451
|
-
});
|
|
1452
|
-
|
|
1453
|
-
// Rank label badge
|
|
1454
|
-
const badge = document.createElement('span');
|
|
1455
|
-
Object.assign(badge.style, {
|
|
1456
|
-
position: 'absolute',
|
|
1457
|
-
top: '-10px',
|
|
1458
|
-
left: '4px',
|
|
1459
|
-
background: colour,
|
|
1460
|
-
color: '#fff',
|
|
1461
|
-
fontSize: '9px',
|
|
1462
|
-
fontFamily: 'system-ui, sans-serif',
|
|
1463
|
-
fontWeight: '700',
|
|
1464
|
-
padding: '1px 5px',
|
|
1465
|
-
borderRadius: '3px',
|
|
1466
|
-
lineHeight: '14px',
|
|
1467
|
-
whiteSpace: 'nowrap',
|
|
1468
|
-
letterSpacing: '0.03em',
|
|
1469
|
-
boxShadow: `0 1px 4px ${colour}99`,
|
|
1470
|
-
});
|
|
1471
|
-
const label = (item.text || item.role || item.tag || '').slice(0, 22) || `#${i + 1}`;
|
|
1472
|
-
badge.textContent = `${i + 1} ${label}`;
|
|
1473
|
-
halo.appendChild(badge);
|
|
1474
|
-
|
|
1475
|
-
getWeavetabRoot().appendChild(halo);
|
|
1476
|
-
__xrayOverlays.push(halo);
|
|
1477
|
-
});
|
|
1478
|
-
|
|
1479
|
-
// Auto-clear after 2.8 s
|
|
1480
|
-
setTimeout(clearXRay, 2800);
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
// Inject keyframe animation for X-Ray
|
|
1484
|
-
(function injectXRayStyles() {
|
|
1485
|
-
if (document.getElementById('__Weavetab_xray_styles')) return;
|
|
1486
|
-
const style = document.createElement('style');
|
|
1487
|
-
style.id = '__Weavetab_xray_styles';
|
|
1488
|
-
style.textContent = `
|
|
1489
|
-
@keyframes __wt_xray_pulse {
|
|
1490
|
-
0% { opacity: 0; transform: scale(0.94); }
|
|
1491
|
-
18% { opacity: 1; transform: scale(1.02); }
|
|
1492
|
-
70% { opacity: 0.85; transform: scale(1); }
|
|
1493
|
-
100% { opacity: 0; transform: scale(0.97); }
|
|
1494
|
-
}
|
|
1495
|
-
.Weavetab-ripple {
|
|
1496
|
-
width: 40px; height: 40px;
|
|
1497
|
-
margin: -20px 0 0 -20px;
|
|
1498
|
-
border-radius: 50%;
|
|
1499
|
-
background: rgba(79,142,247,0.35);
|
|
1500
|
-
border: 2px solid #4F8EF7;
|
|
1501
|
-
animation: __wt_ripple 0.55s ease-out forwards;
|
|
1502
|
-
}
|
|
1503
|
-
@keyframes __wt_ripple {
|
|
1504
|
-
from { transform: scale(0.3); opacity: 1; }
|
|
1505
|
-
to { transform: scale(2.2); opacity: 0; }
|
|
1506
|
-
}
|
|
1507
|
-
@keyframes wt-reading-pulse {
|
|
1508
|
-
0% { opacity: 0; box-shadow: inset 0 0 0 rgba(59,130,246,0); }
|
|
1509
|
-
15% { opacity: 1; box-shadow: inset 0 0 60px rgba(59,130,246,0.15); }
|
|
1510
|
-
85% { opacity: 1; box-shadow: inset 0 0 60px rgba(59,130,246,0.15); }
|
|
1511
|
-
100% { opacity: 0; box-shadow: inset 0 0 0 rgba(59,130,246,0); }
|
|
1512
|
-
}
|
|
1513
|
-
`;
|
|
1514
|
-
(document.head || document.documentElement).appendChild(style);
|
|
1515
|
-
})();
|
|
1516
|
-
|
|
1517
|
-
// ── READING GLOW ────────────────────────────────────────────────
|
|
1518
|
-
let __wt_readingGlow = null;
|
|
1519
|
-
|
|
1520
|
-
function __wt_showReadingGlow() {
|
|
1521
|
-
if (__wt_readingGlow) return;
|
|
1522
|
-
var el = document.createElement('div');
|
|
1523
|
-
el.id = 'wt-reading-glow';
|
|
1524
|
-
el.setAttribute('data-wt-ignore', '');
|
|
1525
|
-
el.style.cssText = 'position:fixed;inset:0;pointer-events:none;z-index:2147483646;border:4px solid;border-image:linear-gradient(135deg, #3b82f6, #8b5cf6, #10b981) 1;animation:wt-reading-pulse 3.5s ease-in-out forwards;';
|
|
1526
|
-
__wt_safeAppendToBody(el);
|
|
1527
|
-
__wt_readingGlow = el;
|
|
1528
|
-
setTimeout(__wt_hideReadingGlow, 3500);
|
|
1529
|
-
}
|
|
1530
|
-
|
|
1531
|
-
function __wt_hideReadingGlow() {
|
|
1532
|
-
if (__wt_readingGlow) { __wt_readingGlow.remove(); __wt_readingGlow = null; }
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
|
-
// ── SEMANTIC DOM MAP ────────────────────────────────────────────
|
|
1536
|
-
// Architecture change: Passive/extension-side ID assignment with live index
|
|
1537
|
-
// The MutationObserver continuously assigns data-wt-id to interactive elements
|
|
1538
|
-
// and maintains a live index that CDP can read lazily
|
|
1539
|
-
|
|
1540
|
-
// Live index - always up-to-date, accessible via window.__wt_liveIndex
|
|
1541
|
-
window.__wt_liveIndex = [];
|
|
1542
|
-
window.__wt_nextId = 1;
|
|
1543
|
-
|
|
1544
|
-
// IntersectionObserver for real occlusion detection (not just display:none)
|
|
1545
|
-
const intersectionObserver = new IntersectionObserver((entries) => {
|
|
1546
|
-
for (const entry of entries) {
|
|
1547
|
-
const el = entry.target;
|
|
1548
|
-
const id = el.getAttribute('data-wt-id');
|
|
1549
|
-
if (id) {
|
|
1550
|
-
const indexEntry = window.__wt_liveIndex.find(e => e.id === id);
|
|
1551
|
-
if (indexEntry) {
|
|
1552
|
-
indexEntry.isVisible = entry.isIntersecting;
|
|
1553
|
-
indexEntry.intersectionRatio = entry.intersectionRatio;
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
}, { threshold: [0, 0.25, 0.5, 0.75, 1.0] });
|
|
1558
|
-
|
|
1559
|
-
// Function to assign data-wt-id to an element and add to live index
|
|
1560
|
-
function assignIdAndIndex(el) {
|
|
1561
|
-
if (el.hasAttribute('data-wt-ignore')) return;
|
|
1562
|
-
if (el.hasAttribute('data-wt-id')) return; // Already assigned
|
|
1563
|
-
|
|
1564
|
-
const id = String(window.__wt_nextId++);
|
|
1565
|
-
el.setAttribute('data-wt-id', id);
|
|
1566
|
-
el.__wt_id = id;
|
|
1567
|
-
|
|
1568
|
-
const rect = el.getBoundingClientRect();
|
|
1569
|
-
const inViewport = (
|
|
1570
|
-
rect.bottom > 0 && rect.right > 0 &&
|
|
1571
|
-
rect.top < window.innerHeight && rect.left < window.innerWidth
|
|
1572
|
-
);
|
|
1573
|
-
const viewportPos = inViewport ? "visible" : (rect.top >= window.innerHeight ? "below" : "above");
|
|
1574
|
-
|
|
1575
|
-
// Check for event listeners
|
|
1576
|
-
const hasClickListener = el.onclick !== null ||
|
|
1577
|
-
el.addEventListener !== undefined &&
|
|
1578
|
-
(el._listeners?.click?.length > 0);
|
|
1579
|
-
|
|
1580
|
-
const indexEntry = {
|
|
1581
|
-
id: id,
|
|
1582
|
-
tag: el.tagName.toLowerCase(),
|
|
1583
|
-
role: el.getAttribute('role') || el.tagName.toLowerCase(),
|
|
1584
|
-
label: el.getAttribute('aria-label') || el.textContent?.trim().slice(0, 60) || '',
|
|
1585
|
-
placeholder: el.getAttribute('placeholder') || '',
|
|
1586
|
-
selector: getStableSelector(el),
|
|
1587
|
-
x: Math.round(rect.left + rect.width / 2),
|
|
1588
|
-
y: Math.round(rect.top + rect.height / 2),
|
|
1589
|
-
viewport: viewportPos,
|
|
1590
|
-
href: el.href || null,
|
|
1591
|
-
isVisible: inViewport,
|
|
1592
|
-
intersectionRatio: inViewport ? 1.0 : 0.0,
|
|
1593
|
-
hasClickListener: hasClickListener,
|
|
1594
|
-
rect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height }
|
|
1595
|
-
};
|
|
1596
|
-
|
|
1597
|
-
window.__wt_liveIndex.push(indexEntry);
|
|
1598
|
-
|
|
1599
|
-
// Observe for intersection changes
|
|
1600
|
-
try {
|
|
1601
|
-
intersectionObserver.observe(el);
|
|
1602
|
-
} catch (e) {
|
|
1603
|
-
// Element might not be observable
|
|
1604
|
-
}
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
|
-
// Function to update an existing entry in the live index
|
|
1608
|
-
function updateIndexEntry(el) {
|
|
1609
|
-
const id = el.getAttribute('data-wt-id');
|
|
1610
|
-
if (!id) return;
|
|
1611
|
-
|
|
1612
|
-
const indexEntry = window.__wt_liveIndex.find(e => e.id === id);
|
|
1613
|
-
if (!indexEntry) return;
|
|
1614
|
-
|
|
1615
|
-
const rect = el.getBoundingClientRect();
|
|
1616
|
-
const inViewport = (
|
|
1617
|
-
rect.bottom > 0 && rect.right > 0 &&
|
|
1618
|
-
rect.top < window.innerHeight && rect.left < window.innerWidth
|
|
1619
|
-
);
|
|
1620
|
-
const viewportPos = inViewport ? "visible" : (rect.top >= window.innerHeight ? "below" : "above");
|
|
1621
|
-
|
|
1622
|
-
indexEntry.x = Math.round(rect.left + rect.width / 2);
|
|
1623
|
-
indexEntry.y = Math.round(rect.top + rect.height / 2);
|
|
1624
|
-
indexEntry.viewport = viewportPos;
|
|
1625
|
-
indexEntry.isVisible = inViewport;
|
|
1626
|
-
indexEntry.rect = { left: rect.left, top: rect.top, width: rect.width, height: rect.height };
|
|
1627
|
-
indexEntry.label = el.getAttribute('aria-label') || el.textContent?.trim().slice(0, 60) || '';
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
// Function to remove an entry from the live index
|
|
1631
|
-
function removeIndexEntry(el) {
|
|
1632
|
-
const id = el.getAttribute('data-wt-id');
|
|
1633
|
-
if (!id) return;
|
|
1634
|
-
|
|
1635
|
-
const index = window.__wt_liveIndex.findIndex(e => e.id === id);
|
|
1636
|
-
if (index !== -1) {
|
|
1637
|
-
window.__wt_liveIndex.splice(index, 1);
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
try {
|
|
1641
|
-
intersectionObserver.unobserve(el);
|
|
1642
|
-
} catch (e) {
|
|
1643
|
-
// Element might not be observable
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
1646
|
-
|
|
1647
|
-
// MutationObserver for continuous ID assignment and index maintenance
|
|
1648
|
-
const idAssignmentObserver = new MutationObserver((mutations) => {
|
|
1649
|
-
for (const mutation of mutations) {
|
|
1650
|
-
// Handle added nodes
|
|
1651
|
-
for (const node of mutation.addedNodes) {
|
|
1652
|
-
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
1653
|
-
// Check if the node itself is interactive
|
|
1654
|
-
const interactiveSelector = 'a, button, input, select, textarea, [role="button"], [role="link"], [role="textbox"], [role="checkbox"], [role="radio"], [role="combobox"], [role="searchbox"], [role="menuitem"], [role="tab"], [tabindex]:not([tabindex="-1"])';
|
|
1655
|
-
if (node.matches && node.matches(interactiveSelector)) {
|
|
1656
|
-
assignIdAndIndex(node);
|
|
1657
|
-
}
|
|
1658
|
-
// Check descendants
|
|
1659
|
-
const descendants = node.querySelectorAll ? node.querySelectorAll(interactiveSelector) : [];
|
|
1660
|
-
for (const desc of descendants) {
|
|
1661
|
-
assignIdAndIndex(desc);
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
1665
|
-
|
|
1666
|
-
// Handle removed nodes
|
|
1667
|
-
for (const node of mutation.removedNodes) {
|
|
1668
|
-
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
1669
|
-
if (node.getAttribute && node.getAttribute('data-wt-id')) {
|
|
1670
|
-
removeIndexEntry(node);
|
|
1671
|
-
}
|
|
1672
|
-
const descendants = node.querySelectorAll ? node.querySelectorAll('[data-wt-id]') : [];
|
|
1673
|
-
for (const desc of descendants) {
|
|
1674
|
-
removeIndexEntry(desc);
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
// Handle attribute changes (position, visibility, etc.)
|
|
1680
|
-
if (mutation.type === 'attributes' && mutation.target.getAttribute) {
|
|
1681
|
-
const target = mutation.target;
|
|
1682
|
-
if (target.getAttribute('data-wt-id')) {
|
|
1683
|
-
updateIndexEntry(target);
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
});
|
|
1688
|
-
|
|
1689
|
-
// Start observing when DOM is ready
|
|
1690
|
-
function startIdAssignment() {
|
|
1691
|
-
const interactiveSelector = 'a, button, input, select, textarea, [role="button"], [role="link"], [role="textbox"], [role="checkbox"], [role="radio"], [role="combobox"], [role="searchbox"], [role="menuitem"], [role="tab"], [tabindex]:not([tabindex="-1"])';
|
|
1692
|
-
|
|
1693
|
-
// Assign IDs to existing interactive elements
|
|
1694
|
-
const existing = document.querySelectorAll(interactiveSelector);
|
|
1695
|
-
for (const el of existing) {
|
|
1696
|
-
assignIdAndIndex(el);
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
|
-
// Start observing for changes
|
|
1700
|
-
if (document.body) {
|
|
1701
|
-
idAssignmentObserver.observe(document.body, {
|
|
1702
|
-
childList: true,
|
|
1703
|
-
subtree: true,
|
|
1704
|
-
attributes: true,
|
|
1705
|
-
attributeFilter: ['style', 'class', 'hidden', 'aria-hidden']
|
|
1706
|
-
});
|
|
1707
|
-
} else {
|
|
1708
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
1709
|
-
const existing = document.querySelectorAll(interactiveSelector);
|
|
1710
|
-
for (const el of existing) {
|
|
1711
|
-
assignIdAndIndex(el);
|
|
1712
|
-
}
|
|
1713
|
-
idAssignmentObserver.observe(document.body, {
|
|
1714
|
-
childList: true,
|
|
1715
|
-
subtree: true,
|
|
1716
|
-
attributes: true,
|
|
1717
|
-
attributeFilter: ['style', 'class', 'hidden', 'aria-hidden']
|
|
1718
|
-
});
|
|
1719
|
-
});
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
// Start the ID assignment system
|
|
1724
|
-
if (document.readyState === 'loading') {
|
|
1725
|
-
document.addEventListener('DOMContentLoaded', startIdAssignment);
|
|
1726
|
-
} else {
|
|
1727
|
-
startIdAssignment();
|
|
1728
|
-
}
|
|
1729
|
-
|
|
1730
|
-
// Legacy buildSemanticMap function for backward compatibility
|
|
1731
|
-
// Now reads from the live index instead of rebuilding
|
|
1732
|
-
let cachedMap = null;
|
|
1733
|
-
const legacyObserver = new MutationObserver(() => {
|
|
1734
|
-
cachedMap = null;
|
|
1735
|
-
});
|
|
1736
|
-
// FIX: document.body may be null at document_start; defer observe until body exists
|
|
1737
|
-
if (document.body) {
|
|
1738
|
-
legacyObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class', 'hidden'] });
|
|
1739
|
-
} else {
|
|
1740
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
1741
|
-
if (document.body) legacyObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class', 'hidden'] });
|
|
1742
|
-
}, { once: true });
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
function buildSemanticMap() {
|
|
1746
|
-
if (cachedMap) return cachedMap;
|
|
1747
|
-
|
|
1748
|
-
// Read from the live index maintained by the MutationObserver
|
|
1749
|
-
cachedMap = window.__wt_liveIndex.map(entry => ({
|
|
1750
|
-
id: entry.id,
|
|
1751
|
-
tag: entry.tag,
|
|
1752
|
-
role: entry.role,
|
|
1753
|
-
label: entry.label,
|
|
1754
|
-
placeholder: entry.placeholder,
|
|
1755
|
-
selector: entry.selector,
|
|
1756
|
-
x: entry.x,
|
|
1757
|
-
y: entry.y,
|
|
1758
|
-
viewport: entry.viewport,
|
|
1759
|
-
href: entry.href,
|
|
1760
|
-
coords: { x: entry.x, y: entry.y }
|
|
1761
|
-
}));
|
|
1762
|
-
|
|
1763
|
-
return cachedMap;
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
// ── HUD ─────────────────────────────────────────────────────────
|
|
1767
|
-
const hudHistory = [];
|
|
1768
|
-
const hud = (() => {
|
|
1769
|
-
const el = document.createElement('div');
|
|
1770
|
-
el.id = 'Weavetab-hud';
|
|
1771
|
-
el.setAttribute('data-wt-ignore', '');
|
|
1772
|
-
Object.assign(el.style, {
|
|
1773
|
-
position: 'fixed', bottom: '24px', right: '24px',
|
|
1774
|
-
transform: 'translateY(8px)',
|
|
1775
|
-
color: '#a1a1aa',
|
|
1776
|
-
font: '11px/1.3 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
|
|
1777
|
-
borderRadius: '8px',
|
|
1778
|
-
opacity: '0', pointerEvents: 'none',
|
|
1779
|
-
zIndex: '2147483640',
|
|
1780
|
-
maxWidth: '400px',
|
|
1781
|
-
display: 'flex', flexDirection: 'column-reverse', gap: '4px',
|
|
1782
|
-
transition: 'opacity 0.2s, transform 0.2s',
|
|
1783
|
-
textAlign: 'right',
|
|
1784
|
-
});
|
|
1785
|
-
__wt_safeAppendToBody(el);
|
|
1786
|
-
return el;
|
|
1787
|
-
})();
|
|
1788
|
-
|
|
1789
|
-
function updateHUD(action, detail) {
|
|
1790
|
-
if (window.self !== window.top) return;
|
|
1791
|
-
// Tool call entries display strictly inside the Sidepanel TOOL CALLS section.
|
|
1792
|
-
// Suppress on-page bottom-right HUD popup overlay.
|
|
1793
|
-
if (hud) {
|
|
1794
|
-
hud.style.display = 'none';
|
|
1795
|
-
hud.style.opacity = '0';
|
|
1796
|
-
}
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
// ── ACTIVE TAB INDICATOR ─────────────────────────────────────────
|
|
1800
|
-
class ControlledTabIndicator {
|
|
1801
|
-
constructor() {
|
|
1802
|
-
this._el = document.createElement('div');
|
|
1803
|
-
this._el.id = '__Weavetab_controlled_indicator';
|
|
1804
|
-
this._el.setAttribute('data-wt-ignore', '');
|
|
1805
|
-
Object.assign(this._el.style, {
|
|
1806
|
-
position: 'fixed',
|
|
1807
|
-
top: '0',
|
|
1808
|
-
left: '0',
|
|
1809
|
-
right: '0',
|
|
1810
|
-
height: '3px',
|
|
1811
|
-
zIndex: '2147483646',
|
|
1812
|
-
background: '#3B82F6',
|
|
1813
|
-
boxShadow: '0 0 12px #3B82F6, 0 0 4px #60a5fa',
|
|
1814
|
-
opacity: '0',
|
|
1815
|
-
transition: 'opacity 0.25s ease-in-out',
|
|
1816
|
-
pointerEvents: 'none',
|
|
1817
|
-
});
|
|
1818
|
-
// Animated shimmer
|
|
1819
|
-
this._shimmer = document.createElement('div');
|
|
1820
|
-
Object.assign(this._shimmer.style, {
|
|
1821
|
-
position: 'absolute',
|
|
1822
|
-
top: '0', left: '-60%', width: '60%', height: '100%',
|
|
1823
|
-
background: 'linear-gradient(90deg,transparent,rgba(255,255,255,0.45),transparent)',
|
|
1824
|
-
animation: 'wt-shimmer 1.6s infinite',
|
|
1825
|
-
});
|
|
1826
|
-
// Inject animation keyframes into shadow root
|
|
1827
|
-
var style = document.createElement('style');
|
|
1828
|
-
style.textContent = '@keyframes wt-shimmer{0%{left:-60%}100%{left:120%}}';
|
|
1829
|
-
try { getWeavetabRoot().appendChild(style); } catch(e) {}
|
|
1830
|
-
this._el.appendChild(this._shimmer);
|
|
1831
|
-
getWeavetabRoot().appendChild(this._el);
|
|
1832
|
-
this._hideTimer = null;
|
|
1833
|
-
this._persistent = false;
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
|
-
// Show persistently (while agent is weaving)
|
|
1837
|
-
showPersistent(color) {
|
|
1838
|
-
this._persistent = true;
|
|
1839
|
-
clearTimeout(this._hideTimer);
|
|
1840
|
-
if (color) {
|
|
1841
|
-
this._el.style.background = color;
|
|
1842
|
-
this._el.style.boxShadow = '0 0 12px ' + color + ', 0 0 4px ' + color;
|
|
1843
|
-
}
|
|
1844
|
-
this._el.style.opacity = '1';
|
|
1845
|
-
this._shimmer.style.display = 'block';
|
|
1846
|
-
}
|
|
1847
|
-
|
|
1848
|
-
// Show briefly (1.5s flash for single actions)
|
|
1849
|
-
show(color) {
|
|
1850
|
-
if (this._persistent) return; // Don't override persistent mode
|
|
1851
|
-
if (color) this._el.style.background = color;
|
|
1852
|
-
this._el.style.opacity = '1';
|
|
1853
|
-
clearTimeout(this._hideTimer);
|
|
1854
|
-
this._hideTimer = setTimeout(() => this.hide(), 1500);
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
hide() {
|
|
1858
|
-
this._persistent = false;
|
|
1859
|
-
this._el.style.opacity = '0';
|
|
1860
|
-
this._shimmer.style.display = 'none';
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
destroy() {
|
|
1864
|
-
clearTimeout(this._hideTimer);
|
|
1865
|
-
this._el.remove();
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
const controlledIndicator = new ControlledTabIndicator();
|
|
1870
|
-
|
|
1871
|
-
// ── MESSAGE BRIDGE ──────────────────────────────────────────────
|
|
1872
|
-
// Shared handler for both CustomEvent (MAIN world dispatch) and
|
|
1873
|
-
// postMessage (cross-world: MAIN → ISOLATED) communication channels.
|
|
1874
|
-
var __wt_lastCursor = { x: -9999, y: -9999 };
|
|
1875
|
-
var __wt_processedMsgIds = new Set();
|
|
1876
|
-
|
|
1877
|
-
function __wt_handleWeavetabMsg(msg) {
|
|
1878
|
-
if (!msg) return;
|
|
1879
|
-
if (Array.isArray(msg)) { msg.forEach(m => __wt_handleWeavetabMsg(m)); return; }
|
|
1880
|
-
if (msg._id) { if (__wt_processedMsgIds.has(msg._id)) return; __wt_processedMsgIds.add(msg._id); if (__wt_processedMsgIds.size > 100) __wt_processedMsgIds.clear(); }
|
|
1881
|
-
|
|
1882
|
-
if (msg.type === 'Weavetab_STATE') {
|
|
1883
|
-
if (msg.cursor) {
|
|
1884
|
-
__wt_lastCursor = { x: msg.cursor.x, y: msg.cursor.y };
|
|
1885
|
-
if (msg.cursor.waypoints && msg.cursor.duration_ms) {
|
|
1886
|
-
__weaveCursor.setWaypoints(msg.cursor.waypoints, msg.cursor.duration_ms);
|
|
1887
|
-
}
|
|
1888
|
-
moveCursor(msg.cursor.x, msg.cursor.y, msg.state);
|
|
1889
|
-
}
|
|
1890
|
-
else if (msg.state) { moveCursor(__wt_lastCursor.x, __wt_lastCursor.y, msg.state); }
|
|
1891
|
-
if (msg.ripple) ripple(msg.ripple.x, msg.ripple.y);
|
|
1892
|
-
|
|
1893
|
-
if (msg.userFeedback === true && msg.feedbackText) {
|
|
1894
|
-
__wt_feedbackSticky = true;
|
|
1895
|
-
clearTimeout(window.__wt_feedbackStickyTimer);
|
|
1896
|
-
window.__wt_feedbackStickyTimer = setTimeout(function(){ __wt_feedbackSticky = false; }, 6000);
|
|
1897
|
-
} else if (msg.userFeedback === false) {
|
|
1898
|
-
setTimeout(function(){ __wt_feedbackSticky = false; }, 2000);
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
if (msg.hud) {
|
|
1902
|
-
updateHUD(msg.hud.action, msg.hud.detail);
|
|
1903
|
-
var a = (msg.hud.action || '').trim();
|
|
1904
|
-
if (a === 'reading the page...') {
|
|
1905
|
-
__weaveCursor.setState('reading');
|
|
1906
|
-
} else if (a === 'Mission Running') {
|
|
1907
|
-
__wt_toolActive = true;
|
|
1908
|
-
__weaveCursor.setState('eval');
|
|
1909
|
-
} else if (a.startsWith('[OK]')) {
|
|
1910
|
-
__weaveCursor.setState('idle');
|
|
1911
|
-
__wt_toolActive = false;
|
|
1912
|
-
} else if (a.startsWith('[ERR]')) {
|
|
1913
|
-
__weaveCursor.setState('stuck');
|
|
1914
|
-
__wt_toolActive = false;
|
|
1915
|
-
setTimeout(function(){ __weaveCursor.setState('idle'); }, 3000);
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
if (msg.cursor || msg.hud) {
|
|
1919
|
-
controlledIndicator.show('#3B82F6');
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
|
|
1923
|
-
// Pure thought event from browser_thoughts tool only
|
|
1924
|
-
if (msg.type === 'WT_THOUGHT' || msg.type === 'Weavetab_THOUGHT') {
|
|
1925
|
-
__wt_showThought(msg.text, msg);
|
|
1926
|
-
}
|
|
1927
|
-
if (msg.type === 'WT_CLEAR_THOUGHT' || msg.type === 'Weavetab_CLEAR_THOUGHT') {
|
|
1928
|
-
__wt_removeThought();
|
|
1929
|
-
}
|
|
1930
|
-
|
|
1931
|
-
// Handle plan updates via sendToExtension channel
|
|
1932
|
-
if (msg.type === 'Weavetab_STATE' && msg.plan) {
|
|
1933
|
-
const p = msg.plan;
|
|
1934
|
-
if (p.action === 'hide') {
|
|
1935
|
-
if (window.__wt_plan) window.__wt_plan('hide', '', []);
|
|
1936
|
-
} else {
|
|
1937
|
-
if (window.__wt_plan) window.__wt_plan(p.action || 'create', p.goal || '', p.tasks || []);
|
|
1938
|
-
}
|
|
1939
|
-
}
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
if (msg.type === 'WT_USER_NOTE_INJECT') {
|
|
1943
|
-
window.__wt_user_notes = window.__wt_user_notes || [];
|
|
1944
|
-
window.__wt_user_notes.push({ text: msg.text, time: Date.now() });
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
|
-
if (msg.type === 'GET_SEMANTIC_MAP') {
|
|
1948
|
-
const map = window.__Weavetab_fullMap();
|
|
1949
|
-
if (window.__wt_user_notes && window.__wt_user_notes.length > 0) {
|
|
1950
|
-
// Keep notes for 2 minutes
|
|
1951
|
-
window.__wt_user_notes = window.__wt_user_notes.filter(n => Date.now() - n.time < 120000);
|
|
1952
|
-
const notesMap = window.__wt_user_notes.map((note, i) => ({
|
|
1953
|
-
id: `user-msg-${i}`,
|
|
1954
|
-
role: 'alert',
|
|
1955
|
-
tag: 'div',
|
|
1956
|
-
text: `URGENT MESSAGE FROM USER: "${note.text}"`,
|
|
1957
|
-
coords: [0, 0, 100, 100],
|
|
1958
|
-
interactive: false
|
|
1959
|
-
}));
|
|
1960
|
-
map.unshift(...notesMap);
|
|
1961
|
-
}
|
|
1962
|
-
activateXRay(map.filter(m => m.coords && m.text).slice(0, 12));
|
|
1963
|
-
window.dispatchEvent(new CustomEvent('Weavetab-msg-out', { detail: { id: msg.id, data: map } }));
|
|
1964
|
-
}
|
|
1965
|
-
|
|
1966
|
-
if (msg.type === 'RESOLVE_EXTENSION_URL') {
|
|
1967
|
-
var url = '';
|
|
1968
|
-
try { url = browserAPI.runtime.getURL((msg.page || 'dashboard') + '.html'); } catch(e) {}
|
|
1969
|
-
window.dispatchEvent(new CustomEvent('Weavetab-msg-out', { detail: { id: msg.id, data: url } }));
|
|
1970
|
-
}
|
|
1971
|
-
|
|
1972
|
-
if (msg.type === 'Weavetab_CONFIG') {
|
|
1973
|
-
window.__wt_devMode = !!msg.devMode;
|
|
1974
|
-
window.dispatchEvent(new CustomEvent('wt-config-sync'));
|
|
1975
|
-
__wt_safeSendMessage(msg);
|
|
1976
|
-
}
|
|
1977
|
-
|
|
1978
|
-
if (msg.type === 'Weavetab_GROUP_STATE') {
|
|
1979
|
-
__wt_safeSendMessage(msg);
|
|
1980
|
-
// Only ever blue dot — no yellow. Idle state = no dot.
|
|
1981
|
-
const dotColor = (msg.state === 'idle') ? null : '#3B82F6';
|
|
1982
|
-
if (dotColor) {
|
|
1983
|
-
controlledIndicator.show(dotColor);
|
|
1984
|
-
}
|
|
1985
|
-
updateFaviconDot(msg.state, msg.isActive);
|
|
1986
|
-
// Parked tabs: clear thought bubble and reading glow
|
|
1987
|
-
if (msg.isActive === false) {
|
|
1988
|
-
__wt_removeThought();
|
|
1989
|
-
__wt_hideReadingGlow();
|
|
1990
|
-
}
|
|
1991
|
-
if (msg.state === 'idle') {
|
|
1992
|
-
__wt_hideReadingGlow();
|
|
1993
|
-
__wt_destroyBubble();
|
|
1994
|
-
__wt_feedbackSticky = false;
|
|
1995
|
-
__wt_processThoughtQueue();
|
|
1996
|
-
restoreOriginalFavicon();
|
|
1997
|
-
// Hide the persistent indicator bar when idle
|
|
1998
|
-
controlledIndicator.hide();
|
|
1999
|
-
__weaveCursor.setState('idle');
|
|
2000
|
-
} else if (msg.state === 'weaving') {
|
|
2001
|
-
// Show persistent blue bar while agent works
|
|
2002
|
-
controlledIndicator.showPersistent('#3B82F6');
|
|
2003
|
-
__weaveCursor.setState('eval');
|
|
2004
|
-
} else if (msg.state === 'stuck') {
|
|
2005
|
-
// Still show blue bar (not red) when stuck
|
|
2006
|
-
controlledIndicator.showPersistent('#3B82F6');
|
|
2007
|
-
__weaveCursor.setState('stuck');
|
|
2008
|
-
}
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
if (msg.type === 'Weavetab_THOUGHT') {
|
|
2013
|
-
__wt_showThought(msg.text, { active: msg.active });
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
if (msg.type === 'Weavetab_ASK') {
|
|
2017
|
-
// Legacy support — route to new unified question system
|
|
2018
|
-
console.log('[Weavetab] Received ASK request (legacy):', msg.question);
|
|
2019
|
-
__wt_showQuestion(msg.question, 'sentence', msg.timeout || 120);
|
|
2020
|
-
window.dispatchEvent(new CustomEvent('Weavetab-msg-out', { detail: { id: msg.id, data: { status: 'shown' } } }));
|
|
2021
|
-
}
|
|
2022
|
-
|
|
2023
|
-
if (msg.type === 'Weavetab_CLEAR_THOUGHT') {
|
|
2024
|
-
__wt_removeThought();
|
|
2025
|
-
__wt_hideReadingGlow();
|
|
2026
|
-
__wt_processThoughtQueue();
|
|
2027
|
-
}
|
|
2028
|
-
|
|
2029
|
-
if (msg.type === 'Weavetab_RELOAD_EXTENSION') {
|
|
2030
|
-
__wt_safeSendMessage({ type: 'RELOAD_EXTENSION' });
|
|
2031
|
-
}
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
|
-
// Channel 1: CustomEvent from MAIN world (used by fallback + sendToExtension)
|
|
2035
|
-
window.addEventListener('Weavetab-msg-in', (e) => {
|
|
2036
|
-
__wt_handleWeavetabMsg(e.detail);
|
|
2037
|
-
});
|
|
2038
|
-
|
|
2039
|
-
// Channel 2: postMessage from MAIN world (cross-world MV3 bridge)
|
|
2040
|
-
window.addEventListener('message', (event) => {
|
|
2041
|
-
if (event.data && event.data.type === 'Weavetab-msg-in' && event.data.data) {
|
|
2042
|
-
__wt_handleWeavetabMsg(event.data.data);
|
|
2043
|
-
}
|
|
2044
|
-
// ── User feedback relay: WeaveTab OS panel → background storage queue ────
|
|
2045
|
-
// When the user types in the chat panel and hits Send, submitFeedback() fires
|
|
2046
|
-
// a __WT_FEEDBACK postMessage. We relay it to the background so it lands in
|
|
2047
|
-
// chrome.storage.local and window.__Weavetab.popUserFeedback() can return it.
|
|
2048
|
-
if (event.data && event.data.type === '__WT_FEEDBACK' && event.data.text) {
|
|
2049
|
-
try {
|
|
2050
|
-
if (browserAPI.runtime && browserAPI.runtime.id) {
|
|
2051
|
-
browserAPI.runtime.sendMessage({ type: 'USER_FEEDBACK', text: event.data.text }).catch(function(){});
|
|
2052
|
-
}
|
|
2053
|
-
} catch(e) {}
|
|
2054
|
-
}
|
|
2055
|
-
});
|
|
2056
|
-
|
|
2057
|
-
// ── FAVICON STATUS DOT INDICATOR ───────────────────────────────
|
|
2058
|
-
let wtFaviconObserver = null;
|
|
2059
|
-
|
|
2060
|
-
let __wt_faviconStaleTimer = null;
|
|
2061
|
-
|
|
2062
|
-
function updateFaviconDot(state, isActive) {
|
|
2063
|
-
if (__wt_faviconStaleTimer) { clearTimeout(__wt_faviconStaleTimer); __wt_faviconStaleTimer = null; }
|
|
2064
|
-
|
|
2065
|
-
if (state === 'idle' || !state || isActive === false) {
|
|
2066
|
-
if (wtFaviconObserver) {
|
|
2067
|
-
wtFaviconObserver.disconnect();
|
|
2068
|
-
wtFaviconObserver = null;
|
|
2069
|
-
}
|
|
2070
|
-
restoreOriginalFavicon();
|
|
2071
|
-
return;
|
|
2072
|
-
}
|
|
2073
|
-
|
|
2074
|
-
// Always use blue — never red or yellow for favicon dot
|
|
2075
|
-
const color = '#3B82F6';
|
|
2076
|
-
|
|
2077
|
-
if (!wtFaviconObserver) {
|
|
2078
|
-
wtFaviconObserver = new MutationObserver(() => {
|
|
2079
|
-
wtFaviconObserver.disconnect();
|
|
2080
|
-
applyFaviconDot(color);
|
|
2081
|
-
wtFaviconObserver.observe(document.head, { childList: true, subtree: true });
|
|
2082
|
-
});
|
|
2083
|
-
} else {
|
|
2084
|
-
wtFaviconObserver.disconnect();
|
|
2085
|
-
}
|
|
2086
|
-
|
|
2087
|
-
applyFaviconDot(color);
|
|
2088
|
-
wtFaviconObserver.observe(document.head, { childList: true, subtree: true });
|
|
2089
|
-
|
|
2090
|
-
// Set stale timer: clear dot + hide glow if no message for 30s
|
|
2091
|
-
__wt_faviconStaleTimer = setTimeout(function() {
|
|
2092
|
-
restoreOriginalFavicon();
|
|
2093
|
-
__wt_hideReadingGlow();
|
|
2094
|
-
__wt_destroyBubble();
|
|
2095
|
-
}, 30000);
|
|
2096
|
-
}
|
|
2097
|
-
|
|
2098
|
-
function restoreOriginalFavicon() {
|
|
2099
|
-
const customLink = document.getElementById('wt-custom-favicon');
|
|
2100
|
-
if (customLink) customLink.remove();
|
|
2101
|
-
|
|
2102
|
-
const links = document.querySelectorAll('link[rel*="icon"]');
|
|
2103
|
-
links.forEach(link => {
|
|
2104
|
-
if (link.dataset.wtOrigHref) {
|
|
2105
|
-
link.href = link.dataset.wtOrigHref;
|
|
2106
|
-
delete link.dataset.wtOrigHref;
|
|
2107
|
-
}
|
|
2108
|
-
});
|
|
2109
|
-
}
|
|
2110
|
-
|
|
2111
|
-
function applyFaviconDot(color) {
|
|
2112
|
-
const links = Array.from(document.querySelectorAll('link[rel*="icon"]')).filter(l => l.id !== 'wt-custom-favicon');
|
|
2113
|
-
let originalHref = '';
|
|
2114
|
-
|
|
2115
|
-
if (links.length > 0) {
|
|
2116
|
-
const link = links[0];
|
|
2117
|
-
if (!link.dataset.wtOrigHref) {
|
|
2118
|
-
link.dataset.wtOrigHref = link.href;
|
|
2119
|
-
}
|
|
2120
|
-
originalHref = link.dataset.wtOrigHref;
|
|
2121
|
-
} else {
|
|
2122
|
-
originalHref = '/favicon.ico';
|
|
2123
|
-
}
|
|
2124
|
-
|
|
2125
|
-
const canvas = document.createElement('canvas');
|
|
2126
|
-
canvas.width = 32;
|
|
2127
|
-
canvas.height = 32;
|
|
2128
|
-
const ctx = canvas.getContext('2d');
|
|
2129
|
-
|
|
2130
|
-
const img = new Image();
|
|
2131
|
-
img.crossOrigin = 'anonymous';
|
|
2132
|
-
img.onload = () => {
|
|
2133
|
-
try {
|
|
2134
|
-
ctx.drawImage(img, 0, 0, 32, 32);
|
|
2135
|
-
drawDot(ctx, color);
|
|
2136
|
-
setFaviconHref(canvas.toDataURL('image/png'));
|
|
2137
|
-
} catch (e) {
|
|
2138
|
-
drawFallbackIcon(ctx, color);
|
|
2139
|
-
setFaviconHref(canvas.toDataURL('image/png'));
|
|
2140
|
-
}
|
|
2141
|
-
};
|
|
2142
|
-
img.onerror = () => {
|
|
2143
|
-
drawFallbackIcon(ctx, color);
|
|
2144
|
-
setFaviconHref(canvas.toDataURL('image/png'));
|
|
2145
|
-
};
|
|
2146
|
-
img.src = originalHref;
|
|
2147
|
-
}
|
|
2148
|
-
|
|
2149
|
-
function drawDot(ctx, color) {
|
|
2150
|
-
// Solid dot
|
|
2151
|
-
ctx.beginPath();
|
|
2152
|
-
ctx.arc(24, 24, 5.5, 0, 2 * Math.PI);
|
|
2153
|
-
ctx.fillStyle = color;
|
|
2154
|
-
ctx.fill();
|
|
2155
|
-
}
|
|
2156
|
-
|
|
2157
|
-
function drawFallbackIcon(ctx, color) {
|
|
2158
|
-
ctx.clearRect(0, 0, 32, 32);
|
|
2159
|
-
ctx.beginPath();
|
|
2160
|
-
ctx.arc(16, 16, 11, 0, 2 * Math.PI);
|
|
2161
|
-
ctx.fillStyle = '#1E1F22';
|
|
2162
|
-
ctx.fill();
|
|
2163
|
-
ctx.strokeStyle = '#3C3F45';
|
|
2164
|
-
ctx.lineWidth = 1.5;
|
|
2165
|
-
ctx.stroke();
|
|
2166
|
-
|
|
2167
|
-
ctx.beginPath();
|
|
2168
|
-
ctx.arc(16, 16, 6, 0, 2 * Math.PI);
|
|
2169
|
-
ctx.fillStyle = '#7C3AED';
|
|
2170
|
-
ctx.fill();
|
|
2171
|
-
|
|
2172
|
-
drawDot(ctx, color);
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
|
-
function setFaviconHref(dataUrl) {
|
|
2176
|
-
let customLink = document.getElementById('wt-custom-favicon');
|
|
2177
|
-
if (!customLink) {
|
|
2178
|
-
customLink = document.createElement('link');
|
|
2179
|
-
customLink.id = 'wt-custom-favicon';
|
|
2180
|
-
customLink.rel = 'icon';
|
|
2181
|
-
customLink.type = 'image/png';
|
|
2182
|
-
(document.head || document.documentElement).appendChild(customLink);
|
|
2183
|
-
}
|
|
2184
|
-
customLink.href = dataUrl;
|
|
2185
|
-
|
|
2186
|
-
const links = document.querySelectorAll('link[rel*="icon"]');
|
|
2187
|
-
links.forEach(link => {
|
|
2188
|
-
if (link.id !== 'wt-custom-favicon') {
|
|
2189
|
-
if (!link.dataset.wtOrigHref) {
|
|
2190
|
-
link.dataset.wtOrigHref = link.href;
|
|
2191
|
-
}
|
|
2192
|
-
link.href = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
|
2193
|
-
}
|
|
2194
|
-
});
|
|
2195
|
-
}
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
// ── SIDE PANEL REMOVED (Replaced by pure middle-down Thought HUD) ──
|
|
2200
|
-
|
|
2201
|
-
(function initAreaSnipeEngine() {
|
|
2202
|
-
let isSnipeActive = false;
|
|
2203
|
-
let currentSnipeMode = 'dom_target';
|
|
2204
|
-
let overlayEl = null;
|
|
2205
|
-
let marqueeBox = null;
|
|
2206
|
-
let sizeBadge = null;
|
|
2207
|
-
let headerPill = null;
|
|
2208
|
-
let startX = 0, startY = 0;
|
|
2209
|
-
let isDragging = false;
|
|
2210
|
-
|
|
2211
|
-
function createOverlay(mode) {
|
|
2212
|
-
if (overlayEl) overlayEl.remove();
|
|
2213
|
-
currentSnipeMode = mode || 'dom_target';
|
|
2214
|
-
|
|
2215
|
-
overlayEl = document.createElement('div');
|
|
2216
|
-
overlayEl.id = 'wt-snipe-overlay';
|
|
2217
|
-
overlayEl.setAttribute('data-wt-ignore', 'true');
|
|
2218
|
-
overlayEl.style.cssText = `
|
|
2219
|
-
position: fixed; inset: 0; z-index: 2147483647;
|
|
2220
|
-
background: rgba(0, 0, 0, 0.35); backdrop-filter: blur(2px);
|
|
2221
|
-
cursor: crosshair; user-select: none;
|
|
2222
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
2223
|
-
`;
|
|
2224
|
-
|
|
2225
|
-
headerPill = document.createElement('div');
|
|
2226
|
-
headerPill.style.cssText = `
|
|
2227
|
-
position: fixed; top: 16px; left: 50%; transform: translateX(-50%);
|
|
2228
|
-
background: #18181b; border: 1px solid #3f3f46; color: #f4f4f5;
|
|
2229
|
-
font-size: 11px; font-weight: 500; padding: 6px 16px; border-radius: 20px;
|
|
2230
|
-
box-shadow: 0 4px 20px rgba(0,0,0,0.6); pointer-events: none;
|
|
2231
|
-
display: flex; align-items: center; gap: 8px; z-index: 2147483647;
|
|
2232
|
-
`;
|
|
2233
|
-
const modeLabel = currentSnipeMode === 'visual_crop' ? 'Vision Crop' : 'DOM Target';
|
|
2234
|
-
headerPill.innerHTML = `<span style="display:inline-flex;align-items:center;gap:6px;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#38bdf8" stroke-width="2"><path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><rect x="7" y="7" width="10" height="10" rx="1"/></svg>Drag box to select area</span> <span style="color:#06b6d4;font-weight:600;">[${modeLabel}]</span> <span style="color:#71717a;">(Esc to cancel)</span>`;
|
|
2235
|
-
|
|
2236
|
-
// Marquee selection box matching img1 Gemini/YouTube Chrome UI style
|
|
2237
|
-
marqueeBox = document.createElement('div');
|
|
2238
|
-
marqueeBox.style.cssText = `
|
|
2239
|
-
position: fixed; border: 1px solid rgba(255, 255, 255, 0.45);
|
|
2240
|
-
border-radius: 12px;
|
|
2241
|
-
box-shadow: 0 0 24px 3px rgba(147, 197, 253, 0.45), inset 0 0 20px rgba(147, 197, 253, 0.2);
|
|
2242
|
-
pointer-events: auto; display: none; z-index: 2147483647;
|
|
2243
|
-
background: rgba(0, 0, 0, 0.1);
|
|
2244
|
-
`;
|
|
2245
|
-
|
|
2246
|
-
// Four thick white rounded corner reticles (matching img1)
|
|
2247
|
-
const corners = [
|
|
2248
|
-
'top: -2px; left: -2px; border-top: 4px solid #ffffff; border-left: 4px solid #ffffff; border-top-left-radius: 10px;',
|
|
2249
|
-
'top: -2px; right: -2px; border-top: 4px solid #ffffff; border-right: 4px solid #ffffff; border-top-right-radius: 10px;',
|
|
2250
|
-
'bottom: -2px; left: -2px; border-bottom: 4px solid #ffffff; border-left: 4px solid #ffffff; border-bottom-left-radius: 10px;',
|
|
2251
|
-
'bottom: -2px; right: -2px; border-bottom: 4px solid #ffffff; border-right: 4px solid #ffffff; border-bottom-right-radius: 10px;'
|
|
2252
|
-
];
|
|
2253
|
-
corners.forEach(styleCss => {
|
|
2254
|
-
const c = document.createElement('div');
|
|
2255
|
-
c.style.cssText = `position: absolute; width: 18px; height: 18px; pointer-events: none; ${styleCss}`;
|
|
2256
|
-
marqueeBox.appendChild(c);
|
|
2257
|
-
});
|
|
2258
|
-
|
|
2259
|
-
// Circular close X button on top-right corner
|
|
2260
|
-
const closeBtn = document.createElement('button');
|
|
2261
|
-
closeBtn.style.cssText = `
|
|
2262
|
-
position: absolute; top: -11px; right: -11px;
|
|
2263
|
-
width: 22px; height: 22px; border-radius: 50%;
|
|
2264
|
-
background: rgba(24, 24, 27, 0.95); border: 1px solid rgba(255, 255, 255, 0.25);
|
|
2265
|
-
color: #f4f4f5; cursor: pointer; display: flex; align-items: center; justify-content: center;
|
|
2266
|
-
box-shadow: 0 4px 12px rgba(0,0,0,0.6); z-index: 2147483647; transition: transform 0.15s, background 0.15s;
|
|
2267
|
-
`;
|
|
2268
|
-
closeBtn.innerHTML = `<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
|
|
2269
|
-
closeBtn.onclick = function(e) {
|
|
2270
|
-
e.stopPropagation();
|
|
2271
|
-
destroyOverlay();
|
|
2272
|
-
};
|
|
2273
|
-
marqueeBox.appendChild(closeBtn);
|
|
2274
|
-
|
|
2275
|
-
sizeBadge = document.createElement('div');
|
|
2276
|
-
sizeBadge.style.cssText = `
|
|
2277
|
-
position: absolute; bottom: -28px; left: 50%; transform: translateX(-50%);
|
|
2278
|
-
background: rgba(15, 23, 42, 0.9); backdrop-filter: blur(8px); color: #e2e8f0; font-weight: 600;
|
|
2279
|
-
font-size: 10px; font-family: -apple-system, BlinkMacSystemFont, sans-serif; padding: 2px 8px; border-radius: 12px;
|
|
2280
|
-
border: 1px solid rgba(255,255,255,0.15); box-shadow: 0 4px 12px rgba(0,0,0,0.5); pointer-events: none;
|
|
2281
|
-
white-space: nowrap;
|
|
2282
|
-
`;
|
|
2283
|
-
marqueeBox.appendChild(sizeBadge);
|
|
2284
|
-
|
|
2285
|
-
overlayEl.appendChild(headerPill);
|
|
2286
|
-
overlayEl.appendChild(marqueeBox);
|
|
2287
|
-
document.documentElement.appendChild(overlayEl);
|
|
2288
|
-
|
|
2289
|
-
isSnipeActive = true;
|
|
2290
|
-
}
|
|
2291
|
-
|
|
2292
|
-
function destroyOverlay() {
|
|
2293
|
-
if (overlayEl) {
|
|
2294
|
-
overlayEl.remove();
|
|
2295
|
-
overlayEl = null;
|
|
2296
|
-
}
|
|
2297
|
-
isSnipeActive = false;
|
|
2298
|
-
isDragging = false;
|
|
2299
|
-
}
|
|
2300
|
-
|
|
2301
|
-
function handleMouseDown(e) {
|
|
2302
|
-
if (!isSnipeActive || e.button !== 0) return;
|
|
2303
|
-
e.preventDefault();
|
|
2304
|
-
e.stopPropagation();
|
|
2305
|
-
|
|
2306
|
-
isDragging = true;
|
|
2307
|
-
startX = e.clientX;
|
|
2308
|
-
startY = e.clientY;
|
|
2309
|
-
|
|
2310
|
-
marqueeBox.style.left = startX + 'px';
|
|
2311
|
-
marqueeBox.style.top = startY + 'px';
|
|
2312
|
-
marqueeBox.style.width = '0px';
|
|
2313
|
-
marqueeBox.style.height = '0px';
|
|
2314
|
-
marqueeBox.style.display = 'block';
|
|
2315
|
-
}
|
|
2316
|
-
|
|
2317
|
-
function handleMouseMove(e) {
|
|
2318
|
-
if (!isSnipeActive || !isDragging) return;
|
|
2319
|
-
e.preventDefault();
|
|
2320
|
-
e.stopPropagation();
|
|
2321
|
-
|
|
2322
|
-
const currentX = e.clientX;
|
|
2323
|
-
const currentY = e.clientY;
|
|
2324
|
-
|
|
2325
|
-
const left = Math.min(startX, currentX);
|
|
2326
|
-
const top = Math.min(startY, currentY);
|
|
2327
|
-
const width = Math.abs(currentX - startX);
|
|
2328
|
-
const height = Math.abs(currentY - startY);
|
|
2329
|
-
|
|
2330
|
-
marqueeBox.style.left = left + 'px';
|
|
2331
|
-
marqueeBox.style.top = top + 'px';
|
|
2332
|
-
marqueeBox.style.width = width + 'px';
|
|
2333
|
-
marqueeBox.style.height = height + 'px';
|
|
2334
|
-
|
|
2335
|
-
sizeBadge.textContent = `${width} × ${height} px`;
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
function handleMouseUp(e) {
|
|
2339
|
-
if (!isSnipeActive || !isDragging) return;
|
|
2340
|
-
e.preventDefault();
|
|
2341
|
-
e.stopPropagation();
|
|
2342
|
-
|
|
2343
|
-
isDragging = false;
|
|
2344
|
-
const currentX = e.clientX;
|
|
2345
|
-
const currentY = e.clientY;
|
|
2346
|
-
|
|
2347
|
-
const left = Math.min(startX, currentX);
|
|
2348
|
-
const top = Math.min(startY, currentY);
|
|
2349
|
-
const width = Math.abs(currentX - startX);
|
|
2350
|
-
const height = Math.abs(currentY - startY);
|
|
2351
|
-
|
|
2352
|
-
if (width < 10 || height < 10) {
|
|
2353
|
-
destroyOverlay();
|
|
2354
|
-
return;
|
|
2355
|
-
}
|
|
2356
|
-
|
|
2357
|
-
if (currentSnipeMode === 'visual_crop') {
|
|
2358
|
-
captureVisualCrop(left, top, width, height);
|
|
2359
|
-
} else {
|
|
2360
|
-
captureDOMTargets(left, top, width, height);
|
|
2361
|
-
}
|
|
2362
|
-
|
|
2363
|
-
destroyOverlay();
|
|
2364
|
-
}
|
|
2365
|
-
|
|
2366
|
-
function handleKeyDown(e) {
|
|
2367
|
-
if (e.key === 'Escape' && isSnipeActive) {
|
|
2368
|
-
destroyOverlay();
|
|
2369
|
-
}
|
|
2370
|
-
if (e.altKey && (e.key === 's' || e.key === 'S')) {
|
|
2371
|
-
e.preventDefault();
|
|
2372
|
-
createOverlay('dom_target');
|
|
2373
|
-
}
|
|
2374
|
-
}
|
|
2375
|
-
|
|
2376
|
-
window.addEventListener('mousedown', handleMouseDown, true);
|
|
2377
|
-
window.addEventListener('mousemove', handleMouseMove, true);
|
|
2378
|
-
window.addEventListener('mouseup', handleMouseUp, true);
|
|
2379
|
-
window.addEventListener('keydown', handleKeyDown, true);
|
|
2380
|
-
|
|
2381
|
-
// ── Mode 1: DOM Element Intersector ──────────────────────────────────────
|
|
2382
|
-
function captureDOMTargets(left, top, width, height) {
|
|
2383
|
-
const right = left + width;
|
|
2384
|
-
const bottom = top + height;
|
|
2385
|
-
const elements = [];
|
|
2386
|
-
|
|
2387
|
-
const allEls = document.querySelectorAll('button, input, select, textarea, a, h1, h2, h3, h4, h5, h6, p, table, tr, td, th, div, span, [role]');
|
|
2388
|
-
allEls.forEach((el) => {
|
|
2389
|
-
if (el.hasAttribute('data-wt-ignore') || el.closest('#wt-snipe-overlay')) return;
|
|
2390
|
-
const r = el.getBoundingClientRect();
|
|
2391
|
-
if (r.width === 0 || r.height === 0) return;
|
|
2392
|
-
|
|
2393
|
-
const overlaps = !(r.right < left || r.left > right || r.bottom < top || r.top > bottom);
|
|
2394
|
-
if (overlaps) {
|
|
2395
|
-
const text = (el.innerText || el.value || el.getAttribute('aria-label') || '').trim().slice(0, 100);
|
|
2396
|
-
if (text || ['INPUT', 'BUTTON', 'A', 'SELECT', 'TEXTAREA'].includes(el.tagName)) {
|
|
2397
|
-
elements.push({
|
|
2398
|
-
tag: el.tagName.toLowerCase(),
|
|
2399
|
-
selector: getStableSelector(el),
|
|
2400
|
-
ref: el.getAttribute('data-wt-id') || null,
|
|
2401
|
-
text: text,
|
|
2402
|
-
role: el.getAttribute('role') || null,
|
|
2403
|
-
rect: { x: Math.round(r.left), y: Math.round(r.top), w: Math.round(r.width), h: Math.round(r.height) }
|
|
2404
|
-
});
|
|
2405
|
-
}
|
|
2406
|
-
}
|
|
2407
|
-
});
|
|
2408
|
-
|
|
2409
|
-
const uniqueElements = elements.slice(0, 15);
|
|
2410
|
-
|
|
2411
|
-
__wt_safeSendMessage({
|
|
2412
|
-
type: 'WT_SNIPE_COMPLETED',
|
|
2413
|
-
payload: {
|
|
2414
|
-
mode: 'dom_target',
|
|
2415
|
-
elements: uniqueElements,
|
|
2416
|
-
boundingRect: { x: left, y: top, width, height }
|
|
2417
|
-
}
|
|
2418
|
-
});
|
|
2419
|
-
}
|
|
2420
|
-
|
|
2421
|
-
// ── Mode 2: Visual WebP Crop ─────────────────────────────────────────────
|
|
2422
|
-
function captureVisualCrop(left, top, width, height) {
|
|
2423
|
-
try {
|
|
2424
|
-
const canvas = document.createElement('canvas');
|
|
2425
|
-
canvas.width = width;
|
|
2426
|
-
canvas.height = height;
|
|
2427
|
-
const ctx = canvas.getContext('2d');
|
|
2428
|
-
|
|
2429
|
-
const htmlContent = document.documentElement.outerHTML;
|
|
2430
|
-
const dataSvg = `
|
|
2431
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="${window.innerWidth}" height="${window.innerHeight}">
|
|
2432
|
-
<foreignObject width="100%" height="100%">
|
|
2433
|
-
<div xmlns="http://www.w3.org/1999/xhtml">
|
|
2434
|
-
${htmlContent}
|
|
2435
|
-
</div>
|
|
2436
|
-
</foreignObject>
|
|
2437
|
-
</svg>
|
|
2438
|
-
`;
|
|
2439
|
-
|
|
2440
|
-
const img = new Image();
|
|
2441
|
-
const blob = new Blob([dataSvg], { type: 'image/svg+xml;charset=utf-8' });
|
|
2442
|
-
const url = URL.createObjectURL(blob);
|
|
2443
|
-
|
|
2444
|
-
img.onload = function() {
|
|
2445
|
-
ctx.drawImage(img, left, top, width, height, 0, 0, width, height);
|
|
2446
|
-
URL.revokeObjectURL(url);
|
|
2447
|
-
const webpBase64 = canvas.toDataURL('image/webp', 0.75);
|
|
2448
|
-
|
|
2449
|
-
__wt_safeSendMessage({
|
|
2450
|
-
type: 'WT_SNIPE_COMPLETED',
|
|
2451
|
-
payload: {
|
|
2452
|
-
mode: 'visual_crop',
|
|
2453
|
-
cropImage: webpBase64,
|
|
2454
|
-
boundingRect: { x: left, y: top, width, height }
|
|
2455
|
-
}
|
|
2456
|
-
});
|
|
2457
|
-
};
|
|
2458
|
-
img.onerror = function() {
|
|
2459
|
-
captureDOMTargets(left, top, width, height);
|
|
2460
|
-
};
|
|
2461
|
-
img.src = url;
|
|
2462
|
-
} catch(e) {
|
|
2463
|
-
captureDOMTargets(left, top, width, height);
|
|
2464
|
-
}
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
|
-
// Listen for snipe trigger from background
|
|
2468
|
-
browserAPI.runtime.onMessage.addListener((msg) => {
|
|
2469
|
-
if (msg.type === 'WT_START_SNIPE') {
|
|
2470
|
-
createOverlay(msg.mode || 'dom_target');
|
|
2471
|
-
}
|
|
2472
|
-
if (msg.type === 'WT_SNIPE_INJECT' && msg.entry) {
|
|
2473
|
-
const noteStr = typeof msg.entry === 'string' ? msg.entry : JSON.stringify(msg.entry);
|
|
2474
|
-
try {
|
|
2475
|
-
if (!window.__wt_chat_queue) window.__wt_chat_queue = [];
|
|
2476
|
-
window.__wt_chat_queue.push(noteStr);
|
|
2477
|
-
} catch(e) {}
|
|
2478
|
-
|
|
2479
|
-
try {
|
|
2480
|
-
const existing = JSON.parse(localStorage.getItem('__wt_chat_queue_persist') || '[]');
|
|
2481
|
-
existing.push(msg.entry);
|
|
2482
|
-
localStorage.setItem('__wt_chat_queue_persist', JSON.stringify(existing));
|
|
2483
|
-
} catch(e) {}
|
|
2484
|
-
|
|
2485
|
-
const bridge = document.getElementById('__wt_ask_bridge');
|
|
2486
|
-
if (bridge) {
|
|
2487
|
-
bridge.dataset.status = 'submitted';
|
|
2488
|
-
bridge.dataset.result = msg.entry.text || 'Annotation submitted';
|
|
2489
|
-
}
|
|
2490
|
-
}
|
|
2491
|
-
});
|
|
2492
|
-
|
|
2493
|
-
})();
|
|
2494
|
-
|
|
2495
|
-
// ── ALERT / ACTION-REQUIRED TAB INDICATOR ─────────────────────
|
|
2496
|
-
(function initAlertDetector() {
|
|
2497
|
-
if (window.self !== window.top) return;
|
|
2498
|
-
// Expose on window so window.__Weavetab.getAlertState() can read it from CDP MAIN world
|
|
2499
|
-
window.__wt_alertPending = false;
|
|
2500
|
-
var _alert = window.alert;
|
|
2501
|
-
window.alert = function(msg) {
|
|
2502
|
-
window.__wt_alertPending = true;
|
|
2503
|
-
__wt_safeSendMessage({ type: 'TAB_ALERT_WAITING' });
|
|
2504
|
-
var r = _alert.apply(this, arguments);
|
|
2505
|
-
window.__wt_alertPending = false;
|
|
2506
|
-
__wt_safeSendMessage({ type: 'TAB_ALERT_CLEARED' });
|
|
2507
|
-
return r;
|
|
2508
|
-
};
|
|
2509
|
-
var _confirm = window.confirm;
|
|
2510
|
-
window.confirm = function(msg) {
|
|
2511
|
-
window.__wt_alertPending = true;
|
|
2512
|
-
__wt_safeSendMessage({ type: 'TAB_ALERT_WAITING' });
|
|
2513
|
-
var r = _confirm.apply(this, arguments);
|
|
2514
|
-
window.__wt_alertPending = false;
|
|
2515
|
-
__wt_safeSendMessage({ type: 'TAB_ALERT_CLEARED' });
|
|
2516
|
-
return r;
|
|
2517
|
-
};
|
|
2518
|
-
var _prompt = window.prompt;
|
|
2519
|
-
window.prompt = function(msg, def) {
|
|
2520
|
-
window.__wt_alertPending = true;
|
|
2521
|
-
__wt_safeSendMessage({ type: 'TAB_ALERT_WAITING' });
|
|
2522
|
-
var r = _prompt.apply(this, arguments);
|
|
2523
|
-
window.__wt_alertPending = false;
|
|
2524
|
-
__wt_safeSendMessage({ type: 'TAB_ALERT_CLEARED' });
|
|
2525
|
-
return r;
|
|
2526
|
-
};
|
|
2527
|
-
})();
|
|
1
|
+
const browserAPI = typeof browser !== 'undefined' ? browser : chrome;
|
|
2
|
+
// Register this tab with background (skip about:blank and chrome:// pages)
|
|
3
|
+
if (location.href !== 'about:blank' && !location.protocol.startsWith('chrome')) {
|
|
4
|
+
try { browserAPI.runtime.sendMessage({ type: 'REGISTER_TAB' }).catch(function(){}); } catch(e) {}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// ── STABLE EXTENSION MARKER ────────────────────────────────────────
|
|
8
|
+
// Unconditionally injected at document root so MCP server can detect the extension reliably.
|
|
9
|
+
if (!document.getElementById('wt-extension-marker')) {
|
|
10
|
+
const marker = document.createElement('meta');
|
|
11
|
+
marker.id = 'wt-extension-marker';
|
|
12
|
+
marker.setAttribute('name', 'weavetab-extension');
|
|
13
|
+
if (document.head) {
|
|
14
|
+
document.head.appendChild(marker);
|
|
15
|
+
} else if (document.documentElement) {
|
|
16
|
+
document.documentElement.appendChild(marker);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ── SAFE MESSAGE BRIDGE ────────────────────────────────────────
|
|
21
|
+
// Wraps browserAPI.runtime.sendMessage to survive "Extension context invalidated"
|
|
22
|
+
// errors that occur when the extension is reloaded/updated mid-session.
|
|
23
|
+
var __wt_extAlive = true;
|
|
24
|
+
function __wt_safeSendMessage(msg) {
|
|
25
|
+
try {
|
|
26
|
+
if (!browserAPI.runtime || !browserAPI.runtime.id) { __wt_extAlive = false; return; }
|
|
27
|
+
browserAPI.runtime.sendMessage(msg).catch(function(){ __wt_extAlive = false; });
|
|
28
|
+
} catch(e) {
|
|
29
|
+
__wt_extAlive = false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function __wt_safeAppendToBody(el) {
|
|
34
|
+
if (document.body) {
|
|
35
|
+
document.body.appendChild(el);
|
|
36
|
+
} else {
|
|
37
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
38
|
+
if (document.body) document.body.appendChild(el);
|
|
39
|
+
}, { once: true });
|
|
40
|
+
// Backup for SPAs that might fire DOMContentLoaded early or clear body
|
|
41
|
+
const obs = new MutationObserver((m, observer) => {
|
|
42
|
+
if (document.body) {
|
|
43
|
+
if (!el.parentNode || !document.body.contains(el)) document.body.appendChild(el);
|
|
44
|
+
observer.disconnect();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
obs.observe(document.documentElement, { childList: true });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── HUD MOUNT LAYER (Phase 1: Closed Shadow DOM + Iframe Architecture) ─────
|
|
52
|
+
// Reload-proof, agent-invisible, real-time visual system
|
|
53
|
+
(function mountHUD() {
|
|
54
|
+
// Guard: exit gracefully on chrome:// pages (content scripts blocked)
|
|
55
|
+
if (location.protocol.startsWith('chrome')) return;
|
|
56
|
+
|
|
57
|
+
let host = null;
|
|
58
|
+
let shadow = null;
|
|
59
|
+
let iframe = null;
|
|
60
|
+
let hudState = {};
|
|
61
|
+
|
|
62
|
+
// Create mount point with closed shadow DOM
|
|
63
|
+
function createMount() {
|
|
64
|
+
if (host && document.documentElement.contains(host)) return;
|
|
65
|
+
|
|
66
|
+
host = document.createElement('weavetab-host');
|
|
67
|
+
host.setAttribute('data-wt-internal', 'true');
|
|
68
|
+
host.setAttribute('aria-hidden', 'true');
|
|
69
|
+
host.style.cssText = 'all:initial !important;position:fixed !important;left:0 !important;top:0 !important;width:0 !important;height:0 !important;z-index:2147483647 !important;pointer-events:none !important;background:transparent !important;opacity:1 !important;transform:none !important;filter:none !important;';
|
|
70
|
+
__wt_safeAppendToBody(host);
|
|
71
|
+
|
|
72
|
+
// Closed shadow root — ref lives ONLY here, never on window
|
|
73
|
+
shadow = host.attachShadow({ mode: 'closed' });
|
|
74
|
+
|
|
75
|
+
iframe = document.createElement('iframe');
|
|
76
|
+
iframe.src = browserAPI.runtime.getURL('hud/index.html');
|
|
77
|
+
iframe.id = 'wt-hud-iframe';
|
|
78
|
+
iframe.setAttribute('allowtransparency', 'true');
|
|
79
|
+
// FIX: Start hidden. Expand only after receiving state to prevent white flash.
|
|
80
|
+
Object.assign(iframe.style, {
|
|
81
|
+
position: 'fixed',
|
|
82
|
+
top: '0',
|
|
83
|
+
left: '0',
|
|
84
|
+
width: '0',
|
|
85
|
+
height: '0',
|
|
86
|
+
border: 'none',
|
|
87
|
+
background: 'transparent',
|
|
88
|
+
pointerEvents: 'none',
|
|
89
|
+
zIndex: '2147483647',
|
|
90
|
+
visibility: 'hidden',
|
|
91
|
+
opacity: '0',
|
|
92
|
+
colorScheme: 'light dark'
|
|
93
|
+
});
|
|
94
|
+
shadow.appendChild(iframe);
|
|
95
|
+
|
|
96
|
+
// REHYDRATE: send state to iframe, but DO NOT make visible yet to avoid white flash.
|
|
97
|
+
iframe.onload = function() {
|
|
98
|
+
browserAPI.storage.local.get('wt-hud-state', function(result) {
|
|
99
|
+
hudState = result['wt-hud-state'] || {};
|
|
100
|
+
if (iframe.contentWindow) {
|
|
101
|
+
iframe.contentWindow.postMessage({ type: 'REHYDRATE', state: hudState }, browserAPI.runtime.getURL(''));
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Listen for HUD state changes from the iframe to toggle visibility
|
|
107
|
+
window.addEventListener('message', function(e) {
|
|
108
|
+
if (e.data && e.data.type === 'HUD_STATE_CHANGED' && e.source === iframe.contentWindow) {
|
|
109
|
+
const visible = e.data.state && e.data.state.hudVisible !== false;
|
|
110
|
+
if (visible) {
|
|
111
|
+
iframe.style.width = '100vw';
|
|
112
|
+
iframe.style.height = '100vh';
|
|
113
|
+
iframe.style.visibility = 'visible';
|
|
114
|
+
iframe.style.opacity = '1';
|
|
115
|
+
} else {
|
|
116
|
+
iframe.style.width = '0';
|
|
117
|
+
iframe.style.height = '0';
|
|
118
|
+
iframe.style.visibility = 'hidden';
|
|
119
|
+
iframe.style.opacity = '0';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Navigation guard: re-mount if host is removed (SPA/bfcache safe)
|
|
126
|
+
const observer = new MutationObserver(function() {
|
|
127
|
+
if (!document.documentElement.contains(host)) {
|
|
128
|
+
createMount();
|
|
129
|
+
}
|
|
130
|
+
if (window.__wt_visualComponents && window.__wt_visualComponents.cursor && !document.getElementById('__Weavetab_cursor_host')) {
|
|
131
|
+
if (typeof __wt_recoverVisuals === 'function') __wt_recoverVisuals();
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
observer.observe(document.documentElement, { childList: true });
|
|
135
|
+
|
|
136
|
+
// Real-time event relays (Agent only)
|
|
137
|
+
// Removed physical mousemove, click, and keydown listeners to prevent lag and interference with agent visuals.
|
|
138
|
+
|
|
139
|
+
let scrollTicking = false;
|
|
140
|
+
document.addEventListener('scroll', function() {
|
|
141
|
+
if (!scrollTicking && iframe && iframe.contentWindow) {
|
|
142
|
+
window.requestAnimationFrame(function() {
|
|
143
|
+
iframe.contentWindow.postMessage({
|
|
144
|
+
type: 'RT_SCROLL',
|
|
145
|
+
y: window.scrollY,
|
|
146
|
+
ratio: window.scrollY / document.body.scrollHeight
|
|
147
|
+
}, '*');
|
|
148
|
+
scrollTicking = false;
|
|
149
|
+
});
|
|
150
|
+
scrollTicking = true;
|
|
151
|
+
}
|
|
152
|
+
}, { passive: true });
|
|
153
|
+
|
|
154
|
+
// ── MASTER VISUALS TOGGLE SYSTEM ───────────────────────────
|
|
155
|
+
let __wt_masterVisualsEnabled = true;
|
|
156
|
+
|
|
157
|
+
function __wt_applyMasterVisuals(enabled) {
|
|
158
|
+
__wt_masterVisualsEnabled = !!enabled;
|
|
159
|
+
window.__wt_masterVisualsEnabled = __wt_masterVisualsEnabled;
|
|
160
|
+
|
|
161
|
+
const cursorHost = document.getElementById('__Weavetab_cursor_host');
|
|
162
|
+
if (cursorHost) {
|
|
163
|
+
cursorHost.style.display = __wt_masterVisualsEnabled ? 'block' : 'none';
|
|
164
|
+
}
|
|
165
|
+
if (typeof __weaveCursor !== 'undefined' && __weaveCursor) {
|
|
166
|
+
__weaveCursor.setVisible(__wt_masterVisualsEnabled);
|
|
167
|
+
if (__weaveCursor._cursor) {
|
|
168
|
+
__weaveCursor._cursor.style.display = __wt_masterVisualsEnabled ? 'block' : 'none';
|
|
169
|
+
} else if (__weaveCursor._canvas) {
|
|
170
|
+
__weaveCursor._canvas.style.display = __wt_masterVisualsEnabled ? 'block' : 'none';
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const hudEl = document.getElementById('Weavetab-hud');
|
|
175
|
+
if (hudEl) {
|
|
176
|
+
hudEl.style.display = 'none';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const hudHost = document.querySelector('weavetab-host');
|
|
180
|
+
if (hudHost) {
|
|
181
|
+
hudHost.style.display = __wt_masterVisualsEnabled ? 'block' : 'none';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const indicatorEl = document.getElementById('wt-controlled-indicator') || document.querySelector('.wt-controlled-indicator') || document.getElementById('__Weavetab_controlled_indicator');
|
|
185
|
+
if (indicatorEl) {
|
|
186
|
+
indicatorEl.style.display = __wt_masterVisualsEnabled ? 'block' : 'none';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!__wt_masterVisualsEnabled) {
|
|
190
|
+
if (typeof clearXRay === 'function') clearXRay();
|
|
191
|
+
document.querySelectorAll('.Weavetab-ripple, .wt-xray-spotlight, .wt-bubble, .__Weavetab_xray').forEach(function(el){
|
|
192
|
+
el.remove();
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
browserAPI.storage.local.get(['wt_master_visuals'], function(res) {
|
|
199
|
+
if (res && res.wt_master_visuals === false) {
|
|
200
|
+
__wt_applyMasterVisuals(false);
|
|
201
|
+
} else {
|
|
202
|
+
__wt_applyMasterVisuals(true);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
browserAPI.storage.onChanged.addListener(function(changes, area) {
|
|
207
|
+
if (area === 'local' && changes.wt_master_visuals !== undefined) {
|
|
208
|
+
__wt_applyMasterVisuals(changes.wt_master_visuals.newValue !== false);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
} catch(e) {}
|
|
212
|
+
|
|
213
|
+
// Ghost CDP cursor relay from background
|
|
214
|
+
browserAPI.runtime.onMessage.addListener(function(msg) {
|
|
215
|
+
if (msg.type === 'WT_TOGGLE_MASTER_VISUALS') {
|
|
216
|
+
__wt_applyMasterVisuals(msg.enabled);
|
|
217
|
+
}
|
|
218
|
+
if (msg.type === 'GHOST_CURSOR_MOVE') {
|
|
219
|
+
if (typeof msg.x === 'number' && typeof msg.y === 'number') {
|
|
220
|
+
moveCursor(msg.x, msg.y, msg.phase);
|
|
221
|
+
}
|
|
222
|
+
if (iframe && iframe.contentWindow) {
|
|
223
|
+
iframe.contentWindow.postMessage({ type: 'RT_CURSOR_MOVE', x: msg.x, y: msg.y, phase: msg.phase }, '*');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (msg.type === 'TOOL_CALL_EVENT' && iframe && iframe.contentWindow) {
|
|
227
|
+
iframe.contentWindow.postMessage({ type: 'TOOL_CALL_EVENT', payload: msg.payload }, '*');
|
|
228
|
+
}
|
|
229
|
+
// Scripted pill toggle — relay to HUD iframe
|
|
230
|
+
if (msg.type === 'WT_SCRIPTED_PILL_TOGGLE' && iframe && iframe.contentWindow) {
|
|
231
|
+
iframe.contentWindow.postMessage({ type: 'WT_SCRIPTED_PILL_TOGGLE', enabled: msg.enabled }, '*');
|
|
232
|
+
}
|
|
233
|
+
// Handle agent thoughts in middle-down HUD
|
|
234
|
+
if (msg.type === 'WT_THOUGHT' || msg.type === 'Weavetab_THOUGHT') {
|
|
235
|
+
__wt_showThought(msg.text, msg);
|
|
236
|
+
}
|
|
237
|
+
if (msg.type === 'WT_CLEAR_THOUGHT' || msg.type === 'Weavetab_CLEAR_THOUGHT') {
|
|
238
|
+
__wt_removeThought();
|
|
239
|
+
}
|
|
240
|
+
// Handle incoming user notes from the extension
|
|
241
|
+
if (msg.type === 'WT_USER_NOTE_INJECT' || msg.type === 'WT_USER_NOTE') {
|
|
242
|
+
window.__wt_chat_queue = window.__wt_chat_queue || [];
|
|
243
|
+
let formattedMsg = msg.text || '';
|
|
244
|
+
if (msg.snipe) {
|
|
245
|
+
formattedMsg = `[USER AREA ANNOTATION ATTACHED (${msg.snipe.mode})]: ` + formattedMsg;
|
|
246
|
+
}
|
|
247
|
+
window.__wt_chat_queue.push(formattedMsg);
|
|
248
|
+
|
|
249
|
+
// Unblock pending ask_bridge polling loops immediately
|
|
250
|
+
window.__wt_ask_bridge = window.__wt_ask_bridge || {};
|
|
251
|
+
if (window.__wt_ask_bridge.resolve) {
|
|
252
|
+
window.__wt_ask_bridge.resolve({
|
|
253
|
+
success: true,
|
|
254
|
+
response: formattedMsg,
|
|
255
|
+
status: 'answered'
|
|
256
|
+
});
|
|
257
|
+
window.__wt_ask_bridge = {};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Display on-page glassmorphism notification banner if master visuals are enabled
|
|
261
|
+
if (window.__wt_masterVisualsEnabled !== false) {
|
|
262
|
+
__wt_showUserNoteVisualBanner(formattedMsg);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
function __wt_showUserNoteVisualBanner(text) {
|
|
268
|
+
let banner = document.getElementById('wt-user-note-banner');
|
|
269
|
+
if (!banner) {
|
|
270
|
+
banner = document.createElement('div');
|
|
271
|
+
banner.id = 'wt-user-note-banner';
|
|
272
|
+
banner.setAttribute('data-wt-ignore', 'true');
|
|
273
|
+
banner.style.cssText = `
|
|
274
|
+
position: fixed; top: 16px; right: 20px; z-index: 2147483647;
|
|
275
|
+
background: rgba(24, 24, 27, 0.94); backdrop-filter: blur(12px);
|
|
276
|
+
border: 1px solid #38bdf8; color: #f4f4f5; font-weight: 500;
|
|
277
|
+
font-size: 11px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
278
|
+
padding: 8px 16px; border-radius: 20px;
|
|
279
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6), 0 0 14px rgba(56, 189, 248, 0.4);
|
|
280
|
+
display: flex; align-items: center; gap: 8px;
|
|
281
|
+
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
282
|
+
opacity: 0; transform: translateY(-10px); pointer-events: none;
|
|
283
|
+
`;
|
|
284
|
+
document.documentElement.appendChild(banner);
|
|
285
|
+
}
|
|
286
|
+
const cleanText = text.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').slice(0, 90);
|
|
287
|
+
banner.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#38bdf8" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg><span style="color:#38bdf8;font-weight:600;">Message Received:</span> <span>${cleanText}</span>`;
|
|
288
|
+
requestAnimationFrame(() => {
|
|
289
|
+
banner.style.opacity = '1';
|
|
290
|
+
banner.style.transform = 'translateY(0)';
|
|
291
|
+
});
|
|
292
|
+
setTimeout(() => {
|
|
293
|
+
banner.style.opacity = '0';
|
|
294
|
+
banner.style.transform = 'translateY(-10px)';
|
|
295
|
+
setTimeout(() => banner.remove(), 300);
|
|
296
|
+
}, 4000);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// State persistence bridge: listen for state changes from iframe
|
|
300
|
+
window.addEventListener('message', function(e) {
|
|
301
|
+
// Forward relevant events to the extension background
|
|
302
|
+
if (e.data && (
|
|
303
|
+
e.data.type === 'WT_TOOL_CALL' ||
|
|
304
|
+
e.data.type === 'WT_PLAN_UPDATE' ||
|
|
305
|
+
e.data.type === 'WT_THOUGHT' ||
|
|
306
|
+
e.data.type === 'TOOL_CALL_EVENT' ||
|
|
307
|
+
e.data.type === 'Weavetab_CONFIG' ||
|
|
308
|
+
e.data.type === 'WT_CONFIG_UPDATE'
|
|
309
|
+
)) {
|
|
310
|
+
__wt_safeSendMessage(e.data);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
if (e.origin !== browserAPI.runtime.getURL('').slice(0, -1)) return;
|
|
315
|
+
if (e.data.type === 'HUD_STATE_CHANGED') {
|
|
316
|
+
hudState = { ...hudState, ...e.data.state };
|
|
317
|
+
browserAPI.storage.local.set({ 'wt-hud-state': hudState });
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Listen for toggle command from background
|
|
322
|
+
browserAPI.runtime.onMessage.addListener(function(msg) {
|
|
323
|
+
console.log('[Weavetab Content] Received message:', msg.type);
|
|
324
|
+
if (msg.type === 'TOGGLE_HUD' && iframe && iframe.contentWindow) {
|
|
325
|
+
iframe.contentWindow.postMessage({ type: 'TOGGLE_HUD' }, '*');
|
|
326
|
+
}
|
|
327
|
+
if (msg.type === 'TOGGLE_DEBUG' && iframe && iframe.contentWindow) {
|
|
328
|
+
console.log('[Weavetab Content] Forwarding TOGGLE_DEBUG to iframe');
|
|
329
|
+
iframe.contentWindow.postMessage({ type: 'TOGGLE_DEBUG' }, '*');
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Initial mount
|
|
334
|
+
createMount();
|
|
335
|
+
})();
|
|
336
|
+
|
|
337
|
+
// ── UNIFIED VISUAL SYSTEM INITIALIZATION ─────────────────────────
|
|
338
|
+
// Ensures all visual components initialize in correct order and persist across reloads
|
|
339
|
+
window.__wt_visualsReady = false;
|
|
340
|
+
window.__wt_visualComponents = {
|
|
341
|
+
cursor: false,
|
|
342
|
+
feedback: false,
|
|
343
|
+
debug: false
|
|
344
|
+
};
|
|
345
|
+
window.__wt_zIndexCounter = 2147483640; // Start near max z-index
|
|
346
|
+
|
|
347
|
+
function __wt_initVisuals() {
|
|
348
|
+
if (window.__wt_visualsReady) return;
|
|
349
|
+
|
|
350
|
+
// Initialize cursor first
|
|
351
|
+
if (!window.__wt_visualComponents.cursor && window.__weaveCursor_instance) {
|
|
352
|
+
window.__wt_visualComponents.cursor = true;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Check for WeaveTab OS panel (todo list + chat)
|
|
356
|
+
if (!window.__wt_visualComponents.feedback && document.getElementById('wt-main-container')) {
|
|
357
|
+
window.__wt_visualComponents.feedback = true;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Check for debug overlay
|
|
361
|
+
if (!window.__wt_visualComponents.debug && document.getElementById('wt-debug-overlay')) {
|
|
362
|
+
window.__wt_visualComponents.debug = true;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Mark as ready when cursor and panel are initialized
|
|
366
|
+
if (window.__wt_visualComponents.cursor && window.__wt_visualComponents.feedback) {
|
|
367
|
+
window.__wt_visualsReady = true;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Health check every 5 seconds to recover missing components
|
|
372
|
+
setInterval(function() {
|
|
373
|
+
__wt_initVisuals();
|
|
374
|
+
__wt_recoverVisuals();
|
|
375
|
+
}, 5000);
|
|
376
|
+
|
|
377
|
+
// Visual recovery - detect and fix missing/broken components
|
|
378
|
+
function __wt_recoverVisuals() {
|
|
379
|
+
// Recover cursor if missing
|
|
380
|
+
if (window.__wt_visualComponents && window.__wt_visualComponents.cursor && !document.getElementById('__Weavetab_cursor_host')) {
|
|
381
|
+
console.warn('[Weavetab] Cursor host missing, reinitializing...');
|
|
382
|
+
if (window.__weaveCursor_instance && window.__weaveCursor_instance.destroy) {
|
|
383
|
+
window.__weaveCursor_instance.destroy();
|
|
384
|
+
}
|
|
385
|
+
__weaveCursor = new WeaveCursor();
|
|
386
|
+
window.__weaveCursor_instance = __weaveCursor;
|
|
387
|
+
window.__wt_visualComponents.cursor = true;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Recover WeaveTab OS panel if missing
|
|
391
|
+
if (window.__wt_visualComponents.feedback && !document.getElementById('wt-main-container')) {
|
|
392
|
+
console.warn('[Weavetab] WeaveTab OS panel missing, this is handled by page reload');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Fix z-index conflicts
|
|
396
|
+
var wtElements = document.querySelectorAll('[data-wt-ignore]');
|
|
397
|
+
wtElements.forEach(function(el) {
|
|
398
|
+
if (el.style.zIndex && parseInt(el.style.zIndex) < 2147483640) {
|
|
399
|
+
el.style.zIndex = ++window.__wt_zIndexCounter;
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ── STALE ELEMENT CLEANUP ─────────────────────────────────────
|
|
405
|
+
// Removes old UI elements from previous server/extension sessions.
|
|
406
|
+
// Observer stays active for the full page lifetime (SPA-safe).
|
|
407
|
+
(function cleanupOldUI() {
|
|
408
|
+
var OLD_IDS = [
|
|
409
|
+
'wt-ghost-cursor','wt-ghost-bubble','wt-ask-panel','wt-cur-canvas',
|
|
410
|
+
'wt-ask-container','wt-msg-panel','wt-ask-title','wt-msg-bar','wt-msg-pill'
|
|
411
|
+
];
|
|
412
|
+
function removeOld() {
|
|
413
|
+
for (var i = 0; i < OLD_IDS.length; i++) {
|
|
414
|
+
var el = document.getElementById(OLD_IDS[i]);
|
|
415
|
+
if (el) el.remove();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
removeOld();
|
|
419
|
+
// Persistent observer — never disconnects so SPA re-renders stay clean
|
|
420
|
+
var obs = new MutationObserver(function() { removeOld(); });
|
|
421
|
+
if (document.documentElement) {
|
|
422
|
+
obs.observe(document.documentElement, { childList: true, subtree: true });
|
|
423
|
+
} else {
|
|
424
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
425
|
+
if (document.documentElement) obs.observe(document.documentElement, { childList: true, subtree: true });
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
})();
|
|
429
|
+
|
|
430
|
+
// ── UI DESIGN SYSTEM (inject once, styles.css handles visuals) ─
|
|
431
|
+
(function injectWeavetabUI() {
|
|
432
|
+
if (document.getElementById('wt-ui-styles')) return;
|
|
433
|
+
// Guard marker so we only run once
|
|
434
|
+
const m = document.createElement('meta');
|
|
435
|
+
m.id = 'wt-ui-styles';
|
|
436
|
+
(document.head || document.documentElement || document.body).appendChild(m);
|
|
437
|
+
})();
|
|
438
|
+
|
|
439
|
+
// ── THOUGHT BUBBLE ─────────────────────────────────────────────
|
|
440
|
+
let __wt_activeBubble = null;
|
|
441
|
+
let __wt_bubbleTimer = null;
|
|
442
|
+
let __wt_typewriterTimer = null;
|
|
443
|
+
let __wt_feedbackSticky = false;
|
|
444
|
+
let __wt_thoughtBirth = 0;
|
|
445
|
+
let __wt_thoughtQueue = [];
|
|
446
|
+
let __wt_lastRemoveTime = 0;
|
|
447
|
+
let __wt_toolActive = false;
|
|
448
|
+
|
|
449
|
+
function __wt_fadeOutBubble(callback) {
|
|
450
|
+
if (!__wt_activeBubble) { if (callback) callback(); return; }
|
|
451
|
+
var bub = __wt_activeBubble;
|
|
452
|
+
bub.style.transition = 'opacity 0.15s ease-out, transform 0.15s ease-out';
|
|
453
|
+
bub.style.opacity = '0';
|
|
454
|
+
bub.style.transform = 'translateX(-50%) translateY(6px)';
|
|
455
|
+
setTimeout(function() {
|
|
456
|
+
bub.remove();
|
|
457
|
+
if (callback) callback();
|
|
458
|
+
}, 150);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function __wt_destroyBubble() {
|
|
462
|
+
if (__wt_bubbleTimer) { clearTimeout(__wt_bubbleTimer); __wt_bubbleTimer = null; }
|
|
463
|
+
if (__wt_typewriterTimer) { clearInterval(__wt_typewriterTimer); __wt_typewriterTimer = null; }
|
|
464
|
+
var current = document.querySelector('.wt-bubble[data-wt-thought]');
|
|
465
|
+
if (current) {
|
|
466
|
+
current.style.transition = 'opacity 0.15s ease-out, transform 0.15s ease-out';
|
|
467
|
+
current.style.opacity = '0';
|
|
468
|
+
current.style.transform = 'translateX(-50%) translateY(6px)';
|
|
469
|
+
setTimeout(function() {
|
|
470
|
+
if (current && current.parentNode) current.remove();
|
|
471
|
+
__wt_activeBubble = null;
|
|
472
|
+
}, 150);
|
|
473
|
+
} else {
|
|
474
|
+
__wt_activeBubble = null;
|
|
475
|
+
}
|
|
476
|
+
__wt_lastRemoveTime = Date.now();
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ── MIDDLE-DOWN THOUGHT & PRESENCE HUD ─────────────────────────────
|
|
480
|
+
let __wt_thoughtHudEl = null;
|
|
481
|
+
let __wt_thoughtHudTimer = null;
|
|
482
|
+
let __wt_thoughtHudCountdown = null;
|
|
483
|
+
|
|
484
|
+
function __wt_highlightAnchor(anchor) {
|
|
485
|
+
if (!anchor) return;
|
|
486
|
+
try {
|
|
487
|
+
let sel = anchor;
|
|
488
|
+
if (anchor.startsWith('w:')) {
|
|
489
|
+
const stripped = anchor.slice(2);
|
|
490
|
+
sel = `[data-wt-id="${stripped}"]`;
|
|
491
|
+
}
|
|
492
|
+
const el = document.querySelector(sel) || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(sel) : null);
|
|
493
|
+
if (!el) return;
|
|
494
|
+
const r = el.getBoundingClientRect();
|
|
495
|
+
if (r.width === 0 && r.height === 0) return;
|
|
496
|
+
|
|
497
|
+
const ring = document.createElement('div');
|
|
498
|
+
ring.setAttribute('data-wt-ignore', 'true');
|
|
499
|
+
ring.style.cssText = `position:fixed;left:${r.left}px;top:${r.top}px;width:${r.width}px;height:${r.height}px;border:1.5px solid #38bdf8;box-shadow:0 0 16px rgba(56,189,248,0.5);border-radius:4px;z-index:2147483646;pointer-events:none;animation:wt-consider-pulse 1.2s ease-in-out infinite;`;
|
|
500
|
+
(document.getElementById('Weavetab-root')?.shadowRoot || document.body || document.documentElement).appendChild(ring);
|
|
501
|
+
setTimeout(() => {
|
|
502
|
+
ring.style.transition = 'opacity 0.25s ease-out';
|
|
503
|
+
ring.style.opacity = '0';
|
|
504
|
+
setTimeout(() => ring.remove(), 260);
|
|
505
|
+
}, 2400);
|
|
506
|
+
} catch(e) {}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function __wt_removeThought() {
|
|
510
|
+
if (__wt_thoughtHudTimer) {
|
|
511
|
+
clearTimeout(__wt_thoughtHudTimer);
|
|
512
|
+
__wt_thoughtHudTimer = null;
|
|
513
|
+
}
|
|
514
|
+
if (__wt_thoughtHudCountdown) {
|
|
515
|
+
clearInterval(__wt_thoughtHudCountdown);
|
|
516
|
+
__wt_thoughtHudCountdown = null;
|
|
517
|
+
}
|
|
518
|
+
if (__wt_thoughtHudEl) {
|
|
519
|
+
const el = __wt_thoughtHudEl;
|
|
520
|
+
__wt_thoughtHudEl = null;
|
|
521
|
+
el.style.opacity = '0';
|
|
522
|
+
el.style.transform = 'translateX(-50%) translateY(10px)';
|
|
523
|
+
setTimeout(() => {
|
|
524
|
+
if (el.parentNode) el.remove();
|
|
525
|
+
}, 220);
|
|
526
|
+
}
|
|
527
|
+
__wt_feedbackSticky = false;
|
|
528
|
+
clearTimeout(window.__wt_feedbackStickyTimer);
|
|
529
|
+
__wt_destroyBubble();
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function __wt_showThought(text, options) {
|
|
533
|
+
if (typeof __wt_masterVisualsEnabled !== 'undefined' && !__wt_masterVisualsEnabled) return;
|
|
534
|
+
if (!text || typeof text !== 'string') return;
|
|
535
|
+
|
|
536
|
+
const opts = options || {};
|
|
537
|
+
const isQuestion = opts.action === 'question' || opts.kind === 'question' || !!opts.ask;
|
|
538
|
+
const anchor = opts.anchor || '';
|
|
539
|
+
|
|
540
|
+
// Contextual visual anchor highlight
|
|
541
|
+
if (anchor) {
|
|
542
|
+
__wt_highlightAnchor(anchor);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Clear pending timers
|
|
546
|
+
if (__wt_thoughtHudTimer) {
|
|
547
|
+
clearTimeout(__wt_thoughtHudTimer);
|
|
548
|
+
__wt_thoughtHudTimer = null;
|
|
549
|
+
}
|
|
550
|
+
if (__wt_thoughtHudCountdown) {
|
|
551
|
+
clearInterval(__wt_thoughtHudCountdown);
|
|
552
|
+
__wt_thoughtHudCountdown = null;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
let hud = __wt_thoughtHudEl;
|
|
556
|
+
if (!hud || !hud.parentNode) {
|
|
557
|
+
hud = document.createElement('div');
|
|
558
|
+
hud.id = '__wt_thought_hud';
|
|
559
|
+
hud.setAttribute('data-wt-ignore', 'true');
|
|
560
|
+
hud.style.cssText = `
|
|
561
|
+
position: fixed;
|
|
562
|
+
bottom: 24px;
|
|
563
|
+
left: 50%;
|
|
564
|
+
transform: translateX(-50%) translateY(8px);
|
|
565
|
+
z-index: 2147483647;
|
|
566
|
+
width: 320px;
|
|
567
|
+
max-width: calc(100vw - 32px);
|
|
568
|
+
background: #0b0d12;
|
|
569
|
+
border: 1px solid #1e222b;
|
|
570
|
+
border-radius: 4px;
|
|
571
|
+
padding: 10px 12px;
|
|
572
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.8), 0 0 1px rgba(255,255,255,0.08);
|
|
573
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Inter", sans-serif;
|
|
574
|
+
color: #f3f4f6;
|
|
575
|
+
pointer-events: auto;
|
|
576
|
+
opacity: 0;
|
|
577
|
+
transition: opacity 0.18s cubic-bezier(0.16, 1, 0.3, 1), transform 0.18s cubic-bezier(0.16, 1, 0.3, 1);
|
|
578
|
+
box-sizing: border-box;
|
|
579
|
+
`;
|
|
580
|
+
(document.getElementById('Weavetab-root')?.shadowRoot || document.body || document.documentElement).appendChild(hud);
|
|
581
|
+
__wt_thoughtHudEl = hud;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const cleanText = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
585
|
+
const anchorBadge = anchor ? `<span style="color:#2b70ff;font-size:10px;font-family:monospace;font-weight:600;margin-left:4px;">@${anchor.replace(/&/g,'&')}</span>` : '';
|
|
586
|
+
|
|
587
|
+
if (isQuestion) {
|
|
588
|
+
const timeoutSec = (opts.timeout || (opts.ask && opts.ask.timeout) || 20);
|
|
589
|
+
hud.innerHTML = `
|
|
590
|
+
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:8px;">
|
|
591
|
+
<div style="font-size:12px;line-height:1.4;color:#ffffff;font-weight:500;word-break:break-word;">
|
|
592
|
+
${cleanText}${anchorBadge}
|
|
593
|
+
</div>
|
|
594
|
+
<button id="wt-hud-close" title="Dismiss" style="background:transparent;border:none;color:#555d6d;font-size:12px;cursor:pointer;padding:0 2px;line-height:1;margin-top:1px;">✕</button>
|
|
595
|
+
</div>
|
|
596
|
+
<div style="display:flex;align-items:center;gap:6px;">
|
|
597
|
+
<input id="wt-hud-input" type="text" placeholder="Type answer..." style="height:28px;background:#14161f;border:1px solid #252a36;border-radius:3px;padding:0 8px;font-size:12px;color:#fff;outline:none;flex:1;min-width:0;caret-color:#2b70ff;font-family:inherit;box-sizing:border-box;" />
|
|
598
|
+
<button id="wt-hud-submit-btn" title="Send" style="width:28px;height:28px;background:#2b70ff;border:none;border-radius:3px;display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0;padding:0;box-sizing:border-box;">
|
|
599
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
|
600
|
+
</button>
|
|
601
|
+
<span id="wt-hud-countdown" style="font-size:10px;color:#64748b;font-family:monospace;margin-left:2px;min-width:20px;text-align:right;">${timeoutSec}s</span>
|
|
602
|
+
</div>
|
|
603
|
+
`;
|
|
604
|
+
} else {
|
|
605
|
+
// Feedback / observation mode — ultra compact single row
|
|
606
|
+
hud.innerHTML = `
|
|
607
|
+
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;">
|
|
608
|
+
<div style="display:flex;align-items:center;gap:7px;min-width:0;">
|
|
609
|
+
<span style="display:inline-block;width:5px;height:5px;border-radius:50%;background:#2b70ff;box-shadow:0 0 6px #2b70ff;flex-shrink:0;"></span>
|
|
610
|
+
<span style="font-size:12px;line-height:1.4;color:#f3f4f6;word-break:break-word;font-weight:450;">${cleanText}${anchorBadge}</span>
|
|
611
|
+
</div>
|
|
612
|
+
<button id="wt-hud-close" title="Dismiss" style="background:transparent;border:none;color:#555d6d;font-size:12px;cursor:pointer;padding:0 2px;line-height:1;flex-shrink:0;">✕</button>
|
|
613
|
+
</div>
|
|
614
|
+
`;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Animate in
|
|
618
|
+
requestAnimationFrame(() => {
|
|
619
|
+
if (hud) {
|
|
620
|
+
hud.style.opacity = '1';
|
|
621
|
+
hud.style.transform = 'translateX(-50%) translateY(0)';
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
// Wire close button
|
|
626
|
+
const closeBtn = hud.querySelector('#wt-hud-close');
|
|
627
|
+
if (closeBtn) {
|
|
628
|
+
closeBtn.addEventListener('click', () => {
|
|
629
|
+
__wt_removeThought();
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (isQuestion) {
|
|
634
|
+
let askBridge = document.getElementById('__wt_ask_bridge');
|
|
635
|
+
if (!askBridge) {
|
|
636
|
+
askBridge = document.createElement('div');
|
|
637
|
+
askBridge.id = '__wt_ask_bridge';
|
|
638
|
+
askBridge.style.display = 'none';
|
|
639
|
+
document.body.appendChild(askBridge);
|
|
640
|
+
}
|
|
641
|
+
askBridge.dataset.status = 'pending';
|
|
642
|
+
askBridge.dataset.result = '';
|
|
643
|
+
|
|
644
|
+
const input = hud.querySelector('#wt-hud-input');
|
|
645
|
+
const submitBtn = hud.querySelector('#wt-hud-submit-btn');
|
|
646
|
+
const countdownSpan = hud.querySelector('#wt-hud-countdown');
|
|
647
|
+
|
|
648
|
+
let remaining = opts.timeout || (opts.ask && opts.ask.timeout) || 20;
|
|
649
|
+
|
|
650
|
+
__wt_thoughtHudCountdown = setInterval(() => {
|
|
651
|
+
remaining--;
|
|
652
|
+
if (countdownSpan) countdownSpan.textContent = `${remaining}s`;
|
|
653
|
+
if (remaining <= 0) {
|
|
654
|
+
clearInterval(__wt_thoughtHudCountdown);
|
|
655
|
+
__wt_thoughtHudCountdown = null;
|
|
656
|
+
if (askBridge) {
|
|
657
|
+
askBridge.dataset.status = 'timeout';
|
|
658
|
+
askBridge.dataset.result = '';
|
|
659
|
+
}
|
|
660
|
+
__wt_removeThought();
|
|
661
|
+
}
|
|
662
|
+
}, 1000);
|
|
663
|
+
|
|
664
|
+
const submit = () => {
|
|
665
|
+
const val = input ? input.value.trim() : '';
|
|
666
|
+
if (askBridge) {
|
|
667
|
+
askBridge.dataset.status = 'submitted';
|
|
668
|
+
askBridge.dataset.result = val;
|
|
669
|
+
}
|
|
670
|
+
if (window.__wt_ask_bridge && window.__wt_ask_bridge.resolve) {
|
|
671
|
+
window.__wt_ask_bridge.resolve({ success: true, response: val, status: 'answered' });
|
|
672
|
+
window.__wt_ask_bridge = {};
|
|
673
|
+
}
|
|
674
|
+
__wt_removeThought();
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
if (submitBtn) submitBtn.addEventListener('click', submit);
|
|
678
|
+
if (input) {
|
|
679
|
+
input.addEventListener('focus', () => { input.style.borderColor = '#2b70ff'; });
|
|
680
|
+
input.addEventListener('blur', () => { input.style.borderColor = '#252a36'; });
|
|
681
|
+
input.addEventListener('keydown', (e) => {
|
|
682
|
+
if (e.key === 'Enter') submit();
|
|
683
|
+
if (e.key === 'Escape') __wt_removeThought();
|
|
684
|
+
});
|
|
685
|
+
setTimeout(() => { if (input) input.focus(); }, 80);
|
|
686
|
+
}
|
|
687
|
+
} else {
|
|
688
|
+
// Auto-fade timer for feedback
|
|
689
|
+
const autoDuration = Math.max(3800, Math.min(8000, text.length * 50));
|
|
690
|
+
__wt_thoughtHudTimer = setTimeout(() => {
|
|
691
|
+
__wt_removeThought();
|
|
692
|
+
}, autoDuration);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function __wt_renderThought(text, options) {
|
|
697
|
+
__wt_showThought(text, options);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function __wt_processThoughtQueue() {
|
|
701
|
+
if (__wt_thoughtQueue.length > 0) {
|
|
702
|
+
var next = __wt_thoughtQueue.shift();
|
|
703
|
+
setTimeout(function() { __wt_showThought(next.text, next.options); }, 200);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function __wt_showQuestion(text, inputType, timeoutSeconds) {
|
|
708
|
+
__wt_showThought(text, { action: 'question', timeout: timeoutSeconds || 20 });
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function __wt_removeAsk() {
|
|
712
|
+
__wt_removeThought();
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// ── SHARED OVERLAY CONTAINER ────────────────────────────────────
|
|
716
|
+
const __wt_shadowStyles = `
|
|
717
|
+
.Weavetab-ripple {
|
|
718
|
+
width: 40px; height: 40px; margin: -20px 0 0 -20px; border-radius: 50%;
|
|
719
|
+
background: rgba(79,142,247,0.35); border: 2px solid #4F8EF7;
|
|
720
|
+
animation: __wt_ripple 0.55s ease-out forwards;
|
|
721
|
+
}
|
|
722
|
+
@keyframes __wt_ripple { from { transform: scale(0.3); opacity: 1; } to { transform: scale(2.2); opacity: 0; } }
|
|
723
|
+
@keyframes __wt_xray_pulse {
|
|
724
|
+
0% { opacity: 0; transform: scale(0.94); }
|
|
725
|
+
18% { opacity: 1; transform: scale(1.02); }
|
|
726
|
+
70% { opacity: 0.85; transform: scale(1); }
|
|
727
|
+
100% { opacity: 0; transform: scale(0.97); }
|
|
728
|
+
}
|
|
729
|
+
`;
|
|
730
|
+
|
|
731
|
+
function getWeavetabRoot() {
|
|
732
|
+
let host = document.getElementById('Weavetab-root');
|
|
733
|
+
if (!host) {
|
|
734
|
+
host = document.createElement('div');
|
|
735
|
+
host.id = 'Weavetab-root';
|
|
736
|
+
host.setAttribute('data-wt-ignore', '');
|
|
737
|
+
Object.assign(host.style, {
|
|
738
|
+
position: 'fixed', left: '0', top: '0',
|
|
739
|
+
width: '0', height: '0',
|
|
740
|
+
zIndex: '2147483640', pointerEvents: 'none',
|
|
741
|
+
});
|
|
742
|
+
var root = document.documentElement || document.body;
|
|
743
|
+
if (root) root.appendChild(host);
|
|
744
|
+
}
|
|
745
|
+
// Reuse existing shadow root (open mode, set by CDP injector or ourselves)
|
|
746
|
+
if (host.shadowRoot) {
|
|
747
|
+
return host.shadowRoot;
|
|
748
|
+
}
|
|
749
|
+
// Guard against NotSupportedError when another context already attached a closed shadow root
|
|
750
|
+
try {
|
|
751
|
+
const sr = host.attachShadow({ mode: 'open' });
|
|
752
|
+
const style = document.createElement('style');
|
|
753
|
+
style.textContent = __wt_shadowStyles;
|
|
754
|
+
sr.appendChild(style);
|
|
755
|
+
return sr;
|
|
756
|
+
} catch (e) {
|
|
757
|
+
// FIX: fallback must be invisible and non-blocking so it can't
|
|
758
|
+
// cover page content or cause opacity bleeding on sites like example.com.
|
|
759
|
+
const fallback = document.createElement('div');
|
|
760
|
+
fallback.setAttribute('data-wt-ignore', '');
|
|
761
|
+
fallback.style.cssText = 'position:fixed;left:0;top:0;width:0;height:0;pointer-events:none;overflow:visible;z-index:2147483640;';
|
|
762
|
+
__wt_safeAppendToBody(fallback);
|
|
763
|
+
return fallback;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// ── COORDINATE ORACLE ───────────────────────────────────────────
|
|
768
|
+
function shadowQuery(selector) {
|
|
769
|
+
function searchRoot(root) {
|
|
770
|
+
const found = root.querySelector(selector);
|
|
771
|
+
if (found) return found;
|
|
772
|
+
|
|
773
|
+
for (const el of root.querySelectorAll('*')) {
|
|
774
|
+
if (el.shadowRoot) {
|
|
775
|
+
const result = searchRoot(el.shadowRoot);
|
|
776
|
+
if (result) return result;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
return searchRoot(document);
|
|
782
|
+
}
|
|
783
|
+
window.__Weavetab = {
|
|
784
|
+
version: '2.5.0',
|
|
785
|
+
suppressVisuals: false,
|
|
786
|
+
shadowQuery: shadowQuery,
|
|
787
|
+
|
|
788
|
+
// ── CDP Co-Processor Helpers ──────────────────────────────────────────────
|
|
789
|
+
// Call these via Runtime.evaluate instead of doing expensive CDP-side work.
|
|
790
|
+
// All return plain JSON-serializable values.
|
|
791
|
+
|
|
792
|
+
/** Scroll position, viewport size, and page dimensions in one call. */
|
|
793
|
+
getScrollInfo: function() {
|
|
794
|
+
var vv = window.visualViewport || {};
|
|
795
|
+
var body = document.body || document.documentElement;
|
|
796
|
+
return {
|
|
797
|
+
scrollX: Math.round(window.scrollX),
|
|
798
|
+
scrollY: Math.round(window.scrollY),
|
|
799
|
+
viewportW: Math.round(vv.width || window.innerWidth),
|
|
800
|
+
viewportH: Math.round(vv.height || window.innerHeight),
|
|
801
|
+
pageW: Math.round(body.scrollWidth),
|
|
802
|
+
pageH: Math.round(body.scrollHeight),
|
|
803
|
+
maxScrollX: Math.round(Math.max(0, body.scrollWidth - window.innerWidth)),
|
|
804
|
+
maxScrollY: Math.round(Math.max(0, body.scrollHeight - window.innerHeight)),
|
|
805
|
+
scrollRatio: body.scrollHeight > 0 ? +(window.scrollY / body.scrollHeight).toFixed(3) : 0
|
|
806
|
+
};
|
|
807
|
+
},
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Wait until no DOM mutations occur for `quietMs` milliseconds.
|
|
811
|
+
* Resolves to true when stable, false on timeout (default 5000ms).
|
|
812
|
+
* CDP calls this and awaits — replaces dumb sleep() calls.
|
|
813
|
+
*/
|
|
814
|
+
waitForStable: function(quietMs, timeoutMs) {
|
|
815
|
+
quietMs = quietMs || 300;
|
|
816
|
+
timeoutMs = timeoutMs || 5000;
|
|
817
|
+
return new Promise(function(resolve) {
|
|
818
|
+
var timer = null;
|
|
819
|
+
var deadline = setTimeout(function() {
|
|
820
|
+
obs.disconnect();
|
|
821
|
+
resolve(false);
|
|
822
|
+
}, timeoutMs);
|
|
823
|
+
function reset() {
|
|
824
|
+
if (timer) clearTimeout(timer);
|
|
825
|
+
timer = setTimeout(function() {
|
|
826
|
+
clearTimeout(deadline);
|
|
827
|
+
obs.disconnect();
|
|
828
|
+
resolve(true);
|
|
829
|
+
}, quietMs);
|
|
830
|
+
}
|
|
831
|
+
var obs = new MutationObserver(reset);
|
|
832
|
+
obs.observe(document.documentElement, { childList: true, subtree: true, attributes: true, characterData: true });
|
|
833
|
+
reset(); // start the quiet timer immediately
|
|
834
|
+
});
|
|
835
|
+
},
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Resolve a data-wt-id directly from the live index — much faster than
|
|
839
|
+
* doing a full accessibility tree walk.
|
|
840
|
+
* Returns { id, x, y, rect, tag, label, viewport, isVisible } or null.
|
|
841
|
+
*/
|
|
842
|
+
getElementByWtId: function(id) {
|
|
843
|
+
var idx = window.__wt_liveIndex;
|
|
844
|
+
if (!idx) return null;
|
|
845
|
+
var entry = null;
|
|
846
|
+
for (var i = 0; i < idx.length; i++) {
|
|
847
|
+
if (idx[i].id === String(id)) { entry = idx[i]; break; }
|
|
848
|
+
}
|
|
849
|
+
if (!entry) return null;
|
|
850
|
+
// Refresh coordinates from DOM if element still exists
|
|
851
|
+
var el = document.querySelector('[data-wt-id="' + entry.id + '"]');
|
|
852
|
+
if (el) {
|
|
853
|
+
var r = el.getBoundingClientRect();
|
|
854
|
+
entry.x = Math.round(r.left + r.width / 2);
|
|
855
|
+
entry.y = Math.round(r.top + r.height / 2);
|
|
856
|
+
entry.rect = { left: Math.round(r.left), top: Math.round(r.top), width: Math.round(r.width), height: Math.round(r.height) };
|
|
857
|
+
}
|
|
858
|
+
return entry;
|
|
859
|
+
},
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Dump all form field current values — lets agent verify form state cheaply.
|
|
863
|
+
* Returns array of { name, id, type, value, tag }.
|
|
864
|
+
*/
|
|
865
|
+
getFormValues: function() {
|
|
866
|
+
var results = [];
|
|
867
|
+
var els = document.querySelectorAll('input, select, textarea');
|
|
868
|
+
for (var i = 0; i < els.length; i++) {
|
|
869
|
+
var el = els[i];
|
|
870
|
+
if (el.hasAttribute('data-wt-ignore')) continue;
|
|
871
|
+
var val = el.type === 'checkbox' || el.type === 'radio' ? el.checked : el.value;
|
|
872
|
+
results.push({
|
|
873
|
+
name: el.name || el.id || null,
|
|
874
|
+
id: el.id || null,
|
|
875
|
+
wtId: el.getAttribute('data-wt-id') || null,
|
|
876
|
+
type: el.type || el.tagName.toLowerCase(),
|
|
877
|
+
tag: el.tagName.toLowerCase(),
|
|
878
|
+
value: val,
|
|
879
|
+
placeholder: el.placeholder || null
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
return results;
|
|
883
|
+
},
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Check visibility of a selector — uses the live IntersectionObserver data
|
|
887
|
+
* maintained by the MutationObserver. Much faster than a full DOM query.
|
|
888
|
+
* Returns { found, visible, occluded, x, y, rect } or null.
|
|
889
|
+
*/
|
|
890
|
+
checkVisible: function(selector) {
|
|
891
|
+
var el = document.querySelector(selector) || shadowQuery(selector);
|
|
892
|
+
if (!el) return { found: false };
|
|
893
|
+
var r = el.getBoundingClientRect();
|
|
894
|
+
var vv = window.visualViewport || {};
|
|
895
|
+
var vw = vv.width || window.innerWidth;
|
|
896
|
+
var vh = vv.height || window.innerHeight;
|
|
897
|
+
var inViewport = r.width > 0 && r.height > 0 && r.top < vh && r.bottom > 0 && r.left < vw && r.right > 0;
|
|
898
|
+
var topEl = document.elementFromPoint(r.left + r.width / 2, r.top + r.height / 2);
|
|
899
|
+
var occluded = topEl !== el && !el.contains(topEl);
|
|
900
|
+
return {
|
|
901
|
+
found: true,
|
|
902
|
+
visible: inViewport,
|
|
903
|
+
occluded: occluded,
|
|
904
|
+
x: Math.round(r.left + r.width / 2),
|
|
905
|
+
y: Math.round(r.top + r.height / 2),
|
|
906
|
+
rect: { left: Math.round(r.left), top: Math.round(r.top), width: Math.round(r.width), height: Math.round(r.height) }
|
|
907
|
+
};
|
|
908
|
+
},
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Return the live element index, optionally filtered.
|
|
912
|
+
* filter: 'visible' | 'all' (default 'visible')
|
|
913
|
+
* Much faster than browser_map for simple lookups.
|
|
914
|
+
*/
|
|
915
|
+
getLiveIndex: function(filter) {
|
|
916
|
+
var idx = window.__wt_liveIndex || [];
|
|
917
|
+
if (filter === 'all') return idx;
|
|
918
|
+
// Default: visible viewport only
|
|
919
|
+
return idx.filter(function(e) { return e.viewport === 'visible' || e.isVisible; });
|
|
920
|
+
},
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Pop and return the next user feedback message from the queue.
|
|
924
|
+
* MCP calls this between tool calls to check for user interrupts.
|
|
925
|
+
* Returns { text, ts, tabId } or null if queue is empty.
|
|
926
|
+
* Non-destructive — call popUserFeedback() to consume, peekUserFeedback() to inspect.
|
|
927
|
+
*/
|
|
928
|
+
popUserFeedback: function() {
|
|
929
|
+
// Read from localStorage queue (written by submitFeedback + persisted by background)
|
|
930
|
+
try {
|
|
931
|
+
var raw = localStorage.getItem('__wt_chat_queue_persist');
|
|
932
|
+
if (!raw) return null;
|
|
933
|
+
var arr = JSON.parse(raw);
|
|
934
|
+
if (!arr || !arr.length) return null;
|
|
935
|
+
var item = arr.shift();
|
|
936
|
+
localStorage.setItem('__wt_chat_queue_persist', JSON.stringify(arr));
|
|
937
|
+
return item;
|
|
938
|
+
} catch(e) { return null; }
|
|
939
|
+
},
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Check if a blocking dialog (alert/confirm/prompt) is pending on this tab.
|
|
943
|
+
* Agent should check this before clicking if it suspects a dialog might block.
|
|
944
|
+
* Returns { pending: bool, ts: number } — reads from extension storage via background relay.
|
|
945
|
+
*/
|
|
946
|
+
getAlertState: function() {
|
|
947
|
+
// Sync check via a DOM marker set by alert wrapper
|
|
948
|
+
return { pending: !!(window.__wt_alertPending), ts: Date.now() };
|
|
949
|
+
},
|
|
950
|
+
};
|
|
951
|
+
window.__Weavetab_shadowQuery = shadowQuery;
|
|
952
|
+
|
|
953
|
+
function isOccluded(el) {
|
|
954
|
+
const rect = el.getBoundingClientRect();
|
|
955
|
+
const cx = rect.left + rect.width / 2;
|
|
956
|
+
const cy = rect.top + rect.height / 2;
|
|
957
|
+
const topEl = document.elementFromPoint(cx, cy);
|
|
958
|
+
return topEl !== el && !el.contains(topEl);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
window.__Weavetab_getCoords = function(selector) {
|
|
962
|
+
const el = document.querySelector(selector) || shadowQuery(selector);
|
|
963
|
+
|
|
964
|
+
if (!el) return null;
|
|
965
|
+
|
|
966
|
+
const rect = el.getBoundingClientRect();
|
|
967
|
+
const vv = window.visualViewport;
|
|
968
|
+
|
|
969
|
+
return {
|
|
970
|
+
x: rect.left + rect.width / 2 + vv.offsetLeft,
|
|
971
|
+
y: rect.top + rect.height / 2 + vv.offsetTop,
|
|
972
|
+
raw: { left: rect.left, top: rect.top, w: rect.width, h: rect.height },
|
|
973
|
+
occluded: isOccluded(el),
|
|
974
|
+
visible: rect.width > 0 && rect.height > 0
|
|
975
|
+
&& rect.top < vv.height && rect.bottom > 0
|
|
976
|
+
};
|
|
977
|
+
};
|
|
978
|
+
|
|
979
|
+
function getStableSelector(el) {
|
|
980
|
+
if (el.id) return `#${el.id}`;
|
|
981
|
+
const path = [];
|
|
982
|
+
while (el.parentNode) {
|
|
983
|
+
let index = 1;
|
|
984
|
+
let sibling = el.previousElementSibling;
|
|
985
|
+
while (sibling) {
|
|
986
|
+
if (sibling.tagName === el.tagName) index++;
|
|
987
|
+
sibling = sibling.previousElementSibling;
|
|
988
|
+
}
|
|
989
|
+
path.unshift(`${el.tagName.toLowerCase()}:nth-of-type(${index})`);
|
|
990
|
+
el = el.parentNode;
|
|
991
|
+
}
|
|
992
|
+
return path.join(' > ');
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
window.__Weavetab_fullMap = function() {
|
|
996
|
+
const map = [];
|
|
997
|
+
let idCounter = 1;
|
|
998
|
+
function crawl(root, depth = 0) {
|
|
999
|
+
for (const el of root.querySelectorAll(
|
|
1000
|
+
'a,button,input,select,textarea,[role],[onclick],[tabindex]'
|
|
1001
|
+
)) {
|
|
1002
|
+
if (el.hasAttribute('data-wt-ignore')) continue;
|
|
1003
|
+
const rect = el.getBoundingClientRect();
|
|
1004
|
+
// Always include form elements (input/textarea/select/button) regardless of rect size
|
|
1005
|
+
// — canvas-heavy pages can occlude them and getBoundingClientRect returns 0
|
|
1006
|
+
const isEssential = ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'].includes(el.tagName);
|
|
1007
|
+
if (rect.width === 0 && !isEssential) continue;
|
|
1008
|
+
let id = el.getAttribute('data-wt-id');
|
|
1009
|
+
if (!id) {
|
|
1010
|
+
id = String(idCounter++);
|
|
1011
|
+
el.setAttribute('data-wt-id', id);
|
|
1012
|
+
}
|
|
1013
|
+
map.push({
|
|
1014
|
+
id: id,
|
|
1015
|
+
tag: el.tagName.toLowerCase(),
|
|
1016
|
+
text: (el.innerText || el.value || el.getAttribute('aria-label') || '').trim().slice(0,80),
|
|
1017
|
+
role: el.getAttribute('role'),
|
|
1018
|
+
selector: `[data-wt-id="${id}"]`,
|
|
1019
|
+
coords: { x: rect.left + rect.width/2, y: rect.top + rect.height/2 },
|
|
1020
|
+
inShadow: depth > 0
|
|
1021
|
+
});
|
|
1022
|
+
if (el.shadowRoot) crawl(el.shadowRoot, depth + 1);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
crawl(document);
|
|
1026
|
+
return map;
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
// ── CONSOLE BUFFERING ───────────────────────────────────────────
|
|
1030
|
+
const __consoleBuffer = [];
|
|
1031
|
+
let __consoleCursor = 0;
|
|
1032
|
+
|
|
1033
|
+
['log','warn','error','info','debug'].forEach(level => {
|
|
1034
|
+
const orig = console[level];
|
|
1035
|
+
console[level] = (...args) => {
|
|
1036
|
+
__consoleBuffer.push({ level, time: Date.now(), text: args.join(' ') });
|
|
1037
|
+
orig.apply(console, args);
|
|
1038
|
+
};
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
window.__Weavetab_readConsole = function({ since = 'last', level = null } = {}) {
|
|
1042
|
+
const from = since === 'last' ? __consoleCursor : 0;
|
|
1043
|
+
const entries = __consoleBuffer.slice(from);
|
|
1044
|
+
__consoleCursor = __consoleBuffer.length;
|
|
1045
|
+
return level ? entries.filter(e => e.level === level) : entries;
|
|
1046
|
+
};
|
|
1047
|
+
|
|
1048
|
+
// ── BLUE SVG WEAVE CURSOR ───────────────────────────────────────
|
|
1049
|
+
// Shadow-DOM-isolated, hardware-accelerated SVG cursor with smooth interpolation
|
|
1050
|
+
class WeaveCursor {
|
|
1051
|
+
constructor() {
|
|
1052
|
+
// Attach a shadow host that sits above all page content
|
|
1053
|
+
this._host = document.createElement('div');
|
|
1054
|
+
this._host.id = '__Weavetab_cursor_host';
|
|
1055
|
+
this._host.setAttribute('data-wt-ignore', 'true');
|
|
1056
|
+
Object.assign(this._host.style, {
|
|
1057
|
+
position: 'fixed',
|
|
1058
|
+
top: '0',
|
|
1059
|
+
left: '0',
|
|
1060
|
+
width: '0',
|
|
1061
|
+
height: '0',
|
|
1062
|
+
zIndex: '2147483647',
|
|
1063
|
+
pointerEvents: 'none',
|
|
1064
|
+
overflow: 'visible',
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
const shadow = this._host.attachShadow({ mode: 'closed' });
|
|
1068
|
+
|
|
1069
|
+
// Liquid trail canvas inside shadow root (zero DOM reflows)
|
|
1070
|
+
this._trailCanvas = document.createElement('canvas');
|
|
1071
|
+
this._trailCanvas.setAttribute('data-wt-ignore', 'true');
|
|
1072
|
+
Object.assign(this._trailCanvas.style, {
|
|
1073
|
+
position: 'fixed',
|
|
1074
|
+
top: '0',
|
|
1075
|
+
left: '0',
|
|
1076
|
+
width: '100vw',
|
|
1077
|
+
height: '100vh',
|
|
1078
|
+
pointerEvents: 'none',
|
|
1079
|
+
zIndex: '2147483645',
|
|
1080
|
+
overflow: 'visible',
|
|
1081
|
+
});
|
|
1082
|
+
this._trailCanvas.width = typeof window !== 'undefined' && window.innerWidth ? window.innerWidth : 1920;
|
|
1083
|
+
this._trailCanvas.height = typeof window !== 'undefined' && window.innerHeight ? window.innerHeight : 1080;
|
|
1084
|
+
shadow.appendChild(this._trailCanvas);
|
|
1085
|
+
this._trailCtx = this._trailCanvas.getContext('2d');
|
|
1086
|
+
this._trailPoints = [];
|
|
1087
|
+
this._lastTrailRecord = 0;
|
|
1088
|
+
|
|
1089
|
+
window.addEventListener('resize', () => {
|
|
1090
|
+
if (this._trailCanvas) {
|
|
1091
|
+
this._trailCanvas.width = window.innerWidth;
|
|
1092
|
+
this._trailCanvas.height = window.innerHeight;
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
// Hotspot aligned with the top-left pointer tip (3.0, 5.0 in 32x32 viewBox)
|
|
1097
|
+
this._hotX = 3.0;
|
|
1098
|
+
this._hotY = 5.0;
|
|
1099
|
+
this._scale = 0.4;
|
|
1100
|
+
|
|
1101
|
+
const initX = typeof window !== 'undefined' && window.innerWidth ? Math.round(window.innerWidth / 2) : 640;
|
|
1102
|
+
const initY = typeof window !== 'undefined' && window.innerHeight ? Math.round(window.innerHeight / 2) : 400;
|
|
1103
|
+
|
|
1104
|
+
// Exact vector SVG cursor matching user design
|
|
1105
|
+
this._cursor = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
1106
|
+
this._cursor.setAttribute('class', 'cursor');
|
|
1107
|
+
this._cursor.setAttribute('viewBox', '0 0 32 32');
|
|
1108
|
+
this._cursor.setAttribute('width', '32');
|
|
1109
|
+
this._cursor.setAttribute('height', '32');
|
|
1110
|
+
this._cursor.setAttribute('aria-hidden', 'true');
|
|
1111
|
+
Object.assign(this._cursor.style, {
|
|
1112
|
+
position: 'fixed',
|
|
1113
|
+
top: '0',
|
|
1114
|
+
left: '0',
|
|
1115
|
+
width: '32px',
|
|
1116
|
+
height: '32px',
|
|
1117
|
+
display: 'block',
|
|
1118
|
+
transformOrigin: `${this._hotX}px ${this._hotY}px`,
|
|
1119
|
+
transform: `translate3d(${initX - this._hotX}px, ${initY - this._hotY}px, 0) scale(${this._scale})`,
|
|
1120
|
+
overflow: 'visible',
|
|
1121
|
+
pointerEvents: 'none',
|
|
1122
|
+
willChange: 'transform',
|
|
1123
|
+
transition: 'opacity 0.2s ease-out',
|
|
1124
|
+
opacity: '1',
|
|
1125
|
+
zIndex: '2147483647',
|
|
1126
|
+
filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.35))',
|
|
1127
|
+
});
|
|
1128
|
+
|
|
1129
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
1130
|
+
path.setAttribute('class', 'cursor-shape');
|
|
1131
|
+
path.setAttribute('d', 'M 6.1,4.7 C 4.5,4.1 3.0,5.0 3.0,6.7 C 3.0,7.1 3.1,7.5 3.3,8.0 L 10.2,27.0 C 10.8,28.7 12.0,29.6 13.6,29.5 C 15.1,29.4 16.0,28.5 16.5,27.0 L 19.4,18.8 L 25.9,17.0 C 27.7,16.5 28.9,15.2 28.9,13.6 C 28.9,12.1 28.0,10.9 26.4,10.3 Z');
|
|
1132
|
+
path.setAttribute('fill', '#1268ff');
|
|
1133
|
+
path.setAttribute('stroke', '#ffffff');
|
|
1134
|
+
path.setAttribute('stroke-width', '3.1');
|
|
1135
|
+
path.setAttribute('stroke-linejoin', 'round');
|
|
1136
|
+
path.setAttribute('stroke-linecap', 'round');
|
|
1137
|
+
|
|
1138
|
+
this._cursor.appendChild(path);
|
|
1139
|
+
shadow.appendChild(this._cursor);
|
|
1140
|
+
|
|
1141
|
+
// Alias _canvas to _cursor for backward compatibility
|
|
1142
|
+
this._canvas = this._cursor;
|
|
1143
|
+
|
|
1144
|
+
this._x = initX;
|
|
1145
|
+
this._y = initY;
|
|
1146
|
+
this._targetX = initX;
|
|
1147
|
+
this._targetY = initY;
|
|
1148
|
+
this._lerpX = initX;
|
|
1149
|
+
this._lerpY = initY;
|
|
1150
|
+
this._state = 'idle';
|
|
1151
|
+
this._visible = true;
|
|
1152
|
+
this._animFrame = null;
|
|
1153
|
+
this._lastTime = performance.now();
|
|
1154
|
+
this._waypointQueue = [];
|
|
1155
|
+
this._playingQueue = false;
|
|
1156
|
+
|
|
1157
|
+
__wt_safeAppendToBody(this._host);
|
|
1158
|
+
this._startLoop();
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
destroy() {
|
|
1162
|
+
if (this._animFrame) {
|
|
1163
|
+
cancelAnimationFrame(this._animFrame);
|
|
1164
|
+
this._animFrame = null;
|
|
1165
|
+
}
|
|
1166
|
+
if (this._host && this._host.parentNode) {
|
|
1167
|
+
this._host.parentNode.removeChild(this._host);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
setState(state) {
|
|
1172
|
+
this._state = state || 'idle';
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
setVisible(visible) {
|
|
1176
|
+
if (typeof __wt_masterVisualsEnabled !== 'undefined' && !__wt_masterVisualsEnabled) {
|
|
1177
|
+
this._visible = false;
|
|
1178
|
+
this._cursor.style.opacity = '0';
|
|
1179
|
+
if (this._trailCtx && this._trailCanvas) {
|
|
1180
|
+
this._trailCtx.clearRect(0, 0, this._trailCanvas.width, this._trailCanvas.height);
|
|
1181
|
+
}
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
this._visible = visible;
|
|
1185
|
+
this._cursor.style.opacity = visible ? '1' : '0';
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
moveTo(x, y) {
|
|
1189
|
+
if (typeof __wt_masterVisualsEnabled !== 'undefined' && !__wt_masterVisualsEnabled) return;
|
|
1190
|
+
if (typeof x !== 'number' || typeof y !== 'number' || isNaN(x) || isNaN(y)) return;
|
|
1191
|
+
this._targetX = x;
|
|
1192
|
+
this._targetY = y;
|
|
1193
|
+
if (this._lerpX === -9999) {
|
|
1194
|
+
this._lerpX = x;
|
|
1195
|
+
this._lerpY = y;
|
|
1196
|
+
}
|
|
1197
|
+
if (!this._visible) {
|
|
1198
|
+
this._visible = true;
|
|
1199
|
+
this._cursor.style.opacity = '1';
|
|
1200
|
+
}
|
|
1201
|
+
if (!this._animFrame) {
|
|
1202
|
+
this._startLoop();
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
setWaypoints(waypoints, durationMs) {
|
|
1207
|
+
if (typeof __wt_masterVisualsEnabled !== 'undefined' && !__wt_masterVisualsEnabled) return;
|
|
1208
|
+
if (!waypoints || !waypoints.length) return;
|
|
1209
|
+
this._waypointQueue = waypoints;
|
|
1210
|
+
this._playingQueue = true;
|
|
1211
|
+
this._waypointStartTime = performance.now();
|
|
1212
|
+
this._waypointDuration = durationMs || (waypoints.length * 16);
|
|
1213
|
+
if (waypoints.length > 0 && this._lerpX === -9999) {
|
|
1214
|
+
this._lerpX = waypoints[0].x;
|
|
1215
|
+
this._lerpY = waypoints[0].y;
|
|
1216
|
+
}
|
|
1217
|
+
if (!this._visible) {
|
|
1218
|
+
this._visible = true;
|
|
1219
|
+
this._cursor.style.opacity = '1';
|
|
1220
|
+
}
|
|
1221
|
+
if (!this._animFrame) this._startLoop();
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
_startLoop() {
|
|
1225
|
+
this._lastTime = performance.now();
|
|
1226
|
+
const draw = (currentTime) => {
|
|
1227
|
+
const dt = Math.min((currentTime - this._lastTime) / 1000, 0.1);
|
|
1228
|
+
this._lastTime = currentTime;
|
|
1229
|
+
|
|
1230
|
+
if (this._playingQueue && this._waypointQueue.length > 0) {
|
|
1231
|
+
const elapsed = currentTime - this._waypointStartTime;
|
|
1232
|
+
const progress = Math.max(0, Math.min(elapsed / (this._waypointDuration || 1), 1));
|
|
1233
|
+
const totalSegments = this._waypointQueue.length - 1;
|
|
1234
|
+
|
|
1235
|
+
if (totalSegments <= 0) {
|
|
1236
|
+
this._targetX = this._waypointQueue[0].x;
|
|
1237
|
+
this._targetY = this._waypointQueue[0].y;
|
|
1238
|
+
} else {
|
|
1239
|
+
const floatIdx = progress * totalSegments;
|
|
1240
|
+
const i0 = Math.floor(floatIdx);
|
|
1241
|
+
const i1 = Math.min(i0 + 1, totalSegments);
|
|
1242
|
+
const t = floatIdx - i0;
|
|
1243
|
+
|
|
1244
|
+
const p0 = this._waypointQueue[i0];
|
|
1245
|
+
const p1 = this._waypointQueue[i1];
|
|
1246
|
+
if (p0 && p1) {
|
|
1247
|
+
this._targetX = p0.x + (p1.x - p0.x) * t;
|
|
1248
|
+
this._targetY = p0.y + (p1.y - p0.y) * t;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
if (progress >= 1) {
|
|
1253
|
+
this._playingQueue = false;
|
|
1254
|
+
this._waypointQueue = [];
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// Smooth exponential lerp (k = 24 for fluid human-smooth tracking)
|
|
1259
|
+
const lerpFactor = 1 - Math.exp(-24 * dt);
|
|
1260
|
+
if (this._targetX !== -9999) {
|
|
1261
|
+
this._lerpX += (this._targetX - this._lerpX) * lerpFactor;
|
|
1262
|
+
this._lerpY += (this._targetY - this._lerpY) * lerpFactor;
|
|
1263
|
+
if (Math.abs(this._targetX - this._lerpX) < 0.1) this._lerpX = this._targetX;
|
|
1264
|
+
if (Math.abs(this._targetY - this._lerpY) < 0.1) this._lerpY = this._targetY;
|
|
1265
|
+
|
|
1266
|
+
// "always show in browser" - stays visible once loaded and active
|
|
1267
|
+
if (typeof __wt_masterVisualsEnabled === 'undefined' || __wt_masterVisualsEnabled) {
|
|
1268
|
+
if (!this._visible) {
|
|
1269
|
+
this._visible = true;
|
|
1270
|
+
this._cursor.style.opacity = '1';
|
|
1271
|
+
}
|
|
1272
|
+
} else {
|
|
1273
|
+
if (this._visible) {
|
|
1274
|
+
this._visible = false;
|
|
1275
|
+
this._cursor.style.opacity = '0';
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
this._render(currentTime);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
this._animFrame = requestAnimationFrame(draw);
|
|
1283
|
+
};
|
|
1284
|
+
if (this._animFrame) cancelAnimationFrame(this._animFrame);
|
|
1285
|
+
this._animFrame = requestAnimationFrame(draw);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
_render(currentTime) {
|
|
1289
|
+
if (typeof __wt_masterVisualsEnabled !== 'undefined' && !__wt_masterVisualsEnabled) {
|
|
1290
|
+
this._cursor.style.opacity = '0';
|
|
1291
|
+
if (this._trailCtx && this._trailCanvas) {
|
|
1292
|
+
this._trailCtx.clearRect(0, 0, this._trailCanvas.width, this._trailCanvas.height);
|
|
1293
|
+
}
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
const x = this._lerpX;
|
|
1297
|
+
const y = this._lerpY;
|
|
1298
|
+
if (x === -9999 || y === -9999) return;
|
|
1299
|
+
|
|
1300
|
+
const now = currentTime || performance.now();
|
|
1301
|
+
|
|
1302
|
+
// 1. Trail decay tracking
|
|
1303
|
+
const dx = this._targetX - this._lerpX;
|
|
1304
|
+
const dy = this._targetY - this._lerpY;
|
|
1305
|
+
const moveDist = Math.sqrt(dx * dx + dy * dy);
|
|
1306
|
+
|
|
1307
|
+
if (moveDist > 1.8 && now - this._lastTrailRecord > 22) {
|
|
1308
|
+
this._trailPoints.push({ x, y, birth: now });
|
|
1309
|
+
this._lastTrailRecord = now;
|
|
1310
|
+
if (this._trailPoints.length > 14) this._trailPoints.shift();
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// Render decaying trail
|
|
1314
|
+
if (this._trailCtx && this._trailCanvas) {
|
|
1315
|
+
this._trailCtx.clearRect(0, 0, this._trailCanvas.width, this._trailCanvas.height);
|
|
1316
|
+
for (let i = this._trailPoints.length - 1; i >= 0; i--) {
|
|
1317
|
+
const pt = this._trailPoints[i];
|
|
1318
|
+
const age = (now - pt.birth) / 260;
|
|
1319
|
+
if (age >= 1) {
|
|
1320
|
+
this._trailPoints.splice(i, 1);
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
const alpha = (1 - age) * 0.45;
|
|
1324
|
+
const radius = Math.max(1, (1 - age) * 2.8);
|
|
1325
|
+
this._trailCtx.beginPath();
|
|
1326
|
+
this._trailCtx.arc(pt.x, pt.y, radius, 0, Math.PI * 2);
|
|
1327
|
+
this._trailCtx.fillStyle = `rgba(18, 104, 255, ${alpha})`;
|
|
1328
|
+
this._trailCtx.shadowColor = '#1268ff';
|
|
1329
|
+
this._trailCtx.shadowBlur = 4;
|
|
1330
|
+
this._trailCtx.fill();
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
// 2. Cursor scale and tactile micro-physics
|
|
1335
|
+
let targetScale = this._scale;
|
|
1336
|
+
if (this._state === 'clicking') {
|
|
1337
|
+
targetScale = this._scale * 0.82; // Tactile compression on click
|
|
1338
|
+
} else if (this._state === 'hover') {
|
|
1339
|
+
targetScale = this._scale * 1.08; // Interactive focus
|
|
1340
|
+
} else if (this._state === 'idle') {
|
|
1341
|
+
targetScale = this._scale + Math.sin(now * 0.003) * 0.012; // Alive breathing oscillation
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// 3. Dynamic velocity tilt (natural motion lag)
|
|
1345
|
+
const tilt = Math.max(-5, Math.min(5, dx * 0.06));
|
|
1346
|
+
|
|
1347
|
+
this._cursor.style.transform = `translate3d(${x - this._hotX}px, ${y - this._hotY}px, 0) rotate(${tilt}deg) scale(${targetScale})`;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// Instantiate the active cursor
|
|
1352
|
+
let __weaveCursor = new WeaveCursor();
|
|
1353
|
+
window.__weaveCursor_instance = __weaveCursor;
|
|
1354
|
+
|
|
1355
|
+
function moveCursor(x, y, state) {
|
|
1356
|
+
if (typeof __wt_masterVisualsEnabled !== 'undefined' && !__wt_masterVisualsEnabled) return;
|
|
1357
|
+
if (__weaveCursor && typeof __weaveCursor.moveTo === 'function') {
|
|
1358
|
+
if (typeof x === 'number' && typeof y === 'number' && x >= 0 && y >= 0) {
|
|
1359
|
+
__weaveCursor.moveTo(x, y);
|
|
1360
|
+
}
|
|
1361
|
+
if (state) __weaveCursor.setState(state);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// Enhanced twin action ripples radiating from click target
|
|
1366
|
+
function ripple(x, y) {
|
|
1367
|
+
if (typeof __wt_masterVisualsEnabled !== 'undefined' && !__wt_masterVisualsEnabled) return;
|
|
1368
|
+
const host = document.createElement('div');
|
|
1369
|
+
host.className = 'Weavetab-ripple-host';
|
|
1370
|
+
host.setAttribute('data-wt-ignore', 'true');
|
|
1371
|
+
host.style.cssText = `position:fixed;left:${x}px;top:${y}px;pointer-events:none;z-index:2147483646;transform:translate(-50%, -50%);`;
|
|
1372
|
+
|
|
1373
|
+
const w1 = document.createElement('div');
|
|
1374
|
+
w1.style.cssText = 'position:absolute;top:50%;left:50%;width:8px;height:8px;margin:-4px 0 0 -4px;border:2px solid #1268ff;border-radius:50%;box-shadow:0 0 12px rgba(18,104,255,0.7);animation:__wt_ripple_expand 0.42s cubic-bezier(0.1, 0.8, 0.3, 1) forwards;pointer-events:none;';
|
|
1375
|
+
|
|
1376
|
+
const w2 = document.createElement('div');
|
|
1377
|
+
w2.style.cssText = 'position:absolute;top:50%;left:50%;width:8px;height:8px;margin:-4px 0 0 -4px;border:1.5px solid #60a5fa;border-radius:50%;animation:__wt_ripple_expand 0.52s 0.08s cubic-bezier(0.1, 0.8, 0.3, 1) forwards;pointer-events:none;';
|
|
1378
|
+
|
|
1379
|
+
host.appendChild(w1);
|
|
1380
|
+
host.appendChild(w2);
|
|
1381
|
+
(document.getElementById('Weavetab-root')?.shadowRoot || document.body || document.documentElement).appendChild(host);
|
|
1382
|
+
setTimeout(() => host.remove(), 620);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// ── X-RAY SPOTLIGHT OVERLAY (Phase 5.2) ────────────────────────
|
|
1386
|
+
// Flashes glowing halos around top interactive elements when the
|
|
1387
|
+
// semantic map is scanned (GET_SEMANTIC_MAP message).
|
|
1388
|
+
let __xrayActive = false;
|
|
1389
|
+
const __xrayOverlays = [];
|
|
1390
|
+
|
|
1391
|
+
function clearXRay() {
|
|
1392
|
+
for (const el of __xrayOverlays) el.remove();
|
|
1393
|
+
__xrayOverlays.length = 0;
|
|
1394
|
+
__xrayActive = false;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
function activateXRay(elements) {
|
|
1398
|
+
clearXRay();
|
|
1399
|
+
__xrayActive = true;
|
|
1400
|
+
|
|
1401
|
+
// Colour spectrum for visual richness
|
|
1402
|
+
const colours = [
|
|
1403
|
+
'#4F8EF7', '#7C3AED', '#10B981', '#F59E0B',
|
|
1404
|
+
'#EF4444', '#06B6D4', '#F97316', '#8B5CF6',
|
|
1405
|
+
];
|
|
1406
|
+
|
|
1407
|
+
// Architecture change: Use live index for visual overlay
|
|
1408
|
+
// The live index maintains up-to-date coordinates and visibility info
|
|
1409
|
+
const liveIndex = window.__wt_liveIndex || [];
|
|
1410
|
+
const top = elements.slice(0, 12); // highlight top 12 nodes
|
|
1411
|
+
|
|
1412
|
+
top.forEach((item, i) => {
|
|
1413
|
+
// Try to get element from live index first for accurate coordinates
|
|
1414
|
+
const liveEntry = liveIndex.find(e => e.id === item.id || e.selector === item.selector);
|
|
1415
|
+
|
|
1416
|
+
let rect;
|
|
1417
|
+
let el;
|
|
1418
|
+
|
|
1419
|
+
if (liveEntry && liveEntry.rect) {
|
|
1420
|
+
// Use coordinates from live index (more accurate, already computed)
|
|
1421
|
+
rect = liveEntry.rect;
|
|
1422
|
+
el = document.querySelector(item.selector) || shadowQuery(item.selector);
|
|
1423
|
+
} else {
|
|
1424
|
+
// Fallback to querying DOM
|
|
1425
|
+
el = document.querySelector(item.selector) || shadowQuery(item.selector);
|
|
1426
|
+
if (!el) return;
|
|
1427
|
+
rect = el.getBoundingClientRect();
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
if (rect.width === 0 || rect.height === 0) return;
|
|
1431
|
+
|
|
1432
|
+
const colour = colours[i % colours.length];
|
|
1433
|
+
const halo = document.createElement('div');
|
|
1434
|
+
halo.className = '__Weavetab_xray';
|
|
1435
|
+
halo.setAttribute('data-wt-ignore', '');
|
|
1436
|
+
|
|
1437
|
+
Object.assign(halo.style, {
|
|
1438
|
+
position: 'fixed',
|
|
1439
|
+
left: `${rect.left - 4}px`,
|
|
1440
|
+
top: `${rect.top - 4}px`,
|
|
1441
|
+
width: `${rect.width + 8}px`,
|
|
1442
|
+
height: `${rect.height + 8}px`,
|
|
1443
|
+
border: `2px solid ${colour}`,
|
|
1444
|
+
borderRadius: '6px',
|
|
1445
|
+
boxShadow: `0 0 12px 4px ${colour}88, inset 0 0 8px ${colour}33`,
|
|
1446
|
+
background: `${colour}0d`,
|
|
1447
|
+
pointerEvents: 'none',
|
|
1448
|
+
zIndex: '2147483645',
|
|
1449
|
+
animation: '__wt_xray_pulse 1.4s ease-in-out forwards',
|
|
1450
|
+
animationDelay: `${i * 55}ms`,
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
// Rank label badge
|
|
1454
|
+
const badge = document.createElement('span');
|
|
1455
|
+
Object.assign(badge.style, {
|
|
1456
|
+
position: 'absolute',
|
|
1457
|
+
top: '-10px',
|
|
1458
|
+
left: '4px',
|
|
1459
|
+
background: colour,
|
|
1460
|
+
color: '#fff',
|
|
1461
|
+
fontSize: '9px',
|
|
1462
|
+
fontFamily: 'system-ui, sans-serif',
|
|
1463
|
+
fontWeight: '700',
|
|
1464
|
+
padding: '1px 5px',
|
|
1465
|
+
borderRadius: '3px',
|
|
1466
|
+
lineHeight: '14px',
|
|
1467
|
+
whiteSpace: 'nowrap',
|
|
1468
|
+
letterSpacing: '0.03em',
|
|
1469
|
+
boxShadow: `0 1px 4px ${colour}99`,
|
|
1470
|
+
});
|
|
1471
|
+
const label = (item.text || item.role || item.tag || '').slice(0, 22) || `#${i + 1}`;
|
|
1472
|
+
badge.textContent = `${i + 1} ${label}`;
|
|
1473
|
+
halo.appendChild(badge);
|
|
1474
|
+
|
|
1475
|
+
getWeavetabRoot().appendChild(halo);
|
|
1476
|
+
__xrayOverlays.push(halo);
|
|
1477
|
+
});
|
|
1478
|
+
|
|
1479
|
+
// Auto-clear after 2.8 s
|
|
1480
|
+
setTimeout(clearXRay, 2800);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// Inject keyframe animation for X-Ray
|
|
1484
|
+
(function injectXRayStyles() {
|
|
1485
|
+
if (document.getElementById('__Weavetab_xray_styles')) return;
|
|
1486
|
+
const style = document.createElement('style');
|
|
1487
|
+
style.id = '__Weavetab_xray_styles';
|
|
1488
|
+
style.textContent = `
|
|
1489
|
+
@keyframes __wt_xray_pulse {
|
|
1490
|
+
0% { opacity: 0; transform: scale(0.94); }
|
|
1491
|
+
18% { opacity: 1; transform: scale(1.02); }
|
|
1492
|
+
70% { opacity: 0.85; transform: scale(1); }
|
|
1493
|
+
100% { opacity: 0; transform: scale(0.97); }
|
|
1494
|
+
}
|
|
1495
|
+
.Weavetab-ripple {
|
|
1496
|
+
width: 40px; height: 40px;
|
|
1497
|
+
margin: -20px 0 0 -20px;
|
|
1498
|
+
border-radius: 50%;
|
|
1499
|
+
background: rgba(79,142,247,0.35);
|
|
1500
|
+
border: 2px solid #4F8EF7;
|
|
1501
|
+
animation: __wt_ripple 0.55s ease-out forwards;
|
|
1502
|
+
}
|
|
1503
|
+
@keyframes __wt_ripple {
|
|
1504
|
+
from { transform: scale(0.3); opacity: 1; }
|
|
1505
|
+
to { transform: scale(2.2); opacity: 0; }
|
|
1506
|
+
}
|
|
1507
|
+
@keyframes wt-reading-pulse {
|
|
1508
|
+
0% { opacity: 0; box-shadow: inset 0 0 0 rgba(59,130,246,0); }
|
|
1509
|
+
15% { opacity: 1; box-shadow: inset 0 0 60px rgba(59,130,246,0.15); }
|
|
1510
|
+
85% { opacity: 1; box-shadow: inset 0 0 60px rgba(59,130,246,0.15); }
|
|
1511
|
+
100% { opacity: 0; box-shadow: inset 0 0 0 rgba(59,130,246,0); }
|
|
1512
|
+
}
|
|
1513
|
+
`;
|
|
1514
|
+
(document.head || document.documentElement).appendChild(style);
|
|
1515
|
+
})();
|
|
1516
|
+
|
|
1517
|
+
// ── READING GLOW ────────────────────────────────────────────────
|
|
1518
|
+
let __wt_readingGlow = null;
|
|
1519
|
+
|
|
1520
|
+
function __wt_showReadingGlow() {
|
|
1521
|
+
if (__wt_readingGlow) return;
|
|
1522
|
+
var el = document.createElement('div');
|
|
1523
|
+
el.id = 'wt-reading-glow';
|
|
1524
|
+
el.setAttribute('data-wt-ignore', '');
|
|
1525
|
+
el.style.cssText = 'position:fixed;inset:0;pointer-events:none;z-index:2147483646;border:4px solid;border-image:linear-gradient(135deg, #3b82f6, #8b5cf6, #10b981) 1;animation:wt-reading-pulse 3.5s ease-in-out forwards;';
|
|
1526
|
+
__wt_safeAppendToBody(el);
|
|
1527
|
+
__wt_readingGlow = el;
|
|
1528
|
+
setTimeout(__wt_hideReadingGlow, 3500);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
function __wt_hideReadingGlow() {
|
|
1532
|
+
if (__wt_readingGlow) { __wt_readingGlow.remove(); __wt_readingGlow = null; }
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
// ── SEMANTIC DOM MAP ────────────────────────────────────────────
|
|
1536
|
+
// Architecture change: Passive/extension-side ID assignment with live index
|
|
1537
|
+
// The MutationObserver continuously assigns data-wt-id to interactive elements
|
|
1538
|
+
// and maintains a live index that CDP can read lazily
|
|
1539
|
+
|
|
1540
|
+
// Live index - always up-to-date, accessible via window.__wt_liveIndex
|
|
1541
|
+
window.__wt_liveIndex = [];
|
|
1542
|
+
window.__wt_nextId = 1;
|
|
1543
|
+
|
|
1544
|
+
// IntersectionObserver for real occlusion detection (not just display:none)
|
|
1545
|
+
const intersectionObserver = new IntersectionObserver((entries) => {
|
|
1546
|
+
for (const entry of entries) {
|
|
1547
|
+
const el = entry.target;
|
|
1548
|
+
const id = el.getAttribute('data-wt-id');
|
|
1549
|
+
if (id) {
|
|
1550
|
+
const indexEntry = window.__wt_liveIndex.find(e => e.id === id);
|
|
1551
|
+
if (indexEntry) {
|
|
1552
|
+
indexEntry.isVisible = entry.isIntersecting;
|
|
1553
|
+
indexEntry.intersectionRatio = entry.intersectionRatio;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}, { threshold: [0, 0.25, 0.5, 0.75, 1.0] });
|
|
1558
|
+
|
|
1559
|
+
// Function to assign data-wt-id to an element and add to live index
|
|
1560
|
+
function assignIdAndIndex(el) {
|
|
1561
|
+
if (el.hasAttribute('data-wt-ignore')) return;
|
|
1562
|
+
if (el.hasAttribute('data-wt-id')) return; // Already assigned
|
|
1563
|
+
|
|
1564
|
+
const id = String(window.__wt_nextId++);
|
|
1565
|
+
el.setAttribute('data-wt-id', id);
|
|
1566
|
+
el.__wt_id = id;
|
|
1567
|
+
|
|
1568
|
+
const rect = el.getBoundingClientRect();
|
|
1569
|
+
const inViewport = (
|
|
1570
|
+
rect.bottom > 0 && rect.right > 0 &&
|
|
1571
|
+
rect.top < window.innerHeight && rect.left < window.innerWidth
|
|
1572
|
+
);
|
|
1573
|
+
const viewportPos = inViewport ? "visible" : (rect.top >= window.innerHeight ? "below" : "above");
|
|
1574
|
+
|
|
1575
|
+
// Check for event listeners
|
|
1576
|
+
const hasClickListener = el.onclick !== null ||
|
|
1577
|
+
el.addEventListener !== undefined &&
|
|
1578
|
+
(el._listeners?.click?.length > 0);
|
|
1579
|
+
|
|
1580
|
+
const indexEntry = {
|
|
1581
|
+
id: id,
|
|
1582
|
+
tag: el.tagName.toLowerCase(),
|
|
1583
|
+
role: el.getAttribute('role') || el.tagName.toLowerCase(),
|
|
1584
|
+
label: el.getAttribute('aria-label') || el.textContent?.trim().slice(0, 60) || '',
|
|
1585
|
+
placeholder: el.getAttribute('placeholder') || '',
|
|
1586
|
+
selector: getStableSelector(el),
|
|
1587
|
+
x: Math.round(rect.left + rect.width / 2),
|
|
1588
|
+
y: Math.round(rect.top + rect.height / 2),
|
|
1589
|
+
viewport: viewportPos,
|
|
1590
|
+
href: el.href || null,
|
|
1591
|
+
isVisible: inViewport,
|
|
1592
|
+
intersectionRatio: inViewport ? 1.0 : 0.0,
|
|
1593
|
+
hasClickListener: hasClickListener,
|
|
1594
|
+
rect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height }
|
|
1595
|
+
};
|
|
1596
|
+
|
|
1597
|
+
window.__wt_liveIndex.push(indexEntry);
|
|
1598
|
+
|
|
1599
|
+
// Observe for intersection changes
|
|
1600
|
+
try {
|
|
1601
|
+
intersectionObserver.observe(el);
|
|
1602
|
+
} catch (e) {
|
|
1603
|
+
// Element might not be observable
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
// Function to update an existing entry in the live index
|
|
1608
|
+
function updateIndexEntry(el) {
|
|
1609
|
+
const id = el.getAttribute('data-wt-id');
|
|
1610
|
+
if (!id) return;
|
|
1611
|
+
|
|
1612
|
+
const indexEntry = window.__wt_liveIndex.find(e => e.id === id);
|
|
1613
|
+
if (!indexEntry) return;
|
|
1614
|
+
|
|
1615
|
+
const rect = el.getBoundingClientRect();
|
|
1616
|
+
const inViewport = (
|
|
1617
|
+
rect.bottom > 0 && rect.right > 0 &&
|
|
1618
|
+
rect.top < window.innerHeight && rect.left < window.innerWidth
|
|
1619
|
+
);
|
|
1620
|
+
const viewportPos = inViewport ? "visible" : (rect.top >= window.innerHeight ? "below" : "above");
|
|
1621
|
+
|
|
1622
|
+
indexEntry.x = Math.round(rect.left + rect.width / 2);
|
|
1623
|
+
indexEntry.y = Math.round(rect.top + rect.height / 2);
|
|
1624
|
+
indexEntry.viewport = viewportPos;
|
|
1625
|
+
indexEntry.isVisible = inViewport;
|
|
1626
|
+
indexEntry.rect = { left: rect.left, top: rect.top, width: rect.width, height: rect.height };
|
|
1627
|
+
indexEntry.label = el.getAttribute('aria-label') || el.textContent?.trim().slice(0, 60) || '';
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
// Function to remove an entry from the live index
|
|
1631
|
+
function removeIndexEntry(el) {
|
|
1632
|
+
const id = el.getAttribute('data-wt-id');
|
|
1633
|
+
if (!id) return;
|
|
1634
|
+
|
|
1635
|
+
const index = window.__wt_liveIndex.findIndex(e => e.id === id);
|
|
1636
|
+
if (index !== -1) {
|
|
1637
|
+
window.__wt_liveIndex.splice(index, 1);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
try {
|
|
1641
|
+
intersectionObserver.unobserve(el);
|
|
1642
|
+
} catch (e) {
|
|
1643
|
+
// Element might not be observable
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// MutationObserver for continuous ID assignment and index maintenance
|
|
1648
|
+
const idAssignmentObserver = new MutationObserver((mutations) => {
|
|
1649
|
+
for (const mutation of mutations) {
|
|
1650
|
+
// Handle added nodes
|
|
1651
|
+
for (const node of mutation.addedNodes) {
|
|
1652
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
1653
|
+
// Check if the node itself is interactive
|
|
1654
|
+
const interactiveSelector = 'a, button, input, select, textarea, [role="button"], [role="link"], [role="textbox"], [role="checkbox"], [role="radio"], [role="combobox"], [role="searchbox"], [role="menuitem"], [role="tab"], [tabindex]:not([tabindex="-1"])';
|
|
1655
|
+
if (node.matches && node.matches(interactiveSelector)) {
|
|
1656
|
+
assignIdAndIndex(node);
|
|
1657
|
+
}
|
|
1658
|
+
// Check descendants
|
|
1659
|
+
const descendants = node.querySelectorAll ? node.querySelectorAll(interactiveSelector) : [];
|
|
1660
|
+
for (const desc of descendants) {
|
|
1661
|
+
assignIdAndIndex(desc);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// Handle removed nodes
|
|
1667
|
+
for (const node of mutation.removedNodes) {
|
|
1668
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
1669
|
+
if (node.getAttribute && node.getAttribute('data-wt-id')) {
|
|
1670
|
+
removeIndexEntry(node);
|
|
1671
|
+
}
|
|
1672
|
+
const descendants = node.querySelectorAll ? node.querySelectorAll('[data-wt-id]') : [];
|
|
1673
|
+
for (const desc of descendants) {
|
|
1674
|
+
removeIndexEntry(desc);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
// Handle attribute changes (position, visibility, etc.)
|
|
1680
|
+
if (mutation.type === 'attributes' && mutation.target.getAttribute) {
|
|
1681
|
+
const target = mutation.target;
|
|
1682
|
+
if (target.getAttribute('data-wt-id')) {
|
|
1683
|
+
updateIndexEntry(target);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
});
|
|
1688
|
+
|
|
1689
|
+
// Start observing when DOM is ready
|
|
1690
|
+
function startIdAssignment() {
|
|
1691
|
+
const interactiveSelector = 'a, button, input, select, textarea, [role="button"], [role="link"], [role="textbox"], [role="checkbox"], [role="radio"], [role="combobox"], [role="searchbox"], [role="menuitem"], [role="tab"], [tabindex]:not([tabindex="-1"])';
|
|
1692
|
+
|
|
1693
|
+
// Assign IDs to existing interactive elements
|
|
1694
|
+
const existing = document.querySelectorAll(interactiveSelector);
|
|
1695
|
+
for (const el of existing) {
|
|
1696
|
+
assignIdAndIndex(el);
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
// Start observing for changes
|
|
1700
|
+
if (document.body) {
|
|
1701
|
+
idAssignmentObserver.observe(document.body, {
|
|
1702
|
+
childList: true,
|
|
1703
|
+
subtree: true,
|
|
1704
|
+
attributes: true,
|
|
1705
|
+
attributeFilter: ['style', 'class', 'hidden', 'aria-hidden']
|
|
1706
|
+
});
|
|
1707
|
+
} else {
|
|
1708
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
1709
|
+
const existing = document.querySelectorAll(interactiveSelector);
|
|
1710
|
+
for (const el of existing) {
|
|
1711
|
+
assignIdAndIndex(el);
|
|
1712
|
+
}
|
|
1713
|
+
idAssignmentObserver.observe(document.body, {
|
|
1714
|
+
childList: true,
|
|
1715
|
+
subtree: true,
|
|
1716
|
+
attributes: true,
|
|
1717
|
+
attributeFilter: ['style', 'class', 'hidden', 'aria-hidden']
|
|
1718
|
+
});
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
// Start the ID assignment system
|
|
1724
|
+
if (document.readyState === 'loading') {
|
|
1725
|
+
document.addEventListener('DOMContentLoaded', startIdAssignment);
|
|
1726
|
+
} else {
|
|
1727
|
+
startIdAssignment();
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
// Legacy buildSemanticMap function for backward compatibility
|
|
1731
|
+
// Now reads from the live index instead of rebuilding
|
|
1732
|
+
let cachedMap = null;
|
|
1733
|
+
const legacyObserver = new MutationObserver(() => {
|
|
1734
|
+
cachedMap = null;
|
|
1735
|
+
});
|
|
1736
|
+
// FIX: document.body may be null at document_start; defer observe until body exists
|
|
1737
|
+
if (document.body) {
|
|
1738
|
+
legacyObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class', 'hidden'] });
|
|
1739
|
+
} else {
|
|
1740
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
1741
|
+
if (document.body) legacyObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class', 'hidden'] });
|
|
1742
|
+
}, { once: true });
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
function buildSemanticMap() {
|
|
1746
|
+
if (cachedMap) return cachedMap;
|
|
1747
|
+
|
|
1748
|
+
// Read from the live index maintained by the MutationObserver
|
|
1749
|
+
cachedMap = window.__wt_liveIndex.map(entry => ({
|
|
1750
|
+
id: entry.id,
|
|
1751
|
+
tag: entry.tag,
|
|
1752
|
+
role: entry.role,
|
|
1753
|
+
label: entry.label,
|
|
1754
|
+
placeholder: entry.placeholder,
|
|
1755
|
+
selector: entry.selector,
|
|
1756
|
+
x: entry.x,
|
|
1757
|
+
y: entry.y,
|
|
1758
|
+
viewport: entry.viewport,
|
|
1759
|
+
href: entry.href,
|
|
1760
|
+
coords: { x: entry.x, y: entry.y }
|
|
1761
|
+
}));
|
|
1762
|
+
|
|
1763
|
+
return cachedMap;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
// ── HUD ─────────────────────────────────────────────────────────
|
|
1767
|
+
const hudHistory = [];
|
|
1768
|
+
const hud = (() => {
|
|
1769
|
+
const el = document.createElement('div');
|
|
1770
|
+
el.id = 'Weavetab-hud';
|
|
1771
|
+
el.setAttribute('data-wt-ignore', '');
|
|
1772
|
+
Object.assign(el.style, {
|
|
1773
|
+
position: 'fixed', bottom: '24px', right: '24px',
|
|
1774
|
+
transform: 'translateY(8px)',
|
|
1775
|
+
color: '#a1a1aa',
|
|
1776
|
+
font: '11px/1.3 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
|
|
1777
|
+
borderRadius: '8px',
|
|
1778
|
+
opacity: '0', pointerEvents: 'none',
|
|
1779
|
+
zIndex: '2147483640',
|
|
1780
|
+
maxWidth: '400px',
|
|
1781
|
+
display: 'flex', flexDirection: 'column-reverse', gap: '4px',
|
|
1782
|
+
transition: 'opacity 0.2s, transform 0.2s',
|
|
1783
|
+
textAlign: 'right',
|
|
1784
|
+
});
|
|
1785
|
+
__wt_safeAppendToBody(el);
|
|
1786
|
+
return el;
|
|
1787
|
+
})();
|
|
1788
|
+
|
|
1789
|
+
function updateHUD(action, detail) {
|
|
1790
|
+
if (window.self !== window.top) return;
|
|
1791
|
+
// Tool call entries display strictly inside the Sidepanel TOOL CALLS section.
|
|
1792
|
+
// Suppress on-page bottom-right HUD popup overlay.
|
|
1793
|
+
if (hud) {
|
|
1794
|
+
hud.style.display = 'none';
|
|
1795
|
+
hud.style.opacity = '0';
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
// ── ACTIVE TAB INDICATOR ─────────────────────────────────────────
|
|
1800
|
+
class ControlledTabIndicator {
|
|
1801
|
+
constructor() {
|
|
1802
|
+
this._el = document.createElement('div');
|
|
1803
|
+
this._el.id = '__Weavetab_controlled_indicator';
|
|
1804
|
+
this._el.setAttribute('data-wt-ignore', '');
|
|
1805
|
+
Object.assign(this._el.style, {
|
|
1806
|
+
position: 'fixed',
|
|
1807
|
+
top: '0',
|
|
1808
|
+
left: '0',
|
|
1809
|
+
right: '0',
|
|
1810
|
+
height: '3px',
|
|
1811
|
+
zIndex: '2147483646',
|
|
1812
|
+
background: '#3B82F6',
|
|
1813
|
+
boxShadow: '0 0 12px #3B82F6, 0 0 4px #60a5fa',
|
|
1814
|
+
opacity: '0',
|
|
1815
|
+
transition: 'opacity 0.25s ease-in-out',
|
|
1816
|
+
pointerEvents: 'none',
|
|
1817
|
+
});
|
|
1818
|
+
// Animated shimmer
|
|
1819
|
+
this._shimmer = document.createElement('div');
|
|
1820
|
+
Object.assign(this._shimmer.style, {
|
|
1821
|
+
position: 'absolute',
|
|
1822
|
+
top: '0', left: '-60%', width: '60%', height: '100%',
|
|
1823
|
+
background: 'linear-gradient(90deg,transparent,rgba(255,255,255,0.45),transparent)',
|
|
1824
|
+
animation: 'wt-shimmer 1.6s infinite',
|
|
1825
|
+
});
|
|
1826
|
+
// Inject animation keyframes into shadow root
|
|
1827
|
+
var style = document.createElement('style');
|
|
1828
|
+
style.textContent = '@keyframes wt-shimmer{0%{left:-60%}100%{left:120%}}';
|
|
1829
|
+
try { getWeavetabRoot().appendChild(style); } catch(e) {}
|
|
1830
|
+
this._el.appendChild(this._shimmer);
|
|
1831
|
+
getWeavetabRoot().appendChild(this._el);
|
|
1832
|
+
this._hideTimer = null;
|
|
1833
|
+
this._persistent = false;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
// Show persistently (while agent is weaving)
|
|
1837
|
+
showPersistent(color) {
|
|
1838
|
+
this._persistent = true;
|
|
1839
|
+
clearTimeout(this._hideTimer);
|
|
1840
|
+
if (color) {
|
|
1841
|
+
this._el.style.background = color;
|
|
1842
|
+
this._el.style.boxShadow = '0 0 12px ' + color + ', 0 0 4px ' + color;
|
|
1843
|
+
}
|
|
1844
|
+
this._el.style.opacity = '1';
|
|
1845
|
+
this._shimmer.style.display = 'block';
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
// Show briefly (1.5s flash for single actions)
|
|
1849
|
+
show(color) {
|
|
1850
|
+
if (this._persistent) return; // Don't override persistent mode
|
|
1851
|
+
if (color) this._el.style.background = color;
|
|
1852
|
+
this._el.style.opacity = '1';
|
|
1853
|
+
clearTimeout(this._hideTimer);
|
|
1854
|
+
this._hideTimer = setTimeout(() => this.hide(), 1500);
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
hide() {
|
|
1858
|
+
this._persistent = false;
|
|
1859
|
+
this._el.style.opacity = '0';
|
|
1860
|
+
this._shimmer.style.display = 'none';
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
destroy() {
|
|
1864
|
+
clearTimeout(this._hideTimer);
|
|
1865
|
+
this._el.remove();
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
const controlledIndicator = new ControlledTabIndicator();
|
|
1870
|
+
|
|
1871
|
+
// ── MESSAGE BRIDGE ──────────────────────────────────────────────
|
|
1872
|
+
// Shared handler for both CustomEvent (MAIN world dispatch) and
|
|
1873
|
+
// postMessage (cross-world: MAIN → ISOLATED) communication channels.
|
|
1874
|
+
var __wt_lastCursor = { x: -9999, y: -9999 };
|
|
1875
|
+
var __wt_processedMsgIds = new Set();
|
|
1876
|
+
|
|
1877
|
+
function __wt_handleWeavetabMsg(msg) {
|
|
1878
|
+
if (!msg) return;
|
|
1879
|
+
if (Array.isArray(msg)) { msg.forEach(m => __wt_handleWeavetabMsg(m)); return; }
|
|
1880
|
+
if (msg._id) { if (__wt_processedMsgIds.has(msg._id)) return; __wt_processedMsgIds.add(msg._id); if (__wt_processedMsgIds.size > 100) __wt_processedMsgIds.clear(); }
|
|
1881
|
+
|
|
1882
|
+
if (msg.type === 'Weavetab_STATE') {
|
|
1883
|
+
if (msg.cursor) {
|
|
1884
|
+
__wt_lastCursor = { x: msg.cursor.x, y: msg.cursor.y };
|
|
1885
|
+
if (msg.cursor.waypoints && msg.cursor.duration_ms) {
|
|
1886
|
+
__weaveCursor.setWaypoints(msg.cursor.waypoints, msg.cursor.duration_ms);
|
|
1887
|
+
}
|
|
1888
|
+
moveCursor(msg.cursor.x, msg.cursor.y, msg.state);
|
|
1889
|
+
}
|
|
1890
|
+
else if (msg.state) { moveCursor(__wt_lastCursor.x, __wt_lastCursor.y, msg.state); }
|
|
1891
|
+
if (msg.ripple) ripple(msg.ripple.x, msg.ripple.y);
|
|
1892
|
+
|
|
1893
|
+
if (msg.userFeedback === true && msg.feedbackText) {
|
|
1894
|
+
__wt_feedbackSticky = true;
|
|
1895
|
+
clearTimeout(window.__wt_feedbackStickyTimer);
|
|
1896
|
+
window.__wt_feedbackStickyTimer = setTimeout(function(){ __wt_feedbackSticky = false; }, 6000);
|
|
1897
|
+
} else if (msg.userFeedback === false) {
|
|
1898
|
+
setTimeout(function(){ __wt_feedbackSticky = false; }, 2000);
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
if (msg.hud) {
|
|
1902
|
+
updateHUD(msg.hud.action, msg.hud.detail);
|
|
1903
|
+
var a = (msg.hud.action || '').trim();
|
|
1904
|
+
if (a === 'reading the page...') {
|
|
1905
|
+
__weaveCursor.setState('reading');
|
|
1906
|
+
} else if (a === 'Mission Running') {
|
|
1907
|
+
__wt_toolActive = true;
|
|
1908
|
+
__weaveCursor.setState('eval');
|
|
1909
|
+
} else if (a.startsWith('[OK]')) {
|
|
1910
|
+
__weaveCursor.setState('idle');
|
|
1911
|
+
__wt_toolActive = false;
|
|
1912
|
+
} else if (a.startsWith('[ERR]')) {
|
|
1913
|
+
__weaveCursor.setState('stuck');
|
|
1914
|
+
__wt_toolActive = false;
|
|
1915
|
+
setTimeout(function(){ __weaveCursor.setState('idle'); }, 3000);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
if (msg.cursor || msg.hud) {
|
|
1919
|
+
controlledIndicator.show('#3B82F6');
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
// Pure thought event from browser_thoughts tool only
|
|
1924
|
+
if (msg.type === 'WT_THOUGHT' || msg.type === 'Weavetab_THOUGHT') {
|
|
1925
|
+
__wt_showThought(msg.text, msg);
|
|
1926
|
+
}
|
|
1927
|
+
if (msg.type === 'WT_CLEAR_THOUGHT' || msg.type === 'Weavetab_CLEAR_THOUGHT') {
|
|
1928
|
+
__wt_removeThought();
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
// Handle plan updates via sendToExtension channel
|
|
1932
|
+
if (msg.type === 'Weavetab_STATE' && msg.plan) {
|
|
1933
|
+
const p = msg.plan;
|
|
1934
|
+
if (p.action === 'hide') {
|
|
1935
|
+
if (window.__wt_plan) window.__wt_plan('hide', '', []);
|
|
1936
|
+
} else {
|
|
1937
|
+
if (window.__wt_plan) window.__wt_plan(p.action || 'create', p.goal || '', p.tasks || []);
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
|
|
1942
|
+
if (msg.type === 'WT_USER_NOTE_INJECT') {
|
|
1943
|
+
window.__wt_user_notes = window.__wt_user_notes || [];
|
|
1944
|
+
window.__wt_user_notes.push({ text: msg.text, time: Date.now() });
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
if (msg.type === 'GET_SEMANTIC_MAP') {
|
|
1948
|
+
const map = window.__Weavetab_fullMap();
|
|
1949
|
+
if (window.__wt_user_notes && window.__wt_user_notes.length > 0) {
|
|
1950
|
+
// Keep notes for 2 minutes
|
|
1951
|
+
window.__wt_user_notes = window.__wt_user_notes.filter(n => Date.now() - n.time < 120000);
|
|
1952
|
+
const notesMap = window.__wt_user_notes.map((note, i) => ({
|
|
1953
|
+
id: `user-msg-${i}`,
|
|
1954
|
+
role: 'alert',
|
|
1955
|
+
tag: 'div',
|
|
1956
|
+
text: `URGENT MESSAGE FROM USER: "${note.text}"`,
|
|
1957
|
+
coords: [0, 0, 100, 100],
|
|
1958
|
+
interactive: false
|
|
1959
|
+
}));
|
|
1960
|
+
map.unshift(...notesMap);
|
|
1961
|
+
}
|
|
1962
|
+
activateXRay(map.filter(m => m.coords && m.text).slice(0, 12));
|
|
1963
|
+
window.dispatchEvent(new CustomEvent('Weavetab-msg-out', { detail: { id: msg.id, data: map } }));
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
if (msg.type === 'RESOLVE_EXTENSION_URL') {
|
|
1967
|
+
var url = '';
|
|
1968
|
+
try { url = browserAPI.runtime.getURL((msg.page || 'dashboard') + '.html'); } catch(e) {}
|
|
1969
|
+
window.dispatchEvent(new CustomEvent('Weavetab-msg-out', { detail: { id: msg.id, data: url } }));
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
if (msg.type === 'Weavetab_CONFIG') {
|
|
1973
|
+
window.__wt_devMode = !!msg.devMode;
|
|
1974
|
+
window.dispatchEvent(new CustomEvent('wt-config-sync'));
|
|
1975
|
+
__wt_safeSendMessage(msg);
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
if (msg.type === 'Weavetab_GROUP_STATE') {
|
|
1979
|
+
__wt_safeSendMessage(msg);
|
|
1980
|
+
// Only ever blue dot — no yellow. Idle state = no dot.
|
|
1981
|
+
const dotColor = (msg.state === 'idle') ? null : '#3B82F6';
|
|
1982
|
+
if (dotColor) {
|
|
1983
|
+
controlledIndicator.show(dotColor);
|
|
1984
|
+
}
|
|
1985
|
+
updateFaviconDot(msg.state, msg.isActive);
|
|
1986
|
+
// Parked tabs: clear thought bubble and reading glow
|
|
1987
|
+
if (msg.isActive === false) {
|
|
1988
|
+
__wt_removeThought();
|
|
1989
|
+
__wt_hideReadingGlow();
|
|
1990
|
+
}
|
|
1991
|
+
if (msg.state === 'idle') {
|
|
1992
|
+
__wt_hideReadingGlow();
|
|
1993
|
+
__wt_destroyBubble();
|
|
1994
|
+
__wt_feedbackSticky = false;
|
|
1995
|
+
__wt_processThoughtQueue();
|
|
1996
|
+
restoreOriginalFavicon();
|
|
1997
|
+
// Hide the persistent indicator bar when idle
|
|
1998
|
+
controlledIndicator.hide();
|
|
1999
|
+
__weaveCursor.setState('idle');
|
|
2000
|
+
} else if (msg.state === 'weaving') {
|
|
2001
|
+
// Show persistent blue bar while agent works
|
|
2002
|
+
controlledIndicator.showPersistent('#3B82F6');
|
|
2003
|
+
__weaveCursor.setState('eval');
|
|
2004
|
+
} else if (msg.state === 'stuck') {
|
|
2005
|
+
// Still show blue bar (not red) when stuck
|
|
2006
|
+
controlledIndicator.showPersistent('#3B82F6');
|
|
2007
|
+
__weaveCursor.setState('stuck');
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
|
|
2012
|
+
if (msg.type === 'Weavetab_THOUGHT') {
|
|
2013
|
+
__wt_showThought(msg.text, { active: msg.active });
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
if (msg.type === 'Weavetab_ASK') {
|
|
2017
|
+
// Legacy support — route to new unified question system
|
|
2018
|
+
console.log('[Weavetab] Received ASK request (legacy):', msg.question);
|
|
2019
|
+
__wt_showQuestion(msg.question, 'sentence', msg.timeout || 120);
|
|
2020
|
+
window.dispatchEvent(new CustomEvent('Weavetab-msg-out', { detail: { id: msg.id, data: { status: 'shown' } } }));
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
if (msg.type === 'Weavetab_CLEAR_THOUGHT') {
|
|
2024
|
+
__wt_removeThought();
|
|
2025
|
+
__wt_hideReadingGlow();
|
|
2026
|
+
__wt_processThoughtQueue();
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
if (msg.type === 'Weavetab_RELOAD_EXTENSION') {
|
|
2030
|
+
__wt_safeSendMessage({ type: 'RELOAD_EXTENSION' });
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
// Channel 1: CustomEvent from MAIN world (used by fallback + sendToExtension)
|
|
2035
|
+
window.addEventListener('Weavetab-msg-in', (e) => {
|
|
2036
|
+
__wt_handleWeavetabMsg(e.detail);
|
|
2037
|
+
});
|
|
2038
|
+
|
|
2039
|
+
// Channel 2: postMessage from MAIN world (cross-world MV3 bridge)
|
|
2040
|
+
window.addEventListener('message', (event) => {
|
|
2041
|
+
if (event.data && event.data.type === 'Weavetab-msg-in' && event.data.data) {
|
|
2042
|
+
__wt_handleWeavetabMsg(event.data.data);
|
|
2043
|
+
}
|
|
2044
|
+
// ── User feedback relay: WeaveTab OS panel → background storage queue ────
|
|
2045
|
+
// When the user types in the chat panel and hits Send, submitFeedback() fires
|
|
2046
|
+
// a __WT_FEEDBACK postMessage. We relay it to the background so it lands in
|
|
2047
|
+
// chrome.storage.local and window.__Weavetab.popUserFeedback() can return it.
|
|
2048
|
+
if (event.data && event.data.type === '__WT_FEEDBACK' && event.data.text) {
|
|
2049
|
+
try {
|
|
2050
|
+
if (browserAPI.runtime && browserAPI.runtime.id) {
|
|
2051
|
+
browserAPI.runtime.sendMessage({ type: 'USER_FEEDBACK', text: event.data.text }).catch(function(){});
|
|
2052
|
+
}
|
|
2053
|
+
} catch(e) {}
|
|
2054
|
+
}
|
|
2055
|
+
});
|
|
2056
|
+
|
|
2057
|
+
// ── FAVICON STATUS DOT INDICATOR ───────────────────────────────
|
|
2058
|
+
let wtFaviconObserver = null;
|
|
2059
|
+
|
|
2060
|
+
let __wt_faviconStaleTimer = null;
|
|
2061
|
+
|
|
2062
|
+
function updateFaviconDot(state, isActive) {
|
|
2063
|
+
if (__wt_faviconStaleTimer) { clearTimeout(__wt_faviconStaleTimer); __wt_faviconStaleTimer = null; }
|
|
2064
|
+
|
|
2065
|
+
if (state === 'idle' || !state || isActive === false) {
|
|
2066
|
+
if (wtFaviconObserver) {
|
|
2067
|
+
wtFaviconObserver.disconnect();
|
|
2068
|
+
wtFaviconObserver = null;
|
|
2069
|
+
}
|
|
2070
|
+
restoreOriginalFavicon();
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
// Always use blue — never red or yellow for favicon dot
|
|
2075
|
+
const color = '#3B82F6';
|
|
2076
|
+
|
|
2077
|
+
if (!wtFaviconObserver) {
|
|
2078
|
+
wtFaviconObserver = new MutationObserver(() => {
|
|
2079
|
+
wtFaviconObserver.disconnect();
|
|
2080
|
+
applyFaviconDot(color);
|
|
2081
|
+
wtFaviconObserver.observe(document.head, { childList: true, subtree: true });
|
|
2082
|
+
});
|
|
2083
|
+
} else {
|
|
2084
|
+
wtFaviconObserver.disconnect();
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
applyFaviconDot(color);
|
|
2088
|
+
wtFaviconObserver.observe(document.head, { childList: true, subtree: true });
|
|
2089
|
+
|
|
2090
|
+
// Set stale timer: clear dot + hide glow if no message for 30s
|
|
2091
|
+
__wt_faviconStaleTimer = setTimeout(function() {
|
|
2092
|
+
restoreOriginalFavicon();
|
|
2093
|
+
__wt_hideReadingGlow();
|
|
2094
|
+
__wt_destroyBubble();
|
|
2095
|
+
}, 30000);
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
function restoreOriginalFavicon() {
|
|
2099
|
+
const customLink = document.getElementById('wt-custom-favicon');
|
|
2100
|
+
if (customLink) customLink.remove();
|
|
2101
|
+
|
|
2102
|
+
const links = document.querySelectorAll('link[rel*="icon"]');
|
|
2103
|
+
links.forEach(link => {
|
|
2104
|
+
if (link.dataset.wtOrigHref) {
|
|
2105
|
+
link.href = link.dataset.wtOrigHref;
|
|
2106
|
+
delete link.dataset.wtOrigHref;
|
|
2107
|
+
}
|
|
2108
|
+
});
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
function applyFaviconDot(color) {
|
|
2112
|
+
const links = Array.from(document.querySelectorAll('link[rel*="icon"]')).filter(l => l.id !== 'wt-custom-favicon');
|
|
2113
|
+
let originalHref = '';
|
|
2114
|
+
|
|
2115
|
+
if (links.length > 0) {
|
|
2116
|
+
const link = links[0];
|
|
2117
|
+
if (!link.dataset.wtOrigHref) {
|
|
2118
|
+
link.dataset.wtOrigHref = link.href;
|
|
2119
|
+
}
|
|
2120
|
+
originalHref = link.dataset.wtOrigHref;
|
|
2121
|
+
} else {
|
|
2122
|
+
originalHref = '/favicon.ico';
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
const canvas = document.createElement('canvas');
|
|
2126
|
+
canvas.width = 32;
|
|
2127
|
+
canvas.height = 32;
|
|
2128
|
+
const ctx = canvas.getContext('2d');
|
|
2129
|
+
|
|
2130
|
+
const img = new Image();
|
|
2131
|
+
img.crossOrigin = 'anonymous';
|
|
2132
|
+
img.onload = () => {
|
|
2133
|
+
try {
|
|
2134
|
+
ctx.drawImage(img, 0, 0, 32, 32);
|
|
2135
|
+
drawDot(ctx, color);
|
|
2136
|
+
setFaviconHref(canvas.toDataURL('image/png'));
|
|
2137
|
+
} catch (e) {
|
|
2138
|
+
drawFallbackIcon(ctx, color);
|
|
2139
|
+
setFaviconHref(canvas.toDataURL('image/png'));
|
|
2140
|
+
}
|
|
2141
|
+
};
|
|
2142
|
+
img.onerror = () => {
|
|
2143
|
+
drawFallbackIcon(ctx, color);
|
|
2144
|
+
setFaviconHref(canvas.toDataURL('image/png'));
|
|
2145
|
+
};
|
|
2146
|
+
img.src = originalHref;
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
function drawDot(ctx, color) {
|
|
2150
|
+
// Solid dot
|
|
2151
|
+
ctx.beginPath();
|
|
2152
|
+
ctx.arc(24, 24, 5.5, 0, 2 * Math.PI);
|
|
2153
|
+
ctx.fillStyle = color;
|
|
2154
|
+
ctx.fill();
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
function drawFallbackIcon(ctx, color) {
|
|
2158
|
+
ctx.clearRect(0, 0, 32, 32);
|
|
2159
|
+
ctx.beginPath();
|
|
2160
|
+
ctx.arc(16, 16, 11, 0, 2 * Math.PI);
|
|
2161
|
+
ctx.fillStyle = '#1E1F22';
|
|
2162
|
+
ctx.fill();
|
|
2163
|
+
ctx.strokeStyle = '#3C3F45';
|
|
2164
|
+
ctx.lineWidth = 1.5;
|
|
2165
|
+
ctx.stroke();
|
|
2166
|
+
|
|
2167
|
+
ctx.beginPath();
|
|
2168
|
+
ctx.arc(16, 16, 6, 0, 2 * Math.PI);
|
|
2169
|
+
ctx.fillStyle = '#7C3AED';
|
|
2170
|
+
ctx.fill();
|
|
2171
|
+
|
|
2172
|
+
drawDot(ctx, color);
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
function setFaviconHref(dataUrl) {
|
|
2176
|
+
let customLink = document.getElementById('wt-custom-favicon');
|
|
2177
|
+
if (!customLink) {
|
|
2178
|
+
customLink = document.createElement('link');
|
|
2179
|
+
customLink.id = 'wt-custom-favicon';
|
|
2180
|
+
customLink.rel = 'icon';
|
|
2181
|
+
customLink.type = 'image/png';
|
|
2182
|
+
(document.head || document.documentElement).appendChild(customLink);
|
|
2183
|
+
}
|
|
2184
|
+
customLink.href = dataUrl;
|
|
2185
|
+
|
|
2186
|
+
const links = document.querySelectorAll('link[rel*="icon"]');
|
|
2187
|
+
links.forEach(link => {
|
|
2188
|
+
if (link.id !== 'wt-custom-favicon') {
|
|
2189
|
+
if (!link.dataset.wtOrigHref) {
|
|
2190
|
+
link.dataset.wtOrigHref = link.href;
|
|
2191
|
+
}
|
|
2192
|
+
link.href = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
|
2193
|
+
}
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
|
|
2198
|
+
|
|
2199
|
+
// ── SIDE PANEL REMOVED (Replaced by pure middle-down Thought HUD) ──
|
|
2200
|
+
|
|
2201
|
+
(function initAreaSnipeEngine() {
|
|
2202
|
+
let isSnipeActive = false;
|
|
2203
|
+
let currentSnipeMode = 'dom_target';
|
|
2204
|
+
let overlayEl = null;
|
|
2205
|
+
let marqueeBox = null;
|
|
2206
|
+
let sizeBadge = null;
|
|
2207
|
+
let headerPill = null;
|
|
2208
|
+
let startX = 0, startY = 0;
|
|
2209
|
+
let isDragging = false;
|
|
2210
|
+
|
|
2211
|
+
function createOverlay(mode) {
|
|
2212
|
+
if (overlayEl) overlayEl.remove();
|
|
2213
|
+
currentSnipeMode = mode || 'dom_target';
|
|
2214
|
+
|
|
2215
|
+
overlayEl = document.createElement('div');
|
|
2216
|
+
overlayEl.id = 'wt-snipe-overlay';
|
|
2217
|
+
overlayEl.setAttribute('data-wt-ignore', 'true');
|
|
2218
|
+
overlayEl.style.cssText = `
|
|
2219
|
+
position: fixed; inset: 0; z-index: 2147483647;
|
|
2220
|
+
background: rgba(0, 0, 0, 0.35); backdrop-filter: blur(2px);
|
|
2221
|
+
cursor: crosshair; user-select: none;
|
|
2222
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
2223
|
+
`;
|
|
2224
|
+
|
|
2225
|
+
headerPill = document.createElement('div');
|
|
2226
|
+
headerPill.style.cssText = `
|
|
2227
|
+
position: fixed; top: 16px; left: 50%; transform: translateX(-50%);
|
|
2228
|
+
background: #18181b; border: 1px solid #3f3f46; color: #f4f4f5;
|
|
2229
|
+
font-size: 11px; font-weight: 500; padding: 6px 16px; border-radius: 20px;
|
|
2230
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.6); pointer-events: none;
|
|
2231
|
+
display: flex; align-items: center; gap: 8px; z-index: 2147483647;
|
|
2232
|
+
`;
|
|
2233
|
+
const modeLabel = currentSnipeMode === 'visual_crop' ? 'Vision Crop' : 'DOM Target';
|
|
2234
|
+
headerPill.innerHTML = `<span style="display:inline-flex;align-items:center;gap:6px;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#38bdf8" stroke-width="2"><path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><rect x="7" y="7" width="10" height="10" rx="1"/></svg>Drag box to select area</span> <span style="color:#06b6d4;font-weight:600;">[${modeLabel}]</span> <span style="color:#71717a;">(Esc to cancel)</span>`;
|
|
2235
|
+
|
|
2236
|
+
// Marquee selection box matching img1 Gemini/YouTube Chrome UI style
|
|
2237
|
+
marqueeBox = document.createElement('div');
|
|
2238
|
+
marqueeBox.style.cssText = `
|
|
2239
|
+
position: fixed; border: 1px solid rgba(255, 255, 255, 0.45);
|
|
2240
|
+
border-radius: 12px;
|
|
2241
|
+
box-shadow: 0 0 24px 3px rgba(147, 197, 253, 0.45), inset 0 0 20px rgba(147, 197, 253, 0.2);
|
|
2242
|
+
pointer-events: auto; display: none; z-index: 2147483647;
|
|
2243
|
+
background: rgba(0, 0, 0, 0.1);
|
|
2244
|
+
`;
|
|
2245
|
+
|
|
2246
|
+
// Four thick white rounded corner reticles (matching img1)
|
|
2247
|
+
const corners = [
|
|
2248
|
+
'top: -2px; left: -2px; border-top: 4px solid #ffffff; border-left: 4px solid #ffffff; border-top-left-radius: 10px;',
|
|
2249
|
+
'top: -2px; right: -2px; border-top: 4px solid #ffffff; border-right: 4px solid #ffffff; border-top-right-radius: 10px;',
|
|
2250
|
+
'bottom: -2px; left: -2px; border-bottom: 4px solid #ffffff; border-left: 4px solid #ffffff; border-bottom-left-radius: 10px;',
|
|
2251
|
+
'bottom: -2px; right: -2px; border-bottom: 4px solid #ffffff; border-right: 4px solid #ffffff; border-bottom-right-radius: 10px;'
|
|
2252
|
+
];
|
|
2253
|
+
corners.forEach(styleCss => {
|
|
2254
|
+
const c = document.createElement('div');
|
|
2255
|
+
c.style.cssText = `position: absolute; width: 18px; height: 18px; pointer-events: none; ${styleCss}`;
|
|
2256
|
+
marqueeBox.appendChild(c);
|
|
2257
|
+
});
|
|
2258
|
+
|
|
2259
|
+
// Circular close X button on top-right corner
|
|
2260
|
+
const closeBtn = document.createElement('button');
|
|
2261
|
+
closeBtn.style.cssText = `
|
|
2262
|
+
position: absolute; top: -11px; right: -11px;
|
|
2263
|
+
width: 22px; height: 22px; border-radius: 50%;
|
|
2264
|
+
background: rgba(24, 24, 27, 0.95); border: 1px solid rgba(255, 255, 255, 0.25);
|
|
2265
|
+
color: #f4f4f5; cursor: pointer; display: flex; align-items: center; justify-content: center;
|
|
2266
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.6); z-index: 2147483647; transition: transform 0.15s, background 0.15s;
|
|
2267
|
+
`;
|
|
2268
|
+
closeBtn.innerHTML = `<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
|
|
2269
|
+
closeBtn.onclick = function(e) {
|
|
2270
|
+
e.stopPropagation();
|
|
2271
|
+
destroyOverlay();
|
|
2272
|
+
};
|
|
2273
|
+
marqueeBox.appendChild(closeBtn);
|
|
2274
|
+
|
|
2275
|
+
sizeBadge = document.createElement('div');
|
|
2276
|
+
sizeBadge.style.cssText = `
|
|
2277
|
+
position: absolute; bottom: -28px; left: 50%; transform: translateX(-50%);
|
|
2278
|
+
background: rgba(15, 23, 42, 0.9); backdrop-filter: blur(8px); color: #e2e8f0; font-weight: 600;
|
|
2279
|
+
font-size: 10px; font-family: -apple-system, BlinkMacSystemFont, sans-serif; padding: 2px 8px; border-radius: 12px;
|
|
2280
|
+
border: 1px solid rgba(255,255,255,0.15); box-shadow: 0 4px 12px rgba(0,0,0,0.5); pointer-events: none;
|
|
2281
|
+
white-space: nowrap;
|
|
2282
|
+
`;
|
|
2283
|
+
marqueeBox.appendChild(sizeBadge);
|
|
2284
|
+
|
|
2285
|
+
overlayEl.appendChild(headerPill);
|
|
2286
|
+
overlayEl.appendChild(marqueeBox);
|
|
2287
|
+
document.documentElement.appendChild(overlayEl);
|
|
2288
|
+
|
|
2289
|
+
isSnipeActive = true;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
function destroyOverlay() {
|
|
2293
|
+
if (overlayEl) {
|
|
2294
|
+
overlayEl.remove();
|
|
2295
|
+
overlayEl = null;
|
|
2296
|
+
}
|
|
2297
|
+
isSnipeActive = false;
|
|
2298
|
+
isDragging = false;
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
function handleMouseDown(e) {
|
|
2302
|
+
if (!isSnipeActive || e.button !== 0) return;
|
|
2303
|
+
e.preventDefault();
|
|
2304
|
+
e.stopPropagation();
|
|
2305
|
+
|
|
2306
|
+
isDragging = true;
|
|
2307
|
+
startX = e.clientX;
|
|
2308
|
+
startY = e.clientY;
|
|
2309
|
+
|
|
2310
|
+
marqueeBox.style.left = startX + 'px';
|
|
2311
|
+
marqueeBox.style.top = startY + 'px';
|
|
2312
|
+
marqueeBox.style.width = '0px';
|
|
2313
|
+
marqueeBox.style.height = '0px';
|
|
2314
|
+
marqueeBox.style.display = 'block';
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
function handleMouseMove(e) {
|
|
2318
|
+
if (!isSnipeActive || !isDragging) return;
|
|
2319
|
+
e.preventDefault();
|
|
2320
|
+
e.stopPropagation();
|
|
2321
|
+
|
|
2322
|
+
const currentX = e.clientX;
|
|
2323
|
+
const currentY = e.clientY;
|
|
2324
|
+
|
|
2325
|
+
const left = Math.min(startX, currentX);
|
|
2326
|
+
const top = Math.min(startY, currentY);
|
|
2327
|
+
const width = Math.abs(currentX - startX);
|
|
2328
|
+
const height = Math.abs(currentY - startY);
|
|
2329
|
+
|
|
2330
|
+
marqueeBox.style.left = left + 'px';
|
|
2331
|
+
marqueeBox.style.top = top + 'px';
|
|
2332
|
+
marqueeBox.style.width = width + 'px';
|
|
2333
|
+
marqueeBox.style.height = height + 'px';
|
|
2334
|
+
|
|
2335
|
+
sizeBadge.textContent = `${width} × ${height} px`;
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
function handleMouseUp(e) {
|
|
2339
|
+
if (!isSnipeActive || !isDragging) return;
|
|
2340
|
+
e.preventDefault();
|
|
2341
|
+
e.stopPropagation();
|
|
2342
|
+
|
|
2343
|
+
isDragging = false;
|
|
2344
|
+
const currentX = e.clientX;
|
|
2345
|
+
const currentY = e.clientY;
|
|
2346
|
+
|
|
2347
|
+
const left = Math.min(startX, currentX);
|
|
2348
|
+
const top = Math.min(startY, currentY);
|
|
2349
|
+
const width = Math.abs(currentX - startX);
|
|
2350
|
+
const height = Math.abs(currentY - startY);
|
|
2351
|
+
|
|
2352
|
+
if (width < 10 || height < 10) {
|
|
2353
|
+
destroyOverlay();
|
|
2354
|
+
return;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
if (currentSnipeMode === 'visual_crop') {
|
|
2358
|
+
captureVisualCrop(left, top, width, height);
|
|
2359
|
+
} else {
|
|
2360
|
+
captureDOMTargets(left, top, width, height);
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
destroyOverlay();
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
function handleKeyDown(e) {
|
|
2367
|
+
if (e.key === 'Escape' && isSnipeActive) {
|
|
2368
|
+
destroyOverlay();
|
|
2369
|
+
}
|
|
2370
|
+
if (e.altKey && (e.key === 's' || e.key === 'S')) {
|
|
2371
|
+
e.preventDefault();
|
|
2372
|
+
createOverlay('dom_target');
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
window.addEventListener('mousedown', handleMouseDown, true);
|
|
2377
|
+
window.addEventListener('mousemove', handleMouseMove, true);
|
|
2378
|
+
window.addEventListener('mouseup', handleMouseUp, true);
|
|
2379
|
+
window.addEventListener('keydown', handleKeyDown, true);
|
|
2380
|
+
|
|
2381
|
+
// ── Mode 1: DOM Element Intersector ──────────────────────────────────────
|
|
2382
|
+
function captureDOMTargets(left, top, width, height) {
|
|
2383
|
+
const right = left + width;
|
|
2384
|
+
const bottom = top + height;
|
|
2385
|
+
const elements = [];
|
|
2386
|
+
|
|
2387
|
+
const allEls = document.querySelectorAll('button, input, select, textarea, a, h1, h2, h3, h4, h5, h6, p, table, tr, td, th, div, span, [role]');
|
|
2388
|
+
allEls.forEach((el) => {
|
|
2389
|
+
if (el.hasAttribute('data-wt-ignore') || el.closest('#wt-snipe-overlay')) return;
|
|
2390
|
+
const r = el.getBoundingClientRect();
|
|
2391
|
+
if (r.width === 0 || r.height === 0) return;
|
|
2392
|
+
|
|
2393
|
+
const overlaps = !(r.right < left || r.left > right || r.bottom < top || r.top > bottom);
|
|
2394
|
+
if (overlaps) {
|
|
2395
|
+
const text = (el.innerText || el.value || el.getAttribute('aria-label') || '').trim().slice(0, 100);
|
|
2396
|
+
if (text || ['INPUT', 'BUTTON', 'A', 'SELECT', 'TEXTAREA'].includes(el.tagName)) {
|
|
2397
|
+
elements.push({
|
|
2398
|
+
tag: el.tagName.toLowerCase(),
|
|
2399
|
+
selector: getStableSelector(el),
|
|
2400
|
+
ref: el.getAttribute('data-wt-id') || null,
|
|
2401
|
+
text: text,
|
|
2402
|
+
role: el.getAttribute('role') || null,
|
|
2403
|
+
rect: { x: Math.round(r.left), y: Math.round(r.top), w: Math.round(r.width), h: Math.round(r.height) }
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
});
|
|
2408
|
+
|
|
2409
|
+
const uniqueElements = elements.slice(0, 15);
|
|
2410
|
+
|
|
2411
|
+
__wt_safeSendMessage({
|
|
2412
|
+
type: 'WT_SNIPE_COMPLETED',
|
|
2413
|
+
payload: {
|
|
2414
|
+
mode: 'dom_target',
|
|
2415
|
+
elements: uniqueElements,
|
|
2416
|
+
boundingRect: { x: left, y: top, width, height }
|
|
2417
|
+
}
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
// ── Mode 2: Visual WebP Crop ─────────────────────────────────────────────
|
|
2422
|
+
function captureVisualCrop(left, top, width, height) {
|
|
2423
|
+
try {
|
|
2424
|
+
const canvas = document.createElement('canvas');
|
|
2425
|
+
canvas.width = width;
|
|
2426
|
+
canvas.height = height;
|
|
2427
|
+
const ctx = canvas.getContext('2d');
|
|
2428
|
+
|
|
2429
|
+
const htmlContent = document.documentElement.outerHTML;
|
|
2430
|
+
const dataSvg = `
|
|
2431
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="${window.innerWidth}" height="${window.innerHeight}">
|
|
2432
|
+
<foreignObject width="100%" height="100%">
|
|
2433
|
+
<div xmlns="http://www.w3.org/1999/xhtml">
|
|
2434
|
+
${htmlContent}
|
|
2435
|
+
</div>
|
|
2436
|
+
</foreignObject>
|
|
2437
|
+
</svg>
|
|
2438
|
+
`;
|
|
2439
|
+
|
|
2440
|
+
const img = new Image();
|
|
2441
|
+
const blob = new Blob([dataSvg], { type: 'image/svg+xml;charset=utf-8' });
|
|
2442
|
+
const url = URL.createObjectURL(blob);
|
|
2443
|
+
|
|
2444
|
+
img.onload = function() {
|
|
2445
|
+
ctx.drawImage(img, left, top, width, height, 0, 0, width, height);
|
|
2446
|
+
URL.revokeObjectURL(url);
|
|
2447
|
+
const webpBase64 = canvas.toDataURL('image/webp', 0.75);
|
|
2448
|
+
|
|
2449
|
+
__wt_safeSendMessage({
|
|
2450
|
+
type: 'WT_SNIPE_COMPLETED',
|
|
2451
|
+
payload: {
|
|
2452
|
+
mode: 'visual_crop',
|
|
2453
|
+
cropImage: webpBase64,
|
|
2454
|
+
boundingRect: { x: left, y: top, width, height }
|
|
2455
|
+
}
|
|
2456
|
+
});
|
|
2457
|
+
};
|
|
2458
|
+
img.onerror = function() {
|
|
2459
|
+
captureDOMTargets(left, top, width, height);
|
|
2460
|
+
};
|
|
2461
|
+
img.src = url;
|
|
2462
|
+
} catch(e) {
|
|
2463
|
+
captureDOMTargets(left, top, width, height);
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
// Listen for snipe trigger from background
|
|
2468
|
+
browserAPI.runtime.onMessage.addListener((msg) => {
|
|
2469
|
+
if (msg.type === 'WT_START_SNIPE') {
|
|
2470
|
+
createOverlay(msg.mode || 'dom_target');
|
|
2471
|
+
}
|
|
2472
|
+
if (msg.type === 'WT_SNIPE_INJECT' && msg.entry) {
|
|
2473
|
+
const noteStr = typeof msg.entry === 'string' ? msg.entry : JSON.stringify(msg.entry);
|
|
2474
|
+
try {
|
|
2475
|
+
if (!window.__wt_chat_queue) window.__wt_chat_queue = [];
|
|
2476
|
+
window.__wt_chat_queue.push(noteStr);
|
|
2477
|
+
} catch(e) {}
|
|
2478
|
+
|
|
2479
|
+
try {
|
|
2480
|
+
const existing = JSON.parse(localStorage.getItem('__wt_chat_queue_persist') || '[]');
|
|
2481
|
+
existing.push(msg.entry);
|
|
2482
|
+
localStorage.setItem('__wt_chat_queue_persist', JSON.stringify(existing));
|
|
2483
|
+
} catch(e) {}
|
|
2484
|
+
|
|
2485
|
+
const bridge = document.getElementById('__wt_ask_bridge');
|
|
2486
|
+
if (bridge) {
|
|
2487
|
+
bridge.dataset.status = 'submitted';
|
|
2488
|
+
bridge.dataset.result = msg.entry.text || 'Annotation submitted';
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
});
|
|
2492
|
+
|
|
2493
|
+
})();
|
|
2494
|
+
|
|
2495
|
+
// ── ALERT / ACTION-REQUIRED TAB INDICATOR ─────────────────────
|
|
2496
|
+
(function initAlertDetector() {
|
|
2497
|
+
if (window.self !== window.top) return;
|
|
2498
|
+
// Expose on window so window.__Weavetab.getAlertState() can read it from CDP MAIN world
|
|
2499
|
+
window.__wt_alertPending = false;
|
|
2500
|
+
var _alert = window.alert;
|
|
2501
|
+
window.alert = function(msg) {
|
|
2502
|
+
window.__wt_alertPending = true;
|
|
2503
|
+
__wt_safeSendMessage({ type: 'TAB_ALERT_WAITING' });
|
|
2504
|
+
var r = _alert.apply(this, arguments);
|
|
2505
|
+
window.__wt_alertPending = false;
|
|
2506
|
+
__wt_safeSendMessage({ type: 'TAB_ALERT_CLEARED' });
|
|
2507
|
+
return r;
|
|
2508
|
+
};
|
|
2509
|
+
var _confirm = window.confirm;
|
|
2510
|
+
window.confirm = function(msg) {
|
|
2511
|
+
window.__wt_alertPending = true;
|
|
2512
|
+
__wt_safeSendMessage({ type: 'TAB_ALERT_WAITING' });
|
|
2513
|
+
var r = _confirm.apply(this, arguments);
|
|
2514
|
+
window.__wt_alertPending = false;
|
|
2515
|
+
__wt_safeSendMessage({ type: 'TAB_ALERT_CLEARED' });
|
|
2516
|
+
return r;
|
|
2517
|
+
};
|
|
2518
|
+
var _prompt = window.prompt;
|
|
2519
|
+
window.prompt = function(msg, def) {
|
|
2520
|
+
window.__wt_alertPending = true;
|
|
2521
|
+
__wt_safeSendMessage({ type: 'TAB_ALERT_WAITING' });
|
|
2522
|
+
var r = _prompt.apply(this, arguments);
|
|
2523
|
+
window.__wt_alertPending = false;
|
|
2524
|
+
__wt_safeSendMessage({ type: 'TAB_ALERT_CLEARED' });
|
|
2525
|
+
return r;
|
|
2526
|
+
};
|
|
2527
|
+
})();
|