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.
Files changed (67) hide show
  1. package/AGENTS.md +11 -6
  2. package/README.md +2 -0
  3. package/dist/{chunk-WB7ZKWK7.js → chunk-4HRLW6YF.js} +509 -109
  4. package/dist/chunk-4HRLW6YF.js.map +1 -0
  5. package/dist/{chunk-3HOXPTM2.js → chunk-AYSVE6NG.js} +808 -53
  6. package/dist/chunk-AYSVE6NG.js.map +1 -0
  7. package/dist/{chunk-6VA7GBAO.js → chunk-QRZ57ME3.js} +70 -2
  8. package/dist/chunk-QRZ57ME3.js.map +1 -0
  9. package/dist/{chunk-UP5JKCFY.js → chunk-TWEYRBDU.js} +281 -44
  10. package/dist/chunk-TWEYRBDU.js.map +1 -0
  11. package/dist/cli/index.js +1040 -546
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/client/index.cjs +536 -109
  14. package/dist/client/index.d.ts +36 -4
  15. package/dist/client/index.js +8 -4
  16. package/dist/client.iife.js +42 -19
  17. package/dist/{notes-BMnonq46.d.ts → commands-fjuqKzkm.d.ts} +125 -114
  18. package/dist/core/index.d.ts +26 -3
  19. package/dist/core/index.js +9 -1
  20. package/dist/daemon/index.d.ts +4 -4
  21. package/dist/daemon/index.js +6 -8
  22. package/dist/{engine-B43IohQY.d.ts → engine-CmchnMDq.d.ts} +2 -2
  23. package/dist/index.d.ts +5 -5
  24. package/dist/index.js +14 -7
  25. package/dist/mcp/index.d.ts +4 -4
  26. package/dist/mcp/index.js +7 -4
  27. package/dist/{projects-D5J-egVN.d.ts → projects-DB7S312i.d.ts} +1 -1
  28. package/dist/{server-BztYp1Zc.d.ts → server-DjV7RWQM.d.ts} +10 -2
  29. package/docs/cli.md +4 -1
  30. package/docs/component-resolver.md +108 -0
  31. package/docs/getting-started.md +60 -6
  32. package/docs/index.md +1 -0
  33. package/docs/mcp-reference.md +44 -2
  34. package/docs/visual-notes.md +36 -0
  35. package/package.json +1 -1
  36. package/packages/cli/src/index.ts +247 -151
  37. package/packages/client/src/client.ts +18 -1
  38. package/packages/client/src/config.ts +84 -1
  39. package/packages/client/src/index.ts +4 -1
  40. package/packages/client/src/interceptors/interaction.ts +3 -0
  41. package/packages/client/src/interceptors/navigation.ts +38 -26
  42. package/packages/client/src/interceptors/network.ts +22 -17
  43. package/packages/client/src/notes/inspector.ts +186 -51
  44. package/packages/client/src/source/resolver.ts +278 -0
  45. package/packages/client/src/transport/websocket.ts +23 -18
  46. package/packages/core/src/index.ts +1 -0
  47. package/packages/core/src/safety.ts +86 -0
  48. package/packages/core/src/types/events.ts +3 -0
  49. package/packages/core/src/types/notes.ts +11 -0
  50. package/packages/daemon/src/server/daemon.ts +7 -1
  51. package/packages/daemon/src/server/http.ts +125 -5
  52. package/packages/daemon/src/server/ws.ts +33 -29
  53. package/packages/daemon/src/storage/db.ts +57 -35
  54. package/packages/mcp/src/handlers.ts +115 -45
  55. package/packages/mcp/src/server.ts +202 -2
  56. package/test/client/component-resolver.test.ts +141 -0
  57. package/test/client/interceptors.test.ts +56 -0
  58. package/test/core/safety.test.ts +106 -0
  59. package/test/daemon/storage.test.ts +36 -0
  60. package/test/e2e/daemon-mcp-e2e.test.ts +10 -0
  61. package/test/mcp/auto-start.test.ts +87 -0
  62. package/dist/chunk-3HOXPTM2.js.map +0 -1
  63. package/dist/chunk-6VA7GBAO.js.map +0 -1
  64. package/dist/chunk-7OCOQGDN.js +0 -635
  65. package/dist/chunk-7OCOQGDN.js.map +0 -1
  66. package/dist/chunk-UP5JKCFY.js.map +0 -1
  67. package/dist/chunk-WB7ZKWK7.js.map +0 -1
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  getSemanticSelector,
3
3
  redactUrl,
4
+ safeJsonStringify,
4
5
  truncate
5
- } from "./chunk-6VA7GBAO.js";
6
+ } from "./chunk-QRZ57ME3.js";
6
7
 
7
8
  // packages/client/src/breadcrumbs.ts
8
9
  var BreadcrumbBuffer = class {
@@ -29,12 +30,226 @@ var BreadcrumbBuffer = class {
29
30
  }
30
31
  };
31
32
 
33
+ // packages/client/src/source/resolver.ts
34
+ function resolveComponentSource(el) {
35
+ if (!el || typeof el !== "object") return void 0;
36
+ const reactInfo = resolveReactComponent(el);
37
+ if (reactInfo) return reactInfo;
38
+ const vueInfo = resolveVueComponent(el);
39
+ if (vueInfo) return vueInfo;
40
+ const svelteInfo = resolveSvelteComponent(el);
41
+ if (svelteInfo) return svelteInfo;
42
+ if (el.tagName && el.tagName.includes("-")) {
43
+ return {
44
+ framework: "web-component",
45
+ componentName: el.tagName.toLowerCase(),
46
+ hierarchy: [el.tagName.toLowerCase()]
47
+ };
48
+ }
49
+ const attrInfo = resolveDataAttributeComponent(el);
50
+ if (attrInfo) return attrInfo;
51
+ return void 0;
52
+ }
53
+ function resolveReactComponent(el) {
54
+ try {
55
+ const fiberKey = Object.keys(el).find(
56
+ (key) => key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$")
57
+ );
58
+ if (!fiberKey) return void 0;
59
+ const hostFiber = el[fiberKey];
60
+ if (!hostFiber) return void 0;
61
+ let sourceFile;
62
+ let sourceLine;
63
+ let sourceColumn;
64
+ let componentName;
65
+ const hierarchy = [];
66
+ let props;
67
+ if (hostFiber._debugSource) {
68
+ sourceFile = hostFiber._debugSource.fileName;
69
+ sourceLine = hostFiber._debugSource.lineNumber;
70
+ sourceColumn = hostFiber._debugSource.columnNumber;
71
+ }
72
+ if (hostFiber._debugOwner) {
73
+ const ownerType = hostFiber._debugOwner.type;
74
+ componentName = getReactComponentName(ownerType);
75
+ if (!sourceFile && hostFiber._debugOwner._debugSource) {
76
+ sourceFile = hostFiber._debugOwner._debugSource.fileName;
77
+ sourceLine = hostFiber._debugOwner._debugSource.lineNumber;
78
+ sourceColumn = hostFiber._debugOwner._debugSource.columnNumber;
79
+ }
80
+ }
81
+ let curr = hostFiber;
82
+ let depth = 0;
83
+ while (curr && depth < 50) {
84
+ depth++;
85
+ const type = curr.type;
86
+ const name = getReactComponentName(type);
87
+ if (name && !name.startsWith("html:") && name !== "Fragment") {
88
+ if (!componentName) {
89
+ componentName = name;
90
+ }
91
+ if (!hierarchy.includes(name)) {
92
+ hierarchy.unshift(name);
93
+ }
94
+ if (!sourceFile && curr._debugSource) {
95
+ sourceFile = curr._debugSource.fileName;
96
+ sourceLine = curr._debugSource.lineNumber;
97
+ sourceColumn = curr._debugSource.columnNumber;
98
+ }
99
+ if (!props && curr.memoizedProps && typeof curr.memoizedProps === "object") {
100
+ props = sanitizeProps(curr.memoizedProps);
101
+ }
102
+ }
103
+ curr = curr.return;
104
+ }
105
+ if (!componentName && !sourceFile) {
106
+ return void 0;
107
+ }
108
+ return {
109
+ framework: "react",
110
+ componentName,
111
+ sourceFile: normalizeFilePath(sourceFile),
112
+ sourceLine,
113
+ sourceColumn,
114
+ hierarchy: hierarchy.length > 0 ? hierarchy : componentName ? [componentName] : void 0,
115
+ props
116
+ };
117
+ } catch {
118
+ return void 0;
119
+ }
120
+ }
121
+ function getReactComponentName(type) {
122
+ if (!type) return void 0;
123
+ if (typeof type === "string") return void 0;
124
+ if (type.displayName) return type.displayName;
125
+ if (type.name) return type.name;
126
+ if (type.render?.displayName) return type.render.displayName;
127
+ if (type.render?.name) return type.render.name;
128
+ return void 0;
129
+ }
130
+ function resolveVueComponent(el) {
131
+ try {
132
+ const vueParent = el.__vueParentComponent || el.__vnode?.ctx;
133
+ if (vueParent) {
134
+ const type = vueParent.type || {};
135
+ const componentName = type.name || type.__name || type.displayName || "AnonymousComponent";
136
+ const sourceFile = type.__file;
137
+ const hierarchy = [];
138
+ let curr = vueParent;
139
+ let depth = 0;
140
+ while (curr && depth < 50) {
141
+ depth++;
142
+ const cType = curr.type || {};
143
+ const cName = cType.name || cType.__name || cType.displayName;
144
+ if (cName && !hierarchy.includes(cName)) {
145
+ hierarchy.unshift(cName);
146
+ }
147
+ curr = curr.parent;
148
+ }
149
+ return {
150
+ framework: "vue",
151
+ componentName,
152
+ sourceFile: normalizeFilePath(sourceFile),
153
+ hierarchy: hierarchy.length > 0 ? hierarchy : [componentName],
154
+ props: vueParent.props ? sanitizeProps(vueParent.props) : void 0
155
+ };
156
+ }
157
+ const vue2Instance = el.__vue__;
158
+ if (vue2Instance) {
159
+ const options = vue2Instance.$options || {};
160
+ const componentName = options.name || options._componentTag || "VueComponent";
161
+ const sourceFile = options.__file;
162
+ return {
163
+ framework: "vue",
164
+ componentName,
165
+ sourceFile: normalizeFilePath(sourceFile),
166
+ hierarchy: [componentName],
167
+ props: vue2Instance.$props ? sanitizeProps(vue2Instance.$props) : void 0
168
+ };
169
+ }
170
+ return void 0;
171
+ } catch {
172
+ return void 0;
173
+ }
174
+ }
175
+ function resolveSvelteComponent(el) {
176
+ try {
177
+ let curr = el;
178
+ let depth = 0;
179
+ while (curr && depth < 50) {
180
+ depth++;
181
+ const meta = curr.__svelte_meta;
182
+ if (meta && meta.loc) {
183
+ return {
184
+ framework: "svelte",
185
+ sourceFile: normalizeFilePath(meta.loc.file),
186
+ sourceLine: meta.loc.line,
187
+ sourceColumn: meta.loc.column,
188
+ componentName: meta.loc.file ? getBaseNameWithoutExt(meta.loc.file) : void 0,
189
+ hierarchy: meta.loc.file ? [getBaseNameWithoutExt(meta.loc.file)] : void 0
190
+ };
191
+ }
192
+ curr = curr.parentElement;
193
+ }
194
+ return void 0;
195
+ } catch {
196
+ return void 0;
197
+ }
198
+ }
199
+ function resolveDataAttributeComponent(el) {
200
+ try {
201
+ const compEl = el.closest("[data-component], [data-component-name], [data-source-file]");
202
+ if (!compEl) return void 0;
203
+ const componentName = compEl.getAttribute("data-component") || compEl.getAttribute("data-component-name") || void 0;
204
+ const sourceFile = compEl.getAttribute("data-source-file") || void 0;
205
+ const lineAttr = compEl.getAttribute("data-source-line");
206
+ const sourceLine = lineAttr ? parseInt(lineAttr, 10) : void 0;
207
+ if (!componentName && !sourceFile) return void 0;
208
+ return {
209
+ framework: "vanilla",
210
+ componentName,
211
+ sourceFile: normalizeFilePath(sourceFile),
212
+ sourceLine: isNaN(sourceLine) ? void 0 : sourceLine,
213
+ hierarchy: componentName ? [componentName] : void 0
214
+ };
215
+ } catch {
216
+ return void 0;
217
+ }
218
+ }
219
+ function normalizeFilePath(filePath) {
220
+ if (!filePath) return void 0;
221
+ const cleaned = filePath.split("?")[0];
222
+ return cleaned;
223
+ }
224
+ function getBaseNameWithoutExt(filePath) {
225
+ const parts = filePath.split("/");
226
+ const last = parts[parts.length - 1] || filePath;
227
+ return last.split(".")[0] || last;
228
+ }
229
+ function sanitizeProps(props) {
230
+ const result = {};
231
+ for (const [key, value] of Object.entries(props)) {
232
+ if (key.startsWith("__") || typeof value === "function") continue;
233
+ if (value === null || value === void 0) {
234
+ result[key] = value;
235
+ } else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
236
+ result[key] = value;
237
+ } else if (Array.isArray(value)) {
238
+ result[key] = `Array(${value.length})`;
239
+ } else if (typeof value === "object") {
240
+ result[key] = "[Object]";
241
+ }
242
+ }
243
+ return result;
244
+ }
245
+
32
246
  // packages/client/src/interceptors/interaction.ts
33
247
  function extractElementSummary(el) {
34
248
  const selector = getSemanticSelector(el);
35
249
  const tag = el.tagName.toLowerCase();
36
250
  const id = el.id || void 0;
37
251
  const classes = Array.from(el.classList || []);
252
+ const componentSource = resolveComponentSource(el);
38
253
  let boundingRect = void 0;
39
254
  let visible = true;
40
255
  try {
@@ -66,7 +281,8 @@ function extractElementSummary(el) {
66
281
  boundingRect,
67
282
  visible,
68
283
  innerText,
69
- outerHTML
284
+ outerHTML,
285
+ componentSource
70
286
  };
71
287
  }
72
288
  function setupInteractionInterceptors(onInteraction) {
@@ -384,10 +600,62 @@ var DEFAULT_OPTIONS = {
384
600
  enabled: true,
385
601
  shortcut: "Alt+Click",
386
602
  showBadges: true,
603
+ showToolbar: true,
387
604
  maskSelectors: ['input[type="password"]', "[data-sensitive]"]
388
605
  },
606
+ hidden: false,
607
+ hideQueryParam: void 0,
389
608
  debug: false
390
609
  };
610
+ var HIDE_VALUES = /* @__PURE__ */ new Set(["0", "false", "hidden", "hide", "off", "none", "disabled", "ui_off", "silent"]);
611
+ function shouldHideUIFromUrl(customParam, searchString) {
612
+ let search = searchString;
613
+ if (search === void 0) {
614
+ if (typeof window === "undefined" || !window.location) return false;
615
+ search = window.location.search;
616
+ }
617
+ if (!search) return false;
618
+ try {
619
+ const params = new URLSearchParams(search);
620
+ if (customParam) {
621
+ const customKeys = Array.isArray(customParam) ? customParam : [customParam];
622
+ for (const key of customKeys) {
623
+ if (params.has(key)) {
624
+ const val = (params.get(key) || "").toLowerCase().trim();
625
+ if (val === "" || val === "1" || val === "true" || HIDE_VALUES.has(val)) {
626
+ return true;
627
+ }
628
+ }
629
+ }
630
+ }
631
+ for (const flag of ["no_bt", "no_browsertrack", "hide_bt", "hide_browsertrack"]) {
632
+ if (params.has(flag)) {
633
+ const val = (params.get(flag) || "").toLowerCase().trim();
634
+ if (val === "" || val === "1" || val === "true" || val === "yes") {
635
+ return true;
636
+ }
637
+ }
638
+ }
639
+ if (params.has("bt")) {
640
+ const val = (params.get("bt") || "").toLowerCase().trim();
641
+ if (HIDE_VALUES.has(val)) return true;
642
+ }
643
+ if (params.has("browsertrack")) {
644
+ const val = (params.get("browsertrack") || "").toLowerCase().trim();
645
+ if (HIDE_VALUES.has(val)) return true;
646
+ }
647
+ if (params.has("bt_ui")) {
648
+ const val = (params.get("bt_ui") || "").toLowerCase().trim();
649
+ if (HIDE_VALUES.has(val) || val === "0" || val === "false") return true;
650
+ }
651
+ if (params.has("bt_hide")) {
652
+ const val = (params.get("bt_hide") || "").toLowerCase().trim();
653
+ if (val === "" || val === "1" || val === "true") return true;
654
+ }
655
+ } catch {
656
+ }
657
+ return false;
658
+ }
391
659
 
392
660
  // packages/client/src/interceptors/console.ts
393
661
  function setupConsoleInterceptors(onConsole) {
@@ -448,12 +716,16 @@ function setupConsoleInterceptors(onConsole) {
448
716
  function setupNavigationInterceptors(onNavigation) {
449
717
  if (typeof window === "undefined" || typeof history === "undefined") return () => {
450
718
  };
451
- let currentUrl = redactUrl(window.location.href);
452
- onNavigation({
453
- to: currentUrl,
454
- type: "initial",
455
- timestamp: Date.now()
456
- });
719
+ let currentUrl = "";
720
+ try {
721
+ currentUrl = redactUrl(window.location.href);
722
+ onNavigation({
723
+ to: currentUrl,
724
+ type: "initial",
725
+ timestamp: Date.now()
726
+ });
727
+ } catch {
728
+ }
457
729
  const originalPushState = history.pushState;
458
730
  const originalReplaceState = history.replaceState;
459
731
  history.pushState = function(data, unused, url) {
@@ -489,26 +761,32 @@ function setupNavigationInterceptors(onNavigation) {
489
761
  return result;
490
762
  };
491
763
  const onPopState = () => {
492
- const from = currentUrl;
493
- const to = redactUrl(window.location.href);
494
- currentUrl = to;
495
- onNavigation({
496
- from,
497
- to,
498
- type: "popstate",
499
- timestamp: Date.now()
500
- });
764
+ try {
765
+ const from = currentUrl;
766
+ const to = redactUrl(window.location.href);
767
+ currentUrl = to;
768
+ onNavigation({
769
+ from,
770
+ to,
771
+ type: "popstate",
772
+ timestamp: Date.now()
773
+ });
774
+ } catch {
775
+ }
501
776
  };
502
777
  const onHashChange = () => {
503
- const from = currentUrl;
504
- const to = redactUrl(window.location.href);
505
- currentUrl = to;
506
- onNavigation({
507
- from,
508
- to,
509
- type: "hashchange",
510
- timestamp: Date.now()
511
- });
778
+ try {
779
+ const from = currentUrl;
780
+ const to = redactUrl(window.location.href);
781
+ currentUrl = to;
782
+ onNavigation({
783
+ from,
784
+ to,
785
+ type: "hashchange",
786
+ timestamp: Date.now()
787
+ });
788
+ } catch {
789
+ }
512
790
  };
513
791
  window.addEventListener("popstate", onPopState);
514
792
  window.addEventListener("hashchange", onHashChange);
@@ -545,31 +823,38 @@ function setupNetworkInterceptors(onNetwork) {
545
823
  urlStr = "unknown_url";
546
824
  }
547
825
  const safeUrl = redactUrl(urlStr);
826
+ let response;
548
827
  try {
549
- const response = await originalFetch.apply(window, [input, init2]);
550
- const durationMs = Date.now() - startTime;
551
- onNetwork({
552
- url: safeUrl,
553
- method,
554
- status: response.status,
555
- statusText: response.statusText,
556
- durationMs,
557
- timestamp: startTime
558
- });
559
- return response;
828
+ response = await originalFetch.apply(window, [input, init2]);
560
829
  } catch (err) {
561
- const durationMs = Date.now() - startTime;
830
+ const durationMs2 = Date.now() - startTime;
562
831
  const isAbort = err?.name === "AbortError";
832
+ try {
833
+ onNetwork({
834
+ url: safeUrl,
835
+ method,
836
+ durationMs: durationMs2,
837
+ error: err?.message || "Network request failed",
838
+ aborted: isAbort,
839
+ timestamp: startTime
840
+ });
841
+ } catch {
842
+ }
843
+ throw err;
844
+ }
845
+ const durationMs = Date.now() - startTime;
846
+ try {
563
847
  onNetwork({
564
848
  url: safeUrl,
565
849
  method,
850
+ status: response.status,
851
+ statusText: response.statusText,
566
852
  durationMs,
567
- error: err?.message || "Network request failed",
568
- aborted: isAbort,
569
853
  timestamp: startTime
570
854
  });
571
- throw err;
855
+ } catch {
572
856
  }
857
+ return response;
573
858
  };
574
859
  cleanups.push(() => {
575
860
  window.fetch = originalFetch;
@@ -955,15 +1240,18 @@ var WebSocketTransport = class {
955
1240
  }
956
1241
  }
957
1242
  send(payload) {
958
- const raw = JSON.stringify(payload);
959
- if (this.isConnected()) {
960
- try {
961
- this.ws.send(raw);
962
- } catch {
1243
+ try {
1244
+ const raw = safeJsonStringify(payload);
1245
+ if (this.isConnected()) {
1246
+ try {
1247
+ this.ws.send(raw);
1248
+ } catch {
1249
+ this.enqueue(raw);
1250
+ }
1251
+ } else {
963
1252
  this.enqueue(raw);
964
1253
  }
965
- } else {
966
- this.enqueue(raw);
1254
+ } catch {
967
1255
  }
968
1256
  }
969
1257
  enqueue(raw) {
@@ -988,17 +1276,17 @@ var WebSocketTransport = class {
988
1276
  }
989
1277
  sendHello() {
990
1278
  if (typeof window === "undefined") return;
991
- const hello = {
992
- type: "hello",
993
- origin: window.location.origin,
994
- url: window.location.href,
995
- title: document.title,
996
- userAgent: navigator.userAgent,
997
- timestamp: Date.now(),
998
- projectId: this.projectId
999
- };
1000
1279
  try {
1001
- this.ws.send(JSON.stringify(hello));
1280
+ const hello = {
1281
+ type: "hello",
1282
+ origin: window.location.origin,
1283
+ url: window.location.href,
1284
+ title: document.title,
1285
+ userAgent: navigator.userAgent,
1286
+ timestamp: Date.now(),
1287
+ projectId: this.projectId
1288
+ };
1289
+ this.ws.send(safeJsonStringify(hello));
1002
1290
  } catch {
1003
1291
  }
1004
1292
  }
@@ -1054,14 +1342,19 @@ var NoteInspector = class {
1054
1342
  dragStartY = 0;
1055
1343
  savedNotes = [];
1056
1344
  showMarkers = true;
1345
+ isHidden = false;
1057
1346
  cleanups = [];
1058
1347
  constructor(transport, screenshotDriver, options = {}) {
1059
1348
  this.transport = transport;
1060
1349
  this.screenshotDriver = screenshotDriver;
1350
+ const hiddenByQuery = shouldHideUIFromUrl(options.hideQueryParam);
1351
+ this.isHidden = options.hidden === true || hiddenByQuery;
1352
+ this.showMarkers = options.showBadges !== false && !this.isHidden;
1061
1353
  this.options = {
1062
1354
  shortcut: "Alt+Click",
1063
1355
  maskSelectors: ['input[type="password"]', "[data-sensitive]"],
1064
1356
  showToolbar: true,
1357
+ showBadges: true,
1065
1358
  ...options
1066
1359
  };
1067
1360
  }
@@ -1139,12 +1432,48 @@ var NoteInspector = class {
1139
1432
  this.hideHighlight();
1140
1433
  }
1141
1434
  }
1435
+ isVisible() {
1436
+ return !this.isHidden;
1437
+ }
1438
+ setVisible(visible) {
1439
+ this.isHidden = !visible;
1440
+ this.showMarkers = this.options.showBadges !== false && !this.isHidden;
1441
+ if (this.container) {
1442
+ this.container.style.display = this.isHidden ? "none" : "block";
1443
+ }
1444
+ if (this.toolbarElement) {
1445
+ this.toolbarElement.style.display = this.isHidden ? "none" : "flex";
1446
+ } else if (!this.isHidden && this.options.showToolbar !== false) {
1447
+ this.createToolbar();
1448
+ }
1449
+ if (this.isHidden) {
1450
+ this.hideHighlight();
1451
+ this.hideRegionOverlay();
1452
+ if (this.modalOverlay && this.shadowRoot) {
1453
+ this.shadowRoot.removeChild(this.modalOverlay);
1454
+ this.modalOverlay = null;
1455
+ }
1456
+ if (this.cardOverlay && this.shadowRoot) {
1457
+ this.shadowRoot.removeChild(this.cardOverlay);
1458
+ this.cardOverlay = null;
1459
+ }
1460
+ if (this.markersContainer) {
1461
+ this.markersContainer.innerHTML = "";
1462
+ }
1463
+ } else {
1464
+ this.renderMarkers();
1465
+ this.updateToolbarCount();
1466
+ }
1467
+ }
1142
1468
  ensureContainer() {
1143
1469
  if (typeof document === "undefined") return null;
1144
1470
  if (!this.container) {
1145
1471
  this.container = document.createElement("div");
1146
1472
  this.container.id = "browsertrack-inspector-host";
1147
1473
  this.container.style.cssText = "all: initial; position: fixed; top: 0; left: 0; width: 0; height: 0; z-index: 2147483647; pointer-events: none;";
1474
+ if (this.isHidden) {
1475
+ this.container.style.display = "none";
1476
+ }
1148
1477
  this.shadowRoot = this.container.attachShadow({ mode: "open" });
1149
1478
  const style = document.createElement("style");
1150
1479
  style.textContent = `
@@ -1587,6 +1916,22 @@ var NoteInspector = class {
1587
1916
  color: #cbd5e1;
1588
1917
  }
1589
1918
 
1919
+ .bt-component-pill {
1920
+ display: flex;
1921
+ align-items: center;
1922
+ gap: 6px;
1923
+ background: rgba(15, 23, 42, 0.95);
1924
+ border: 1px solid rgba(99, 102, 241, 0.35);
1925
+ border-radius: 6px;
1926
+ padding: 6px 10px;
1927
+ font-size: 11.5px;
1928
+ color: #c7d2fe;
1929
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
1930
+ overflow: hidden;
1931
+ text-overflow: ellipsis;
1932
+ white-space: nowrap;
1933
+ }
1934
+
1590
1935
  .bt-scenario-stepper {
1591
1936
  display: flex;
1592
1937
  align-items: center;
@@ -1826,7 +2171,7 @@ var NoteInspector = class {
1826
2171
  this.toastContainer = document.createElement("div");
1827
2172
  this.toastContainer.className = "bt-toast-container";
1828
2173
  this.shadowRoot.appendChild(this.toastContainer);
1829
- if (this.options.showToolbar) {
2174
+ if (this.options.showToolbar && !this.isHidden) {
1830
2175
  this.createToolbar();
1831
2176
  }
1832
2177
  }
@@ -1927,11 +2272,17 @@ var NoteInspector = class {
1927
2272
  const root = this.ensureContainer();
1928
2273
  if (!root || !this.markersContainer) return;
1929
2274
  this.markersContainer.innerHTML = "";
1930
- if (!this.showMarkers) return;
2275
+ if (!this.showMarkers || this.isHidden) return;
1931
2276
  const currentPath = window.location.pathname;
1932
- const activeNotes = this.savedNotes.filter(
1933
- (n) => n.status === "OPEN" && (n.route === currentPath || !n.route || n.route === "/" || window.location.href.includes(n.route))
1934
- );
2277
+ const activeNotes = this.savedNotes.filter((n) => {
2278
+ if (n.status !== "OPEN") return false;
2279
+ if (!n.route) return true;
2280
+ if (n.route === currentPath) return true;
2281
+ if (n.route.includes("#") || n.route.includes("?")) {
2282
+ return window.location.href.includes(n.route);
2283
+ }
2284
+ return false;
2285
+ });
1935
2286
  const pageNotes = [];
1936
2287
  activeNotes.forEach((note, index) => {
1937
2288
  if (note.type === "page") {
@@ -2058,6 +2409,13 @@ var NoteInspector = class {
2058
2409
 
2059
2410
  ${scenarioStepsHtml}
2060
2411
 
2412
+ ${note.elementContext?.componentSource?.componentName ? `<div class="bt-component-pill" title="${note.elementContext.componentSource.sourceFile ? note.elementContext.componentSource.sourceFile + (note.elementContext.componentSource.sourceLine ? ":" + note.elementContext.componentSource.sourceLine : "") : note.elementContext.componentSource.componentName}">
2413
+ <span>\u{1F9EC}</span>
2414
+ <span style="font-weight: 600; color: #a5b4fc;">&lt;${note.elementContext.componentSource.componentName}&gt;</span>
2415
+ ${note.elementContext.componentSource.sourceFile ? `<span style="color: #64748b; font-size: 11px; margin-left: 4px;">${note.elementContext.componentSource.sourceFile}${note.elementContext.componentSource.sourceLine ? ":" + note.elementContext.componentSource.sourceLine : ""}</span>` : ""}
2416
+ ${note.elementContext.componentSource.framework ? `<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>` : ""}
2417
+ </div>` : ""}
2418
+
2061
2419
  <div class="bt-target-pill" title="${contextText}">
2062
2420
  <span>\u{1F3F7}\uFE0F</span>
2063
2421
  <span class="bt-pill-content">${contextText}</span>
@@ -2149,65 +2507,83 @@ var NoteInspector = class {
2149
2507
  }
2150
2508
  setupListeners() {
2151
2509
  const onMouseMove = (e) => {
2152
- if (this.modalOverlay || this.cardOverlay || this.isDraggingRegion || this.activeMode === "region") return;
2153
- const path = e.composedPath ? e.composedPath() : [];
2154
- if (this.container && path.includes(this.container)) {
2155
- this.hideHighlight();
2156
- return;
2157
- }
2158
- if (e.altKey || this.activeMode === "element") {
2159
- const target = document.elementFromPoint(e.clientX, e.clientY);
2160
- if (target && target !== this.container && !this.container?.contains(target)) {
2161
- this.hoveredElement = target;
2162
- this.updateHighlight(target);
2510
+ try {
2511
+ if (this.isHidden || this.modalOverlay || this.cardOverlay || this.isDraggingRegion || this.activeMode === "region") return;
2512
+ const path = e.composedPath ? e.composedPath() : [];
2513
+ if (this.container && path.includes(this.container)) {
2514
+ this.hideHighlight();
2163
2515
  return;
2164
2516
  }
2165
- }
2166
- if (!e.altKey && this.activeMode !== "element") {
2167
- this.hideHighlight();
2517
+ if (e.altKey || this.activeMode === "element") {
2518
+ const target = document.elementFromPoint(e.clientX, e.clientY);
2519
+ if (target && target !== this.container && !this.container?.contains(target)) {
2520
+ this.hoveredElement = target;
2521
+ this.updateHighlight(target);
2522
+ return;
2523
+ }
2524
+ }
2525
+ if (!e.altKey && this.activeMode !== "element") {
2526
+ this.hideHighlight();
2527
+ }
2528
+ } catch {
2168
2529
  }
2169
2530
  };
2170
2531
  const onClick = (e) => {
2171
- if (this.modalOverlay || this.cardOverlay) return;
2172
- const path = e.composedPath ? e.composedPath() : [];
2173
- if (this.container && path.includes(this.container)) {
2174
- return;
2175
- }
2176
- if (this.activeMode === "region") return;
2177
- if (e.altKey || this.activeMode === "element") {
2178
- e.preventDefault();
2179
- e.stopPropagation();
2180
- const target = this.hoveredElement || document.elementFromPoint(e.clientX, e.clientY);
2181
- if (target && target !== this.container && !this.container?.contains(target)) {
2182
- this.selectedElement = target;
2183
- this.openNoteEditor(target, "element");
2184
- if (this.activeMode === "element" && !this.activeScenario) {
2185
- this.setMode("idle");
2532
+ try {
2533
+ if (this.isHidden || this.modalOverlay || this.cardOverlay) return;
2534
+ const path = e.composedPath ? e.composedPath() : [];
2535
+ if (this.container && path.includes(this.container)) {
2536
+ return;
2537
+ }
2538
+ if (this.activeMode === "region") return;
2539
+ if (e.altKey || this.activeMode === "element") {
2540
+ e.preventDefault();
2541
+ e.stopPropagation();
2542
+ const target = this.hoveredElement || document.elementFromPoint(e.clientX, e.clientY);
2543
+ if (target && target !== this.container && !this.container?.contains(target)) {
2544
+ this.selectedElement = target;
2545
+ this.openNoteEditor(target, "element");
2546
+ if (this.activeMode === "element" && !this.activeScenario) {
2547
+ this.setMode("idle");
2548
+ }
2186
2549
  }
2187
2550
  }
2551
+ } catch {
2188
2552
  }
2189
2553
  };
2190
2554
  const onKeyDown = (e) => {
2191
- if (e.key === "Escape") {
2192
- if (this.cardOverlay && this.cardOverlay.parentElement && this.shadowRoot) {
2193
- this.shadowRoot.removeChild(this.cardOverlay);
2194
- this.cardOverlay = null;
2195
- } else if (this.activeMode === "region" || this.activeMode === "element") {
2196
- if (!this.activeScenario) {
2197
- this.setMode("idle");
2555
+ try {
2556
+ if (e.key === "Escape") {
2557
+ if (this.cardOverlay && this.cardOverlay.parentElement && this.shadowRoot) {
2558
+ this.shadowRoot.removeChild(this.cardOverlay);
2559
+ this.cardOverlay = null;
2560
+ } else if (this.activeMode === "region" || this.activeMode === "element") {
2561
+ if (!this.activeScenario) {
2562
+ this.setMode("idle");
2563
+ }
2198
2564
  }
2199
2565
  }
2566
+ } catch {
2200
2567
  }
2201
2568
  };
2202
2569
  const onKeyUp = (e) => {
2203
- if (e.key === "Alt" && !this.modalOverlay && !this.cardOverlay && this.activeMode !== "element") {
2204
- this.hideHighlight();
2570
+ try {
2571
+ if (e.key === "Alt" && !this.modalOverlay && !this.cardOverlay && this.activeMode !== "element") {
2572
+ this.hideHighlight();
2573
+ }
2574
+ } catch {
2205
2575
  }
2206
2576
  };
2207
2577
  const onScrollOrResize = () => {
2208
- requestAnimationFrame(() => {
2209
- this.updateMarkerPositions();
2210
- });
2578
+ try {
2579
+ requestAnimationFrame(() => {
2580
+ try {
2581
+ this.updateMarkerPositions();
2582
+ } catch {
2583
+ }
2584
+ });
2585
+ } catch {
2586
+ }
2211
2587
  };
2212
2588
  window.addEventListener("mousemove", onMouseMove, { capture: true, passive: true });
2213
2589
  window.addEventListener("click", onClick, { capture: true });
@@ -2356,10 +2732,12 @@ var NoteInspector = class {
2356
2732
  if (noteType === "element") {
2357
2733
  this.updateHighlight(targetEl);
2358
2734
  }
2735
+ const comp = noteType === "element" ? resolveComponentSource(targetEl) : void 0;
2736
+ const compPrefix = comp?.componentName ? `\u{1F9EC} <${comp.componentName}>${comp.sourceFile ? " (" + comp.sourceFile + (comp.sourceLine ? ":" + comp.sourceLine : "") + ")" : ""} \xB7 ` : "";
2359
2737
  this.renderNoteModal({
2360
2738
  title: this.activeScenario ? `Step ${this.activeScenario.stepNumber}: ${this.activeScenario.title}` : "Add Visual Note",
2361
2739
  modeBadge: this.activeScenario ? `STEP ${this.activeScenario.stepNumber}` : noteType.toUpperCase(),
2362
- pillText: noteType === "page" ? `Page Viewport: ${window.innerWidth} \xD7 ${window.innerHeight} px` : `${getSemanticSelector(targetEl)} (${Math.round(targetEl.getBoundingClientRect().width)}\xD7${Math.round(targetEl.getBoundingClientRect().height)}) \xB7 Viewport: ${window.innerWidth}\xD7${window.innerHeight}`,
2740
+ pillText: noteType === "page" ? `Page Viewport: ${window.innerWidth} \xD7 ${window.innerHeight} px` : `${compPrefix}${getSemanticSelector(targetEl)} (${Math.round(targetEl.getBoundingClientRect().width)}\xD7${Math.round(targetEl.getBoundingClientRect().height)}) \xB7 Viewport: ${window.innerWidth}\xD7${window.innerHeight}`,
2363
2741
  onSave: async (message, action) => {
2364
2742
  let scenarioParam;
2365
2743
  if (action === "next_step" && !this.activeScenario) {
@@ -2629,12 +3007,14 @@ var NoteInspector = class {
2629
3007
  if (innerText && innerText.length > 200) {
2630
3008
  innerText = truncate(innerText, 200);
2631
3009
  }
3010
+ const componentSource = resolveComponentSource(el);
2632
3011
  return {
2633
3012
  selector,
2634
3013
  tag: el.tagName.toLowerCase(),
2635
3014
  attributes,
2636
3015
  outerHTML,
2637
3016
  innerText,
3017
+ componentSource,
2638
3018
  parent: el.parentElement ? {
2639
3019
  selector: getSemanticSelector(el.parentElement),
2640
3020
  tag: el.parentElement.tagName.toLowerCase()
@@ -2665,7 +3045,11 @@ var NoteInspector = class {
2665
3045
  region.width,
2666
3046
  region.height
2667
3047
  );
2668
- resolve(canvas.toDataURL("image/png"));
3048
+ try {
3049
+ resolve(canvas.toDataURL("image/png"));
3050
+ } catch {
3051
+ resolve(dataUrl);
3052
+ }
2669
3053
  };
2670
3054
  img.onerror = () => resolve(dataUrl);
2671
3055
  img.src = dataUrl;
@@ -2717,7 +3101,11 @@ var BrowserTrackClient = class {
2717
3101
  if (this.options.notes.enabled) {
2718
3102
  this.inspector = new NoteInspector(this.transport, this.screenshotDriver, {
2719
3103
  shortcut: this.options.notes.shortcut,
2720
- maskSelectors: this.options.notes.maskSelectors
3104
+ maskSelectors: this.options.notes.maskSelectors,
3105
+ showToolbar: this.options.notes.showToolbar,
3106
+ showBadges: this.options.notes.showBadges,
3107
+ hidden: this.options.hidden,
3108
+ hideQueryParam: this.options.hideQueryParam
2721
3109
  });
2722
3110
  }
2723
3111
  }
@@ -2783,6 +3171,14 @@ var BrowserTrackClient = class {
2783
3171
  this.inspector.setMode("element");
2784
3172
  }
2785
3173
  }
3174
+ isUIVisible() {
3175
+ return this.inspector ? this.inspector.isVisible() : false;
3176
+ }
3177
+ setUIVisible(visible) {
3178
+ if (this.inspector) {
3179
+ this.inspector.setVisible(visible);
3180
+ }
3181
+ }
2786
3182
  setInspectMode(mode) {
2787
3183
  if (this.inspector) {
2788
3184
  this.inspector.setMode(mode);
@@ -2923,8 +3319,10 @@ if (typeof window !== "undefined") {
2923
3319
  const autoInit = currentScript?.getAttribute("data-auto-init") !== "false";
2924
3320
  const daemonUrl = currentScript?.getAttribute("data-daemon-url") || void 0;
2925
3321
  const projectId = currentScript?.getAttribute("data-project-id") || void 0;
3322
+ const hidden = currentScript?.getAttribute("data-hidden") === "true";
3323
+ const hideQueryParam = currentScript?.getAttribute("data-hide-query-param") || void 0;
2926
3324
  if (autoInit) {
2927
- init({ daemonUrl, projectId });
3325
+ init({ daemonUrl, projectId, hidden, hideQueryParam });
2928
3326
  }
2929
3327
  } catch {
2930
3328
  }
@@ -2932,11 +3330,13 @@ if (typeof window !== "undefined") {
2932
3330
 
2933
3331
  export {
2934
3332
  BreadcrumbBuffer,
3333
+ resolveComponentSource,
2935
3334
  DEFAULT_OPTIONS,
3335
+ shouldHideUIFromUrl,
2936
3336
  BrowserScriptScreenshotDriver,
2937
3337
  NoteInspector,
2938
3338
  BrowserTrackClient,
2939
3339
  init,
2940
3340
  getClient
2941
3341
  };
2942
- //# sourceMappingURL=chunk-WB7ZKWK7.js.map
3342
+ //# sourceMappingURL=chunk-4HRLW6YF.js.map