@coframe-gtm/annotations 1.0.4 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -8
- package/src/README.md +4 -3
- package/src/api.ts +12 -0
- package/src/bundle.ts +11 -5
- package/src/inject/build.ts +0 -2
- package/src/inject/bundle-source.generated.ts +1 -1
- package/src/output.ts +1 -1
- package/src/picker.ts +23 -5
- package/src/signal.ts +56 -0
- package/src/store.ts +36 -2
- package/src/ui/Avatar.ts +62 -0
- package/src/ui/Composer.ts +206 -0
- package/src/ui/Cursors.ts +48 -0
- package/src/ui/Pins.ts +138 -0
- package/src/ui/ThreadPanel.ts +132 -0
- package/src/ui/Toolbar.ts +270 -0
- package/src/ui/brand.ts +16 -0
- package/src/ui/dom.ts +95 -0
- package/src/ui/helpers.ts +89 -0
- package/src/ui/overlay.ts +163 -0
- package/src/ui/styles.ts +212 -34
- package/src/ui/App.tsx +0 -69
- package/src/ui/Composer.tsx +0 -138
- package/src/ui/Cursors.tsx +0 -50
- package/src/ui/Pins.tsx +0 -58
- package/src/ui/ThreadPanel.tsx +0 -124
- package/src/ui/Toolbar.tsx +0 -116
package/src/ui/styles.ts
CHANGED
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export const STYLES = `
|
|
9
|
+
/* Box-sizing reset — the host page's reset is scoped to its own tree and
|
|
10
|
+
doesn't reach the overlay, so width:100% controls + padding would
|
|
11
|
+
otherwise overflow their popups. Keep everything border-box. */
|
|
12
|
+
.cf-overlay, .cf-overlay *, .cf-overlay *::before, .cf-overlay *::after {
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
}
|
|
9
15
|
.cf-overlay {
|
|
10
16
|
position: fixed;
|
|
11
17
|
inset: 0;
|
|
@@ -17,12 +23,18 @@ export const STYLES = `
|
|
|
17
23
|
--cf-border: rgba(255, 255, 255, 0.1);
|
|
18
24
|
--cf-muted: rgba(255, 255, 255, 0.55);
|
|
19
25
|
--cf-accent: #a78bfa;
|
|
26
|
+
/* Liquid-glass fill for popups/panels — low alpha so the backdrop blur
|
|
27
|
+
actually reads as frosted glass instead of a solid black box. */
|
|
28
|
+
--cf-glass: rgba(24, 24, 34, 0.55);
|
|
29
|
+
--cf-glass-border: rgba(255, 255, 255, 0.14);
|
|
20
30
|
}
|
|
21
31
|
.cf-overlay[data-theme="light"] {
|
|
22
32
|
color: #18181b;
|
|
23
33
|
--cf-bg: rgba(252, 252, 253, 0.97);
|
|
24
34
|
--cf-border: rgba(0, 0, 0, 0.1);
|
|
25
35
|
--cf-muted: rgba(0, 0, 0, 0.5);
|
|
36
|
+
--cf-glass: rgba(252, 252, 253, 0.60);
|
|
37
|
+
--cf-glass-border: rgba(0, 0, 0, 0.10);
|
|
26
38
|
}
|
|
27
39
|
|
|
28
40
|
/* Hover highlight + multi-select boxes */
|
|
@@ -77,8 +89,29 @@ export const STYLES = `
|
|
|
77
89
|
.cf-cursor-agent .cf-cursor-label { color: #0b0b0f; }
|
|
78
90
|
|
|
79
91
|
/* Pins */
|
|
80
|
-
|
|
92
|
+
/* Outline box hugging the annotated element (so it's obvious what the
|
|
93
|
+
annotation refers to). Colour-coded per author via --cf-pin. */
|
|
94
|
+
/* A multi-element annotation renders several .cf-anno outlines wrapped in
|
|
95
|
+
one group — display:contents so the wrapper itself adds no layout box and
|
|
96
|
+
the fixed-positioned outlines anchor straight to the viewport. */
|
|
97
|
+
.cf-anno-group { display: contents; }
|
|
98
|
+
.cf-anno {
|
|
81
99
|
position: fixed;
|
|
100
|
+
pointer-events: none;
|
|
101
|
+
border: 1.5px solid var(--cf-pin, #a78bfa);
|
|
102
|
+
border-radius: 7px;
|
|
103
|
+
background: color-mix(in srgb, var(--cf-pin, #a78bfa) 9%, transparent);
|
|
104
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--cf-pin, #a78bfa) 15%, transparent);
|
|
105
|
+
z-index: 2;
|
|
106
|
+
}
|
|
107
|
+
/* Entrance animation plays only on first render (.cf-fresh), never on the
|
|
108
|
+
scroll/resize re-renders — otherwise the box flickers while scrolling. */
|
|
109
|
+
.cf-anno.cf-fresh { animation: cf-rise .18s cubic-bezier(.16,1,.3,1) both; }
|
|
110
|
+
.cf-anno.agent { border-style: dashed; }
|
|
111
|
+
.cf-anno.resolved { opacity: 0.4; }
|
|
112
|
+
.cf-pin {
|
|
113
|
+
position: absolute;
|
|
114
|
+
top: -11px; left: -11px;
|
|
82
115
|
width: 22px; height: 22px;
|
|
83
116
|
border-radius: 50% 50% 50% 2px;
|
|
84
117
|
background: var(--cf-pin, #a78bfa);
|
|
@@ -92,21 +125,40 @@ export const STYLES = `
|
|
|
92
125
|
z-index: 3;
|
|
93
126
|
transition: transform .12s cubic-bezier(.34,1.56,.64,1);
|
|
94
127
|
padding: 0;
|
|
95
|
-
/* Pop in when a pin first renders so new annotations are obvious. */
|
|
96
|
-
animation: cf-pin-pop .32s cubic-bezier(.34,1.56,.64,1) both;
|
|
97
128
|
}
|
|
129
|
+
/* Pop in only when a pin first renders (.cf-fresh) — not on the scroll
|
|
130
|
+
re-renders, which would otherwise replay the pop on every tick. */
|
|
131
|
+
.cf-pin.cf-fresh { animation: cf-pin-pop .32s cubic-bezier(.34,1.56,.64,1) both; }
|
|
98
132
|
.cf-pin:hover { transform: scale(1.2); }
|
|
99
133
|
.cf-pin.resolved { opacity: 0.45; }
|
|
100
|
-
/*
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
134
|
+
/* Human vs agent reads from the box outline (solid vs dashed) + the pin
|
|
135
|
+
glyph (number vs initial) + colour — no extra per-pin chrome. */
|
|
136
|
+
|
|
137
|
+
/* Hover-to-preview card — shown next to a pin on mouseenter. */
|
|
138
|
+
.cf-pin-preview {
|
|
139
|
+
position: absolute;
|
|
140
|
+
top: 26px; left: 0;
|
|
141
|
+
max-width: 240px;
|
|
142
|
+
width: max-content;
|
|
108
143
|
pointer-events: none;
|
|
109
|
-
|
|
144
|
+
background: var(--cf-glass);
|
|
145
|
+
border: 1px solid var(--cf-glass-border);
|
|
146
|
+
border-radius: 9px;
|
|
147
|
+
padding: 7px 10px;
|
|
148
|
+
font: 500 12px -apple-system, system-ui;
|
|
149
|
+
line-height: 1.4;
|
|
150
|
+
color: #f4f4f5;
|
|
151
|
+
white-space: normal;
|
|
152
|
+
overflow-wrap: anywhere;
|
|
153
|
+
-webkit-backdrop-filter: blur(24px) saturate(180%);
|
|
154
|
+
backdrop-filter: blur(24px) saturate(180%);
|
|
155
|
+
box-shadow:
|
|
156
|
+
0 8px 28px rgba(0,0,0,0.4),
|
|
157
|
+
inset 0 1px 0 rgba(255,255,255,0.18);
|
|
158
|
+
z-index: 7;
|
|
159
|
+
animation: cf-rise .14s cubic-bezier(.16,1,.3,1) both;
|
|
160
|
+
}
|
|
161
|
+
.cf-overlay[data-theme="light"] .cf-pin-preview { color: #18181b; }
|
|
110
162
|
|
|
111
163
|
@keyframes cf-pin-pop {
|
|
112
164
|
0% { transform: scale(0); opacity: 0; }
|
|
@@ -123,24 +175,83 @@ export const STYLES = `
|
|
|
123
175
|
position: fixed;
|
|
124
176
|
left: 16px; bottom: 16px;
|
|
125
177
|
pointer-events: auto;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
border
|
|
129
|
-
|
|
178
|
+
/* Liquid glass — translucent, frosted, with a bright top edge. */
|
|
179
|
+
background: rgba(24,24,34,0.52);
|
|
180
|
+
border: 1px solid rgba(255,255,255,0.14);
|
|
181
|
+
border-radius: 17px;
|
|
182
|
+
padding: 9px;
|
|
130
183
|
display: flex; flex-direction: column; gap: 8px;
|
|
131
|
-
backdrop-filter: blur(
|
|
132
|
-
|
|
133
|
-
|
|
184
|
+
-webkit-backdrop-filter: blur(30px) saturate(185%);
|
|
185
|
+
backdrop-filter: blur(30px) saturate(185%);
|
|
186
|
+
box-shadow:
|
|
187
|
+
0 18px 50px rgba(0,0,0,0.34),
|
|
188
|
+
inset 0 1px 0 rgba(255,255,255,0.22),
|
|
189
|
+
inset 0 -1px 0 rgba(0,0,0,0.22);
|
|
134
190
|
z-index: 4;
|
|
135
191
|
}
|
|
136
192
|
.cf-toolbar-head {
|
|
137
193
|
display: flex; align-items: center; gap: 7px;
|
|
138
|
-
|
|
194
|
+
cursor: grab; user-select: none; touch-action: none;
|
|
195
|
+
}
|
|
196
|
+
.cf-toolbar-head:active { cursor: grabbing; }
|
|
197
|
+
/* Visible drag affordance — signals the panel can be moved. */
|
|
198
|
+
.cf-grip {
|
|
199
|
+
flex: 0 0 auto; color: var(--cf-muted);
|
|
200
|
+
font-size: 13px; line-height: 1; letter-spacing: -2px; opacity: 0.5;
|
|
201
|
+
}
|
|
202
|
+
.cf-toolbar-head:hover .cf-grip { opacity: 0.85; }
|
|
203
|
+
/* Logo button — tap to collapse back to the puck. */
|
|
204
|
+
.cf-collapse {
|
|
205
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
206
|
+
width: 26px; height: 26px; flex: 0 0 auto; padding: 0;
|
|
207
|
+
border: 0; border-radius: 8px; cursor: pointer;
|
|
208
|
+
/* Light tile so a dark brand logo reads on the dark panel. */
|
|
209
|
+
background: #fff;
|
|
210
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.25);
|
|
211
|
+
}
|
|
212
|
+
.cf-collapse:hover { filter: brightness(0.94); }
|
|
213
|
+
.cf-brand {
|
|
214
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
215
|
+
width: 22px; height: 22px; flex: 0 0 auto; pointer-events: none;
|
|
216
|
+
}
|
|
217
|
+
.cf-brand svg { width: 18px; height: 18px; display: block; }
|
|
218
|
+
.cf-toolbar.cf-dragging {
|
|
219
|
+
box-shadow: 0 24px 64px rgba(0,0,0,0.6);
|
|
220
|
+
transition: none;
|
|
139
221
|
}
|
|
140
222
|
.cf-logo-dot {
|
|
141
|
-
width:
|
|
142
|
-
background: var(--cf-accent);
|
|
143
|
-
|
|
223
|
+
width: 9px; height: 9px; border-radius: 50%;
|
|
224
|
+
background: var(--cf-accent); box-shadow: 0 0 10px var(--cf-accent);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* Collapsed FAB — a draggable logo puck; tap to expand. */
|
|
228
|
+
.cf-fab {
|
|
229
|
+
position: fixed; left: 16px; bottom: 16px;
|
|
230
|
+
width: 48px; height: 48px; border-radius: 16px;
|
|
231
|
+
pointer-events: auto; cursor: grab; user-select: none; touch-action: none;
|
|
232
|
+
display: flex; align-items: center; justify-content: center;
|
|
233
|
+
/* Liquid glass puck — frosted light so the dark mark reads. */
|
|
234
|
+
background: rgba(255,255,255,0.66);
|
|
235
|
+
-webkit-backdrop-filter: blur(22px) saturate(185%);
|
|
236
|
+
backdrop-filter: blur(22px) saturate(185%);
|
|
237
|
+
box-shadow:
|
|
238
|
+
0 12px 34px rgba(0,0,0,0.20),
|
|
239
|
+
inset 0 1px 0 rgba(255,255,255,0.95),
|
|
240
|
+
0 0 0 0.5px rgba(0,0,0,0.06);
|
|
241
|
+
z-index: 4;
|
|
242
|
+
transition: transform .13s cubic-bezier(.34,1.56,.64,1), box-shadow .15s ease;
|
|
243
|
+
}
|
|
244
|
+
.cf-fab:hover { transform: scale(1.06); }
|
|
245
|
+
.cf-fab.cf-dragging { cursor: grabbing; transition: none; box-shadow: 0 18px 44px rgba(0,0,0,0.42); }
|
|
246
|
+
.cf-fab .cf-brand { width: 28px; height: 28px; cursor: inherit; }
|
|
247
|
+
.cf-fab .cf-brand svg { width: 24px; height: 24px; }
|
|
248
|
+
.cf-fab-badge {
|
|
249
|
+
position: absolute; top: -5px; right: -5px;
|
|
250
|
+
min-width: 18px; height: 18px; padding: 0 5px; box-sizing: border-box;
|
|
251
|
+
border-radius: 9px; background: var(--cf-accent); color: #fff;
|
|
252
|
+
font: 800 10px -apple-system, system-ui;
|
|
253
|
+
display: flex; align-items: center; justify-content: center;
|
|
254
|
+
box-shadow: 0 0 0 2px var(--cf-bg);
|
|
144
255
|
}
|
|
145
256
|
.cf-count {
|
|
146
257
|
margin-left: auto;
|
|
@@ -151,24 +262,45 @@ export const STYLES = `
|
|
|
151
262
|
}
|
|
152
263
|
.cf-overlay[data-theme="light"] .cf-count { background: rgba(0,0,0,0.06); }
|
|
153
264
|
|
|
265
|
+
/* View / Comment reads as a pill switch — rounded track + sliding pill. */
|
|
154
266
|
.cf-seg {
|
|
155
267
|
display: flex; gap: 2px;
|
|
156
|
-
background: rgba(255,255,255,0.
|
|
157
|
-
border-radius:
|
|
268
|
+
background: rgba(255,255,255,0.07);
|
|
269
|
+
border-radius: 999px; padding: 3px;
|
|
158
270
|
}
|
|
159
271
|
.cf-overlay[data-theme="light"] .cf-seg { background: rgba(0,0,0,0.05); }
|
|
160
272
|
.cf-seg-btn {
|
|
161
273
|
flex: 1; border: none; background: transparent; color: var(--cf-muted);
|
|
162
|
-
padding: 5px
|
|
274
|
+
padding: 5px 14px; border-radius: 999px; cursor: pointer;
|
|
275
|
+
font: 600 12px -apple-system, system-ui;
|
|
276
|
+
transition: color .15s ease, background .2s ease;
|
|
163
277
|
}
|
|
278
|
+
.cf-seg-btn:hover { color: #fff; }
|
|
164
279
|
.cf-seg-btn.on {
|
|
165
280
|
background: var(--cf-accent); color: #fff;
|
|
166
|
-
box-shadow: 0 1px
|
|
281
|
+
box-shadow: 0 1px 5px rgba(124,92,255,0.45);
|
|
282
|
+
}
|
|
283
|
+
/* Suggestions navigator — count + jump to each annotation. */
|
|
284
|
+
.cf-nav {
|
|
285
|
+
display: flex; align-items: center; justify-content: space-between; gap: 6px;
|
|
286
|
+
background: rgba(255,255,255,0.05); border-radius: 10px; padding: 2px 3px;
|
|
167
287
|
}
|
|
288
|
+
.cf-nav-btn {
|
|
289
|
+
border: 0; background: transparent; color: var(--cf-muted); cursor: pointer;
|
|
290
|
+
width: 24px; height: 22px; border-radius: 7px; font-size: 16px; line-height: 1; padding: 0;
|
|
291
|
+
}
|
|
292
|
+
.cf-nav-btn:hover { background: rgba(255,255,255,0.1); color: #fff; }
|
|
293
|
+
.cf-nav-label { font: 700 11px -apple-system, system-ui; color: var(--cf-muted); }
|
|
168
294
|
.cf-tools .cf-seg-btn.on { background: rgba(255,255,255,0.16); color: #fff; }
|
|
169
295
|
.cf-overlay[data-theme="light"] .cf-tools .cf-seg-btn.on { background: rgba(0,0,0,0.12); color: #111; }
|
|
170
296
|
.cf-hint { font-size: 11px; color: var(--cf-muted); padding: 0 2px; }
|
|
171
297
|
.cf-commit { width: 100%; }
|
|
298
|
+
.cf-launch {
|
|
299
|
+
width: 100%; border: 0; margin-top: 2px;
|
|
300
|
+
background: linear-gradient(135deg, #7c5cff, #3ecf8e);
|
|
301
|
+
box-shadow: 0 6px 18px rgba(124,92,255,0.35);
|
|
302
|
+
}
|
|
303
|
+
.cf-launch:hover { filter: brightness(1.07); }
|
|
172
304
|
|
|
173
305
|
/* Buttons */
|
|
174
306
|
.cf-btn {
|
|
@@ -190,13 +322,20 @@ export const STYLES = `
|
|
|
190
322
|
left: 16px; bottom: 92px;
|
|
191
323
|
width: 320px;
|
|
192
324
|
pointer-events: auto;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
325
|
+
/* Liquid glass — translucent + heavily frosted with a bright top edge,
|
|
326
|
+
matching the toolbar/FAB. The low-alpha fill is what lets the blur
|
|
327
|
+
read as glass rather than a solid black box. */
|
|
328
|
+
background: var(--cf-glass);
|
|
329
|
+
border: 1px solid var(--cf-glass-border);
|
|
330
|
+
border-radius: 16px;
|
|
196
331
|
padding: 12px;
|
|
197
332
|
display: flex; flex-direction: column; gap: 10px;
|
|
198
|
-
backdrop-filter: blur(
|
|
199
|
-
|
|
333
|
+
-webkit-backdrop-filter: blur(30px) saturate(185%);
|
|
334
|
+
backdrop-filter: blur(30px) saturate(185%);
|
|
335
|
+
box-shadow:
|
|
336
|
+
0 18px 50px rgba(0,0,0,0.40),
|
|
337
|
+
inset 0 1px 0 rgba(255,255,255,0.22),
|
|
338
|
+
inset 0 -1px 0 rgba(0,0,0,0.22);
|
|
200
339
|
z-index: 5;
|
|
201
340
|
animation: cf-rise .2s cubic-bezier(.16,1,.3,1) both;
|
|
202
341
|
}
|
|
@@ -218,6 +357,13 @@ export const STYLES = `
|
|
|
218
357
|
width: 22px; height: 22px; border-radius: 6px;
|
|
219
358
|
}
|
|
220
359
|
.cf-icon-btn:hover { background: rgba(255,255,255,0.08); }
|
|
360
|
+
/* Clear / erase-board — small text button after the count. */
|
|
361
|
+
.cf-clear {
|
|
362
|
+
margin-left: 4px; width: auto; height: auto; padding: 3px 8px;
|
|
363
|
+
font: 700 10px -apple-system, system-ui; text-transform: uppercase;
|
|
364
|
+
letter-spacing: 0.05em;
|
|
365
|
+
}
|
|
366
|
+
.cf-clear:hover { color: #fff; }
|
|
221
367
|
|
|
222
368
|
.cf-textarea {
|
|
223
369
|
width: 100%; min-height: 72px; resize: vertical;
|
|
@@ -246,7 +392,29 @@ export const STYLES = `
|
|
|
246
392
|
border-color: var(--cf-chip, var(--cf-accent));
|
|
247
393
|
color: #fff;
|
|
248
394
|
}
|
|
249
|
-
.cf-popup-actions { display: flex; gap: 8px; justify-content: flex-end; }
|
|
395
|
+
.cf-popup-actions { display: flex; gap: 8px; align-items: center; justify-content: flex-end; }
|
|
396
|
+
|
|
397
|
+
/* Composer grows out of the floater — the WAAPI morph drives it, so drop
|
|
398
|
+
the CSS keyframe to avoid a double animation. */
|
|
399
|
+
.cf-popup.cf-morph { animation: none; }
|
|
400
|
+
/* The logo in the popup head — the floater mark that flew over + opened. */
|
|
401
|
+
.cf-popup-brand {
|
|
402
|
+
width: 20px; height: 20px; flex: 0 0 auto;
|
|
403
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
404
|
+
background: #fff; border-radius: 6px; box-shadow: 0 1px 2px rgba(0,0,0,0.25);
|
|
405
|
+
}
|
|
406
|
+
.cf-popup-brand svg { width: 15px; height: 15px; display: block; }
|
|
407
|
+
/* Intent/severity disclosure — minimal by default. */
|
|
408
|
+
.cf-details { display: flex; flex-direction: column; gap: 10px; }
|
|
409
|
+
.cf-details[hidden] { display: none; }
|
|
410
|
+
.cf-disclose {
|
|
411
|
+
margin-right: auto; border: 0; background: transparent; color: var(--cf-muted);
|
|
412
|
+
font: 600 11px -apple-system, system-ui; cursor: pointer;
|
|
413
|
+
padding: 6px 6px; border-radius: 7px;
|
|
414
|
+
}
|
|
415
|
+
.cf-disclose:hover, .cf-disclose.on { color: #fff; background: rgba(255,255,255,0.06); }
|
|
416
|
+
.cf-overlay[data-theme="light"] .cf-disclose:hover,
|
|
417
|
+
.cf-overlay[data-theme="light"] .cf-disclose.on { color: #111; background: rgba(0,0,0,0.05); }
|
|
250
418
|
|
|
251
419
|
/* Thread */
|
|
252
420
|
.cf-thread-body {
|
|
@@ -255,7 +423,17 @@ export const STYLES = `
|
|
|
255
423
|
}
|
|
256
424
|
.cf-msg { display: flex; flex-direction: column; gap: 3px; }
|
|
257
425
|
.cf-msg-meta { display: flex; align-items: center; gap: 6px; }
|
|
258
|
-
.cf-avatar {
|
|
426
|
+
.cf-avatar {
|
|
427
|
+
width: 20px; height: 20px; flex: 0 0 auto;
|
|
428
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
429
|
+
border-radius: 50%; overflow: hidden;
|
|
430
|
+
font: 800 9px -apple-system, system-ui; letter-spacing: 0.02em;
|
|
431
|
+
color: #fff; text-shadow: 0 1px 1px rgba(0,0,0,0.25);
|
|
432
|
+
box-shadow: 0 0 0 1.5px rgba(255,255,255,0.14), 0 1px 2px rgba(0,0,0,0.3);
|
|
433
|
+
}
|
|
434
|
+
/* Agents read as a distinct rounded "logo" tile; humans stay round. */
|
|
435
|
+
.cf-avatar-agent { border-radius: 7px; }
|
|
436
|
+
.cf-avatar-img { width: 100%; height: 100%; object-fit: cover; }
|
|
259
437
|
.cf-msg-name {
|
|
260
438
|
font: 700 11px -apple-system, system-ui; color: var(--cf-muted);
|
|
261
439
|
}
|
package/src/ui/App.tsx
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Root Preact tree — the full annotation overlay.
|
|
3
|
-
*
|
|
4
|
-
* Real JSX (the file is `.tsx`); esbuild + tsc transpile via the
|
|
5
|
-
* package's `jsx: react-jsx` + `jsxImportSource: preact` config.
|
|
6
|
-
* Local component state uses preact/hooks; cross-cutting state lives
|
|
7
|
-
* in the signals from `../store.js`.
|
|
8
|
-
*
|
|
9
|
-
* Layers (all inside the closed Shadow DOM, fixed-positioned):
|
|
10
|
-
* 1. hover highlight — element under the cursor in feedback mode
|
|
11
|
-
* 2. multi-select boxes
|
|
12
|
-
* 3. pins — one numbered marker per annotation
|
|
13
|
-
* 4. toolbar — mode + tool switches
|
|
14
|
-
* 5. composer popup — comment + intent/severity/kind on capture
|
|
15
|
-
* 6. thread panel — per-pin discussion (human ↔ agent)
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import type { ComponentChild } from "preact";
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
activeThreadId,
|
|
22
|
-
annotations,
|
|
23
|
-
cursors,
|
|
24
|
-
draft,
|
|
25
|
-
hoverBox,
|
|
26
|
-
multiSelection,
|
|
27
|
-
theme,
|
|
28
|
-
viewportTick,
|
|
29
|
-
} from "../store.js";
|
|
30
|
-
import { boxStyle } from "./helpers.js";
|
|
31
|
-
import { Toolbar } from "./Toolbar.js";
|
|
32
|
-
import { MultiBox, Pin } from "./Pins.js";
|
|
33
|
-
import { Cursor } from "./Cursors.js";
|
|
34
|
-
import { Composer } from "./Composer.js";
|
|
35
|
-
import { ThreadPanel } from "./ThreadPanel.js";
|
|
36
|
-
import { STYLES } from "./styles.js";
|
|
37
|
-
|
|
38
|
-
export function App(): ComponentChild {
|
|
39
|
-
const themeAttr = theme.value === "auto" ? "dark" : theme.value;
|
|
40
|
-
// Subscribe to viewport changes so pins recompute their screen pos.
|
|
41
|
-
void viewportTick.value;
|
|
42
|
-
|
|
43
|
-
return (
|
|
44
|
-
<div class="cf-overlay" data-theme={themeAttr}>
|
|
45
|
-
<style key="styles">{STYLES}</style>
|
|
46
|
-
{hoverBox.value && !draft.value ? (
|
|
47
|
-
<div key="hover" class="cf-hover" style={boxStyle(hoverBox.value)} />
|
|
48
|
-
) : null}
|
|
49
|
-
{multiSelection.value.map((el, i) => (
|
|
50
|
-
<MultiBox key={`multi-${i}`} el={el} />
|
|
51
|
-
))}
|
|
52
|
-
<div key="pins">
|
|
53
|
-
{annotations.value.map((a, i) => (
|
|
54
|
-
<Pin key={a.id} a={a} index={i + 1} />
|
|
55
|
-
))}
|
|
56
|
-
</div>
|
|
57
|
-
<div key="cursors">
|
|
58
|
-
{cursors.value.map((c) => (
|
|
59
|
-
<Cursor key={`cursor-${c.id}`} c={c} />
|
|
60
|
-
))}
|
|
61
|
-
</div>
|
|
62
|
-
<Toolbar />
|
|
63
|
-
{draft.value ? <Composer key="composer" target={draft.value} /> : null}
|
|
64
|
-
{activeThreadId.value ? (
|
|
65
|
-
<ThreadPanel key="thread" id={activeThreadId.value} />
|
|
66
|
-
) : null}
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
69
|
-
}
|
package/src/ui/Composer.tsx
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Composer popup — the comment + intent/severity form shown when a
|
|
3
|
-
* target is captured. Local form state lives in preact/hooks; on
|
|
4
|
-
* submit it pushes through the AFS API and clears the draft.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { ComponentChild } from "preact";
|
|
8
|
-
import { useState } from "preact/hooks";
|
|
9
|
-
|
|
10
|
-
import { api } from "../api.js";
|
|
11
|
-
import type { CapturedTarget } from "../capture.js";
|
|
12
|
-
import { author, draft } from "../store.js";
|
|
13
|
-
import type {
|
|
14
|
-
AnnotationIntent,
|
|
15
|
-
AnnotationKind,
|
|
16
|
-
AnnotationSeverity,
|
|
17
|
-
} from "../types.js";
|
|
18
|
-
import {
|
|
19
|
-
INTENTS,
|
|
20
|
-
SEVERITIES,
|
|
21
|
-
SEVERITY_COLOR,
|
|
22
|
-
SEVERITY_NEUTRAL,
|
|
23
|
-
} from "./constants.js";
|
|
24
|
-
import { truncate } from "./helpers.js";
|
|
25
|
-
|
|
26
|
-
interface ChipProps {
|
|
27
|
-
label: string;
|
|
28
|
-
on: boolean;
|
|
29
|
-
color: string;
|
|
30
|
-
onClick: () => void;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function Chip({ label, on, color, onClick }: ChipProps): ComponentChild {
|
|
34
|
-
return (
|
|
35
|
-
<button
|
|
36
|
-
class={`cf-chip${on ? " on" : ""}`}
|
|
37
|
-
style={on ? `--cf-chip:${color};` : undefined}
|
|
38
|
-
onClick={onClick}
|
|
39
|
-
>
|
|
40
|
-
{label}
|
|
41
|
-
</button>
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface ComposerProps {
|
|
46
|
-
target: CapturedTarget;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function Composer({ target }: ComposerProps): ComponentChild {
|
|
50
|
-
const [comment, setComment] = useState("");
|
|
51
|
-
const [intent, setIntent] = useState<AnnotationIntent>("change");
|
|
52
|
-
const [severity, setSeverity] = useState<AnnotationSeverity>("important");
|
|
53
|
-
|
|
54
|
-
const submit = (): void => {
|
|
55
|
-
const text = comment.trim();
|
|
56
|
-
if (!text) return;
|
|
57
|
-
api.addAnnotation({
|
|
58
|
-
...target,
|
|
59
|
-
comment: text,
|
|
60
|
-
intent,
|
|
61
|
-
severity,
|
|
62
|
-
kind: "feedback" satisfies AnnotationKind,
|
|
63
|
-
author: author.value,
|
|
64
|
-
});
|
|
65
|
-
draft.value = null;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const cancel = (): void => {
|
|
69
|
-
draft.value = null;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const label = target.isMultiSelect
|
|
73
|
-
? `${target.elementBoundingBoxes?.length ?? 0} elements`
|
|
74
|
-
: target.selectedText
|
|
75
|
-
? `"${truncate(target.selectedText, 40)}"`
|
|
76
|
-
: `<${target.element}>`;
|
|
77
|
-
|
|
78
|
-
return (
|
|
79
|
-
<div class="cf-popup cf-composer">
|
|
80
|
-
<div key="head" class="cf-popup-head">
|
|
81
|
-
<span key="t" class="cf-popup-target">{label}</span>
|
|
82
|
-
<button key="x" class="cf-icon-btn" onClick={cancel} title="Cancel">
|
|
83
|
-
✕
|
|
84
|
-
</button>
|
|
85
|
-
</div>
|
|
86
|
-
<textarea
|
|
87
|
-
key="ta"
|
|
88
|
-
class="cf-textarea"
|
|
89
|
-
placeholder="Describe the change… (Markdown supported)"
|
|
90
|
-
autofocus
|
|
91
|
-
value={comment}
|
|
92
|
-
onInput={(e: Event) => setComment((e.target as HTMLTextAreaElement).value)}
|
|
93
|
-
onKeyDown={(e: KeyboardEvent) => {
|
|
94
|
-
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") submit();
|
|
95
|
-
}}
|
|
96
|
-
/>
|
|
97
|
-
<div key="intent" class="cf-field">
|
|
98
|
-
<span key="l" class="cf-field-label">Intent</span>
|
|
99
|
-
<div key="g" class="cf-chipset">
|
|
100
|
-
{INTENTS.map((it) => (
|
|
101
|
-
<Chip
|
|
102
|
-
key={it}
|
|
103
|
-
label={it}
|
|
104
|
-
on={intent === it}
|
|
105
|
-
color={SEVERITY_NEUTRAL}
|
|
106
|
-
onClick={() => setIntent(it)}
|
|
107
|
-
/>
|
|
108
|
-
))}
|
|
109
|
-
</div>
|
|
110
|
-
</div>
|
|
111
|
-
<div key="sev" class="cf-field">
|
|
112
|
-
<span key="l" class="cf-field-label">Severity</span>
|
|
113
|
-
<div key="g" class="cf-chipset">
|
|
114
|
-
{SEVERITIES.map((s) => (
|
|
115
|
-
<Chip
|
|
116
|
-
key={s}
|
|
117
|
-
label={s}
|
|
118
|
-
on={severity === s}
|
|
119
|
-
color={SEVERITY_COLOR[s]}
|
|
120
|
-
onClick={() => setSeverity(s)}
|
|
121
|
-
/>
|
|
122
|
-
))}
|
|
123
|
-
</div>
|
|
124
|
-
</div>
|
|
125
|
-
<div key="actions" class="cf-popup-actions">
|
|
126
|
-
<button key="c" class="cf-btn" onClick={cancel}>Cancel</button>
|
|
127
|
-
<button
|
|
128
|
-
key="s"
|
|
129
|
-
class="cf-btn cf-btn-primary"
|
|
130
|
-
disabled={!comment.trim()}
|
|
131
|
-
onClick={submit}
|
|
132
|
-
>
|
|
133
|
-
Comment
|
|
134
|
-
</button>
|
|
135
|
-
</div>
|
|
136
|
-
</div>
|
|
137
|
-
);
|
|
138
|
-
}
|
package/src/ui/Cursors.tsx
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Live presence cursors — one labeled arrow per remote actor (human
|
|
3
|
-
* reviewers + spun-off agents) collaborating on the same page.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { ComponentChild } from "preact";
|
|
7
|
-
|
|
8
|
-
import type { PresenceCursor } from "../store.js";
|
|
9
|
-
import { CURSOR_PALETTE } from "./constants.js";
|
|
10
|
-
import { hashId } from "./helpers.js";
|
|
11
|
-
|
|
12
|
-
interface CursorProps {
|
|
13
|
-
c: PresenceCursor;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function Cursor({ c }: CursorProps): ComponentChild {
|
|
17
|
-
// x is % of viewport width; y is px from document top.
|
|
18
|
-
const left = (c.x / 100) * window.innerWidth;
|
|
19
|
-
const top = c.y - window.scrollY;
|
|
20
|
-
const color =
|
|
21
|
-
c.color ??
|
|
22
|
-
(c.kind === "agent"
|
|
23
|
-
? CURSOR_PALETTE[Math.abs(hashId(c.id)) % CURSOR_PALETTE.length]!
|
|
24
|
-
: "#ffffff");
|
|
25
|
-
return (
|
|
26
|
-
<div
|
|
27
|
-
class={`cf-cursor cf-cursor-${c.kind}`}
|
|
28
|
-
style={`top:${top}px;left:${left}px;--cf-cursor:${color};`}
|
|
29
|
-
>
|
|
30
|
-
{/* Arrow pointer. */}
|
|
31
|
-
<svg
|
|
32
|
-
key="arrow"
|
|
33
|
-
width="18"
|
|
34
|
-
height="18"
|
|
35
|
-
viewBox="0 0 18 18"
|
|
36
|
-
class="cf-cursor-arrow"
|
|
37
|
-
>
|
|
38
|
-
<path
|
|
39
|
-
d="M2 2 L2 14 L6 10 L9 16 L11 15 L8 9 L14 9 Z"
|
|
40
|
-
fill={color}
|
|
41
|
-
stroke="rgba(0,0,0,0.35)"
|
|
42
|
-
stroke-width="0.75"
|
|
43
|
-
/>
|
|
44
|
-
</svg>
|
|
45
|
-
<span key="label" class="cf-cursor-label">
|
|
46
|
-
{`${c.kind === "agent" ? "🤖 " : ""}${c.label}`}
|
|
47
|
-
</span>
|
|
48
|
-
</div>
|
|
49
|
-
);
|
|
50
|
-
}
|
package/src/ui/Pins.tsx
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pins + multi-select boxes.
|
|
3
|
-
*
|
|
4
|
-
* - `Pin` — one numbered marker per annotation, re-anchored to
|
|
5
|
-
* its live element each render.
|
|
6
|
-
* - `MultiBox` — a pulsing outline around each element in the
|
|
7
|
-
* current multi-selection set.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { ComponentChild } from "preact";
|
|
11
|
-
|
|
12
|
-
import { activeThreadId } from "../store.js";
|
|
13
|
-
import type { Annotation } from "../types.js";
|
|
14
|
-
import { DEFAULT_DOT, SEVERITY_COLOR } from "./constants.js";
|
|
15
|
-
import { boxStyle, firstLine, pinPosition } from "./helpers.js";
|
|
16
|
-
|
|
17
|
-
interface PinProps {
|
|
18
|
-
a: Annotation;
|
|
19
|
-
index: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function Pin({ a, index }: PinProps): ComponentChild {
|
|
23
|
-
const pos = pinPosition(a);
|
|
24
|
-
if (!pos) return null;
|
|
25
|
-
const color = a.severity ? SEVERITY_COLOR[a.severity] : DEFAULT_DOT;
|
|
26
|
-
const resolved = a.status === "resolved" || a.status === "dismissed";
|
|
27
|
-
return (
|
|
28
|
-
<button
|
|
29
|
-
class={`cf-pin${resolved ? " resolved" : ""}${a.author?.kind === "agent" ? " agent" : ""}`}
|
|
30
|
-
style={`top:${pos.top}px;left:${pos.left}px;--cf-pin:${color};`}
|
|
31
|
-
title={firstLine(a.comment)}
|
|
32
|
-
onClick={() => {
|
|
33
|
-
activeThreadId.value = activeThreadId.value === a.id ? null : a.id;
|
|
34
|
-
}}
|
|
35
|
-
>
|
|
36
|
-
{a.author?.kind === "agent" ? "★" : String(index)}
|
|
37
|
-
</button>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
interface MultiBoxProps {
|
|
42
|
-
el: Element;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function MultiBox({ el }: MultiBoxProps): ComponentChild {
|
|
46
|
-
const rect = el.getBoundingClientRect();
|
|
47
|
-
return (
|
|
48
|
-
<div
|
|
49
|
-
class="cf-multi-box"
|
|
50
|
-
style={boxStyle({
|
|
51
|
-
top: rect.top,
|
|
52
|
-
left: rect.left,
|
|
53
|
-
width: rect.width,
|
|
54
|
-
height: rect.height,
|
|
55
|
-
})}
|
|
56
|
-
/>
|
|
57
|
-
);
|
|
58
|
-
}
|