browsertrack 0.1.2 → 0.2.0

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 (43) hide show
  1. package/AGENTS.md +4 -2
  2. package/dist/{chunk-ILRYKMME.js → chunk-3HOXPTM2.js} +97 -3
  3. package/dist/chunk-3HOXPTM2.js.map +1 -0
  4. package/dist/{chunk-SPCIROIU.js → chunk-7OCOQGDN.js} +24 -5
  5. package/dist/chunk-7OCOQGDN.js.map +1 -0
  6. package/dist/{chunk-G2Y3CXCY.js → chunk-UP5JKCFY.js} +84 -2
  7. package/dist/chunk-UP5JKCFY.js.map +1 -0
  8. package/dist/{chunk-SKCMT2DE.js → chunk-WB7ZKWK7.js} +453 -152
  9. package/dist/chunk-WB7ZKWK7.js.map +1 -0
  10. package/dist/cli/index.js +200 -5
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/client/index.cjs +452 -151
  13. package/dist/client/index.d.ts +28 -9
  14. package/dist/client/index.js +1 -1
  15. package/dist/client.iife.js +185 -29
  16. package/dist/core/index.d.ts +2 -2
  17. package/dist/daemon/index.d.ts +3 -3
  18. package/dist/daemon/index.js +2 -2
  19. package/dist/{engine-CeT9URuN.d.ts → engine-B43IohQY.d.ts} +13 -2
  20. package/dist/index.d.ts +4 -4
  21. package/dist/index.js +4 -4
  22. package/dist/mcp/index.d.ts +4 -4
  23. package/dist/mcp/index.js +2 -2
  24. package/dist/{notes-CBvN91Wf.d.ts → notes-BMnonq46.d.ts} +24 -1
  25. package/dist/{projects-CY8ungMt.d.ts → projects-D5J-egVN.d.ts} +1 -1
  26. package/dist/{server-Dd8NX2Mk.d.ts → server-BztYp1Zc.d.ts} +1 -1
  27. package/docs/index.md +2 -1
  28. package/docs/mcp-reference.md +14 -1
  29. package/docs/scenarios-flows.md +86 -0
  30. package/package.json +1 -1
  31. package/packages/client/src/notes/inspector.ts +533 -161
  32. package/packages/core/src/types/notes.ts +25 -0
  33. package/packages/daemon/src/notes/engine.ts +6 -0
  34. package/packages/daemon/src/server/ws.ts +23 -1
  35. package/packages/daemon/src/storage/db.ts +106 -3
  36. package/packages/mcp/src/handlers.ts +58 -0
  37. package/packages/mcp/src/tools.ts +28 -0
  38. package/test/client/interceptors.test.ts +64 -0
  39. package/test/daemon/scenario-storage.test.ts +158 -0
  40. package/dist/chunk-G2Y3CXCY.js.map +0 -1
  41. package/dist/chunk-ILRYKMME.js.map +0 -1
  42. package/dist/chunk-SKCMT2DE.js.map +0 -1
  43. package/dist/chunk-SPCIROIU.js.map +0 -1
@@ -1184,7 +1184,9 @@ var NoteInspector = class {
1184
1184
  __publicField(this, "toolbarElement", null);
1185
1185
  __publicField(this, "modalOverlay", null);
1186
1186
  __publicField(this, "cardOverlay", null);
1187
+ __publicField(this, "toastContainer", null);
1187
1188
  __publicField(this, "activeMode", "idle");
1189
+ __publicField(this, "activeScenario", null);
1188
1190
  __publicField(this, "hoveredElement", null);
1189
1191
  __publicField(this, "selectedElement", null);
1190
1192
  __publicField(this, "selectedRegion", null);
@@ -1225,6 +1227,41 @@ var NoteInspector = class {
1225
1227
  this.updateToolbarCount();
1226
1228
  this.renderMarkers();
1227
1229
  }
1230
+ showToast(message, icon = "\u2728", durationMs = 2500) {
1231
+ if (typeof document === "undefined") return;
1232
+ const root = this.ensureContainer();
1233
+ if (!root || !this.toastContainer) return;
1234
+ const toast = document.createElement("div");
1235
+ toast.className = "bt-toast";
1236
+ toast.innerHTML = `<span>${icon}</span> <span>${message}</span>`;
1237
+ this.toastContainer.appendChild(toast);
1238
+ setTimeout(() => {
1239
+ toast.classList.add("bt-toast-fadeout");
1240
+ setTimeout(() => {
1241
+ if (toast.parentElement) {
1242
+ toast.parentElement.removeChild(toast);
1243
+ }
1244
+ }, 250);
1245
+ }, durationMs);
1246
+ }
1247
+ startScenario(title) {
1248
+ const defaultTitle = title || `Scenario ${(/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`;
1249
+ this.activeScenario = {
1250
+ id: `scen_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`,
1251
+ title: defaultTitle,
1252
+ stepNumber: 1
1253
+ };
1254
+ this.updateToolbarState();
1255
+ this.setMode("element");
1256
+ this.showToast(`Started flow: "${defaultTitle}"`, "\u{1F3AC}");
1257
+ }
1258
+ finishScenario() {
1259
+ const title = this.activeScenario?.title;
1260
+ this.activeScenario = null;
1261
+ this.updateToolbarState();
1262
+ this.setMode("idle");
1263
+ this.showToast(title ? `Finished flow: "${title}"` : "Flow recording finished", "\u2713");
1264
+ }
1228
1265
  setMode(mode) {
1229
1266
  this.activeMode = mode;
1230
1267
  this.updateToolbarState();
@@ -1404,6 +1441,12 @@ var NoteInspector = class {
1404
1441
  z-index: 2147483641;
1405
1442
  }
1406
1443
 
1444
+ .bt-note-marker-step {
1445
+ background: linear-gradient(135deg, #f59e0b, #d97706);
1446
+ border-color: #fef3c7;
1447
+ box-shadow: 0 4px 14px rgba(245, 158, 11, 0.4), 0 0 0 1px rgba(217, 119, 6, 0.5);
1448
+ }
1449
+
1407
1450
  @keyframes bt-pop-in {
1408
1451
  0% { transform: scale(0.6); opacity: 0; }
1409
1452
  100% { transform: scale(1); opacity: 1; }
@@ -1414,6 +1457,10 @@ var NoteInspector = class {
1414
1457
  box-shadow: 0 8px 20px rgba(37,99,235,0.6), 0 0 0 2px #60a5fa;
1415
1458
  }
1416
1459
 
1460
+ .bt-note-marker-step:hover {
1461
+ box-shadow: 0 8px 20px rgba(245, 158, 11, 0.7), 0 0 0 2px #fde68a;
1462
+ }
1463
+
1417
1464
  .bt-marker-resolved {
1418
1465
  background: linear-gradient(135deg, #475569, #334155);
1419
1466
  border-color: #94a3b8;
@@ -1510,6 +1557,19 @@ var NoteInspector = class {
1510
1557
  box-shadow: 0 2px 6px rgba(37, 99, 235, 0.35);
1511
1558
  }
1512
1559
 
1560
+ .bt-toolbar-btn.active-scenario {
1561
+ background: linear-gradient(135deg, #f59e0b, #d97706);
1562
+ color: #ffffff;
1563
+ font-weight: 700;
1564
+ box-shadow: 0 2px 8px rgba(245, 158, 11, 0.4);
1565
+ animation: bt-pulse 2s infinite;
1566
+ }
1567
+
1568
+ @keyframes bt-pulse {
1569
+ 0%, 100% { opacity: 1; }
1570
+ 50% { opacity: 0.85; }
1571
+ }
1572
+
1513
1573
  .bt-count-pill {
1514
1574
  background: rgba(255, 255, 255, 0.2);
1515
1575
  color: #ffffff;
@@ -1557,7 +1617,7 @@ var NoteInspector = class {
1557
1617
  border: 1px solid #334155;
1558
1618
  border-radius: 14px;
1559
1619
  padding: 20px;
1560
- width: 450px;
1620
+ width: 470px;
1561
1621
  max-width: 92vw;
1562
1622
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(255, 255, 255, 0.05);
1563
1623
  display: flex;
@@ -1580,6 +1640,7 @@ var NoteInspector = class {
1580
1640
  align-items: center;
1581
1641
  gap: 8px;
1582
1642
  color: #f8fafc;
1643
+ flex-wrap: wrap;
1583
1644
  }
1584
1645
 
1585
1646
  .bt-mode-badge {
@@ -1609,6 +1670,12 @@ var NoteInspector = class {
1609
1670
  border: 1px solid rgba(168, 85, 247, 0.4);
1610
1671
  }
1611
1672
 
1673
+ .bt-badge-step {
1674
+ background: rgba(245, 158, 11, 0.15);
1675
+ color: #fbbf24;
1676
+ border: 1px solid rgba(245, 158, 11, 0.4);
1677
+ }
1678
+
1612
1679
  .bt-badge-status-open {
1613
1680
  background: rgba(16, 185, 129, 0.15);
1614
1681
  color: #34d399;
@@ -1660,6 +1727,43 @@ var NoteInspector = class {
1660
1727
  color: #cbd5e1;
1661
1728
  }
1662
1729
 
1730
+ .bt-scenario-stepper {
1731
+ display: flex;
1732
+ align-items: center;
1733
+ justify-content: space-between;
1734
+ background: #090d16;
1735
+ border: 1px solid #1e293b;
1736
+ border-radius: 8px;
1737
+ padding: 6px 10px;
1738
+ font-size: 12px;
1739
+ font-weight: 600;
1740
+ color: #f59e0b;
1741
+ }
1742
+
1743
+ .bt-step-nav-btn {
1744
+ background: #1e293b;
1745
+ color: #f8fafc;
1746
+ border: 1px solid #334155;
1747
+ padding: 4px 10px;
1748
+ border-radius: 6px;
1749
+ cursor: pointer;
1750
+ font-size: 11px;
1751
+ font-weight: 600;
1752
+ transition: all 0.12s ease;
1753
+ font-family: inherit;
1754
+ }
1755
+
1756
+ .bt-step-nav-btn:hover:not(:disabled) {
1757
+ background: #2563eb;
1758
+ border-color: #3b82f6;
1759
+ color: #ffffff;
1760
+ }
1761
+
1762
+ .bt-step-nav-btn:disabled {
1763
+ opacity: 0.3;
1764
+ cursor: not-allowed;
1765
+ }
1766
+
1663
1767
  .bt-note-message-box {
1664
1768
  background: #090d16;
1665
1769
  border: 1px solid #1e293b;
@@ -1704,8 +1808,9 @@ var NoteInspector = class {
1704
1808
  display: flex;
1705
1809
  justify-content: space-between;
1706
1810
  align-items: center;
1707
- gap: 12px;
1811
+ gap: 10px;
1708
1812
  margin-top: 2px;
1813
+ flex-wrap: wrap;
1709
1814
  }
1710
1815
 
1711
1816
  .bt-kbd-hint {
@@ -1733,12 +1838,13 @@ var NoteInspector = class {
1733
1838
  justify-content: flex-end;
1734
1839
  align-items: center;
1735
1840
  gap: 8px;
1841
+ flex-wrap: wrap;
1736
1842
  }
1737
1843
 
1738
1844
  .bt-btn {
1739
- padding: 8px 16px;
1845
+ padding: 7px 14px;
1740
1846
  border-radius: 7px;
1741
- font-size: 12.5px;
1847
+ font-size: 12px;
1742
1848
  font-weight: 600;
1743
1849
  cursor: pointer;
1744
1850
  border: 1px solid transparent;
@@ -1747,7 +1853,7 @@ var NoteInspector = class {
1747
1853
  display: inline-flex;
1748
1854
  align-items: center;
1749
1855
  justify-content: center;
1750
- gap: 6px;
1856
+ gap: 5px;
1751
1857
  }
1752
1858
 
1753
1859
  .bt-btn-cancel {
@@ -1769,6 +1875,25 @@ var NoteInspector = class {
1769
1875
  box-shadow: 0 4px 10px rgba(37, 99, 235, 0.45);
1770
1876
  }
1771
1877
 
1878
+ .bt-btn-next-step {
1879
+ background: linear-gradient(135deg, #6366f1, #4f46e5);
1880
+ color: #ffffff;
1881
+ box-shadow: 0 2px 8px rgba(99, 102, 241, 0.4);
1882
+ }
1883
+ .bt-btn-next-step:hover {
1884
+ background: linear-gradient(135deg, #4f46e5, #4338ca);
1885
+ box-shadow: 0 4px 12px rgba(99, 102, 241, 0.55);
1886
+ }
1887
+
1888
+ .bt-btn-finish-flow {
1889
+ background: linear-gradient(135deg, #10b981, #059669);
1890
+ color: #ffffff;
1891
+ box-shadow: 0 2px 8px rgba(16, 185, 129, 0.35);
1892
+ }
1893
+ .bt-btn-finish-flow:hover {
1894
+ background: linear-gradient(135deg, #059669, #047857);
1895
+ }
1896
+
1772
1897
  .bt-btn-resolve {
1773
1898
  background: rgba(16, 185, 129, 0.15);
1774
1899
  color: #6ee7b7;
@@ -1788,6 +1913,47 @@ var NoteInspector = class {
1788
1913
  background: rgba(239, 68, 68, 0.3);
1789
1914
  color: #ffffff;
1790
1915
  }
1916
+
1917
+ /* 6. Toast Notifications */
1918
+ .bt-toast-container {
1919
+ position: fixed;
1920
+ bottom: 74px;
1921
+ right: 24px;
1922
+ display: flex;
1923
+ flex-direction: column;
1924
+ gap: 8px;
1925
+ pointer-events: none;
1926
+ z-index: 2147483647;
1927
+ align-items: flex-end;
1928
+ }
1929
+
1930
+ .bt-toast {
1931
+ background: #0f172a;
1932
+ color: #f8fafc;
1933
+ border: 1px solid #334155;
1934
+ border-radius: 30px;
1935
+ padding: 8px 16px;
1936
+ font-size: 12.5px;
1937
+ font-weight: 500;
1938
+ display: inline-flex;
1939
+ align-items: center;
1940
+ gap: 8px;
1941
+ box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.6), 0 4px 6px -4px rgba(0, 0, 0, 0.4);
1942
+ pointer-events: auto;
1943
+ animation: bt-toast-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);
1944
+ transition: opacity 0.25s ease, transform 0.25s ease;
1945
+ user-select: none;
1946
+ }
1947
+
1948
+ .bt-toast-fadeout {
1949
+ opacity: 0;
1950
+ transform: translateY(6px);
1951
+ }
1952
+
1953
+ @keyframes bt-toast-in {
1954
+ from { opacity: 0; transform: translateY(8px) scale(0.95); }
1955
+ to { opacity: 1; transform: translateY(0) scale(1); }
1956
+ }
1791
1957
  `;
1792
1958
  this.shadowRoot.appendChild(style);
1793
1959
  this.highlightOverlay = document.createElement("div");
@@ -1797,6 +1963,9 @@ var NoteInspector = class {
1797
1963
  this.markersContainer = document.createElement("div");
1798
1964
  this.markersContainer.className = "bt-markers-layer";
1799
1965
  this.shadowRoot.appendChild(this.markersContainer);
1966
+ this.toastContainer = document.createElement("div");
1967
+ this.toastContainer.className = "bt-toast-container";
1968
+ this.shadowRoot.appendChild(this.toastContainer);
1800
1969
  if (this.options.showToolbar) {
1801
1970
  this.createToolbar();
1802
1971
  }
@@ -1824,6 +1993,10 @@ var NoteInspector = class {
1824
1993
  <span>\u{1F4C4}</span> Page
1825
1994
  </button>
1826
1995
  <div class="bt-toolbar-divider"></div>
1996
+ <button class="bt-toolbar-btn" id="bt-mode-flow" title="Record multi-step reproduction flow">
1997
+ <span>\u{1F3AC}</span> Flow
1998
+ </button>
1999
+ <div class="bt-toolbar-divider"></div>
1827
2000
  <button class="bt-toolbar-btn active" id="bt-toggle-notes" title="Toggle visible note markers on screen">
1828
2001
  <span>\u{1F4CC}</span> Notes <span class="bt-count-pill" id="bt-notes-count">0</span>
1829
2002
  </button>
@@ -1831,6 +2004,7 @@ var NoteInspector = class {
1831
2004
  const btnElement = this.toolbarElement.querySelector("#bt-mode-element");
1832
2005
  const btnRegion = this.toolbarElement.querySelector("#bt-mode-region");
1833
2006
  const btnPage = this.toolbarElement.querySelector("#bt-mode-page");
2007
+ const btnFlow = this.toolbarElement.querySelector("#bt-mode-flow");
1834
2008
  const btnToggleNotes = this.toolbarElement.querySelector("#bt-toggle-notes");
1835
2009
  btnElement.onclick = (e) => {
1836
2010
  e.stopPropagation();
@@ -1844,6 +2018,14 @@ var NoteInspector = class {
1844
2018
  e.stopPropagation();
1845
2019
  this.setMode("page");
1846
2020
  };
2021
+ btnFlow.onclick = (e) => {
2022
+ e.stopPropagation();
2023
+ if (this.activeScenario) {
2024
+ this.finishScenario();
2025
+ } else {
2026
+ this.startScenario();
2027
+ }
2028
+ };
1847
2029
  btnToggleNotes.onclick = (e) => {
1848
2030
  e.stopPropagation();
1849
2031
  this.showMarkers = !this.showMarkers;
@@ -1857,9 +2039,21 @@ var NoteInspector = class {
1857
2039
  const btnElement = this.toolbarElement.querySelector("#bt-mode-element");
1858
2040
  const btnRegion = this.toolbarElement.querySelector("#bt-mode-region");
1859
2041
  const btnPage = this.toolbarElement.querySelector("#bt-mode-page");
2042
+ const btnFlow = this.toolbarElement.querySelector("#bt-mode-flow");
1860
2043
  btnElement?.classList.toggle("active", this.activeMode === "element");
1861
2044
  btnRegion?.classList.toggle("active", this.activeMode === "region");
1862
2045
  btnPage?.classList.toggle("active", this.activeMode === "page");
2046
+ if (btnFlow) {
2047
+ if (this.activeScenario) {
2048
+ btnFlow.className = "bt-toolbar-btn active-scenario";
2049
+ btnFlow.innerHTML = `<span>\u{1F3AC}</span> Step ${this.activeScenario.stepNumber} (Finish)`;
2050
+ btnFlow.setAttribute("title", `Click to finish recording "${this.activeScenario.title}"`);
2051
+ } else {
2052
+ btnFlow.className = "bt-toolbar-btn";
2053
+ btnFlow.innerHTML = `<span>\u{1F3AC}</span> Flow`;
2054
+ btnFlow.setAttribute("title", "Record multi-step reproduction flow");
2055
+ }
2056
+ }
1863
2057
  }
1864
2058
  updateToolbarCount() {
1865
2059
  if (!this.toolbarElement) return;
@@ -1884,6 +2078,9 @@ var NoteInspector = class {
1884
2078
  pageNotes.push(note);
1885
2079
  return;
1886
2080
  }
2081
+ const isStep = !!note.scenarioId && note.stepNumber != null;
2082
+ const stepLabel = isStep ? `\u{1F3AC} Step ${note.stepNumber}` : `#${index + 1}`;
2083
+ const markerClass = `bt-note-marker ${isStep ? "bt-note-marker-step" : ""}`;
1887
2084
  if (note.type === "region" && note.region) {
1888
2085
  const regBox = document.createElement("div");
1889
2086
  regBox.className = "bt-region-marker-box";
@@ -1893,11 +2090,11 @@ var NoteInspector = class {
1893
2090
  regBox.style.height = `${note.region.height}px`;
1894
2091
  this.markersContainer.appendChild(regBox);
1895
2092
  const pin = document.createElement("div");
1896
- pin.className = "bt-note-marker";
2093
+ pin.className = markerClass;
1897
2094
  pin.style.left = `${Math.max(4, note.region.x - 12)}px`;
1898
2095
  pin.style.top = `${Math.max(4, note.region.y - 12)}px`;
1899
- pin.title = note.message;
1900
- pin.innerHTML = `<span>\u{1F4D0}</span> <span>#${index + 1}</span>`;
2096
+ pin.title = isStep ? `[${note.scenarioTitle || "Scenario"}] Step ${note.stepNumber}: ${note.message}` : note.message;
2097
+ pin.innerHTML = `<span>${isStep ? "\u{1F3AC}" : "\u{1F4D0}"}</span> <span>${stepLabel}</span>`;
1901
2098
  pin.onclick = (e) => {
1902
2099
  e.stopPropagation();
1903
2100
  this.openNoteCard(note);
@@ -1915,12 +2112,12 @@ var NoteInspector = class {
1915
2112
  const rect = targetEl ? targetEl.getBoundingClientRect() : note.target?.boundingRect;
1916
2113
  if (rect) {
1917
2114
  const pin = document.createElement("div");
1918
- pin.className = "bt-note-marker";
2115
+ pin.className = markerClass;
1919
2116
  pin.setAttribute("data-note-id", note.id);
1920
- pin.title = note.message;
2117
+ pin.title = isStep ? `[${note.scenarioTitle || "Scenario"}] Step ${note.stepNumber}: ${note.message}` : note.message;
1921
2118
  pin.style.left = `${Math.max(4, rect.left - 10)}px`;
1922
2119
  pin.style.top = `${Math.max(4, rect.top - 12)}px`;
1923
- pin.innerHTML = `<span>\u{1F4DD}</span> <span>#${index + 1}</span>`;
2120
+ pin.innerHTML = `<span>${isStep ? "\u{1F3AC}" : "\u{1F4DD}"}</span> <span>${stepLabel}</span>`;
1924
2121
  pin.onclick = (e) => {
1925
2122
  e.stopPropagation();
1926
2123
  this.openNoteCard(note);
@@ -1935,10 +2132,11 @@ var NoteInspector = class {
1935
2132
  if (pageNotes.length > 0) {
1936
2133
  const pageDock = document.createElement("div");
1937
2134
  pageDock.className = "bt-page-notes-dock";
1938
- pageNotes.forEach((pNote, idx) => {
2135
+ pageNotes.forEach((pNote) => {
1939
2136
  const pill = document.createElement("div");
1940
2137
  pill.className = "bt-page-note-pill";
1941
- pill.innerHTML = `<span>\u{1F4C4}</span> <span>Page Note (${truncate(pNote.message, 25)})</span>`;
2138
+ const label = pNote.scenarioId && pNote.stepNumber ? `Step ${pNote.stepNumber}: ` : "";
2139
+ pill.innerHTML = `<span>\u{1F4C4}</span> <span>${label}${truncate(pNote.message, 25)}</span>`;
1942
2140
  pill.onclick = (e) => {
1943
2141
  e.stopPropagation();
1944
2142
  this.openNoteCard(pNote);
@@ -1967,17 +2165,39 @@ var NoteInspector = class {
1967
2165
  const icon = note.type === "region" ? "\u{1F4D0}" : note.type === "page" ? "\u{1F4C4}" : "\u{1F3AF}";
1968
2166
  const contextText = note.type === "region" && note.region ? `Region: ${note.region.width} \xD7 ${note.region.height} px \xB7 Route: ${note.route}` : note.type === "page" ? `Page Note \xB7 Route: ${note.route}` : `${note.target?.selector || "Element"} (${note.target?.boundingRect?.width || 0}\xD7${note.target?.boundingRect?.height || 0}px) \xB7 ${note.route}`;
1969
2167
  const formattedDate = new Date(note.createdAt).toLocaleString();
2168
+ let scenarioStepsHtml = "";
2169
+ let scenarioSteps = [];
2170
+ if (note.scenarioId) {
2171
+ scenarioSteps = this.savedNotes.filter((n) => n.scenarioId === note.scenarioId).sort((a, b) => (a.stepNumber || 0) - (b.stepNumber || 0));
2172
+ const currentIndex = scenarioSteps.findIndex((s) => s.id === note.id);
2173
+ const prevStep = currentIndex > 0 ? scenarioSteps[currentIndex - 1] : null;
2174
+ const nextStep = currentIndex >= 0 && currentIndex < scenarioSteps.length - 1 ? scenarioSteps[currentIndex + 1] : null;
2175
+ scenarioStepsHtml = `
2176
+ <div class="bt-scenario-stepper">
2177
+ <button class="bt-step-nav-btn" id="btn-prev-step" ${!prevStep ? "disabled" : ""}>
2178
+ \u25C0 Step ${prevStep ? prevStep.stepNumber : ""}
2179
+ </button>
2180
+ <span>\u{1F3AC} Step ${note.stepNumber || 1} of ${scenarioSteps.length}</span>
2181
+ <button class="bt-step-nav-btn" id="btn-next-step-card" ${!nextStep ? "disabled" : ""}>
2182
+ Step ${nextStep ? nextStep.stepNumber : ""} \u25B6
2183
+ </button>
2184
+ </div>
2185
+ `;
2186
+ }
1970
2187
  backdrop.innerHTML = `
1971
2188
  <div class="bt-modal">
1972
2189
  <div class="bt-modal-header">
1973
2190
  <div class="bt-modal-title">
1974
- <span>${icon} Visual Note Details</span>
2191
+ <span>${note.scenarioId ? "\u{1F3AC} " + (note.scenarioTitle || "Scenario Flow") : icon + " Visual Note Details"}</span>
1975
2192
  <span class="bt-mode-badge ${badgeClass}">${note.type.toUpperCase()}</span>
2193
+ ${note.scenarioId && note.stepNumber ? `<span class="bt-mode-badge bt-badge-step">STEP ${note.stepNumber}</span>` : ""}
1976
2194
  <span class="bt-mode-badge ${statusClass}">${note.status}</span>
1977
2195
  </div>
1978
2196
  <button class="bt-close-btn" id="btn-card-close" title="Close (Esc)">\u2715</button>
1979
2197
  </div>
1980
2198
 
2199
+ ${scenarioStepsHtml}
2200
+
1981
2201
  <div class="bt-target-pill" title="${contextText}">
1982
2202
  <span>\u{1F3F7}\uFE0F</span>
1983
2203
  <span class="bt-pill-content">${contextText}</span>
@@ -1991,9 +2211,14 @@ var NoteInspector = class {
1991
2211
  </div>
1992
2212
 
1993
2213
  <div class="bt-modal-footer">
1994
- <button class="bt-btn bt-btn-delete" id="btn-card-delete" title="Delete this note">
1995
- <span>\u{1F5D1}\uFE0F</span> Delete
1996
- </button>
2214
+ <div style="display: flex; gap: 6px;">
2215
+ <button class="bt-btn bt-btn-delete" id="btn-card-delete" title="Delete this note">
2216
+ <span>\u{1F5D1}\uFE0F</span> Delete
2217
+ </button>
2218
+ ${note.scenarioId ? `<button class="bt-btn bt-btn-delete" id="btn-card-delete-flow" title="Delete entire scenario flow">
2219
+ <span>\u{1F5D1}\uFE0F</span> Delete Flow
2220
+ </button>` : ""}
2221
+ </div>
1997
2222
  <div class="bt-modal-actions">
1998
2223
  <button class="bt-btn bt-btn-cancel" id="btn-card-dismiss">Close</button>
1999
2224
  <button class="bt-btn ${note.status === "OPEN" ? "bt-btn-resolve" : "bt-btn-save"}" id="btn-card-resolve">
@@ -2007,6 +2232,9 @@ var NoteInspector = class {
2007
2232
  const btnDismiss = backdrop.querySelector("#btn-card-dismiss");
2008
2233
  const btnResolve = backdrop.querySelector("#btn-card-resolve");
2009
2234
  const btnDelete = backdrop.querySelector("#btn-card-delete");
2235
+ const btnDeleteFlow = backdrop.querySelector("#btn-card-delete-flow");
2236
+ const btnPrevStep = backdrop.querySelector("#btn-prev-step");
2237
+ const btnNextStepCard = backdrop.querySelector("#btn-next-step-card");
2010
2238
  const closeCard = () => {
2011
2239
  if (this.cardOverlay) {
2012
2240
  root.removeChild(this.cardOverlay);
@@ -2019,18 +2247,44 @@ var NoteInspector = class {
2019
2247
  backdrop.onclick = (e) => {
2020
2248
  if (e.target === backdrop) closeCard();
2021
2249
  };
2250
+ if (btnPrevStep && scenarioSteps.length > 0) {
2251
+ const currentIndex = scenarioSteps.findIndex((s) => s.id === note.id);
2252
+ if (currentIndex > 0) {
2253
+ btnPrevStep.onclick = () => {
2254
+ this.openNoteCard(scenarioSteps[currentIndex - 1]);
2255
+ };
2256
+ }
2257
+ }
2258
+ if (btnNextStepCard && scenarioSteps.length > 0) {
2259
+ const currentIndex = scenarioSteps.findIndex((s) => s.id === note.id);
2260
+ if (currentIndex >= 0 && currentIndex < scenarioSteps.length - 1) {
2261
+ btnNextStepCard.onclick = () => {
2262
+ this.openNoteCard(scenarioSteps[currentIndex + 1]);
2263
+ };
2264
+ }
2265
+ }
2022
2266
  btnResolve.onclick = () => {
2023
2267
  if (note.status === "OPEN") {
2024
2268
  this.transport.send({ type: "resolve_note", noteId: note.id });
2269
+ this.showToast("Note marked as resolved", "\u2705");
2025
2270
  } else {
2026
2271
  this.transport.send({ type: "reopen_note", noteId: note.id });
2272
+ this.showToast("Note reopened", "\u21BA");
2027
2273
  }
2028
2274
  closeCard();
2029
2275
  };
2030
2276
  btnDelete.onclick = () => {
2031
2277
  this.transport.send({ type: "delete_note", noteId: note.id });
2278
+ this.showToast("Note deleted", "\u{1F5D1}\uFE0F");
2032
2279
  closeCard();
2033
2280
  };
2281
+ if (btnDeleteFlow && note.scenarioId) {
2282
+ btnDeleteFlow.onclick = () => {
2283
+ this.transport.send({ type: "delete_scenario", scenarioId: note.scenarioId });
2284
+ this.showToast("Scenario flow deleted", "\u{1F5D1}\uFE0F");
2285
+ closeCard();
2286
+ };
2287
+ }
2034
2288
  root.appendChild(backdrop);
2035
2289
  }
2036
2290
  setupListeners() {
@@ -2067,7 +2321,7 @@ var NoteInspector = class {
2067
2321
  if (target && target !== this.container && !this.container?.contains(target)) {
2068
2322
  this.selectedElement = target;
2069
2323
  this.openNoteEditor(target, "element");
2070
- if (this.activeMode === "element") {
2324
+ if (this.activeMode === "element" && !this.activeScenario) {
2071
2325
  this.setMode("idle");
2072
2326
  }
2073
2327
  }
@@ -2079,7 +2333,9 @@ var NoteInspector = class {
2079
2333
  this.shadowRoot.removeChild(this.cardOverlay);
2080
2334
  this.cardOverlay = null;
2081
2335
  } else if (this.activeMode === "region" || this.activeMode === "element") {
2082
- this.setMode("idle");
2336
+ if (!this.activeScenario) {
2337
+ this.setMode("idle");
2338
+ }
2083
2339
  }
2084
2340
  }
2085
2341
  };
@@ -2185,28 +2441,27 @@ var NoteInspector = class {
2185
2441
  width: Math.round(width),
2186
2442
  height: Math.round(height)
2187
2443
  };
2188
- this.hideRegionOverlay();
2189
- this.setMode("idle");
2190
2444
  this.openRegionNoteEditor(this.selectedRegion);
2191
- } else {
2192
- this.hideRegionOverlay();
2445
+ }
2446
+ if (this.regionBox) {
2447
+ this.regionBox.style.display = "none";
2448
+ }
2449
+ if (!this.activeScenario) {
2193
2450
  this.setMode("idle");
2194
2451
  }
2195
2452
  };
2196
- }
2197
- if (this.toolbarElement && this.toolbarElement.parentNode === root) {
2198
- root.insertBefore(this.regionOverlay, this.toolbarElement);
2199
- } else {
2200
2453
  root.appendChild(this.regionOverlay);
2454
+ } else {
2455
+ this.regionOverlay.style.display = "block";
2201
2456
  }
2202
2457
  }
2203
2458
  hideRegionOverlay() {
2204
- this.isDraggingRegion = false;
2205
- if (this.regionOverlay && this.regionOverlay.parentNode) {
2206
- this.regionOverlay.parentNode.removeChild(this.regionOverlay);
2207
- }
2208
- if (this.regionBox) {
2209
- this.regionBox.style.display = "none";
2459
+ if (this.regionOverlay) {
2460
+ this.regionOverlay.style.display = "none";
2461
+ this.isDraggingRegion = false;
2462
+ if (this.regionBox) {
2463
+ this.regionBox.style.display = "none";
2464
+ }
2210
2465
  }
2211
2466
  }
2212
2467
  updateHighlight(el) {
@@ -2214,19 +2469,19 @@ var NoteInspector = class {
2214
2469
  if (!root || !this.highlightOverlay) return;
2215
2470
  const rect = el.getBoundingClientRect();
2216
2471
  this.highlightOverlay.style.display = "block";
2217
- this.highlightOverlay.style.top = `${rect.top}px`;
2218
2472
  this.highlightOverlay.style.left = `${rect.left}px`;
2473
+ this.highlightOverlay.style.top = `${rect.top}px`;
2219
2474
  this.highlightOverlay.style.width = `${rect.width}px`;
2220
2475
  this.highlightOverlay.style.height = `${rect.height}px`;
2221
- const selector = getSemanticSelector(el);
2222
- const badgeText = `${selector} \xB7 ${Math.round(rect.width)}\xD7${Math.round(rect.height)}`;
2223
2476
  let badge = this.highlightOverlay.querySelector(".bt-badge");
2224
2477
  if (!badge) {
2225
2478
  badge = document.createElement("div");
2226
2479
  badge.className = "bt-badge";
2227
2480
  this.highlightOverlay.appendChild(badge);
2228
2481
  }
2229
- badge.textContent = badgeText;
2482
+ const selector = getSemanticSelector(el);
2483
+ const label = this.activeScenario ? `\u{1F3AC} Step ${this.activeScenario.stepNumber} \xB7 ${selector}` : selector;
2484
+ badge.textContent = `${label} (${Math.round(rect.width)} \xD7 ${Math.round(rect.height)} px)`;
2230
2485
  }
2231
2486
  hideHighlight() {
2232
2487
  if (this.highlightOverlay) {
@@ -2242,21 +2497,57 @@ var NoteInspector = class {
2242
2497
  this.updateHighlight(targetEl);
2243
2498
  }
2244
2499
  this.renderNoteModal({
2245
- title: "Add Visual Note",
2246
- modeBadge: noteType.toUpperCase(),
2500
+ title: this.activeScenario ? `Step ${this.activeScenario.stepNumber}: ${this.activeScenario.title}` : "Add Visual Note",
2501
+ modeBadge: this.activeScenario ? `STEP ${this.activeScenario.stepNumber}` : noteType.toUpperCase(),
2247
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}`,
2248
- onSave: async (message) => {
2249
- await this.saveVisualNote(targetEl, message, noteType);
2503
+ onSave: async (message, action) => {
2504
+ let scenarioParam;
2505
+ if (action === "next_step" && !this.activeScenario) {
2506
+ this.startScenario();
2507
+ }
2508
+ if (this.activeScenario) {
2509
+ scenarioParam = {
2510
+ scenarioId: this.activeScenario.id,
2511
+ stepNumber: this.activeScenario.stepNumber,
2512
+ scenarioTitle: this.activeScenario.title
2513
+ };
2514
+ }
2515
+ await this.saveVisualNote(targetEl, message, noteType, scenarioParam);
2516
+ if (action === "next_step" && this.activeScenario) {
2517
+ this.activeScenario.stepNumber++;
2518
+ this.updateToolbarState();
2519
+ this.setMode("element");
2520
+ } else if (action === "finish_flow" && this.activeScenario) {
2521
+ this.finishScenario();
2522
+ }
2250
2523
  }
2251
2524
  });
2252
2525
  }
2253
2526
  openRegionNoteEditor(region) {
2254
2527
  this.renderNoteModal({
2255
- title: "Add Region Note",
2256
- modeBadge: "REGION",
2528
+ title: this.activeScenario ? `Step ${this.activeScenario.stepNumber}: ${this.activeScenario.title}` : "Add Region Note",
2529
+ modeBadge: this.activeScenario ? `STEP ${this.activeScenario.stepNumber}` : "REGION",
2257
2530
  pillText: `Selected Area: x:${region.x}, y:${region.y} (${region.width} \xD7 ${region.height} px)`,
2258
- onSave: async (message) => {
2259
- await this.saveRegionVisualNote(region, message);
2531
+ onSave: async (message, action) => {
2532
+ let scenarioParam;
2533
+ if (action === "next_step" && !this.activeScenario) {
2534
+ this.startScenario();
2535
+ }
2536
+ if (this.activeScenario) {
2537
+ scenarioParam = {
2538
+ scenarioId: this.activeScenario.id,
2539
+ stepNumber: this.activeScenario.stepNumber,
2540
+ scenarioTitle: this.activeScenario.title
2541
+ };
2542
+ }
2543
+ await this.saveRegionVisualNote(region, message, scenarioParam);
2544
+ if (action === "next_step" && this.activeScenario) {
2545
+ this.activeScenario.stepNumber++;
2546
+ this.updateToolbarState();
2547
+ this.setMode("element");
2548
+ } else if (action === "finish_flow" && this.activeScenario) {
2549
+ this.finishScenario();
2550
+ }
2260
2551
  }
2261
2552
  });
2262
2553
  }
@@ -2270,14 +2561,15 @@ var NoteInspector = class {
2270
2561
  const backdrop = document.createElement("div");
2271
2562
  backdrop.className = "bt-modal-backdrop";
2272
2563
  this.modalOverlay = backdrop;
2273
- const badgeClass = `bt-badge-${options.modeBadge.toLowerCase()}`;
2274
- const icon = options.modeBadge === "REGION" ? "\u{1F4D0}" : options.modeBadge === "PAGE" ? "\u{1F4C4}" : "\u{1F3AF}";
2564
+ const isStep = options.modeBadge.startsWith("STEP");
2565
+ const badgeClass = isStep ? "bt-badge-step" : `bt-badge-${options.modeBadge.toLowerCase()}`;
2566
+ const icon = isStep ? "\u{1F3AC}" : options.modeBadge === "REGION" ? "\u{1F4D0}" : options.modeBadge === "PAGE" ? "\u{1F4C4}" : "\u{1F3AF}";
2275
2567
  const isMac = typeof navigator !== "undefined" && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
2276
2568
  backdrop.innerHTML = `
2277
2569
  <div class="bt-modal">
2278
2570
  <div class="bt-modal-header">
2279
2571
  <div class="bt-modal-title">
2280
- <span>\u{1F4DD} ${options.title}</span>
2572
+ <span>${icon} ${options.title}</span>
2281
2573
  <span class="bt-mode-badge ${badgeClass}">${options.modeBadge}</span>
2282
2574
  </div>
2283
2575
  <button class="bt-close-btn" id="btn-close" title="Close (Esc)">\u2715</button>
@@ -2286,14 +2578,19 @@ var NoteInspector = class {
2286
2578
  <span>${icon}</span>
2287
2579
  <span class="bt-pill-content">${options.pillText}</span>
2288
2580
  </div>
2289
- <textarea class="bt-textarea" placeholder="Describe the layout issue, styling bug, or note for AI agent..." autofocus></textarea>
2581
+ <textarea class="bt-textarea" placeholder="Describe the layout issue, user action, or note for AI agent..." autofocus></textarea>
2290
2582
  <div class="bt-modal-footer">
2291
2583
  <div class="bt-kbd-hint">
2292
2584
  <kbd>${isMac ? "\u2318" : "Ctrl"}+Enter</kbd> save \xB7 <kbd>Esc</kbd> cancel
2293
2585
  </div>
2294
2586
  <div class="bt-modal-actions">
2295
2587
  <button class="bt-btn bt-btn-cancel" id="btn-cancel">Cancel</button>
2296
- <button class="bt-btn bt-btn-save" id="btn-save">Save Note</button>
2588
+ <button class="bt-btn bt-btn-next-step" id="btn-next-step" title="Save this step and immediately select the next element">
2589
+ <span>\u27A1\uFE0F</span> ${this.activeScenario ? "Save & Next Step" : "Save as Step 1 (Flow)"}
2590
+ </button>
2591
+ ${this.activeScenario ? `<button class="bt-btn bt-btn-finish-flow" id="btn-finish-flow" title="Save final step and complete scenario">
2592
+ <span>\u2713</span> Save & Finish Flow
2593
+ </button>` : `<button class="bt-btn bt-btn-save" id="btn-save">Save Note</button>`}
2297
2594
  </div>
2298
2595
  </div>
2299
2596
  </div>
@@ -2302,6 +2599,8 @@ var NoteInspector = class {
2302
2599
  const btnClose = backdrop.querySelector("#btn-close");
2303
2600
  const btnCancel = backdrop.querySelector("#btn-cancel");
2304
2601
  const btnSave = backdrop.querySelector("#btn-save");
2602
+ const btnNextStep = backdrop.querySelector("#btn-next-step");
2603
+ const btnFinishFlow = backdrop.querySelector("#btn-finish-flow");
2305
2604
  const closeModal = () => {
2306
2605
  if (this.modalOverlay) {
2307
2606
  root.removeChild(this.modalOverlay);
@@ -2317,22 +2616,29 @@ var NoteInspector = class {
2317
2616
  closeModal();
2318
2617
  }
2319
2618
  };
2320
- const submitNote = async () => {
2619
+ const handleAction = async (action) => {
2321
2620
  const message = textarea.value.trim();
2322
2621
  if (!message) return;
2323
- btnSave.textContent = "Saving...";
2324
- btnSave.disabled = true;
2622
+ btnNextStep.disabled = true;
2623
+ if (btnSave) btnSave.disabled = true;
2624
+ if (btnFinishFlow) btnFinishFlow.disabled = true;
2325
2625
  try {
2326
- await options.onSave(message);
2626
+ await options.onSave(message, action);
2327
2627
  } finally {
2328
2628
  closeModal();
2329
2629
  }
2330
2630
  };
2331
- btnSave.onclick = submitNote;
2631
+ if (btnSave) {
2632
+ btnSave.onclick = () => handleAction("save");
2633
+ }
2634
+ btnNextStep.onclick = () => handleAction("next_step");
2635
+ if (btnFinishFlow) {
2636
+ btnFinishFlow.onclick = () => handleAction("finish_flow");
2637
+ }
2332
2638
  textarea.onkeydown = (e) => {
2333
- if (e.key === "Enter" && (e.metaKey || e.ctrlKey || !e.shiftKey)) {
2639
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
2334
2640
  e.preventDefault();
2335
- submitNote();
2641
+ handleAction(this.activeScenario ? "next_step" : "save");
2336
2642
  }
2337
2643
  if (e.key === "Escape") {
2338
2644
  closeModal();
@@ -2341,7 +2647,7 @@ var NoteInspector = class {
2341
2647
  root.appendChild(backdrop);
2342
2648
  setTimeout(() => textarea?.focus(), 50);
2343
2649
  }
2344
- async saveVisualNote(targetEl, message, noteType = "element") {
2650
+ async saveVisualNote(targetEl, message, noteType = "element", scenario) {
2345
2651
  const rect = targetEl.getBoundingClientRect();
2346
2652
  const selector = noteType === "page" ? "body" : getSemanticSelector(targetEl);
2347
2653
  let screenshotDataUrl;
@@ -2368,33 +2674,38 @@ var NoteInspector = class {
2368
2674
  visible: rect.width > 0 && rect.height > 0,
2369
2675
  confidence: selector.startsWith("[data-test") ? "high" : selector.startsWith("#") ? "medium" : "low"
2370
2676
  };
2677
+ const route = typeof window !== "undefined" ? window.location.pathname + window.location.search : "/";
2678
+ const url = typeof window !== "undefined" ? window.location.href : "http://localhost/";
2679
+ const viewport = typeof window !== "undefined" ? { width: window.innerWidth, height: window.innerHeight, devicePixelRatio: window.devicePixelRatio || 1 } : { width: 1280, height: 800, devicePixelRatio: 1 };
2680
+ const scroll = typeof window !== "undefined" ? { scrollX: window.scrollX, scrollY: window.scrollY } : { scrollX: 0, scrollY: 0 };
2371
2681
  const notePayload = {
2372
2682
  type: "create_note",
2373
2683
  sessionId: this.transport.getSessionId() || "",
2374
2684
  noteType,
2375
2685
  message,
2376
- route: window.location.pathname + window.location.search,
2377
- url: window.location.href,
2378
- viewport: {
2379
- width: window.innerWidth,
2380
- height: window.innerHeight,
2381
- devicePixelRatio: window.devicePixelRatio || 1
2382
- },
2383
- scroll: {
2384
- scrollX: window.scrollX,
2385
- scrollY: window.scrollY
2386
- },
2686
+ route,
2687
+ url,
2688
+ viewport,
2689
+ scroll,
2387
2690
  target,
2388
2691
  elementContext,
2389
2692
  screenshot: screenshotDataUrl,
2693
+ scenarioId: scenario?.scenarioId,
2694
+ stepNumber: scenario?.stepNumber,
2695
+ scenarioTitle: scenario?.scenarioTitle,
2390
2696
  timestamp: Date.now()
2391
2697
  };
2392
2698
  this.transport.send(notePayload);
2393
2699
  if (this.options.onNoteCreated) {
2394
2700
  this.options.onNoteCreated(notePayload);
2395
2701
  }
2702
+ if (scenario?.scenarioId) {
2703
+ this.showToast(`Step ${scenario.stepNumber || 1} recorded`, "\u{1F3AC}");
2704
+ } else {
2705
+ this.showToast("Visual note saved", "\u2728");
2706
+ }
2396
2707
  }
2397
- async saveRegionVisualNote(region, message) {
2708
+ async saveRegionVisualNote(region, message, scenario) {
2398
2709
  let screenshotDataUrl;
2399
2710
  try {
2400
2711
  const snap = await this.screenshotDriver.captureElement(document.body || document.documentElement);
@@ -2403,123 +2714,113 @@ var NoteInspector = class {
2403
2714
  }
2404
2715
  } catch {
2405
2716
  }
2717
+ const route = typeof window !== "undefined" ? window.location.pathname + window.location.search : "/";
2718
+ const url = typeof window !== "undefined" ? window.location.href : "http://localhost/";
2719
+ const viewport = typeof window !== "undefined" ? { width: window.innerWidth, height: window.innerHeight, devicePixelRatio: window.devicePixelRatio || 1 } : { width: 1280, height: 800, devicePixelRatio: 1 };
2720
+ const scroll = typeof window !== "undefined" ? { scrollX: window.scrollX, scrollY: window.scrollY } : { scrollX: 0, scrollY: 0 };
2406
2721
  const notePayload = {
2407
2722
  type: "create_note",
2408
2723
  sessionId: this.transport.getSessionId() || "",
2409
2724
  noteType: "region",
2410
2725
  message,
2411
- route: window.location.pathname + window.location.search,
2412
- url: window.location.href,
2413
- viewport: {
2414
- width: window.innerWidth,
2415
- height: window.innerHeight,
2416
- devicePixelRatio: window.devicePixelRatio || 1
2417
- },
2418
- scroll: {
2419
- scrollX: window.scrollX,
2420
- scrollY: window.scrollY
2421
- },
2726
+ route,
2727
+ url,
2728
+ viewport,
2729
+ scroll,
2422
2730
  region,
2423
2731
  screenshot: screenshotDataUrl,
2732
+ scenarioId: scenario?.scenarioId,
2733
+ stepNumber: scenario?.stepNumber,
2734
+ scenarioTitle: scenario?.scenarioTitle,
2424
2735
  timestamp: Date.now()
2425
2736
  };
2426
2737
  this.transport.send(notePayload);
2427
2738
  if (this.options.onNoteCreated) {
2428
2739
  this.options.onNoteCreated(notePayload);
2429
2740
  }
2430
- }
2431
- async cropDataUrl(dataUrl, region) {
2432
- return new Promise((resolve) => {
2433
- const img = new Image();
2434
- img.onload = () => {
2435
- try {
2436
- const canvas = document.createElement("canvas");
2437
- canvas.width = region.width;
2438
- canvas.height = region.height;
2439
- const ctx = canvas.getContext("2d");
2440
- if (!ctx) {
2441
- resolve(dataUrl);
2442
- return;
2443
- }
2444
- const dpr = window.devicePixelRatio || 1;
2445
- ctx.drawImage(
2446
- img,
2447
- region.x * dpr,
2448
- region.y * dpr,
2449
- region.width * dpr,
2450
- region.height * dpr,
2451
- 0,
2452
- 0,
2453
- region.width,
2454
- region.height
2455
- );
2456
- resolve(canvas.toDataURL("image/webp", 0.9));
2457
- } catch {
2458
- resolve(dataUrl);
2459
- }
2460
- };
2461
- img.onerror = () => resolve(dataUrl);
2462
- img.src = dataUrl;
2463
- });
2741
+ if (scenario?.scenarioId) {
2742
+ this.showToast(`Step ${scenario.stepNumber || 1} region recorded`, "\u{1F3AC}");
2743
+ } else {
2744
+ this.showToast("Region note saved", "\u{1F4D0}");
2745
+ }
2464
2746
  }
2465
2747
  extractElementContext(el) {
2466
2748
  const selector = getSemanticSelector(el);
2467
- const tag = el.tagName.toLowerCase();
2468
2749
  const attributes = {};
2469
2750
  for (let i = 0; i < el.attributes.length; i++) {
2470
2751
  const attr = el.attributes[i];
2471
- if (attr.name === "value" && el.type === "password") {
2752
+ if (this.options.maskSelectors?.some((mask) => {
2753
+ try {
2754
+ return el.matches(mask);
2755
+ } catch {
2756
+ return false;
2757
+ }
2758
+ }) && (attr.name === "value" || attr.name === "data-secret")) {
2472
2759
  attributes[attr.name] = "[REDACTED]";
2473
2760
  } else {
2474
2761
  attributes[attr.name] = attr.value;
2475
2762
  }
2476
2763
  }
2477
- let outerHTML = "";
2478
- try {
2479
- const clone = el.cloneNode(true);
2480
- for (const passInput of Array.from(clone.querySelectorAll('input[type="password"]'))) {
2481
- passInput.setAttribute("value", "[REDACTED]");
2482
- }
2483
- outerHTML = truncate(clone.outerHTML, 10240);
2484
- } catch {
2485
- outerHTML = truncate(el.outerHTML, 10240);
2764
+ let outerHTML = el.outerHTML;
2765
+ if (outerHTML && outerHTML.length > 1e3) {
2766
+ outerHTML = truncate(outerHTML, 1e3);
2486
2767
  }
2487
- let parent;
2488
- if (el.parentElement && el.parentElement !== document.body) {
2489
- parent = {
2490
- selector: getSemanticSelector(el.parentElement),
2491
- tag: el.parentElement.tagName.toLowerCase()
2492
- };
2768
+ let innerText = el.innerText || el.textContent || "";
2769
+ if (innerText && innerText.length > 200) {
2770
+ innerText = truncate(innerText, 200);
2493
2771
  }
2494
2772
  return {
2495
2773
  selector,
2496
- tag,
2774
+ tag: el.tagName.toLowerCase(),
2497
2775
  attributes,
2498
2776
  outerHTML,
2499
- innerText: truncate(el.textContent?.trim(), 200),
2500
- parent
2777
+ innerText,
2778
+ parent: el.parentElement ? {
2779
+ selector: getSemanticSelector(el.parentElement),
2780
+ tag: el.parentElement.tagName.toLowerCase()
2781
+ } : void 0
2501
2782
  };
2502
2783
  }
2784
+ async cropDataUrl(dataUrl, region) {
2785
+ return new Promise((resolve) => {
2786
+ const img = new Image();
2787
+ img.onload = () => {
2788
+ const canvas = document.createElement("canvas");
2789
+ canvas.width = region.width;
2790
+ canvas.height = region.height;
2791
+ const ctx = canvas.getContext("2d");
2792
+ if (!ctx) {
2793
+ resolve(dataUrl);
2794
+ return;
2795
+ }
2796
+ const dpr = window.devicePixelRatio || 1;
2797
+ ctx.drawImage(
2798
+ img,
2799
+ region.x * dpr,
2800
+ region.y * dpr,
2801
+ region.width * dpr,
2802
+ region.height * dpr,
2803
+ 0,
2804
+ 0,
2805
+ region.width,
2806
+ region.height
2807
+ );
2808
+ resolve(canvas.toDataURL("image/png"));
2809
+ };
2810
+ img.onerror = () => resolve(dataUrl);
2811
+ img.src = dataUrl;
2812
+ });
2813
+ }
2503
2814
  destroy() {
2504
2815
  for (const cleanup of this.cleanups) {
2505
- try {
2506
- cleanup();
2507
- } catch {
2508
- }
2816
+ cleanup();
2509
2817
  }
2510
2818
  this.cleanups = [];
2511
- if (this.container && this.container.parentElement) {
2512
- this.container.parentElement.removeChild(this.container);
2513
- }
2514
- this.container = null;
2515
- this.shadowRoot = null;
2516
- this.toolbarElement = null;
2517
- this.regionOverlay = null;
2518
- this.regionBox = null;
2519
- this.regionBanner = null;
2520
- this.markersContainer = null;
2521
- this.modalOverlay = null;
2522
- this.cardOverlay = null;
2819
+ if (this.container && this.container.parentNode) {
2820
+ this.container.parentNode.removeChild(this.container);
2821
+ this.container = null;
2822
+ this.shadowRoot = null;
2823
+ }
2523
2824
  }
2524
2825
  };
2525
2826