@coframe-gtm/annotations 1.0.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.
@@ -0,0 +1,283 @@
1
+ /**
2
+ * All overlay CSS as a single string injected into the Shadow DOM.
3
+ * Scoped under `.cf-overlay` + `[data-theme]`; nothing leaks to the
4
+ * host page (closed shadow) and host styles can't leak in (reset in
5
+ * shadow.ts). Colors follow a dark-first palette.
6
+ */
7
+
8
+ export const STYLES = `
9
+ .cf-overlay {
10
+ position: fixed;
11
+ inset: 0;
12
+ pointer-events: none;
13
+ font-family: -apple-system, "SF Pro Text", system-ui, sans-serif;
14
+ font-size: 13px;
15
+ color: #f4f4f5;
16
+ --cf-bg: rgba(18, 18, 22, 0.96);
17
+ --cf-border: rgba(255, 255, 255, 0.1);
18
+ --cf-muted: rgba(255, 255, 255, 0.55);
19
+ --cf-accent: #a78bfa;
20
+ }
21
+ .cf-overlay[data-theme="light"] {
22
+ color: #18181b;
23
+ --cf-bg: rgba(252, 252, 253, 0.97);
24
+ --cf-border: rgba(0, 0, 0, 0.1);
25
+ --cf-muted: rgba(0, 0, 0, 0.5);
26
+ }
27
+
28
+ /* Hover highlight + multi-select boxes */
29
+ .cf-hover {
30
+ position: fixed;
31
+ pointer-events: none;
32
+ border: 2px solid var(--cf-accent);
33
+ background: rgba(167, 139, 250, 0.1);
34
+ border-radius: 4px;
35
+ box-shadow: 0 0 0 1px rgba(167, 139, 250, 0.4);
36
+ transition: top .06s ease, left .06s ease, width .06s ease, height .06s ease;
37
+ z-index: 1;
38
+ }
39
+ .cf-multi-box {
40
+ position: fixed;
41
+ pointer-events: none;
42
+ border: 2px solid #3ecf8e;
43
+ background: rgba(62, 207, 142, 0.12);
44
+ border-radius: 4px;
45
+ z-index: 1;
46
+ animation: cf-multi-pulse 1.4s ease-in-out infinite;
47
+ }
48
+ @keyframes cf-multi-pulse {
49
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(62,207,142,0.0); }
50
+ 50% { box-shadow: 0 0 0 3px rgba(62,207,142,0.25); }
51
+ }
52
+
53
+ /* Live presence cursors (multi-cursor collaboration) */
54
+ .cf-cursor {
55
+ position: fixed;
56
+ z-index: 6;
57
+ pointer-events: none;
58
+ display: flex;
59
+ align-items: flex-start;
60
+ gap: 4px;
61
+ /* Glide to new positions so multiple actors read as "live". */
62
+ transition: top .12s ease, left .12s ease;
63
+ will-change: top, left;
64
+ }
65
+ .cf-cursor-arrow { display: block; filter: drop-shadow(0 1px 2px rgba(0,0,0,0.4)); }
66
+ .cf-cursor-label {
67
+ transform: translateY(2px);
68
+ background: var(--cf-cursor, #a78bfa);
69
+ color: #0b0b0f;
70
+ font: 700 11px -apple-system, system-ui;
71
+ padding: 2px 7px;
72
+ border-radius: 8px;
73
+ white-space: nowrap;
74
+ box-shadow: 0 2px 8px rgba(0,0,0,0.35);
75
+ }
76
+ .cf-cursor-human .cf-cursor-label { color: #18181b; }
77
+ .cf-cursor-agent .cf-cursor-label { color: #0b0b0f; }
78
+
79
+ /* Pins */
80
+ .cf-pin {
81
+ position: fixed;
82
+ width: 22px; height: 22px;
83
+ border-radius: 50% 50% 50% 2px;
84
+ background: var(--cf-pin, #a78bfa);
85
+ color: #fff;
86
+ border: 2px solid rgba(255,255,255,0.9);
87
+ font: 700 11px -apple-system, system-ui;
88
+ display: flex; align-items: center; justify-content: center;
89
+ cursor: pointer;
90
+ pointer-events: auto;
91
+ box-shadow: 0 2px 8px rgba(0,0,0,0.35);
92
+ z-index: 3;
93
+ transition: transform .12s cubic-bezier(.34,1.56,.64,1);
94
+ 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
+ }
98
+ .cf-pin:hover { transform: scale(1.2); }
99
+ .cf-pin.resolved { opacity: 0.45; }
100
+ /* Agent-pushed pins pulse a ring so reviewers spot what the agent added. */
101
+ .cf-pin.agent { border-style: dashed; }
102
+ .cf-pin.agent::after {
103
+ content: "";
104
+ position: absolute; inset: -4px;
105
+ border-radius: inherit;
106
+ border: 2px solid var(--cf-pin, #a78bfa);
107
+ animation: cf-pin-ring 1.6s ease-out 3;
108
+ pointer-events: none;
109
+ }
110
+
111
+ @keyframes cf-pin-pop {
112
+ 0% { transform: scale(0); opacity: 0; }
113
+ 60% { transform: scale(1.25); opacity: 1; }
114
+ 100% { transform: scale(1); opacity: 1; }
115
+ }
116
+ @keyframes cf-pin-ring {
117
+ 0% { transform: scale(1); opacity: .7; }
118
+ 100% { transform: scale(2.4); opacity: 0; }
119
+ }
120
+
121
+ /* Toolbar */
122
+ .cf-toolbar {
123
+ position: fixed;
124
+ left: 16px; bottom: 16px;
125
+ pointer-events: auto;
126
+ background: var(--cf-bg);
127
+ border: 1px solid var(--cf-border);
128
+ border-radius: 14px;
129
+ padding: 10px;
130
+ display: flex; flex-direction: column; gap: 8px;
131
+ backdrop-filter: blur(16px) saturate(140%);
132
+ box-shadow: 0 16px 48px rgba(0,0,0,0.5);
133
+ min-width: 220px;
134
+ z-index: 4;
135
+ }
136
+ .cf-toolbar-head {
137
+ display: flex; align-items: center; gap: 7px;
138
+ font-weight: 700; letter-spacing: 0.02em;
139
+ }
140
+ .cf-logo-dot {
141
+ width: 8px; height: 8px; border-radius: 50%;
142
+ background: var(--cf-accent);
143
+ box-shadow: 0 0 10px var(--cf-accent);
144
+ }
145
+ .cf-count {
146
+ margin-left: auto;
147
+ font: 700 11px -apple-system, system-ui;
148
+ color: var(--cf-muted);
149
+ background: rgba(255,255,255,0.08);
150
+ padding: 1px 7px; border-radius: 8px;
151
+ }
152
+ .cf-overlay[data-theme="light"] .cf-count { background: rgba(0,0,0,0.06); }
153
+
154
+ .cf-seg {
155
+ display: flex; gap: 2px;
156
+ background: rgba(255,255,255,0.06);
157
+ border-radius: 9px; padding: 2px;
158
+ }
159
+ .cf-overlay[data-theme="light"] .cf-seg { background: rgba(0,0,0,0.05); }
160
+ .cf-seg-btn {
161
+ flex: 1; border: none; background: transparent; color: var(--cf-muted);
162
+ padding: 5px 10px; border-radius: 7px; font: 600 12px -apple-system, system-ui;
163
+ }
164
+ .cf-seg-btn.on {
165
+ background: var(--cf-accent); color: #fff;
166
+ box-shadow: 0 1px 4px rgba(0,0,0,0.3);
167
+ }
168
+ .cf-tools .cf-seg-btn.on { background: rgba(255,255,255,0.16); color: #fff; }
169
+ .cf-overlay[data-theme="light"] .cf-tools .cf-seg-btn.on { background: rgba(0,0,0,0.12); color: #111; }
170
+ .cf-hint { font-size: 11px; color: var(--cf-muted); padding: 0 2px; }
171
+ .cf-commit { width: 100%; }
172
+
173
+ /* Buttons */
174
+ .cf-btn {
175
+ border: 1px solid var(--cf-border); background: transparent; color: inherit;
176
+ padding: 6px 12px; border-radius: 8px; font: 600 12px -apple-system, system-ui;
177
+ }
178
+ .cf-btn:hover { background: rgba(255,255,255,0.06); }
179
+ .cf-overlay[data-theme="light"] .cf-btn:hover { background: rgba(0,0,0,0.04); }
180
+ .cf-btn-primary {
181
+ background: var(--cf-accent); border-color: var(--cf-accent); color: #fff;
182
+ }
183
+ .cf-btn-primary:hover { background: #9173f0; }
184
+ .cf-btn-primary:disabled { opacity: 0.4; cursor: not-allowed; }
185
+ .cf-btn-ghost { border-color: transparent; color: var(--cf-muted); }
186
+
187
+ /* Popups (composer + thread) */
188
+ .cf-popup {
189
+ position: fixed;
190
+ left: 16px; bottom: 92px;
191
+ width: 320px;
192
+ pointer-events: auto;
193
+ background: var(--cf-bg);
194
+ border: 1px solid var(--cf-border);
195
+ border-radius: 14px;
196
+ padding: 12px;
197
+ display: flex; flex-direction: column; gap: 10px;
198
+ backdrop-filter: blur(16px) saturate(140%);
199
+ box-shadow: 0 16px 48px rgba(0,0,0,0.55);
200
+ z-index: 5;
201
+ animation: cf-rise .2s cubic-bezier(.16,1,.3,1) both;
202
+ }
203
+ @keyframes cf-rise {
204
+ from { transform: translateY(10px); opacity: 0; }
205
+ to { transform: translateY(0); opacity: 1; }
206
+ }
207
+ .cf-popup-head {
208
+ display: flex; align-items: center; gap: 8px;
209
+ }
210
+ .cf-popup-target {
211
+ font: 600 12px ui-monospace, "SF Mono", monospace;
212
+ color: var(--cf-accent);
213
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
214
+ }
215
+ .cf-icon-btn {
216
+ margin-left: auto; border: none; background: transparent;
217
+ color: var(--cf-muted); font-size: 13px; cursor: pointer;
218
+ width: 22px; height: 22px; border-radius: 6px;
219
+ }
220
+ .cf-icon-btn:hover { background: rgba(255,255,255,0.08); }
221
+
222
+ .cf-textarea {
223
+ width: 100%; min-height: 72px; resize: vertical;
224
+ background: rgba(255,255,255,0.04);
225
+ border: 1px solid var(--cf-border); border-radius: 9px;
226
+ padding: 8px 10px; color: inherit; font: 400 13px -apple-system, system-ui;
227
+ line-height: 1.45;
228
+ }
229
+ .cf-overlay[data-theme="light"] .cf-textarea { background: rgba(0,0,0,0.03); }
230
+ .cf-textarea:focus { outline: none; border-color: var(--cf-accent); }
231
+ .cf-textarea-sm { min-height: 48px; }
232
+
233
+ .cf-field { display: flex; flex-direction: column; gap: 5px; }
234
+ .cf-field-label {
235
+ font: 700 10px -apple-system, system-ui; letter-spacing: 0.06em;
236
+ text-transform: uppercase; color: var(--cf-muted);
237
+ }
238
+ .cf-chipset { display: flex; flex-wrap: wrap; gap: 5px; }
239
+ .cf-chip {
240
+ border: 1px solid var(--cf-border); background: transparent; color: var(--cf-muted);
241
+ padding: 4px 10px; border-radius: 20px; font: 600 11px -apple-system, system-ui;
242
+ text-transform: capitalize;
243
+ }
244
+ .cf-chip.on {
245
+ background: var(--cf-chip, var(--cf-accent));
246
+ border-color: var(--cf-chip, var(--cf-accent));
247
+ color: #fff;
248
+ }
249
+ .cf-popup-actions { display: flex; gap: 8px; justify-content: flex-end; }
250
+
251
+ /* Thread */
252
+ .cf-thread-body {
253
+ display: flex; flex-direction: column; gap: 12px;
254
+ max-height: 280px; overflow-y: auto; padding-right: 2px;
255
+ }
256
+ .cf-msg { display: flex; flex-direction: column; gap: 3px; }
257
+ .cf-msg-meta { display: flex; align-items: center; gap: 6px; }
258
+ .cf-avatar { font-size: 12px; }
259
+ .cf-msg-name {
260
+ font: 700 11px -apple-system, system-ui; color: var(--cf-muted);
261
+ }
262
+ .cf-msg-body {
263
+ font-size: 13px; line-height: 1.5; white-space: pre-wrap; word-break: break-word;
264
+ background: rgba(255,255,255,0.04); border-radius: 9px; padding: 7px 10px;
265
+ }
266
+ .cf-overlay[data-theme="light"] .cf-msg-body { background: rgba(0,0,0,0.03); }
267
+ .cf-msg-agent .cf-msg-body { border-left: 2px solid var(--cf-accent); }
268
+ .cf-thread-compose { display: flex; flex-direction: column; gap: 8px; }
269
+ .cf-thread-actions { display: flex; gap: 8px; align-items: center; }
270
+ .cf-thread-actions .cf-btn-primary { margin-left: auto; }
271
+ .cf-resolved-tag { font: 600 11px -apple-system, system-ui; color: #3ecf8e; }
272
+ .cf-tag {
273
+ font: 700 9px -apple-system, system-ui; text-transform: uppercase;
274
+ letter-spacing: 0.05em; padding: 2px 6px; border-radius: 5px;
275
+ background: var(--cf-tag, var(--cf-accent)); color: #fff;
276
+ }
277
+
278
+ /* Respect reduced-motion: keep the UI usable, drop the motion. */
279
+ @media (prefers-reduced-motion: reduce) {
280
+ .cf-pin, .cf-popup, .cf-multi-box { animation: none !important; }
281
+ .cf-pin.agent::after { display: none; }
282
+ }
283
+ `;
package/src/ulid.ts ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Tiny ULID. Inlined instead of pulling the npm `ulid` package so
3
+ * the IIFE bundle stays dep-free.
4
+ *
5
+ * Format: 10 chars of base32 timestamp (ms) + 16 chars of base32
6
+ * random. Lexically sortable by creation time, ~26 chars long.
7
+ */
8
+
9
+ const ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
10
+
11
+ export function ulid(now: number = Date.now()): string {
12
+ let t = now;
13
+ let time = "";
14
+ for (let i = 0; i < 10; i++) {
15
+ time = ALPHABET[t & 31]! + time;
16
+ t = Math.floor(t / 32);
17
+ }
18
+ let rand = "";
19
+ for (let i = 0; i < 16; i++) rand += ALPHABET[Math.floor(Math.random() * 32)];
20
+ return time + rand;
21
+ }
package/src/webhook.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Push-based outbound event emitter — AFS v1.1 envelope shape.
3
+ *
4
+ * { type, timestamp (ISO), sessionId, sequence, payload }
5
+ *
6
+ * `sequence` is a per-session monotonic counter so the host
7
+ * webhook can detect missed events and request replay.
8
+ */
9
+
10
+ import { sequence, sessionId, webhookUrl } from "./store.js";
11
+ import type { AgentationEvent, AgentationEventType } from "./types.js";
12
+
13
+ export function emit<P>(type: AgentationEventType, payload: P): void {
14
+ const url = webhookUrl.value;
15
+ const envelope: AgentationEvent<P> = {
16
+ type,
17
+ timestamp: new Date().toISOString(),
18
+ sessionId: sessionId.value,
19
+ sequence: ++sequence.value,
20
+ payload,
21
+ };
22
+ if (!url) return;
23
+ try {
24
+ void fetch(url, {
25
+ method: "POST",
26
+ headers: { "Content-Type": "application/json" },
27
+ body: JSON.stringify(envelope),
28
+ keepalive: true,
29
+ }).catch((err) => console.warn("[annotations] webhook emit failed", err));
30
+ } catch (err) {
31
+ console.warn("[annotations] webhook emit threw synchronously", err);
32
+ }
33
+ }