browsertrack 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/AGENTS.md +2 -0
  2. package/dist/{chunk-UP5JKCFY.js → chunk-464D4U2U.js} +3 -2
  3. package/dist/chunk-464D4U2U.js.map +1 -0
  4. package/dist/{chunk-WB7ZKWK7.js → chunk-INXDWPJW.js} +354 -9
  5. package/dist/chunk-INXDWPJW.js.map +1 -0
  6. package/dist/cli/index.js +2 -1
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/client/index.cjs +357 -10
  9. package/dist/client/index.d.ts +36 -4
  10. package/dist/client/index.js +7 -3
  11. package/dist/client.iife.js +42 -19
  12. package/dist/{notes-BMnonq46.d.ts → commands-fjuqKzkm.d.ts} +125 -114
  13. package/dist/core/index.d.ts +2 -2
  14. package/dist/daemon/index.d.ts +3 -3
  15. package/dist/{engine-B43IohQY.d.ts → engine-CmchnMDq.d.ts} +2 -2
  16. package/dist/index.d.ts +4 -4
  17. package/dist/index.js +2 -2
  18. package/dist/mcp/index.d.ts +4 -4
  19. package/dist/mcp/index.js +1 -1
  20. package/dist/{projects-D5J-egVN.d.ts → projects-DB7S312i.d.ts} +1 -1
  21. package/dist/{server-BztYp1Zc.d.ts → server-DiVmTrIR.d.ts} +1 -1
  22. package/docs/component-resolver.md +108 -0
  23. package/docs/index.md +1 -0
  24. package/docs/mcp-reference.md +2 -2
  25. package/docs/visual-notes.md +36 -0
  26. package/package.json +1 -1
  27. package/packages/client/src/client.ts +18 -1
  28. package/packages/client/src/config.ts +84 -1
  29. package/packages/client/src/index.ts +4 -1
  30. package/packages/client/src/interceptors/interaction.ts +3 -0
  31. package/packages/client/src/notes/inspector.ts +107 -5
  32. package/packages/client/src/source/resolver.ts +272 -0
  33. package/packages/core/src/types/events.ts +3 -0
  34. package/packages/core/src/types/notes.ts +11 -0
  35. package/packages/mcp/src/handlers.ts +1 -0
  36. package/test/client/component-resolver.test.ts +141 -0
  37. package/test/client/interceptors.test.ts +56 -0
  38. package/dist/chunk-UP5JKCFY.js.map +0 -1
  39. 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,220 @@ function truncate(str, maxLength = 200) {
167
169
  return str.slice(0, maxLength) + "...";
168
170
  }
169
171
 
172
+ // packages/client/src/source/resolver.ts
173
+ function resolveComponentSource(el) {
174
+ if (!el || typeof el !== "object") return void 0;
175
+ const reactInfo = resolveReactComponent(el);
176
+ if (reactInfo) return reactInfo;
177
+ const vueInfo = resolveVueComponent(el);
178
+ if (vueInfo) return vueInfo;
179
+ const svelteInfo = resolveSvelteComponent(el);
180
+ if (svelteInfo) return svelteInfo;
181
+ if (el.tagName && el.tagName.includes("-")) {
182
+ return {
183
+ framework: "web-component",
184
+ componentName: el.tagName.toLowerCase(),
185
+ hierarchy: [el.tagName.toLowerCase()]
186
+ };
187
+ }
188
+ const attrInfo = resolveDataAttributeComponent(el);
189
+ if (attrInfo) return attrInfo;
190
+ return void 0;
191
+ }
192
+ function resolveReactComponent(el) {
193
+ try {
194
+ const fiberKey = Object.keys(el).find(
195
+ (key) => key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$")
196
+ );
197
+ if (!fiberKey) return void 0;
198
+ const hostFiber = el[fiberKey];
199
+ if (!hostFiber) return void 0;
200
+ let sourceFile;
201
+ let sourceLine;
202
+ let sourceColumn;
203
+ let componentName;
204
+ const hierarchy = [];
205
+ let props;
206
+ if (hostFiber._debugSource) {
207
+ sourceFile = hostFiber._debugSource.fileName;
208
+ sourceLine = hostFiber._debugSource.lineNumber;
209
+ sourceColumn = hostFiber._debugSource.columnNumber;
210
+ }
211
+ if (hostFiber._debugOwner) {
212
+ const ownerType = hostFiber._debugOwner.type;
213
+ componentName = getReactComponentName(ownerType);
214
+ if (!sourceFile && hostFiber._debugOwner._debugSource) {
215
+ sourceFile = hostFiber._debugOwner._debugSource.fileName;
216
+ sourceLine = hostFiber._debugOwner._debugSource.lineNumber;
217
+ sourceColumn = hostFiber._debugOwner._debugSource.columnNumber;
218
+ }
219
+ }
220
+ let curr = hostFiber;
221
+ while (curr) {
222
+ const type = curr.type;
223
+ const name = getReactComponentName(type);
224
+ if (name && !name.startsWith("html:") && name !== "Fragment") {
225
+ if (!componentName) {
226
+ componentName = name;
227
+ }
228
+ if (!hierarchy.includes(name)) {
229
+ hierarchy.unshift(name);
230
+ }
231
+ if (!sourceFile && curr._debugSource) {
232
+ sourceFile = curr._debugSource.fileName;
233
+ sourceLine = curr._debugSource.lineNumber;
234
+ sourceColumn = curr._debugSource.columnNumber;
235
+ }
236
+ if (!props && curr.memoizedProps && typeof curr.memoizedProps === "object") {
237
+ props = sanitizeProps(curr.memoizedProps);
238
+ }
239
+ }
240
+ curr = curr.return;
241
+ }
242
+ if (!componentName && !sourceFile) {
243
+ return void 0;
244
+ }
245
+ return {
246
+ framework: "react",
247
+ componentName,
248
+ sourceFile: normalizeFilePath(sourceFile),
249
+ sourceLine,
250
+ sourceColumn,
251
+ hierarchy: hierarchy.length > 0 ? hierarchy : componentName ? [componentName] : void 0,
252
+ props
253
+ };
254
+ } catch {
255
+ return void 0;
256
+ }
257
+ }
258
+ function getReactComponentName(type) {
259
+ if (!type) return void 0;
260
+ if (typeof type === "string") return void 0;
261
+ if (type.displayName) return type.displayName;
262
+ if (type.name) return type.name;
263
+ if (type.render?.displayName) return type.render.displayName;
264
+ if (type.render?.name) return type.render.name;
265
+ return void 0;
266
+ }
267
+ function resolveVueComponent(el) {
268
+ try {
269
+ const vueParent = el.__vueParentComponent || el.__vnode?.ctx;
270
+ if (vueParent) {
271
+ const type = vueParent.type || {};
272
+ const componentName = type.name || type.__name || type.displayName || "AnonymousComponent";
273
+ const sourceFile = type.__file;
274
+ const hierarchy = [];
275
+ let curr = vueParent;
276
+ while (curr) {
277
+ const cType = curr.type || {};
278
+ const cName = cType.name || cType.__name || cType.displayName;
279
+ if (cName && !hierarchy.includes(cName)) {
280
+ hierarchy.unshift(cName);
281
+ }
282
+ curr = curr.parent;
283
+ }
284
+ return {
285
+ framework: "vue",
286
+ componentName,
287
+ sourceFile: normalizeFilePath(sourceFile),
288
+ hierarchy: hierarchy.length > 0 ? hierarchy : [componentName],
289
+ props: vueParent.props ? sanitizeProps(vueParent.props) : void 0
290
+ };
291
+ }
292
+ const vue2Instance = el.__vue__;
293
+ if (vue2Instance) {
294
+ const options = vue2Instance.$options || {};
295
+ const componentName = options.name || options._componentTag || "VueComponent";
296
+ const sourceFile = options.__file;
297
+ return {
298
+ framework: "vue",
299
+ componentName,
300
+ sourceFile: normalizeFilePath(sourceFile),
301
+ hierarchy: [componentName],
302
+ props: vue2Instance.$props ? sanitizeProps(vue2Instance.$props) : void 0
303
+ };
304
+ }
305
+ return void 0;
306
+ } catch {
307
+ return void 0;
308
+ }
309
+ }
310
+ function resolveSvelteComponent(el) {
311
+ try {
312
+ let curr = el;
313
+ while (curr) {
314
+ const meta = curr.__svelte_meta;
315
+ if (meta && meta.loc) {
316
+ return {
317
+ framework: "svelte",
318
+ sourceFile: normalizeFilePath(meta.loc.file),
319
+ sourceLine: meta.loc.line,
320
+ sourceColumn: meta.loc.column,
321
+ componentName: meta.loc.file ? getBaseNameWithoutExt(meta.loc.file) : void 0,
322
+ hierarchy: meta.loc.file ? [getBaseNameWithoutExt(meta.loc.file)] : void 0
323
+ };
324
+ }
325
+ curr = curr.parentElement;
326
+ }
327
+ return void 0;
328
+ } catch {
329
+ return void 0;
330
+ }
331
+ }
332
+ function resolveDataAttributeComponent(el) {
333
+ try {
334
+ const compEl = el.closest("[data-component], [data-component-name], [data-source-file]");
335
+ if (!compEl) return void 0;
336
+ const componentName = compEl.getAttribute("data-component") || compEl.getAttribute("data-component-name") || void 0;
337
+ const sourceFile = compEl.getAttribute("data-source-file") || void 0;
338
+ const lineAttr = compEl.getAttribute("data-source-line");
339
+ const sourceLine = lineAttr ? parseInt(lineAttr, 10) : void 0;
340
+ if (!componentName && !sourceFile) return void 0;
341
+ return {
342
+ framework: "vanilla",
343
+ componentName,
344
+ sourceFile: normalizeFilePath(sourceFile),
345
+ sourceLine: isNaN(sourceLine) ? void 0 : sourceLine,
346
+ hierarchy: componentName ? [componentName] : void 0
347
+ };
348
+ } catch {
349
+ return void 0;
350
+ }
351
+ }
352
+ function normalizeFilePath(filePath) {
353
+ if (!filePath) return void 0;
354
+ const cleaned = filePath.split("?")[0];
355
+ return cleaned;
356
+ }
357
+ function getBaseNameWithoutExt(filePath) {
358
+ const parts = filePath.split("/");
359
+ const last = parts[parts.length - 1] || filePath;
360
+ return last.split(".")[0] || last;
361
+ }
362
+ function sanitizeProps(props) {
363
+ const result = {};
364
+ for (const [key, value] of Object.entries(props)) {
365
+ if (key.startsWith("__") || typeof value === "function") continue;
366
+ if (value === null || value === void 0) {
367
+ result[key] = value;
368
+ } else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
369
+ result[key] = value;
370
+ } else if (Array.isArray(value)) {
371
+ result[key] = `Array(${value.length})`;
372
+ } else if (typeof value === "object") {
373
+ result[key] = "[Object]";
374
+ }
375
+ }
376
+ return result;
377
+ }
378
+
170
379
  // packages/client/src/interceptors/interaction.ts
171
380
  function extractElementSummary(el) {
172
381
  const selector = getSemanticSelector(el);
173
382
  const tag = el.tagName.toLowerCase();
174
383
  const id = el.id || void 0;
175
384
  const classes = Array.from(el.classList || []);
385
+ const componentSource = resolveComponentSource(el);
176
386
  let boundingRect = void 0;
177
387
  let visible = true;
178
388
  try {
@@ -204,7 +414,8 @@ function extractElementSummary(el) {
204
414
  boundingRect,
205
415
  visible,
206
416
  innerText,
207
- outerHTML
417
+ outerHTML,
418
+ componentSource
208
419
  };
209
420
  }
210
421
  function setupInteractionInterceptors(onInteraction) {
@@ -522,10 +733,62 @@ var DEFAULT_OPTIONS = {
522
733
  enabled: true,
523
734
  shortcut: "Alt+Click",
524
735
  showBadges: true,
736
+ showToolbar: true,
525
737
  maskSelectors: ['input[type="password"]', "[data-sensitive]"]
526
738
  },
739
+ hidden: false,
740
+ hideQueryParam: void 0,
527
741
  debug: false
528
742
  };
743
+ var HIDE_VALUES = /* @__PURE__ */ new Set(["0", "false", "hidden", "hide", "off", "none", "disabled", "ui_off", "silent"]);
744
+ function shouldHideUIFromUrl(customParam, searchString) {
745
+ let search = searchString;
746
+ if (search === void 0) {
747
+ if (typeof window === "undefined" || !window.location) return false;
748
+ search = window.location.search;
749
+ }
750
+ if (!search) return false;
751
+ try {
752
+ const params = new URLSearchParams(search);
753
+ if (customParam) {
754
+ const customKeys = Array.isArray(customParam) ? customParam : [customParam];
755
+ for (const key of customKeys) {
756
+ if (params.has(key)) {
757
+ const val = (params.get(key) || "").toLowerCase().trim();
758
+ if (val === "" || val === "1" || val === "true" || HIDE_VALUES.has(val)) {
759
+ return true;
760
+ }
761
+ }
762
+ }
763
+ }
764
+ for (const flag of ["no_bt", "no_browsertrack", "hide_bt", "hide_browsertrack"]) {
765
+ if (params.has(flag)) {
766
+ const val = (params.get(flag) || "").toLowerCase().trim();
767
+ if (val === "" || val === "1" || val === "true" || val === "yes") {
768
+ return true;
769
+ }
770
+ }
771
+ }
772
+ if (params.has("bt")) {
773
+ const val = (params.get("bt") || "").toLowerCase().trim();
774
+ if (HIDE_VALUES.has(val)) return true;
775
+ }
776
+ if (params.has("browsertrack")) {
777
+ const val = (params.get("browsertrack") || "").toLowerCase().trim();
778
+ if (HIDE_VALUES.has(val)) return true;
779
+ }
780
+ if (params.has("bt_ui")) {
781
+ const val = (params.get("bt_ui") || "").toLowerCase().trim();
782
+ if (HIDE_VALUES.has(val) || val === "0" || val === "false") return true;
783
+ }
784
+ if (params.has("bt_hide")) {
785
+ const val = (params.get("bt_hide") || "").toLowerCase().trim();
786
+ if (val === "" || val === "1" || val === "true") return true;
787
+ }
788
+ } catch {
789
+ }
790
+ return false;
791
+ }
529
792
 
530
793
  // packages/client/src/interceptors/console.ts
531
794
  function setupConsoleInterceptors(onConsole) {
@@ -1195,13 +1458,18 @@ var NoteInspector = class {
1195
1458
  __publicField(this, "dragStartY", 0);
1196
1459
  __publicField(this, "savedNotes", []);
1197
1460
  __publicField(this, "showMarkers", true);
1461
+ __publicField(this, "isHidden", false);
1198
1462
  __publicField(this, "cleanups", []);
1199
1463
  this.transport = transport;
1200
1464
  this.screenshotDriver = screenshotDriver;
1465
+ const hiddenByQuery = shouldHideUIFromUrl(options.hideQueryParam);
1466
+ this.isHidden = options.hidden === true || hiddenByQuery;
1467
+ this.showMarkers = options.showBadges !== false && !this.isHidden;
1201
1468
  this.options = {
1202
1469
  shortcut: "Alt+Click",
1203
1470
  maskSelectors: ['input[type="password"]', "[data-sensitive]"],
1204
1471
  showToolbar: true,
1472
+ showBadges: true,
1205
1473
  ...options
1206
1474
  };
1207
1475
  }
@@ -1279,12 +1547,48 @@ var NoteInspector = class {
1279
1547
  this.hideHighlight();
1280
1548
  }
1281
1549
  }
1550
+ isVisible() {
1551
+ return !this.isHidden;
1552
+ }
1553
+ setVisible(visible) {
1554
+ this.isHidden = !visible;
1555
+ this.showMarkers = this.options.showBadges !== false && !this.isHidden;
1556
+ if (this.container) {
1557
+ this.container.style.display = this.isHidden ? "none" : "block";
1558
+ }
1559
+ if (this.toolbarElement) {
1560
+ this.toolbarElement.style.display = this.isHidden ? "none" : "flex";
1561
+ } else if (!this.isHidden && this.options.showToolbar !== false) {
1562
+ this.createToolbar();
1563
+ }
1564
+ if (this.isHidden) {
1565
+ this.hideHighlight();
1566
+ this.hideRegionOverlay();
1567
+ if (this.modalOverlay && this.shadowRoot) {
1568
+ this.shadowRoot.removeChild(this.modalOverlay);
1569
+ this.modalOverlay = null;
1570
+ }
1571
+ if (this.cardOverlay && this.shadowRoot) {
1572
+ this.shadowRoot.removeChild(this.cardOverlay);
1573
+ this.cardOverlay = null;
1574
+ }
1575
+ if (this.markersContainer) {
1576
+ this.markersContainer.innerHTML = "";
1577
+ }
1578
+ } else {
1579
+ this.renderMarkers();
1580
+ this.updateToolbarCount();
1581
+ }
1582
+ }
1282
1583
  ensureContainer() {
1283
1584
  if (typeof document === "undefined") return null;
1284
1585
  if (!this.container) {
1285
1586
  this.container = document.createElement("div");
1286
1587
  this.container.id = "browsertrack-inspector-host";
1287
1588
  this.container.style.cssText = "all: initial; position: fixed; top: 0; left: 0; width: 0; height: 0; z-index: 2147483647; pointer-events: none;";
1589
+ if (this.isHidden) {
1590
+ this.container.style.display = "none";
1591
+ }
1288
1592
  this.shadowRoot = this.container.attachShadow({ mode: "open" });
1289
1593
  const style = document.createElement("style");
1290
1594
  style.textContent = `
@@ -1727,6 +2031,22 @@ var NoteInspector = class {
1727
2031
  color: #cbd5e1;
1728
2032
  }
1729
2033
 
2034
+ .bt-component-pill {
2035
+ display: flex;
2036
+ align-items: center;
2037
+ gap: 6px;
2038
+ background: rgba(15, 23, 42, 0.95);
2039
+ border: 1px solid rgba(99, 102, 241, 0.35);
2040
+ border-radius: 6px;
2041
+ padding: 6px 10px;
2042
+ font-size: 11.5px;
2043
+ color: #c7d2fe;
2044
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
2045
+ overflow: hidden;
2046
+ text-overflow: ellipsis;
2047
+ white-space: nowrap;
2048
+ }
2049
+
1730
2050
  .bt-scenario-stepper {
1731
2051
  display: flex;
1732
2052
  align-items: center;
@@ -1966,7 +2286,7 @@ var NoteInspector = class {
1966
2286
  this.toastContainer = document.createElement("div");
1967
2287
  this.toastContainer.className = "bt-toast-container";
1968
2288
  this.shadowRoot.appendChild(this.toastContainer);
1969
- if (this.options.showToolbar) {
2289
+ if (this.options.showToolbar && !this.isHidden) {
1970
2290
  this.createToolbar();
1971
2291
  }
1972
2292
  }
@@ -2067,7 +2387,7 @@ var NoteInspector = class {
2067
2387
  const root = this.ensureContainer();
2068
2388
  if (!root || !this.markersContainer) return;
2069
2389
  this.markersContainer.innerHTML = "";
2070
- if (!this.showMarkers) return;
2390
+ if (!this.showMarkers || this.isHidden) return;
2071
2391
  const currentPath = window.location.pathname;
2072
2392
  const activeNotes = this.savedNotes.filter(
2073
2393
  (n) => n.status === "OPEN" && (n.route === currentPath || !n.route || n.route === "/" || window.location.href.includes(n.route))
@@ -2198,6 +2518,13 @@ var NoteInspector = class {
2198
2518
 
2199
2519
  ${scenarioStepsHtml}
2200
2520
 
2521
+ ${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}">
2522
+ <span>\u{1F9EC}</span>
2523
+ <span style="font-weight: 600; color: #a5b4fc;">&lt;${note.elementContext.componentSource.componentName}&gt;</span>
2524
+ ${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>` : ""}
2525
+ ${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>` : ""}
2526
+ </div>` : ""}
2527
+
2201
2528
  <div class="bt-target-pill" title="${contextText}">
2202
2529
  <span>\u{1F3F7}\uFE0F</span>
2203
2530
  <span class="bt-pill-content">${contextText}</span>
@@ -2289,7 +2616,7 @@ var NoteInspector = class {
2289
2616
  }
2290
2617
  setupListeners() {
2291
2618
  const onMouseMove = (e) => {
2292
- if (this.modalOverlay || this.cardOverlay || this.isDraggingRegion || this.activeMode === "region") return;
2619
+ if (this.isHidden || this.modalOverlay || this.cardOverlay || this.isDraggingRegion || this.activeMode === "region") return;
2293
2620
  const path = e.composedPath ? e.composedPath() : [];
2294
2621
  if (this.container && path.includes(this.container)) {
2295
2622
  this.hideHighlight();
@@ -2308,7 +2635,7 @@ var NoteInspector = class {
2308
2635
  }
2309
2636
  };
2310
2637
  const onClick = (e) => {
2311
- if (this.modalOverlay || this.cardOverlay) return;
2638
+ if (this.isHidden || this.modalOverlay || this.cardOverlay) return;
2312
2639
  const path = e.composedPath ? e.composedPath() : [];
2313
2640
  if (this.container && path.includes(this.container)) {
2314
2641
  return;
@@ -2496,10 +2823,12 @@ var NoteInspector = class {
2496
2823
  if (noteType === "element") {
2497
2824
  this.updateHighlight(targetEl);
2498
2825
  }
2826
+ const comp = noteType === "element" ? resolveComponentSource(targetEl) : void 0;
2827
+ const compPrefix = comp?.componentName ? `\u{1F9EC} <${comp.componentName}>${comp.sourceFile ? " (" + comp.sourceFile + (comp.sourceLine ? ":" + comp.sourceLine : "") + ")" : ""} \xB7 ` : "";
2499
2828
  this.renderNoteModal({
2500
2829
  title: this.activeScenario ? `Step ${this.activeScenario.stepNumber}: ${this.activeScenario.title}` : "Add Visual Note",
2501
2830
  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}`,
2831
+ 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
2832
  onSave: async (message, action) => {
2504
2833
  let scenarioParam;
2505
2834
  if (action === "next_step" && !this.activeScenario) {
@@ -2769,12 +3098,14 @@ var NoteInspector = class {
2769
3098
  if (innerText && innerText.length > 200) {
2770
3099
  innerText = truncate(innerText, 200);
2771
3100
  }
3101
+ const componentSource = resolveComponentSource(el);
2772
3102
  return {
2773
3103
  selector,
2774
3104
  tag: el.tagName.toLowerCase(),
2775
3105
  attributes,
2776
3106
  outerHTML,
2777
3107
  innerText,
3108
+ componentSource,
2778
3109
  parent: el.parentElement ? {
2779
3110
  selector: getSemanticSelector(el.parentElement),
2780
3111
  tag: el.parentElement.tagName.toLowerCase()
@@ -2857,7 +3188,11 @@ var BrowserTrackClient = class {
2857
3188
  if (this.options.notes.enabled) {
2858
3189
  this.inspector = new NoteInspector(this.transport, this.screenshotDriver, {
2859
3190
  shortcut: this.options.notes.shortcut,
2860
- maskSelectors: this.options.notes.maskSelectors
3191
+ maskSelectors: this.options.notes.maskSelectors,
3192
+ showToolbar: this.options.notes.showToolbar,
3193
+ showBadges: this.options.notes.showBadges,
3194
+ hidden: this.options.hidden,
3195
+ hideQueryParam: this.options.hideQueryParam
2861
3196
  });
2862
3197
  }
2863
3198
  }
@@ -2923,6 +3258,14 @@ var BrowserTrackClient = class {
2923
3258
  this.inspector.setMode("element");
2924
3259
  }
2925
3260
  }
3261
+ isUIVisible() {
3262
+ return this.inspector ? this.inspector.isVisible() : false;
3263
+ }
3264
+ setUIVisible(visible) {
3265
+ if (this.inspector) {
3266
+ this.inspector.setVisible(visible);
3267
+ }
3268
+ }
2926
3269
  setInspectMode(mode) {
2927
3270
  if (this.inspector) {
2928
3271
  this.inspector.setMode(mode);
@@ -3063,8 +3406,10 @@ if (typeof window !== "undefined") {
3063
3406
  const autoInit = currentScript?.getAttribute("data-auto-init") !== "false";
3064
3407
  const daemonUrl = currentScript?.getAttribute("data-daemon-url") || void 0;
3065
3408
  const projectId = currentScript?.getAttribute("data-project-id") || void 0;
3409
+ const hidden = currentScript?.getAttribute("data-hidden") === "true";
3410
+ const hideQueryParam = currentScript?.getAttribute("data-hide-query-param") || void 0;
3066
3411
  if (autoInit) {
3067
- init({ daemonUrl, projectId });
3412
+ init({ daemonUrl, projectId, hidden, hideQueryParam });
3068
3413
  }
3069
3414
  } catch {
3070
3415
  }
@@ -3077,5 +3422,7 @@ if (typeof window !== "undefined") {
3077
3422
  DEFAULT_OPTIONS,
3078
3423
  NoteInspector,
3079
3424
  getClient,
3080
- init
3425
+ init,
3426
+ resolveComponentSource,
3427
+ shouldHideUIFromUrl
3081
3428
  });
@@ -1,4 +1,4 @@
1
- import { B as Breadcrumb, i as ElementSummary, b as ClientCommand, d as CommandResponse, v as VisualNote, R as RegionContext } from '../notes-BMnonq46.js';
1
+ import { B as Breadcrumb, j as ElementSummary, b as ClientCommand, d as CommandResponse, w as VisualNote, R as RegionContext, f as ComponentSourceInfo } from '../commands-fjuqKzkm.js';
2
2
 
3
3
  interface BrowserDiagClientOptions {
4
4
  daemonUrl?: string;
@@ -14,16 +14,32 @@ interface BrowserDiagClientOptions {
14
14
  enabled?: boolean;
15
15
  shortcut?: string;
16
16
  showBadges?: boolean;
17
+ showToolbar?: boolean;
17
18
  maskSelectors?: string[];
18
19
  };
20
+ /**
21
+ * Hide visible BrowserTrack UI (toolbar dock, pins, hover overlay) by default.
22
+ */
23
+ hidden?: boolean;
24
+ /**
25
+ * Custom query parameter(s) used to hide the UI (e.g. 'e2e', 'no_ui', 'clean').
26
+ * Built-in query params like ?bt=0, ?bt=false, ?bt=hidden, ?browsertrack=false, ?no_bt are supported automatically.
27
+ */
28
+ hideQueryParam?: string | string[];
19
29
  debug?: boolean;
20
30
  }
21
- declare const DEFAULT_OPTIONS: Required<Omit<BrowserDiagClientOptions, 'notes'>> & {
31
+ declare const DEFAULT_OPTIONS: Required<Omit<BrowserDiagClientOptions, 'notes' | 'hideQueryParam'>> & {
32
+ hideQueryParam?: string | string[];
22
33
  notes: Required<NonNullable<BrowserDiagClientOptions['notes']>>;
23
34
  };
35
+ /**
36
+ * Checks if the tool's visible UI should be hidden based on URL search query parameters.
37
+ */
38
+ declare function shouldHideUIFromUrl(customParam?: string | string[], searchString?: string): boolean;
24
39
 
25
40
  declare class BrowserTrackClient {
26
- options: Required<Omit<BrowserDiagClientOptions, 'notes'>> & {
41
+ options: Required<Omit<BrowserDiagClientOptions, 'notes' | 'hideQueryParam'>> & {
42
+ hideQueryParam?: string | string[];
27
43
  notes: Required<NonNullable<BrowserDiagClientOptions['notes']>>;
28
44
  };
29
45
  private breadcrumbs;
@@ -40,6 +56,8 @@ declare class BrowserTrackClient {
40
56
  openNoteEditor(el?: HTMLElement, noteType?: 'element' | 'page'): void;
41
57
  startRegionSelection(): void;
42
58
  startElementSelection(): void;
59
+ isUIVisible(): boolean;
60
+ setUIVisible(visible: boolean): void;
43
61
  setInspectMode(mode: 'element' | 'region' | 'page' | 'idle'): void;
44
62
  createNote(targetEl: HTMLElement, message: string, noteType?: 'element' | 'page'): Promise<void>;
45
63
  private handleRuntimeError;
@@ -140,6 +158,9 @@ interface InspectorOptions {
140
158
  shortcut?: string;
141
159
  maskSelectors?: string[];
142
160
  showToolbar?: boolean;
161
+ showBadges?: boolean;
162
+ hidden?: boolean;
163
+ hideQueryParam?: string | string[];
143
164
  onNoteCreated?: (note: Partial<VisualNote>) => void;
144
165
  }
145
166
  interface ActiveScenarioState {
@@ -157,6 +178,7 @@ interface ActiveScenarioState {
157
178
  * 5. Real-time Saved Note & Step Markers (pins with step sequencing numbers)
158
179
  * 6. Interactive Note & Step Detail Card (with previous/next step walk-through navigation)
159
180
  * 7. Floating quick dock / toolbar in bottom-right corner with Notes count and Flow controls
181
+ * 8. Automatic invisibility via query parameter (?bt=0, ?bt=false, ?bt=hidden, ?no_bt, custom query params)
160
182
  */
161
183
  declare class NoteInspector {
162
184
  private transport;
@@ -183,6 +205,7 @@ declare class NoteInspector {
183
205
  private dragStartY;
184
206
  private savedNotes;
185
207
  private showMarkers;
208
+ private isHidden;
186
209
  private cleanups;
187
210
  constructor(transport: WebSocketTransport, screenshotDriver: ScreenshotDriver, options?: InspectorOptions);
188
211
  init(): void;
@@ -191,6 +214,8 @@ declare class NoteInspector {
191
214
  startScenario(title?: string): void;
192
215
  finishScenario(): void;
193
216
  setMode(mode: NoteInspectMode): void;
217
+ isVisible(): boolean;
218
+ setVisible(visible: boolean): void;
194
219
  private ensureContainer;
195
220
  private createToolbar;
196
221
  private updateToolbarState;
@@ -221,10 +246,17 @@ declare class NoteInspector {
221
246
  destroy(): void;
222
247
  }
223
248
 
249
+ /**
250
+ * Resolves framework component metadata, source file paths, line numbers,
251
+ * and component hierarchy directly from a live DOM element.
252
+ * Supports React (Fiber), Vue 2 & 3, Svelte, Custom Elements, and standard data attributes.
253
+ */
254
+ declare function resolveComponentSource(el: HTMLElement): ComponentSourceInfo | undefined;
255
+
224
256
  /**
225
257
  * Initializes the BrowserTrack client.
226
258
  */
227
259
  declare function init(options?: BrowserDiagClientOptions): BrowserTrackClient;
228
260
  declare function getClient(): BrowserTrackClient | null;
229
261
 
230
- export { type ActiveScenarioState, BreadcrumbBuffer, type BrowserDiagClientOptions, BrowserScriptScreenshotDriver, BrowserTrackClient, DEFAULT_OPTIONS, type InspectorOptions, type NoteInspectMode, NoteInspector, type ScreenshotDriver, type ScreenshotResult, getClient, init };
262
+ export { type ActiveScenarioState, BreadcrumbBuffer, type BrowserDiagClientOptions, BrowserScriptScreenshotDriver, BrowserTrackClient, DEFAULT_OPTIONS, type InspectorOptions, type NoteInspectMode, NoteInspector, type ScreenshotDriver, type ScreenshotResult, getClient, init, resolveComponentSource, shouldHideUIFromUrl };
@@ -5,8 +5,10 @@ import {
5
5
  DEFAULT_OPTIONS,
6
6
  NoteInspector,
7
7
  getClient,
8
- init
9
- } from "../chunk-WB7ZKWK7.js";
8
+ init,
9
+ resolveComponentSource,
10
+ shouldHideUIFromUrl
11
+ } from "../chunk-INXDWPJW.js";
10
12
  import "../chunk-6VA7GBAO.js";
11
13
  export {
12
14
  BreadcrumbBuffer,
@@ -15,6 +17,8 @@ export {
15
17
  DEFAULT_OPTIONS,
16
18
  NoteInspector,
17
19
  getClient,
18
- init
20
+ init,
21
+ resolveComponentSource,
22
+ shouldHideUIFromUrl
19
23
  };
20
24
  //# sourceMappingURL=index.js.map