browsertrack 0.2.0 → 0.2.2
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 +11 -6
- package/README.md +2 -0
- package/dist/{chunk-WB7ZKWK7.js → chunk-4HRLW6YF.js} +509 -109
- package/dist/chunk-4HRLW6YF.js.map +1 -0
- package/dist/{chunk-3HOXPTM2.js → chunk-AYSVE6NG.js} +808 -53
- package/dist/chunk-AYSVE6NG.js.map +1 -0
- package/dist/{chunk-6VA7GBAO.js → chunk-QRZ57ME3.js} +70 -2
- package/dist/chunk-QRZ57ME3.js.map +1 -0
- package/dist/{chunk-UP5JKCFY.js → chunk-TWEYRBDU.js} +281 -44
- package/dist/chunk-TWEYRBDU.js.map +1 -0
- package/dist/cli/index.js +1040 -546
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.cjs +536 -109
- package/dist/client/index.d.ts +36 -4
- package/dist/client/index.js +8 -4
- package/dist/client.iife.js +42 -19
- package/dist/{notes-BMnonq46.d.ts → commands-fjuqKzkm.d.ts} +125 -114
- package/dist/core/index.d.ts +26 -3
- package/dist/core/index.js +9 -1
- package/dist/daemon/index.d.ts +4 -4
- package/dist/daemon/index.js +6 -8
- package/dist/{engine-B43IohQY.d.ts → engine-CmchnMDq.d.ts} +2 -2
- package/dist/index.d.ts +5 -5
- package/dist/index.js +14 -7
- package/dist/mcp/index.d.ts +4 -4
- package/dist/mcp/index.js +7 -4
- package/dist/{projects-D5J-egVN.d.ts → projects-DB7S312i.d.ts} +1 -1
- package/dist/{server-BztYp1Zc.d.ts → server-DjV7RWQM.d.ts} +10 -2
- package/docs/cli.md +4 -1
- package/docs/component-resolver.md +108 -0
- package/docs/getting-started.md +60 -6
- package/docs/index.md +1 -0
- package/docs/mcp-reference.md +44 -2
- package/docs/visual-notes.md +36 -0
- package/package.json +1 -1
- package/packages/cli/src/index.ts +247 -151
- package/packages/client/src/client.ts +18 -1
- package/packages/client/src/config.ts +84 -1
- package/packages/client/src/index.ts +4 -1
- package/packages/client/src/interceptors/interaction.ts +3 -0
- package/packages/client/src/interceptors/navigation.ts +38 -26
- package/packages/client/src/interceptors/network.ts +22 -17
- package/packages/client/src/notes/inspector.ts +186 -51
- package/packages/client/src/source/resolver.ts +278 -0
- package/packages/client/src/transport/websocket.ts +23 -18
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/safety.ts +86 -0
- package/packages/core/src/types/events.ts +3 -0
- package/packages/core/src/types/notes.ts +11 -0
- package/packages/daemon/src/server/daemon.ts +7 -1
- package/packages/daemon/src/server/http.ts +125 -5
- package/packages/daemon/src/server/ws.ts +33 -29
- package/packages/daemon/src/storage/db.ts +57 -35
- package/packages/mcp/src/handlers.ts +115 -45
- package/packages/mcp/src/server.ts +202 -2
- package/test/client/component-resolver.test.ts +141 -0
- package/test/client/interceptors.test.ts +56 -0
- package/test/core/safety.test.ts +106 -0
- package/test/daemon/storage.test.ts +36 -0
- package/test/e2e/daemon-mcp-e2e.test.ts +10 -0
- package/test/mcp/auto-start.test.ts +87 -0
- package/dist/chunk-3HOXPTM2.js.map +0 -1
- package/dist/chunk-6VA7GBAO.js.map +0 -1
- package/dist/chunk-7OCOQGDN.js +0 -635
- package/dist/chunk-7OCOQGDN.js.map +0 -1
- package/dist/chunk-UP5JKCFY.js.map +0 -1
- package/dist/chunk-WB7ZKWK7.js.map +0 -1
|
@@ -7,6 +7,7 @@ export * from './breadcrumbs.js';
|
|
|
7
7
|
export * from './screenshot/driver.js';
|
|
8
8
|
export * from './screenshot/browser-script-driver.js';
|
|
9
9
|
export * from './notes/inspector.js';
|
|
10
|
+
export * from './source/resolver.js';
|
|
10
11
|
|
|
11
12
|
let defaultClient: BrowserTrackClient | null = null;
|
|
12
13
|
|
|
@@ -40,9 +41,11 @@ if (typeof window !== 'undefined') {
|
|
|
40
41
|
const autoInit = currentScript?.getAttribute('data-auto-init') !== 'false';
|
|
41
42
|
const daemonUrl = currentScript?.getAttribute('data-daemon-url') || undefined;
|
|
42
43
|
const projectId = currentScript?.getAttribute('data-project-id') || undefined;
|
|
44
|
+
const hidden = currentScript?.getAttribute('data-hidden') === 'true';
|
|
45
|
+
const hideQueryParam = currentScript?.getAttribute('data-hide-query-param') || undefined;
|
|
43
46
|
|
|
44
47
|
if (autoInit) {
|
|
45
|
-
init({ daemonUrl, projectId });
|
|
48
|
+
init({ daemonUrl, projectId, hidden, hideQueryParam });
|
|
46
49
|
}
|
|
47
50
|
} catch {
|
|
48
51
|
// If auto-start fails, don't crash
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Breadcrumb, ElementSummary } from '../../../core/src/index.js';
|
|
2
2
|
import { getSemanticSelector, truncate } from '../../../core/src/index.js';
|
|
3
|
+
import { resolveComponentSource } from '../source/resolver.js';
|
|
3
4
|
|
|
4
5
|
export type InteractionCallback = (breadcrumb: Breadcrumb, elementSummary?: ElementSummary) => void;
|
|
5
6
|
|
|
@@ -12,6 +13,7 @@ export function extractElementSummary(el: HTMLElement | Element): ElementSummary
|
|
|
12
13
|
const tag = el.tagName.toLowerCase();
|
|
13
14
|
const id = el.id || undefined;
|
|
14
15
|
const classes = Array.from(el.classList || []);
|
|
16
|
+
const componentSource = resolveComponentSource(el as HTMLElement);
|
|
15
17
|
|
|
16
18
|
let boundingRect: ElementSummary['boundingRect'] = undefined;
|
|
17
19
|
let visible = true;
|
|
@@ -53,6 +55,7 @@ export function extractElementSummary(el: HTMLElement | Element): ElementSummary
|
|
|
53
55
|
visible,
|
|
54
56
|
innerText,
|
|
55
57
|
outerHTML,
|
|
58
|
+
componentSource,
|
|
56
59
|
};
|
|
57
60
|
}
|
|
58
61
|
|
|
@@ -9,14 +9,18 @@ export type NavigationCallback = (event: NavigationEvent) => void;
|
|
|
9
9
|
export function setupNavigationInterceptors(onNavigation: NavigationCallback): () => void {
|
|
10
10
|
if (typeof window === 'undefined' || typeof history === 'undefined') return () => {};
|
|
11
11
|
|
|
12
|
-
let currentUrl =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
let currentUrl = '';
|
|
13
|
+
try {
|
|
14
|
+
currentUrl = redactUrl(window.location.href);
|
|
15
|
+
// Initial navigation
|
|
16
|
+
onNavigation({
|
|
17
|
+
to: currentUrl,
|
|
18
|
+
type: 'initial',
|
|
19
|
+
timestamp: Date.now(),
|
|
20
|
+
});
|
|
21
|
+
} catch {
|
|
22
|
+
// Defensive
|
|
23
|
+
}
|
|
20
24
|
|
|
21
25
|
const originalPushState = history.pushState;
|
|
22
26
|
const originalReplaceState = history.replaceState;
|
|
@@ -58,27 +62,35 @@ export function setupNavigationInterceptors(onNavigation: NavigationCallback): (
|
|
|
58
62
|
};
|
|
59
63
|
|
|
60
64
|
const onPopState = () => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
try {
|
|
66
|
+
const from = currentUrl;
|
|
67
|
+
const to = redactUrl(window.location.href);
|
|
68
|
+
currentUrl = to;
|
|
69
|
+
onNavigation({
|
|
70
|
+
from,
|
|
71
|
+
to,
|
|
72
|
+
type: 'popstate',
|
|
73
|
+
timestamp: Date.now(),
|
|
74
|
+
});
|
|
75
|
+
} catch {
|
|
76
|
+
// Defensive
|
|
77
|
+
}
|
|
70
78
|
};
|
|
71
79
|
|
|
72
80
|
const onHashChange = () => {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
try {
|
|
82
|
+
const from = currentUrl;
|
|
83
|
+
const to = redactUrl(window.location.href);
|
|
84
|
+
currentUrl = to;
|
|
85
|
+
onNavigation({
|
|
86
|
+
from,
|
|
87
|
+
to,
|
|
88
|
+
type: 'hashchange',
|
|
89
|
+
timestamp: Date.now(),
|
|
90
|
+
});
|
|
91
|
+
} catch {
|
|
92
|
+
// Defensive
|
|
93
|
+
}
|
|
82
94
|
};
|
|
83
95
|
|
|
84
96
|
window.addEventListener('popstate', onPopState);
|
|
@@ -37,35 +37,40 @@ export function setupNetworkInterceptors(onNetwork: NetworkCallback): () => void
|
|
|
37
37
|
|
|
38
38
|
const safeUrl = redactUrl(urlStr);
|
|
39
39
|
|
|
40
|
+
let response: Response;
|
|
40
41
|
try {
|
|
41
|
-
|
|
42
|
-
const durationMs = Date.now() - startTime;
|
|
43
|
-
|
|
44
|
-
onNetwork({
|
|
45
|
-
url: safeUrl,
|
|
46
|
-
method,
|
|
47
|
-
status: response.status,
|
|
48
|
-
statusText: response.statusText,
|
|
49
|
-
durationMs,
|
|
50
|
-
timestamp: startTime,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
return response;
|
|
42
|
+
response = await originalFetch.apply(window, [input as any, init]);
|
|
54
43
|
} catch (err: any) {
|
|
55
44
|
const durationMs = Date.now() - startTime;
|
|
56
45
|
const isAbort = err?.name === 'AbortError';
|
|
57
46
|
|
|
47
|
+
try {
|
|
48
|
+
onNetwork({
|
|
49
|
+
url: safeUrl,
|
|
50
|
+
method,
|
|
51
|
+
durationMs,
|
|
52
|
+
error: err?.message || 'Network request failed',
|
|
53
|
+
aborted: isAbort,
|
|
54
|
+
timestamp: startTime,
|
|
55
|
+
});
|
|
56
|
+
} catch {}
|
|
57
|
+
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const durationMs = Date.now() - startTime;
|
|
62
|
+
try {
|
|
58
63
|
onNetwork({
|
|
59
64
|
url: safeUrl,
|
|
60
65
|
method,
|
|
66
|
+
status: response.status,
|
|
67
|
+
statusText: response.statusText,
|
|
61
68
|
durationMs,
|
|
62
|
-
error: err?.message || 'Network request failed',
|
|
63
|
-
aborted: isAbort,
|
|
64
69
|
timestamp: startTime,
|
|
65
70
|
});
|
|
71
|
+
} catch {}
|
|
66
72
|
|
|
67
|
-
|
|
68
|
-
}
|
|
73
|
+
return response;
|
|
69
74
|
};
|
|
70
75
|
|
|
71
76
|
cleanups.push(() => {
|
|
@@ -2,6 +2,8 @@ import type { ElementContext, NoteTarget, RegionContext, VisualNote } from '../.
|
|
|
2
2
|
import { getSemanticSelector, truncate } from '../../../core/src/index.js';
|
|
3
3
|
import type { ScreenshotDriver } from '../screenshot/driver.js';
|
|
4
4
|
import type { WebSocketTransport } from '../transport/websocket.js';
|
|
5
|
+
import { shouldHideUIFromUrl } from '../config.js';
|
|
6
|
+
import { resolveComponentSource } from '../source/resolver.js';
|
|
5
7
|
|
|
6
8
|
export type NoteInspectMode = 'element' | 'region' | 'page' | 'idle';
|
|
7
9
|
|
|
@@ -9,6 +11,9 @@ export interface InspectorOptions {
|
|
|
9
11
|
shortcut?: string; // e.g. "Alt+Click"
|
|
10
12
|
maskSelectors?: string[];
|
|
11
13
|
showToolbar?: boolean;
|
|
14
|
+
showBadges?: boolean;
|
|
15
|
+
hidden?: boolean;
|
|
16
|
+
hideQueryParam?: string | string[];
|
|
12
17
|
onNoteCreated?: (note: Partial<VisualNote>) => void;
|
|
13
18
|
}
|
|
14
19
|
|
|
@@ -28,6 +33,7 @@ export interface ActiveScenarioState {
|
|
|
28
33
|
* 5. Real-time Saved Note & Step Markers (pins with step sequencing numbers)
|
|
29
34
|
* 6. Interactive Note & Step Detail Card (with previous/next step walk-through navigation)
|
|
30
35
|
* 7. Floating quick dock / toolbar in bottom-right corner with Notes count and Flow controls
|
|
36
|
+
* 8. Automatic invisibility via query parameter (?bt=0, ?bt=false, ?bt=hidden, ?no_bt, custom query params)
|
|
31
37
|
*/
|
|
32
38
|
export class NoteInspector {
|
|
33
39
|
private transport: WebSocketTransport;
|
|
@@ -58,15 +64,20 @@ export class NoteInspector {
|
|
|
58
64
|
|
|
59
65
|
private savedNotes: VisualNote[] = [];
|
|
60
66
|
private showMarkers = true;
|
|
67
|
+
private isHidden = false;
|
|
61
68
|
private cleanups: (() => void)[] = [];
|
|
62
69
|
|
|
63
70
|
constructor(transport: WebSocketTransport, screenshotDriver: ScreenshotDriver, options: InspectorOptions = {}) {
|
|
64
71
|
this.transport = transport;
|
|
65
72
|
this.screenshotDriver = screenshotDriver;
|
|
73
|
+
const hiddenByQuery = shouldHideUIFromUrl(options.hideQueryParam);
|
|
74
|
+
this.isHidden = options.hidden === true || hiddenByQuery;
|
|
75
|
+
this.showMarkers = options.showBadges !== false && !this.isHidden;
|
|
66
76
|
this.options = {
|
|
67
77
|
shortcut: 'Alt+Click',
|
|
68
78
|
maskSelectors: ['input[type="password"]', '[data-sensitive]'],
|
|
69
79
|
showToolbar: true,
|
|
80
|
+
showBadges: true,
|
|
70
81
|
...options,
|
|
71
82
|
};
|
|
72
83
|
}
|
|
@@ -158,6 +169,44 @@ export class NoteInspector {
|
|
|
158
169
|
}
|
|
159
170
|
}
|
|
160
171
|
|
|
172
|
+
public isVisible(): boolean {
|
|
173
|
+
return !this.isHidden;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
public setVisible(visible: boolean): void {
|
|
177
|
+
this.isHidden = !visible;
|
|
178
|
+
this.showMarkers = this.options.showBadges !== false && !this.isHidden;
|
|
179
|
+
|
|
180
|
+
if (this.container) {
|
|
181
|
+
this.container.style.display = this.isHidden ? 'none' : 'block';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (this.toolbarElement) {
|
|
185
|
+
this.toolbarElement.style.display = this.isHidden ? 'none' : 'flex';
|
|
186
|
+
} else if (!this.isHidden && this.options.showToolbar !== false) {
|
|
187
|
+
this.createToolbar();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (this.isHidden) {
|
|
191
|
+
this.hideHighlight();
|
|
192
|
+
this.hideRegionOverlay();
|
|
193
|
+
if (this.modalOverlay && this.shadowRoot) {
|
|
194
|
+
this.shadowRoot.removeChild(this.modalOverlay);
|
|
195
|
+
this.modalOverlay = null;
|
|
196
|
+
}
|
|
197
|
+
if (this.cardOverlay && this.shadowRoot) {
|
|
198
|
+
this.shadowRoot.removeChild(this.cardOverlay);
|
|
199
|
+
this.cardOverlay = null;
|
|
200
|
+
}
|
|
201
|
+
if (this.markersContainer) {
|
|
202
|
+
this.markersContainer.innerHTML = '';
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
this.renderMarkers();
|
|
206
|
+
this.updateToolbarCount();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
161
210
|
private ensureContainer(): ShadowRoot | null {
|
|
162
211
|
if (typeof document === 'undefined') return null;
|
|
163
212
|
|
|
@@ -166,6 +215,9 @@ export class NoteInspector {
|
|
|
166
215
|
this.container.id = 'browsertrack-inspector-host';
|
|
167
216
|
this.container.style.cssText =
|
|
168
217
|
'all: initial; position: fixed; top: 0; left: 0; width: 0; height: 0; z-index: 2147483647; pointer-events: none;';
|
|
218
|
+
if (this.isHidden) {
|
|
219
|
+
this.container.style.display = 'none';
|
|
220
|
+
}
|
|
169
221
|
|
|
170
222
|
this.shadowRoot = this.container.attachShadow({ mode: 'open' });
|
|
171
223
|
|
|
@@ -611,6 +663,22 @@ export class NoteInspector {
|
|
|
611
663
|
color: #cbd5e1;
|
|
612
664
|
}
|
|
613
665
|
|
|
666
|
+
.bt-component-pill {
|
|
667
|
+
display: flex;
|
|
668
|
+
align-items: center;
|
|
669
|
+
gap: 6px;
|
|
670
|
+
background: rgba(15, 23, 42, 0.95);
|
|
671
|
+
border: 1px solid rgba(99, 102, 241, 0.35);
|
|
672
|
+
border-radius: 6px;
|
|
673
|
+
padding: 6px 10px;
|
|
674
|
+
font-size: 11.5px;
|
|
675
|
+
color: #c7d2fe;
|
|
676
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
677
|
+
overflow: hidden;
|
|
678
|
+
text-overflow: ellipsis;
|
|
679
|
+
white-space: nowrap;
|
|
680
|
+
}
|
|
681
|
+
|
|
614
682
|
.bt-scenario-stepper {
|
|
615
683
|
display: flex;
|
|
616
684
|
align-items: center;
|
|
@@ -859,7 +927,7 @@ export class NoteInspector {
|
|
|
859
927
|
this.shadowRoot.appendChild(this.toastContainer);
|
|
860
928
|
|
|
861
929
|
// Floating Toolbar
|
|
862
|
-
if (this.options.showToolbar) {
|
|
930
|
+
if (this.options.showToolbar && !this.isHidden) {
|
|
863
931
|
this.createToolbar();
|
|
864
932
|
}
|
|
865
933
|
}
|
|
@@ -978,13 +1046,20 @@ export class NoteInspector {
|
|
|
978
1046
|
if (!root || !this.markersContainer) return;
|
|
979
1047
|
|
|
980
1048
|
this.markersContainer.innerHTML = '';
|
|
981
|
-
if (!this.showMarkers) return;
|
|
1049
|
+
if (!this.showMarkers || this.isHidden) return;
|
|
982
1050
|
|
|
983
1051
|
const currentPath = window.location.pathname;
|
|
984
1052
|
// Filter open notes on this route or global
|
|
985
|
-
const activeNotes = this.savedNotes.filter(
|
|
986
|
-
(n
|
|
987
|
-
|
|
1053
|
+
const activeNotes = this.savedNotes.filter((n) => {
|
|
1054
|
+
if (n.status !== 'OPEN') return false;
|
|
1055
|
+
if (!n.route) return true;
|
|
1056
|
+
if (n.route === currentPath) return true;
|
|
1057
|
+
// Support hash or query routes (e.g. /#modal or /?tab=settings)
|
|
1058
|
+
if (n.route.includes('#') || n.route.includes('?')) {
|
|
1059
|
+
return window.location.href.includes(n.route);
|
|
1060
|
+
}
|
|
1061
|
+
return false;
|
|
1062
|
+
});
|
|
988
1063
|
|
|
989
1064
|
const pageNotes: VisualNote[] = [];
|
|
990
1065
|
|
|
@@ -1143,6 +1218,32 @@ export class NoteInspector {
|
|
|
1143
1218
|
|
|
1144
1219
|
${scenarioStepsHtml}
|
|
1145
1220
|
|
|
1221
|
+
${
|
|
1222
|
+
note.elementContext?.componentSource?.componentName
|
|
1223
|
+
? `<div class="bt-component-pill" title="${
|
|
1224
|
+
note.elementContext.componentSource.sourceFile
|
|
1225
|
+
? note.elementContext.componentSource.sourceFile +
|
|
1226
|
+
(note.elementContext.componentSource.sourceLine ? ':' + note.elementContext.componentSource.sourceLine : '')
|
|
1227
|
+
: note.elementContext.componentSource.componentName
|
|
1228
|
+
}">
|
|
1229
|
+
<span>🧬</span>
|
|
1230
|
+
<span style="font-weight: 600; color: #a5b4fc;"><${note.elementContext.componentSource.componentName}></span>
|
|
1231
|
+
${
|
|
1232
|
+
note.elementContext.componentSource.sourceFile
|
|
1233
|
+
? `<span style="color: #64748b; font-size: 11px; margin-left: 4px;">${note.elementContext.componentSource.sourceFile}${
|
|
1234
|
+
note.elementContext.componentSource.sourceLine ? ':' + note.elementContext.componentSource.sourceLine : ''
|
|
1235
|
+
}</span>`
|
|
1236
|
+
: ''
|
|
1237
|
+
}
|
|
1238
|
+
${
|
|
1239
|
+
note.elementContext.componentSource.framework
|
|
1240
|
+
? `<span class="bt-mode-badge" style="background: rgba(99, 102, 241, 0.15); color: #818cf8; margin-left: auto; font-size: 10px;">${note.elementContext.componentSource.framework.toUpperCase()}</span>`
|
|
1241
|
+
: ''
|
|
1242
|
+
}
|
|
1243
|
+
</div>`
|
|
1244
|
+
: ''
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1146
1247
|
<div class="bt-target-pill" title="${contextText}">
|
|
1147
1248
|
<span>🏷️</span>
|
|
1148
1249
|
<span class="bt-pill-content">${contextText}</span>
|
|
@@ -1249,81 +1350,103 @@ export class NoteInspector {
|
|
|
1249
1350
|
private setupListeners(): void {
|
|
1250
1351
|
// 1. Mouse move: hover highlight in element mode or with Alt key
|
|
1251
1352
|
const onMouseMove = (e: MouseEvent) => {
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
// Don't highlight elements if mouse is over our own inspector UI
|
|
1255
|
-
const path = e.composedPath ? e.composedPath() : [];
|
|
1256
|
-
if (this.container && path.includes(this.container)) {
|
|
1257
|
-
this.hideHighlight();
|
|
1258
|
-
return;
|
|
1259
|
-
}
|
|
1353
|
+
try {
|
|
1354
|
+
if (this.isHidden || this.modalOverlay || this.cardOverlay || this.isDraggingRegion || this.activeMode === 'region') return;
|
|
1260
1355
|
|
|
1261
|
-
|
|
1262
|
-
const
|
|
1263
|
-
if (
|
|
1264
|
-
this.
|
|
1265
|
-
this.updateHighlight(target);
|
|
1356
|
+
// Don't highlight elements if mouse is over our own inspector UI
|
|
1357
|
+
const path = e.composedPath ? e.composedPath() : [];
|
|
1358
|
+
if (this.container && path.includes(this.container)) {
|
|
1359
|
+
this.hideHighlight();
|
|
1266
1360
|
return;
|
|
1267
1361
|
}
|
|
1268
|
-
}
|
|
1269
1362
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1363
|
+
if (e.altKey || this.activeMode === 'element') {
|
|
1364
|
+
const target = document.elementFromPoint(e.clientX, e.clientY) as HTMLElement | null;
|
|
1365
|
+
if (target && target !== this.container && !this.container?.contains(target)) {
|
|
1366
|
+
this.hoveredElement = target;
|
|
1367
|
+
this.updateHighlight(target);
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
if (!e.altKey && this.activeMode !== 'element') {
|
|
1373
|
+
this.hideHighlight();
|
|
1374
|
+
}
|
|
1375
|
+
} catch {
|
|
1376
|
+
// Defensive: never let inspector hover crash host app
|
|
1272
1377
|
}
|
|
1273
1378
|
};
|
|
1274
1379
|
|
|
1275
1380
|
// 2. Click: Alt+Click or Element mode click selects element and opens editor
|
|
1276
1381
|
const onClick = (e: MouseEvent) => {
|
|
1277
|
-
|
|
1382
|
+
try {
|
|
1383
|
+
if (this.isHidden || this.modalOverlay || this.cardOverlay) return;
|
|
1278
1384
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1385
|
+
// If clicking inside inspector toolbar or container, do NOT intercept
|
|
1386
|
+
const path = e.composedPath ? e.composedPath() : [];
|
|
1387
|
+
if (this.container && path.includes(this.container)) {
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1284
1390
|
|
|
1285
|
-
|
|
1391
|
+
if (this.activeMode === 'region') return;
|
|
1286
1392
|
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1393
|
+
if (e.altKey || this.activeMode === 'element') {
|
|
1394
|
+
e.preventDefault();
|
|
1395
|
+
e.stopPropagation();
|
|
1290
1396
|
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1397
|
+
const target = (this.hoveredElement || document.elementFromPoint(e.clientX, e.clientY)) as HTMLElement | null;
|
|
1398
|
+
if (target && target !== this.container && !this.container?.contains(target)) {
|
|
1399
|
+
this.selectedElement = target;
|
|
1400
|
+
this.openNoteEditor(target, 'element');
|
|
1401
|
+
if (this.activeMode === 'element' && !this.activeScenario) {
|
|
1402
|
+
this.setMode('idle');
|
|
1403
|
+
}
|
|
1297
1404
|
}
|
|
1298
1405
|
}
|
|
1406
|
+
} catch {
|
|
1407
|
+
// Defensive
|
|
1299
1408
|
}
|
|
1300
1409
|
};
|
|
1301
1410
|
|
|
1302
1411
|
// 3. Key handling: Escape cancels active modes, Alt release hides highlight
|
|
1303
1412
|
const onKeyDown = (e: KeyboardEvent) => {
|
|
1304
|
-
|
|
1305
|
-
if (
|
|
1306
|
-
this.
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
if (
|
|
1310
|
-
this.
|
|
1413
|
+
try {
|
|
1414
|
+
if (e.key === 'Escape') {
|
|
1415
|
+
if (this.cardOverlay && this.cardOverlay.parentElement && this.shadowRoot) {
|
|
1416
|
+
this.shadowRoot.removeChild(this.cardOverlay);
|
|
1417
|
+
this.cardOverlay = null;
|
|
1418
|
+
} else if (this.activeMode === 'region' || this.activeMode === 'element') {
|
|
1419
|
+
if (!this.activeScenario) {
|
|
1420
|
+
this.setMode('idle');
|
|
1421
|
+
}
|
|
1311
1422
|
}
|
|
1312
1423
|
}
|
|
1424
|
+
} catch {
|
|
1425
|
+
// Defensive
|
|
1313
1426
|
}
|
|
1314
1427
|
};
|
|
1315
1428
|
|
|
1316
1429
|
const onKeyUp = (e: KeyboardEvent) => {
|
|
1317
|
-
|
|
1318
|
-
this.
|
|
1430
|
+
try {
|
|
1431
|
+
if (e.key === 'Alt' && !this.modalOverlay && !this.cardOverlay && this.activeMode !== 'element') {
|
|
1432
|
+
this.hideHighlight();
|
|
1433
|
+
}
|
|
1434
|
+
} catch {
|
|
1435
|
+
// Defensive
|
|
1319
1436
|
}
|
|
1320
1437
|
};
|
|
1321
1438
|
|
|
1322
1439
|
// 4. Scroll & resize: reposition markers accurately
|
|
1323
1440
|
const onScrollOrResize = () => {
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1441
|
+
try {
|
|
1442
|
+
requestAnimationFrame(() => {
|
|
1443
|
+
try {
|
|
1444
|
+
this.updateMarkerPositions();
|
|
1445
|
+
} catch {}
|
|
1446
|
+
});
|
|
1447
|
+
} catch {
|
|
1448
|
+
// Defensive
|
|
1449
|
+
}
|
|
1327
1450
|
};
|
|
1328
1451
|
|
|
1329
1452
|
window.addEventListener('mousemove', onMouseMove, { capture: true, passive: true });
|
|
@@ -1505,6 +1628,11 @@ export class NoteInspector {
|
|
|
1505
1628
|
this.updateHighlight(targetEl);
|
|
1506
1629
|
}
|
|
1507
1630
|
|
|
1631
|
+
const comp = noteType === 'element' ? resolveComponentSource(targetEl) : undefined;
|
|
1632
|
+
const compPrefix = comp?.componentName
|
|
1633
|
+
? `🧬 <${comp.componentName}>${comp.sourceFile ? ' (' + comp.sourceFile + (comp.sourceLine ? ':' + comp.sourceLine : '') + ')' : ''} · `
|
|
1634
|
+
: '';
|
|
1635
|
+
|
|
1508
1636
|
this.renderNoteModal({
|
|
1509
1637
|
title: this.activeScenario
|
|
1510
1638
|
? `Step ${this.activeScenario.stepNumber}: ${this.activeScenario.title}`
|
|
@@ -1513,7 +1641,7 @@ export class NoteInspector {
|
|
|
1513
1641
|
pillText:
|
|
1514
1642
|
noteType === 'page'
|
|
1515
1643
|
? `Page Viewport: ${window.innerWidth} × ${window.innerHeight} px`
|
|
1516
|
-
: `${getSemanticSelector(targetEl)} (${Math.round(targetEl.getBoundingClientRect().width)}×${Math.round(targetEl.getBoundingClientRect().height)}) · Viewport: ${window.innerWidth}×${window.innerHeight}`,
|
|
1644
|
+
: `${compPrefix}${getSemanticSelector(targetEl)} (${Math.round(targetEl.getBoundingClientRect().width)}×${Math.round(targetEl.getBoundingClientRect().height)}) · Viewport: ${window.innerWidth}×${window.innerHeight}`,
|
|
1517
1645
|
onSave: async (message, action) => {
|
|
1518
1646
|
let scenarioParam: { scenarioId?: string; stepNumber?: number; scenarioTitle?: string } | undefined;
|
|
1519
1647
|
|
|
@@ -1863,12 +1991,15 @@ export class NoteInspector {
|
|
|
1863
1991
|
innerText = truncate(innerText, 200);
|
|
1864
1992
|
}
|
|
1865
1993
|
|
|
1994
|
+
const componentSource = resolveComponentSource(el);
|
|
1995
|
+
|
|
1866
1996
|
return {
|
|
1867
1997
|
selector,
|
|
1868
1998
|
tag: el.tagName.toLowerCase(),
|
|
1869
1999
|
attributes,
|
|
1870
2000
|
outerHTML,
|
|
1871
2001
|
innerText,
|
|
2002
|
+
componentSource,
|
|
1872
2003
|
parent: el.parentElement
|
|
1873
2004
|
? {
|
|
1874
2005
|
selector: getSemanticSelector(el.parentElement),
|
|
@@ -1903,7 +2034,11 @@ export class NoteInspector {
|
|
|
1903
2034
|
region.width,
|
|
1904
2035
|
region.height
|
|
1905
2036
|
);
|
|
1906
|
-
|
|
2037
|
+
try {
|
|
2038
|
+
resolve(canvas.toDataURL('image/png'));
|
|
2039
|
+
} catch {
|
|
2040
|
+
resolve(dataUrl);
|
|
2041
|
+
}
|
|
1907
2042
|
};
|
|
1908
2043
|
img.onerror = () => resolve(dataUrl);
|
|
1909
2044
|
img.src = dataUrl;
|