browsertrack 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-TWEYRBDU.js → chunk-5NR5K3ER.js} +24 -5
- package/dist/chunk-5NR5K3ER.js.map +1 -0
- package/dist/{chunk-AYSVE6NG.js → chunk-G5CIZSQM.js} +2 -2
- package/dist/{chunk-QRZ57ME3.js → chunk-ONPW7AYL.js} +75 -11
- package/dist/chunk-ONPW7AYL.js.map +1 -0
- package/dist/{chunk-4HRLW6YF.js → chunk-PG4JJDCV.js} +196 -17
- package/dist/chunk-PG4JJDCV.js.map +1 -0
- package/dist/cli/index.js +22 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.cjs +267 -26
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +2 -2
- package/dist/client.iife.js +121 -15
- package/dist/core/index.d.ts +9 -2
- package/dist/core/index.js +3 -1
- package/dist/daemon/index.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -4
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +3 -3
- package/dist/{server-DjV7RWQM.d.ts → server-BRG-RQQP.d.ts} +1 -0
- package/package.json +1 -1
- package/packages/client/src/notes/inspector.ts +210 -19
- package/packages/core/src/selector.ts +110 -15
- package/packages/mcp/src/handlers.ts +7 -0
- package/packages/mcp/src/server.ts +21 -3
- package/test/core/selector.test.ts +133 -0
- package/dist/chunk-4HRLW6YF.js.map +0 -1
- package/dist/chunk-QRZ57ME3.js.map +0 -1
- package/dist/chunk-TWEYRBDU.js.map +0 -1
- /package/dist/{chunk-AYSVE6NG.js.map → chunk-G5CIZSQM.js.map} +0 -0
package/dist/core/index.d.ts
CHANGED
|
@@ -72,12 +72,19 @@ interface SelectorOptions {
|
|
|
72
72
|
maxClasses?: number;
|
|
73
73
|
maxOuterHTMLLength?: number;
|
|
74
74
|
maxInnerTextLength?: number;
|
|
75
|
+
maxParentDepth?: number;
|
|
75
76
|
}
|
|
76
77
|
/**
|
|
77
78
|
* Builds a clean, human-readable semantic selector for a DOM element.
|
|
78
|
-
* Prioritizes data-testid/data-
|
|
79
|
+
* Prioritizes data-testid/data-component, id, semantic tag + curated class, aria/role, or concise unique parent path.
|
|
79
80
|
*/
|
|
80
81
|
declare function getSemanticSelector(element: HTMLElement | Element, options?: SelectorOptions): string;
|
|
82
|
+
/**
|
|
83
|
+
* If an element under the cursor is an inline or leaf formatting element
|
|
84
|
+
* (e.g. svg, path, i, b, strong, or an inner span without attributes)
|
|
85
|
+
* inside an interactive container or component, resolves to the parent container.
|
|
86
|
+
*/
|
|
87
|
+
declare function resolveMeaningfulTarget(element: HTMLElement | Element): HTMLElement;
|
|
81
88
|
/**
|
|
82
89
|
* Truncates string to specified max length with ellipsis
|
|
83
90
|
*/
|
|
@@ -106,4 +113,4 @@ declare function safeExecute<T>(fn: () => T, fallback: T, onError?: (err: any) =
|
|
|
106
113
|
*/
|
|
107
114
|
declare function safeAsync<T>(action: Promise<T> | (() => Promise<T>), fallback: T, onError?: (err: any) => void): Promise<T>;
|
|
108
115
|
|
|
109
|
-
export { BROWSER_SECURITY_RULES, type FingerprintInput, REDACTED_PLACEHOLDER, SENSITIVE_KEY_PATTERNS, SENSITIVE_QUERY_PARAMS, type SelectorOptions, computeFingerprint, extractSourceFromStack, getSemanticSelector, isSensitiveKey, normalizeErrorMessage, normalizeSourceFile, redactHeaders, redactSensitiveData, redactUrl, safeAsync, safeExecute, safeJsonParse, safeJsonStringify, truncate };
|
|
116
|
+
export { BROWSER_SECURITY_RULES, type FingerprintInput, REDACTED_PLACEHOLDER, SENSITIVE_KEY_PATTERNS, SENSITIVE_QUERY_PARAMS, type SelectorOptions, computeFingerprint, extractSourceFromStack, getSemanticSelector, isSensitiveKey, normalizeErrorMessage, normalizeSourceFile, redactHeaders, redactSensitiveData, redactUrl, resolveMeaningfulTarget, safeAsync, safeExecute, safeJsonParse, safeJsonStringify, truncate };
|
package/dist/core/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
redactHeaders,
|
|
13
13
|
redactSensitiveData,
|
|
14
14
|
redactUrl,
|
|
15
|
+
resolveMeaningfulTarget,
|
|
15
16
|
safeAsync,
|
|
16
17
|
safeExecute,
|
|
17
18
|
safeJsonParse,
|
|
@@ -19,7 +20,7 @@ import {
|
|
|
19
20
|
standardRules,
|
|
20
21
|
truncate,
|
|
21
22
|
visulimaRedact
|
|
22
|
-
} from "../chunk-
|
|
23
|
+
} from "../chunk-ONPW7AYL.js";
|
|
23
24
|
export {
|
|
24
25
|
BROWSER_SECURITY_RULES,
|
|
25
26
|
REDACTED_PLACEHOLDER,
|
|
@@ -34,6 +35,7 @@ export {
|
|
|
34
35
|
redactHeaders,
|
|
35
36
|
redactSensitiveData,
|
|
36
37
|
redactUrl,
|
|
38
|
+
resolveMeaningfulTarget,
|
|
37
39
|
safeAsync,
|
|
38
40
|
safeExecute,
|
|
39
41
|
safeJsonParse,
|
package/dist/daemon/index.js
CHANGED
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
createHttpHandler,
|
|
12
12
|
getDaemonConfig,
|
|
13
13
|
setupWebSocketServer
|
|
14
|
-
} from "../chunk-
|
|
15
|
-
import "../chunk-
|
|
14
|
+
} from "../chunk-G5CIZSQM.js";
|
|
15
|
+
import "../chunk-ONPW7AYL.js";
|
|
16
16
|
export {
|
|
17
17
|
BrowserTrackDaemon,
|
|
18
18
|
IncidentEngine,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { B as Breadcrumb, C as CaptureElementParams, a as CaptureElementResult, b as ClientCommand, c as ClientEventMessage, d as CommandResponse, e as CommandType, f as ComponentSourceInfo, g as ConsoleEvent, h as ConsoleLevel, D as DOMRectJson, E as ElementContext, i as ElementStyleResult, j as ElementSummary, k as EventType, H as HelloMessage, N as NavigateParams, l as NavigationEvent, m as NetworkEvent, n as NoteStatus, o as NoteTarget, p as NoteType, q as NoteVerificationResult, O as OverflowCheckResult, P as PageStateResult, Q as QueryElementParams, r as QueryElementResult, R as RegionContext, s as ReloadParams, t as RuntimeErrorEvent, S as ScenarioDetail, u as ScenarioOverview, v as ScrollContext, V as ViewportContext, w as VisualNote } from './commands-fjuqKzkm.js';
|
|
2
2
|
export { I as Incident, a as IncidentOccurrence, b as IncidentSeverity, c as IncidentSource, d as IncidentStatus, P as ProbeResult, e as ProbeType, f as Project, S as Session, V as VerificationProbe, g as VerificationRecipe, h as VerificationResult } from './projects-DB7S312i.js';
|
|
3
|
-
export { BROWSER_SECURITY_RULES, FingerprintInput, REDACTED_PLACEHOLDER, SENSITIVE_KEY_PATTERNS, SENSITIVE_QUERY_PARAMS, SelectorOptions, computeFingerprint, extractSourceFromStack, getSemanticSelector, isSensitiveKey, normalizeErrorMessage, normalizeSourceFile, redactHeaders, redactSensitiveData, redactUrl, safeAsync, safeExecute, safeJsonParse, safeJsonStringify, truncate } from './core/index.js';
|
|
3
|
+
export { BROWSER_SECURITY_RULES, FingerprintInput, REDACTED_PLACEHOLDER, SENSITIVE_KEY_PATTERNS, SENSITIVE_QUERY_PARAMS, SelectorOptions, computeFingerprint, extractSourceFromStack, getSemanticSelector, isSensitiveKey, normalizeErrorMessage, normalizeSourceFile, redactHeaders, redactSensitiveData, redactUrl, resolveMeaningfulTarget, safeAsync, safeExecute, safeJsonParse, safeJsonStringify, truncate } from './core/index.js';
|
|
4
4
|
export { BrowserTrackClient, getClient, init as initClient } from './client/index.js';
|
|
5
5
|
export { BrowserTrackDaemon, createDaemon } from './daemon/index.js';
|
|
6
|
-
export { c as createMcpServer } from './server-
|
|
6
|
+
export { c as createMcpServer } from './server-BRG-RQQP.js';
|
|
7
7
|
export { Rules, standardRules, redact as visulimaRedact } from '@visulima/redact';
|
|
8
8
|
import './engine-CmchnMDq.js';
|
|
9
9
|
import 'ws';
|
package/dist/index.js
CHANGED
|
@@ -2,14 +2,14 @@ import {
|
|
|
2
2
|
BrowserTrackClient,
|
|
3
3
|
getClient,
|
|
4
4
|
init
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-PG4JJDCV.js";
|
|
6
6
|
import {
|
|
7
7
|
createMcpServer
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-5NR5K3ER.js";
|
|
9
9
|
import {
|
|
10
10
|
BrowserTrackDaemon,
|
|
11
11
|
createDaemon
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-G5CIZSQM.js";
|
|
13
13
|
import {
|
|
14
14
|
BROWSER_SECURITY_RULES,
|
|
15
15
|
REDACTED_PLACEHOLDER,
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
redactHeaders,
|
|
25
25
|
redactSensitiveData,
|
|
26
26
|
redactUrl,
|
|
27
|
+
resolveMeaningfulTarget,
|
|
27
28
|
safeAsync,
|
|
28
29
|
safeExecute,
|
|
29
30
|
safeJsonParse,
|
|
@@ -31,7 +32,7 @@ import {
|
|
|
31
32
|
standardRules,
|
|
32
33
|
truncate,
|
|
33
34
|
visulimaRedact
|
|
34
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-ONPW7AYL.js";
|
|
35
36
|
export {
|
|
36
37
|
BROWSER_SECURITY_RULES,
|
|
37
38
|
BrowserTrackClient,
|
|
@@ -52,6 +53,7 @@ export {
|
|
|
52
53
|
redactHeaders,
|
|
53
54
|
redactSensitiveData,
|
|
54
55
|
redactUrl,
|
|
56
|
+
resolveMeaningfulTarget,
|
|
55
57
|
safeAsync,
|
|
56
58
|
safeExecute,
|
|
57
59
|
safeJsonParse,
|
package/dist/mcp/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
-
export { M as McpContext, a as McpServerOptions, c as createMcpServer, h as handleToolCall, i as isDaemonRunning } from '../server-
|
|
2
|
+
export { M as McpContext, a as McpServerOptions, c as createMcpServer, h as handleToolCall, i as isDaemonRunning } from '../server-BRG-RQQP.js';
|
|
3
3
|
import '@modelcontextprotocol/sdk/server/index.js';
|
|
4
4
|
import '../engine-CmchnMDq.js';
|
|
5
5
|
import '../commands-fjuqKzkm.js';
|
package/dist/mcp/index.js
CHANGED
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
createMcpServer,
|
|
4
4
|
handleToolCall,
|
|
5
5
|
isDaemonRunning
|
|
6
|
-
} from "../chunk-
|
|
7
|
-
import "../chunk-
|
|
8
|
-
import "../chunk-
|
|
6
|
+
} from "../chunk-5NR5K3ER.js";
|
|
7
|
+
import "../chunk-G5CIZSQM.js";
|
|
8
|
+
import "../chunk-ONPW7AYL.js";
|
|
9
9
|
export {
|
|
10
10
|
TOOLS,
|
|
11
11
|
createMcpServer,
|
|
@@ -7,6 +7,7 @@ interface McpContext {
|
|
|
7
7
|
verificationEngine?: VerificationEngine;
|
|
8
8
|
noteVerificationEngine?: NoteVerificationEngine;
|
|
9
9
|
daemonUrl?: string;
|
|
10
|
+
daemonInitPromise?: Promise<void>;
|
|
10
11
|
}
|
|
11
12
|
declare function handleToolCall(name: string, args: any, ctx: McpContext): Promise<any>;
|
|
12
13
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ElementContext, NoteTarget, RegionContext, VisualNote } from '../../../core/src/index.js';
|
|
2
|
-
import { getSemanticSelector, truncate } from '../../../core/src/index.js';
|
|
2
|
+
import { getSemanticSelector, resolveMeaningfulTarget, truncate } from '../../../core/src/index.js';
|
|
3
3
|
import type { ScreenshotDriver } from '../screenshot/driver.js';
|
|
4
4
|
import type { WebSocketTransport } from '../transport/websocket.js';
|
|
5
5
|
import { shouldHideUIFromUrl } from '../config.js';
|
|
@@ -65,6 +65,7 @@ export class NoteInspector {
|
|
|
65
65
|
private savedNotes: VisualNote[] = [];
|
|
66
66
|
private showMarkers = true;
|
|
67
67
|
private isHidden = false;
|
|
68
|
+
private isToolbarCollapsed = false;
|
|
68
69
|
private cleanups: (() => void)[] = [];
|
|
69
70
|
|
|
70
71
|
constructor(transport: WebSocketTransport, screenshotDriver: ScreenshotDriver, options: InspectorOptions = {}) {
|
|
@@ -73,6 +74,11 @@ export class NoteInspector {
|
|
|
73
74
|
const hiddenByQuery = shouldHideUIFromUrl(options.hideQueryParam);
|
|
74
75
|
this.isHidden = options.hidden === true || hiddenByQuery;
|
|
75
76
|
this.showMarkers = options.showBadges !== false && !this.isHidden;
|
|
77
|
+
try {
|
|
78
|
+
if (typeof window !== 'undefined' && window.sessionStorage) {
|
|
79
|
+
this.isToolbarCollapsed = window.sessionStorage.getItem('bt_toolbar_collapsed') === 'true';
|
|
80
|
+
}
|
|
81
|
+
} catch {}
|
|
76
82
|
this.options = {
|
|
77
83
|
shortcut: 'Alt+Click',
|
|
78
84
|
maskSelectors: ['input[type="password"]', '[data-sensitive]'],
|
|
@@ -224,11 +230,28 @@ export class NoteInspector {
|
|
|
224
230
|
// Styles for highlight overlay, region drag box, markers, floating toolbar and modals
|
|
225
231
|
const style = document.createElement('style');
|
|
226
232
|
style.textContent = `
|
|
227
|
-
:
|
|
228
|
-
|
|
229
|
-
|
|
233
|
+
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
|
|
234
|
+
|
|
235
|
+
:host, :host *, :host *::before, :host *::after {
|
|
236
|
+
box-sizing: border-box;
|
|
230
237
|
-webkit-font-smoothing: antialiased;
|
|
231
238
|
-moz-osx-font-smoothing: grayscale;
|
|
239
|
+
text-rendering: optimizeLegibility;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
:host {
|
|
243
|
+
font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
244
|
+
color-scheme: dark;
|
|
245
|
+
letter-spacing: -0.012em;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
button, input, textarea, select {
|
|
249
|
+
font-family: inherit;
|
|
250
|
+
letter-spacing: inherit;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
code, pre, kbd, .bt-mono, .bt-badge, .bt-region-badge, .bt-target-pill, .bt-component-pill {
|
|
254
|
+
font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
232
255
|
}
|
|
233
256
|
|
|
234
257
|
/* 1. Element Highlight Overlay */
|
|
@@ -462,7 +485,7 @@ export class NoteInspector {
|
|
|
462
485
|
pointer-events: auto;
|
|
463
486
|
z-index: 2147483646;
|
|
464
487
|
user-select: none;
|
|
465
|
-
transition: all 0.
|
|
488
|
+
transition: all 0.22s cubic-bezier(0.16, 1, 0.3, 1);
|
|
466
489
|
}
|
|
467
490
|
|
|
468
491
|
.bt-toolbar-btn {
|
|
@@ -523,6 +546,86 @@ export class NoteInspector {
|
|
|
523
546
|
margin: 0 2px;
|
|
524
547
|
}
|
|
525
548
|
|
|
549
|
+
.bt-toolbar-collapse-btn {
|
|
550
|
+
background: transparent;
|
|
551
|
+
border: none;
|
|
552
|
+
color: #64748b;
|
|
553
|
+
font-size: 11px;
|
|
554
|
+
font-weight: 700;
|
|
555
|
+
padding: 6px 8px;
|
|
556
|
+
border-radius: 20px;
|
|
557
|
+
cursor: pointer;
|
|
558
|
+
display: flex;
|
|
559
|
+
align-items: center;
|
|
560
|
+
justify-content: center;
|
|
561
|
+
transition: all 0.15s ease;
|
|
562
|
+
font-family: inherit;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.bt-toolbar-collapse-btn:hover {
|
|
566
|
+
background: #1e293b;
|
|
567
|
+
color: #f8fafc;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/* Collapsed Toolbar State */
|
|
571
|
+
.bt-toolbar.bt-toolbar-collapsed {
|
|
572
|
+
padding: 0;
|
|
573
|
+
background: transparent;
|
|
574
|
+
border: none;
|
|
575
|
+
box-shadow: none;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.bt-toolbar.bt-toolbar-collapsed .bt-toolbar-btn,
|
|
579
|
+
.bt-toolbar.bt-toolbar-collapsed .bt-toolbar-divider,
|
|
580
|
+
.bt-toolbar.bt-toolbar-collapsed .bt-toolbar-collapse-btn {
|
|
581
|
+
display: none !important;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.bt-toolbar-trigger {
|
|
585
|
+
display: none;
|
|
586
|
+
background: #0f172a;
|
|
587
|
+
border: 1px solid #334155;
|
|
588
|
+
border-radius: 30px;
|
|
589
|
+
padding: 6px 14px;
|
|
590
|
+
color: #38bdf8;
|
|
591
|
+
font-size: 12px;
|
|
592
|
+
font-weight: 600;
|
|
593
|
+
cursor: pointer;
|
|
594
|
+
align-items: center;
|
|
595
|
+
gap: 8px;
|
|
596
|
+
box-shadow: 0 10px 25px -3px rgba(0,0,0,0.6), 0 4px 6px -4px rgba(0,0,0,0.4);
|
|
597
|
+
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
598
|
+
font-family: inherit;
|
|
599
|
+
user-select: none;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
.bt-toolbar.bt-toolbar-collapsed .bt-toolbar-trigger {
|
|
603
|
+
display: inline-flex;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.bt-toolbar-trigger:hover {
|
|
607
|
+
background: #1e293b;
|
|
608
|
+
color: #ffffff;
|
|
609
|
+
transform: translateY(-2px);
|
|
610
|
+
box-shadow: 0 12px 28px -3px rgba(0,0,0,0.7);
|
|
611
|
+
border-color: #38bdf8;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.bt-pulse-dot {
|
|
615
|
+
width: 7px;
|
|
616
|
+
height: 7px;
|
|
617
|
+
border-radius: 50%;
|
|
618
|
+
background: #38bdf8;
|
|
619
|
+
box-shadow: 0 0 8px #38bdf8;
|
|
620
|
+
display: inline-block;
|
|
621
|
+
animation: bt-dot-pulse 2s infinite;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
@keyframes bt-dot-pulse {
|
|
625
|
+
0%, 100% { opacity: 1; transform: scale(1); }
|
|
626
|
+
50% { opacity: 0.4; transform: scale(0.85); }
|
|
627
|
+
}
|
|
628
|
+
|
|
526
629
|
/* 5. Modals & Popover Card */
|
|
527
630
|
.bt-modal-backdrop {
|
|
528
631
|
position: fixed;
|
|
@@ -946,8 +1049,13 @@ export class NoteInspector {
|
|
|
946
1049
|
if (!this.shadowRoot || this.toolbarElement) return;
|
|
947
1050
|
|
|
948
1051
|
this.toolbarElement = document.createElement('div');
|
|
949
|
-
this.toolbarElement.className = 'bt-toolbar'
|
|
1052
|
+
this.toolbarElement.className = `bt-toolbar ${this.isToolbarCollapsed ? 'bt-toolbar-collapsed' : ''}`;
|
|
950
1053
|
this.toolbarElement.innerHTML = `
|
|
1054
|
+
<button class="bt-toolbar-trigger" id="bt-toolbar-expand" title="Expand BrowserTrack toolbar (Alt+T)">
|
|
1055
|
+
<span class="bt-pulse-dot"></span>
|
|
1056
|
+
<span>⚡ BrowserTrack</span>
|
|
1057
|
+
<span class="bt-count-pill" id="bt-notes-count-collapsed">0</span>
|
|
1058
|
+
</button>
|
|
951
1059
|
<button class="bt-toolbar-btn" id="bt-mode-element" title="Inspect element (or hold Alt+Click)">
|
|
952
1060
|
<span>🎯</span> Element
|
|
953
1061
|
</button>
|
|
@@ -965,14 +1073,30 @@ export class NoteInspector {
|
|
|
965
1073
|
<button class="bt-toolbar-btn active" id="bt-toggle-notes" title="Toggle visible note markers on screen">
|
|
966
1074
|
<span>📌</span> Notes <span class="bt-count-pill" id="bt-notes-count">0</span>
|
|
967
1075
|
</button>
|
|
1076
|
+
<div class="bt-toolbar-divider"></div>
|
|
1077
|
+
<button class="bt-toolbar-collapse-btn" id="bt-toolbar-collapse" title="Collapse toolbar (Alt+T)">
|
|
1078
|
+
<span>❯</span>
|
|
1079
|
+
</button>
|
|
968
1080
|
`;
|
|
969
1081
|
|
|
1082
|
+
const btnExpand = this.toolbarElement.querySelector('#bt-toolbar-expand') as HTMLButtonElement;
|
|
1083
|
+
const btnCollapse = this.toolbarElement.querySelector('#bt-toolbar-collapse') as HTMLButtonElement;
|
|
970
1084
|
const btnElement = this.toolbarElement.querySelector('#bt-mode-element') as HTMLButtonElement;
|
|
971
1085
|
const btnRegion = this.toolbarElement.querySelector('#bt-mode-region') as HTMLButtonElement;
|
|
972
1086
|
const btnPage = this.toolbarElement.querySelector('#bt-mode-page') as HTMLButtonElement;
|
|
973
1087
|
const btnFlow = this.toolbarElement.querySelector('#bt-mode-flow') as HTMLButtonElement;
|
|
974
1088
|
const btnToggleNotes = this.toolbarElement.querySelector('#bt-toggle-notes') as HTMLButtonElement;
|
|
975
1089
|
|
|
1090
|
+
btnExpand.onclick = (e: MouseEvent) => {
|
|
1091
|
+
e.stopPropagation();
|
|
1092
|
+
this.toggleToolbarCollapse(false);
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1095
|
+
btnCollapse.onclick = (e: MouseEvent) => {
|
|
1096
|
+
e.stopPropagation();
|
|
1097
|
+
this.toggleToolbarCollapse(true);
|
|
1098
|
+
};
|
|
1099
|
+
|
|
976
1100
|
btnElement.onclick = (e: MouseEvent) => {
|
|
977
1101
|
e.stopPropagation();
|
|
978
1102
|
this.setMode(this.activeMode === 'element' ? 'idle' : 'element');
|
|
@@ -1007,6 +1131,17 @@ export class NoteInspector {
|
|
|
1007
1131
|
this.shadowRoot.appendChild(this.toolbarElement);
|
|
1008
1132
|
}
|
|
1009
1133
|
|
|
1134
|
+
public toggleToolbarCollapse(force?: boolean): void {
|
|
1135
|
+
if (!this.toolbarElement) return;
|
|
1136
|
+
this.isToolbarCollapsed = typeof force === 'boolean' ? force : !this.isToolbarCollapsed;
|
|
1137
|
+
this.toolbarElement.classList.toggle('bt-toolbar-collapsed', this.isToolbarCollapsed);
|
|
1138
|
+
try {
|
|
1139
|
+
if (typeof window !== 'undefined' && window.sessionStorage) {
|
|
1140
|
+
window.sessionStorage.setItem('bt_toolbar_collapsed', String(this.isToolbarCollapsed));
|
|
1141
|
+
}
|
|
1142
|
+
} catch {}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1010
1145
|
private updateToolbarState(): void {
|
|
1011
1146
|
if (!this.toolbarElement) return;
|
|
1012
1147
|
|
|
@@ -1034,11 +1169,15 @@ export class NoteInspector {
|
|
|
1034
1169
|
|
|
1035
1170
|
private updateToolbarCount(): void {
|
|
1036
1171
|
if (!this.toolbarElement) return;
|
|
1172
|
+
const openCount = this.savedNotes.filter((n) => n.status === 'OPEN').length;
|
|
1037
1173
|
const countEl = this.toolbarElement.querySelector('#bt-notes-count');
|
|
1038
1174
|
if (countEl) {
|
|
1039
|
-
const openCount = this.savedNotes.filter((n) => n.status === 'OPEN').length;
|
|
1040
1175
|
countEl.textContent = String(openCount);
|
|
1041
1176
|
}
|
|
1177
|
+
const countCollapsedEl = this.toolbarElement.querySelector('#bt-notes-count-collapsed');
|
|
1178
|
+
if (countCollapsedEl) {
|
|
1179
|
+
countCollapsedEl.textContent = String(openCount);
|
|
1180
|
+
}
|
|
1042
1181
|
}
|
|
1043
1182
|
|
|
1044
1183
|
public renderMarkers(): void {
|
|
@@ -1108,6 +1247,14 @@ export class NoteInspector {
|
|
|
1108
1247
|
|
|
1109
1248
|
const rect = targetEl ? targetEl.getBoundingClientRect() : note.target?.boundingRect;
|
|
1110
1249
|
if (rect) {
|
|
1250
|
+
// If element is completely scrolled out of view, do not display or pin it to corner
|
|
1251
|
+
const isOffscreen =
|
|
1252
|
+
targetEl &&
|
|
1253
|
+
(rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth);
|
|
1254
|
+
if (isOffscreen) {
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1111
1258
|
const pin = document.createElement('div');
|
|
1112
1259
|
pin.className = markerClass;
|
|
1113
1260
|
pin.setAttribute('data-note-id', note.id);
|
|
@@ -1361,7 +1508,8 @@ export class NoteInspector {
|
|
|
1361
1508
|
}
|
|
1362
1509
|
|
|
1363
1510
|
if (e.altKey || this.activeMode === 'element') {
|
|
1364
|
-
const
|
|
1511
|
+
const rawTarget = document.elementFromPoint(e.clientX, e.clientY) as HTMLElement | null;
|
|
1512
|
+
const target = rawTarget ? resolveMeaningfulTarget(rawTarget) : null;
|
|
1365
1513
|
if (target && target !== this.container && !this.container?.contains(target)) {
|
|
1366
1514
|
this.hoveredElement = target;
|
|
1367
1515
|
this.updateHighlight(target);
|
|
@@ -1394,7 +1542,8 @@ export class NoteInspector {
|
|
|
1394
1542
|
e.preventDefault();
|
|
1395
1543
|
e.stopPropagation();
|
|
1396
1544
|
|
|
1397
|
-
const
|
|
1545
|
+
const rawTarget = (this.hoveredElement || document.elementFromPoint(e.clientX, e.clientY)) as HTMLElement | null;
|
|
1546
|
+
const target = rawTarget ? resolveMeaningfulTarget(rawTarget) : null;
|
|
1398
1547
|
if (target && target !== this.container && !this.container?.contains(target)) {
|
|
1399
1548
|
this.selectedElement = target;
|
|
1400
1549
|
this.openNoteEditor(target, 'element');
|
|
@@ -1408,9 +1557,15 @@ export class NoteInspector {
|
|
|
1408
1557
|
}
|
|
1409
1558
|
};
|
|
1410
1559
|
|
|
1411
|
-
// 3. Key handling: Escape cancels active modes, Alt release hides highlight
|
|
1560
|
+
// 3. Key handling: Escape cancels active modes, Alt release hides highlight, Alt+T toggles toolbar
|
|
1412
1561
|
const onKeyDown = (e: KeyboardEvent) => {
|
|
1413
1562
|
try {
|
|
1563
|
+
if (e.altKey && (e.key === 't' || e.key === 'T')) {
|
|
1564
|
+
e.preventDefault();
|
|
1565
|
+
this.toggleToolbarCollapse();
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1414
1569
|
if (e.key === 'Escape') {
|
|
1415
1570
|
if (this.cardOverlay && this.cardOverlay.parentElement && this.shadowRoot) {
|
|
1416
1571
|
this.shadowRoot.removeChild(this.cardOverlay);
|
|
@@ -1542,6 +1697,11 @@ export class NoteInspector {
|
|
|
1542
1697
|
this.regionBox.appendChild(badge);
|
|
1543
1698
|
}
|
|
1544
1699
|
badge.textContent = `Region: ${Math.round(width)} × ${Math.round(height)} px`;
|
|
1700
|
+
if (top < 32) {
|
|
1701
|
+
badge.style.top = '4px';
|
|
1702
|
+
} else {
|
|
1703
|
+
badge.style.top = '-26px';
|
|
1704
|
+
}
|
|
1545
1705
|
};
|
|
1546
1706
|
|
|
1547
1707
|
this.regionOverlay.onmouseup = (e: MouseEvent) => {
|
|
@@ -1610,6 +1770,22 @@ export class NoteInspector {
|
|
|
1610
1770
|
const selector = getSemanticSelector(el);
|
|
1611
1771
|
const label = this.activeScenario ? `🎬 Step ${this.activeScenario.stepNumber} · ${selector}` : selector;
|
|
1612
1772
|
badge.textContent = `${label} (${Math.round(rect.width)} × ${Math.round(rect.height)} px)`;
|
|
1773
|
+
|
|
1774
|
+
// Viewport-aware badge clamping: flip below element if near top of window
|
|
1775
|
+
if (rect.top < 32) {
|
|
1776
|
+
badge.style.top = `${rect.height + 4}px`;
|
|
1777
|
+
} else {
|
|
1778
|
+
badge.style.top = '-26px';
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
// Align to right edge if overflowing the right viewport boundary
|
|
1782
|
+
if (rect.left + 260 > window.innerWidth) {
|
|
1783
|
+
badge.style.left = 'auto';
|
|
1784
|
+
badge.style.right = '0';
|
|
1785
|
+
} else {
|
|
1786
|
+
badge.style.left = '0';
|
|
1787
|
+
badge.style.right = 'auto';
|
|
1788
|
+
}
|
|
1613
1789
|
}
|
|
1614
1790
|
|
|
1615
1791
|
private hideHighlight(): void {
|
|
@@ -1797,10 +1973,13 @@ export class NoteInspector {
|
|
|
1797
1973
|
if (btnSave) btnSave.disabled = true;
|
|
1798
1974
|
if (btnFinishFlow) btnFinishFlow.disabled = true;
|
|
1799
1975
|
|
|
1976
|
+
// Close modal immediately for instant, snappy user experience
|
|
1977
|
+
closeModal();
|
|
1978
|
+
|
|
1800
1979
|
try {
|
|
1801
1980
|
await options.onSave(message, action);
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1981
|
+
} catch {
|
|
1982
|
+
this.showToast('Failed to save note', '⚠️');
|
|
1804
1983
|
}
|
|
1805
1984
|
};
|
|
1806
1985
|
|
|
@@ -1835,11 +2014,15 @@ export class NoteInspector {
|
|
|
1835
2014
|
const rect = targetEl.getBoundingClientRect();
|
|
1836
2015
|
const selector = noteType === 'page' ? 'body' : getSemanticSelector(targetEl);
|
|
1837
2016
|
|
|
1838
|
-
// 1. Capture screenshot via screenshotDriver
|
|
2017
|
+
// 1. Capture screenshot via screenshotDriver with fast timeout protection
|
|
1839
2018
|
let screenshotDataUrl: string | undefined;
|
|
1840
2019
|
try {
|
|
1841
|
-
const
|
|
1842
|
-
|
|
2020
|
+
const snapPromise = this.screenshotDriver.captureElement(targetEl);
|
|
2021
|
+
const timeoutPromise = new Promise<{ ok: boolean; dataUrl?: string }>((resolve) =>
|
|
2022
|
+
setTimeout(() => resolve({ ok: false }), 800)
|
|
2023
|
+
);
|
|
2024
|
+
const snap = await Promise.race([snapPromise, timeoutPromise]);
|
|
2025
|
+
if (snap && snap.ok && snap.dataUrl) {
|
|
1843
2026
|
screenshotDataUrl = snap.dataUrl;
|
|
1844
2027
|
}
|
|
1845
2028
|
} catch {
|
|
@@ -1910,12 +2093,20 @@ export class NoteInspector {
|
|
|
1910
2093
|
message: string,
|
|
1911
2094
|
scenario?: { scenarioId?: string; stepNumber?: number; scenarioTitle?: string }
|
|
1912
2095
|
): Promise<void> {
|
|
1913
|
-
// 1.
|
|
2096
|
+
// 1. Fast regional capture: capture element under region center rather than heavy whole-document serialization
|
|
1914
2097
|
let screenshotDataUrl: string | undefined;
|
|
1915
2098
|
try {
|
|
1916
|
-
const
|
|
1917
|
-
|
|
1918
|
-
|
|
2099
|
+
const centerX = region.x + region.width / 2;
|
|
2100
|
+
const centerY = region.y + region.height / 2;
|
|
2101
|
+
const targetElement = (document.elementFromPoint(centerX, centerY) || document.body) as HTMLElement;
|
|
2102
|
+
|
|
2103
|
+
const snapPromise = this.screenshotDriver.captureElement(targetElement);
|
|
2104
|
+
const timeoutPromise = new Promise<{ ok: boolean; dataUrl?: string }>((resolve) =>
|
|
2105
|
+
setTimeout(() => resolve({ ok: false }), 800)
|
|
2106
|
+
);
|
|
2107
|
+
const snap = await Promise.race([snapPromise, timeoutPromise]);
|
|
2108
|
+
if (snap && snap.ok && snap.dataUrl) {
|
|
2109
|
+
screenshotDataUrl = snap.dataUrl;
|
|
1919
2110
|
}
|
|
1920
2111
|
} catch {
|
|
1921
2112
|
// Defensive
|