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
@@ -28,7 +28,9 @@ __export(src_exports, {
28
28
  DEFAULT_OPTIONS: () => DEFAULT_OPTIONS,
29
29
  NoteInspector: () => NoteInspector,
30
30
  getClient: () => getClient,
31
- init: () => init
31
+ init: () => init,
32
+ resolveComponentSource: () => resolveComponentSource,
33
+ shouldHideUIFromUrl: () => shouldHideUIFromUrl
32
34
  });
33
35
  module.exports = __toCommonJS(src_exports);
34
36
 
@@ -167,12 +169,252 @@ function truncate(str, maxLength = 200) {
167
169
  return str.slice(0, maxLength) + "...";
168
170
  }
169
171
 
172
+ // packages/core/src/safety.ts
173
+ function safeJsonStringify(val, fallback = "{}") {
174
+ if (val === void 0) return fallback;
175
+ try {
176
+ const seen = /* @__PURE__ */ new WeakSet();
177
+ return JSON.stringify(val, (key, value) => {
178
+ if (typeof value === "object" && value !== null) {
179
+ if (seen.has(value)) {
180
+ return "[Circular]";
181
+ }
182
+ seen.add(value);
183
+ }
184
+ if (typeof value === "bigint") {
185
+ return value.toString();
186
+ }
187
+ return value;
188
+ });
189
+ } catch {
190
+ try {
191
+ return JSON.stringify(String(val));
192
+ } catch {
193
+ return fallback;
194
+ }
195
+ }
196
+ }
197
+
198
+ // packages/client/src/source/resolver.ts
199
+ function resolveComponentSource(el) {
200
+ if (!el || typeof el !== "object") return void 0;
201
+ const reactInfo = resolveReactComponent(el);
202
+ if (reactInfo) return reactInfo;
203
+ const vueInfo = resolveVueComponent(el);
204
+ if (vueInfo) return vueInfo;
205
+ const svelteInfo = resolveSvelteComponent(el);
206
+ if (svelteInfo) return svelteInfo;
207
+ if (el.tagName && el.tagName.includes("-")) {
208
+ return {
209
+ framework: "web-component",
210
+ componentName: el.tagName.toLowerCase(),
211
+ hierarchy: [el.tagName.toLowerCase()]
212
+ };
213
+ }
214
+ const attrInfo = resolveDataAttributeComponent(el);
215
+ if (attrInfo) return attrInfo;
216
+ return void 0;
217
+ }
218
+ function resolveReactComponent(el) {
219
+ try {
220
+ const fiberKey = Object.keys(el).find(
221
+ (key) => key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$")
222
+ );
223
+ if (!fiberKey) return void 0;
224
+ const hostFiber = el[fiberKey];
225
+ if (!hostFiber) return void 0;
226
+ let sourceFile;
227
+ let sourceLine;
228
+ let sourceColumn;
229
+ let componentName;
230
+ const hierarchy = [];
231
+ let props;
232
+ if (hostFiber._debugSource) {
233
+ sourceFile = hostFiber._debugSource.fileName;
234
+ sourceLine = hostFiber._debugSource.lineNumber;
235
+ sourceColumn = hostFiber._debugSource.columnNumber;
236
+ }
237
+ if (hostFiber._debugOwner) {
238
+ const ownerType = hostFiber._debugOwner.type;
239
+ componentName = getReactComponentName(ownerType);
240
+ if (!sourceFile && hostFiber._debugOwner._debugSource) {
241
+ sourceFile = hostFiber._debugOwner._debugSource.fileName;
242
+ sourceLine = hostFiber._debugOwner._debugSource.lineNumber;
243
+ sourceColumn = hostFiber._debugOwner._debugSource.columnNumber;
244
+ }
245
+ }
246
+ let curr = hostFiber;
247
+ let depth = 0;
248
+ while (curr && depth < 50) {
249
+ depth++;
250
+ const type = curr.type;
251
+ const name = getReactComponentName(type);
252
+ if (name && !name.startsWith("html:") && name !== "Fragment") {
253
+ if (!componentName) {
254
+ componentName = name;
255
+ }
256
+ if (!hierarchy.includes(name)) {
257
+ hierarchy.unshift(name);
258
+ }
259
+ if (!sourceFile && curr._debugSource) {
260
+ sourceFile = curr._debugSource.fileName;
261
+ sourceLine = curr._debugSource.lineNumber;
262
+ sourceColumn = curr._debugSource.columnNumber;
263
+ }
264
+ if (!props && curr.memoizedProps && typeof curr.memoizedProps === "object") {
265
+ props = sanitizeProps(curr.memoizedProps);
266
+ }
267
+ }
268
+ curr = curr.return;
269
+ }
270
+ if (!componentName && !sourceFile) {
271
+ return void 0;
272
+ }
273
+ return {
274
+ framework: "react",
275
+ componentName,
276
+ sourceFile: normalizeFilePath(sourceFile),
277
+ sourceLine,
278
+ sourceColumn,
279
+ hierarchy: hierarchy.length > 0 ? hierarchy : componentName ? [componentName] : void 0,
280
+ props
281
+ };
282
+ } catch {
283
+ return void 0;
284
+ }
285
+ }
286
+ function getReactComponentName(type) {
287
+ if (!type) return void 0;
288
+ if (typeof type === "string") return void 0;
289
+ if (type.displayName) return type.displayName;
290
+ if (type.name) return type.name;
291
+ if (type.render?.displayName) return type.render.displayName;
292
+ if (type.render?.name) return type.render.name;
293
+ return void 0;
294
+ }
295
+ function resolveVueComponent(el) {
296
+ try {
297
+ const vueParent = el.__vueParentComponent || el.__vnode?.ctx;
298
+ if (vueParent) {
299
+ const type = vueParent.type || {};
300
+ const componentName = type.name || type.__name || type.displayName || "AnonymousComponent";
301
+ const sourceFile = type.__file;
302
+ const hierarchy = [];
303
+ let curr = vueParent;
304
+ let depth = 0;
305
+ while (curr && depth < 50) {
306
+ depth++;
307
+ const cType = curr.type || {};
308
+ const cName = cType.name || cType.__name || cType.displayName;
309
+ if (cName && !hierarchy.includes(cName)) {
310
+ hierarchy.unshift(cName);
311
+ }
312
+ curr = curr.parent;
313
+ }
314
+ return {
315
+ framework: "vue",
316
+ componentName,
317
+ sourceFile: normalizeFilePath(sourceFile),
318
+ hierarchy: hierarchy.length > 0 ? hierarchy : [componentName],
319
+ props: vueParent.props ? sanitizeProps(vueParent.props) : void 0
320
+ };
321
+ }
322
+ const vue2Instance = el.__vue__;
323
+ if (vue2Instance) {
324
+ const options = vue2Instance.$options || {};
325
+ const componentName = options.name || options._componentTag || "VueComponent";
326
+ const sourceFile = options.__file;
327
+ return {
328
+ framework: "vue",
329
+ componentName,
330
+ sourceFile: normalizeFilePath(sourceFile),
331
+ hierarchy: [componentName],
332
+ props: vue2Instance.$props ? sanitizeProps(vue2Instance.$props) : void 0
333
+ };
334
+ }
335
+ return void 0;
336
+ } catch {
337
+ return void 0;
338
+ }
339
+ }
340
+ function resolveSvelteComponent(el) {
341
+ try {
342
+ let curr = el;
343
+ let depth = 0;
344
+ while (curr && depth < 50) {
345
+ depth++;
346
+ const meta = curr.__svelte_meta;
347
+ if (meta && meta.loc) {
348
+ return {
349
+ framework: "svelte",
350
+ sourceFile: normalizeFilePath(meta.loc.file),
351
+ sourceLine: meta.loc.line,
352
+ sourceColumn: meta.loc.column,
353
+ componentName: meta.loc.file ? getBaseNameWithoutExt(meta.loc.file) : void 0,
354
+ hierarchy: meta.loc.file ? [getBaseNameWithoutExt(meta.loc.file)] : void 0
355
+ };
356
+ }
357
+ curr = curr.parentElement;
358
+ }
359
+ return void 0;
360
+ } catch {
361
+ return void 0;
362
+ }
363
+ }
364
+ function resolveDataAttributeComponent(el) {
365
+ try {
366
+ const compEl = el.closest("[data-component], [data-component-name], [data-source-file]");
367
+ if (!compEl) return void 0;
368
+ const componentName = compEl.getAttribute("data-component") || compEl.getAttribute("data-component-name") || void 0;
369
+ const sourceFile = compEl.getAttribute("data-source-file") || void 0;
370
+ const lineAttr = compEl.getAttribute("data-source-line");
371
+ const sourceLine = lineAttr ? parseInt(lineAttr, 10) : void 0;
372
+ if (!componentName && !sourceFile) return void 0;
373
+ return {
374
+ framework: "vanilla",
375
+ componentName,
376
+ sourceFile: normalizeFilePath(sourceFile),
377
+ sourceLine: isNaN(sourceLine) ? void 0 : sourceLine,
378
+ hierarchy: componentName ? [componentName] : void 0
379
+ };
380
+ } catch {
381
+ return void 0;
382
+ }
383
+ }
384
+ function normalizeFilePath(filePath) {
385
+ if (!filePath) return void 0;
386
+ const cleaned = filePath.split("?")[0];
387
+ return cleaned;
388
+ }
389
+ function getBaseNameWithoutExt(filePath) {
390
+ const parts = filePath.split("/");
391
+ const last = parts[parts.length - 1] || filePath;
392
+ return last.split(".")[0] || last;
393
+ }
394
+ function sanitizeProps(props) {
395
+ const result = {};
396
+ for (const [key, value] of Object.entries(props)) {
397
+ if (key.startsWith("__") || typeof value === "function") continue;
398
+ if (value === null || value === void 0) {
399
+ result[key] = value;
400
+ } else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
401
+ result[key] = value;
402
+ } else if (Array.isArray(value)) {
403
+ result[key] = `Array(${value.length})`;
404
+ } else if (typeof value === "object") {
405
+ result[key] = "[Object]";
406
+ }
407
+ }
408
+ return result;
409
+ }
410
+
170
411
  // packages/client/src/interceptors/interaction.ts
171
412
  function extractElementSummary(el) {
172
413
  const selector = getSemanticSelector(el);
173
414
  const tag = el.tagName.toLowerCase();
174
415
  const id = el.id || void 0;
175
416
  const classes = Array.from(el.classList || []);
417
+ const componentSource = resolveComponentSource(el);
176
418
  let boundingRect = void 0;
177
419
  let visible = true;
178
420
  try {
@@ -204,7 +446,8 @@ function extractElementSummary(el) {
204
446
  boundingRect,
205
447
  visible,
206
448
  innerText,
207
- outerHTML
449
+ outerHTML,
450
+ componentSource
208
451
  };
209
452
  }
210
453
  function setupInteractionInterceptors(onInteraction) {
@@ -522,10 +765,62 @@ var DEFAULT_OPTIONS = {
522
765
  enabled: true,
523
766
  shortcut: "Alt+Click",
524
767
  showBadges: true,
768
+ showToolbar: true,
525
769
  maskSelectors: ['input[type="password"]', "[data-sensitive]"]
526
770
  },
771
+ hidden: false,
772
+ hideQueryParam: void 0,
527
773
  debug: false
528
774
  };
775
+ var HIDE_VALUES = /* @__PURE__ */ new Set(["0", "false", "hidden", "hide", "off", "none", "disabled", "ui_off", "silent"]);
776
+ function shouldHideUIFromUrl(customParam, searchString) {
777
+ let search = searchString;
778
+ if (search === void 0) {
779
+ if (typeof window === "undefined" || !window.location) return false;
780
+ search = window.location.search;
781
+ }
782
+ if (!search) return false;
783
+ try {
784
+ const params = new URLSearchParams(search);
785
+ if (customParam) {
786
+ const customKeys = Array.isArray(customParam) ? customParam : [customParam];
787
+ for (const key of customKeys) {
788
+ if (params.has(key)) {
789
+ const val = (params.get(key) || "").toLowerCase().trim();
790
+ if (val === "" || val === "1" || val === "true" || HIDE_VALUES.has(val)) {
791
+ return true;
792
+ }
793
+ }
794
+ }
795
+ }
796
+ for (const flag of ["no_bt", "no_browsertrack", "hide_bt", "hide_browsertrack"]) {
797
+ if (params.has(flag)) {
798
+ const val = (params.get(flag) || "").toLowerCase().trim();
799
+ if (val === "" || val === "1" || val === "true" || val === "yes") {
800
+ return true;
801
+ }
802
+ }
803
+ }
804
+ if (params.has("bt")) {
805
+ const val = (params.get("bt") || "").toLowerCase().trim();
806
+ if (HIDE_VALUES.has(val)) return true;
807
+ }
808
+ if (params.has("browsertrack")) {
809
+ const val = (params.get("browsertrack") || "").toLowerCase().trim();
810
+ if (HIDE_VALUES.has(val)) return true;
811
+ }
812
+ if (params.has("bt_ui")) {
813
+ const val = (params.get("bt_ui") || "").toLowerCase().trim();
814
+ if (HIDE_VALUES.has(val) || val === "0" || val === "false") return true;
815
+ }
816
+ if (params.has("bt_hide")) {
817
+ const val = (params.get("bt_hide") || "").toLowerCase().trim();
818
+ if (val === "" || val === "1" || val === "true") return true;
819
+ }
820
+ } catch {
821
+ }
822
+ return false;
823
+ }
529
824
 
530
825
  // packages/client/src/interceptors/console.ts
531
826
  function setupConsoleInterceptors(onConsole) {
@@ -586,12 +881,16 @@ function setupConsoleInterceptors(onConsole) {
586
881
  function setupNavigationInterceptors(onNavigation) {
587
882
  if (typeof window === "undefined" || typeof history === "undefined") return () => {
588
883
  };
589
- let currentUrl = redactUrl(window.location.href);
590
- onNavigation({
591
- to: currentUrl,
592
- type: "initial",
593
- timestamp: Date.now()
594
- });
884
+ let currentUrl = "";
885
+ try {
886
+ currentUrl = redactUrl(window.location.href);
887
+ onNavigation({
888
+ to: currentUrl,
889
+ type: "initial",
890
+ timestamp: Date.now()
891
+ });
892
+ } catch {
893
+ }
595
894
  const originalPushState = history.pushState;
596
895
  const originalReplaceState = history.replaceState;
597
896
  history.pushState = function(data, unused, url) {
@@ -627,26 +926,32 @@ function setupNavigationInterceptors(onNavigation) {
627
926
  return result;
628
927
  };
629
928
  const onPopState = () => {
630
- const from = currentUrl;
631
- const to = redactUrl(window.location.href);
632
- currentUrl = to;
633
- onNavigation({
634
- from,
635
- to,
636
- type: "popstate",
637
- timestamp: Date.now()
638
- });
929
+ try {
930
+ const from = currentUrl;
931
+ const to = redactUrl(window.location.href);
932
+ currentUrl = to;
933
+ onNavigation({
934
+ from,
935
+ to,
936
+ type: "popstate",
937
+ timestamp: Date.now()
938
+ });
939
+ } catch {
940
+ }
639
941
  };
640
942
  const onHashChange = () => {
641
- const from = currentUrl;
642
- const to = redactUrl(window.location.href);
643
- currentUrl = to;
644
- onNavigation({
645
- from,
646
- to,
647
- type: "hashchange",
648
- timestamp: Date.now()
649
- });
943
+ try {
944
+ const from = currentUrl;
945
+ const to = redactUrl(window.location.href);
946
+ currentUrl = to;
947
+ onNavigation({
948
+ from,
949
+ to,
950
+ type: "hashchange",
951
+ timestamp: Date.now()
952
+ });
953
+ } catch {
954
+ }
650
955
  };
651
956
  window.addEventListener("popstate", onPopState);
652
957
  window.addEventListener("hashchange", onHashChange);
@@ -683,31 +988,38 @@ function setupNetworkInterceptors(onNetwork) {
683
988
  urlStr = "unknown_url";
684
989
  }
685
990
  const safeUrl = redactUrl(urlStr);
991
+ let response;
686
992
  try {
687
- const response = await originalFetch.apply(window, [input, init2]);
688
- const durationMs = Date.now() - startTime;
689
- onNetwork({
690
- url: safeUrl,
691
- method,
692
- status: response.status,
693
- statusText: response.statusText,
694
- durationMs,
695
- timestamp: startTime
696
- });
697
- return response;
993
+ response = await originalFetch.apply(window, [input, init2]);
698
994
  } catch (err) {
699
- const durationMs = Date.now() - startTime;
995
+ const durationMs2 = Date.now() - startTime;
700
996
  const isAbort = err?.name === "AbortError";
997
+ try {
998
+ onNetwork({
999
+ url: safeUrl,
1000
+ method,
1001
+ durationMs: durationMs2,
1002
+ error: err?.message || "Network request failed",
1003
+ aborted: isAbort,
1004
+ timestamp: startTime
1005
+ });
1006
+ } catch {
1007
+ }
1008
+ throw err;
1009
+ }
1010
+ const durationMs = Date.now() - startTime;
1011
+ try {
701
1012
  onNetwork({
702
1013
  url: safeUrl,
703
1014
  method,
1015
+ status: response.status,
1016
+ statusText: response.statusText,
704
1017
  durationMs,
705
- error: err?.message || "Network request failed",
706
- aborted: isAbort,
707
1018
  timestamp: startTime
708
1019
  });
709
- throw err;
1020
+ } catch {
710
1021
  }
1022
+ return response;
711
1023
  };
712
1024
  cleanups.push(() => {
713
1025
  window.fetch = originalFetch;
@@ -1095,15 +1407,18 @@ var WebSocketTransport = class {
1095
1407
  }
1096
1408
  }
1097
1409
  send(payload) {
1098
- const raw = JSON.stringify(payload);
1099
- if (this.isConnected()) {
1100
- try {
1101
- this.ws.send(raw);
1102
- } catch {
1410
+ try {
1411
+ const raw = safeJsonStringify(payload);
1412
+ if (this.isConnected()) {
1413
+ try {
1414
+ this.ws.send(raw);
1415
+ } catch {
1416
+ this.enqueue(raw);
1417
+ }
1418
+ } else {
1103
1419
  this.enqueue(raw);
1104
1420
  }
1105
- } else {
1106
- this.enqueue(raw);
1421
+ } catch {
1107
1422
  }
1108
1423
  }
1109
1424
  enqueue(raw) {
@@ -1128,17 +1443,17 @@ var WebSocketTransport = class {
1128
1443
  }
1129
1444
  sendHello() {
1130
1445
  if (typeof window === "undefined") return;
1131
- const hello = {
1132
- type: "hello",
1133
- origin: window.location.origin,
1134
- url: window.location.href,
1135
- title: document.title,
1136
- userAgent: navigator.userAgent,
1137
- timestamp: Date.now(),
1138
- projectId: this.projectId
1139
- };
1140
1446
  try {
1141
- this.ws.send(JSON.stringify(hello));
1447
+ const hello = {
1448
+ type: "hello",
1449
+ origin: window.location.origin,
1450
+ url: window.location.href,
1451
+ title: document.title,
1452
+ userAgent: navigator.userAgent,
1453
+ timestamp: Date.now(),
1454
+ projectId: this.projectId
1455
+ };
1456
+ this.ws.send(safeJsonStringify(hello));
1142
1457
  } catch {
1143
1458
  }
1144
1459
  }
@@ -1195,13 +1510,18 @@ var NoteInspector = class {
1195
1510
  __publicField(this, "dragStartY", 0);
1196
1511
  __publicField(this, "savedNotes", []);
1197
1512
  __publicField(this, "showMarkers", true);
1513
+ __publicField(this, "isHidden", false);
1198
1514
  __publicField(this, "cleanups", []);
1199
1515
  this.transport = transport;
1200
1516
  this.screenshotDriver = screenshotDriver;
1517
+ const hiddenByQuery = shouldHideUIFromUrl(options.hideQueryParam);
1518
+ this.isHidden = options.hidden === true || hiddenByQuery;
1519
+ this.showMarkers = options.showBadges !== false && !this.isHidden;
1201
1520
  this.options = {
1202
1521
  shortcut: "Alt+Click",
1203
1522
  maskSelectors: ['input[type="password"]', "[data-sensitive]"],
1204
1523
  showToolbar: true,
1524
+ showBadges: true,
1205
1525
  ...options
1206
1526
  };
1207
1527
  }
@@ -1279,12 +1599,48 @@ var NoteInspector = class {
1279
1599
  this.hideHighlight();
1280
1600
  }
1281
1601
  }
1602
+ isVisible() {
1603
+ return !this.isHidden;
1604
+ }
1605
+ setVisible(visible) {
1606
+ this.isHidden = !visible;
1607
+ this.showMarkers = this.options.showBadges !== false && !this.isHidden;
1608
+ if (this.container) {
1609
+ this.container.style.display = this.isHidden ? "none" : "block";
1610
+ }
1611
+ if (this.toolbarElement) {
1612
+ this.toolbarElement.style.display = this.isHidden ? "none" : "flex";
1613
+ } else if (!this.isHidden && this.options.showToolbar !== false) {
1614
+ this.createToolbar();
1615
+ }
1616
+ if (this.isHidden) {
1617
+ this.hideHighlight();
1618
+ this.hideRegionOverlay();
1619
+ if (this.modalOverlay && this.shadowRoot) {
1620
+ this.shadowRoot.removeChild(this.modalOverlay);
1621
+ this.modalOverlay = null;
1622
+ }
1623
+ if (this.cardOverlay && this.shadowRoot) {
1624
+ this.shadowRoot.removeChild(this.cardOverlay);
1625
+ this.cardOverlay = null;
1626
+ }
1627
+ if (this.markersContainer) {
1628
+ this.markersContainer.innerHTML = "";
1629
+ }
1630
+ } else {
1631
+ this.renderMarkers();
1632
+ this.updateToolbarCount();
1633
+ }
1634
+ }
1282
1635
  ensureContainer() {
1283
1636
  if (typeof document === "undefined") return null;
1284
1637
  if (!this.container) {
1285
1638
  this.container = document.createElement("div");
1286
1639
  this.container.id = "browsertrack-inspector-host";
1287
1640
  this.container.style.cssText = "all: initial; position: fixed; top: 0; left: 0; width: 0; height: 0; z-index: 2147483647; pointer-events: none;";
1641
+ if (this.isHidden) {
1642
+ this.container.style.display = "none";
1643
+ }
1288
1644
  this.shadowRoot = this.container.attachShadow({ mode: "open" });
1289
1645
  const style = document.createElement("style");
1290
1646
  style.textContent = `
@@ -1727,6 +2083,22 @@ var NoteInspector = class {
1727
2083
  color: #cbd5e1;
1728
2084
  }
1729
2085
 
2086
+ .bt-component-pill {
2087
+ display: flex;
2088
+ align-items: center;
2089
+ gap: 6px;
2090
+ background: rgba(15, 23, 42, 0.95);
2091
+ border: 1px solid rgba(99, 102, 241, 0.35);
2092
+ border-radius: 6px;
2093
+ padding: 6px 10px;
2094
+ font-size: 11.5px;
2095
+ color: #c7d2fe;
2096
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
2097
+ overflow: hidden;
2098
+ text-overflow: ellipsis;
2099
+ white-space: nowrap;
2100
+ }
2101
+
1730
2102
  .bt-scenario-stepper {
1731
2103
  display: flex;
1732
2104
  align-items: center;
@@ -1966,7 +2338,7 @@ var NoteInspector = class {
1966
2338
  this.toastContainer = document.createElement("div");
1967
2339
  this.toastContainer.className = "bt-toast-container";
1968
2340
  this.shadowRoot.appendChild(this.toastContainer);
1969
- if (this.options.showToolbar) {
2341
+ if (this.options.showToolbar && !this.isHidden) {
1970
2342
  this.createToolbar();
1971
2343
  }
1972
2344
  }
@@ -2067,11 +2439,17 @@ var NoteInspector = class {
2067
2439
  const root = this.ensureContainer();
2068
2440
  if (!root || !this.markersContainer) return;
2069
2441
  this.markersContainer.innerHTML = "";
2070
- if (!this.showMarkers) return;
2442
+ if (!this.showMarkers || this.isHidden) return;
2071
2443
  const currentPath = window.location.pathname;
2072
- const activeNotes = this.savedNotes.filter(
2073
- (n) => n.status === "OPEN" && (n.route === currentPath || !n.route || n.route === "/" || window.location.href.includes(n.route))
2074
- );
2444
+ const activeNotes = this.savedNotes.filter((n) => {
2445
+ if (n.status !== "OPEN") return false;
2446
+ if (!n.route) return true;
2447
+ if (n.route === currentPath) return true;
2448
+ if (n.route.includes("#") || n.route.includes("?")) {
2449
+ return window.location.href.includes(n.route);
2450
+ }
2451
+ return false;
2452
+ });
2075
2453
  const pageNotes = [];
2076
2454
  activeNotes.forEach((note, index) => {
2077
2455
  if (note.type === "page") {
@@ -2198,6 +2576,13 @@ var NoteInspector = class {
2198
2576
 
2199
2577
  ${scenarioStepsHtml}
2200
2578
 
2579
+ ${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}">
2580
+ <span>\u{1F9EC}</span>
2581
+ <span style="font-weight: 600; color: #a5b4fc;">&lt;${note.elementContext.componentSource.componentName}&gt;</span>
2582
+ ${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>` : ""}
2583
+ ${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>` : ""}
2584
+ </div>` : ""}
2585
+
2201
2586
  <div class="bt-target-pill" title="${contextText}">
2202
2587
  <span>\u{1F3F7}\uFE0F</span>
2203
2588
  <span class="bt-pill-content">${contextText}</span>
@@ -2289,65 +2674,83 @@ var NoteInspector = class {
2289
2674
  }
2290
2675
  setupListeners() {
2291
2676
  const onMouseMove = (e) => {
2292
- if (this.modalOverlay || this.cardOverlay || this.isDraggingRegion || this.activeMode === "region") return;
2293
- const path = e.composedPath ? e.composedPath() : [];
2294
- if (this.container && path.includes(this.container)) {
2295
- this.hideHighlight();
2296
- return;
2297
- }
2298
- if (e.altKey || this.activeMode === "element") {
2299
- const target = document.elementFromPoint(e.clientX, e.clientY);
2300
- if (target && target !== this.container && !this.container?.contains(target)) {
2301
- this.hoveredElement = target;
2302
- this.updateHighlight(target);
2677
+ try {
2678
+ if (this.isHidden || this.modalOverlay || this.cardOverlay || this.isDraggingRegion || this.activeMode === "region") return;
2679
+ const path = e.composedPath ? e.composedPath() : [];
2680
+ if (this.container && path.includes(this.container)) {
2681
+ this.hideHighlight();
2303
2682
  return;
2304
2683
  }
2305
- }
2306
- if (!e.altKey && this.activeMode !== "element") {
2307
- this.hideHighlight();
2684
+ if (e.altKey || this.activeMode === "element") {
2685
+ const target = document.elementFromPoint(e.clientX, e.clientY);
2686
+ if (target && target !== this.container && !this.container?.contains(target)) {
2687
+ this.hoveredElement = target;
2688
+ this.updateHighlight(target);
2689
+ return;
2690
+ }
2691
+ }
2692
+ if (!e.altKey && this.activeMode !== "element") {
2693
+ this.hideHighlight();
2694
+ }
2695
+ } catch {
2308
2696
  }
2309
2697
  };
2310
2698
  const onClick = (e) => {
2311
- if (this.modalOverlay || this.cardOverlay) return;
2312
- const path = e.composedPath ? e.composedPath() : [];
2313
- if (this.container && path.includes(this.container)) {
2314
- return;
2315
- }
2316
- if (this.activeMode === "region") return;
2317
- if (e.altKey || this.activeMode === "element") {
2318
- e.preventDefault();
2319
- e.stopPropagation();
2320
- const target = this.hoveredElement || document.elementFromPoint(e.clientX, e.clientY);
2321
- if (target && target !== this.container && !this.container?.contains(target)) {
2322
- this.selectedElement = target;
2323
- this.openNoteEditor(target, "element");
2324
- if (this.activeMode === "element" && !this.activeScenario) {
2325
- this.setMode("idle");
2699
+ try {
2700
+ if (this.isHidden || this.modalOverlay || this.cardOverlay) return;
2701
+ const path = e.composedPath ? e.composedPath() : [];
2702
+ if (this.container && path.includes(this.container)) {
2703
+ return;
2704
+ }
2705
+ if (this.activeMode === "region") return;
2706
+ if (e.altKey || this.activeMode === "element") {
2707
+ e.preventDefault();
2708
+ e.stopPropagation();
2709
+ const target = this.hoveredElement || document.elementFromPoint(e.clientX, e.clientY);
2710
+ if (target && target !== this.container && !this.container?.contains(target)) {
2711
+ this.selectedElement = target;
2712
+ this.openNoteEditor(target, "element");
2713
+ if (this.activeMode === "element" && !this.activeScenario) {
2714
+ this.setMode("idle");
2715
+ }
2326
2716
  }
2327
2717
  }
2718
+ } catch {
2328
2719
  }
2329
2720
  };
2330
2721
  const onKeyDown = (e) => {
2331
- if (e.key === "Escape") {
2332
- if (this.cardOverlay && this.cardOverlay.parentElement && this.shadowRoot) {
2333
- this.shadowRoot.removeChild(this.cardOverlay);
2334
- this.cardOverlay = null;
2335
- } else if (this.activeMode === "region" || this.activeMode === "element") {
2336
- if (!this.activeScenario) {
2337
- this.setMode("idle");
2722
+ try {
2723
+ if (e.key === "Escape") {
2724
+ if (this.cardOverlay && this.cardOverlay.parentElement && this.shadowRoot) {
2725
+ this.shadowRoot.removeChild(this.cardOverlay);
2726
+ this.cardOverlay = null;
2727
+ } else if (this.activeMode === "region" || this.activeMode === "element") {
2728
+ if (!this.activeScenario) {
2729
+ this.setMode("idle");
2730
+ }
2338
2731
  }
2339
2732
  }
2733
+ } catch {
2340
2734
  }
2341
2735
  };
2342
2736
  const onKeyUp = (e) => {
2343
- if (e.key === "Alt" && !this.modalOverlay && !this.cardOverlay && this.activeMode !== "element") {
2344
- this.hideHighlight();
2737
+ try {
2738
+ if (e.key === "Alt" && !this.modalOverlay && !this.cardOverlay && this.activeMode !== "element") {
2739
+ this.hideHighlight();
2740
+ }
2741
+ } catch {
2345
2742
  }
2346
2743
  };
2347
2744
  const onScrollOrResize = () => {
2348
- requestAnimationFrame(() => {
2349
- this.updateMarkerPositions();
2350
- });
2745
+ try {
2746
+ requestAnimationFrame(() => {
2747
+ try {
2748
+ this.updateMarkerPositions();
2749
+ } catch {
2750
+ }
2751
+ });
2752
+ } catch {
2753
+ }
2351
2754
  };
2352
2755
  window.addEventListener("mousemove", onMouseMove, { capture: true, passive: true });
2353
2756
  window.addEventListener("click", onClick, { capture: true });
@@ -2496,10 +2899,12 @@ var NoteInspector = class {
2496
2899
  if (noteType === "element") {
2497
2900
  this.updateHighlight(targetEl);
2498
2901
  }
2902
+ const comp = noteType === "element" ? resolveComponentSource(targetEl) : void 0;
2903
+ const compPrefix = comp?.componentName ? `\u{1F9EC} <${comp.componentName}>${comp.sourceFile ? " (" + comp.sourceFile + (comp.sourceLine ? ":" + comp.sourceLine : "") + ")" : ""} \xB7 ` : "";
2499
2904
  this.renderNoteModal({
2500
2905
  title: this.activeScenario ? `Step ${this.activeScenario.stepNumber}: ${this.activeScenario.title}` : "Add Visual Note",
2501
2906
  modeBadge: this.activeScenario ? `STEP ${this.activeScenario.stepNumber}` : noteType.toUpperCase(),
2502
- 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}`,
2907
+ 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}`,
2503
2908
  onSave: async (message, action) => {
2504
2909
  let scenarioParam;
2505
2910
  if (action === "next_step" && !this.activeScenario) {
@@ -2769,12 +3174,14 @@ var NoteInspector = class {
2769
3174
  if (innerText && innerText.length > 200) {
2770
3175
  innerText = truncate(innerText, 200);
2771
3176
  }
3177
+ const componentSource = resolveComponentSource(el);
2772
3178
  return {
2773
3179
  selector,
2774
3180
  tag: el.tagName.toLowerCase(),
2775
3181
  attributes,
2776
3182
  outerHTML,
2777
3183
  innerText,
3184
+ componentSource,
2778
3185
  parent: el.parentElement ? {
2779
3186
  selector: getSemanticSelector(el.parentElement),
2780
3187
  tag: el.parentElement.tagName.toLowerCase()
@@ -2805,7 +3212,11 @@ var NoteInspector = class {
2805
3212
  region.width,
2806
3213
  region.height
2807
3214
  );
2808
- resolve(canvas.toDataURL("image/png"));
3215
+ try {
3216
+ resolve(canvas.toDataURL("image/png"));
3217
+ } catch {
3218
+ resolve(dataUrl);
3219
+ }
2809
3220
  };
2810
3221
  img.onerror = () => resolve(dataUrl);
2811
3222
  img.src = dataUrl;
@@ -2857,7 +3268,11 @@ var BrowserTrackClient = class {
2857
3268
  if (this.options.notes.enabled) {
2858
3269
  this.inspector = new NoteInspector(this.transport, this.screenshotDriver, {
2859
3270
  shortcut: this.options.notes.shortcut,
2860
- maskSelectors: this.options.notes.maskSelectors
3271
+ maskSelectors: this.options.notes.maskSelectors,
3272
+ showToolbar: this.options.notes.showToolbar,
3273
+ showBadges: this.options.notes.showBadges,
3274
+ hidden: this.options.hidden,
3275
+ hideQueryParam: this.options.hideQueryParam
2861
3276
  });
2862
3277
  }
2863
3278
  }
@@ -2923,6 +3338,14 @@ var BrowserTrackClient = class {
2923
3338
  this.inspector.setMode("element");
2924
3339
  }
2925
3340
  }
3341
+ isUIVisible() {
3342
+ return this.inspector ? this.inspector.isVisible() : false;
3343
+ }
3344
+ setUIVisible(visible) {
3345
+ if (this.inspector) {
3346
+ this.inspector.setVisible(visible);
3347
+ }
3348
+ }
2926
3349
  setInspectMode(mode) {
2927
3350
  if (this.inspector) {
2928
3351
  this.inspector.setMode(mode);
@@ -3063,8 +3486,10 @@ if (typeof window !== "undefined") {
3063
3486
  const autoInit = currentScript?.getAttribute("data-auto-init") !== "false";
3064
3487
  const daemonUrl = currentScript?.getAttribute("data-daemon-url") || void 0;
3065
3488
  const projectId = currentScript?.getAttribute("data-project-id") || void 0;
3489
+ const hidden = currentScript?.getAttribute("data-hidden") === "true";
3490
+ const hideQueryParam = currentScript?.getAttribute("data-hide-query-param") || void 0;
3066
3491
  if (autoInit) {
3067
- init({ daemonUrl, projectId });
3492
+ init({ daemonUrl, projectId, hidden, hideQueryParam });
3068
3493
  }
3069
3494
  } catch {
3070
3495
  }
@@ -3077,5 +3502,7 @@ if (typeof window !== "undefined") {
3077
3502
  DEFAULT_OPTIONS,
3078
3503
  NoteInspector,
3079
3504
  getClient,
3080
- init
3505
+ init,
3506
+ resolveComponentSource,
3507
+ shouldHideUIFromUrl
3081
3508
  });