browsertrack 0.1.0 → 0.1.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/.github/workflows/docs.yml +54 -0
- package/AGENTS.md +133 -0
- package/README.md +1 -1
- package/dist/{chunk-6A7FFDIB.js → chunk-6VA7GBAO.js} +29 -4
- package/dist/chunk-6VA7GBAO.js.map +1 -0
- package/dist/{chunk-ANMR5WBH.js → chunk-G2Y3CXCY.js} +2 -2
- package/dist/{chunk-BSKNG4V7.js → chunk-ILRYKMME.js} +30 -1
- package/dist/chunk-ILRYKMME.js.map +1 -0
- package/dist/{chunk-X7C5CHBO.js → chunk-SKCMT2DE.js} +637 -76
- package/dist/chunk-SKCMT2DE.js.map +1 -0
- package/dist/{chunk-ZLNGOOH2.js → chunk-SPCIROIU.js} +65 -3
- package/dist/chunk-SPCIROIU.js.map +1 -0
- package/dist/cli/index.js +115 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.cjs +637 -75
- package/dist/client/index.d.ts +16 -2
- package/dist/client/index.js +2 -2
- package/dist/client.iife.js +393 -72
- package/dist/core/index.d.ts +12 -4
- package/dist/core/index.js +9 -3
- package/dist/daemon/index.d.ts +1 -1
- package/dist/daemon/index.js +3 -3
- package/dist/{engine-DKloFjvs.d.ts → engine-CeT9URuN.d.ts} +4 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +13 -7
- package/dist/mcp/index.d.ts +2 -2
- package/dist/mcp/index.js +2 -2
- package/dist/{server-CN-se8td.d.ts → server-Dd8NX2Mk.d.ts} +1 -1
- package/docboot.config.js +16 -0
- package/docs/cli.md +51 -0
- package/docs/closed-loop-verification.md +57 -0
- package/docs/getting-started.md +122 -0
- package/docs/incidents-diagnostics.md +61 -0
- package/docs/index.md +46 -0
- package/docs/mcp-reference.md +128 -0
- package/docs/security-privacy.md +32 -0
- package/docs/visual-notes.md +67 -0
- package/package.json +6 -2
- package/packages/client/package.json +2 -2
- package/packages/client/src/notes/inspector.ts +689 -79
- package/packages/client/src/transport/websocket.ts +15 -0
- package/packages/core/package.json +3 -0
- package/packages/core/src/redaction.ts +40 -5
- package/packages/daemon/src/server/ws.ts +72 -1
- package/packages/daemon/src/session/manager.ts +21 -0
- package/packages/daemon/src/storage/db.ts +10 -1
- package/test/client/interceptors.test.ts +61 -0
- package/test/core/redaction.test.ts +12 -0
- package/dist/chunk-6A7FFDIB.js.map +0 -1
- package/dist/chunk-BSKNG4V7.js.map +0 -1
- package/dist/chunk-X7C5CHBO.js.map +0 -1
- package/dist/chunk-ZLNGOOH2.js.map +0 -1
- package/packages/core/dist/index.d.ts +0 -317
- package/packages/core/dist/index.js +0 -221
- /package/dist/{chunk-ANMR5WBH.js.map → chunk-G2Y3CXCY.js.map} +0 -0
|
@@ -16,9 +16,11 @@ export interface InspectorOptions {
|
|
|
16
16
|
* Isolated Visual Note Inspector rendering in Shadow DOM.
|
|
17
17
|
* Supports:
|
|
18
18
|
* 1. Element selection (hover highlight & click)
|
|
19
|
-
* 2. Region / Area selection (drag-and-drop rectangle on screen)
|
|
19
|
+
* 2. Region / Area selection (drag-and-drop rectangle on screen with cancel banner)
|
|
20
20
|
* 3. Whole page note
|
|
21
|
-
* 4.
|
|
21
|
+
* 4. Real-time Saved Note Markers (pins on elements/regions when page loads or syncs)
|
|
22
|
+
* 5. Interactive Note Detail Card (view message, target, resolve/delete actions)
|
|
23
|
+
* 6. Floating quick dock / toolbar in bottom-right corner with Notes count toggle
|
|
22
24
|
*/
|
|
23
25
|
export class NoteInspector {
|
|
24
26
|
private transport: WebSocketTransport;
|
|
@@ -30,8 +32,11 @@ export class NoteInspector {
|
|
|
30
32
|
private highlightOverlay: HTMLDivElement | null = null;
|
|
31
33
|
private regionOverlay: HTMLDivElement | null = null;
|
|
32
34
|
private regionBox: HTMLDivElement | null = null;
|
|
35
|
+
private regionBanner: HTMLDivElement | null = null;
|
|
36
|
+
private markersContainer: HTMLDivElement | null = null;
|
|
33
37
|
private toolbarElement: HTMLDivElement | null = null;
|
|
34
38
|
private modalOverlay: HTMLDivElement | null = null;
|
|
39
|
+
private cardOverlay: HTMLDivElement | null = null;
|
|
35
40
|
|
|
36
41
|
private activeMode: NoteInspectMode = 'idle';
|
|
37
42
|
private hoveredElement: HTMLElement | null = null;
|
|
@@ -41,6 +46,9 @@ export class NoteInspector {
|
|
|
41
46
|
private isDraggingRegion = false;
|
|
42
47
|
private dragStartX = 0;
|
|
43
48
|
private dragStartY = 0;
|
|
49
|
+
|
|
50
|
+
private savedNotes: VisualNote[] = [];
|
|
51
|
+
private showMarkers = true;
|
|
44
52
|
private cleanups: (() => void)[] = [];
|
|
45
53
|
|
|
46
54
|
constructor(transport: WebSocketTransport, screenshotDriver: ScreenshotDriver, options: InspectorOptions = {}) {
|
|
@@ -65,9 +73,23 @@ export class NoteInspector {
|
|
|
65
73
|
this.ensureContainer();
|
|
66
74
|
}
|
|
67
75
|
|
|
76
|
+
// Subscribe to incoming notes synchronization from daemon
|
|
77
|
+
const unsubscribe = this.transport.onMessage((msg) => {
|
|
78
|
+
if (msg && msg.type === 'notes_sync' && Array.isArray(msg.notes)) {
|
|
79
|
+
this.syncNotes(msg.notes);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
this.cleanups.push(unsubscribe);
|
|
83
|
+
|
|
68
84
|
this.setupListeners();
|
|
69
85
|
}
|
|
70
86
|
|
|
87
|
+
public syncNotes(notes: VisualNote[]): void {
|
|
88
|
+
this.savedNotes = notes;
|
|
89
|
+
this.updateToolbarCount();
|
|
90
|
+
this.renderMarkers();
|
|
91
|
+
}
|
|
92
|
+
|
|
71
93
|
public setMode(mode: NoteInspectMode): void {
|
|
72
94
|
this.activeMode = mode;
|
|
73
95
|
this.updateToolbarState();
|
|
@@ -98,20 +120,21 @@ export class NoteInspector {
|
|
|
98
120
|
|
|
99
121
|
this.shadowRoot = this.container.attachShadow({ mode: 'open' });
|
|
100
122
|
|
|
101
|
-
// Styles for highlight overlay, region drag box, floating toolbar and
|
|
123
|
+
// Styles for highlight overlay, region drag box, markers, floating toolbar and modals
|
|
102
124
|
const style = document.createElement('style');
|
|
103
125
|
style.textContent = `
|
|
104
126
|
:host {
|
|
105
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
127
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
106
128
|
color-scheme: dark;
|
|
107
129
|
-webkit-font-smoothing: antialiased;
|
|
130
|
+
-moz-osx-font-smoothing: grayscale;
|
|
108
131
|
}
|
|
109
132
|
|
|
110
133
|
/* 1. Element Highlight Overlay */
|
|
111
134
|
.bt-highlight {
|
|
112
135
|
position: fixed;
|
|
113
136
|
border: 2px solid #3b82f6;
|
|
114
|
-
background: rgba(59, 130, 246, 0.
|
|
137
|
+
background: rgba(59, 130, 246, 0.16);
|
|
115
138
|
border-radius: 4px;
|
|
116
139
|
pointer-events: none;
|
|
117
140
|
transition: all 0.05s ease-out;
|
|
@@ -121,18 +144,19 @@ export class NoteInspector {
|
|
|
121
144
|
|
|
122
145
|
.bt-badge {
|
|
123
146
|
position: absolute;
|
|
124
|
-
top: -
|
|
147
|
+
top: -26px;
|
|
125
148
|
left: 0;
|
|
126
|
-
background: #
|
|
149
|
+
background: #0f172a;
|
|
127
150
|
color: #38bdf8;
|
|
128
151
|
border: 1px solid #3b82f6;
|
|
129
152
|
font-size: 11px;
|
|
130
153
|
font-weight: 600;
|
|
131
|
-
|
|
132
|
-
|
|
154
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
155
|
+
padding: 2px 7px;
|
|
156
|
+
border-radius: 4px;
|
|
133
157
|
white-space: nowrap;
|
|
134
158
|
pointer-events: none;
|
|
135
|
-
box-shadow: 0
|
|
159
|
+
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.5);
|
|
136
160
|
}
|
|
137
161
|
|
|
138
162
|
/* 2. Region Selection Overlay */
|
|
@@ -144,7 +168,8 @@ export class NoteInspector {
|
|
|
144
168
|
cursor: crosshair;
|
|
145
169
|
z-index: 2147483642;
|
|
146
170
|
pointer-events: auto;
|
|
147
|
-
background: rgba(15, 23, 42, 0.
|
|
171
|
+
background: rgba(15, 23, 42, 0.25);
|
|
172
|
+
user-select: none;
|
|
148
173
|
}
|
|
149
174
|
|
|
150
175
|
.bt-region-box {
|
|
@@ -158,32 +183,171 @@ export class NoteInspector {
|
|
|
158
183
|
|
|
159
184
|
.bt-region-badge {
|
|
160
185
|
position: absolute;
|
|
161
|
-
top: -
|
|
186
|
+
top: -26px;
|
|
162
187
|
left: 0;
|
|
163
188
|
background: #0f172a;
|
|
164
189
|
color: #38bdf8;
|
|
165
190
|
border: 1px solid #0284c7;
|
|
166
191
|
font-size: 11px;
|
|
167
192
|
font-weight: 600;
|
|
168
|
-
|
|
169
|
-
|
|
193
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
194
|
+
padding: 2px 7px;
|
|
195
|
+
border-radius: 4px;
|
|
170
196
|
white-space: nowrap;
|
|
171
197
|
pointer-events: none;
|
|
198
|
+
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.5);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.bt-region-banner {
|
|
202
|
+
position: fixed;
|
|
203
|
+
top: 20px;
|
|
204
|
+
left: 50%;
|
|
205
|
+
transform: translateX(-50%);
|
|
206
|
+
background: #0f172a;
|
|
207
|
+
color: #f8fafc;
|
|
208
|
+
border: 1px solid #0284c7;
|
|
209
|
+
border-radius: 30px;
|
|
210
|
+
padding: 8px 16px;
|
|
211
|
+
display: flex;
|
|
212
|
+
align-items: center;
|
|
213
|
+
gap: 12px;
|
|
214
|
+
box-shadow: 0 10px 25px -5px rgba(0,0,0,0.6);
|
|
215
|
+
font-size: 12.5px;
|
|
216
|
+
font-weight: 500;
|
|
217
|
+
pointer-events: auto;
|
|
218
|
+
z-index: 2147483645;
|
|
219
|
+
user-select: none;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.bt-region-cancel-btn {
|
|
223
|
+
background: rgba(239, 68, 68, 0.15);
|
|
224
|
+
color: #fca5a5;
|
|
225
|
+
border: 1px solid rgba(239, 68, 68, 0.4);
|
|
226
|
+
padding: 3px 10px;
|
|
227
|
+
border-radius: 20px;
|
|
228
|
+
font-size: 11px;
|
|
229
|
+
font-weight: 600;
|
|
230
|
+
cursor: pointer;
|
|
231
|
+
transition: all 0.15s ease;
|
|
232
|
+
display: inline-flex;
|
|
233
|
+
align-items: center;
|
|
234
|
+
gap: 4px;
|
|
235
|
+
font-family: inherit;
|
|
172
236
|
}
|
|
173
237
|
|
|
174
|
-
|
|
238
|
+
.bt-region-cancel-btn:hover {
|
|
239
|
+
background: rgba(239, 68, 68, 0.3);
|
|
240
|
+
color: #ffffff;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* 3. Note Markers on Page */
|
|
244
|
+
.bt-markers-layer {
|
|
245
|
+
position: fixed;
|
|
246
|
+
top: 0;
|
|
247
|
+
left: 0;
|
|
248
|
+
width: 0;
|
|
249
|
+
height: 0;
|
|
250
|
+
pointer-events: none;
|
|
251
|
+
z-index: 2147483638;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.bt-note-marker {
|
|
255
|
+
position: fixed;
|
|
256
|
+
pointer-events: auto;
|
|
257
|
+
background: linear-gradient(135deg, #2563eb, #1d4ed8);
|
|
258
|
+
color: #ffffff;
|
|
259
|
+
border: 2px solid #ffffff;
|
|
260
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.5), 0 0 0 1px rgba(37,99,235,0.4);
|
|
261
|
+
border-radius: 999px;
|
|
262
|
+
height: 26px;
|
|
263
|
+
min-width: 26px;
|
|
264
|
+
padding: 0 7px;
|
|
265
|
+
display: inline-flex;
|
|
266
|
+
align-items: center;
|
|
267
|
+
justify-content: center;
|
|
268
|
+
gap: 4px;
|
|
269
|
+
cursor: pointer;
|
|
270
|
+
font-size: 11px;
|
|
271
|
+
font-weight: 700;
|
|
272
|
+
user-select: none;
|
|
273
|
+
transition: transform 0.15s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.15s ease;
|
|
274
|
+
animation: bt-pop-in 0.2s ease-out;
|
|
275
|
+
box-sizing: border-box;
|
|
276
|
+
z-index: 2147483641;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
@keyframes bt-pop-in {
|
|
280
|
+
0% { transform: scale(0.6); opacity: 0; }
|
|
281
|
+
100% { transform: scale(1); opacity: 1; }
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.bt-note-marker:hover {
|
|
285
|
+
transform: scale(1.15) translateY(-2px);
|
|
286
|
+
box-shadow: 0 8px 20px rgba(37,99,235,0.6), 0 0 0 2px #60a5fa;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.bt-marker-resolved {
|
|
290
|
+
background: linear-gradient(135deg, #475569, #334155);
|
|
291
|
+
border-color: #94a3b8;
|
|
292
|
+
opacity: 0.75;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.bt-region-marker-box {
|
|
296
|
+
position: fixed;
|
|
297
|
+
border: 2px dashed #0284c7;
|
|
298
|
+
background: rgba(14, 165, 233, 0.08);
|
|
299
|
+
pointer-events: none;
|
|
300
|
+
box-sizing: border-box;
|
|
301
|
+
border-radius: 6px;
|
|
302
|
+
z-index: 2147483639;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.bt-page-notes-dock {
|
|
306
|
+
position: fixed;
|
|
307
|
+
top: 16px;
|
|
308
|
+
right: 16px;
|
|
309
|
+
display: flex;
|
|
310
|
+
flex-direction: column;
|
|
311
|
+
gap: 6px;
|
|
312
|
+
pointer-events: none;
|
|
313
|
+
z-index: 2147483641;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.bt-page-note-pill {
|
|
317
|
+
background: #0f172a;
|
|
318
|
+
color: #c084fc;
|
|
319
|
+
border: 1px solid #7e22ce;
|
|
320
|
+
border-radius: 20px;
|
|
321
|
+
padding: 5px 12px;
|
|
322
|
+
font-size: 11.5px;
|
|
323
|
+
font-weight: 600;
|
|
324
|
+
display: inline-flex;
|
|
325
|
+
align-items: center;
|
|
326
|
+
gap: 6px;
|
|
327
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.5);
|
|
328
|
+
cursor: pointer;
|
|
329
|
+
pointer-events: auto;
|
|
330
|
+
transition: all 0.15s ease;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.bt-page-note-pill:hover {
|
|
334
|
+
background: #1e1b4b;
|
|
335
|
+
transform: translateY(-1px);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* 4. Floating Quick Toolbar */
|
|
175
339
|
.bt-toolbar {
|
|
176
340
|
position: fixed;
|
|
177
341
|
bottom: 20px;
|
|
178
342
|
right: 20px;
|
|
179
|
-
background: #
|
|
343
|
+
background: #0f172a;
|
|
180
344
|
border: 1px solid #334155;
|
|
181
345
|
border-radius: 30px;
|
|
182
346
|
padding: 4px 6px;
|
|
183
347
|
display: flex;
|
|
184
348
|
align-items: center;
|
|
185
349
|
gap: 4px;
|
|
186
|
-
box-shadow: 0 10px
|
|
350
|
+
box-shadow: 0 10px 25px -3px rgba(0,0,0,0.6), 0 4px 6px -4px rgba(0,0,0,0.4);
|
|
187
351
|
pointer-events: auto;
|
|
188
352
|
z-index: 2147483646;
|
|
189
353
|
user-select: none;
|
|
@@ -196,17 +360,18 @@ export class NoteInspector {
|
|
|
196
360
|
color: #94a3b8;
|
|
197
361
|
font-size: 12px;
|
|
198
362
|
font-weight: 500;
|
|
199
|
-
padding: 6px
|
|
363
|
+
padding: 6px 12px;
|
|
200
364
|
border-radius: 20px;
|
|
201
365
|
cursor: pointer;
|
|
202
366
|
display: flex;
|
|
203
367
|
align-items: center;
|
|
204
|
-
gap:
|
|
368
|
+
gap: 6px;
|
|
205
369
|
transition: all 0.15s ease;
|
|
370
|
+
font-family: inherit;
|
|
206
371
|
}
|
|
207
372
|
|
|
208
373
|
.bt-toolbar-btn:hover {
|
|
209
|
-
background: #
|
|
374
|
+
background: #1e293b;
|
|
210
375
|
color: #f8fafc;
|
|
211
376
|
}
|
|
212
377
|
|
|
@@ -214,15 +379,27 @@ export class NoteInspector {
|
|
|
214
379
|
background: #2563eb;
|
|
215
380
|
color: #ffffff;
|
|
216
381
|
font-weight: 600;
|
|
382
|
+
box-shadow: 0 2px 6px rgba(37, 99, 235, 0.35);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.bt-count-pill {
|
|
386
|
+
background: rgba(255, 255, 255, 0.2);
|
|
387
|
+
color: #ffffff;
|
|
388
|
+
font-size: 10px;
|
|
389
|
+
font-weight: 700;
|
|
390
|
+
padding: 1px 6px;
|
|
391
|
+
border-radius: 10px;
|
|
392
|
+
line-height: 1.2;
|
|
217
393
|
}
|
|
218
394
|
|
|
219
395
|
.bt-toolbar-divider {
|
|
220
396
|
width: 1px;
|
|
221
397
|
height: 16px;
|
|
222
398
|
background: #334155;
|
|
399
|
+
margin: 0 2px;
|
|
223
400
|
}
|
|
224
401
|
|
|
225
|
-
/*
|
|
402
|
+
/* 5. Modals & Popover Card */
|
|
226
403
|
.bt-modal-backdrop {
|
|
227
404
|
position: fixed;
|
|
228
405
|
top: 0;
|
|
@@ -230,7 +407,7 @@ export class NoteInspector {
|
|
|
230
407
|
width: 100vw;
|
|
231
408
|
height: 100vh;
|
|
232
409
|
background: rgba(15, 23, 42, 0.75);
|
|
233
|
-
backdrop-filter: blur(
|
|
410
|
+
backdrop-filter: blur(6px);
|
|
234
411
|
display: flex;
|
|
235
412
|
align-items: center;
|
|
236
413
|
justify-content: center;
|
|
@@ -238,20 +415,26 @@ export class NoteInspector {
|
|
|
238
415
|
z-index: 2147483647;
|
|
239
416
|
opacity: 1;
|
|
240
417
|
box-sizing: border-box;
|
|
418
|
+
animation: bt-fade-in 0.15s ease-out;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
@keyframes bt-fade-in {
|
|
422
|
+
from { opacity: 0; transform: scale(0.98); }
|
|
423
|
+
to { opacity: 1; transform: scale(1); }
|
|
241
424
|
}
|
|
242
425
|
|
|
243
426
|
.bt-modal {
|
|
244
|
-
background: #
|
|
427
|
+
background: #0f172a;
|
|
245
428
|
color: #f8fafc;
|
|
246
429
|
border: 1px solid #334155;
|
|
247
|
-
border-radius:
|
|
248
|
-
padding:
|
|
249
|
-
width:
|
|
250
|
-
max-width:
|
|
251
|
-
box-shadow: 0
|
|
430
|
+
border-radius: 14px;
|
|
431
|
+
padding: 20px;
|
|
432
|
+
width: 450px;
|
|
433
|
+
max-width: 92vw;
|
|
434
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(255, 255, 255, 0.05);
|
|
252
435
|
display: flex;
|
|
253
436
|
flex-direction: column;
|
|
254
|
-
gap:
|
|
437
|
+
gap: 14px;
|
|
255
438
|
box-sizing: border-box;
|
|
256
439
|
}
|
|
257
440
|
|
|
@@ -262,87 +445,181 @@ export class NoteInspector {
|
|
|
262
445
|
}
|
|
263
446
|
|
|
264
447
|
.bt-modal-title {
|
|
265
|
-
font-size:
|
|
448
|
+
font-size: 14.5px;
|
|
266
449
|
font-weight: 600;
|
|
450
|
+
letter-spacing: -0.01em;
|
|
267
451
|
display: flex;
|
|
268
452
|
align-items: center;
|
|
269
|
-
gap:
|
|
453
|
+
gap: 8px;
|
|
270
454
|
color: #f8fafc;
|
|
271
455
|
}
|
|
272
456
|
|
|
273
457
|
.bt-mode-badge {
|
|
274
458
|
font-size: 10px;
|
|
275
459
|
font-weight: 700;
|
|
460
|
+
letter-spacing: 0.05em;
|
|
276
461
|
text-transform: uppercase;
|
|
277
|
-
|
|
462
|
+
padding: 2px 7px;
|
|
463
|
+
border-radius: 6px;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.bt-badge-element {
|
|
467
|
+
background: rgba(59, 130, 246, 0.15);
|
|
278
468
|
color: #60a5fa;
|
|
279
|
-
border: 1px solid
|
|
280
|
-
|
|
281
|
-
|
|
469
|
+
border: 1px solid rgba(59, 130, 246, 0.4);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.bt-badge-region {
|
|
473
|
+
background: rgba(14, 165, 233, 0.15);
|
|
474
|
+
color: #38bdf8;
|
|
475
|
+
border: 1px solid rgba(14, 165, 233, 0.4);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.bt-badge-page {
|
|
479
|
+
background: rgba(168, 85, 247, 0.15);
|
|
480
|
+
color: #c084fc;
|
|
481
|
+
border: 1px solid rgba(168, 85, 247, 0.4);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.bt-badge-status-open {
|
|
485
|
+
background: rgba(16, 185, 129, 0.15);
|
|
486
|
+
color: #34d399;
|
|
487
|
+
border: 1px solid rgba(16, 185, 129, 0.4);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.bt-badge-status-resolved {
|
|
491
|
+
background: rgba(100, 116, 139, 0.2);
|
|
492
|
+
color: #94a3b8;
|
|
493
|
+
border: 1px solid rgba(100, 116, 139, 0.4);
|
|
282
494
|
}
|
|
283
495
|
|
|
284
496
|
.bt-close-btn {
|
|
285
497
|
background: transparent;
|
|
286
498
|
border: none;
|
|
287
499
|
color: #94a3b8;
|
|
288
|
-
font-size:
|
|
500
|
+
font-size: 15px;
|
|
289
501
|
cursor: pointer;
|
|
290
|
-
padding:
|
|
291
|
-
border-radius:
|
|
502
|
+
padding: 3px 7px;
|
|
503
|
+
border-radius: 6px;
|
|
504
|
+
transition: all 0.12s ease;
|
|
505
|
+
display: flex;
|
|
506
|
+
align-items: center;
|
|
507
|
+
justify-content: center;
|
|
292
508
|
}
|
|
293
509
|
.bt-close-btn:hover {
|
|
294
|
-
background: #
|
|
510
|
+
background: #1e293b;
|
|
295
511
|
color: #ffffff;
|
|
296
512
|
}
|
|
297
513
|
|
|
298
514
|
.bt-target-pill {
|
|
299
|
-
background: #
|
|
515
|
+
background: #090d16;
|
|
300
516
|
color: #94a3b8;
|
|
301
|
-
border: 1px solid #
|
|
302
|
-
border-radius:
|
|
303
|
-
padding:
|
|
517
|
+
border: 1px solid #1e293b;
|
|
518
|
+
border-radius: 8px;
|
|
519
|
+
padding: 8px 12px;
|
|
304
520
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
305
|
-
font-size:
|
|
521
|
+
font-size: 11.5px;
|
|
522
|
+
display: flex;
|
|
523
|
+
align-items: center;
|
|
524
|
+
gap: 8px;
|
|
525
|
+
overflow: hidden;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.bt-pill-content {
|
|
306
529
|
overflow: hidden;
|
|
307
530
|
text-overflow: ellipsis;
|
|
308
531
|
white-space: nowrap;
|
|
532
|
+
color: #cbd5e1;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.bt-note-message-box {
|
|
536
|
+
background: #090d16;
|
|
537
|
+
border: 1px solid #1e293b;
|
|
538
|
+
border-radius: 8px;
|
|
539
|
+
padding: 12px 14px;
|
|
540
|
+
font-size: 13.5px;
|
|
541
|
+
line-height: 1.5;
|
|
542
|
+
color: #f1f5f9;
|
|
543
|
+
white-space: pre-wrap;
|
|
544
|
+
word-break: break-word;
|
|
545
|
+
max-height: 180px;
|
|
546
|
+
overflow-y: auto;
|
|
309
547
|
}
|
|
310
548
|
|
|
311
549
|
.bt-textarea {
|
|
312
|
-
background: #
|
|
550
|
+
background: #090d16;
|
|
313
551
|
color: #f8fafc;
|
|
314
552
|
border: 1px solid #334155;
|
|
315
|
-
border-radius:
|
|
316
|
-
padding:
|
|
317
|
-
font-size:
|
|
553
|
+
border-radius: 8px;
|
|
554
|
+
padding: 12px;
|
|
555
|
+
font-size: 13.5px;
|
|
556
|
+
line-height: 1.5;
|
|
318
557
|
resize: vertical;
|
|
319
|
-
min-height:
|
|
558
|
+
min-height: 95px;
|
|
320
559
|
outline: none;
|
|
321
560
|
box-sizing: border-box;
|
|
322
561
|
width: 100%;
|
|
323
562
|
font-family: inherit;
|
|
563
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
.bt-textarea::placeholder {
|
|
567
|
+
color: #64748b;
|
|
324
568
|
}
|
|
325
569
|
|
|
326
570
|
.bt-textarea:focus {
|
|
327
571
|
border-color: #3b82f6;
|
|
328
|
-
box-shadow: 0 0 0
|
|
572
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.25);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.bt-modal-footer {
|
|
576
|
+
display: flex;
|
|
577
|
+
justify-content: space-between;
|
|
578
|
+
align-items: center;
|
|
579
|
+
gap: 12px;
|
|
580
|
+
margin-top: 2px;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.bt-kbd-hint {
|
|
584
|
+
font-size: 11px;
|
|
585
|
+
color: #64748b;
|
|
586
|
+
display: flex;
|
|
587
|
+
align-items: center;
|
|
588
|
+
gap: 4px;
|
|
589
|
+
user-select: none;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.bt-kbd-hint kbd {
|
|
593
|
+
background: #1e293b;
|
|
594
|
+
color: #94a3b8;
|
|
595
|
+
border: 1px solid #334155;
|
|
596
|
+
border-radius: 4px;
|
|
597
|
+
padding: 1px 5px;
|
|
598
|
+
font-family: inherit;
|
|
599
|
+
font-size: 10px;
|
|
600
|
+
font-weight: 600;
|
|
329
601
|
}
|
|
330
602
|
|
|
331
603
|
.bt-modal-actions {
|
|
332
604
|
display: flex;
|
|
333
605
|
justify-content: flex-end;
|
|
606
|
+
align-items: center;
|
|
334
607
|
gap: 8px;
|
|
335
608
|
}
|
|
336
609
|
|
|
337
610
|
.bt-btn {
|
|
338
|
-
padding: 8px
|
|
339
|
-
border-radius:
|
|
340
|
-
font-size:
|
|
611
|
+
padding: 8px 16px;
|
|
612
|
+
border-radius: 7px;
|
|
613
|
+
font-size: 12.5px;
|
|
341
614
|
font-weight: 600;
|
|
342
615
|
cursor: pointer;
|
|
343
616
|
border: 1px solid transparent;
|
|
344
|
-
transition: all 0.
|
|
617
|
+
transition: all 0.15s ease;
|
|
345
618
|
font-family: inherit;
|
|
619
|
+
display: inline-flex;
|
|
620
|
+
align-items: center;
|
|
621
|
+
justify-content: center;
|
|
622
|
+
gap: 6px;
|
|
346
623
|
}
|
|
347
624
|
|
|
348
625
|
.bt-btn-cancel {
|
|
@@ -350,31 +627,54 @@ export class NoteInspector {
|
|
|
350
627
|
color: #94a3b8;
|
|
351
628
|
}
|
|
352
629
|
.bt-btn-cancel:hover {
|
|
353
|
-
background: #
|
|
630
|
+
background: #1e293b;
|
|
354
631
|
color: #f8fafc;
|
|
355
632
|
}
|
|
356
633
|
|
|
357
634
|
.bt-btn-save {
|
|
358
635
|
background: #2563eb;
|
|
359
636
|
color: #ffffff;
|
|
637
|
+
box-shadow: 0 2px 6px rgba(37, 99, 235, 0.35);
|
|
360
638
|
}
|
|
361
639
|
.bt-btn-save:hover {
|
|
362
640
|
background: #1d4ed8;
|
|
641
|
+
box-shadow: 0 4px 10px rgba(37, 99, 235, 0.45);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.bt-btn-resolve {
|
|
645
|
+
background: rgba(16, 185, 129, 0.15);
|
|
646
|
+
color: #6ee7b7;
|
|
647
|
+
border: 1px solid rgba(16, 185, 129, 0.35);
|
|
648
|
+
}
|
|
649
|
+
.bt-btn-resolve:hover {
|
|
650
|
+
background: rgba(16, 185, 129, 0.3);
|
|
651
|
+
color: #ffffff;
|
|
363
652
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
653
|
+
|
|
654
|
+
.bt-btn-delete {
|
|
655
|
+
background: rgba(239, 68, 68, 0.15);
|
|
656
|
+
color: #fca5a5;
|
|
657
|
+
border: 1px solid rgba(239, 68, 68, 0.35);
|
|
658
|
+
}
|
|
659
|
+
.bt-btn-delete:hover {
|
|
660
|
+
background: rgba(239, 68, 68, 0.3);
|
|
661
|
+
color: #ffffff;
|
|
367
662
|
}
|
|
368
663
|
`;
|
|
369
664
|
|
|
370
665
|
this.shadowRoot.appendChild(style);
|
|
371
666
|
|
|
372
|
-
// Element Highlight
|
|
667
|
+
// Element Highlight Overlay
|
|
373
668
|
this.highlightOverlay = document.createElement('div');
|
|
374
669
|
this.highlightOverlay.className = 'bt-highlight';
|
|
375
670
|
this.highlightOverlay.style.display = 'none';
|
|
376
671
|
this.shadowRoot.appendChild(this.highlightOverlay);
|
|
377
672
|
|
|
673
|
+
// Markers container layer
|
|
674
|
+
this.markersContainer = document.createElement('div');
|
|
675
|
+
this.markersContainer.className = 'bt-markers-layer';
|
|
676
|
+
this.shadowRoot.appendChild(this.markersContainer);
|
|
677
|
+
|
|
378
678
|
// Floating Toolbar
|
|
379
679
|
if (this.options.showToolbar) {
|
|
380
680
|
this.createToolbar();
|
|
@@ -406,24 +706,39 @@ export class NoteInspector {
|
|
|
406
706
|
<button class="bt-toolbar-btn" id="bt-mode-page" title="Leave full-page note">
|
|
407
707
|
<span>📄</span> Page
|
|
408
708
|
</button>
|
|
709
|
+
<div class="bt-toolbar-divider"></div>
|
|
710
|
+
<button class="bt-toolbar-btn active" id="bt-toggle-notes" title="Toggle visible note markers on screen">
|
|
711
|
+
<span>📌</span> Notes <span class="bt-count-pill" id="bt-notes-count">0</span>
|
|
712
|
+
</button>
|
|
409
713
|
`;
|
|
410
714
|
|
|
411
715
|
const btnElement = this.toolbarElement.querySelector('#bt-mode-element') as HTMLButtonElement;
|
|
412
716
|
const btnRegion = this.toolbarElement.querySelector('#bt-mode-region') as HTMLButtonElement;
|
|
413
717
|
const btnPage = this.toolbarElement.querySelector('#bt-mode-page') as HTMLButtonElement;
|
|
718
|
+
const btnToggleNotes = this.toolbarElement.querySelector('#bt-toggle-notes') as HTMLButtonElement;
|
|
414
719
|
|
|
415
|
-
btnElement.onclick = () => {
|
|
720
|
+
btnElement.onclick = (e: MouseEvent) => {
|
|
721
|
+
e.stopPropagation();
|
|
416
722
|
this.setMode(this.activeMode === 'element' ? 'idle' : 'element');
|
|
417
723
|
};
|
|
418
724
|
|
|
419
|
-
btnRegion.onclick = () => {
|
|
725
|
+
btnRegion.onclick = (e: MouseEvent) => {
|
|
726
|
+
e.stopPropagation();
|
|
420
727
|
this.setMode(this.activeMode === 'region' ? 'idle' : 'region');
|
|
421
728
|
};
|
|
422
729
|
|
|
423
|
-
btnPage.onclick = () => {
|
|
730
|
+
btnPage.onclick = (e: MouseEvent) => {
|
|
731
|
+
e.stopPropagation();
|
|
424
732
|
this.setMode('page');
|
|
425
733
|
};
|
|
426
734
|
|
|
735
|
+
btnToggleNotes.onclick = (e: MouseEvent) => {
|
|
736
|
+
e.stopPropagation();
|
|
737
|
+
this.showMarkers = !this.showMarkers;
|
|
738
|
+
btnToggleNotes.classList.toggle('active', this.showMarkers);
|
|
739
|
+
this.renderMarkers();
|
|
740
|
+
};
|
|
741
|
+
|
|
427
742
|
this.shadowRoot.appendChild(this.toolbarElement);
|
|
428
743
|
}
|
|
429
744
|
|
|
@@ -439,10 +754,225 @@ export class NoteInspector {
|
|
|
439
754
|
btnPage?.classList.toggle('active', this.activeMode === 'page');
|
|
440
755
|
}
|
|
441
756
|
|
|
757
|
+
private updateToolbarCount(): void {
|
|
758
|
+
if (!this.toolbarElement) return;
|
|
759
|
+
const countEl = this.toolbarElement.querySelector('#bt-notes-count');
|
|
760
|
+
if (countEl) {
|
|
761
|
+
const openCount = this.savedNotes.filter((n) => n.status === 'OPEN').length;
|
|
762
|
+
countEl.textContent = String(openCount);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
public renderMarkers(): void {
|
|
767
|
+
const root = this.ensureContainer();
|
|
768
|
+
if (!root || !this.markersContainer) return;
|
|
769
|
+
|
|
770
|
+
this.markersContainer.innerHTML = '';
|
|
771
|
+
if (!this.showMarkers) return;
|
|
772
|
+
|
|
773
|
+
const currentPath = window.location.pathname;
|
|
774
|
+
// Filter open notes on this route or global
|
|
775
|
+
const activeNotes = this.savedNotes.filter(
|
|
776
|
+
(n) => n.status === 'OPEN' && (n.route === currentPath || !n.route || n.route === '/' || window.location.href.includes(n.route))
|
|
777
|
+
);
|
|
778
|
+
|
|
779
|
+
const pageNotes: VisualNote[] = [];
|
|
780
|
+
|
|
781
|
+
activeNotes.forEach((note, index) => {
|
|
782
|
+
if (note.type === 'page') {
|
|
783
|
+
pageNotes.push(note);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (note.type === 'region' && note.region) {
|
|
788
|
+
// Region box
|
|
789
|
+
const regBox = document.createElement('div');
|
|
790
|
+
regBox.className = 'bt-region-marker-box';
|
|
791
|
+
regBox.style.left = `${note.region.x}px`;
|
|
792
|
+
regBox.style.top = `${note.region.y}px`;
|
|
793
|
+
regBox.style.width = `${note.region.width}px`;
|
|
794
|
+
regBox.style.height = `${note.region.height}px`;
|
|
795
|
+
this.markersContainer!.appendChild(regBox);
|
|
796
|
+
|
|
797
|
+
// Pin on top-left of region
|
|
798
|
+
const pin = document.createElement('div');
|
|
799
|
+
pin.className = 'bt-note-marker';
|
|
800
|
+
pin.style.left = `${Math.max(4, note.region.x - 12)}px`;
|
|
801
|
+
pin.style.top = `${Math.max(4, note.region.y - 12)}px`;
|
|
802
|
+
pin.title = note.message;
|
|
803
|
+
pin.innerHTML = `<span>📐</span> <span>#${index + 1}</span>`;
|
|
804
|
+
pin.onclick = (e) => {
|
|
805
|
+
e.stopPropagation();
|
|
806
|
+
this.openNoteCard(note);
|
|
807
|
+
};
|
|
808
|
+
this.markersContainer!.appendChild(pin);
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Element note
|
|
813
|
+
let targetEl: HTMLElement | null = null;
|
|
814
|
+
if (note.target?.selector) {
|
|
815
|
+
try {
|
|
816
|
+
targetEl = document.querySelector(note.target.selector) as HTMLElement | null;
|
|
817
|
+
} catch {}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const rect = targetEl ? targetEl.getBoundingClientRect() : note.target?.boundingRect;
|
|
821
|
+
if (rect) {
|
|
822
|
+
const pin = document.createElement('div');
|
|
823
|
+
pin.className = 'bt-note-marker';
|
|
824
|
+
pin.setAttribute('data-note-id', note.id);
|
|
825
|
+
pin.title = note.message;
|
|
826
|
+
pin.style.left = `${Math.max(4, rect.left - 10)}px`;
|
|
827
|
+
pin.style.top = `${Math.max(4, rect.top - 12)}px`;
|
|
828
|
+
pin.innerHTML = `<span>📝</span> <span>#${index + 1}</span>`;
|
|
829
|
+
|
|
830
|
+
pin.onclick = (e) => {
|
|
831
|
+
e.stopPropagation();
|
|
832
|
+
this.openNoteCard(note);
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
if (targetEl) {
|
|
836
|
+
pin.onmouseenter = () => this.updateHighlight(targetEl!);
|
|
837
|
+
pin.onmouseleave = () => this.hideHighlight();
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
this.markersContainer!.appendChild(pin);
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
// Page notes docked in top-right
|
|
845
|
+
if (pageNotes.length > 0) {
|
|
846
|
+
const pageDock = document.createElement('div');
|
|
847
|
+
pageDock.className = 'bt-page-notes-dock';
|
|
848
|
+
pageNotes.forEach((pNote, idx) => {
|
|
849
|
+
const pill = document.createElement('div');
|
|
850
|
+
pill.className = 'bt-page-note-pill';
|
|
851
|
+
pill.innerHTML = `<span>📄</span> <span>Page Note (${truncate(pNote.message, 25)})</span>`;
|
|
852
|
+
pill.onclick = (e) => {
|
|
853
|
+
e.stopPropagation();
|
|
854
|
+
this.openNoteCard(pNote);
|
|
855
|
+
};
|
|
856
|
+
pageDock.appendChild(pill);
|
|
857
|
+
});
|
|
858
|
+
this.markersContainer.appendChild(pageDock);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
private updateMarkerPositions(): void {
|
|
863
|
+
if (!this.showMarkers || !this.markersContainer) return;
|
|
864
|
+
this.renderMarkers();
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
public openNoteCard(note: VisualNote): void {
|
|
868
|
+
const root = this.ensureContainer();
|
|
869
|
+
if (!root) return;
|
|
870
|
+
|
|
871
|
+
if (this.cardOverlay && this.cardOverlay.parentElement) {
|
|
872
|
+
this.cardOverlay.parentElement.removeChild(this.cardOverlay);
|
|
873
|
+
this.cardOverlay = null;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const backdrop = document.createElement('div');
|
|
877
|
+
backdrop.className = 'bt-modal-backdrop';
|
|
878
|
+
this.cardOverlay = backdrop;
|
|
879
|
+
|
|
880
|
+
const badgeClass = `bt-badge-${note.type.toLowerCase()}`;
|
|
881
|
+
const statusClass = note.status === 'OPEN' ? 'bt-badge-status-open' : 'bt-badge-status-resolved';
|
|
882
|
+
const icon = note.type === 'region' ? '📐' : note.type === 'page' ? '📄' : '🎯';
|
|
883
|
+
const contextText =
|
|
884
|
+
note.type === 'region' && note.region
|
|
885
|
+
? `Region: ${note.region.width} × ${note.region.height} px · Route: ${note.route}`
|
|
886
|
+
: note.type === 'page'
|
|
887
|
+
? `Page Note · Route: ${note.route}`
|
|
888
|
+
: `${note.target?.selector || 'Element'} (${note.target?.boundingRect?.width || 0}×${note.target?.boundingRect?.height || 0}px) · ${note.route}`;
|
|
889
|
+
|
|
890
|
+
const formattedDate = new Date(note.createdAt).toLocaleString();
|
|
891
|
+
|
|
892
|
+
backdrop.innerHTML = `
|
|
893
|
+
<div class="bt-modal">
|
|
894
|
+
<div class="bt-modal-header">
|
|
895
|
+
<div class="bt-modal-title">
|
|
896
|
+
<span>${icon} Visual Note Details</span>
|
|
897
|
+
<span class="bt-mode-badge ${badgeClass}">${note.type.toUpperCase()}</span>
|
|
898
|
+
<span class="bt-mode-badge ${statusClass}">${note.status}</span>
|
|
899
|
+
</div>
|
|
900
|
+
<button class="bt-close-btn" id="btn-card-close" title="Close (Esc)">✕</button>
|
|
901
|
+
</div>
|
|
902
|
+
|
|
903
|
+
<div class="bt-target-pill" title="${contextText}">
|
|
904
|
+
<span>🏷️</span>
|
|
905
|
+
<span class="bt-pill-content">${contextText}</span>
|
|
906
|
+
</div>
|
|
907
|
+
|
|
908
|
+
<div class="bt-note-message-box">${note.message}</div>
|
|
909
|
+
|
|
910
|
+
<div style="font-size: 11px; color: #64748b; display: flex; justify-content: space-between; padding: 0 2px;">
|
|
911
|
+
<span>Created: ${formattedDate}</span>
|
|
912
|
+
<span>ID: <code style="font-family: monospace; color: #94a3b8;">${note.id}</code></span>
|
|
913
|
+
</div>
|
|
914
|
+
|
|
915
|
+
<div class="bt-modal-footer">
|
|
916
|
+
<button class="bt-btn bt-btn-delete" id="btn-card-delete" title="Delete this note">
|
|
917
|
+
<span>🗑️</span> Delete
|
|
918
|
+
</button>
|
|
919
|
+
<div class="bt-modal-actions">
|
|
920
|
+
<button class="bt-btn bt-btn-cancel" id="btn-card-dismiss">Close</button>
|
|
921
|
+
<button class="bt-btn ${note.status === 'OPEN' ? 'bt-btn-resolve' : 'bt-btn-save'}" id="btn-card-resolve">
|
|
922
|
+
${note.status === 'OPEN' ? '<span>✅</span> Resolve Note' : '<span>↺</span> Reopen'}
|
|
923
|
+
</button>
|
|
924
|
+
</div>
|
|
925
|
+
</div>
|
|
926
|
+
</div>
|
|
927
|
+
`;
|
|
928
|
+
|
|
929
|
+
const btnClose = backdrop.querySelector('#btn-card-close') as HTMLButtonElement;
|
|
930
|
+
const btnDismiss = backdrop.querySelector('#btn-card-dismiss') as HTMLButtonElement;
|
|
931
|
+
const btnResolve = backdrop.querySelector('#btn-card-resolve') as HTMLButtonElement;
|
|
932
|
+
const btnDelete = backdrop.querySelector('#btn-card-delete') as HTMLButtonElement;
|
|
933
|
+
|
|
934
|
+
const closeCard = () => {
|
|
935
|
+
if (this.cardOverlay) {
|
|
936
|
+
root.removeChild(this.cardOverlay);
|
|
937
|
+
this.cardOverlay = null;
|
|
938
|
+
this.hideHighlight();
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
btnClose.onclick = closeCard;
|
|
943
|
+
btnDismiss.onclick = closeCard;
|
|
944
|
+
backdrop.onclick = (e) => {
|
|
945
|
+
if (e.target === backdrop) closeCard();
|
|
946
|
+
};
|
|
947
|
+
|
|
948
|
+
btnResolve.onclick = () => {
|
|
949
|
+
if (note.status === 'OPEN') {
|
|
950
|
+
this.transport.send({ type: 'resolve_note', noteId: note.id });
|
|
951
|
+
} else {
|
|
952
|
+
this.transport.send({ type: 'reopen_note', noteId: note.id });
|
|
953
|
+
}
|
|
954
|
+
closeCard();
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
btnDelete.onclick = () => {
|
|
958
|
+
this.transport.send({ type: 'delete_note', noteId: note.id });
|
|
959
|
+
closeCard();
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
root.appendChild(backdrop);
|
|
963
|
+
}
|
|
964
|
+
|
|
442
965
|
private setupListeners(): void {
|
|
443
966
|
// 1. Mouse move: hover highlight in element mode or with Alt key
|
|
444
967
|
const onMouseMove = (e: MouseEvent) => {
|
|
445
|
-
if (this.modalOverlay || this.isDraggingRegion || this.activeMode === 'region') return;
|
|
968
|
+
if (this.modalOverlay || this.cardOverlay || this.isDraggingRegion || this.activeMode === 'region') return;
|
|
969
|
+
|
|
970
|
+
// Don't highlight elements if mouse is over our own inspector UI
|
|
971
|
+
const path = e.composedPath ? e.composedPath() : [];
|
|
972
|
+
if (this.container && path.includes(this.container)) {
|
|
973
|
+
this.hideHighlight();
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
446
976
|
|
|
447
977
|
if (e.altKey || this.activeMode === 'element') {
|
|
448
978
|
const target = document.elementFromPoint(e.clientX, e.clientY) as HTMLElement | null;
|
|
@@ -460,7 +990,15 @@ export class NoteInspector {
|
|
|
460
990
|
|
|
461
991
|
// 2. Click: Alt+Click or Element mode click selects element and opens editor
|
|
462
992
|
const onClick = (e: MouseEvent) => {
|
|
463
|
-
if (this.modalOverlay || this.
|
|
993
|
+
if (this.modalOverlay || this.cardOverlay) return;
|
|
994
|
+
|
|
995
|
+
// If clicking inside inspector toolbar or container, do NOT intercept
|
|
996
|
+
const path = e.composedPath ? e.composedPath() : [];
|
|
997
|
+
if (this.container && path.includes(this.container)) {
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
if (this.activeMode === 'region') return;
|
|
464
1002
|
|
|
465
1003
|
if (e.altKey || this.activeMode === 'element') {
|
|
466
1004
|
e.preventDefault();
|
|
@@ -477,21 +1015,45 @@ export class NoteInspector {
|
|
|
477
1015
|
}
|
|
478
1016
|
};
|
|
479
1017
|
|
|
480
|
-
// 3.
|
|
1018
|
+
// 3. Key handling: Escape cancels active modes, Alt release hides highlight
|
|
1019
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
1020
|
+
if (e.key === 'Escape') {
|
|
1021
|
+
if (this.cardOverlay && this.cardOverlay.parentElement && this.shadowRoot) {
|
|
1022
|
+
this.shadowRoot.removeChild(this.cardOverlay);
|
|
1023
|
+
this.cardOverlay = null;
|
|
1024
|
+
} else if (this.activeMode === 'region' || this.activeMode === 'element') {
|
|
1025
|
+
this.setMode('idle');
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
|
|
481
1030
|
const onKeyUp = (e: KeyboardEvent) => {
|
|
482
|
-
if (e.key === 'Alt' && !this.modalOverlay && this.activeMode !== 'element') {
|
|
1031
|
+
if (e.key === 'Alt' && !this.modalOverlay && !this.cardOverlay && this.activeMode !== 'element') {
|
|
483
1032
|
this.hideHighlight();
|
|
484
1033
|
}
|
|
485
1034
|
};
|
|
486
1035
|
|
|
1036
|
+
// 4. Scroll & resize: reposition markers accurately
|
|
1037
|
+
const onScrollOrResize = () => {
|
|
1038
|
+
requestAnimationFrame(() => {
|
|
1039
|
+
this.updateMarkerPositions();
|
|
1040
|
+
});
|
|
1041
|
+
};
|
|
1042
|
+
|
|
487
1043
|
window.addEventListener('mousemove', onMouseMove, { capture: true, passive: true });
|
|
488
1044
|
window.addEventListener('click', onClick, { capture: true });
|
|
1045
|
+
window.addEventListener('keydown', onKeyDown, { capture: true });
|
|
489
1046
|
window.addEventListener('keyup', onKeyUp, { capture: true });
|
|
1047
|
+
window.addEventListener('scroll', onScrollOrResize, { capture: true, passive: true });
|
|
1048
|
+
window.addEventListener('resize', onScrollOrResize, { passive: true });
|
|
490
1049
|
|
|
491
1050
|
this.cleanups.push(() => {
|
|
492
1051
|
window.removeEventListener('mousemove', onMouseMove, { capture: true });
|
|
493
1052
|
window.removeEventListener('click', onClick, { capture: true });
|
|
1053
|
+
window.removeEventListener('keydown', onKeyDown, { capture: true });
|
|
494
1054
|
window.removeEventListener('keyup', onKeyUp, { capture: true });
|
|
1055
|
+
window.removeEventListener('scroll', onScrollOrResize, { capture: true });
|
|
1056
|
+
window.removeEventListener('resize', onScrollOrResize);
|
|
495
1057
|
});
|
|
496
1058
|
}
|
|
497
1059
|
|
|
@@ -508,8 +1070,34 @@ export class NoteInspector {
|
|
|
508
1070
|
this.regionBox.style.display = 'none';
|
|
509
1071
|
this.regionOverlay.appendChild(this.regionBox);
|
|
510
1072
|
|
|
1073
|
+
// Top helper & cancel banner
|
|
1074
|
+
this.regionBanner = document.createElement('div');
|
|
1075
|
+
this.regionBanner.className = 'bt-region-banner';
|
|
1076
|
+
this.regionBanner.innerHTML = `
|
|
1077
|
+
<span>📐 Drag rectangle on screen to select region</span>
|
|
1078
|
+
<button class="bt-region-cancel-btn" id="bt-region-cancel">✕ Cancel (Esc)</button>
|
|
1079
|
+
`;
|
|
1080
|
+
|
|
1081
|
+
const btnCancel = this.regionBanner.querySelector('#bt-region-cancel') as HTMLButtonElement;
|
|
1082
|
+
btnCancel.onclick = (e: MouseEvent) => {
|
|
1083
|
+
e.stopPropagation();
|
|
1084
|
+
this.setMode('idle');
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
this.regionOverlay.appendChild(this.regionBanner);
|
|
1088
|
+
|
|
1089
|
+
// Right-click cancels region mode
|
|
1090
|
+
this.regionOverlay.oncontextmenu = (e: MouseEvent) => {
|
|
1091
|
+
e.preventDefault();
|
|
1092
|
+
this.setMode('idle');
|
|
1093
|
+
};
|
|
1094
|
+
|
|
511
1095
|
// Drag events
|
|
512
1096
|
this.regionOverlay.onmousedown = (e: MouseEvent) => {
|
|
1097
|
+
const path = e.composedPath ? e.composedPath() : [];
|
|
1098
|
+
if (this.regionBanner && path.includes(this.regionBanner)) return;
|
|
1099
|
+
if (this.toolbarElement && path.includes(this.toolbarElement)) return;
|
|
1100
|
+
|
|
513
1101
|
this.isDraggingRegion = true;
|
|
514
1102
|
this.dragStartX = e.clientX;
|
|
515
1103
|
this.dragStartY = e.clientY;
|
|
@@ -558,7 +1146,7 @@ export class NoteInspector {
|
|
|
558
1146
|
const width = Math.abs(currentX - this.dragStartX);
|
|
559
1147
|
const height = Math.abs(currentY - this.dragStartY);
|
|
560
1148
|
|
|
561
|
-
if (width >
|
|
1149
|
+
if (width > 15 && height > 15) {
|
|
562
1150
|
this.selectedRegion = {
|
|
563
1151
|
x: Math.round(left),
|
|
564
1152
|
y: Math.round(top),
|
|
@@ -575,12 +1163,18 @@ export class NoteInspector {
|
|
|
575
1163
|
};
|
|
576
1164
|
}
|
|
577
1165
|
|
|
578
|
-
|
|
1166
|
+
// Always ensure regionOverlay is mounted before toolbar so toolbar is interactable
|
|
1167
|
+
if (this.toolbarElement && this.toolbarElement.parentNode === root) {
|
|
1168
|
+
root.insertBefore(this.regionOverlay, this.toolbarElement);
|
|
1169
|
+
} else {
|
|
1170
|
+
root.appendChild(this.regionOverlay);
|
|
1171
|
+
}
|
|
579
1172
|
}
|
|
580
1173
|
|
|
581
1174
|
private hideRegionOverlay(): void {
|
|
582
|
-
|
|
583
|
-
|
|
1175
|
+
this.isDraggingRegion = false;
|
|
1176
|
+
if (this.regionOverlay && this.regionOverlay.parentNode) {
|
|
1177
|
+
this.regionOverlay.parentNode.removeChild(this.regionOverlay);
|
|
584
1178
|
}
|
|
585
1179
|
if (this.regionBox) {
|
|
586
1180
|
this.regionBox.style.display = 'none';
|
|
@@ -627,7 +1221,7 @@ export class NoteInspector {
|
|
|
627
1221
|
}
|
|
628
1222
|
|
|
629
1223
|
this.renderNoteModal({
|
|
630
|
-
title: '
|
|
1224
|
+
title: 'Add Visual Note',
|
|
631
1225
|
modeBadge: noteType.toUpperCase(),
|
|
632
1226
|
pillText:
|
|
633
1227
|
noteType === 'page'
|
|
@@ -641,7 +1235,7 @@ export class NoteInspector {
|
|
|
641
1235
|
|
|
642
1236
|
public openRegionNoteEditor(region: RegionContext): void {
|
|
643
1237
|
this.renderNoteModal({
|
|
644
|
-
title: '
|
|
1238
|
+
title: 'Add Region Note',
|
|
645
1239
|
modeBadge: 'REGION',
|
|
646
1240
|
pillText: `Selected Area: x:${region.x}, y:${region.y} (${region.width} × ${region.height} px)`,
|
|
647
1241
|
onSave: async (message) => {
|
|
@@ -669,22 +1263,32 @@ export class NoteInspector {
|
|
|
669
1263
|
backdrop.className = 'bt-modal-backdrop';
|
|
670
1264
|
this.modalOverlay = backdrop;
|
|
671
1265
|
|
|
1266
|
+
const badgeClass = `bt-badge-${options.modeBadge.toLowerCase()}`;
|
|
1267
|
+
const icon = options.modeBadge === 'REGION' ? '📐' : options.modeBadge === 'PAGE' ? '📄' : '🎯';
|
|
1268
|
+
const isMac = typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
1269
|
+
|
|
672
1270
|
backdrop.innerHTML = `
|
|
673
1271
|
<div class="bt-modal">
|
|
674
1272
|
<div class="bt-modal-header">
|
|
675
1273
|
<div class="bt-modal-title">
|
|
676
|
-
<span
|
|
677
|
-
<span class="bt-mode-badge">${options.modeBadge}</span>
|
|
1274
|
+
<span>📝 ${options.title}</span>
|
|
1275
|
+
<span class="bt-mode-badge ${badgeClass}">${options.modeBadge}</span>
|
|
678
1276
|
</div>
|
|
679
|
-
<button class="bt-close-btn" id="btn-close" title="Close">✕</button>
|
|
1277
|
+
<button class="bt-close-btn" id="btn-close" title="Close (Esc)">✕</button>
|
|
680
1278
|
</div>
|
|
681
1279
|
<div class="bt-target-pill" title="${options.pillText}">
|
|
682
|
-
|
|
1280
|
+
<span>${icon}</span>
|
|
1281
|
+
<span class="bt-pill-content">${options.pillText}</span>
|
|
683
1282
|
</div>
|
|
684
1283
|
<textarea class="bt-textarea" placeholder="Describe the layout issue, styling bug, or note for AI agent..." autofocus></textarea>
|
|
685
|
-
<div class="bt-modal-
|
|
686
|
-
<
|
|
687
|
-
|
|
1284
|
+
<div class="bt-modal-footer">
|
|
1285
|
+
<div class="bt-kbd-hint">
|
|
1286
|
+
<kbd>${isMac ? '⌘' : 'Ctrl'}+Enter</kbd> save · <kbd>Esc</kbd> cancel
|
|
1287
|
+
</div>
|
|
1288
|
+
<div class="bt-modal-actions">
|
|
1289
|
+
<button class="bt-btn bt-btn-cancel" id="btn-cancel">Cancel</button>
|
|
1290
|
+
<button class="bt-btn bt-btn-save" id="btn-save">Save Note</button>
|
|
1291
|
+
</div>
|
|
688
1292
|
</div>
|
|
689
1293
|
</div>
|
|
690
1294
|
`;
|
|
@@ -939,5 +1543,11 @@ export class NoteInspector {
|
|
|
939
1543
|
this.container = null;
|
|
940
1544
|
this.shadowRoot = null;
|
|
941
1545
|
this.toolbarElement = null;
|
|
1546
|
+
this.regionOverlay = null;
|
|
1547
|
+
this.regionBox = null;
|
|
1548
|
+
this.regionBanner = null;
|
|
1549
|
+
this.markersContainer = null;
|
|
1550
|
+
this.modalOverlay = null;
|
|
1551
|
+
this.cardOverlay = null;
|
|
942
1552
|
}
|
|
943
1553
|
}
|