browsertrack 0.2.1 → 0.2.3

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 (55) hide show
  1. package/AGENTS.md +9 -6
  2. package/README.md +2 -0
  3. package/dist/{chunk-464D4U2U.js → chunk-5NR5K3ER.js} +299 -44
  4. package/dist/chunk-5NR5K3ER.js.map +1 -0
  5. package/dist/{chunk-3HOXPTM2.js → chunk-G5CIZSQM.js} +808 -53
  6. package/dist/chunk-G5CIZSQM.js.map +1 -0
  7. package/dist/{chunk-6VA7GBAO.js → chunk-ONPW7AYL.js} +144 -12
  8. package/dist/chunk-ONPW7AYL.js.map +1 -0
  9. package/dist/{chunk-INXDWPJW.js → chunk-PG4JJDCV.js} +353 -119
  10. package/dist/chunk-PG4JJDCV.js.map +1 -0
  11. package/dist/cli/index.js +1058 -546
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/client/index.cjs +449 -128
  14. package/dist/client/index.d.ts +2 -0
  15. package/dist/client/index.js +2 -2
  16. package/dist/client.iife.js +121 -15
  17. package/dist/core/index.d.ts +32 -2
  18. package/dist/core/index.js +11 -1
  19. package/dist/daemon/index.d.ts +2 -2
  20. package/dist/daemon/index.js +6 -8
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +16 -7
  23. package/dist/mcp/index.d.ts +1 -1
  24. package/dist/mcp/index.js +7 -4
  25. package/dist/{server-DiVmTrIR.d.ts → server-BRG-RQQP.d.ts} +10 -1
  26. package/docs/cli.md +4 -1
  27. package/docs/getting-started.md +60 -6
  28. package/docs/mcp-reference.md +42 -0
  29. package/package.json +1 -1
  30. package/packages/cli/src/index.ts +247 -151
  31. package/packages/client/src/interceptors/navigation.ts +38 -26
  32. package/packages/client/src/interceptors/network.ts +22 -17
  33. package/packages/client/src/notes/inspector.ts +289 -65
  34. package/packages/client/src/source/resolver.ts +9 -3
  35. package/packages/client/src/transport/websocket.ts +23 -18
  36. package/packages/core/src/index.ts +1 -0
  37. package/packages/core/src/safety.ts +86 -0
  38. package/packages/core/src/selector.ts +110 -15
  39. package/packages/daemon/src/server/daemon.ts +7 -1
  40. package/packages/daemon/src/server/http.ts +125 -5
  41. package/packages/daemon/src/server/ws.ts +33 -29
  42. package/packages/daemon/src/storage/db.ts +57 -35
  43. package/packages/mcp/src/handlers.ts +121 -45
  44. package/packages/mcp/src/server.ts +221 -3
  45. package/test/core/safety.test.ts +106 -0
  46. package/test/core/selector.test.ts +133 -0
  47. package/test/daemon/storage.test.ts +36 -0
  48. package/test/e2e/daemon-mcp-e2e.test.ts +10 -0
  49. package/test/mcp/auto-start.test.ts +87 -0
  50. package/dist/chunk-3HOXPTM2.js.map +0 -1
  51. package/dist/chunk-464D4U2U.js.map +0 -1
  52. package/dist/chunk-6VA7GBAO.js.map +0 -1
  53. package/dist/chunk-7OCOQGDN.js +0 -635
  54. package/dist/chunk-7OCOQGDN.js.map +0 -1
  55. package/dist/chunk-INXDWPJW.js.map +0 -1
@@ -1,8 +1,10 @@
1
1
  import {
2
2
  getSemanticSelector,
3
3
  redactUrl,
4
+ resolveMeaningfulTarget,
5
+ safeJsonStringify,
4
6
  truncate
5
- } from "./chunk-6VA7GBAO.js";
7
+ } from "./chunk-ONPW7AYL.js";
6
8
 
7
9
  // packages/client/src/breadcrumbs.ts
8
10
  var BreadcrumbBuffer = class {
@@ -78,7 +80,9 @@ function resolveReactComponent(el) {
78
80
  }
79
81
  }
80
82
  let curr = hostFiber;
81
- while (curr) {
83
+ let depth = 0;
84
+ while (curr && depth < 50) {
85
+ depth++;
82
86
  const type = curr.type;
83
87
  const name = getReactComponentName(type);
84
88
  if (name && !name.startsWith("html:") && name !== "Fragment") {
@@ -133,7 +137,9 @@ function resolveVueComponent(el) {
133
137
  const sourceFile = type.__file;
134
138
  const hierarchy = [];
135
139
  let curr = vueParent;
136
- while (curr) {
140
+ let depth = 0;
141
+ while (curr && depth < 50) {
142
+ depth++;
137
143
  const cType = curr.type || {};
138
144
  const cName = cType.name || cType.__name || cType.displayName;
139
145
  if (cName && !hierarchy.includes(cName)) {
@@ -170,7 +176,9 @@ function resolveVueComponent(el) {
170
176
  function resolveSvelteComponent(el) {
171
177
  try {
172
178
  let curr = el;
173
- while (curr) {
179
+ let depth = 0;
180
+ while (curr && depth < 50) {
181
+ depth++;
174
182
  const meta = curr.__svelte_meta;
175
183
  if (meta && meta.loc) {
176
184
  return {
@@ -709,12 +717,16 @@ function setupConsoleInterceptors(onConsole) {
709
717
  function setupNavigationInterceptors(onNavigation) {
710
718
  if (typeof window === "undefined" || typeof history === "undefined") return () => {
711
719
  };
712
- let currentUrl = redactUrl(window.location.href);
713
- onNavigation({
714
- to: currentUrl,
715
- type: "initial",
716
- timestamp: Date.now()
717
- });
720
+ let currentUrl = "";
721
+ try {
722
+ currentUrl = redactUrl(window.location.href);
723
+ onNavigation({
724
+ to: currentUrl,
725
+ type: "initial",
726
+ timestamp: Date.now()
727
+ });
728
+ } catch {
729
+ }
718
730
  const originalPushState = history.pushState;
719
731
  const originalReplaceState = history.replaceState;
720
732
  history.pushState = function(data, unused, url) {
@@ -750,26 +762,32 @@ function setupNavigationInterceptors(onNavigation) {
750
762
  return result;
751
763
  };
752
764
  const onPopState = () => {
753
- const from = currentUrl;
754
- const to = redactUrl(window.location.href);
755
- currentUrl = to;
756
- onNavigation({
757
- from,
758
- to,
759
- type: "popstate",
760
- timestamp: Date.now()
761
- });
765
+ try {
766
+ const from = currentUrl;
767
+ const to = redactUrl(window.location.href);
768
+ currentUrl = to;
769
+ onNavigation({
770
+ from,
771
+ to,
772
+ type: "popstate",
773
+ timestamp: Date.now()
774
+ });
775
+ } catch {
776
+ }
762
777
  };
763
778
  const onHashChange = () => {
764
- const from = currentUrl;
765
- const to = redactUrl(window.location.href);
766
- currentUrl = to;
767
- onNavigation({
768
- from,
769
- to,
770
- type: "hashchange",
771
- timestamp: Date.now()
772
- });
779
+ try {
780
+ const from = currentUrl;
781
+ const to = redactUrl(window.location.href);
782
+ currentUrl = to;
783
+ onNavigation({
784
+ from,
785
+ to,
786
+ type: "hashchange",
787
+ timestamp: Date.now()
788
+ });
789
+ } catch {
790
+ }
773
791
  };
774
792
  window.addEventListener("popstate", onPopState);
775
793
  window.addEventListener("hashchange", onHashChange);
@@ -806,31 +824,38 @@ function setupNetworkInterceptors(onNetwork) {
806
824
  urlStr = "unknown_url";
807
825
  }
808
826
  const safeUrl = redactUrl(urlStr);
827
+ let response;
809
828
  try {
810
- const response = await originalFetch.apply(window, [input, init2]);
811
- const durationMs = Date.now() - startTime;
812
- onNetwork({
813
- url: safeUrl,
814
- method,
815
- status: response.status,
816
- statusText: response.statusText,
817
- durationMs,
818
- timestamp: startTime
819
- });
820
- return response;
829
+ response = await originalFetch.apply(window, [input, init2]);
821
830
  } catch (err) {
822
- const durationMs = Date.now() - startTime;
831
+ const durationMs2 = Date.now() - startTime;
823
832
  const isAbort = err?.name === "AbortError";
833
+ try {
834
+ onNetwork({
835
+ url: safeUrl,
836
+ method,
837
+ durationMs: durationMs2,
838
+ error: err?.message || "Network request failed",
839
+ aborted: isAbort,
840
+ timestamp: startTime
841
+ });
842
+ } catch {
843
+ }
844
+ throw err;
845
+ }
846
+ const durationMs = Date.now() - startTime;
847
+ try {
824
848
  onNetwork({
825
849
  url: safeUrl,
826
850
  method,
851
+ status: response.status,
852
+ statusText: response.statusText,
827
853
  durationMs,
828
- error: err?.message || "Network request failed",
829
- aborted: isAbort,
830
854
  timestamp: startTime
831
855
  });
832
- throw err;
856
+ } catch {
833
857
  }
858
+ return response;
834
859
  };
835
860
  cleanups.push(() => {
836
861
  window.fetch = originalFetch;
@@ -1216,15 +1241,18 @@ var WebSocketTransport = class {
1216
1241
  }
1217
1242
  }
1218
1243
  send(payload) {
1219
- const raw = JSON.stringify(payload);
1220
- if (this.isConnected()) {
1221
- try {
1222
- this.ws.send(raw);
1223
- } catch {
1244
+ try {
1245
+ const raw = safeJsonStringify(payload);
1246
+ if (this.isConnected()) {
1247
+ try {
1248
+ this.ws.send(raw);
1249
+ } catch {
1250
+ this.enqueue(raw);
1251
+ }
1252
+ } else {
1224
1253
  this.enqueue(raw);
1225
1254
  }
1226
- } else {
1227
- this.enqueue(raw);
1255
+ } catch {
1228
1256
  }
1229
1257
  }
1230
1258
  enqueue(raw) {
@@ -1249,17 +1277,17 @@ var WebSocketTransport = class {
1249
1277
  }
1250
1278
  sendHello() {
1251
1279
  if (typeof window === "undefined") return;
1252
- const hello = {
1253
- type: "hello",
1254
- origin: window.location.origin,
1255
- url: window.location.href,
1256
- title: document.title,
1257
- userAgent: navigator.userAgent,
1258
- timestamp: Date.now(),
1259
- projectId: this.projectId
1260
- };
1261
1280
  try {
1262
- this.ws.send(JSON.stringify(hello));
1281
+ const hello = {
1282
+ type: "hello",
1283
+ origin: window.location.origin,
1284
+ url: window.location.href,
1285
+ title: document.title,
1286
+ userAgent: navigator.userAgent,
1287
+ timestamp: Date.now(),
1288
+ projectId: this.projectId
1289
+ };
1290
+ this.ws.send(safeJsonStringify(hello));
1263
1291
  } catch {
1264
1292
  }
1265
1293
  }
@@ -1316,6 +1344,7 @@ var NoteInspector = class {
1316
1344
  savedNotes = [];
1317
1345
  showMarkers = true;
1318
1346
  isHidden = false;
1347
+ isToolbarCollapsed = false;
1319
1348
  cleanups = [];
1320
1349
  constructor(transport, screenshotDriver, options = {}) {
1321
1350
  this.transport = transport;
@@ -1323,6 +1352,12 @@ var NoteInspector = class {
1323
1352
  const hiddenByQuery = shouldHideUIFromUrl(options.hideQueryParam);
1324
1353
  this.isHidden = options.hidden === true || hiddenByQuery;
1325
1354
  this.showMarkers = options.showBadges !== false && !this.isHidden;
1355
+ try {
1356
+ if (typeof window !== "undefined" && window.sessionStorage) {
1357
+ this.isToolbarCollapsed = window.sessionStorage.getItem("bt_toolbar_collapsed") === "true";
1358
+ }
1359
+ } catch {
1360
+ }
1326
1361
  this.options = {
1327
1362
  shortcut: "Alt+Click",
1328
1363
  maskSelectors: ['input[type="password"]', "[data-sensitive]"],
@@ -1450,11 +1485,28 @@ var NoteInspector = class {
1450
1485
  this.shadowRoot = this.container.attachShadow({ mode: "open" });
1451
1486
  const style = document.createElement("style");
1452
1487
  style.textContent = `
1453
- :host {
1454
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
1455
- color-scheme: dark;
1488
+ @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
1489
+
1490
+ :host, :host *, :host *::before, :host *::after {
1491
+ box-sizing: border-box;
1456
1492
  -webkit-font-smoothing: antialiased;
1457
1493
  -moz-osx-font-smoothing: grayscale;
1494
+ text-rendering: optimizeLegibility;
1495
+ }
1496
+
1497
+ :host {
1498
+ font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
1499
+ color-scheme: dark;
1500
+ letter-spacing: -0.012em;
1501
+ }
1502
+
1503
+ button, input, textarea, select {
1504
+ font-family: inherit;
1505
+ letter-spacing: inherit;
1506
+ }
1507
+
1508
+ code, pre, kbd, .bt-mono, .bt-badge, .bt-region-badge, .bt-target-pill, .bt-component-pill {
1509
+ font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
1458
1510
  }
1459
1511
 
1460
1512
  /* 1. Element Highlight Overlay */
@@ -1688,7 +1740,7 @@ var NoteInspector = class {
1688
1740
  pointer-events: auto;
1689
1741
  z-index: 2147483646;
1690
1742
  user-select: none;
1691
- transition: all 0.2s ease;
1743
+ transition: all 0.22s cubic-bezier(0.16, 1, 0.3, 1);
1692
1744
  }
1693
1745
 
1694
1746
  .bt-toolbar-btn {
@@ -1749,6 +1801,86 @@ var NoteInspector = class {
1749
1801
  margin: 0 2px;
1750
1802
  }
1751
1803
 
1804
+ .bt-toolbar-collapse-btn {
1805
+ background: transparent;
1806
+ border: none;
1807
+ color: #64748b;
1808
+ font-size: 11px;
1809
+ font-weight: 700;
1810
+ padding: 6px 8px;
1811
+ border-radius: 20px;
1812
+ cursor: pointer;
1813
+ display: flex;
1814
+ align-items: center;
1815
+ justify-content: center;
1816
+ transition: all 0.15s ease;
1817
+ font-family: inherit;
1818
+ }
1819
+
1820
+ .bt-toolbar-collapse-btn:hover {
1821
+ background: #1e293b;
1822
+ color: #f8fafc;
1823
+ }
1824
+
1825
+ /* Collapsed Toolbar State */
1826
+ .bt-toolbar.bt-toolbar-collapsed {
1827
+ padding: 0;
1828
+ background: transparent;
1829
+ border: none;
1830
+ box-shadow: none;
1831
+ }
1832
+
1833
+ .bt-toolbar.bt-toolbar-collapsed .bt-toolbar-btn,
1834
+ .bt-toolbar.bt-toolbar-collapsed .bt-toolbar-divider,
1835
+ .bt-toolbar.bt-toolbar-collapsed .bt-toolbar-collapse-btn {
1836
+ display: none !important;
1837
+ }
1838
+
1839
+ .bt-toolbar-trigger {
1840
+ display: none;
1841
+ background: #0f172a;
1842
+ border: 1px solid #334155;
1843
+ border-radius: 30px;
1844
+ padding: 6px 14px;
1845
+ color: #38bdf8;
1846
+ font-size: 12px;
1847
+ font-weight: 600;
1848
+ cursor: pointer;
1849
+ align-items: center;
1850
+ gap: 8px;
1851
+ box-shadow: 0 10px 25px -3px rgba(0,0,0,0.6), 0 4px 6px -4px rgba(0,0,0,0.4);
1852
+ transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
1853
+ font-family: inherit;
1854
+ user-select: none;
1855
+ }
1856
+
1857
+ .bt-toolbar.bt-toolbar-collapsed .bt-toolbar-trigger {
1858
+ display: inline-flex;
1859
+ }
1860
+
1861
+ .bt-toolbar-trigger:hover {
1862
+ background: #1e293b;
1863
+ color: #ffffff;
1864
+ transform: translateY(-2px);
1865
+ box-shadow: 0 12px 28px -3px rgba(0,0,0,0.7);
1866
+ border-color: #38bdf8;
1867
+ }
1868
+
1869
+ .bt-pulse-dot {
1870
+ width: 7px;
1871
+ height: 7px;
1872
+ border-radius: 50%;
1873
+ background: #38bdf8;
1874
+ box-shadow: 0 0 8px #38bdf8;
1875
+ display: inline-block;
1876
+ animation: bt-dot-pulse 2s infinite;
1877
+ }
1878
+
1879
+ @keyframes bt-dot-pulse {
1880
+ 0%, 100% { opacity: 1; transform: scale(1); }
1881
+ 50% { opacity: 0.4; transform: scale(0.85); }
1882
+ }
1883
+
1752
1884
  /* 5. Modals & Popover Card */
1753
1885
  .bt-modal-backdrop {
1754
1886
  position: fixed;
@@ -2159,8 +2291,13 @@ var NoteInspector = class {
2159
2291
  createToolbar() {
2160
2292
  if (!this.shadowRoot || this.toolbarElement) return;
2161
2293
  this.toolbarElement = document.createElement("div");
2162
- this.toolbarElement.className = "bt-toolbar";
2294
+ this.toolbarElement.className = `bt-toolbar ${this.isToolbarCollapsed ? "bt-toolbar-collapsed" : ""}`;
2163
2295
  this.toolbarElement.innerHTML = `
2296
+ <button class="bt-toolbar-trigger" id="bt-toolbar-expand" title="Expand BrowserTrack toolbar (Alt+T)">
2297
+ <span class="bt-pulse-dot"></span>
2298
+ <span>\u26A1 BrowserTrack</span>
2299
+ <span class="bt-count-pill" id="bt-notes-count-collapsed">0</span>
2300
+ </button>
2164
2301
  <button class="bt-toolbar-btn" id="bt-mode-element" title="Inspect element (or hold Alt+Click)">
2165
2302
  <span>\u{1F3AF}</span> Element
2166
2303
  </button>
@@ -2178,12 +2315,26 @@ var NoteInspector = class {
2178
2315
  <button class="bt-toolbar-btn active" id="bt-toggle-notes" title="Toggle visible note markers on screen">
2179
2316
  <span>\u{1F4CC}</span> Notes <span class="bt-count-pill" id="bt-notes-count">0</span>
2180
2317
  </button>
2318
+ <div class="bt-toolbar-divider"></div>
2319
+ <button class="bt-toolbar-collapse-btn" id="bt-toolbar-collapse" title="Collapse toolbar (Alt+T)">
2320
+ <span>\u276F</span>
2321
+ </button>
2181
2322
  `;
2323
+ const btnExpand = this.toolbarElement.querySelector("#bt-toolbar-expand");
2324
+ const btnCollapse = this.toolbarElement.querySelector("#bt-toolbar-collapse");
2182
2325
  const btnElement = this.toolbarElement.querySelector("#bt-mode-element");
2183
2326
  const btnRegion = this.toolbarElement.querySelector("#bt-mode-region");
2184
2327
  const btnPage = this.toolbarElement.querySelector("#bt-mode-page");
2185
2328
  const btnFlow = this.toolbarElement.querySelector("#bt-mode-flow");
2186
2329
  const btnToggleNotes = this.toolbarElement.querySelector("#bt-toggle-notes");
2330
+ btnExpand.onclick = (e) => {
2331
+ e.stopPropagation();
2332
+ this.toggleToolbarCollapse(false);
2333
+ };
2334
+ btnCollapse.onclick = (e) => {
2335
+ e.stopPropagation();
2336
+ this.toggleToolbarCollapse(true);
2337
+ };
2187
2338
  btnElement.onclick = (e) => {
2188
2339
  e.stopPropagation();
2189
2340
  this.setMode(this.activeMode === "element" ? "idle" : "element");
@@ -2212,6 +2363,17 @@ var NoteInspector = class {
2212
2363
  };
2213
2364
  this.shadowRoot.appendChild(this.toolbarElement);
2214
2365
  }
2366
+ toggleToolbarCollapse(force) {
2367
+ if (!this.toolbarElement) return;
2368
+ this.isToolbarCollapsed = typeof force === "boolean" ? force : !this.isToolbarCollapsed;
2369
+ this.toolbarElement.classList.toggle("bt-toolbar-collapsed", this.isToolbarCollapsed);
2370
+ try {
2371
+ if (typeof window !== "undefined" && window.sessionStorage) {
2372
+ window.sessionStorage.setItem("bt_toolbar_collapsed", String(this.isToolbarCollapsed));
2373
+ }
2374
+ } catch {
2375
+ }
2376
+ }
2215
2377
  updateToolbarState() {
2216
2378
  if (!this.toolbarElement) return;
2217
2379
  const btnElement = this.toolbarElement.querySelector("#bt-mode-element");
@@ -2235,11 +2397,15 @@ var NoteInspector = class {
2235
2397
  }
2236
2398
  updateToolbarCount() {
2237
2399
  if (!this.toolbarElement) return;
2400
+ const openCount = this.savedNotes.filter((n) => n.status === "OPEN").length;
2238
2401
  const countEl = this.toolbarElement.querySelector("#bt-notes-count");
2239
2402
  if (countEl) {
2240
- const openCount = this.savedNotes.filter((n) => n.status === "OPEN").length;
2241
2403
  countEl.textContent = String(openCount);
2242
2404
  }
2405
+ const countCollapsedEl = this.toolbarElement.querySelector("#bt-notes-count-collapsed");
2406
+ if (countCollapsedEl) {
2407
+ countCollapsedEl.textContent = String(openCount);
2408
+ }
2243
2409
  }
2244
2410
  renderMarkers() {
2245
2411
  const root = this.ensureContainer();
@@ -2247,9 +2413,15 @@ var NoteInspector = class {
2247
2413
  this.markersContainer.innerHTML = "";
2248
2414
  if (!this.showMarkers || this.isHidden) return;
2249
2415
  const currentPath = window.location.pathname;
2250
- const activeNotes = this.savedNotes.filter(
2251
- (n) => n.status === "OPEN" && (n.route === currentPath || !n.route || n.route === "/" || window.location.href.includes(n.route))
2252
- );
2416
+ const activeNotes = this.savedNotes.filter((n) => {
2417
+ if (n.status !== "OPEN") return false;
2418
+ if (!n.route) return true;
2419
+ if (n.route === currentPath) return true;
2420
+ if (n.route.includes("#") || n.route.includes("?")) {
2421
+ return window.location.href.includes(n.route);
2422
+ }
2423
+ return false;
2424
+ });
2253
2425
  const pageNotes = [];
2254
2426
  activeNotes.forEach((note, index) => {
2255
2427
  if (note.type === "page") {
@@ -2289,6 +2461,10 @@ var NoteInspector = class {
2289
2461
  }
2290
2462
  const rect = targetEl ? targetEl.getBoundingClientRect() : note.target?.boundingRect;
2291
2463
  if (rect) {
2464
+ const isOffscreen = targetEl && (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth);
2465
+ if (isOffscreen) {
2466
+ return;
2467
+ }
2292
2468
  const pin = document.createElement("div");
2293
2469
  pin.className = markerClass;
2294
2470
  pin.setAttribute("data-note-id", note.id);
@@ -2474,65 +2650,90 @@ var NoteInspector = class {
2474
2650
  }
2475
2651
  setupListeners() {
2476
2652
  const onMouseMove = (e) => {
2477
- if (this.isHidden || this.modalOverlay || this.cardOverlay || this.isDraggingRegion || this.activeMode === "region") return;
2478
- const path = e.composedPath ? e.composedPath() : [];
2479
- if (this.container && path.includes(this.container)) {
2480
- this.hideHighlight();
2481
- return;
2482
- }
2483
- if (e.altKey || this.activeMode === "element") {
2484
- const target = document.elementFromPoint(e.clientX, e.clientY);
2485
- if (target && target !== this.container && !this.container?.contains(target)) {
2486
- this.hoveredElement = target;
2487
- this.updateHighlight(target);
2653
+ try {
2654
+ if (this.isHidden || this.modalOverlay || this.cardOverlay || this.isDraggingRegion || this.activeMode === "region") return;
2655
+ const path = e.composedPath ? e.composedPath() : [];
2656
+ if (this.container && path.includes(this.container)) {
2657
+ this.hideHighlight();
2488
2658
  return;
2489
2659
  }
2490
- }
2491
- if (!e.altKey && this.activeMode !== "element") {
2492
- this.hideHighlight();
2660
+ if (e.altKey || this.activeMode === "element") {
2661
+ const rawTarget = document.elementFromPoint(e.clientX, e.clientY);
2662
+ const target = rawTarget ? resolveMeaningfulTarget(rawTarget) : null;
2663
+ if (target && target !== this.container && !this.container?.contains(target)) {
2664
+ this.hoveredElement = target;
2665
+ this.updateHighlight(target);
2666
+ return;
2667
+ }
2668
+ }
2669
+ if (!e.altKey && this.activeMode !== "element") {
2670
+ this.hideHighlight();
2671
+ }
2672
+ } catch {
2493
2673
  }
2494
2674
  };
2495
2675
  const onClick = (e) => {
2496
- if (this.isHidden || this.modalOverlay || this.cardOverlay) return;
2497
- const path = e.composedPath ? e.composedPath() : [];
2498
- if (this.container && path.includes(this.container)) {
2499
- return;
2500
- }
2501
- if (this.activeMode === "region") return;
2502
- if (e.altKey || this.activeMode === "element") {
2503
- e.preventDefault();
2504
- e.stopPropagation();
2505
- const target = this.hoveredElement || document.elementFromPoint(e.clientX, e.clientY);
2506
- if (target && target !== this.container && !this.container?.contains(target)) {
2507
- this.selectedElement = target;
2508
- this.openNoteEditor(target, "element");
2509
- if (this.activeMode === "element" && !this.activeScenario) {
2510
- this.setMode("idle");
2676
+ try {
2677
+ if (this.isHidden || this.modalOverlay || this.cardOverlay) return;
2678
+ const path = e.composedPath ? e.composedPath() : [];
2679
+ if (this.container && path.includes(this.container)) {
2680
+ return;
2681
+ }
2682
+ if (this.activeMode === "region") return;
2683
+ if (e.altKey || this.activeMode === "element") {
2684
+ e.preventDefault();
2685
+ e.stopPropagation();
2686
+ const rawTarget = this.hoveredElement || document.elementFromPoint(e.clientX, e.clientY);
2687
+ const target = rawTarget ? resolveMeaningfulTarget(rawTarget) : null;
2688
+ if (target && target !== this.container && !this.container?.contains(target)) {
2689
+ this.selectedElement = target;
2690
+ this.openNoteEditor(target, "element");
2691
+ if (this.activeMode === "element" && !this.activeScenario) {
2692
+ this.setMode("idle");
2693
+ }
2511
2694
  }
2512
2695
  }
2696
+ } catch {
2513
2697
  }
2514
2698
  };
2515
2699
  const onKeyDown = (e) => {
2516
- if (e.key === "Escape") {
2517
- if (this.cardOverlay && this.cardOverlay.parentElement && this.shadowRoot) {
2518
- this.shadowRoot.removeChild(this.cardOverlay);
2519
- this.cardOverlay = null;
2520
- } else if (this.activeMode === "region" || this.activeMode === "element") {
2521
- if (!this.activeScenario) {
2522
- this.setMode("idle");
2700
+ try {
2701
+ if (e.altKey && (e.key === "t" || e.key === "T")) {
2702
+ e.preventDefault();
2703
+ this.toggleToolbarCollapse();
2704
+ return;
2705
+ }
2706
+ if (e.key === "Escape") {
2707
+ if (this.cardOverlay && this.cardOverlay.parentElement && this.shadowRoot) {
2708
+ this.shadowRoot.removeChild(this.cardOverlay);
2709
+ this.cardOverlay = null;
2710
+ } else if (this.activeMode === "region" || this.activeMode === "element") {
2711
+ if (!this.activeScenario) {
2712
+ this.setMode("idle");
2713
+ }
2523
2714
  }
2524
2715
  }
2716
+ } catch {
2525
2717
  }
2526
2718
  };
2527
2719
  const onKeyUp = (e) => {
2528
- if (e.key === "Alt" && !this.modalOverlay && !this.cardOverlay && this.activeMode !== "element") {
2529
- this.hideHighlight();
2720
+ try {
2721
+ if (e.key === "Alt" && !this.modalOverlay && !this.cardOverlay && this.activeMode !== "element") {
2722
+ this.hideHighlight();
2723
+ }
2724
+ } catch {
2530
2725
  }
2531
2726
  };
2532
2727
  const onScrollOrResize = () => {
2533
- requestAnimationFrame(() => {
2534
- this.updateMarkerPositions();
2535
- });
2728
+ try {
2729
+ requestAnimationFrame(() => {
2730
+ try {
2731
+ this.updateMarkerPositions();
2732
+ } catch {
2733
+ }
2734
+ });
2735
+ } catch {
2736
+ }
2536
2737
  };
2537
2738
  window.addEventListener("mousemove", onMouseMove, { capture: true, passive: true });
2538
2739
  window.addEventListener("click", onClick, { capture: true });
@@ -2609,6 +2810,11 @@ var NoteInspector = class {
2609
2810
  this.regionBox.appendChild(badge);
2610
2811
  }
2611
2812
  badge.textContent = `Region: ${Math.round(width)} \xD7 ${Math.round(height)} px`;
2813
+ if (top < 32) {
2814
+ badge.style.top = "4px";
2815
+ } else {
2816
+ badge.style.top = "-26px";
2817
+ }
2612
2818
  };
2613
2819
  this.regionOverlay.onmouseup = (e) => {
2614
2820
  if (!this.isDraggingRegion) return;
@@ -2667,6 +2873,18 @@ var NoteInspector = class {
2667
2873
  const selector = getSemanticSelector(el);
2668
2874
  const label = this.activeScenario ? `\u{1F3AC} Step ${this.activeScenario.stepNumber} \xB7 ${selector}` : selector;
2669
2875
  badge.textContent = `${label} (${Math.round(rect.width)} \xD7 ${Math.round(rect.height)} px)`;
2876
+ if (rect.top < 32) {
2877
+ badge.style.top = `${rect.height + 4}px`;
2878
+ } else {
2879
+ badge.style.top = "-26px";
2880
+ }
2881
+ if (rect.left + 260 > window.innerWidth) {
2882
+ badge.style.left = "auto";
2883
+ badge.style.right = "0";
2884
+ } else {
2885
+ badge.style.left = "0";
2886
+ badge.style.right = "auto";
2887
+ }
2670
2888
  }
2671
2889
  hideHighlight() {
2672
2890
  if (this.highlightOverlay) {
@@ -2809,10 +3027,11 @@ var NoteInspector = class {
2809
3027
  btnNextStep.disabled = true;
2810
3028
  if (btnSave) btnSave.disabled = true;
2811
3029
  if (btnFinishFlow) btnFinishFlow.disabled = true;
3030
+ closeModal();
2812
3031
  try {
2813
3032
  await options.onSave(message, action);
2814
- } finally {
2815
- closeModal();
3033
+ } catch {
3034
+ this.showToast("Failed to save note", "\u26A0\uFE0F");
2816
3035
  }
2817
3036
  };
2818
3037
  if (btnSave) {
@@ -2839,8 +3058,12 @@ var NoteInspector = class {
2839
3058
  const selector = noteType === "page" ? "body" : getSemanticSelector(targetEl);
2840
3059
  let screenshotDataUrl;
2841
3060
  try {
2842
- const snap = await this.screenshotDriver.captureElement(targetEl);
2843
- if (snap.ok) {
3061
+ const snapPromise = this.screenshotDriver.captureElement(targetEl);
3062
+ const timeoutPromise = new Promise(
3063
+ (resolve) => setTimeout(() => resolve({ ok: false }), 800)
3064
+ );
3065
+ const snap = await Promise.race([snapPromise, timeoutPromise]);
3066
+ if (snap && snap.ok && snap.dataUrl) {
2844
3067
  screenshotDataUrl = snap.dataUrl;
2845
3068
  }
2846
3069
  } catch {
@@ -2895,9 +3118,16 @@ var NoteInspector = class {
2895
3118
  async saveRegionVisualNote(region, message, scenario) {
2896
3119
  let screenshotDataUrl;
2897
3120
  try {
2898
- const snap = await this.screenshotDriver.captureElement(document.body || document.documentElement);
2899
- if (snap.ok && snap.dataUrl) {
2900
- screenshotDataUrl = await this.cropDataUrl(snap.dataUrl, region);
3121
+ const centerX = region.x + region.width / 2;
3122
+ const centerY = region.y + region.height / 2;
3123
+ const targetElement = document.elementFromPoint(centerX, centerY) || document.body;
3124
+ const snapPromise = this.screenshotDriver.captureElement(targetElement);
3125
+ const timeoutPromise = new Promise(
3126
+ (resolve) => setTimeout(() => resolve({ ok: false }), 800)
3127
+ );
3128
+ const snap = await Promise.race([snapPromise, timeoutPromise]);
3129
+ if (snap && snap.ok && snap.dataUrl) {
3130
+ screenshotDataUrl = snap.dataUrl;
2901
3131
  }
2902
3132
  } catch {
2903
3133
  }
@@ -2994,7 +3224,11 @@ var NoteInspector = class {
2994
3224
  region.width,
2995
3225
  region.height
2996
3226
  );
2997
- resolve(canvas.toDataURL("image/png"));
3227
+ try {
3228
+ resolve(canvas.toDataURL("image/png"));
3229
+ } catch {
3230
+ resolve(dataUrl);
3231
+ }
2998
3232
  };
2999
3233
  img.onerror = () => resolve(dataUrl);
3000
3234
  img.src = dataUrl;
@@ -3284,4 +3518,4 @@ export {
3284
3518
  init,
3285
3519
  getClient
3286
3520
  };
3287
- //# sourceMappingURL=chunk-INXDWPJW.js.map
3521
+ //# sourceMappingURL=chunk-PG4JJDCV.js.map