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
package/dist/client/index.cjs
CHANGED
|
@@ -28,7 +28,9 @@ __export(src_exports, {
|
|
|
28
28
|
DEFAULT_OPTIONS: () => DEFAULT_OPTIONS,
|
|
29
29
|
NoteInspector: () => NoteInspector,
|
|
30
30
|
getClient: () => getClient,
|
|
31
|
-
init: () => init
|
|
31
|
+
init: () => init,
|
|
32
|
+
resolveComponentSource: () => resolveComponentSource,
|
|
33
|
+
shouldHideUIFromUrl: () => shouldHideUIFromUrl
|
|
32
34
|
});
|
|
33
35
|
module.exports = __toCommonJS(src_exports);
|
|
34
36
|
|
|
@@ -167,12 +169,220 @@ function truncate(str, maxLength = 200) {
|
|
|
167
169
|
return str.slice(0, maxLength) + "...";
|
|
168
170
|
}
|
|
169
171
|
|
|
172
|
+
// packages/client/src/source/resolver.ts
|
|
173
|
+
function resolveComponentSource(el) {
|
|
174
|
+
if (!el || typeof el !== "object") return void 0;
|
|
175
|
+
const reactInfo = resolveReactComponent(el);
|
|
176
|
+
if (reactInfo) return reactInfo;
|
|
177
|
+
const vueInfo = resolveVueComponent(el);
|
|
178
|
+
if (vueInfo) return vueInfo;
|
|
179
|
+
const svelteInfo = resolveSvelteComponent(el);
|
|
180
|
+
if (svelteInfo) return svelteInfo;
|
|
181
|
+
if (el.tagName && el.tagName.includes("-")) {
|
|
182
|
+
return {
|
|
183
|
+
framework: "web-component",
|
|
184
|
+
componentName: el.tagName.toLowerCase(),
|
|
185
|
+
hierarchy: [el.tagName.toLowerCase()]
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const attrInfo = resolveDataAttributeComponent(el);
|
|
189
|
+
if (attrInfo) return attrInfo;
|
|
190
|
+
return void 0;
|
|
191
|
+
}
|
|
192
|
+
function resolveReactComponent(el) {
|
|
193
|
+
try {
|
|
194
|
+
const fiberKey = Object.keys(el).find(
|
|
195
|
+
(key) => key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$")
|
|
196
|
+
);
|
|
197
|
+
if (!fiberKey) return void 0;
|
|
198
|
+
const hostFiber = el[fiberKey];
|
|
199
|
+
if (!hostFiber) return void 0;
|
|
200
|
+
let sourceFile;
|
|
201
|
+
let sourceLine;
|
|
202
|
+
let sourceColumn;
|
|
203
|
+
let componentName;
|
|
204
|
+
const hierarchy = [];
|
|
205
|
+
let props;
|
|
206
|
+
if (hostFiber._debugSource) {
|
|
207
|
+
sourceFile = hostFiber._debugSource.fileName;
|
|
208
|
+
sourceLine = hostFiber._debugSource.lineNumber;
|
|
209
|
+
sourceColumn = hostFiber._debugSource.columnNumber;
|
|
210
|
+
}
|
|
211
|
+
if (hostFiber._debugOwner) {
|
|
212
|
+
const ownerType = hostFiber._debugOwner.type;
|
|
213
|
+
componentName = getReactComponentName(ownerType);
|
|
214
|
+
if (!sourceFile && hostFiber._debugOwner._debugSource) {
|
|
215
|
+
sourceFile = hostFiber._debugOwner._debugSource.fileName;
|
|
216
|
+
sourceLine = hostFiber._debugOwner._debugSource.lineNumber;
|
|
217
|
+
sourceColumn = hostFiber._debugOwner._debugSource.columnNumber;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
let curr = hostFiber;
|
|
221
|
+
while (curr) {
|
|
222
|
+
const type = curr.type;
|
|
223
|
+
const name = getReactComponentName(type);
|
|
224
|
+
if (name && !name.startsWith("html:") && name !== "Fragment") {
|
|
225
|
+
if (!componentName) {
|
|
226
|
+
componentName = name;
|
|
227
|
+
}
|
|
228
|
+
if (!hierarchy.includes(name)) {
|
|
229
|
+
hierarchy.unshift(name);
|
|
230
|
+
}
|
|
231
|
+
if (!sourceFile && curr._debugSource) {
|
|
232
|
+
sourceFile = curr._debugSource.fileName;
|
|
233
|
+
sourceLine = curr._debugSource.lineNumber;
|
|
234
|
+
sourceColumn = curr._debugSource.columnNumber;
|
|
235
|
+
}
|
|
236
|
+
if (!props && curr.memoizedProps && typeof curr.memoizedProps === "object") {
|
|
237
|
+
props = sanitizeProps(curr.memoizedProps);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
curr = curr.return;
|
|
241
|
+
}
|
|
242
|
+
if (!componentName && !sourceFile) {
|
|
243
|
+
return void 0;
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
framework: "react",
|
|
247
|
+
componentName,
|
|
248
|
+
sourceFile: normalizeFilePath(sourceFile),
|
|
249
|
+
sourceLine,
|
|
250
|
+
sourceColumn,
|
|
251
|
+
hierarchy: hierarchy.length > 0 ? hierarchy : componentName ? [componentName] : void 0,
|
|
252
|
+
props
|
|
253
|
+
};
|
|
254
|
+
} catch {
|
|
255
|
+
return void 0;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function getReactComponentName(type) {
|
|
259
|
+
if (!type) return void 0;
|
|
260
|
+
if (typeof type === "string") return void 0;
|
|
261
|
+
if (type.displayName) return type.displayName;
|
|
262
|
+
if (type.name) return type.name;
|
|
263
|
+
if (type.render?.displayName) return type.render.displayName;
|
|
264
|
+
if (type.render?.name) return type.render.name;
|
|
265
|
+
return void 0;
|
|
266
|
+
}
|
|
267
|
+
function resolveVueComponent(el) {
|
|
268
|
+
try {
|
|
269
|
+
const vueParent = el.__vueParentComponent || el.__vnode?.ctx;
|
|
270
|
+
if (vueParent) {
|
|
271
|
+
const type = vueParent.type || {};
|
|
272
|
+
const componentName = type.name || type.__name || type.displayName || "AnonymousComponent";
|
|
273
|
+
const sourceFile = type.__file;
|
|
274
|
+
const hierarchy = [];
|
|
275
|
+
let curr = vueParent;
|
|
276
|
+
while (curr) {
|
|
277
|
+
const cType = curr.type || {};
|
|
278
|
+
const cName = cType.name || cType.__name || cType.displayName;
|
|
279
|
+
if (cName && !hierarchy.includes(cName)) {
|
|
280
|
+
hierarchy.unshift(cName);
|
|
281
|
+
}
|
|
282
|
+
curr = curr.parent;
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
framework: "vue",
|
|
286
|
+
componentName,
|
|
287
|
+
sourceFile: normalizeFilePath(sourceFile),
|
|
288
|
+
hierarchy: hierarchy.length > 0 ? hierarchy : [componentName],
|
|
289
|
+
props: vueParent.props ? sanitizeProps(vueParent.props) : void 0
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const vue2Instance = el.__vue__;
|
|
293
|
+
if (vue2Instance) {
|
|
294
|
+
const options = vue2Instance.$options || {};
|
|
295
|
+
const componentName = options.name || options._componentTag || "VueComponent";
|
|
296
|
+
const sourceFile = options.__file;
|
|
297
|
+
return {
|
|
298
|
+
framework: "vue",
|
|
299
|
+
componentName,
|
|
300
|
+
sourceFile: normalizeFilePath(sourceFile),
|
|
301
|
+
hierarchy: [componentName],
|
|
302
|
+
props: vue2Instance.$props ? sanitizeProps(vue2Instance.$props) : void 0
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
return void 0;
|
|
306
|
+
} catch {
|
|
307
|
+
return void 0;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function resolveSvelteComponent(el) {
|
|
311
|
+
try {
|
|
312
|
+
let curr = el;
|
|
313
|
+
while (curr) {
|
|
314
|
+
const meta = curr.__svelte_meta;
|
|
315
|
+
if (meta && meta.loc) {
|
|
316
|
+
return {
|
|
317
|
+
framework: "svelte",
|
|
318
|
+
sourceFile: normalizeFilePath(meta.loc.file),
|
|
319
|
+
sourceLine: meta.loc.line,
|
|
320
|
+
sourceColumn: meta.loc.column,
|
|
321
|
+
componentName: meta.loc.file ? getBaseNameWithoutExt(meta.loc.file) : void 0,
|
|
322
|
+
hierarchy: meta.loc.file ? [getBaseNameWithoutExt(meta.loc.file)] : void 0
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
curr = curr.parentElement;
|
|
326
|
+
}
|
|
327
|
+
return void 0;
|
|
328
|
+
} catch {
|
|
329
|
+
return void 0;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function resolveDataAttributeComponent(el) {
|
|
333
|
+
try {
|
|
334
|
+
const compEl = el.closest("[data-component], [data-component-name], [data-source-file]");
|
|
335
|
+
if (!compEl) return void 0;
|
|
336
|
+
const componentName = compEl.getAttribute("data-component") || compEl.getAttribute("data-component-name") || void 0;
|
|
337
|
+
const sourceFile = compEl.getAttribute("data-source-file") || void 0;
|
|
338
|
+
const lineAttr = compEl.getAttribute("data-source-line");
|
|
339
|
+
const sourceLine = lineAttr ? parseInt(lineAttr, 10) : void 0;
|
|
340
|
+
if (!componentName && !sourceFile) return void 0;
|
|
341
|
+
return {
|
|
342
|
+
framework: "vanilla",
|
|
343
|
+
componentName,
|
|
344
|
+
sourceFile: normalizeFilePath(sourceFile),
|
|
345
|
+
sourceLine: isNaN(sourceLine) ? void 0 : sourceLine,
|
|
346
|
+
hierarchy: componentName ? [componentName] : void 0
|
|
347
|
+
};
|
|
348
|
+
} catch {
|
|
349
|
+
return void 0;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function normalizeFilePath(filePath) {
|
|
353
|
+
if (!filePath) return void 0;
|
|
354
|
+
const cleaned = filePath.split("?")[0];
|
|
355
|
+
return cleaned;
|
|
356
|
+
}
|
|
357
|
+
function getBaseNameWithoutExt(filePath) {
|
|
358
|
+
const parts = filePath.split("/");
|
|
359
|
+
const last = parts[parts.length - 1] || filePath;
|
|
360
|
+
return last.split(".")[0] || last;
|
|
361
|
+
}
|
|
362
|
+
function sanitizeProps(props) {
|
|
363
|
+
const result = {};
|
|
364
|
+
for (const [key, value] of Object.entries(props)) {
|
|
365
|
+
if (key.startsWith("__") || typeof value === "function") continue;
|
|
366
|
+
if (value === null || value === void 0) {
|
|
367
|
+
result[key] = value;
|
|
368
|
+
} else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
369
|
+
result[key] = value;
|
|
370
|
+
} else if (Array.isArray(value)) {
|
|
371
|
+
result[key] = `Array(${value.length})`;
|
|
372
|
+
} else if (typeof value === "object") {
|
|
373
|
+
result[key] = "[Object]";
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return result;
|
|
377
|
+
}
|
|
378
|
+
|
|
170
379
|
// packages/client/src/interceptors/interaction.ts
|
|
171
380
|
function extractElementSummary(el) {
|
|
172
381
|
const selector = getSemanticSelector(el);
|
|
173
382
|
const tag = el.tagName.toLowerCase();
|
|
174
383
|
const id = el.id || void 0;
|
|
175
384
|
const classes = Array.from(el.classList || []);
|
|
385
|
+
const componentSource = resolveComponentSource(el);
|
|
176
386
|
let boundingRect = void 0;
|
|
177
387
|
let visible = true;
|
|
178
388
|
try {
|
|
@@ -204,7 +414,8 @@ function extractElementSummary(el) {
|
|
|
204
414
|
boundingRect,
|
|
205
415
|
visible,
|
|
206
416
|
innerText,
|
|
207
|
-
outerHTML
|
|
417
|
+
outerHTML,
|
|
418
|
+
componentSource
|
|
208
419
|
};
|
|
209
420
|
}
|
|
210
421
|
function setupInteractionInterceptors(onInteraction) {
|
|
@@ -522,10 +733,62 @@ var DEFAULT_OPTIONS = {
|
|
|
522
733
|
enabled: true,
|
|
523
734
|
shortcut: "Alt+Click",
|
|
524
735
|
showBadges: true,
|
|
736
|
+
showToolbar: true,
|
|
525
737
|
maskSelectors: ['input[type="password"]', "[data-sensitive]"]
|
|
526
738
|
},
|
|
739
|
+
hidden: false,
|
|
740
|
+
hideQueryParam: void 0,
|
|
527
741
|
debug: false
|
|
528
742
|
};
|
|
743
|
+
var HIDE_VALUES = /* @__PURE__ */ new Set(["0", "false", "hidden", "hide", "off", "none", "disabled", "ui_off", "silent"]);
|
|
744
|
+
function shouldHideUIFromUrl(customParam, searchString) {
|
|
745
|
+
let search = searchString;
|
|
746
|
+
if (search === void 0) {
|
|
747
|
+
if (typeof window === "undefined" || !window.location) return false;
|
|
748
|
+
search = window.location.search;
|
|
749
|
+
}
|
|
750
|
+
if (!search) return false;
|
|
751
|
+
try {
|
|
752
|
+
const params = new URLSearchParams(search);
|
|
753
|
+
if (customParam) {
|
|
754
|
+
const customKeys = Array.isArray(customParam) ? customParam : [customParam];
|
|
755
|
+
for (const key of customKeys) {
|
|
756
|
+
if (params.has(key)) {
|
|
757
|
+
const val = (params.get(key) || "").toLowerCase().trim();
|
|
758
|
+
if (val === "" || val === "1" || val === "true" || HIDE_VALUES.has(val)) {
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
for (const flag of ["no_bt", "no_browsertrack", "hide_bt", "hide_browsertrack"]) {
|
|
765
|
+
if (params.has(flag)) {
|
|
766
|
+
const val = (params.get(flag) || "").toLowerCase().trim();
|
|
767
|
+
if (val === "" || val === "1" || val === "true" || val === "yes") {
|
|
768
|
+
return true;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
if (params.has("bt")) {
|
|
773
|
+
const val = (params.get("bt") || "").toLowerCase().trim();
|
|
774
|
+
if (HIDE_VALUES.has(val)) return true;
|
|
775
|
+
}
|
|
776
|
+
if (params.has("browsertrack")) {
|
|
777
|
+
const val = (params.get("browsertrack") || "").toLowerCase().trim();
|
|
778
|
+
if (HIDE_VALUES.has(val)) return true;
|
|
779
|
+
}
|
|
780
|
+
if (params.has("bt_ui")) {
|
|
781
|
+
const val = (params.get("bt_ui") || "").toLowerCase().trim();
|
|
782
|
+
if (HIDE_VALUES.has(val) || val === "0" || val === "false") return true;
|
|
783
|
+
}
|
|
784
|
+
if (params.has("bt_hide")) {
|
|
785
|
+
const val = (params.get("bt_hide") || "").toLowerCase().trim();
|
|
786
|
+
if (val === "" || val === "1" || val === "true") return true;
|
|
787
|
+
}
|
|
788
|
+
} catch {
|
|
789
|
+
}
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
529
792
|
|
|
530
793
|
// packages/client/src/interceptors/console.ts
|
|
531
794
|
function setupConsoleInterceptors(onConsole) {
|
|
@@ -1184,7 +1447,9 @@ var NoteInspector = class {
|
|
|
1184
1447
|
__publicField(this, "toolbarElement", null);
|
|
1185
1448
|
__publicField(this, "modalOverlay", null);
|
|
1186
1449
|
__publicField(this, "cardOverlay", null);
|
|
1450
|
+
__publicField(this, "toastContainer", null);
|
|
1187
1451
|
__publicField(this, "activeMode", "idle");
|
|
1452
|
+
__publicField(this, "activeScenario", null);
|
|
1188
1453
|
__publicField(this, "hoveredElement", null);
|
|
1189
1454
|
__publicField(this, "selectedElement", null);
|
|
1190
1455
|
__publicField(this, "selectedRegion", null);
|
|
@@ -1193,13 +1458,18 @@ var NoteInspector = class {
|
|
|
1193
1458
|
__publicField(this, "dragStartY", 0);
|
|
1194
1459
|
__publicField(this, "savedNotes", []);
|
|
1195
1460
|
__publicField(this, "showMarkers", true);
|
|
1461
|
+
__publicField(this, "isHidden", false);
|
|
1196
1462
|
__publicField(this, "cleanups", []);
|
|
1197
1463
|
this.transport = transport;
|
|
1198
1464
|
this.screenshotDriver = screenshotDriver;
|
|
1465
|
+
const hiddenByQuery = shouldHideUIFromUrl(options.hideQueryParam);
|
|
1466
|
+
this.isHidden = options.hidden === true || hiddenByQuery;
|
|
1467
|
+
this.showMarkers = options.showBadges !== false && !this.isHidden;
|
|
1199
1468
|
this.options = {
|
|
1200
1469
|
shortcut: "Alt+Click",
|
|
1201
1470
|
maskSelectors: ['input[type="password"]', "[data-sensitive]"],
|
|
1202
1471
|
showToolbar: true,
|
|
1472
|
+
showBadges: true,
|
|
1203
1473
|
...options
|
|
1204
1474
|
};
|
|
1205
1475
|
}
|
|
@@ -1225,6 +1495,41 @@ var NoteInspector = class {
|
|
|
1225
1495
|
this.updateToolbarCount();
|
|
1226
1496
|
this.renderMarkers();
|
|
1227
1497
|
}
|
|
1498
|
+
showToast(message, icon = "\u2728", durationMs = 2500) {
|
|
1499
|
+
if (typeof document === "undefined") return;
|
|
1500
|
+
const root = this.ensureContainer();
|
|
1501
|
+
if (!root || !this.toastContainer) return;
|
|
1502
|
+
const toast = document.createElement("div");
|
|
1503
|
+
toast.className = "bt-toast";
|
|
1504
|
+
toast.innerHTML = `<span>${icon}</span> <span>${message}</span>`;
|
|
1505
|
+
this.toastContainer.appendChild(toast);
|
|
1506
|
+
setTimeout(() => {
|
|
1507
|
+
toast.classList.add("bt-toast-fadeout");
|
|
1508
|
+
setTimeout(() => {
|
|
1509
|
+
if (toast.parentElement) {
|
|
1510
|
+
toast.parentElement.removeChild(toast);
|
|
1511
|
+
}
|
|
1512
|
+
}, 250);
|
|
1513
|
+
}, durationMs);
|
|
1514
|
+
}
|
|
1515
|
+
startScenario(title) {
|
|
1516
|
+
const defaultTitle = title || `Scenario ${(/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`;
|
|
1517
|
+
this.activeScenario = {
|
|
1518
|
+
id: `scen_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`,
|
|
1519
|
+
title: defaultTitle,
|
|
1520
|
+
stepNumber: 1
|
|
1521
|
+
};
|
|
1522
|
+
this.updateToolbarState();
|
|
1523
|
+
this.setMode("element");
|
|
1524
|
+
this.showToast(`Started flow: "${defaultTitle}"`, "\u{1F3AC}");
|
|
1525
|
+
}
|
|
1526
|
+
finishScenario() {
|
|
1527
|
+
const title = this.activeScenario?.title;
|
|
1528
|
+
this.activeScenario = null;
|
|
1529
|
+
this.updateToolbarState();
|
|
1530
|
+
this.setMode("idle");
|
|
1531
|
+
this.showToast(title ? `Finished flow: "${title}"` : "Flow recording finished", "\u2713");
|
|
1532
|
+
}
|
|
1228
1533
|
setMode(mode) {
|
|
1229
1534
|
this.activeMode = mode;
|
|
1230
1535
|
this.updateToolbarState();
|
|
@@ -1242,12 +1547,48 @@ var NoteInspector = class {
|
|
|
1242
1547
|
this.hideHighlight();
|
|
1243
1548
|
}
|
|
1244
1549
|
}
|
|
1550
|
+
isVisible() {
|
|
1551
|
+
return !this.isHidden;
|
|
1552
|
+
}
|
|
1553
|
+
setVisible(visible) {
|
|
1554
|
+
this.isHidden = !visible;
|
|
1555
|
+
this.showMarkers = this.options.showBadges !== false && !this.isHidden;
|
|
1556
|
+
if (this.container) {
|
|
1557
|
+
this.container.style.display = this.isHidden ? "none" : "block";
|
|
1558
|
+
}
|
|
1559
|
+
if (this.toolbarElement) {
|
|
1560
|
+
this.toolbarElement.style.display = this.isHidden ? "none" : "flex";
|
|
1561
|
+
} else if (!this.isHidden && this.options.showToolbar !== false) {
|
|
1562
|
+
this.createToolbar();
|
|
1563
|
+
}
|
|
1564
|
+
if (this.isHidden) {
|
|
1565
|
+
this.hideHighlight();
|
|
1566
|
+
this.hideRegionOverlay();
|
|
1567
|
+
if (this.modalOverlay && this.shadowRoot) {
|
|
1568
|
+
this.shadowRoot.removeChild(this.modalOverlay);
|
|
1569
|
+
this.modalOverlay = null;
|
|
1570
|
+
}
|
|
1571
|
+
if (this.cardOverlay && this.shadowRoot) {
|
|
1572
|
+
this.shadowRoot.removeChild(this.cardOverlay);
|
|
1573
|
+
this.cardOverlay = null;
|
|
1574
|
+
}
|
|
1575
|
+
if (this.markersContainer) {
|
|
1576
|
+
this.markersContainer.innerHTML = "";
|
|
1577
|
+
}
|
|
1578
|
+
} else {
|
|
1579
|
+
this.renderMarkers();
|
|
1580
|
+
this.updateToolbarCount();
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1245
1583
|
ensureContainer() {
|
|
1246
1584
|
if (typeof document === "undefined") return null;
|
|
1247
1585
|
if (!this.container) {
|
|
1248
1586
|
this.container = document.createElement("div");
|
|
1249
1587
|
this.container.id = "browsertrack-inspector-host";
|
|
1250
1588
|
this.container.style.cssText = "all: initial; position: fixed; top: 0; left: 0; width: 0; height: 0; z-index: 2147483647; pointer-events: none;";
|
|
1589
|
+
if (this.isHidden) {
|
|
1590
|
+
this.container.style.display = "none";
|
|
1591
|
+
}
|
|
1251
1592
|
this.shadowRoot = this.container.attachShadow({ mode: "open" });
|
|
1252
1593
|
const style = document.createElement("style");
|
|
1253
1594
|
style.textContent = `
|
|
@@ -1404,6 +1745,12 @@ var NoteInspector = class {
|
|
|
1404
1745
|
z-index: 2147483641;
|
|
1405
1746
|
}
|
|
1406
1747
|
|
|
1748
|
+
.bt-note-marker-step {
|
|
1749
|
+
background: linear-gradient(135deg, #f59e0b, #d97706);
|
|
1750
|
+
border-color: #fef3c7;
|
|
1751
|
+
box-shadow: 0 4px 14px rgba(245, 158, 11, 0.4), 0 0 0 1px rgba(217, 119, 6, 0.5);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1407
1754
|
@keyframes bt-pop-in {
|
|
1408
1755
|
0% { transform: scale(0.6); opacity: 0; }
|
|
1409
1756
|
100% { transform: scale(1); opacity: 1; }
|
|
@@ -1414,6 +1761,10 @@ var NoteInspector = class {
|
|
|
1414
1761
|
box-shadow: 0 8px 20px rgba(37,99,235,0.6), 0 0 0 2px #60a5fa;
|
|
1415
1762
|
}
|
|
1416
1763
|
|
|
1764
|
+
.bt-note-marker-step:hover {
|
|
1765
|
+
box-shadow: 0 8px 20px rgba(245, 158, 11, 0.7), 0 0 0 2px #fde68a;
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1417
1768
|
.bt-marker-resolved {
|
|
1418
1769
|
background: linear-gradient(135deg, #475569, #334155);
|
|
1419
1770
|
border-color: #94a3b8;
|
|
@@ -1510,6 +1861,19 @@ var NoteInspector = class {
|
|
|
1510
1861
|
box-shadow: 0 2px 6px rgba(37, 99, 235, 0.35);
|
|
1511
1862
|
}
|
|
1512
1863
|
|
|
1864
|
+
.bt-toolbar-btn.active-scenario {
|
|
1865
|
+
background: linear-gradient(135deg, #f59e0b, #d97706);
|
|
1866
|
+
color: #ffffff;
|
|
1867
|
+
font-weight: 700;
|
|
1868
|
+
box-shadow: 0 2px 8px rgba(245, 158, 11, 0.4);
|
|
1869
|
+
animation: bt-pulse 2s infinite;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
@keyframes bt-pulse {
|
|
1873
|
+
0%, 100% { opacity: 1; }
|
|
1874
|
+
50% { opacity: 0.85; }
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1513
1877
|
.bt-count-pill {
|
|
1514
1878
|
background: rgba(255, 255, 255, 0.2);
|
|
1515
1879
|
color: #ffffff;
|
|
@@ -1557,7 +1921,7 @@ var NoteInspector = class {
|
|
|
1557
1921
|
border: 1px solid #334155;
|
|
1558
1922
|
border-radius: 14px;
|
|
1559
1923
|
padding: 20px;
|
|
1560
|
-
width:
|
|
1924
|
+
width: 470px;
|
|
1561
1925
|
max-width: 92vw;
|
|
1562
1926
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(255, 255, 255, 0.05);
|
|
1563
1927
|
display: flex;
|
|
@@ -1580,6 +1944,7 @@ var NoteInspector = class {
|
|
|
1580
1944
|
align-items: center;
|
|
1581
1945
|
gap: 8px;
|
|
1582
1946
|
color: #f8fafc;
|
|
1947
|
+
flex-wrap: wrap;
|
|
1583
1948
|
}
|
|
1584
1949
|
|
|
1585
1950
|
.bt-mode-badge {
|
|
@@ -1609,6 +1974,12 @@ var NoteInspector = class {
|
|
|
1609
1974
|
border: 1px solid rgba(168, 85, 247, 0.4);
|
|
1610
1975
|
}
|
|
1611
1976
|
|
|
1977
|
+
.bt-badge-step {
|
|
1978
|
+
background: rgba(245, 158, 11, 0.15);
|
|
1979
|
+
color: #fbbf24;
|
|
1980
|
+
border: 1px solid rgba(245, 158, 11, 0.4);
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1612
1983
|
.bt-badge-status-open {
|
|
1613
1984
|
background: rgba(16, 185, 129, 0.15);
|
|
1614
1985
|
color: #34d399;
|
|
@@ -1660,6 +2031,59 @@ var NoteInspector = class {
|
|
|
1660
2031
|
color: #cbd5e1;
|
|
1661
2032
|
}
|
|
1662
2033
|
|
|
2034
|
+
.bt-component-pill {
|
|
2035
|
+
display: flex;
|
|
2036
|
+
align-items: center;
|
|
2037
|
+
gap: 6px;
|
|
2038
|
+
background: rgba(15, 23, 42, 0.95);
|
|
2039
|
+
border: 1px solid rgba(99, 102, 241, 0.35);
|
|
2040
|
+
border-radius: 6px;
|
|
2041
|
+
padding: 6px 10px;
|
|
2042
|
+
font-size: 11.5px;
|
|
2043
|
+
color: #c7d2fe;
|
|
2044
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
2045
|
+
overflow: hidden;
|
|
2046
|
+
text-overflow: ellipsis;
|
|
2047
|
+
white-space: nowrap;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
.bt-scenario-stepper {
|
|
2051
|
+
display: flex;
|
|
2052
|
+
align-items: center;
|
|
2053
|
+
justify-content: space-between;
|
|
2054
|
+
background: #090d16;
|
|
2055
|
+
border: 1px solid #1e293b;
|
|
2056
|
+
border-radius: 8px;
|
|
2057
|
+
padding: 6px 10px;
|
|
2058
|
+
font-size: 12px;
|
|
2059
|
+
font-weight: 600;
|
|
2060
|
+
color: #f59e0b;
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
.bt-step-nav-btn {
|
|
2064
|
+
background: #1e293b;
|
|
2065
|
+
color: #f8fafc;
|
|
2066
|
+
border: 1px solid #334155;
|
|
2067
|
+
padding: 4px 10px;
|
|
2068
|
+
border-radius: 6px;
|
|
2069
|
+
cursor: pointer;
|
|
2070
|
+
font-size: 11px;
|
|
2071
|
+
font-weight: 600;
|
|
2072
|
+
transition: all 0.12s ease;
|
|
2073
|
+
font-family: inherit;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
.bt-step-nav-btn:hover:not(:disabled) {
|
|
2077
|
+
background: #2563eb;
|
|
2078
|
+
border-color: #3b82f6;
|
|
2079
|
+
color: #ffffff;
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
.bt-step-nav-btn:disabled {
|
|
2083
|
+
opacity: 0.3;
|
|
2084
|
+
cursor: not-allowed;
|
|
2085
|
+
}
|
|
2086
|
+
|
|
1663
2087
|
.bt-note-message-box {
|
|
1664
2088
|
background: #090d16;
|
|
1665
2089
|
border: 1px solid #1e293b;
|
|
@@ -1704,8 +2128,9 @@ var NoteInspector = class {
|
|
|
1704
2128
|
display: flex;
|
|
1705
2129
|
justify-content: space-between;
|
|
1706
2130
|
align-items: center;
|
|
1707
|
-
gap:
|
|
2131
|
+
gap: 10px;
|
|
1708
2132
|
margin-top: 2px;
|
|
2133
|
+
flex-wrap: wrap;
|
|
1709
2134
|
}
|
|
1710
2135
|
|
|
1711
2136
|
.bt-kbd-hint {
|
|
@@ -1733,12 +2158,13 @@ var NoteInspector = class {
|
|
|
1733
2158
|
justify-content: flex-end;
|
|
1734
2159
|
align-items: center;
|
|
1735
2160
|
gap: 8px;
|
|
2161
|
+
flex-wrap: wrap;
|
|
1736
2162
|
}
|
|
1737
2163
|
|
|
1738
2164
|
.bt-btn {
|
|
1739
|
-
padding:
|
|
2165
|
+
padding: 7px 14px;
|
|
1740
2166
|
border-radius: 7px;
|
|
1741
|
-
font-size:
|
|
2167
|
+
font-size: 12px;
|
|
1742
2168
|
font-weight: 600;
|
|
1743
2169
|
cursor: pointer;
|
|
1744
2170
|
border: 1px solid transparent;
|
|
@@ -1747,7 +2173,7 @@ var NoteInspector = class {
|
|
|
1747
2173
|
display: inline-flex;
|
|
1748
2174
|
align-items: center;
|
|
1749
2175
|
justify-content: center;
|
|
1750
|
-
gap:
|
|
2176
|
+
gap: 5px;
|
|
1751
2177
|
}
|
|
1752
2178
|
|
|
1753
2179
|
.bt-btn-cancel {
|
|
@@ -1769,6 +2195,25 @@ var NoteInspector = class {
|
|
|
1769
2195
|
box-shadow: 0 4px 10px rgba(37, 99, 235, 0.45);
|
|
1770
2196
|
}
|
|
1771
2197
|
|
|
2198
|
+
.bt-btn-next-step {
|
|
2199
|
+
background: linear-gradient(135deg, #6366f1, #4f46e5);
|
|
2200
|
+
color: #ffffff;
|
|
2201
|
+
box-shadow: 0 2px 8px rgba(99, 102, 241, 0.4);
|
|
2202
|
+
}
|
|
2203
|
+
.bt-btn-next-step:hover {
|
|
2204
|
+
background: linear-gradient(135deg, #4f46e5, #4338ca);
|
|
2205
|
+
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.55);
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
.bt-btn-finish-flow {
|
|
2209
|
+
background: linear-gradient(135deg, #10b981, #059669);
|
|
2210
|
+
color: #ffffff;
|
|
2211
|
+
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.35);
|
|
2212
|
+
}
|
|
2213
|
+
.bt-btn-finish-flow:hover {
|
|
2214
|
+
background: linear-gradient(135deg, #059669, #047857);
|
|
2215
|
+
}
|
|
2216
|
+
|
|
1772
2217
|
.bt-btn-resolve {
|
|
1773
2218
|
background: rgba(16, 185, 129, 0.15);
|
|
1774
2219
|
color: #6ee7b7;
|
|
@@ -1788,6 +2233,47 @@ var NoteInspector = class {
|
|
|
1788
2233
|
background: rgba(239, 68, 68, 0.3);
|
|
1789
2234
|
color: #ffffff;
|
|
1790
2235
|
}
|
|
2236
|
+
|
|
2237
|
+
/* 6. Toast Notifications */
|
|
2238
|
+
.bt-toast-container {
|
|
2239
|
+
position: fixed;
|
|
2240
|
+
bottom: 74px;
|
|
2241
|
+
right: 24px;
|
|
2242
|
+
display: flex;
|
|
2243
|
+
flex-direction: column;
|
|
2244
|
+
gap: 8px;
|
|
2245
|
+
pointer-events: none;
|
|
2246
|
+
z-index: 2147483647;
|
|
2247
|
+
align-items: flex-end;
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
.bt-toast {
|
|
2251
|
+
background: #0f172a;
|
|
2252
|
+
color: #f8fafc;
|
|
2253
|
+
border: 1px solid #334155;
|
|
2254
|
+
border-radius: 30px;
|
|
2255
|
+
padding: 8px 16px;
|
|
2256
|
+
font-size: 12.5px;
|
|
2257
|
+
font-weight: 500;
|
|
2258
|
+
display: inline-flex;
|
|
2259
|
+
align-items: center;
|
|
2260
|
+
gap: 8px;
|
|
2261
|
+
box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.6), 0 4px 6px -4px rgba(0, 0, 0, 0.4);
|
|
2262
|
+
pointer-events: auto;
|
|
2263
|
+
animation: bt-toast-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
2264
|
+
transition: opacity 0.25s ease, transform 0.25s ease;
|
|
2265
|
+
user-select: none;
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
.bt-toast-fadeout {
|
|
2269
|
+
opacity: 0;
|
|
2270
|
+
transform: translateY(6px);
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
@keyframes bt-toast-in {
|
|
2274
|
+
from { opacity: 0; transform: translateY(8px) scale(0.95); }
|
|
2275
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
2276
|
+
}
|
|
1791
2277
|
`;
|
|
1792
2278
|
this.shadowRoot.appendChild(style);
|
|
1793
2279
|
this.highlightOverlay = document.createElement("div");
|
|
@@ -1797,7 +2283,10 @@ var NoteInspector = class {
|
|
|
1797
2283
|
this.markersContainer = document.createElement("div");
|
|
1798
2284
|
this.markersContainer.className = "bt-markers-layer";
|
|
1799
2285
|
this.shadowRoot.appendChild(this.markersContainer);
|
|
1800
|
-
|
|
2286
|
+
this.toastContainer = document.createElement("div");
|
|
2287
|
+
this.toastContainer.className = "bt-toast-container";
|
|
2288
|
+
this.shadowRoot.appendChild(this.toastContainer);
|
|
2289
|
+
if (this.options.showToolbar && !this.isHidden) {
|
|
1801
2290
|
this.createToolbar();
|
|
1802
2291
|
}
|
|
1803
2292
|
}
|
|
@@ -1824,6 +2313,10 @@ var NoteInspector = class {
|
|
|
1824
2313
|
<span>\u{1F4C4}</span> Page
|
|
1825
2314
|
</button>
|
|
1826
2315
|
<div class="bt-toolbar-divider"></div>
|
|
2316
|
+
<button class="bt-toolbar-btn" id="bt-mode-flow" title="Record multi-step reproduction flow">
|
|
2317
|
+
<span>\u{1F3AC}</span> Flow
|
|
2318
|
+
</button>
|
|
2319
|
+
<div class="bt-toolbar-divider"></div>
|
|
1827
2320
|
<button class="bt-toolbar-btn active" id="bt-toggle-notes" title="Toggle visible note markers on screen">
|
|
1828
2321
|
<span>\u{1F4CC}</span> Notes <span class="bt-count-pill" id="bt-notes-count">0</span>
|
|
1829
2322
|
</button>
|
|
@@ -1831,6 +2324,7 @@ var NoteInspector = class {
|
|
|
1831
2324
|
const btnElement = this.toolbarElement.querySelector("#bt-mode-element");
|
|
1832
2325
|
const btnRegion = this.toolbarElement.querySelector("#bt-mode-region");
|
|
1833
2326
|
const btnPage = this.toolbarElement.querySelector("#bt-mode-page");
|
|
2327
|
+
const btnFlow = this.toolbarElement.querySelector("#bt-mode-flow");
|
|
1834
2328
|
const btnToggleNotes = this.toolbarElement.querySelector("#bt-toggle-notes");
|
|
1835
2329
|
btnElement.onclick = (e) => {
|
|
1836
2330
|
e.stopPropagation();
|
|
@@ -1844,6 +2338,14 @@ var NoteInspector = class {
|
|
|
1844
2338
|
e.stopPropagation();
|
|
1845
2339
|
this.setMode("page");
|
|
1846
2340
|
};
|
|
2341
|
+
btnFlow.onclick = (e) => {
|
|
2342
|
+
e.stopPropagation();
|
|
2343
|
+
if (this.activeScenario) {
|
|
2344
|
+
this.finishScenario();
|
|
2345
|
+
} else {
|
|
2346
|
+
this.startScenario();
|
|
2347
|
+
}
|
|
2348
|
+
};
|
|
1847
2349
|
btnToggleNotes.onclick = (e) => {
|
|
1848
2350
|
e.stopPropagation();
|
|
1849
2351
|
this.showMarkers = !this.showMarkers;
|
|
@@ -1857,9 +2359,21 @@ var NoteInspector = class {
|
|
|
1857
2359
|
const btnElement = this.toolbarElement.querySelector("#bt-mode-element");
|
|
1858
2360
|
const btnRegion = this.toolbarElement.querySelector("#bt-mode-region");
|
|
1859
2361
|
const btnPage = this.toolbarElement.querySelector("#bt-mode-page");
|
|
2362
|
+
const btnFlow = this.toolbarElement.querySelector("#bt-mode-flow");
|
|
1860
2363
|
btnElement?.classList.toggle("active", this.activeMode === "element");
|
|
1861
2364
|
btnRegion?.classList.toggle("active", this.activeMode === "region");
|
|
1862
2365
|
btnPage?.classList.toggle("active", this.activeMode === "page");
|
|
2366
|
+
if (btnFlow) {
|
|
2367
|
+
if (this.activeScenario) {
|
|
2368
|
+
btnFlow.className = "bt-toolbar-btn active-scenario";
|
|
2369
|
+
btnFlow.innerHTML = `<span>\u{1F3AC}</span> Step ${this.activeScenario.stepNumber} (Finish)`;
|
|
2370
|
+
btnFlow.setAttribute("title", `Click to finish recording "${this.activeScenario.title}"`);
|
|
2371
|
+
} else {
|
|
2372
|
+
btnFlow.className = "bt-toolbar-btn";
|
|
2373
|
+
btnFlow.innerHTML = `<span>\u{1F3AC}</span> Flow`;
|
|
2374
|
+
btnFlow.setAttribute("title", "Record multi-step reproduction flow");
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
1863
2377
|
}
|
|
1864
2378
|
updateToolbarCount() {
|
|
1865
2379
|
if (!this.toolbarElement) return;
|
|
@@ -1873,7 +2387,7 @@ var NoteInspector = class {
|
|
|
1873
2387
|
const root = this.ensureContainer();
|
|
1874
2388
|
if (!root || !this.markersContainer) return;
|
|
1875
2389
|
this.markersContainer.innerHTML = "";
|
|
1876
|
-
if (!this.showMarkers) return;
|
|
2390
|
+
if (!this.showMarkers || this.isHidden) return;
|
|
1877
2391
|
const currentPath = window.location.pathname;
|
|
1878
2392
|
const activeNotes = this.savedNotes.filter(
|
|
1879
2393
|
(n) => n.status === "OPEN" && (n.route === currentPath || !n.route || n.route === "/" || window.location.href.includes(n.route))
|
|
@@ -1884,6 +2398,9 @@ var NoteInspector = class {
|
|
|
1884
2398
|
pageNotes.push(note);
|
|
1885
2399
|
return;
|
|
1886
2400
|
}
|
|
2401
|
+
const isStep = !!note.scenarioId && note.stepNumber != null;
|
|
2402
|
+
const stepLabel = isStep ? `\u{1F3AC} Step ${note.stepNumber}` : `#${index + 1}`;
|
|
2403
|
+
const markerClass = `bt-note-marker ${isStep ? "bt-note-marker-step" : ""}`;
|
|
1887
2404
|
if (note.type === "region" && note.region) {
|
|
1888
2405
|
const regBox = document.createElement("div");
|
|
1889
2406
|
regBox.className = "bt-region-marker-box";
|
|
@@ -1893,11 +2410,11 @@ var NoteInspector = class {
|
|
|
1893
2410
|
regBox.style.height = `${note.region.height}px`;
|
|
1894
2411
|
this.markersContainer.appendChild(regBox);
|
|
1895
2412
|
const pin = document.createElement("div");
|
|
1896
|
-
pin.className =
|
|
2413
|
+
pin.className = markerClass;
|
|
1897
2414
|
pin.style.left = `${Math.max(4, note.region.x - 12)}px`;
|
|
1898
2415
|
pin.style.top = `${Math.max(4, note.region.y - 12)}px`;
|
|
1899
|
-
pin.title = note.message;
|
|
1900
|
-
pin.innerHTML = `<span
|
|
2416
|
+
pin.title = isStep ? `[${note.scenarioTitle || "Scenario"}] Step ${note.stepNumber}: ${note.message}` : note.message;
|
|
2417
|
+
pin.innerHTML = `<span>${isStep ? "\u{1F3AC}" : "\u{1F4D0}"}</span> <span>${stepLabel}</span>`;
|
|
1901
2418
|
pin.onclick = (e) => {
|
|
1902
2419
|
e.stopPropagation();
|
|
1903
2420
|
this.openNoteCard(note);
|
|
@@ -1915,12 +2432,12 @@ var NoteInspector = class {
|
|
|
1915
2432
|
const rect = targetEl ? targetEl.getBoundingClientRect() : note.target?.boundingRect;
|
|
1916
2433
|
if (rect) {
|
|
1917
2434
|
const pin = document.createElement("div");
|
|
1918
|
-
pin.className =
|
|
2435
|
+
pin.className = markerClass;
|
|
1919
2436
|
pin.setAttribute("data-note-id", note.id);
|
|
1920
|
-
pin.title = note.message;
|
|
2437
|
+
pin.title = isStep ? `[${note.scenarioTitle || "Scenario"}] Step ${note.stepNumber}: ${note.message}` : note.message;
|
|
1921
2438
|
pin.style.left = `${Math.max(4, rect.left - 10)}px`;
|
|
1922
2439
|
pin.style.top = `${Math.max(4, rect.top - 12)}px`;
|
|
1923
|
-
pin.innerHTML = `<span
|
|
2440
|
+
pin.innerHTML = `<span>${isStep ? "\u{1F3AC}" : "\u{1F4DD}"}</span> <span>${stepLabel}</span>`;
|
|
1924
2441
|
pin.onclick = (e) => {
|
|
1925
2442
|
e.stopPropagation();
|
|
1926
2443
|
this.openNoteCard(note);
|
|
@@ -1935,10 +2452,11 @@ var NoteInspector = class {
|
|
|
1935
2452
|
if (pageNotes.length > 0) {
|
|
1936
2453
|
const pageDock = document.createElement("div");
|
|
1937
2454
|
pageDock.className = "bt-page-notes-dock";
|
|
1938
|
-
pageNotes.forEach((pNote
|
|
2455
|
+
pageNotes.forEach((pNote) => {
|
|
1939
2456
|
const pill = document.createElement("div");
|
|
1940
2457
|
pill.className = "bt-page-note-pill";
|
|
1941
|
-
|
|
2458
|
+
const label = pNote.scenarioId && pNote.stepNumber ? `Step ${pNote.stepNumber}: ` : "";
|
|
2459
|
+
pill.innerHTML = `<span>\u{1F4C4}</span> <span>${label}${truncate(pNote.message, 25)}</span>`;
|
|
1942
2460
|
pill.onclick = (e) => {
|
|
1943
2461
|
e.stopPropagation();
|
|
1944
2462
|
this.openNoteCard(pNote);
|
|
@@ -1967,17 +2485,46 @@ var NoteInspector = class {
|
|
|
1967
2485
|
const icon = note.type === "region" ? "\u{1F4D0}" : note.type === "page" ? "\u{1F4C4}" : "\u{1F3AF}";
|
|
1968
2486
|
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
2487
|
const formattedDate = new Date(note.createdAt).toLocaleString();
|
|
2488
|
+
let scenarioStepsHtml = "";
|
|
2489
|
+
let scenarioSteps = [];
|
|
2490
|
+
if (note.scenarioId) {
|
|
2491
|
+
scenarioSteps = this.savedNotes.filter((n) => n.scenarioId === note.scenarioId).sort((a, b) => (a.stepNumber || 0) - (b.stepNumber || 0));
|
|
2492
|
+
const currentIndex = scenarioSteps.findIndex((s) => s.id === note.id);
|
|
2493
|
+
const prevStep = currentIndex > 0 ? scenarioSteps[currentIndex - 1] : null;
|
|
2494
|
+
const nextStep = currentIndex >= 0 && currentIndex < scenarioSteps.length - 1 ? scenarioSteps[currentIndex + 1] : null;
|
|
2495
|
+
scenarioStepsHtml = `
|
|
2496
|
+
<div class="bt-scenario-stepper">
|
|
2497
|
+
<button class="bt-step-nav-btn" id="btn-prev-step" ${!prevStep ? "disabled" : ""}>
|
|
2498
|
+
\u25C0 Step ${prevStep ? prevStep.stepNumber : ""}
|
|
2499
|
+
</button>
|
|
2500
|
+
<span>\u{1F3AC} Step ${note.stepNumber || 1} of ${scenarioSteps.length}</span>
|
|
2501
|
+
<button class="bt-step-nav-btn" id="btn-next-step-card" ${!nextStep ? "disabled" : ""}>
|
|
2502
|
+
Step ${nextStep ? nextStep.stepNumber : ""} \u25B6
|
|
2503
|
+
</button>
|
|
2504
|
+
</div>
|
|
2505
|
+
`;
|
|
2506
|
+
}
|
|
1970
2507
|
backdrop.innerHTML = `
|
|
1971
2508
|
<div class="bt-modal">
|
|
1972
2509
|
<div class="bt-modal-header">
|
|
1973
2510
|
<div class="bt-modal-title">
|
|
1974
|
-
<span>${
|
|
2511
|
+
<span>${note.scenarioId ? "\u{1F3AC} " + (note.scenarioTitle || "Scenario Flow") : icon + " Visual Note Details"}</span>
|
|
1975
2512
|
<span class="bt-mode-badge ${badgeClass}">${note.type.toUpperCase()}</span>
|
|
2513
|
+
${note.scenarioId && note.stepNumber ? `<span class="bt-mode-badge bt-badge-step">STEP ${note.stepNumber}</span>` : ""}
|
|
1976
2514
|
<span class="bt-mode-badge ${statusClass}">${note.status}</span>
|
|
1977
2515
|
</div>
|
|
1978
2516
|
<button class="bt-close-btn" id="btn-card-close" title="Close (Esc)">\u2715</button>
|
|
1979
2517
|
</div>
|
|
1980
2518
|
|
|
2519
|
+
${scenarioStepsHtml}
|
|
2520
|
+
|
|
2521
|
+
${note.elementContext?.componentSource?.componentName ? `<div class="bt-component-pill" title="${note.elementContext.componentSource.sourceFile ? note.elementContext.componentSource.sourceFile + (note.elementContext.componentSource.sourceLine ? ":" + note.elementContext.componentSource.sourceLine : "") : note.elementContext.componentSource.componentName}">
|
|
2522
|
+
<span>\u{1F9EC}</span>
|
|
2523
|
+
<span style="font-weight: 600; color: #a5b4fc;"><${note.elementContext.componentSource.componentName}></span>
|
|
2524
|
+
${note.elementContext.componentSource.sourceFile ? `<span style="color: #64748b; font-size: 11px; margin-left: 4px;">${note.elementContext.componentSource.sourceFile}${note.elementContext.componentSource.sourceLine ? ":" + note.elementContext.componentSource.sourceLine : ""}</span>` : ""}
|
|
2525
|
+
${note.elementContext.componentSource.framework ? `<span class="bt-mode-badge" style="background: rgba(99, 102, 241, 0.15); color: #818cf8; margin-left: auto; font-size: 10px;">${note.elementContext.componentSource.framework.toUpperCase()}</span>` : ""}
|
|
2526
|
+
</div>` : ""}
|
|
2527
|
+
|
|
1981
2528
|
<div class="bt-target-pill" title="${contextText}">
|
|
1982
2529
|
<span>\u{1F3F7}\uFE0F</span>
|
|
1983
2530
|
<span class="bt-pill-content">${contextText}</span>
|
|
@@ -1991,9 +2538,14 @@ var NoteInspector = class {
|
|
|
1991
2538
|
</div>
|
|
1992
2539
|
|
|
1993
2540
|
<div class="bt-modal-footer">
|
|
1994
|
-
<
|
|
1995
|
-
<
|
|
1996
|
-
|
|
2541
|
+
<div style="display: flex; gap: 6px;">
|
|
2542
|
+
<button class="bt-btn bt-btn-delete" id="btn-card-delete" title="Delete this note">
|
|
2543
|
+
<span>\u{1F5D1}\uFE0F</span> Delete
|
|
2544
|
+
</button>
|
|
2545
|
+
${note.scenarioId ? `<button class="bt-btn bt-btn-delete" id="btn-card-delete-flow" title="Delete entire scenario flow">
|
|
2546
|
+
<span>\u{1F5D1}\uFE0F</span> Delete Flow
|
|
2547
|
+
</button>` : ""}
|
|
2548
|
+
</div>
|
|
1997
2549
|
<div class="bt-modal-actions">
|
|
1998
2550
|
<button class="bt-btn bt-btn-cancel" id="btn-card-dismiss">Close</button>
|
|
1999
2551
|
<button class="bt-btn ${note.status === "OPEN" ? "bt-btn-resolve" : "bt-btn-save"}" id="btn-card-resolve">
|
|
@@ -2007,6 +2559,9 @@ var NoteInspector = class {
|
|
|
2007
2559
|
const btnDismiss = backdrop.querySelector("#btn-card-dismiss");
|
|
2008
2560
|
const btnResolve = backdrop.querySelector("#btn-card-resolve");
|
|
2009
2561
|
const btnDelete = backdrop.querySelector("#btn-card-delete");
|
|
2562
|
+
const btnDeleteFlow = backdrop.querySelector("#btn-card-delete-flow");
|
|
2563
|
+
const btnPrevStep = backdrop.querySelector("#btn-prev-step");
|
|
2564
|
+
const btnNextStepCard = backdrop.querySelector("#btn-next-step-card");
|
|
2010
2565
|
const closeCard = () => {
|
|
2011
2566
|
if (this.cardOverlay) {
|
|
2012
2567
|
root.removeChild(this.cardOverlay);
|
|
@@ -2019,23 +2574,49 @@ var NoteInspector = class {
|
|
|
2019
2574
|
backdrop.onclick = (e) => {
|
|
2020
2575
|
if (e.target === backdrop) closeCard();
|
|
2021
2576
|
};
|
|
2577
|
+
if (btnPrevStep && scenarioSteps.length > 0) {
|
|
2578
|
+
const currentIndex = scenarioSteps.findIndex((s) => s.id === note.id);
|
|
2579
|
+
if (currentIndex > 0) {
|
|
2580
|
+
btnPrevStep.onclick = () => {
|
|
2581
|
+
this.openNoteCard(scenarioSteps[currentIndex - 1]);
|
|
2582
|
+
};
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
if (btnNextStepCard && scenarioSteps.length > 0) {
|
|
2586
|
+
const currentIndex = scenarioSteps.findIndex((s) => s.id === note.id);
|
|
2587
|
+
if (currentIndex >= 0 && currentIndex < scenarioSteps.length - 1) {
|
|
2588
|
+
btnNextStepCard.onclick = () => {
|
|
2589
|
+
this.openNoteCard(scenarioSteps[currentIndex + 1]);
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2022
2593
|
btnResolve.onclick = () => {
|
|
2023
2594
|
if (note.status === "OPEN") {
|
|
2024
2595
|
this.transport.send({ type: "resolve_note", noteId: note.id });
|
|
2596
|
+
this.showToast("Note marked as resolved", "\u2705");
|
|
2025
2597
|
} else {
|
|
2026
2598
|
this.transport.send({ type: "reopen_note", noteId: note.id });
|
|
2599
|
+
this.showToast("Note reopened", "\u21BA");
|
|
2027
2600
|
}
|
|
2028
2601
|
closeCard();
|
|
2029
2602
|
};
|
|
2030
2603
|
btnDelete.onclick = () => {
|
|
2031
2604
|
this.transport.send({ type: "delete_note", noteId: note.id });
|
|
2605
|
+
this.showToast("Note deleted", "\u{1F5D1}\uFE0F");
|
|
2032
2606
|
closeCard();
|
|
2033
2607
|
};
|
|
2608
|
+
if (btnDeleteFlow && note.scenarioId) {
|
|
2609
|
+
btnDeleteFlow.onclick = () => {
|
|
2610
|
+
this.transport.send({ type: "delete_scenario", scenarioId: note.scenarioId });
|
|
2611
|
+
this.showToast("Scenario flow deleted", "\u{1F5D1}\uFE0F");
|
|
2612
|
+
closeCard();
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2034
2615
|
root.appendChild(backdrop);
|
|
2035
2616
|
}
|
|
2036
2617
|
setupListeners() {
|
|
2037
2618
|
const onMouseMove = (e) => {
|
|
2038
|
-
if (this.modalOverlay || this.cardOverlay || this.isDraggingRegion || this.activeMode === "region") return;
|
|
2619
|
+
if (this.isHidden || this.modalOverlay || this.cardOverlay || this.isDraggingRegion || this.activeMode === "region") return;
|
|
2039
2620
|
const path = e.composedPath ? e.composedPath() : [];
|
|
2040
2621
|
if (this.container && path.includes(this.container)) {
|
|
2041
2622
|
this.hideHighlight();
|
|
@@ -2054,7 +2635,7 @@ var NoteInspector = class {
|
|
|
2054
2635
|
}
|
|
2055
2636
|
};
|
|
2056
2637
|
const onClick = (e) => {
|
|
2057
|
-
if (this.modalOverlay || this.cardOverlay) return;
|
|
2638
|
+
if (this.isHidden || this.modalOverlay || this.cardOverlay) return;
|
|
2058
2639
|
const path = e.composedPath ? e.composedPath() : [];
|
|
2059
2640
|
if (this.container && path.includes(this.container)) {
|
|
2060
2641
|
return;
|
|
@@ -2067,7 +2648,7 @@ var NoteInspector = class {
|
|
|
2067
2648
|
if (target && target !== this.container && !this.container?.contains(target)) {
|
|
2068
2649
|
this.selectedElement = target;
|
|
2069
2650
|
this.openNoteEditor(target, "element");
|
|
2070
|
-
if (this.activeMode === "element") {
|
|
2651
|
+
if (this.activeMode === "element" && !this.activeScenario) {
|
|
2071
2652
|
this.setMode("idle");
|
|
2072
2653
|
}
|
|
2073
2654
|
}
|
|
@@ -2079,7 +2660,9 @@ var NoteInspector = class {
|
|
|
2079
2660
|
this.shadowRoot.removeChild(this.cardOverlay);
|
|
2080
2661
|
this.cardOverlay = null;
|
|
2081
2662
|
} else if (this.activeMode === "region" || this.activeMode === "element") {
|
|
2082
|
-
this.
|
|
2663
|
+
if (!this.activeScenario) {
|
|
2664
|
+
this.setMode("idle");
|
|
2665
|
+
}
|
|
2083
2666
|
}
|
|
2084
2667
|
}
|
|
2085
2668
|
};
|
|
@@ -2185,28 +2768,27 @@ var NoteInspector = class {
|
|
|
2185
2768
|
width: Math.round(width),
|
|
2186
2769
|
height: Math.round(height)
|
|
2187
2770
|
};
|
|
2188
|
-
this.hideRegionOverlay();
|
|
2189
|
-
this.setMode("idle");
|
|
2190
2771
|
this.openRegionNoteEditor(this.selectedRegion);
|
|
2191
|
-
}
|
|
2192
|
-
|
|
2772
|
+
}
|
|
2773
|
+
if (this.regionBox) {
|
|
2774
|
+
this.regionBox.style.display = "none";
|
|
2775
|
+
}
|
|
2776
|
+
if (!this.activeScenario) {
|
|
2193
2777
|
this.setMode("idle");
|
|
2194
2778
|
}
|
|
2195
2779
|
};
|
|
2196
|
-
}
|
|
2197
|
-
if (this.toolbarElement && this.toolbarElement.parentNode === root) {
|
|
2198
|
-
root.insertBefore(this.regionOverlay, this.toolbarElement);
|
|
2199
|
-
} else {
|
|
2200
2780
|
root.appendChild(this.regionOverlay);
|
|
2781
|
+
} else {
|
|
2782
|
+
this.regionOverlay.style.display = "block";
|
|
2201
2783
|
}
|
|
2202
2784
|
}
|
|
2203
2785
|
hideRegionOverlay() {
|
|
2204
|
-
this.
|
|
2205
|
-
|
|
2206
|
-
this.
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2786
|
+
if (this.regionOverlay) {
|
|
2787
|
+
this.regionOverlay.style.display = "none";
|
|
2788
|
+
this.isDraggingRegion = false;
|
|
2789
|
+
if (this.regionBox) {
|
|
2790
|
+
this.regionBox.style.display = "none";
|
|
2791
|
+
}
|
|
2210
2792
|
}
|
|
2211
2793
|
}
|
|
2212
2794
|
updateHighlight(el) {
|
|
@@ -2214,19 +2796,19 @@ var NoteInspector = class {
|
|
|
2214
2796
|
if (!root || !this.highlightOverlay) return;
|
|
2215
2797
|
const rect = el.getBoundingClientRect();
|
|
2216
2798
|
this.highlightOverlay.style.display = "block";
|
|
2217
|
-
this.highlightOverlay.style.top = `${rect.top}px`;
|
|
2218
2799
|
this.highlightOverlay.style.left = `${rect.left}px`;
|
|
2800
|
+
this.highlightOverlay.style.top = `${rect.top}px`;
|
|
2219
2801
|
this.highlightOverlay.style.width = `${rect.width}px`;
|
|
2220
2802
|
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
2803
|
let badge = this.highlightOverlay.querySelector(".bt-badge");
|
|
2224
2804
|
if (!badge) {
|
|
2225
2805
|
badge = document.createElement("div");
|
|
2226
2806
|
badge.className = "bt-badge";
|
|
2227
2807
|
this.highlightOverlay.appendChild(badge);
|
|
2228
2808
|
}
|
|
2229
|
-
|
|
2809
|
+
const selector = getSemanticSelector(el);
|
|
2810
|
+
const label = this.activeScenario ? `\u{1F3AC} Step ${this.activeScenario.stepNumber} \xB7 ${selector}` : selector;
|
|
2811
|
+
badge.textContent = `${label} (${Math.round(rect.width)} \xD7 ${Math.round(rect.height)} px)`;
|
|
2230
2812
|
}
|
|
2231
2813
|
hideHighlight() {
|
|
2232
2814
|
if (this.highlightOverlay) {
|
|
@@ -2241,22 +2823,60 @@ var NoteInspector = class {
|
|
|
2241
2823
|
if (noteType === "element") {
|
|
2242
2824
|
this.updateHighlight(targetEl);
|
|
2243
2825
|
}
|
|
2826
|
+
const comp = noteType === "element" ? resolveComponentSource(targetEl) : void 0;
|
|
2827
|
+
const compPrefix = comp?.componentName ? `\u{1F9EC} <${comp.componentName}>${comp.sourceFile ? " (" + comp.sourceFile + (comp.sourceLine ? ":" + comp.sourceLine : "") + ")" : ""} \xB7 ` : "";
|
|
2244
2828
|
this.renderNoteModal({
|
|
2245
|
-
title: "Add Visual Note",
|
|
2246
|
-
modeBadge: noteType.toUpperCase(),
|
|
2247
|
-
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
|
-
|
|
2829
|
+
title: this.activeScenario ? `Step ${this.activeScenario.stepNumber}: ${this.activeScenario.title}` : "Add Visual Note",
|
|
2830
|
+
modeBadge: this.activeScenario ? `STEP ${this.activeScenario.stepNumber}` : noteType.toUpperCase(),
|
|
2831
|
+
pillText: noteType === "page" ? `Page Viewport: ${window.innerWidth} \xD7 ${window.innerHeight} px` : `${compPrefix}${getSemanticSelector(targetEl)} (${Math.round(targetEl.getBoundingClientRect().width)}\xD7${Math.round(targetEl.getBoundingClientRect().height)}) \xB7 Viewport: ${window.innerWidth}\xD7${window.innerHeight}`,
|
|
2832
|
+
onSave: async (message, action) => {
|
|
2833
|
+
let scenarioParam;
|
|
2834
|
+
if (action === "next_step" && !this.activeScenario) {
|
|
2835
|
+
this.startScenario();
|
|
2836
|
+
}
|
|
2837
|
+
if (this.activeScenario) {
|
|
2838
|
+
scenarioParam = {
|
|
2839
|
+
scenarioId: this.activeScenario.id,
|
|
2840
|
+
stepNumber: this.activeScenario.stepNumber,
|
|
2841
|
+
scenarioTitle: this.activeScenario.title
|
|
2842
|
+
};
|
|
2843
|
+
}
|
|
2844
|
+
await this.saveVisualNote(targetEl, message, noteType, scenarioParam);
|
|
2845
|
+
if (action === "next_step" && this.activeScenario) {
|
|
2846
|
+
this.activeScenario.stepNumber++;
|
|
2847
|
+
this.updateToolbarState();
|
|
2848
|
+
this.setMode("element");
|
|
2849
|
+
} else if (action === "finish_flow" && this.activeScenario) {
|
|
2850
|
+
this.finishScenario();
|
|
2851
|
+
}
|
|
2250
2852
|
}
|
|
2251
2853
|
});
|
|
2252
2854
|
}
|
|
2253
2855
|
openRegionNoteEditor(region) {
|
|
2254
2856
|
this.renderNoteModal({
|
|
2255
|
-
title: "Add Region Note",
|
|
2256
|
-
modeBadge: "REGION",
|
|
2857
|
+
title: this.activeScenario ? `Step ${this.activeScenario.stepNumber}: ${this.activeScenario.title}` : "Add Region Note",
|
|
2858
|
+
modeBadge: this.activeScenario ? `STEP ${this.activeScenario.stepNumber}` : "REGION",
|
|
2257
2859
|
pillText: `Selected Area: x:${region.x}, y:${region.y} (${region.width} \xD7 ${region.height} px)`,
|
|
2258
|
-
onSave: async (message) => {
|
|
2259
|
-
|
|
2860
|
+
onSave: async (message, action) => {
|
|
2861
|
+
let scenarioParam;
|
|
2862
|
+
if (action === "next_step" && !this.activeScenario) {
|
|
2863
|
+
this.startScenario();
|
|
2864
|
+
}
|
|
2865
|
+
if (this.activeScenario) {
|
|
2866
|
+
scenarioParam = {
|
|
2867
|
+
scenarioId: this.activeScenario.id,
|
|
2868
|
+
stepNumber: this.activeScenario.stepNumber,
|
|
2869
|
+
scenarioTitle: this.activeScenario.title
|
|
2870
|
+
};
|
|
2871
|
+
}
|
|
2872
|
+
await this.saveRegionVisualNote(region, message, scenarioParam);
|
|
2873
|
+
if (action === "next_step" && this.activeScenario) {
|
|
2874
|
+
this.activeScenario.stepNumber++;
|
|
2875
|
+
this.updateToolbarState();
|
|
2876
|
+
this.setMode("element");
|
|
2877
|
+
} else if (action === "finish_flow" && this.activeScenario) {
|
|
2878
|
+
this.finishScenario();
|
|
2879
|
+
}
|
|
2260
2880
|
}
|
|
2261
2881
|
});
|
|
2262
2882
|
}
|
|
@@ -2270,14 +2890,15 @@ var NoteInspector = class {
|
|
|
2270
2890
|
const backdrop = document.createElement("div");
|
|
2271
2891
|
backdrop.className = "bt-modal-backdrop";
|
|
2272
2892
|
this.modalOverlay = backdrop;
|
|
2273
|
-
const
|
|
2274
|
-
const
|
|
2893
|
+
const isStep = options.modeBadge.startsWith("STEP");
|
|
2894
|
+
const badgeClass = isStep ? "bt-badge-step" : `bt-badge-${options.modeBadge.toLowerCase()}`;
|
|
2895
|
+
const icon = isStep ? "\u{1F3AC}" : options.modeBadge === "REGION" ? "\u{1F4D0}" : options.modeBadge === "PAGE" ? "\u{1F4C4}" : "\u{1F3AF}";
|
|
2275
2896
|
const isMac = typeof navigator !== "undefined" && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
2276
2897
|
backdrop.innerHTML = `
|
|
2277
2898
|
<div class="bt-modal">
|
|
2278
2899
|
<div class="bt-modal-header">
|
|
2279
2900
|
<div class="bt-modal-title">
|
|
2280
|
-
<span
|
|
2901
|
+
<span>${icon} ${options.title}</span>
|
|
2281
2902
|
<span class="bt-mode-badge ${badgeClass}">${options.modeBadge}</span>
|
|
2282
2903
|
</div>
|
|
2283
2904
|
<button class="bt-close-btn" id="btn-close" title="Close (Esc)">\u2715</button>
|
|
@@ -2286,14 +2907,19 @@ var NoteInspector = class {
|
|
|
2286
2907
|
<span>${icon}</span>
|
|
2287
2908
|
<span class="bt-pill-content">${options.pillText}</span>
|
|
2288
2909
|
</div>
|
|
2289
|
-
<textarea class="bt-textarea" placeholder="Describe the layout issue,
|
|
2910
|
+
<textarea class="bt-textarea" placeholder="Describe the layout issue, user action, or note for AI agent..." autofocus></textarea>
|
|
2290
2911
|
<div class="bt-modal-footer">
|
|
2291
2912
|
<div class="bt-kbd-hint">
|
|
2292
2913
|
<kbd>${isMac ? "\u2318" : "Ctrl"}+Enter</kbd> save \xB7 <kbd>Esc</kbd> cancel
|
|
2293
2914
|
</div>
|
|
2294
2915
|
<div class="bt-modal-actions">
|
|
2295
2916
|
<button class="bt-btn bt-btn-cancel" id="btn-cancel">Cancel</button>
|
|
2296
|
-
<button class="bt-btn bt-btn-
|
|
2917
|
+
<button class="bt-btn bt-btn-next-step" id="btn-next-step" title="Save this step and immediately select the next element">
|
|
2918
|
+
<span>\u27A1\uFE0F</span> ${this.activeScenario ? "Save & Next Step" : "Save as Step 1 (Flow)"}
|
|
2919
|
+
</button>
|
|
2920
|
+
${this.activeScenario ? `<button class="bt-btn bt-btn-finish-flow" id="btn-finish-flow" title="Save final step and complete scenario">
|
|
2921
|
+
<span>\u2713</span> Save & Finish Flow
|
|
2922
|
+
</button>` : `<button class="bt-btn bt-btn-save" id="btn-save">Save Note</button>`}
|
|
2297
2923
|
</div>
|
|
2298
2924
|
</div>
|
|
2299
2925
|
</div>
|
|
@@ -2302,6 +2928,8 @@ var NoteInspector = class {
|
|
|
2302
2928
|
const btnClose = backdrop.querySelector("#btn-close");
|
|
2303
2929
|
const btnCancel = backdrop.querySelector("#btn-cancel");
|
|
2304
2930
|
const btnSave = backdrop.querySelector("#btn-save");
|
|
2931
|
+
const btnNextStep = backdrop.querySelector("#btn-next-step");
|
|
2932
|
+
const btnFinishFlow = backdrop.querySelector("#btn-finish-flow");
|
|
2305
2933
|
const closeModal = () => {
|
|
2306
2934
|
if (this.modalOverlay) {
|
|
2307
2935
|
root.removeChild(this.modalOverlay);
|
|
@@ -2317,22 +2945,29 @@ var NoteInspector = class {
|
|
|
2317
2945
|
closeModal();
|
|
2318
2946
|
}
|
|
2319
2947
|
};
|
|
2320
|
-
const
|
|
2948
|
+
const handleAction = async (action) => {
|
|
2321
2949
|
const message = textarea.value.trim();
|
|
2322
2950
|
if (!message) return;
|
|
2323
|
-
|
|
2324
|
-
btnSave.disabled = true;
|
|
2951
|
+
btnNextStep.disabled = true;
|
|
2952
|
+
if (btnSave) btnSave.disabled = true;
|
|
2953
|
+
if (btnFinishFlow) btnFinishFlow.disabled = true;
|
|
2325
2954
|
try {
|
|
2326
|
-
await options.onSave(message);
|
|
2955
|
+
await options.onSave(message, action);
|
|
2327
2956
|
} finally {
|
|
2328
2957
|
closeModal();
|
|
2329
2958
|
}
|
|
2330
2959
|
};
|
|
2331
|
-
btnSave
|
|
2960
|
+
if (btnSave) {
|
|
2961
|
+
btnSave.onclick = () => handleAction("save");
|
|
2962
|
+
}
|
|
2963
|
+
btnNextStep.onclick = () => handleAction("next_step");
|
|
2964
|
+
if (btnFinishFlow) {
|
|
2965
|
+
btnFinishFlow.onclick = () => handleAction("finish_flow");
|
|
2966
|
+
}
|
|
2332
2967
|
textarea.onkeydown = (e) => {
|
|
2333
|
-
if (e.key === "Enter" && (e.metaKey || e.ctrlKey
|
|
2968
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
2334
2969
|
e.preventDefault();
|
|
2335
|
-
|
|
2970
|
+
handleAction(this.activeScenario ? "next_step" : "save");
|
|
2336
2971
|
}
|
|
2337
2972
|
if (e.key === "Escape") {
|
|
2338
2973
|
closeModal();
|
|
@@ -2341,7 +2976,7 @@ var NoteInspector = class {
|
|
|
2341
2976
|
root.appendChild(backdrop);
|
|
2342
2977
|
setTimeout(() => textarea?.focus(), 50);
|
|
2343
2978
|
}
|
|
2344
|
-
async saveVisualNote(targetEl, message, noteType = "element") {
|
|
2979
|
+
async saveVisualNote(targetEl, message, noteType = "element", scenario) {
|
|
2345
2980
|
const rect = targetEl.getBoundingClientRect();
|
|
2346
2981
|
const selector = noteType === "page" ? "body" : getSemanticSelector(targetEl);
|
|
2347
2982
|
let screenshotDataUrl;
|
|
@@ -2368,33 +3003,38 @@ var NoteInspector = class {
|
|
|
2368
3003
|
visible: rect.width > 0 && rect.height > 0,
|
|
2369
3004
|
confidence: selector.startsWith("[data-test") ? "high" : selector.startsWith("#") ? "medium" : "low"
|
|
2370
3005
|
};
|
|
3006
|
+
const route = typeof window !== "undefined" ? window.location.pathname + window.location.search : "/";
|
|
3007
|
+
const url = typeof window !== "undefined" ? window.location.href : "http://localhost/";
|
|
3008
|
+
const viewport = typeof window !== "undefined" ? { width: window.innerWidth, height: window.innerHeight, devicePixelRatio: window.devicePixelRatio || 1 } : { width: 1280, height: 800, devicePixelRatio: 1 };
|
|
3009
|
+
const scroll = typeof window !== "undefined" ? { scrollX: window.scrollX, scrollY: window.scrollY } : { scrollX: 0, scrollY: 0 };
|
|
2371
3010
|
const notePayload = {
|
|
2372
3011
|
type: "create_note",
|
|
2373
3012
|
sessionId: this.transport.getSessionId() || "",
|
|
2374
3013
|
noteType,
|
|
2375
3014
|
message,
|
|
2376
|
-
route
|
|
2377
|
-
url
|
|
2378
|
-
viewport
|
|
2379
|
-
|
|
2380
|
-
height: window.innerHeight,
|
|
2381
|
-
devicePixelRatio: window.devicePixelRatio || 1
|
|
2382
|
-
},
|
|
2383
|
-
scroll: {
|
|
2384
|
-
scrollX: window.scrollX,
|
|
2385
|
-
scrollY: window.scrollY
|
|
2386
|
-
},
|
|
3015
|
+
route,
|
|
3016
|
+
url,
|
|
3017
|
+
viewport,
|
|
3018
|
+
scroll,
|
|
2387
3019
|
target,
|
|
2388
3020
|
elementContext,
|
|
2389
3021
|
screenshot: screenshotDataUrl,
|
|
3022
|
+
scenarioId: scenario?.scenarioId,
|
|
3023
|
+
stepNumber: scenario?.stepNumber,
|
|
3024
|
+
scenarioTitle: scenario?.scenarioTitle,
|
|
2390
3025
|
timestamp: Date.now()
|
|
2391
3026
|
};
|
|
2392
3027
|
this.transport.send(notePayload);
|
|
2393
3028
|
if (this.options.onNoteCreated) {
|
|
2394
3029
|
this.options.onNoteCreated(notePayload);
|
|
2395
3030
|
}
|
|
3031
|
+
if (scenario?.scenarioId) {
|
|
3032
|
+
this.showToast(`Step ${scenario.stepNumber || 1} recorded`, "\u{1F3AC}");
|
|
3033
|
+
} else {
|
|
3034
|
+
this.showToast("Visual note saved", "\u2728");
|
|
3035
|
+
}
|
|
2396
3036
|
}
|
|
2397
|
-
async saveRegionVisualNote(region, message) {
|
|
3037
|
+
async saveRegionVisualNote(region, message, scenario) {
|
|
2398
3038
|
let screenshotDataUrl;
|
|
2399
3039
|
try {
|
|
2400
3040
|
const snap = await this.screenshotDriver.captureElement(document.body || document.documentElement);
|
|
@@ -2403,123 +3043,115 @@ var NoteInspector = class {
|
|
|
2403
3043
|
}
|
|
2404
3044
|
} catch {
|
|
2405
3045
|
}
|
|
3046
|
+
const route = typeof window !== "undefined" ? window.location.pathname + window.location.search : "/";
|
|
3047
|
+
const url = typeof window !== "undefined" ? window.location.href : "http://localhost/";
|
|
3048
|
+
const viewport = typeof window !== "undefined" ? { width: window.innerWidth, height: window.innerHeight, devicePixelRatio: window.devicePixelRatio || 1 } : { width: 1280, height: 800, devicePixelRatio: 1 };
|
|
3049
|
+
const scroll = typeof window !== "undefined" ? { scrollX: window.scrollX, scrollY: window.scrollY } : { scrollX: 0, scrollY: 0 };
|
|
2406
3050
|
const notePayload = {
|
|
2407
3051
|
type: "create_note",
|
|
2408
3052
|
sessionId: this.transport.getSessionId() || "",
|
|
2409
3053
|
noteType: "region",
|
|
2410
3054
|
message,
|
|
2411
|
-
route
|
|
2412
|
-
url
|
|
2413
|
-
viewport
|
|
2414
|
-
|
|
2415
|
-
height: window.innerHeight,
|
|
2416
|
-
devicePixelRatio: window.devicePixelRatio || 1
|
|
2417
|
-
},
|
|
2418
|
-
scroll: {
|
|
2419
|
-
scrollX: window.scrollX,
|
|
2420
|
-
scrollY: window.scrollY
|
|
2421
|
-
},
|
|
3055
|
+
route,
|
|
3056
|
+
url,
|
|
3057
|
+
viewport,
|
|
3058
|
+
scroll,
|
|
2422
3059
|
region,
|
|
2423
3060
|
screenshot: screenshotDataUrl,
|
|
3061
|
+
scenarioId: scenario?.scenarioId,
|
|
3062
|
+
stepNumber: scenario?.stepNumber,
|
|
3063
|
+
scenarioTitle: scenario?.scenarioTitle,
|
|
2424
3064
|
timestamp: Date.now()
|
|
2425
3065
|
};
|
|
2426
3066
|
this.transport.send(notePayload);
|
|
2427
3067
|
if (this.options.onNoteCreated) {
|
|
2428
3068
|
this.options.onNoteCreated(notePayload);
|
|
2429
3069
|
}
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
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
|
-
});
|
|
3070
|
+
if (scenario?.scenarioId) {
|
|
3071
|
+
this.showToast(`Step ${scenario.stepNumber || 1} region recorded`, "\u{1F3AC}");
|
|
3072
|
+
} else {
|
|
3073
|
+
this.showToast("Region note saved", "\u{1F4D0}");
|
|
3074
|
+
}
|
|
2464
3075
|
}
|
|
2465
3076
|
extractElementContext(el) {
|
|
2466
3077
|
const selector = getSemanticSelector(el);
|
|
2467
|
-
const tag = el.tagName.toLowerCase();
|
|
2468
3078
|
const attributes = {};
|
|
2469
3079
|
for (let i = 0; i < el.attributes.length; i++) {
|
|
2470
3080
|
const attr = el.attributes[i];
|
|
2471
|
-
if (
|
|
3081
|
+
if (this.options.maskSelectors?.some((mask) => {
|
|
3082
|
+
try {
|
|
3083
|
+
return el.matches(mask);
|
|
3084
|
+
} catch {
|
|
3085
|
+
return false;
|
|
3086
|
+
}
|
|
3087
|
+
}) && (attr.name === "value" || attr.name === "data-secret")) {
|
|
2472
3088
|
attributes[attr.name] = "[REDACTED]";
|
|
2473
3089
|
} else {
|
|
2474
3090
|
attributes[attr.name] = attr.value;
|
|
2475
3091
|
}
|
|
2476
3092
|
}
|
|
2477
|
-
let outerHTML =
|
|
2478
|
-
|
|
2479
|
-
|
|
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);
|
|
3093
|
+
let outerHTML = el.outerHTML;
|
|
3094
|
+
if (outerHTML && outerHTML.length > 1e3) {
|
|
3095
|
+
outerHTML = truncate(outerHTML, 1e3);
|
|
2486
3096
|
}
|
|
2487
|
-
let
|
|
2488
|
-
if (
|
|
2489
|
-
|
|
2490
|
-
selector: getSemanticSelector(el.parentElement),
|
|
2491
|
-
tag: el.parentElement.tagName.toLowerCase()
|
|
2492
|
-
};
|
|
3097
|
+
let innerText = el.innerText || el.textContent || "";
|
|
3098
|
+
if (innerText && innerText.length > 200) {
|
|
3099
|
+
innerText = truncate(innerText, 200);
|
|
2493
3100
|
}
|
|
3101
|
+
const componentSource = resolveComponentSource(el);
|
|
2494
3102
|
return {
|
|
2495
3103
|
selector,
|
|
2496
|
-
tag,
|
|
3104
|
+
tag: el.tagName.toLowerCase(),
|
|
2497
3105
|
attributes,
|
|
2498
3106
|
outerHTML,
|
|
2499
|
-
innerText
|
|
2500
|
-
|
|
3107
|
+
innerText,
|
|
3108
|
+
componentSource,
|
|
3109
|
+
parent: el.parentElement ? {
|
|
3110
|
+
selector: getSemanticSelector(el.parentElement),
|
|
3111
|
+
tag: el.parentElement.tagName.toLowerCase()
|
|
3112
|
+
} : void 0
|
|
2501
3113
|
};
|
|
2502
3114
|
}
|
|
3115
|
+
async cropDataUrl(dataUrl, region) {
|
|
3116
|
+
return new Promise((resolve) => {
|
|
3117
|
+
const img = new Image();
|
|
3118
|
+
img.onload = () => {
|
|
3119
|
+
const canvas = document.createElement("canvas");
|
|
3120
|
+
canvas.width = region.width;
|
|
3121
|
+
canvas.height = region.height;
|
|
3122
|
+
const ctx = canvas.getContext("2d");
|
|
3123
|
+
if (!ctx) {
|
|
3124
|
+
resolve(dataUrl);
|
|
3125
|
+
return;
|
|
3126
|
+
}
|
|
3127
|
+
const dpr = window.devicePixelRatio || 1;
|
|
3128
|
+
ctx.drawImage(
|
|
3129
|
+
img,
|
|
3130
|
+
region.x * dpr,
|
|
3131
|
+
region.y * dpr,
|
|
3132
|
+
region.width * dpr,
|
|
3133
|
+
region.height * dpr,
|
|
3134
|
+
0,
|
|
3135
|
+
0,
|
|
3136
|
+
region.width,
|
|
3137
|
+
region.height
|
|
3138
|
+
);
|
|
3139
|
+
resolve(canvas.toDataURL("image/png"));
|
|
3140
|
+
};
|
|
3141
|
+
img.onerror = () => resolve(dataUrl);
|
|
3142
|
+
img.src = dataUrl;
|
|
3143
|
+
});
|
|
3144
|
+
}
|
|
2503
3145
|
destroy() {
|
|
2504
3146
|
for (const cleanup of this.cleanups) {
|
|
2505
|
-
|
|
2506
|
-
cleanup();
|
|
2507
|
-
} catch {
|
|
2508
|
-
}
|
|
3147
|
+
cleanup();
|
|
2509
3148
|
}
|
|
2510
3149
|
this.cleanups = [];
|
|
2511
|
-
if (this.container && this.container.
|
|
2512
|
-
this.container.
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
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;
|
|
3150
|
+
if (this.container && this.container.parentNode) {
|
|
3151
|
+
this.container.parentNode.removeChild(this.container);
|
|
3152
|
+
this.container = null;
|
|
3153
|
+
this.shadowRoot = null;
|
|
3154
|
+
}
|
|
2523
3155
|
}
|
|
2524
3156
|
};
|
|
2525
3157
|
|
|
@@ -2556,7 +3188,11 @@ var BrowserTrackClient = class {
|
|
|
2556
3188
|
if (this.options.notes.enabled) {
|
|
2557
3189
|
this.inspector = new NoteInspector(this.transport, this.screenshotDriver, {
|
|
2558
3190
|
shortcut: this.options.notes.shortcut,
|
|
2559
|
-
maskSelectors: this.options.notes.maskSelectors
|
|
3191
|
+
maskSelectors: this.options.notes.maskSelectors,
|
|
3192
|
+
showToolbar: this.options.notes.showToolbar,
|
|
3193
|
+
showBadges: this.options.notes.showBadges,
|
|
3194
|
+
hidden: this.options.hidden,
|
|
3195
|
+
hideQueryParam: this.options.hideQueryParam
|
|
2560
3196
|
});
|
|
2561
3197
|
}
|
|
2562
3198
|
}
|
|
@@ -2622,6 +3258,14 @@ var BrowserTrackClient = class {
|
|
|
2622
3258
|
this.inspector.setMode("element");
|
|
2623
3259
|
}
|
|
2624
3260
|
}
|
|
3261
|
+
isUIVisible() {
|
|
3262
|
+
return this.inspector ? this.inspector.isVisible() : false;
|
|
3263
|
+
}
|
|
3264
|
+
setUIVisible(visible) {
|
|
3265
|
+
if (this.inspector) {
|
|
3266
|
+
this.inspector.setVisible(visible);
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
2625
3269
|
setInspectMode(mode) {
|
|
2626
3270
|
if (this.inspector) {
|
|
2627
3271
|
this.inspector.setMode(mode);
|
|
@@ -2762,8 +3406,10 @@ if (typeof window !== "undefined") {
|
|
|
2762
3406
|
const autoInit = currentScript?.getAttribute("data-auto-init") !== "false";
|
|
2763
3407
|
const daemonUrl = currentScript?.getAttribute("data-daemon-url") || void 0;
|
|
2764
3408
|
const projectId = currentScript?.getAttribute("data-project-id") || void 0;
|
|
3409
|
+
const hidden = currentScript?.getAttribute("data-hidden") === "true";
|
|
3410
|
+
const hideQueryParam = currentScript?.getAttribute("data-hide-query-param") || void 0;
|
|
2765
3411
|
if (autoInit) {
|
|
2766
|
-
init({ daemonUrl, projectId });
|
|
3412
|
+
init({ daemonUrl, projectId, hidden, hideQueryParam });
|
|
2767
3413
|
}
|
|
2768
3414
|
} catch {
|
|
2769
3415
|
}
|
|
@@ -2776,5 +3422,7 @@ if (typeof window !== "undefined") {
|
|
|
2776
3422
|
DEFAULT_OPTIONS,
|
|
2777
3423
|
NoteInspector,
|
|
2778
3424
|
getClient,
|
|
2779
|
-
init
|
|
3425
|
+
init,
|
|
3426
|
+
resolveComponentSource,
|
|
3427
|
+
shouldHideUIFromUrl
|
|
2780
3428
|
});
|