browsertrack 0.1.2 → 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.
- package/AGENTS.md +6 -2
- package/dist/{chunk-ILRYKMME.js → chunk-3HOXPTM2.js} +97 -3
- package/dist/chunk-3HOXPTM2.js.map +1 -0
- package/dist/{chunk-G2Y3CXCY.js → chunk-464D4U2U.js} +86 -3
- package/dist/chunk-464D4U2U.js.map +1 -0
- package/dist/{chunk-SPCIROIU.js → chunk-7OCOQGDN.js} +24 -5
- package/dist/chunk-7OCOQGDN.js.map +1 -0
- package/dist/{chunk-SKCMT2DE.js → chunk-INXDWPJW.js} +806 -160
- package/dist/chunk-INXDWPJW.js.map +1 -0
- package/dist/cli/index.js +202 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.cjs +809 -161
- package/dist/client/index.d.ts +62 -11
- package/dist/client/index.js +7 -3
- package/dist/client.iife.js +206 -27
- package/dist/{notes-CBvN91Wf.d.ts → commands-fjuqKzkm.d.ts} +125 -91
- package/dist/core/index.d.ts +2 -2
- package/dist/daemon/index.d.ts +3 -3
- package/dist/daemon/index.js +2 -2
- package/dist/{engine-CeT9URuN.d.ts → engine-CmchnMDq.d.ts} +13 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/mcp/index.d.ts +4 -4
- package/dist/mcp/index.js +2 -2
- package/dist/{projects-CY8ungMt.d.ts → projects-DB7S312i.d.ts} +1 -1
- package/dist/{server-Dd8NX2Mk.d.ts → server-DiVmTrIR.d.ts} +1 -1
- package/docs/component-resolver.md +108 -0
- package/docs/index.md +3 -1
- package/docs/mcp-reference.md +15 -2
- package/docs/scenarios-flows.md +86 -0
- package/docs/visual-notes.md +36 -0
- package/package.json +1 -1
- package/packages/client/src/client.ts +18 -1
- package/packages/client/src/config.ts +84 -1
- package/packages/client/src/index.ts +4 -1
- package/packages/client/src/interceptors/interaction.ts +3 -0
- package/packages/client/src/notes/inspector.ts +640 -166
- package/packages/client/src/source/resolver.ts +272 -0
- package/packages/core/src/types/events.ts +3 -0
- package/packages/core/src/types/notes.ts +36 -0
- package/packages/daemon/src/notes/engine.ts +6 -0
- package/packages/daemon/src/server/ws.ts +23 -1
- package/packages/daemon/src/storage/db.ts +106 -3
- package/packages/mcp/src/handlers.ts +59 -0
- package/packages/mcp/src/tools.ts +28 -0
- package/test/client/component-resolver.test.ts +141 -0
- package/test/client/interceptors.test.ts +120 -0
- package/test/daemon/scenario-storage.test.ts +158 -0
- package/dist/chunk-G2Y3CXCY.js.map +0 -1
- package/dist/chunk-ILRYKMME.js.map +0 -1
- package/dist/chunk-SKCMT2DE.js.map +0 -1
- package/dist/chunk-SPCIROIU.js.map +0 -1
|
@@ -29,12 +29,220 @@ var BreadcrumbBuffer = class {
|
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
// packages/client/src/source/resolver.ts
|
|
33
|
+
function resolveComponentSource(el) {
|
|
34
|
+
if (!el || typeof el !== "object") return void 0;
|
|
35
|
+
const reactInfo = resolveReactComponent(el);
|
|
36
|
+
if (reactInfo) return reactInfo;
|
|
37
|
+
const vueInfo = resolveVueComponent(el);
|
|
38
|
+
if (vueInfo) return vueInfo;
|
|
39
|
+
const svelteInfo = resolveSvelteComponent(el);
|
|
40
|
+
if (svelteInfo) return svelteInfo;
|
|
41
|
+
if (el.tagName && el.tagName.includes("-")) {
|
|
42
|
+
return {
|
|
43
|
+
framework: "web-component",
|
|
44
|
+
componentName: el.tagName.toLowerCase(),
|
|
45
|
+
hierarchy: [el.tagName.toLowerCase()]
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const attrInfo = resolveDataAttributeComponent(el);
|
|
49
|
+
if (attrInfo) return attrInfo;
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
function resolveReactComponent(el) {
|
|
53
|
+
try {
|
|
54
|
+
const fiberKey = Object.keys(el).find(
|
|
55
|
+
(key) => key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$")
|
|
56
|
+
);
|
|
57
|
+
if (!fiberKey) return void 0;
|
|
58
|
+
const hostFiber = el[fiberKey];
|
|
59
|
+
if (!hostFiber) return void 0;
|
|
60
|
+
let sourceFile;
|
|
61
|
+
let sourceLine;
|
|
62
|
+
let sourceColumn;
|
|
63
|
+
let componentName;
|
|
64
|
+
const hierarchy = [];
|
|
65
|
+
let props;
|
|
66
|
+
if (hostFiber._debugSource) {
|
|
67
|
+
sourceFile = hostFiber._debugSource.fileName;
|
|
68
|
+
sourceLine = hostFiber._debugSource.lineNumber;
|
|
69
|
+
sourceColumn = hostFiber._debugSource.columnNumber;
|
|
70
|
+
}
|
|
71
|
+
if (hostFiber._debugOwner) {
|
|
72
|
+
const ownerType = hostFiber._debugOwner.type;
|
|
73
|
+
componentName = getReactComponentName(ownerType);
|
|
74
|
+
if (!sourceFile && hostFiber._debugOwner._debugSource) {
|
|
75
|
+
sourceFile = hostFiber._debugOwner._debugSource.fileName;
|
|
76
|
+
sourceLine = hostFiber._debugOwner._debugSource.lineNumber;
|
|
77
|
+
sourceColumn = hostFiber._debugOwner._debugSource.columnNumber;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
let curr = hostFiber;
|
|
81
|
+
while (curr) {
|
|
82
|
+
const type = curr.type;
|
|
83
|
+
const name = getReactComponentName(type);
|
|
84
|
+
if (name && !name.startsWith("html:") && name !== "Fragment") {
|
|
85
|
+
if (!componentName) {
|
|
86
|
+
componentName = name;
|
|
87
|
+
}
|
|
88
|
+
if (!hierarchy.includes(name)) {
|
|
89
|
+
hierarchy.unshift(name);
|
|
90
|
+
}
|
|
91
|
+
if (!sourceFile && curr._debugSource) {
|
|
92
|
+
sourceFile = curr._debugSource.fileName;
|
|
93
|
+
sourceLine = curr._debugSource.lineNumber;
|
|
94
|
+
sourceColumn = curr._debugSource.columnNumber;
|
|
95
|
+
}
|
|
96
|
+
if (!props && curr.memoizedProps && typeof curr.memoizedProps === "object") {
|
|
97
|
+
props = sanitizeProps(curr.memoizedProps);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
curr = curr.return;
|
|
101
|
+
}
|
|
102
|
+
if (!componentName && !sourceFile) {
|
|
103
|
+
return void 0;
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
framework: "react",
|
|
107
|
+
componentName,
|
|
108
|
+
sourceFile: normalizeFilePath(sourceFile),
|
|
109
|
+
sourceLine,
|
|
110
|
+
sourceColumn,
|
|
111
|
+
hierarchy: hierarchy.length > 0 ? hierarchy : componentName ? [componentName] : void 0,
|
|
112
|
+
props
|
|
113
|
+
};
|
|
114
|
+
} catch {
|
|
115
|
+
return void 0;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function getReactComponentName(type) {
|
|
119
|
+
if (!type) return void 0;
|
|
120
|
+
if (typeof type === "string") return void 0;
|
|
121
|
+
if (type.displayName) return type.displayName;
|
|
122
|
+
if (type.name) return type.name;
|
|
123
|
+
if (type.render?.displayName) return type.render.displayName;
|
|
124
|
+
if (type.render?.name) return type.render.name;
|
|
125
|
+
return void 0;
|
|
126
|
+
}
|
|
127
|
+
function resolveVueComponent(el) {
|
|
128
|
+
try {
|
|
129
|
+
const vueParent = el.__vueParentComponent || el.__vnode?.ctx;
|
|
130
|
+
if (vueParent) {
|
|
131
|
+
const type = vueParent.type || {};
|
|
132
|
+
const componentName = type.name || type.__name || type.displayName || "AnonymousComponent";
|
|
133
|
+
const sourceFile = type.__file;
|
|
134
|
+
const hierarchy = [];
|
|
135
|
+
let curr = vueParent;
|
|
136
|
+
while (curr) {
|
|
137
|
+
const cType = curr.type || {};
|
|
138
|
+
const cName = cType.name || cType.__name || cType.displayName;
|
|
139
|
+
if (cName && !hierarchy.includes(cName)) {
|
|
140
|
+
hierarchy.unshift(cName);
|
|
141
|
+
}
|
|
142
|
+
curr = curr.parent;
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
framework: "vue",
|
|
146
|
+
componentName,
|
|
147
|
+
sourceFile: normalizeFilePath(sourceFile),
|
|
148
|
+
hierarchy: hierarchy.length > 0 ? hierarchy : [componentName],
|
|
149
|
+
props: vueParent.props ? sanitizeProps(vueParent.props) : void 0
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const vue2Instance = el.__vue__;
|
|
153
|
+
if (vue2Instance) {
|
|
154
|
+
const options = vue2Instance.$options || {};
|
|
155
|
+
const componentName = options.name || options._componentTag || "VueComponent";
|
|
156
|
+
const sourceFile = options.__file;
|
|
157
|
+
return {
|
|
158
|
+
framework: "vue",
|
|
159
|
+
componentName,
|
|
160
|
+
sourceFile: normalizeFilePath(sourceFile),
|
|
161
|
+
hierarchy: [componentName],
|
|
162
|
+
props: vue2Instance.$props ? sanitizeProps(vue2Instance.$props) : void 0
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return void 0;
|
|
166
|
+
} catch {
|
|
167
|
+
return void 0;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function resolveSvelteComponent(el) {
|
|
171
|
+
try {
|
|
172
|
+
let curr = el;
|
|
173
|
+
while (curr) {
|
|
174
|
+
const meta = curr.__svelte_meta;
|
|
175
|
+
if (meta && meta.loc) {
|
|
176
|
+
return {
|
|
177
|
+
framework: "svelte",
|
|
178
|
+
sourceFile: normalizeFilePath(meta.loc.file),
|
|
179
|
+
sourceLine: meta.loc.line,
|
|
180
|
+
sourceColumn: meta.loc.column,
|
|
181
|
+
componentName: meta.loc.file ? getBaseNameWithoutExt(meta.loc.file) : void 0,
|
|
182
|
+
hierarchy: meta.loc.file ? [getBaseNameWithoutExt(meta.loc.file)] : void 0
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
curr = curr.parentElement;
|
|
186
|
+
}
|
|
187
|
+
return void 0;
|
|
188
|
+
} catch {
|
|
189
|
+
return void 0;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function resolveDataAttributeComponent(el) {
|
|
193
|
+
try {
|
|
194
|
+
const compEl = el.closest("[data-component], [data-component-name], [data-source-file]");
|
|
195
|
+
if (!compEl) return void 0;
|
|
196
|
+
const componentName = compEl.getAttribute("data-component") || compEl.getAttribute("data-component-name") || void 0;
|
|
197
|
+
const sourceFile = compEl.getAttribute("data-source-file") || void 0;
|
|
198
|
+
const lineAttr = compEl.getAttribute("data-source-line");
|
|
199
|
+
const sourceLine = lineAttr ? parseInt(lineAttr, 10) : void 0;
|
|
200
|
+
if (!componentName && !sourceFile) return void 0;
|
|
201
|
+
return {
|
|
202
|
+
framework: "vanilla",
|
|
203
|
+
componentName,
|
|
204
|
+
sourceFile: normalizeFilePath(sourceFile),
|
|
205
|
+
sourceLine: isNaN(sourceLine) ? void 0 : sourceLine,
|
|
206
|
+
hierarchy: componentName ? [componentName] : void 0
|
|
207
|
+
};
|
|
208
|
+
} catch {
|
|
209
|
+
return void 0;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function normalizeFilePath(filePath) {
|
|
213
|
+
if (!filePath) return void 0;
|
|
214
|
+
const cleaned = filePath.split("?")[0];
|
|
215
|
+
return cleaned;
|
|
216
|
+
}
|
|
217
|
+
function getBaseNameWithoutExt(filePath) {
|
|
218
|
+
const parts = filePath.split("/");
|
|
219
|
+
const last = parts[parts.length - 1] || filePath;
|
|
220
|
+
return last.split(".")[0] || last;
|
|
221
|
+
}
|
|
222
|
+
function sanitizeProps(props) {
|
|
223
|
+
const result = {};
|
|
224
|
+
for (const [key, value] of Object.entries(props)) {
|
|
225
|
+
if (key.startsWith("__") || typeof value === "function") continue;
|
|
226
|
+
if (value === null || value === void 0) {
|
|
227
|
+
result[key] = value;
|
|
228
|
+
} else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
229
|
+
result[key] = value;
|
|
230
|
+
} else if (Array.isArray(value)) {
|
|
231
|
+
result[key] = `Array(${value.length})`;
|
|
232
|
+
} else if (typeof value === "object") {
|
|
233
|
+
result[key] = "[Object]";
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
|
|
32
239
|
// packages/client/src/interceptors/interaction.ts
|
|
33
240
|
function extractElementSummary(el) {
|
|
34
241
|
const selector = getSemanticSelector(el);
|
|
35
242
|
const tag = el.tagName.toLowerCase();
|
|
36
243
|
const id = el.id || void 0;
|
|
37
244
|
const classes = Array.from(el.classList || []);
|
|
245
|
+
const componentSource = resolveComponentSource(el);
|
|
38
246
|
let boundingRect = void 0;
|
|
39
247
|
let visible = true;
|
|
40
248
|
try {
|
|
@@ -66,7 +274,8 @@ function extractElementSummary(el) {
|
|
|
66
274
|
boundingRect,
|
|
67
275
|
visible,
|
|
68
276
|
innerText,
|
|
69
|
-
outerHTML
|
|
277
|
+
outerHTML,
|
|
278
|
+
componentSource
|
|
70
279
|
};
|
|
71
280
|
}
|
|
72
281
|
function setupInteractionInterceptors(onInteraction) {
|
|
@@ -384,10 +593,62 @@ var DEFAULT_OPTIONS = {
|
|
|
384
593
|
enabled: true,
|
|
385
594
|
shortcut: "Alt+Click",
|
|
386
595
|
showBadges: true,
|
|
596
|
+
showToolbar: true,
|
|
387
597
|
maskSelectors: ['input[type="password"]', "[data-sensitive]"]
|
|
388
598
|
},
|
|
599
|
+
hidden: false,
|
|
600
|
+
hideQueryParam: void 0,
|
|
389
601
|
debug: false
|
|
390
602
|
};
|
|
603
|
+
var HIDE_VALUES = /* @__PURE__ */ new Set(["0", "false", "hidden", "hide", "off", "none", "disabled", "ui_off", "silent"]);
|
|
604
|
+
function shouldHideUIFromUrl(customParam, searchString) {
|
|
605
|
+
let search = searchString;
|
|
606
|
+
if (search === void 0) {
|
|
607
|
+
if (typeof window === "undefined" || !window.location) return false;
|
|
608
|
+
search = window.location.search;
|
|
609
|
+
}
|
|
610
|
+
if (!search) return false;
|
|
611
|
+
try {
|
|
612
|
+
const params = new URLSearchParams(search);
|
|
613
|
+
if (customParam) {
|
|
614
|
+
const customKeys = Array.isArray(customParam) ? customParam : [customParam];
|
|
615
|
+
for (const key of customKeys) {
|
|
616
|
+
if (params.has(key)) {
|
|
617
|
+
const val = (params.get(key) || "").toLowerCase().trim();
|
|
618
|
+
if (val === "" || val === "1" || val === "true" || HIDE_VALUES.has(val)) {
|
|
619
|
+
return true;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
for (const flag of ["no_bt", "no_browsertrack", "hide_bt", "hide_browsertrack"]) {
|
|
625
|
+
if (params.has(flag)) {
|
|
626
|
+
const val = (params.get(flag) || "").toLowerCase().trim();
|
|
627
|
+
if (val === "" || val === "1" || val === "true" || val === "yes") {
|
|
628
|
+
return true;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (params.has("bt")) {
|
|
633
|
+
const val = (params.get("bt") || "").toLowerCase().trim();
|
|
634
|
+
if (HIDE_VALUES.has(val)) return true;
|
|
635
|
+
}
|
|
636
|
+
if (params.has("browsertrack")) {
|
|
637
|
+
const val = (params.get("browsertrack") || "").toLowerCase().trim();
|
|
638
|
+
if (HIDE_VALUES.has(val)) return true;
|
|
639
|
+
}
|
|
640
|
+
if (params.has("bt_ui")) {
|
|
641
|
+
const val = (params.get("bt_ui") || "").toLowerCase().trim();
|
|
642
|
+
if (HIDE_VALUES.has(val) || val === "0" || val === "false") return true;
|
|
643
|
+
}
|
|
644
|
+
if (params.has("bt_hide")) {
|
|
645
|
+
const val = (params.get("bt_hide") || "").toLowerCase().trim();
|
|
646
|
+
if (val === "" || val === "1" || val === "true") return true;
|
|
647
|
+
}
|
|
648
|
+
} catch {
|
|
649
|
+
}
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
391
652
|
|
|
392
653
|
// packages/client/src/interceptors/console.ts
|
|
393
654
|
function setupConsoleInterceptors(onConsole) {
|
|
@@ -1043,7 +1304,9 @@ var NoteInspector = class {
|
|
|
1043
1304
|
toolbarElement = null;
|
|
1044
1305
|
modalOverlay = null;
|
|
1045
1306
|
cardOverlay = null;
|
|
1307
|
+
toastContainer = null;
|
|
1046
1308
|
activeMode = "idle";
|
|
1309
|
+
activeScenario = null;
|
|
1047
1310
|
hoveredElement = null;
|
|
1048
1311
|
selectedElement = null;
|
|
1049
1312
|
selectedRegion = null;
|
|
@@ -1052,14 +1315,19 @@ var NoteInspector = class {
|
|
|
1052
1315
|
dragStartY = 0;
|
|
1053
1316
|
savedNotes = [];
|
|
1054
1317
|
showMarkers = true;
|
|
1318
|
+
isHidden = false;
|
|
1055
1319
|
cleanups = [];
|
|
1056
1320
|
constructor(transport, screenshotDriver, options = {}) {
|
|
1057
1321
|
this.transport = transport;
|
|
1058
1322
|
this.screenshotDriver = screenshotDriver;
|
|
1323
|
+
const hiddenByQuery = shouldHideUIFromUrl(options.hideQueryParam);
|
|
1324
|
+
this.isHidden = options.hidden === true || hiddenByQuery;
|
|
1325
|
+
this.showMarkers = options.showBadges !== false && !this.isHidden;
|
|
1059
1326
|
this.options = {
|
|
1060
1327
|
shortcut: "Alt+Click",
|
|
1061
1328
|
maskSelectors: ['input[type="password"]', "[data-sensitive]"],
|
|
1062
1329
|
showToolbar: true,
|
|
1330
|
+
showBadges: true,
|
|
1063
1331
|
...options
|
|
1064
1332
|
};
|
|
1065
1333
|
}
|
|
@@ -1085,6 +1353,41 @@ var NoteInspector = class {
|
|
|
1085
1353
|
this.updateToolbarCount();
|
|
1086
1354
|
this.renderMarkers();
|
|
1087
1355
|
}
|
|
1356
|
+
showToast(message, icon = "\u2728", durationMs = 2500) {
|
|
1357
|
+
if (typeof document === "undefined") return;
|
|
1358
|
+
const root = this.ensureContainer();
|
|
1359
|
+
if (!root || !this.toastContainer) return;
|
|
1360
|
+
const toast = document.createElement("div");
|
|
1361
|
+
toast.className = "bt-toast";
|
|
1362
|
+
toast.innerHTML = `<span>${icon}</span> <span>${message}</span>`;
|
|
1363
|
+
this.toastContainer.appendChild(toast);
|
|
1364
|
+
setTimeout(() => {
|
|
1365
|
+
toast.classList.add("bt-toast-fadeout");
|
|
1366
|
+
setTimeout(() => {
|
|
1367
|
+
if (toast.parentElement) {
|
|
1368
|
+
toast.parentElement.removeChild(toast);
|
|
1369
|
+
}
|
|
1370
|
+
}, 250);
|
|
1371
|
+
}, durationMs);
|
|
1372
|
+
}
|
|
1373
|
+
startScenario(title) {
|
|
1374
|
+
const defaultTitle = title || `Scenario ${(/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`;
|
|
1375
|
+
this.activeScenario = {
|
|
1376
|
+
id: `scen_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`,
|
|
1377
|
+
title: defaultTitle,
|
|
1378
|
+
stepNumber: 1
|
|
1379
|
+
};
|
|
1380
|
+
this.updateToolbarState();
|
|
1381
|
+
this.setMode("element");
|
|
1382
|
+
this.showToast(`Started flow: "${defaultTitle}"`, "\u{1F3AC}");
|
|
1383
|
+
}
|
|
1384
|
+
finishScenario() {
|
|
1385
|
+
const title = this.activeScenario?.title;
|
|
1386
|
+
this.activeScenario = null;
|
|
1387
|
+
this.updateToolbarState();
|
|
1388
|
+
this.setMode("idle");
|
|
1389
|
+
this.showToast(title ? `Finished flow: "${title}"` : "Flow recording finished", "\u2713");
|
|
1390
|
+
}
|
|
1088
1391
|
setMode(mode) {
|
|
1089
1392
|
this.activeMode = mode;
|
|
1090
1393
|
this.updateToolbarState();
|
|
@@ -1102,12 +1405,48 @@ var NoteInspector = class {
|
|
|
1102
1405
|
this.hideHighlight();
|
|
1103
1406
|
}
|
|
1104
1407
|
}
|
|
1408
|
+
isVisible() {
|
|
1409
|
+
return !this.isHidden;
|
|
1410
|
+
}
|
|
1411
|
+
setVisible(visible) {
|
|
1412
|
+
this.isHidden = !visible;
|
|
1413
|
+
this.showMarkers = this.options.showBadges !== false && !this.isHidden;
|
|
1414
|
+
if (this.container) {
|
|
1415
|
+
this.container.style.display = this.isHidden ? "none" : "block";
|
|
1416
|
+
}
|
|
1417
|
+
if (this.toolbarElement) {
|
|
1418
|
+
this.toolbarElement.style.display = this.isHidden ? "none" : "flex";
|
|
1419
|
+
} else if (!this.isHidden && this.options.showToolbar !== false) {
|
|
1420
|
+
this.createToolbar();
|
|
1421
|
+
}
|
|
1422
|
+
if (this.isHidden) {
|
|
1423
|
+
this.hideHighlight();
|
|
1424
|
+
this.hideRegionOverlay();
|
|
1425
|
+
if (this.modalOverlay && this.shadowRoot) {
|
|
1426
|
+
this.shadowRoot.removeChild(this.modalOverlay);
|
|
1427
|
+
this.modalOverlay = null;
|
|
1428
|
+
}
|
|
1429
|
+
if (this.cardOverlay && this.shadowRoot) {
|
|
1430
|
+
this.shadowRoot.removeChild(this.cardOverlay);
|
|
1431
|
+
this.cardOverlay = null;
|
|
1432
|
+
}
|
|
1433
|
+
if (this.markersContainer) {
|
|
1434
|
+
this.markersContainer.innerHTML = "";
|
|
1435
|
+
}
|
|
1436
|
+
} else {
|
|
1437
|
+
this.renderMarkers();
|
|
1438
|
+
this.updateToolbarCount();
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1105
1441
|
ensureContainer() {
|
|
1106
1442
|
if (typeof document === "undefined") return null;
|
|
1107
1443
|
if (!this.container) {
|
|
1108
1444
|
this.container = document.createElement("div");
|
|
1109
1445
|
this.container.id = "browsertrack-inspector-host";
|
|
1110
1446
|
this.container.style.cssText = "all: initial; position: fixed; top: 0; left: 0; width: 0; height: 0; z-index: 2147483647; pointer-events: none;";
|
|
1447
|
+
if (this.isHidden) {
|
|
1448
|
+
this.container.style.display = "none";
|
|
1449
|
+
}
|
|
1111
1450
|
this.shadowRoot = this.container.attachShadow({ mode: "open" });
|
|
1112
1451
|
const style = document.createElement("style");
|
|
1113
1452
|
style.textContent = `
|
|
@@ -1264,6 +1603,12 @@ var NoteInspector = class {
|
|
|
1264
1603
|
z-index: 2147483641;
|
|
1265
1604
|
}
|
|
1266
1605
|
|
|
1606
|
+
.bt-note-marker-step {
|
|
1607
|
+
background: linear-gradient(135deg, #f59e0b, #d97706);
|
|
1608
|
+
border-color: #fef3c7;
|
|
1609
|
+
box-shadow: 0 4px 14px rgba(245, 158, 11, 0.4), 0 0 0 1px rgba(217, 119, 6, 0.5);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1267
1612
|
@keyframes bt-pop-in {
|
|
1268
1613
|
0% { transform: scale(0.6); opacity: 0; }
|
|
1269
1614
|
100% { transform: scale(1); opacity: 1; }
|
|
@@ -1274,6 +1619,10 @@ var NoteInspector = class {
|
|
|
1274
1619
|
box-shadow: 0 8px 20px rgba(37,99,235,0.6), 0 0 0 2px #60a5fa;
|
|
1275
1620
|
}
|
|
1276
1621
|
|
|
1622
|
+
.bt-note-marker-step:hover {
|
|
1623
|
+
box-shadow: 0 8px 20px rgba(245, 158, 11, 0.7), 0 0 0 2px #fde68a;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1277
1626
|
.bt-marker-resolved {
|
|
1278
1627
|
background: linear-gradient(135deg, #475569, #334155);
|
|
1279
1628
|
border-color: #94a3b8;
|
|
@@ -1370,6 +1719,19 @@ var NoteInspector = class {
|
|
|
1370
1719
|
box-shadow: 0 2px 6px rgba(37, 99, 235, 0.35);
|
|
1371
1720
|
}
|
|
1372
1721
|
|
|
1722
|
+
.bt-toolbar-btn.active-scenario {
|
|
1723
|
+
background: linear-gradient(135deg, #f59e0b, #d97706);
|
|
1724
|
+
color: #ffffff;
|
|
1725
|
+
font-weight: 700;
|
|
1726
|
+
box-shadow: 0 2px 8px rgba(245, 158, 11, 0.4);
|
|
1727
|
+
animation: bt-pulse 2s infinite;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
@keyframes bt-pulse {
|
|
1731
|
+
0%, 100% { opacity: 1; }
|
|
1732
|
+
50% { opacity: 0.85; }
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1373
1735
|
.bt-count-pill {
|
|
1374
1736
|
background: rgba(255, 255, 255, 0.2);
|
|
1375
1737
|
color: #ffffff;
|
|
@@ -1417,7 +1779,7 @@ var NoteInspector = class {
|
|
|
1417
1779
|
border: 1px solid #334155;
|
|
1418
1780
|
border-radius: 14px;
|
|
1419
1781
|
padding: 20px;
|
|
1420
|
-
width:
|
|
1782
|
+
width: 470px;
|
|
1421
1783
|
max-width: 92vw;
|
|
1422
1784
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(255, 255, 255, 0.05);
|
|
1423
1785
|
display: flex;
|
|
@@ -1440,6 +1802,7 @@ var NoteInspector = class {
|
|
|
1440
1802
|
align-items: center;
|
|
1441
1803
|
gap: 8px;
|
|
1442
1804
|
color: #f8fafc;
|
|
1805
|
+
flex-wrap: wrap;
|
|
1443
1806
|
}
|
|
1444
1807
|
|
|
1445
1808
|
.bt-mode-badge {
|
|
@@ -1469,6 +1832,12 @@ var NoteInspector = class {
|
|
|
1469
1832
|
border: 1px solid rgba(168, 85, 247, 0.4);
|
|
1470
1833
|
}
|
|
1471
1834
|
|
|
1835
|
+
.bt-badge-step {
|
|
1836
|
+
background: rgba(245, 158, 11, 0.15);
|
|
1837
|
+
color: #fbbf24;
|
|
1838
|
+
border: 1px solid rgba(245, 158, 11, 0.4);
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1472
1841
|
.bt-badge-status-open {
|
|
1473
1842
|
background: rgba(16, 185, 129, 0.15);
|
|
1474
1843
|
color: #34d399;
|
|
@@ -1520,6 +1889,59 @@ var NoteInspector = class {
|
|
|
1520
1889
|
color: #cbd5e1;
|
|
1521
1890
|
}
|
|
1522
1891
|
|
|
1892
|
+
.bt-component-pill {
|
|
1893
|
+
display: flex;
|
|
1894
|
+
align-items: center;
|
|
1895
|
+
gap: 6px;
|
|
1896
|
+
background: rgba(15, 23, 42, 0.95);
|
|
1897
|
+
border: 1px solid rgba(99, 102, 241, 0.35);
|
|
1898
|
+
border-radius: 6px;
|
|
1899
|
+
padding: 6px 10px;
|
|
1900
|
+
font-size: 11.5px;
|
|
1901
|
+
color: #c7d2fe;
|
|
1902
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
1903
|
+
overflow: hidden;
|
|
1904
|
+
text-overflow: ellipsis;
|
|
1905
|
+
white-space: nowrap;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
.bt-scenario-stepper {
|
|
1909
|
+
display: flex;
|
|
1910
|
+
align-items: center;
|
|
1911
|
+
justify-content: space-between;
|
|
1912
|
+
background: #090d16;
|
|
1913
|
+
border: 1px solid #1e293b;
|
|
1914
|
+
border-radius: 8px;
|
|
1915
|
+
padding: 6px 10px;
|
|
1916
|
+
font-size: 12px;
|
|
1917
|
+
font-weight: 600;
|
|
1918
|
+
color: #f59e0b;
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
.bt-step-nav-btn {
|
|
1922
|
+
background: #1e293b;
|
|
1923
|
+
color: #f8fafc;
|
|
1924
|
+
border: 1px solid #334155;
|
|
1925
|
+
padding: 4px 10px;
|
|
1926
|
+
border-radius: 6px;
|
|
1927
|
+
cursor: pointer;
|
|
1928
|
+
font-size: 11px;
|
|
1929
|
+
font-weight: 600;
|
|
1930
|
+
transition: all 0.12s ease;
|
|
1931
|
+
font-family: inherit;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
.bt-step-nav-btn:hover:not(:disabled) {
|
|
1935
|
+
background: #2563eb;
|
|
1936
|
+
border-color: #3b82f6;
|
|
1937
|
+
color: #ffffff;
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
.bt-step-nav-btn:disabled {
|
|
1941
|
+
opacity: 0.3;
|
|
1942
|
+
cursor: not-allowed;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1523
1945
|
.bt-note-message-box {
|
|
1524
1946
|
background: #090d16;
|
|
1525
1947
|
border: 1px solid #1e293b;
|
|
@@ -1564,8 +1986,9 @@ var NoteInspector = class {
|
|
|
1564
1986
|
display: flex;
|
|
1565
1987
|
justify-content: space-between;
|
|
1566
1988
|
align-items: center;
|
|
1567
|
-
gap:
|
|
1989
|
+
gap: 10px;
|
|
1568
1990
|
margin-top: 2px;
|
|
1991
|
+
flex-wrap: wrap;
|
|
1569
1992
|
}
|
|
1570
1993
|
|
|
1571
1994
|
.bt-kbd-hint {
|
|
@@ -1593,12 +2016,13 @@ var NoteInspector = class {
|
|
|
1593
2016
|
justify-content: flex-end;
|
|
1594
2017
|
align-items: center;
|
|
1595
2018
|
gap: 8px;
|
|
2019
|
+
flex-wrap: wrap;
|
|
1596
2020
|
}
|
|
1597
2021
|
|
|
1598
2022
|
.bt-btn {
|
|
1599
|
-
padding:
|
|
2023
|
+
padding: 7px 14px;
|
|
1600
2024
|
border-radius: 7px;
|
|
1601
|
-
font-size:
|
|
2025
|
+
font-size: 12px;
|
|
1602
2026
|
font-weight: 600;
|
|
1603
2027
|
cursor: pointer;
|
|
1604
2028
|
border: 1px solid transparent;
|
|
@@ -1607,7 +2031,7 @@ var NoteInspector = class {
|
|
|
1607
2031
|
display: inline-flex;
|
|
1608
2032
|
align-items: center;
|
|
1609
2033
|
justify-content: center;
|
|
1610
|
-
gap:
|
|
2034
|
+
gap: 5px;
|
|
1611
2035
|
}
|
|
1612
2036
|
|
|
1613
2037
|
.bt-btn-cancel {
|
|
@@ -1629,6 +2053,25 @@ var NoteInspector = class {
|
|
|
1629
2053
|
box-shadow: 0 4px 10px rgba(37, 99, 235, 0.45);
|
|
1630
2054
|
}
|
|
1631
2055
|
|
|
2056
|
+
.bt-btn-next-step {
|
|
2057
|
+
background: linear-gradient(135deg, #6366f1, #4f46e5);
|
|
2058
|
+
color: #ffffff;
|
|
2059
|
+
box-shadow: 0 2px 8px rgba(99, 102, 241, 0.4);
|
|
2060
|
+
}
|
|
2061
|
+
.bt-btn-next-step:hover {
|
|
2062
|
+
background: linear-gradient(135deg, #4f46e5, #4338ca);
|
|
2063
|
+
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.55);
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
.bt-btn-finish-flow {
|
|
2067
|
+
background: linear-gradient(135deg, #10b981, #059669);
|
|
2068
|
+
color: #ffffff;
|
|
2069
|
+
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.35);
|
|
2070
|
+
}
|
|
2071
|
+
.bt-btn-finish-flow:hover {
|
|
2072
|
+
background: linear-gradient(135deg, #059669, #047857);
|
|
2073
|
+
}
|
|
2074
|
+
|
|
1632
2075
|
.bt-btn-resolve {
|
|
1633
2076
|
background: rgba(16, 185, 129, 0.15);
|
|
1634
2077
|
color: #6ee7b7;
|
|
@@ -1648,6 +2091,47 @@ var NoteInspector = class {
|
|
|
1648
2091
|
background: rgba(239, 68, 68, 0.3);
|
|
1649
2092
|
color: #ffffff;
|
|
1650
2093
|
}
|
|
2094
|
+
|
|
2095
|
+
/* 6. Toast Notifications */
|
|
2096
|
+
.bt-toast-container {
|
|
2097
|
+
position: fixed;
|
|
2098
|
+
bottom: 74px;
|
|
2099
|
+
right: 24px;
|
|
2100
|
+
display: flex;
|
|
2101
|
+
flex-direction: column;
|
|
2102
|
+
gap: 8px;
|
|
2103
|
+
pointer-events: none;
|
|
2104
|
+
z-index: 2147483647;
|
|
2105
|
+
align-items: flex-end;
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
.bt-toast {
|
|
2109
|
+
background: #0f172a;
|
|
2110
|
+
color: #f8fafc;
|
|
2111
|
+
border: 1px solid #334155;
|
|
2112
|
+
border-radius: 30px;
|
|
2113
|
+
padding: 8px 16px;
|
|
2114
|
+
font-size: 12.5px;
|
|
2115
|
+
font-weight: 500;
|
|
2116
|
+
display: inline-flex;
|
|
2117
|
+
align-items: center;
|
|
2118
|
+
gap: 8px;
|
|
2119
|
+
box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.6), 0 4px 6px -4px rgba(0, 0, 0, 0.4);
|
|
2120
|
+
pointer-events: auto;
|
|
2121
|
+
animation: bt-toast-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
2122
|
+
transition: opacity 0.25s ease, transform 0.25s ease;
|
|
2123
|
+
user-select: none;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
.bt-toast-fadeout {
|
|
2127
|
+
opacity: 0;
|
|
2128
|
+
transform: translateY(6px);
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
@keyframes bt-toast-in {
|
|
2132
|
+
from { opacity: 0; transform: translateY(8px) scale(0.95); }
|
|
2133
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
2134
|
+
}
|
|
1651
2135
|
`;
|
|
1652
2136
|
this.shadowRoot.appendChild(style);
|
|
1653
2137
|
this.highlightOverlay = document.createElement("div");
|
|
@@ -1657,7 +2141,10 @@ var NoteInspector = class {
|
|
|
1657
2141
|
this.markersContainer = document.createElement("div");
|
|
1658
2142
|
this.markersContainer.className = "bt-markers-layer";
|
|
1659
2143
|
this.shadowRoot.appendChild(this.markersContainer);
|
|
1660
|
-
|
|
2144
|
+
this.toastContainer = document.createElement("div");
|
|
2145
|
+
this.toastContainer.className = "bt-toast-container";
|
|
2146
|
+
this.shadowRoot.appendChild(this.toastContainer);
|
|
2147
|
+
if (this.options.showToolbar && !this.isHidden) {
|
|
1661
2148
|
this.createToolbar();
|
|
1662
2149
|
}
|
|
1663
2150
|
}
|
|
@@ -1684,6 +2171,10 @@ var NoteInspector = class {
|
|
|
1684
2171
|
<span>\u{1F4C4}</span> Page
|
|
1685
2172
|
</button>
|
|
1686
2173
|
<div class="bt-toolbar-divider"></div>
|
|
2174
|
+
<button class="bt-toolbar-btn" id="bt-mode-flow" title="Record multi-step reproduction flow">
|
|
2175
|
+
<span>\u{1F3AC}</span> Flow
|
|
2176
|
+
</button>
|
|
2177
|
+
<div class="bt-toolbar-divider"></div>
|
|
1687
2178
|
<button class="bt-toolbar-btn active" id="bt-toggle-notes" title="Toggle visible note markers on screen">
|
|
1688
2179
|
<span>\u{1F4CC}</span> Notes <span class="bt-count-pill" id="bt-notes-count">0</span>
|
|
1689
2180
|
</button>
|
|
@@ -1691,6 +2182,7 @@ var NoteInspector = class {
|
|
|
1691
2182
|
const btnElement = this.toolbarElement.querySelector("#bt-mode-element");
|
|
1692
2183
|
const btnRegion = this.toolbarElement.querySelector("#bt-mode-region");
|
|
1693
2184
|
const btnPage = this.toolbarElement.querySelector("#bt-mode-page");
|
|
2185
|
+
const btnFlow = this.toolbarElement.querySelector("#bt-mode-flow");
|
|
1694
2186
|
const btnToggleNotes = this.toolbarElement.querySelector("#bt-toggle-notes");
|
|
1695
2187
|
btnElement.onclick = (e) => {
|
|
1696
2188
|
e.stopPropagation();
|
|
@@ -1704,6 +2196,14 @@ var NoteInspector = class {
|
|
|
1704
2196
|
e.stopPropagation();
|
|
1705
2197
|
this.setMode("page");
|
|
1706
2198
|
};
|
|
2199
|
+
btnFlow.onclick = (e) => {
|
|
2200
|
+
e.stopPropagation();
|
|
2201
|
+
if (this.activeScenario) {
|
|
2202
|
+
this.finishScenario();
|
|
2203
|
+
} else {
|
|
2204
|
+
this.startScenario();
|
|
2205
|
+
}
|
|
2206
|
+
};
|
|
1707
2207
|
btnToggleNotes.onclick = (e) => {
|
|
1708
2208
|
e.stopPropagation();
|
|
1709
2209
|
this.showMarkers = !this.showMarkers;
|
|
@@ -1717,9 +2217,21 @@ var NoteInspector = class {
|
|
|
1717
2217
|
const btnElement = this.toolbarElement.querySelector("#bt-mode-element");
|
|
1718
2218
|
const btnRegion = this.toolbarElement.querySelector("#bt-mode-region");
|
|
1719
2219
|
const btnPage = this.toolbarElement.querySelector("#bt-mode-page");
|
|
2220
|
+
const btnFlow = this.toolbarElement.querySelector("#bt-mode-flow");
|
|
1720
2221
|
btnElement?.classList.toggle("active", this.activeMode === "element");
|
|
1721
2222
|
btnRegion?.classList.toggle("active", this.activeMode === "region");
|
|
1722
2223
|
btnPage?.classList.toggle("active", this.activeMode === "page");
|
|
2224
|
+
if (btnFlow) {
|
|
2225
|
+
if (this.activeScenario) {
|
|
2226
|
+
btnFlow.className = "bt-toolbar-btn active-scenario";
|
|
2227
|
+
btnFlow.innerHTML = `<span>\u{1F3AC}</span> Step ${this.activeScenario.stepNumber} (Finish)`;
|
|
2228
|
+
btnFlow.setAttribute("title", `Click to finish recording "${this.activeScenario.title}"`);
|
|
2229
|
+
} else {
|
|
2230
|
+
btnFlow.className = "bt-toolbar-btn";
|
|
2231
|
+
btnFlow.innerHTML = `<span>\u{1F3AC}</span> Flow`;
|
|
2232
|
+
btnFlow.setAttribute("title", "Record multi-step reproduction flow");
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
1723
2235
|
}
|
|
1724
2236
|
updateToolbarCount() {
|
|
1725
2237
|
if (!this.toolbarElement) return;
|
|
@@ -1733,7 +2245,7 @@ var NoteInspector = class {
|
|
|
1733
2245
|
const root = this.ensureContainer();
|
|
1734
2246
|
if (!root || !this.markersContainer) return;
|
|
1735
2247
|
this.markersContainer.innerHTML = "";
|
|
1736
|
-
if (!this.showMarkers) return;
|
|
2248
|
+
if (!this.showMarkers || this.isHidden) return;
|
|
1737
2249
|
const currentPath = window.location.pathname;
|
|
1738
2250
|
const activeNotes = this.savedNotes.filter(
|
|
1739
2251
|
(n) => n.status === "OPEN" && (n.route === currentPath || !n.route || n.route === "/" || window.location.href.includes(n.route))
|
|
@@ -1744,6 +2256,9 @@ var NoteInspector = class {
|
|
|
1744
2256
|
pageNotes.push(note);
|
|
1745
2257
|
return;
|
|
1746
2258
|
}
|
|
2259
|
+
const isStep = !!note.scenarioId && note.stepNumber != null;
|
|
2260
|
+
const stepLabel = isStep ? `\u{1F3AC} Step ${note.stepNumber}` : `#${index + 1}`;
|
|
2261
|
+
const markerClass = `bt-note-marker ${isStep ? "bt-note-marker-step" : ""}`;
|
|
1747
2262
|
if (note.type === "region" && note.region) {
|
|
1748
2263
|
const regBox = document.createElement("div");
|
|
1749
2264
|
regBox.className = "bt-region-marker-box";
|
|
@@ -1753,11 +2268,11 @@ var NoteInspector = class {
|
|
|
1753
2268
|
regBox.style.height = `${note.region.height}px`;
|
|
1754
2269
|
this.markersContainer.appendChild(regBox);
|
|
1755
2270
|
const pin = document.createElement("div");
|
|
1756
|
-
pin.className =
|
|
2271
|
+
pin.className = markerClass;
|
|
1757
2272
|
pin.style.left = `${Math.max(4, note.region.x - 12)}px`;
|
|
1758
2273
|
pin.style.top = `${Math.max(4, note.region.y - 12)}px`;
|
|
1759
|
-
pin.title = note.message;
|
|
1760
|
-
pin.innerHTML = `<span
|
|
2274
|
+
pin.title = isStep ? `[${note.scenarioTitle || "Scenario"}] Step ${note.stepNumber}: ${note.message}` : note.message;
|
|
2275
|
+
pin.innerHTML = `<span>${isStep ? "\u{1F3AC}" : "\u{1F4D0}"}</span> <span>${stepLabel}</span>`;
|
|
1761
2276
|
pin.onclick = (e) => {
|
|
1762
2277
|
e.stopPropagation();
|
|
1763
2278
|
this.openNoteCard(note);
|
|
@@ -1775,12 +2290,12 @@ var NoteInspector = class {
|
|
|
1775
2290
|
const rect = targetEl ? targetEl.getBoundingClientRect() : note.target?.boundingRect;
|
|
1776
2291
|
if (rect) {
|
|
1777
2292
|
const pin = document.createElement("div");
|
|
1778
|
-
pin.className =
|
|
2293
|
+
pin.className = markerClass;
|
|
1779
2294
|
pin.setAttribute("data-note-id", note.id);
|
|
1780
|
-
pin.title = note.message;
|
|
2295
|
+
pin.title = isStep ? `[${note.scenarioTitle || "Scenario"}] Step ${note.stepNumber}: ${note.message}` : note.message;
|
|
1781
2296
|
pin.style.left = `${Math.max(4, rect.left - 10)}px`;
|
|
1782
2297
|
pin.style.top = `${Math.max(4, rect.top - 12)}px`;
|
|
1783
|
-
pin.innerHTML = `<span
|
|
2298
|
+
pin.innerHTML = `<span>${isStep ? "\u{1F3AC}" : "\u{1F4DD}"}</span> <span>${stepLabel}</span>`;
|
|
1784
2299
|
pin.onclick = (e) => {
|
|
1785
2300
|
e.stopPropagation();
|
|
1786
2301
|
this.openNoteCard(note);
|
|
@@ -1795,10 +2310,11 @@ var NoteInspector = class {
|
|
|
1795
2310
|
if (pageNotes.length > 0) {
|
|
1796
2311
|
const pageDock = document.createElement("div");
|
|
1797
2312
|
pageDock.className = "bt-page-notes-dock";
|
|
1798
|
-
pageNotes.forEach((pNote
|
|
2313
|
+
pageNotes.forEach((pNote) => {
|
|
1799
2314
|
const pill = document.createElement("div");
|
|
1800
2315
|
pill.className = "bt-page-note-pill";
|
|
1801
|
-
|
|
2316
|
+
const label = pNote.scenarioId && pNote.stepNumber ? `Step ${pNote.stepNumber}: ` : "";
|
|
2317
|
+
pill.innerHTML = `<span>\u{1F4C4}</span> <span>${label}${truncate(pNote.message, 25)}</span>`;
|
|
1802
2318
|
pill.onclick = (e) => {
|
|
1803
2319
|
e.stopPropagation();
|
|
1804
2320
|
this.openNoteCard(pNote);
|
|
@@ -1827,17 +2343,46 @@ var NoteInspector = class {
|
|
|
1827
2343
|
const icon = note.type === "region" ? "\u{1F4D0}" : note.type === "page" ? "\u{1F4C4}" : "\u{1F3AF}";
|
|
1828
2344
|
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}`;
|
|
1829
2345
|
const formattedDate = new Date(note.createdAt).toLocaleString();
|
|
2346
|
+
let scenarioStepsHtml = "";
|
|
2347
|
+
let scenarioSteps = [];
|
|
2348
|
+
if (note.scenarioId) {
|
|
2349
|
+
scenarioSteps = this.savedNotes.filter((n) => n.scenarioId === note.scenarioId).sort((a, b) => (a.stepNumber || 0) - (b.stepNumber || 0));
|
|
2350
|
+
const currentIndex = scenarioSteps.findIndex((s) => s.id === note.id);
|
|
2351
|
+
const prevStep = currentIndex > 0 ? scenarioSteps[currentIndex - 1] : null;
|
|
2352
|
+
const nextStep = currentIndex >= 0 && currentIndex < scenarioSteps.length - 1 ? scenarioSteps[currentIndex + 1] : null;
|
|
2353
|
+
scenarioStepsHtml = `
|
|
2354
|
+
<div class="bt-scenario-stepper">
|
|
2355
|
+
<button class="bt-step-nav-btn" id="btn-prev-step" ${!prevStep ? "disabled" : ""}>
|
|
2356
|
+
\u25C0 Step ${prevStep ? prevStep.stepNumber : ""}
|
|
2357
|
+
</button>
|
|
2358
|
+
<span>\u{1F3AC} Step ${note.stepNumber || 1} of ${scenarioSteps.length}</span>
|
|
2359
|
+
<button class="bt-step-nav-btn" id="btn-next-step-card" ${!nextStep ? "disabled" : ""}>
|
|
2360
|
+
Step ${nextStep ? nextStep.stepNumber : ""} \u25B6
|
|
2361
|
+
</button>
|
|
2362
|
+
</div>
|
|
2363
|
+
`;
|
|
2364
|
+
}
|
|
1830
2365
|
backdrop.innerHTML = `
|
|
1831
2366
|
<div class="bt-modal">
|
|
1832
2367
|
<div class="bt-modal-header">
|
|
1833
2368
|
<div class="bt-modal-title">
|
|
1834
|
-
<span>${
|
|
2369
|
+
<span>${note.scenarioId ? "\u{1F3AC} " + (note.scenarioTitle || "Scenario Flow") : icon + " Visual Note Details"}</span>
|
|
1835
2370
|
<span class="bt-mode-badge ${badgeClass}">${note.type.toUpperCase()}</span>
|
|
2371
|
+
${note.scenarioId && note.stepNumber ? `<span class="bt-mode-badge bt-badge-step">STEP ${note.stepNumber}</span>` : ""}
|
|
1836
2372
|
<span class="bt-mode-badge ${statusClass}">${note.status}</span>
|
|
1837
2373
|
</div>
|
|
1838
2374
|
<button class="bt-close-btn" id="btn-card-close" title="Close (Esc)">\u2715</button>
|
|
1839
2375
|
</div>
|
|
1840
2376
|
|
|
2377
|
+
${scenarioStepsHtml}
|
|
2378
|
+
|
|
2379
|
+
${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}">
|
|
2380
|
+
<span>\u{1F9EC}</span>
|
|
2381
|
+
<span style="font-weight: 600; color: #a5b4fc;"><${note.elementContext.componentSource.componentName}></span>
|
|
2382
|
+
${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>` : ""}
|
|
2383
|
+
${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>` : ""}
|
|
2384
|
+
</div>` : ""}
|
|
2385
|
+
|
|
1841
2386
|
<div class="bt-target-pill" title="${contextText}">
|
|
1842
2387
|
<span>\u{1F3F7}\uFE0F</span>
|
|
1843
2388
|
<span class="bt-pill-content">${contextText}</span>
|
|
@@ -1851,9 +2396,14 @@ var NoteInspector = class {
|
|
|
1851
2396
|
</div>
|
|
1852
2397
|
|
|
1853
2398
|
<div class="bt-modal-footer">
|
|
1854
|
-
<
|
|
1855
|
-
<
|
|
1856
|
-
|
|
2399
|
+
<div style="display: flex; gap: 6px;">
|
|
2400
|
+
<button class="bt-btn bt-btn-delete" id="btn-card-delete" title="Delete this note">
|
|
2401
|
+
<span>\u{1F5D1}\uFE0F</span> Delete
|
|
2402
|
+
</button>
|
|
2403
|
+
${note.scenarioId ? `<button class="bt-btn bt-btn-delete" id="btn-card-delete-flow" title="Delete entire scenario flow">
|
|
2404
|
+
<span>\u{1F5D1}\uFE0F</span> Delete Flow
|
|
2405
|
+
</button>` : ""}
|
|
2406
|
+
</div>
|
|
1857
2407
|
<div class="bt-modal-actions">
|
|
1858
2408
|
<button class="bt-btn bt-btn-cancel" id="btn-card-dismiss">Close</button>
|
|
1859
2409
|
<button class="bt-btn ${note.status === "OPEN" ? "bt-btn-resolve" : "bt-btn-save"}" id="btn-card-resolve">
|
|
@@ -1867,6 +2417,9 @@ var NoteInspector = class {
|
|
|
1867
2417
|
const btnDismiss = backdrop.querySelector("#btn-card-dismiss");
|
|
1868
2418
|
const btnResolve = backdrop.querySelector("#btn-card-resolve");
|
|
1869
2419
|
const btnDelete = backdrop.querySelector("#btn-card-delete");
|
|
2420
|
+
const btnDeleteFlow = backdrop.querySelector("#btn-card-delete-flow");
|
|
2421
|
+
const btnPrevStep = backdrop.querySelector("#btn-prev-step");
|
|
2422
|
+
const btnNextStepCard = backdrop.querySelector("#btn-next-step-card");
|
|
1870
2423
|
const closeCard = () => {
|
|
1871
2424
|
if (this.cardOverlay) {
|
|
1872
2425
|
root.removeChild(this.cardOverlay);
|
|
@@ -1879,23 +2432,49 @@ var NoteInspector = class {
|
|
|
1879
2432
|
backdrop.onclick = (e) => {
|
|
1880
2433
|
if (e.target === backdrop) closeCard();
|
|
1881
2434
|
};
|
|
2435
|
+
if (btnPrevStep && scenarioSteps.length > 0) {
|
|
2436
|
+
const currentIndex = scenarioSteps.findIndex((s) => s.id === note.id);
|
|
2437
|
+
if (currentIndex > 0) {
|
|
2438
|
+
btnPrevStep.onclick = () => {
|
|
2439
|
+
this.openNoteCard(scenarioSteps[currentIndex - 1]);
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
if (btnNextStepCard && scenarioSteps.length > 0) {
|
|
2444
|
+
const currentIndex = scenarioSteps.findIndex((s) => s.id === note.id);
|
|
2445
|
+
if (currentIndex >= 0 && currentIndex < scenarioSteps.length - 1) {
|
|
2446
|
+
btnNextStepCard.onclick = () => {
|
|
2447
|
+
this.openNoteCard(scenarioSteps[currentIndex + 1]);
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
1882
2451
|
btnResolve.onclick = () => {
|
|
1883
2452
|
if (note.status === "OPEN") {
|
|
1884
2453
|
this.transport.send({ type: "resolve_note", noteId: note.id });
|
|
2454
|
+
this.showToast("Note marked as resolved", "\u2705");
|
|
1885
2455
|
} else {
|
|
1886
2456
|
this.transport.send({ type: "reopen_note", noteId: note.id });
|
|
2457
|
+
this.showToast("Note reopened", "\u21BA");
|
|
1887
2458
|
}
|
|
1888
2459
|
closeCard();
|
|
1889
2460
|
};
|
|
1890
2461
|
btnDelete.onclick = () => {
|
|
1891
2462
|
this.transport.send({ type: "delete_note", noteId: note.id });
|
|
2463
|
+
this.showToast("Note deleted", "\u{1F5D1}\uFE0F");
|
|
1892
2464
|
closeCard();
|
|
1893
2465
|
};
|
|
2466
|
+
if (btnDeleteFlow && note.scenarioId) {
|
|
2467
|
+
btnDeleteFlow.onclick = () => {
|
|
2468
|
+
this.transport.send({ type: "delete_scenario", scenarioId: note.scenarioId });
|
|
2469
|
+
this.showToast("Scenario flow deleted", "\u{1F5D1}\uFE0F");
|
|
2470
|
+
closeCard();
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
1894
2473
|
root.appendChild(backdrop);
|
|
1895
2474
|
}
|
|
1896
2475
|
setupListeners() {
|
|
1897
2476
|
const onMouseMove = (e) => {
|
|
1898
|
-
if (this.modalOverlay || this.cardOverlay || this.isDraggingRegion || this.activeMode === "region") return;
|
|
2477
|
+
if (this.isHidden || this.modalOverlay || this.cardOverlay || this.isDraggingRegion || this.activeMode === "region") return;
|
|
1899
2478
|
const path = e.composedPath ? e.composedPath() : [];
|
|
1900
2479
|
if (this.container && path.includes(this.container)) {
|
|
1901
2480
|
this.hideHighlight();
|
|
@@ -1914,7 +2493,7 @@ var NoteInspector = class {
|
|
|
1914
2493
|
}
|
|
1915
2494
|
};
|
|
1916
2495
|
const onClick = (e) => {
|
|
1917
|
-
if (this.modalOverlay || this.cardOverlay) return;
|
|
2496
|
+
if (this.isHidden || this.modalOverlay || this.cardOverlay) return;
|
|
1918
2497
|
const path = e.composedPath ? e.composedPath() : [];
|
|
1919
2498
|
if (this.container && path.includes(this.container)) {
|
|
1920
2499
|
return;
|
|
@@ -1927,7 +2506,7 @@ var NoteInspector = class {
|
|
|
1927
2506
|
if (target && target !== this.container && !this.container?.contains(target)) {
|
|
1928
2507
|
this.selectedElement = target;
|
|
1929
2508
|
this.openNoteEditor(target, "element");
|
|
1930
|
-
if (this.activeMode === "element") {
|
|
2509
|
+
if (this.activeMode === "element" && !this.activeScenario) {
|
|
1931
2510
|
this.setMode("idle");
|
|
1932
2511
|
}
|
|
1933
2512
|
}
|
|
@@ -1939,7 +2518,9 @@ var NoteInspector = class {
|
|
|
1939
2518
|
this.shadowRoot.removeChild(this.cardOverlay);
|
|
1940
2519
|
this.cardOverlay = null;
|
|
1941
2520
|
} else if (this.activeMode === "region" || this.activeMode === "element") {
|
|
1942
|
-
this.
|
|
2521
|
+
if (!this.activeScenario) {
|
|
2522
|
+
this.setMode("idle");
|
|
2523
|
+
}
|
|
1943
2524
|
}
|
|
1944
2525
|
}
|
|
1945
2526
|
};
|
|
@@ -2045,28 +2626,27 @@ var NoteInspector = class {
|
|
|
2045
2626
|
width: Math.round(width),
|
|
2046
2627
|
height: Math.round(height)
|
|
2047
2628
|
};
|
|
2048
|
-
this.hideRegionOverlay();
|
|
2049
|
-
this.setMode("idle");
|
|
2050
2629
|
this.openRegionNoteEditor(this.selectedRegion);
|
|
2051
|
-
}
|
|
2052
|
-
|
|
2630
|
+
}
|
|
2631
|
+
if (this.regionBox) {
|
|
2632
|
+
this.regionBox.style.display = "none";
|
|
2633
|
+
}
|
|
2634
|
+
if (!this.activeScenario) {
|
|
2053
2635
|
this.setMode("idle");
|
|
2054
2636
|
}
|
|
2055
2637
|
};
|
|
2056
|
-
}
|
|
2057
|
-
if (this.toolbarElement && this.toolbarElement.parentNode === root) {
|
|
2058
|
-
root.insertBefore(this.regionOverlay, this.toolbarElement);
|
|
2059
|
-
} else {
|
|
2060
2638
|
root.appendChild(this.regionOverlay);
|
|
2639
|
+
} else {
|
|
2640
|
+
this.regionOverlay.style.display = "block";
|
|
2061
2641
|
}
|
|
2062
2642
|
}
|
|
2063
2643
|
hideRegionOverlay() {
|
|
2064
|
-
this.
|
|
2065
|
-
|
|
2066
|
-
this.
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2644
|
+
if (this.regionOverlay) {
|
|
2645
|
+
this.regionOverlay.style.display = "none";
|
|
2646
|
+
this.isDraggingRegion = false;
|
|
2647
|
+
if (this.regionBox) {
|
|
2648
|
+
this.regionBox.style.display = "none";
|
|
2649
|
+
}
|
|
2070
2650
|
}
|
|
2071
2651
|
}
|
|
2072
2652
|
updateHighlight(el) {
|
|
@@ -2074,19 +2654,19 @@ var NoteInspector = class {
|
|
|
2074
2654
|
if (!root || !this.highlightOverlay) return;
|
|
2075
2655
|
const rect = el.getBoundingClientRect();
|
|
2076
2656
|
this.highlightOverlay.style.display = "block";
|
|
2077
|
-
this.highlightOverlay.style.top = `${rect.top}px`;
|
|
2078
2657
|
this.highlightOverlay.style.left = `${rect.left}px`;
|
|
2658
|
+
this.highlightOverlay.style.top = `${rect.top}px`;
|
|
2079
2659
|
this.highlightOverlay.style.width = `${rect.width}px`;
|
|
2080
2660
|
this.highlightOverlay.style.height = `${rect.height}px`;
|
|
2081
|
-
const selector = getSemanticSelector(el);
|
|
2082
|
-
const badgeText = `${selector} \xB7 ${Math.round(rect.width)}\xD7${Math.round(rect.height)}`;
|
|
2083
2661
|
let badge = this.highlightOverlay.querySelector(".bt-badge");
|
|
2084
2662
|
if (!badge) {
|
|
2085
2663
|
badge = document.createElement("div");
|
|
2086
2664
|
badge.className = "bt-badge";
|
|
2087
2665
|
this.highlightOverlay.appendChild(badge);
|
|
2088
2666
|
}
|
|
2089
|
-
|
|
2667
|
+
const selector = getSemanticSelector(el);
|
|
2668
|
+
const label = this.activeScenario ? `\u{1F3AC} Step ${this.activeScenario.stepNumber} \xB7 ${selector}` : selector;
|
|
2669
|
+
badge.textContent = `${label} (${Math.round(rect.width)} \xD7 ${Math.round(rect.height)} px)`;
|
|
2090
2670
|
}
|
|
2091
2671
|
hideHighlight() {
|
|
2092
2672
|
if (this.highlightOverlay) {
|
|
@@ -2101,22 +2681,60 @@ var NoteInspector = class {
|
|
|
2101
2681
|
if (noteType === "element") {
|
|
2102
2682
|
this.updateHighlight(targetEl);
|
|
2103
2683
|
}
|
|
2684
|
+
const comp = noteType === "element" ? resolveComponentSource(targetEl) : void 0;
|
|
2685
|
+
const compPrefix = comp?.componentName ? `\u{1F9EC} <${comp.componentName}>${comp.sourceFile ? " (" + comp.sourceFile + (comp.sourceLine ? ":" + comp.sourceLine : "") + ")" : ""} \xB7 ` : "";
|
|
2104
2686
|
this.renderNoteModal({
|
|
2105
|
-
title: "Add Visual Note",
|
|
2106
|
-
modeBadge: noteType.toUpperCase(),
|
|
2107
|
-
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}`,
|
|
2108
|
-
onSave: async (message) => {
|
|
2109
|
-
|
|
2687
|
+
title: this.activeScenario ? `Step ${this.activeScenario.stepNumber}: ${this.activeScenario.title}` : "Add Visual Note",
|
|
2688
|
+
modeBadge: this.activeScenario ? `STEP ${this.activeScenario.stepNumber}` : noteType.toUpperCase(),
|
|
2689
|
+
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}`,
|
|
2690
|
+
onSave: async (message, action) => {
|
|
2691
|
+
let scenarioParam;
|
|
2692
|
+
if (action === "next_step" && !this.activeScenario) {
|
|
2693
|
+
this.startScenario();
|
|
2694
|
+
}
|
|
2695
|
+
if (this.activeScenario) {
|
|
2696
|
+
scenarioParam = {
|
|
2697
|
+
scenarioId: this.activeScenario.id,
|
|
2698
|
+
stepNumber: this.activeScenario.stepNumber,
|
|
2699
|
+
scenarioTitle: this.activeScenario.title
|
|
2700
|
+
};
|
|
2701
|
+
}
|
|
2702
|
+
await this.saveVisualNote(targetEl, message, noteType, scenarioParam);
|
|
2703
|
+
if (action === "next_step" && this.activeScenario) {
|
|
2704
|
+
this.activeScenario.stepNumber++;
|
|
2705
|
+
this.updateToolbarState();
|
|
2706
|
+
this.setMode("element");
|
|
2707
|
+
} else if (action === "finish_flow" && this.activeScenario) {
|
|
2708
|
+
this.finishScenario();
|
|
2709
|
+
}
|
|
2110
2710
|
}
|
|
2111
2711
|
});
|
|
2112
2712
|
}
|
|
2113
2713
|
openRegionNoteEditor(region) {
|
|
2114
2714
|
this.renderNoteModal({
|
|
2115
|
-
title: "Add Region Note",
|
|
2116
|
-
modeBadge: "REGION",
|
|
2715
|
+
title: this.activeScenario ? `Step ${this.activeScenario.stepNumber}: ${this.activeScenario.title}` : "Add Region Note",
|
|
2716
|
+
modeBadge: this.activeScenario ? `STEP ${this.activeScenario.stepNumber}` : "REGION",
|
|
2117
2717
|
pillText: `Selected Area: x:${region.x}, y:${region.y} (${region.width} \xD7 ${region.height} px)`,
|
|
2118
|
-
onSave: async (message) => {
|
|
2119
|
-
|
|
2718
|
+
onSave: async (message, action) => {
|
|
2719
|
+
let scenarioParam;
|
|
2720
|
+
if (action === "next_step" && !this.activeScenario) {
|
|
2721
|
+
this.startScenario();
|
|
2722
|
+
}
|
|
2723
|
+
if (this.activeScenario) {
|
|
2724
|
+
scenarioParam = {
|
|
2725
|
+
scenarioId: this.activeScenario.id,
|
|
2726
|
+
stepNumber: this.activeScenario.stepNumber,
|
|
2727
|
+
scenarioTitle: this.activeScenario.title
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
await this.saveRegionVisualNote(region, message, scenarioParam);
|
|
2731
|
+
if (action === "next_step" && this.activeScenario) {
|
|
2732
|
+
this.activeScenario.stepNumber++;
|
|
2733
|
+
this.updateToolbarState();
|
|
2734
|
+
this.setMode("element");
|
|
2735
|
+
} else if (action === "finish_flow" && this.activeScenario) {
|
|
2736
|
+
this.finishScenario();
|
|
2737
|
+
}
|
|
2120
2738
|
}
|
|
2121
2739
|
});
|
|
2122
2740
|
}
|
|
@@ -2130,14 +2748,15 @@ var NoteInspector = class {
|
|
|
2130
2748
|
const backdrop = document.createElement("div");
|
|
2131
2749
|
backdrop.className = "bt-modal-backdrop";
|
|
2132
2750
|
this.modalOverlay = backdrop;
|
|
2133
|
-
const
|
|
2134
|
-
const
|
|
2751
|
+
const isStep = options.modeBadge.startsWith("STEP");
|
|
2752
|
+
const badgeClass = isStep ? "bt-badge-step" : `bt-badge-${options.modeBadge.toLowerCase()}`;
|
|
2753
|
+
const icon = isStep ? "\u{1F3AC}" : options.modeBadge === "REGION" ? "\u{1F4D0}" : options.modeBadge === "PAGE" ? "\u{1F4C4}" : "\u{1F3AF}";
|
|
2135
2754
|
const isMac = typeof navigator !== "undefined" && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
2136
2755
|
backdrop.innerHTML = `
|
|
2137
2756
|
<div class="bt-modal">
|
|
2138
2757
|
<div class="bt-modal-header">
|
|
2139
2758
|
<div class="bt-modal-title">
|
|
2140
|
-
<span
|
|
2759
|
+
<span>${icon} ${options.title}</span>
|
|
2141
2760
|
<span class="bt-mode-badge ${badgeClass}">${options.modeBadge}</span>
|
|
2142
2761
|
</div>
|
|
2143
2762
|
<button class="bt-close-btn" id="btn-close" title="Close (Esc)">\u2715</button>
|
|
@@ -2146,14 +2765,19 @@ var NoteInspector = class {
|
|
|
2146
2765
|
<span>${icon}</span>
|
|
2147
2766
|
<span class="bt-pill-content">${options.pillText}</span>
|
|
2148
2767
|
</div>
|
|
2149
|
-
<textarea class="bt-textarea" placeholder="Describe the layout issue,
|
|
2768
|
+
<textarea class="bt-textarea" placeholder="Describe the layout issue, user action, or note for AI agent..." autofocus></textarea>
|
|
2150
2769
|
<div class="bt-modal-footer">
|
|
2151
2770
|
<div class="bt-kbd-hint">
|
|
2152
2771
|
<kbd>${isMac ? "\u2318" : "Ctrl"}+Enter</kbd> save \xB7 <kbd>Esc</kbd> cancel
|
|
2153
2772
|
</div>
|
|
2154
2773
|
<div class="bt-modal-actions">
|
|
2155
2774
|
<button class="bt-btn bt-btn-cancel" id="btn-cancel">Cancel</button>
|
|
2156
|
-
<button class="bt-btn bt-btn-
|
|
2775
|
+
<button class="bt-btn bt-btn-next-step" id="btn-next-step" title="Save this step and immediately select the next element">
|
|
2776
|
+
<span>\u27A1\uFE0F</span> ${this.activeScenario ? "Save & Next Step" : "Save as Step 1 (Flow)"}
|
|
2777
|
+
</button>
|
|
2778
|
+
${this.activeScenario ? `<button class="bt-btn bt-btn-finish-flow" id="btn-finish-flow" title="Save final step and complete scenario">
|
|
2779
|
+
<span>\u2713</span> Save & Finish Flow
|
|
2780
|
+
</button>` : `<button class="bt-btn bt-btn-save" id="btn-save">Save Note</button>`}
|
|
2157
2781
|
</div>
|
|
2158
2782
|
</div>
|
|
2159
2783
|
</div>
|
|
@@ -2162,6 +2786,8 @@ var NoteInspector = class {
|
|
|
2162
2786
|
const btnClose = backdrop.querySelector("#btn-close");
|
|
2163
2787
|
const btnCancel = backdrop.querySelector("#btn-cancel");
|
|
2164
2788
|
const btnSave = backdrop.querySelector("#btn-save");
|
|
2789
|
+
const btnNextStep = backdrop.querySelector("#btn-next-step");
|
|
2790
|
+
const btnFinishFlow = backdrop.querySelector("#btn-finish-flow");
|
|
2165
2791
|
const closeModal = () => {
|
|
2166
2792
|
if (this.modalOverlay) {
|
|
2167
2793
|
root.removeChild(this.modalOverlay);
|
|
@@ -2177,22 +2803,29 @@ var NoteInspector = class {
|
|
|
2177
2803
|
closeModal();
|
|
2178
2804
|
}
|
|
2179
2805
|
};
|
|
2180
|
-
const
|
|
2806
|
+
const handleAction = async (action) => {
|
|
2181
2807
|
const message = textarea.value.trim();
|
|
2182
2808
|
if (!message) return;
|
|
2183
|
-
|
|
2184
|
-
btnSave.disabled = true;
|
|
2809
|
+
btnNextStep.disabled = true;
|
|
2810
|
+
if (btnSave) btnSave.disabled = true;
|
|
2811
|
+
if (btnFinishFlow) btnFinishFlow.disabled = true;
|
|
2185
2812
|
try {
|
|
2186
|
-
await options.onSave(message);
|
|
2813
|
+
await options.onSave(message, action);
|
|
2187
2814
|
} finally {
|
|
2188
2815
|
closeModal();
|
|
2189
2816
|
}
|
|
2190
2817
|
};
|
|
2191
|
-
btnSave
|
|
2818
|
+
if (btnSave) {
|
|
2819
|
+
btnSave.onclick = () => handleAction("save");
|
|
2820
|
+
}
|
|
2821
|
+
btnNextStep.onclick = () => handleAction("next_step");
|
|
2822
|
+
if (btnFinishFlow) {
|
|
2823
|
+
btnFinishFlow.onclick = () => handleAction("finish_flow");
|
|
2824
|
+
}
|
|
2192
2825
|
textarea.onkeydown = (e) => {
|
|
2193
|
-
if (e.key === "Enter" && (e.metaKey || e.ctrlKey
|
|
2826
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
2194
2827
|
e.preventDefault();
|
|
2195
|
-
|
|
2828
|
+
handleAction(this.activeScenario ? "next_step" : "save");
|
|
2196
2829
|
}
|
|
2197
2830
|
if (e.key === "Escape") {
|
|
2198
2831
|
closeModal();
|
|
@@ -2201,7 +2834,7 @@ var NoteInspector = class {
|
|
|
2201
2834
|
root.appendChild(backdrop);
|
|
2202
2835
|
setTimeout(() => textarea?.focus(), 50);
|
|
2203
2836
|
}
|
|
2204
|
-
async saveVisualNote(targetEl, message, noteType = "element") {
|
|
2837
|
+
async saveVisualNote(targetEl, message, noteType = "element", scenario) {
|
|
2205
2838
|
const rect = targetEl.getBoundingClientRect();
|
|
2206
2839
|
const selector = noteType === "page" ? "body" : getSemanticSelector(targetEl);
|
|
2207
2840
|
let screenshotDataUrl;
|
|
@@ -2228,33 +2861,38 @@ var NoteInspector = class {
|
|
|
2228
2861
|
visible: rect.width > 0 && rect.height > 0,
|
|
2229
2862
|
confidence: selector.startsWith("[data-test") ? "high" : selector.startsWith("#") ? "medium" : "low"
|
|
2230
2863
|
};
|
|
2864
|
+
const route = typeof window !== "undefined" ? window.location.pathname + window.location.search : "/";
|
|
2865
|
+
const url = typeof window !== "undefined" ? window.location.href : "http://localhost/";
|
|
2866
|
+
const viewport = typeof window !== "undefined" ? { width: window.innerWidth, height: window.innerHeight, devicePixelRatio: window.devicePixelRatio || 1 } : { width: 1280, height: 800, devicePixelRatio: 1 };
|
|
2867
|
+
const scroll = typeof window !== "undefined" ? { scrollX: window.scrollX, scrollY: window.scrollY } : { scrollX: 0, scrollY: 0 };
|
|
2231
2868
|
const notePayload = {
|
|
2232
2869
|
type: "create_note",
|
|
2233
2870
|
sessionId: this.transport.getSessionId() || "",
|
|
2234
2871
|
noteType,
|
|
2235
2872
|
message,
|
|
2236
|
-
route
|
|
2237
|
-
url
|
|
2238
|
-
viewport
|
|
2239
|
-
|
|
2240
|
-
height: window.innerHeight,
|
|
2241
|
-
devicePixelRatio: window.devicePixelRatio || 1
|
|
2242
|
-
},
|
|
2243
|
-
scroll: {
|
|
2244
|
-
scrollX: window.scrollX,
|
|
2245
|
-
scrollY: window.scrollY
|
|
2246
|
-
},
|
|
2873
|
+
route,
|
|
2874
|
+
url,
|
|
2875
|
+
viewport,
|
|
2876
|
+
scroll,
|
|
2247
2877
|
target,
|
|
2248
2878
|
elementContext,
|
|
2249
2879
|
screenshot: screenshotDataUrl,
|
|
2880
|
+
scenarioId: scenario?.scenarioId,
|
|
2881
|
+
stepNumber: scenario?.stepNumber,
|
|
2882
|
+
scenarioTitle: scenario?.scenarioTitle,
|
|
2250
2883
|
timestamp: Date.now()
|
|
2251
2884
|
};
|
|
2252
2885
|
this.transport.send(notePayload);
|
|
2253
2886
|
if (this.options.onNoteCreated) {
|
|
2254
2887
|
this.options.onNoteCreated(notePayload);
|
|
2255
2888
|
}
|
|
2889
|
+
if (scenario?.scenarioId) {
|
|
2890
|
+
this.showToast(`Step ${scenario.stepNumber || 1} recorded`, "\u{1F3AC}");
|
|
2891
|
+
} else {
|
|
2892
|
+
this.showToast("Visual note saved", "\u2728");
|
|
2893
|
+
}
|
|
2256
2894
|
}
|
|
2257
|
-
async saveRegionVisualNote(region, message) {
|
|
2895
|
+
async saveRegionVisualNote(region, message, scenario) {
|
|
2258
2896
|
let screenshotDataUrl;
|
|
2259
2897
|
try {
|
|
2260
2898
|
const snap = await this.screenshotDriver.captureElement(document.body || document.documentElement);
|
|
@@ -2263,123 +2901,115 @@ var NoteInspector = class {
|
|
|
2263
2901
|
}
|
|
2264
2902
|
} catch {
|
|
2265
2903
|
}
|
|
2904
|
+
const route = typeof window !== "undefined" ? window.location.pathname + window.location.search : "/";
|
|
2905
|
+
const url = typeof window !== "undefined" ? window.location.href : "http://localhost/";
|
|
2906
|
+
const viewport = typeof window !== "undefined" ? { width: window.innerWidth, height: window.innerHeight, devicePixelRatio: window.devicePixelRatio || 1 } : { width: 1280, height: 800, devicePixelRatio: 1 };
|
|
2907
|
+
const scroll = typeof window !== "undefined" ? { scrollX: window.scrollX, scrollY: window.scrollY } : { scrollX: 0, scrollY: 0 };
|
|
2266
2908
|
const notePayload = {
|
|
2267
2909
|
type: "create_note",
|
|
2268
2910
|
sessionId: this.transport.getSessionId() || "",
|
|
2269
2911
|
noteType: "region",
|
|
2270
2912
|
message,
|
|
2271
|
-
route
|
|
2272
|
-
url
|
|
2273
|
-
viewport
|
|
2274
|
-
|
|
2275
|
-
height: window.innerHeight,
|
|
2276
|
-
devicePixelRatio: window.devicePixelRatio || 1
|
|
2277
|
-
},
|
|
2278
|
-
scroll: {
|
|
2279
|
-
scrollX: window.scrollX,
|
|
2280
|
-
scrollY: window.scrollY
|
|
2281
|
-
},
|
|
2913
|
+
route,
|
|
2914
|
+
url,
|
|
2915
|
+
viewport,
|
|
2916
|
+
scroll,
|
|
2282
2917
|
region,
|
|
2283
2918
|
screenshot: screenshotDataUrl,
|
|
2919
|
+
scenarioId: scenario?.scenarioId,
|
|
2920
|
+
stepNumber: scenario?.stepNumber,
|
|
2921
|
+
scenarioTitle: scenario?.scenarioTitle,
|
|
2284
2922
|
timestamp: Date.now()
|
|
2285
2923
|
};
|
|
2286
2924
|
this.transport.send(notePayload);
|
|
2287
2925
|
if (this.options.onNoteCreated) {
|
|
2288
2926
|
this.options.onNoteCreated(notePayload);
|
|
2289
2927
|
}
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
try {
|
|
2296
|
-
const canvas = document.createElement("canvas");
|
|
2297
|
-
canvas.width = region.width;
|
|
2298
|
-
canvas.height = region.height;
|
|
2299
|
-
const ctx = canvas.getContext("2d");
|
|
2300
|
-
if (!ctx) {
|
|
2301
|
-
resolve(dataUrl);
|
|
2302
|
-
return;
|
|
2303
|
-
}
|
|
2304
|
-
const dpr = window.devicePixelRatio || 1;
|
|
2305
|
-
ctx.drawImage(
|
|
2306
|
-
img,
|
|
2307
|
-
region.x * dpr,
|
|
2308
|
-
region.y * dpr,
|
|
2309
|
-
region.width * dpr,
|
|
2310
|
-
region.height * dpr,
|
|
2311
|
-
0,
|
|
2312
|
-
0,
|
|
2313
|
-
region.width,
|
|
2314
|
-
region.height
|
|
2315
|
-
);
|
|
2316
|
-
resolve(canvas.toDataURL("image/webp", 0.9));
|
|
2317
|
-
} catch {
|
|
2318
|
-
resolve(dataUrl);
|
|
2319
|
-
}
|
|
2320
|
-
};
|
|
2321
|
-
img.onerror = () => resolve(dataUrl);
|
|
2322
|
-
img.src = dataUrl;
|
|
2323
|
-
});
|
|
2928
|
+
if (scenario?.scenarioId) {
|
|
2929
|
+
this.showToast(`Step ${scenario.stepNumber || 1} region recorded`, "\u{1F3AC}");
|
|
2930
|
+
} else {
|
|
2931
|
+
this.showToast("Region note saved", "\u{1F4D0}");
|
|
2932
|
+
}
|
|
2324
2933
|
}
|
|
2325
2934
|
extractElementContext(el) {
|
|
2326
2935
|
const selector = getSemanticSelector(el);
|
|
2327
|
-
const tag = el.tagName.toLowerCase();
|
|
2328
2936
|
const attributes = {};
|
|
2329
2937
|
for (let i = 0; i < el.attributes.length; i++) {
|
|
2330
2938
|
const attr = el.attributes[i];
|
|
2331
|
-
if (
|
|
2939
|
+
if (this.options.maskSelectors?.some((mask) => {
|
|
2940
|
+
try {
|
|
2941
|
+
return el.matches(mask);
|
|
2942
|
+
} catch {
|
|
2943
|
+
return false;
|
|
2944
|
+
}
|
|
2945
|
+
}) && (attr.name === "value" || attr.name === "data-secret")) {
|
|
2332
2946
|
attributes[attr.name] = "[REDACTED]";
|
|
2333
2947
|
} else {
|
|
2334
2948
|
attributes[attr.name] = attr.value;
|
|
2335
2949
|
}
|
|
2336
2950
|
}
|
|
2337
|
-
let outerHTML =
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
for (const passInput of Array.from(clone.querySelectorAll('input[type="password"]'))) {
|
|
2341
|
-
passInput.setAttribute("value", "[REDACTED]");
|
|
2342
|
-
}
|
|
2343
|
-
outerHTML = truncate(clone.outerHTML, 10240);
|
|
2344
|
-
} catch {
|
|
2345
|
-
outerHTML = truncate(el.outerHTML, 10240);
|
|
2951
|
+
let outerHTML = el.outerHTML;
|
|
2952
|
+
if (outerHTML && outerHTML.length > 1e3) {
|
|
2953
|
+
outerHTML = truncate(outerHTML, 1e3);
|
|
2346
2954
|
}
|
|
2347
|
-
let
|
|
2348
|
-
if (
|
|
2349
|
-
|
|
2350
|
-
selector: getSemanticSelector(el.parentElement),
|
|
2351
|
-
tag: el.parentElement.tagName.toLowerCase()
|
|
2352
|
-
};
|
|
2955
|
+
let innerText = el.innerText || el.textContent || "";
|
|
2956
|
+
if (innerText && innerText.length > 200) {
|
|
2957
|
+
innerText = truncate(innerText, 200);
|
|
2353
2958
|
}
|
|
2959
|
+
const componentSource = resolveComponentSource(el);
|
|
2354
2960
|
return {
|
|
2355
2961
|
selector,
|
|
2356
|
-
tag,
|
|
2962
|
+
tag: el.tagName.toLowerCase(),
|
|
2357
2963
|
attributes,
|
|
2358
2964
|
outerHTML,
|
|
2359
|
-
innerText
|
|
2360
|
-
|
|
2965
|
+
innerText,
|
|
2966
|
+
componentSource,
|
|
2967
|
+
parent: el.parentElement ? {
|
|
2968
|
+
selector: getSemanticSelector(el.parentElement),
|
|
2969
|
+
tag: el.parentElement.tagName.toLowerCase()
|
|
2970
|
+
} : void 0
|
|
2361
2971
|
};
|
|
2362
2972
|
}
|
|
2973
|
+
async cropDataUrl(dataUrl, region) {
|
|
2974
|
+
return new Promise((resolve) => {
|
|
2975
|
+
const img = new Image();
|
|
2976
|
+
img.onload = () => {
|
|
2977
|
+
const canvas = document.createElement("canvas");
|
|
2978
|
+
canvas.width = region.width;
|
|
2979
|
+
canvas.height = region.height;
|
|
2980
|
+
const ctx = canvas.getContext("2d");
|
|
2981
|
+
if (!ctx) {
|
|
2982
|
+
resolve(dataUrl);
|
|
2983
|
+
return;
|
|
2984
|
+
}
|
|
2985
|
+
const dpr = window.devicePixelRatio || 1;
|
|
2986
|
+
ctx.drawImage(
|
|
2987
|
+
img,
|
|
2988
|
+
region.x * dpr,
|
|
2989
|
+
region.y * dpr,
|
|
2990
|
+
region.width * dpr,
|
|
2991
|
+
region.height * dpr,
|
|
2992
|
+
0,
|
|
2993
|
+
0,
|
|
2994
|
+
region.width,
|
|
2995
|
+
region.height
|
|
2996
|
+
);
|
|
2997
|
+
resolve(canvas.toDataURL("image/png"));
|
|
2998
|
+
};
|
|
2999
|
+
img.onerror = () => resolve(dataUrl);
|
|
3000
|
+
img.src = dataUrl;
|
|
3001
|
+
});
|
|
3002
|
+
}
|
|
2363
3003
|
destroy() {
|
|
2364
3004
|
for (const cleanup of this.cleanups) {
|
|
2365
|
-
|
|
2366
|
-
cleanup();
|
|
2367
|
-
} catch {
|
|
2368
|
-
}
|
|
3005
|
+
cleanup();
|
|
2369
3006
|
}
|
|
2370
3007
|
this.cleanups = [];
|
|
2371
|
-
if (this.container && this.container.
|
|
2372
|
-
this.container.
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
this.toolbarElement = null;
|
|
2377
|
-
this.regionOverlay = null;
|
|
2378
|
-
this.regionBox = null;
|
|
2379
|
-
this.regionBanner = null;
|
|
2380
|
-
this.markersContainer = null;
|
|
2381
|
-
this.modalOverlay = null;
|
|
2382
|
-
this.cardOverlay = null;
|
|
3008
|
+
if (this.container && this.container.parentNode) {
|
|
3009
|
+
this.container.parentNode.removeChild(this.container);
|
|
3010
|
+
this.container = null;
|
|
3011
|
+
this.shadowRoot = null;
|
|
3012
|
+
}
|
|
2383
3013
|
}
|
|
2384
3014
|
};
|
|
2385
3015
|
|
|
@@ -2416,7 +3046,11 @@ var BrowserTrackClient = class {
|
|
|
2416
3046
|
if (this.options.notes.enabled) {
|
|
2417
3047
|
this.inspector = new NoteInspector(this.transport, this.screenshotDriver, {
|
|
2418
3048
|
shortcut: this.options.notes.shortcut,
|
|
2419
|
-
maskSelectors: this.options.notes.maskSelectors
|
|
3049
|
+
maskSelectors: this.options.notes.maskSelectors,
|
|
3050
|
+
showToolbar: this.options.notes.showToolbar,
|
|
3051
|
+
showBadges: this.options.notes.showBadges,
|
|
3052
|
+
hidden: this.options.hidden,
|
|
3053
|
+
hideQueryParam: this.options.hideQueryParam
|
|
2420
3054
|
});
|
|
2421
3055
|
}
|
|
2422
3056
|
}
|
|
@@ -2482,6 +3116,14 @@ var BrowserTrackClient = class {
|
|
|
2482
3116
|
this.inspector.setMode("element");
|
|
2483
3117
|
}
|
|
2484
3118
|
}
|
|
3119
|
+
isUIVisible() {
|
|
3120
|
+
return this.inspector ? this.inspector.isVisible() : false;
|
|
3121
|
+
}
|
|
3122
|
+
setUIVisible(visible) {
|
|
3123
|
+
if (this.inspector) {
|
|
3124
|
+
this.inspector.setVisible(visible);
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
2485
3127
|
setInspectMode(mode) {
|
|
2486
3128
|
if (this.inspector) {
|
|
2487
3129
|
this.inspector.setMode(mode);
|
|
@@ -2622,8 +3264,10 @@ if (typeof window !== "undefined") {
|
|
|
2622
3264
|
const autoInit = currentScript?.getAttribute("data-auto-init") !== "false";
|
|
2623
3265
|
const daemonUrl = currentScript?.getAttribute("data-daemon-url") || void 0;
|
|
2624
3266
|
const projectId = currentScript?.getAttribute("data-project-id") || void 0;
|
|
3267
|
+
const hidden = currentScript?.getAttribute("data-hidden") === "true";
|
|
3268
|
+
const hideQueryParam = currentScript?.getAttribute("data-hide-query-param") || void 0;
|
|
2625
3269
|
if (autoInit) {
|
|
2626
|
-
init({ daemonUrl, projectId });
|
|
3270
|
+
init({ daemonUrl, projectId, hidden, hideQueryParam });
|
|
2627
3271
|
}
|
|
2628
3272
|
} catch {
|
|
2629
3273
|
}
|
|
@@ -2631,11 +3275,13 @@ if (typeof window !== "undefined") {
|
|
|
2631
3275
|
|
|
2632
3276
|
export {
|
|
2633
3277
|
BreadcrumbBuffer,
|
|
3278
|
+
resolveComponentSource,
|
|
2634
3279
|
DEFAULT_OPTIONS,
|
|
3280
|
+
shouldHideUIFromUrl,
|
|
2635
3281
|
BrowserScriptScreenshotDriver,
|
|
2636
3282
|
NoteInspector,
|
|
2637
3283
|
BrowserTrackClient,
|
|
2638
3284
|
init,
|
|
2639
3285
|
getClient
|
|
2640
3286
|
};
|
|
2641
|
-
//# sourceMappingURL=chunk-
|
|
3287
|
+
//# sourceMappingURL=chunk-INXDWPJW.js.map
|