instruckt 0.1.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.
@@ -0,0 +1,1450 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __spreadValues = (a, b) => {
10
+ for (var prop in b || (b = {}))
11
+ if (__hasOwnProp.call(b, prop))
12
+ __defNormalProp(a, prop, b[prop]);
13
+ if (__getOwnPropSymbols)
14
+ for (var prop of __getOwnPropSymbols(b)) {
15
+ if (__propIsEnum.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ }
18
+ return a;
19
+ };
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, { get: all[name], enumerable: true });
23
+ };
24
+ var __copyProps = (to, from, except, desc) => {
25
+ if (from && typeof from === "object" || typeof from === "function") {
26
+ for (let key of __getOwnPropNames(from))
27
+ if (!__hasOwnProp.call(to, key) && key !== except)
28
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
29
+ }
30
+ return to;
31
+ };
32
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
+
34
+ // src/index.ts
35
+ var src_exports = {};
36
+ __export(src_exports, {
37
+ Instruckt: () => Instruckt,
38
+ init: () => init
39
+ });
40
+ module.exports = __toCommonJS(src_exports);
41
+
42
+ // src/api.ts
43
+ function getCsrfToken() {
44
+ const match = document.cookie.match(/(?:^|;\s*)XSRF-TOKEN=([^;]+)/);
45
+ return match ? decodeURIComponent(match[1]) : "";
46
+ }
47
+ function headers() {
48
+ const h = {
49
+ "Content-Type": "application/json",
50
+ Accept: "application/json"
51
+ };
52
+ const csrf = getCsrfToken();
53
+ if (csrf) h["X-XSRF-TOKEN"] = csrf;
54
+ return h;
55
+ }
56
+ var InstrucktApi = class {
57
+ constructor(endpoint) {
58
+ this.endpoint = endpoint;
59
+ }
60
+ async createSession(url) {
61
+ const res = await fetch(`${this.endpoint}/sessions`, {
62
+ method: "POST",
63
+ headers: headers(),
64
+ body: JSON.stringify({ url })
65
+ });
66
+ if (!res.ok) throw new Error(`instruckt: failed to create session (${res.status})`);
67
+ return res.json();
68
+ }
69
+ async getSession(sessionId) {
70
+ const res = await fetch(`${this.endpoint}/sessions/${sessionId}`, {
71
+ headers: { Accept: "application/json" }
72
+ });
73
+ if (!res.ok) throw new Error(`instruckt: failed to get session (${res.status})`);
74
+ return res.json();
75
+ }
76
+ async addAnnotation(sessionId, data) {
77
+ const res = await fetch(`${this.endpoint}/sessions/${sessionId}/annotations`, {
78
+ method: "POST",
79
+ headers: headers(),
80
+ body: JSON.stringify(data)
81
+ });
82
+ if (!res.ok) throw new Error(`instruckt: failed to add annotation (${res.status})`);
83
+ return res.json();
84
+ }
85
+ async updateAnnotation(annotationId, data) {
86
+ const res = await fetch(`${this.endpoint}/annotations/${annotationId}`, {
87
+ method: "PATCH",
88
+ headers: headers(),
89
+ body: JSON.stringify(data)
90
+ });
91
+ if (!res.ok) throw new Error(`instruckt: failed to update annotation (${res.status})`);
92
+ return res.json();
93
+ }
94
+ async addReply(annotationId, content, role = "human") {
95
+ const res = await fetch(`${this.endpoint}/annotations/${annotationId}/reply`, {
96
+ method: "POST",
97
+ headers: headers(),
98
+ body: JSON.stringify({ role, content })
99
+ });
100
+ if (!res.ok) throw new Error(`instruckt: failed to add reply (${res.status})`);
101
+ return res.json();
102
+ }
103
+ };
104
+
105
+ // src/sse.ts
106
+ var InstrucktSSE = class {
107
+ constructor(endpoint, sessionId, onUpdate) {
108
+ this.endpoint = endpoint;
109
+ this.sessionId = sessionId;
110
+ this.onUpdate = onUpdate;
111
+ this.source = null;
112
+ }
113
+ connect() {
114
+ if (this.source) return;
115
+ this.source = new EventSource(`${this.endpoint}/sessions/${this.sessionId}/events`);
116
+ this.source.addEventListener("annotation.updated", (e) => {
117
+ try {
118
+ const annotation = JSON.parse(e.data);
119
+ this.onUpdate(annotation);
120
+ } catch (e2) {
121
+ }
122
+ });
123
+ this.source.onerror = () => {
124
+ };
125
+ }
126
+ disconnect() {
127
+ var _a;
128
+ (_a = this.source) == null ? void 0 : _a.close();
129
+ this.source = null;
130
+ }
131
+ };
132
+
133
+ // src/ui/styles.ts
134
+ var GLOBAL_CSS = (
135
+ /* css */
136
+ `
137
+ body.ik-annotating,
138
+ body.ik-annotating * { cursor: crosshair !important; }
139
+ `
140
+ );
141
+ var TOOLBAR_CSS = (
142
+ /* css */
143
+ `
144
+ :host {
145
+ all: initial;
146
+ display: block;
147
+ position: fixed;
148
+ z-index: 2147483646;
149
+ }
150
+
151
+ * { box-sizing: border-box; }
152
+
153
+ :host-context([data-instruckt-theme="dark"]),
154
+ @media (prefers-color-scheme: dark) {
155
+ :host {
156
+ --ik-bg: #1c1c1e; --ik-bg2: #2c2c2e; --ik-border: #3a3a3c;
157
+ --ik-text: #f4f4f5; --ik-muted: #a1a1aa;
158
+ --ik-shadow: 0 4px 24px rgba(0,0,0,.5);
159
+ }
160
+ }
161
+
162
+ :host {
163
+ --ik-accent: #6366f1;
164
+ --ik-accent-h: #4f46e5;
165
+ --ik-bg: #ffffff;
166
+ --ik-bg2: #f8f8f8;
167
+ --ik-border: #e4e4e7;
168
+ --ik-text: #18181b;
169
+ --ik-muted: #71717a;
170
+ --ik-shadow: 0 4px 24px rgba(0,0,0,.12);
171
+ }
172
+
173
+ .toolbar {
174
+ display: flex;
175
+ flex-direction: column;
176
+ align-items: center;
177
+ gap: 6px;
178
+ background: var(--ik-bg);
179
+ border: 1px solid var(--ik-border);
180
+ border-radius: 14px;
181
+ padding: 8px 6px;
182
+ box-shadow: var(--ik-shadow);
183
+ user-select: none;
184
+ touch-action: none;
185
+ cursor: grab;
186
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
187
+ }
188
+ .toolbar:active { cursor: grabbing; }
189
+
190
+ .btn {
191
+ display: flex;
192
+ align-items: center;
193
+ justify-content: center;
194
+ width: 36px;
195
+ height: 36px;
196
+ border-radius: 8px;
197
+ border: none;
198
+ background: transparent;
199
+ color: var(--ik-muted);
200
+ cursor: pointer;
201
+ padding: 0;
202
+ font-size: 17px;
203
+ line-height: 1;
204
+ position: relative;
205
+ transition: background .1s, color .1s;
206
+ }
207
+ .btn:hover { background: var(--ik-bg2); color: var(--ik-text); }
208
+ .btn.active { background: var(--ik-accent); color: #fff; }
209
+ .btn.active:hover { background: var(--ik-accent-h); }
210
+
211
+ .divider { width: 20px; height: 1px; background: var(--ik-border); margin: 2px 0; }
212
+
213
+ .badge {
214
+ position: absolute;
215
+ top: -4px; right: -4px;
216
+ min-width: 16px; height: 16px;
217
+ background: var(--ik-accent);
218
+ color: #fff;
219
+ border-radius: 8px;
220
+ font-size: 10px; font-weight: 700;
221
+ display: flex; align-items: center; justify-content: center;
222
+ padding: 0 3px;
223
+ }
224
+ `
225
+ );
226
+ var POPUP_CSS = (
227
+ /* css */
228
+ `
229
+ :host {
230
+ all: initial;
231
+ display: block;
232
+ position: fixed;
233
+ z-index: 2147483647;
234
+ }
235
+
236
+ * { box-sizing: border-box; }
237
+
238
+ :host {
239
+ --ik-accent: #6366f1;
240
+ --ik-accent-h: #4f46e5;
241
+ --ik-bg: #ffffff;
242
+ --ik-bg2: #f8f8f8;
243
+ --ik-border: #e4e4e7;
244
+ --ik-text: #18181b;
245
+ --ik-muted: #71717a;
246
+ --ik-shadow: 0 4px 24px rgba(0,0,0,.12);
247
+ --ik-radius: 10px;
248
+ --ik-hl: rgba(99,102,241,.15);
249
+ }
250
+
251
+ @media (prefers-color-scheme: dark) {
252
+ :host {
253
+ --ik-bg: #1c1c1e; --ik-bg2: #2c2c2e; --ik-border: #3a3a3c;
254
+ --ik-text: #f4f4f5; --ik-muted: #a1a1aa;
255
+ --ik-shadow: 0 4px 24px rgba(0,0,0,.5);
256
+ --ik-hl: rgba(99,102,241,.2);
257
+ }
258
+ }
259
+
260
+ .popup {
261
+ width: 340px;
262
+ background: var(--ik-bg);
263
+ border: 1px solid var(--ik-border);
264
+ border-radius: var(--ik-radius);
265
+ box-shadow: var(--ik-shadow);
266
+ padding: 14px;
267
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
268
+ font-size: 13px;
269
+ color: var(--ik-text);
270
+ animation: pop-in .12s ease;
271
+ }
272
+ @keyframes pop-in {
273
+ from { opacity:0; transform: scale(.95) translateY(4px); }
274
+ to { opacity:1; transform: scale(1) translateY(0); }
275
+ }
276
+
277
+ .header { display:flex; align-items:center; justify-content:space-between; margin-bottom:10px; }
278
+ .element-tag {
279
+ font-size:11px; font-family:ui-monospace,monospace; color:var(--ik-muted);
280
+ background:var(--ik-bg2); border-radius:4px; padding:2px 6px;
281
+ max-width:220px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
282
+ }
283
+ .close-btn {
284
+ background:none; border:none; color:var(--ik-muted);
285
+ cursor:pointer; font-size:18px; line-height:1; padding:0;
286
+ }
287
+
288
+ .fw-badge {
289
+ display:inline-flex; align-items:center; gap:4px;
290
+ font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.05em;
291
+ color:var(--ik-accent); background:var(--ik-hl); border-radius:4px;
292
+ padding:2px 6px; margin-bottom:8px;
293
+ }
294
+ .selected-text {
295
+ font-size:12px; color:var(--ik-muted); background:var(--ik-bg2);
296
+ border-left:3px solid var(--ik-accent); padding:4px 8px;
297
+ border-radius:0 4px 4px 0; margin-bottom:10px;
298
+ overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
299
+ }
300
+
301
+ .label {
302
+ font-size:10px; font-weight:700; text-transform:uppercase;
303
+ letter-spacing:.05em; color:var(--ik-muted); margin-bottom:4px;
304
+ }
305
+ .row { display:flex; gap:6px; margin-bottom:10px; }
306
+ .chips { display:flex; gap:4px; flex-wrap:wrap; }
307
+
308
+ .chip {
309
+ font-size:11px; padding:3px 8px; border-radius:12px;
310
+ border:1px solid var(--ik-border); background:transparent;
311
+ color:var(--ik-muted); cursor:pointer; transition:all .1s;
312
+ }
313
+ .chip:hover { border-color:var(--ik-accent); color:var(--ik-accent); }
314
+ .chip.sel { background:var(--ik-accent); border-color:var(--ik-accent); color:#fff; }
315
+ .chip.blocking.sel { background:#ef4444; border-color:#ef4444; }
316
+ .chip.important.sel { background:#f97316; border-color:#f97316; }
317
+ .chip.suggestion.sel{ background:#22c55e; border-color:#22c55e; }
318
+
319
+ textarea {
320
+ width:100%; min-height:80px; resize:vertical;
321
+ border:1px solid var(--ik-border); border-radius:6px;
322
+ background:var(--ik-bg2); color:var(--ik-text);
323
+ font-family:inherit; font-size:13px; padding:8px 10px;
324
+ outline:none; transition:border-color .15s; margin-bottom:10px;
325
+ }
326
+ textarea:focus { border-color:var(--ik-accent); }
327
+ textarea::placeholder { color:var(--ik-muted); }
328
+
329
+ .actions { display:flex; justify-content:flex-end; gap:6px; }
330
+
331
+ .btn-secondary {
332
+ padding:6px 14px; border-radius:6px; border:1px solid var(--ik-border);
333
+ background:transparent; color:var(--ik-muted); font-size:12px; cursor:pointer; transition:all .1s;
334
+ }
335
+ .btn-secondary:hover { border-color:var(--ik-muted); color:var(--ik-text); }
336
+
337
+ .btn-primary {
338
+ padding:6px 14px; border-radius:6px; border:none;
339
+ background:var(--ik-accent); color:#fff;
340
+ font-size:12px; font-weight:700; cursor:pointer; transition:background .1s;
341
+ }
342
+ .btn-primary:hover { background:var(--ik-accent-h); }
343
+ .btn-primary:disabled { opacity:.5; cursor:not-allowed; }
344
+
345
+ /* Thread view */
346
+ .thread { margin-top:10px; border-top:1px solid var(--ik-border); padding-top:10px; }
347
+ .msg { margin-bottom:8px; }
348
+ .msg-role {
349
+ font-size:10px; font-weight:700; text-transform:uppercase;
350
+ letter-spacing:.05em; margin-bottom:2px;
351
+ }
352
+ .msg-role.human { color:var(--ik-accent); }
353
+ .msg-role.agent { color:#22c55e; }
354
+ .msg-content { font-size:12px; line-height:1.5; }
355
+
356
+ .status-badge {
357
+ display:inline-flex; align-items:center; gap:4px;
358
+ font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.05em;
359
+ border-radius:4px; padding:2px 6px;
360
+ }
361
+ .status-badge.pending { background:rgba(99,102,241,.15); color:var(--ik-accent); }
362
+ .status-badge.acknowledged { background:rgba(249,115,22,.15); color:#f97316; }
363
+ .status-badge.resolved { background:rgba(34,197,94,.15); color:#22c55e; }
364
+ .status-badge.dismissed { background:var(--ik-bg2); color:var(--ik-muted); }
365
+ `
366
+ );
367
+ var MARKER_CSS = (
368
+ /* css */
369
+ `
370
+ .ik-marker {
371
+ position: absolute;
372
+ z-index: 2147483645;
373
+ width: 24px; height: 24px;
374
+ border-radius: 50%;
375
+ background: #6366f1;
376
+ color: #fff;
377
+ font-size: 11px; font-weight: 700;
378
+ display: flex; align-items: center; justify-content: center;
379
+ cursor: pointer;
380
+ box-shadow: 0 2px 8px rgba(99,102,241,.4);
381
+ transition: transform .15s ease;
382
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
383
+ pointer-events: all;
384
+ user-select: none;
385
+ }
386
+ .ik-marker:hover { transform: scale(1.15); }
387
+ .ik-marker.resolved { background: #22c55e; box-shadow: 0 2px 8px rgba(34,197,94,.4); }
388
+ .ik-marker.dismissed { background: #71717a; box-shadow: 0 2px 8px rgba(0,0,0,.2); }
389
+ .ik-marker.acknowledged { background: #f97316; box-shadow: 0 2px 8px rgba(249,115,22,.4); }
390
+ `
391
+ );
392
+ function injectGlobalStyles() {
393
+ if (document.getElementById("instruckt-global")) return;
394
+ const style = document.createElement("style");
395
+ style.id = "instruckt-global";
396
+ style.textContent = GLOBAL_CSS + MARKER_CSS;
397
+ document.head.appendChild(style);
398
+ }
399
+
400
+ // src/ui/toolbar.ts
401
+ var Toolbar = class {
402
+ constructor(position, callbacks) {
403
+ this.position = position;
404
+ this.callbacks = callbacks;
405
+ this.mode = "idle";
406
+ this.dragging = false;
407
+ this.dragOffset = { x: 0, y: 0 };
408
+ this.build();
409
+ this.setupDrag();
410
+ }
411
+ build() {
412
+ this.host = document.createElement("div");
413
+ this.host.setAttribute("data-instruckt", "toolbar");
414
+ this.shadow = this.host.attachShadow({ mode: "open" });
415
+ const style = document.createElement("style");
416
+ style.textContent = TOOLBAR_CSS;
417
+ this.shadow.appendChild(style);
418
+ const toolbar = document.createElement("div");
419
+ toolbar.className = "toolbar";
420
+ this.annotateBtn = this.makeBtn("\u270F\uFE0F", "Annotate elements (A)", () => {
421
+ const next = this.mode !== "annotating";
422
+ this.setMode(next ? "annotating" : "idle");
423
+ this.callbacks.onToggleAnnotate(next);
424
+ });
425
+ this.freezeBtn = this.makeBtn("\u23F8", "Freeze animations (F)", () => {
426
+ const next = this.mode !== "frozen";
427
+ this.setMode(next ? "frozen" : "idle");
428
+ this.callbacks.onFreezeAnimations(next);
429
+ });
430
+ this.copyBtn = this.makeBtn("\u{1F4CB}", "Copy annotations as markdown", () => {
431
+ this.callbacks.onCopy();
432
+ this.copyBtn.textContent = "\u2713";
433
+ setTimeout(() => {
434
+ this.copyBtn.textContent = "\u{1F4CB}";
435
+ }, 1200);
436
+ });
437
+ const divider = document.createElement("div");
438
+ divider.className = "divider";
439
+ const divider2 = document.createElement("div");
440
+ divider2.className = "divider";
441
+ toolbar.append(this.annotateBtn, divider, this.freezeBtn, divider2, this.copyBtn);
442
+ this.shadow.appendChild(toolbar);
443
+ this.applyPosition();
444
+ document.body.appendChild(this.host);
445
+ }
446
+ makeBtn(icon, title, onClick) {
447
+ const btn = document.createElement("button");
448
+ btn.className = "btn";
449
+ btn.title = title;
450
+ btn.setAttribute("aria-label", title);
451
+ btn.textContent = icon;
452
+ btn.addEventListener("click", (e) => {
453
+ e.stopPropagation();
454
+ onClick();
455
+ });
456
+ return btn;
457
+ }
458
+ applyPosition() {
459
+ const m = "16px";
460
+ Object.assign(this.host.style, {
461
+ position: "fixed",
462
+ zIndex: "2147483646",
463
+ bottom: this.position.includes("bottom") ? m : "auto",
464
+ top: this.position.includes("top") ? m : "auto",
465
+ right: this.position.includes("right") ? m : "auto",
466
+ left: this.position.includes("left") ? m : "auto"
467
+ });
468
+ }
469
+ setupDrag() {
470
+ this.shadow.addEventListener("mousedown", (e) => {
471
+ if (e.target.closest(".btn")) return;
472
+ this.dragging = true;
473
+ const rect = this.host.getBoundingClientRect();
474
+ this.dragOffset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
475
+ e.preventDefault();
476
+ });
477
+ document.addEventListener("mousemove", (e) => {
478
+ if (!this.dragging) return;
479
+ Object.assign(this.host.style, {
480
+ left: `${e.clientX - this.dragOffset.x}px`,
481
+ top: `${e.clientY - this.dragOffset.y}px`,
482
+ right: "auto",
483
+ bottom: "auto"
484
+ });
485
+ });
486
+ document.addEventListener("mouseup", () => {
487
+ this.dragging = false;
488
+ });
489
+ }
490
+ setMode(mode) {
491
+ this.mode = mode;
492
+ this.annotateBtn.classList.toggle("active", mode === "annotating");
493
+ this.freezeBtn.classList.toggle("active", mode === "frozen");
494
+ document.body.classList.toggle("ik-annotating", mode === "annotating");
495
+ }
496
+ setAnnotationCount(count) {
497
+ let badge = this.annotateBtn.querySelector(".badge");
498
+ if (count > 0) {
499
+ if (!badge) {
500
+ badge = document.createElement("span");
501
+ badge.className = "badge";
502
+ this.annotateBtn.appendChild(badge);
503
+ }
504
+ badge.textContent = count > 99 ? "99+" : String(count);
505
+ } else {
506
+ badge == null ? void 0 : badge.remove();
507
+ }
508
+ }
509
+ destroy() {
510
+ this.host.remove();
511
+ document.body.classList.remove("ik-annotating");
512
+ }
513
+ };
514
+
515
+ // src/ui/highlight.ts
516
+ var ElementHighlight = class {
517
+ constructor() {
518
+ this.el = document.createElement("div");
519
+ Object.assign(this.el.style, {
520
+ position: "fixed",
521
+ pointerEvents: "none",
522
+ // MUST be none — prevents swallowing clicks
523
+ zIndex: "2147483644",
524
+ border: "2px solid rgba(99,102,241,0.7)",
525
+ background: "rgba(99,102,241,0.1)",
526
+ borderRadius: "3px",
527
+ transition: "all 0.06s ease",
528
+ display: "none"
529
+ });
530
+ this.el.setAttribute("data-instruckt", "highlight");
531
+ document.body.appendChild(this.el);
532
+ }
533
+ show(el) {
534
+ const rect = el.getBoundingClientRect();
535
+ if (rect.width === 0 && rect.height === 0) {
536
+ this.hide();
537
+ return;
538
+ }
539
+ Object.assign(this.el.style, {
540
+ display: "block",
541
+ left: `${rect.left}px`,
542
+ top: `${rect.top}px`,
543
+ width: `${rect.width}px`,
544
+ height: `${rect.height}px`
545
+ });
546
+ }
547
+ hide() {
548
+ this.el.style.display = "none";
549
+ }
550
+ destroy() {
551
+ this.el.remove();
552
+ }
553
+ };
554
+
555
+ // src/ui/popup.ts
556
+ function esc(s) {
557
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
558
+ }
559
+ function fwIcon(fw) {
560
+ var _a;
561
+ return (_a = { livewire: "\u26A1", vue: "\u{1F49A}", svelte: "\u{1F9E1}" }[fw]) != null ? _a : "\u{1F527}";
562
+ }
563
+ var AnnotationPopup = class {
564
+ constructor() {
565
+ this.host = null;
566
+ this.shadow = null;
567
+ this.intent = "fix";
568
+ this.severity = "important";
569
+ this.boundOutside = (e) => {
570
+ if (this.host && !this.host.contains(e.target)) {
571
+ this.destroy();
572
+ }
573
+ };
574
+ }
575
+ // ── New annotation popup ──────────────────────────────────────
576
+ showNew(pending, callbacks) {
577
+ this.destroy();
578
+ this.host = document.createElement("div");
579
+ this.host.setAttribute("data-instruckt", "popup");
580
+ this.shadow = this.host.attachShadow({ mode: "open" });
581
+ const style = document.createElement("style");
582
+ style.textContent = POPUP_CSS;
583
+ this.shadow.appendChild(style);
584
+ const popup = document.createElement("div");
585
+ popup.className = "popup";
586
+ const fwBadge = pending.framework ? `<div class="fw-badge">${fwIcon(pending.framework.framework)} ${esc(pending.framework.component)}</div>` : "";
587
+ const selText = pending.selectedText ? `<div class="selected-text">"${esc(pending.selectedText.slice(0, 80))}"</div>` : "";
588
+ popup.innerHTML = `
589
+ <div class="header">
590
+ <span class="element-tag" title="${esc(pending.elementPath)}">${esc(pending.elementName)}</span>
591
+ <button class="close-btn" title="Cancel (Esc)">\u2715</button>
592
+ </div>
593
+ ${fwBadge}${selText}
594
+ <div class="label">Intent</div>
595
+ <div class="row">
596
+ <div class="chips" data-group="intent">
597
+ <button class="chip sel" data-value="fix">Fix</button>
598
+ <button class="chip" data-value="change">Change</button>
599
+ <button class="chip" data-value="question">Question</button>
600
+ <button class="chip" data-value="approve">Approve</button>
601
+ </div>
602
+ </div>
603
+ <div class="label">Severity</div>
604
+ <div class="row">
605
+ <div class="chips" data-group="severity">
606
+ <button class="chip blocking" data-value="blocking">Blocking</button>
607
+ <button class="chip important sel" data-value="important">Important</button>
608
+ <button class="chip suggestion" data-value="suggestion">Suggestion</button>
609
+ </div>
610
+ </div>
611
+ <textarea placeholder="Describe what you'd like changed\u2026" rows="3"></textarea>
612
+ <div class="actions">
613
+ <button class="btn-secondary" data-action="cancel">Cancel</button>
614
+ <button class="btn-primary" data-action="submit" disabled>Add note</button>
615
+ </div>
616
+ `;
617
+ this.wireChips(popup, "intent", (v) => {
618
+ this.intent = v;
619
+ });
620
+ this.wireChips(popup, "severity", (v) => {
621
+ this.severity = v;
622
+ });
623
+ const textarea = popup.querySelector("textarea");
624
+ const submitBtn = popup.querySelector('[data-action="submit"]');
625
+ textarea.addEventListener("input", () => {
626
+ submitBtn.disabled = textarea.value.trim().length === 0;
627
+ });
628
+ textarea.addEventListener("keydown", (e) => {
629
+ if (e.key === "Enter" && !e.shiftKey) {
630
+ e.preventDefault();
631
+ if (!submitBtn.disabled) submitBtn.click();
632
+ }
633
+ if (e.key === "Escape") {
634
+ callbacks.onCancel();
635
+ this.destroy();
636
+ }
637
+ });
638
+ popup.querySelector('[data-action="cancel"]').addEventListener("click", () => {
639
+ callbacks.onCancel();
640
+ this.destroy();
641
+ });
642
+ popup.querySelector(".close-btn").addEventListener("click", () => {
643
+ callbacks.onCancel();
644
+ this.destroy();
645
+ });
646
+ submitBtn.addEventListener("click", () => {
647
+ const comment = textarea.value.trim();
648
+ if (!comment) return;
649
+ callbacks.onSubmit({ comment, intent: this.intent, severity: this.severity });
650
+ this.destroy();
651
+ });
652
+ this.shadow.appendChild(popup);
653
+ document.body.appendChild(this.host);
654
+ this.positionHost(pending.x, pending.y);
655
+ this.setupOutsideClick();
656
+ textarea.focus();
657
+ }
658
+ // ── Thread / existing annotation popup ───────────────────────
659
+ showThread(annotation, callbacks) {
660
+ var _a;
661
+ this.destroy();
662
+ this.host = document.createElement("div");
663
+ this.host.setAttribute("data-instruckt", "popup");
664
+ this.shadow = this.host.attachShadow({ mode: "open" });
665
+ const style = document.createElement("style");
666
+ style.textContent = POPUP_CSS;
667
+ this.shadow.appendChild(style);
668
+ const popup = document.createElement("div");
669
+ popup.className = "popup";
670
+ const statusLabel = (s) => `<span class="status-badge ${esc(s)}">${esc(s)}</span>`;
671
+ const thread = ((_a = annotation.thread) != null ? _a : []).map((m) => `
672
+ <div class="msg">
673
+ <div class="msg-role ${esc(m.role)}">${m.role === "agent" ? "\u{1F916} Agent" : "\u{1F464} You"}</div>
674
+ <div class="msg-content">${esc(m.content)}</div>
675
+ </div>
676
+ `).join("");
677
+ const isPending = ["pending", "acknowledged"].includes(annotation.status);
678
+ popup.innerHTML = `
679
+ <div class="header">
680
+ <span class="element-tag">${esc(annotation.element)}</span>
681
+ <button class="close-btn">\u2715</button>
682
+ </div>
683
+ ${statusLabel(annotation.status)}
684
+ <div class="selected-text" style="margin-top:8px;">${esc(annotation.comment)}</div>
685
+ ${thread ? `<div class="thread">${thread}</div>` : ""}
686
+ ${isPending ? `
687
+ <div class="thread" style="margin-top:8px;">
688
+ <textarea placeholder="Add a reply\u2026" rows="2"></textarea>
689
+ <div class="actions" style="margin-top:6px;">
690
+ <button class="btn-secondary" data-action="resolve">Mark resolved</button>
691
+ <button class="btn-primary" data-action="reply" disabled>Reply</button>
692
+ </div>
693
+ </div>
694
+ ` : ""}
695
+ `;
696
+ popup.querySelector(".close-btn").addEventListener("click", () => this.destroy());
697
+ if (isPending) {
698
+ const textarea = popup.querySelector("textarea");
699
+ const replyBtn = popup.querySelector('[data-action="reply"]');
700
+ textarea.addEventListener("input", () => {
701
+ replyBtn.disabled = textarea.value.trim().length === 0;
702
+ });
703
+ replyBtn.addEventListener("click", () => {
704
+ const content = textarea.value.trim();
705
+ if (!content) return;
706
+ callbacks.onReply(annotation, content);
707
+ this.destroy();
708
+ });
709
+ popup.querySelector('[data-action="resolve"]').addEventListener("click", () => {
710
+ callbacks.onResolve(annotation);
711
+ this.destroy();
712
+ });
713
+ }
714
+ this.shadow.appendChild(popup);
715
+ document.body.appendChild(this.host);
716
+ this.positionHost(window.innerWidth / 2 - 170, window.innerHeight / 2 - 150);
717
+ this.setupOutsideClick();
718
+ }
719
+ // ── Helpers ───────────────────────────────────────────────────
720
+ wireChips(container, group, onChange) {
721
+ container.querySelectorAll(`[data-group="${group}"] .chip`).forEach((btn) => {
722
+ btn.addEventListener("click", () => {
723
+ container.querySelectorAll(`[data-group="${group}"] .chip`).forEach((b) => b.classList.remove("sel"));
724
+ btn.classList.add("sel");
725
+ onChange(btn.dataset.value);
726
+ });
727
+ });
728
+ }
729
+ positionHost(x, y) {
730
+ if (!this.host) return;
731
+ Object.assign(this.host.style, { position: "fixed", zIndex: "2147483647", left: "-9999px", top: "0" });
732
+ requestAnimationFrame(() => {
733
+ var _a, _b;
734
+ if (!this.host) return;
735
+ const w = 340 + 20;
736
+ const h = (_b = (_a = this.host.querySelector(".popup")) == null ? void 0 : _a.getBoundingClientRect().height) != null ? _b : 300;
737
+ const vw = window.innerWidth;
738
+ const vh = window.innerHeight;
739
+ const left = Math.max(10, Math.min(x + 10, vw - w));
740
+ const top = Math.max(10, Math.min(y + 10, vh - h - 10));
741
+ Object.assign(this.host.style, { left: `${left}px`, top: `${top}px` });
742
+ });
743
+ }
744
+ setupOutsideClick() {
745
+ setTimeout(() => document.addEventListener("mousedown", this.boundOutside), 0);
746
+ }
747
+ destroy() {
748
+ var _a;
749
+ (_a = this.host) == null ? void 0 : _a.remove();
750
+ this.host = null;
751
+ this.shadow = null;
752
+ document.removeEventListener("mousedown", this.boundOutside);
753
+ }
754
+ };
755
+
756
+ // src/ui/markers.ts
757
+ var AnnotationMarkers = class {
758
+ constructor(onClick) {
759
+ this.onClick = onClick;
760
+ this.markers = /* @__PURE__ */ new Map();
761
+ this.container = document.createElement("div");
762
+ Object.assign(this.container.style, {
763
+ position: "fixed",
764
+ inset: "0",
765
+ pointerEvents: "none",
766
+ zIndex: "2147483645"
767
+ });
768
+ this.container.setAttribute("data-instruckt", "markers");
769
+ document.body.appendChild(this.container);
770
+ }
771
+ /** Add or update a marker for an annotation */
772
+ upsert(annotation, index) {
773
+ const existing = this.markers.get(annotation.id);
774
+ if (existing) {
775
+ this.updateStyle(existing.el, annotation);
776
+ return;
777
+ }
778
+ const el = document.createElement("div");
779
+ el.className = `ik-marker ${this.statusClass(annotation.status)}`;
780
+ el.textContent = String(index);
781
+ el.title = annotation.comment.slice(0, 60);
782
+ el.style.pointerEvents = "all";
783
+ el.style.left = `${annotation.x / 100 * window.innerWidth}px`;
784
+ el.style.top = `${annotation.y - window.scrollY}px`;
785
+ el.addEventListener("click", (e) => {
786
+ e.stopPropagation();
787
+ this.onClick(annotation);
788
+ });
789
+ this.container.appendChild(el);
790
+ this.markers.set(annotation.id, { el, annotationId: annotation.id });
791
+ }
792
+ /** Update an existing marker after its annotation status changed */
793
+ update(annotation) {
794
+ const marker = this.markers.get(annotation.id);
795
+ if (!marker) return;
796
+ this.updateStyle(marker.el, annotation);
797
+ }
798
+ updateStyle(el, annotation) {
799
+ el.className = `ik-marker ${this.statusClass(annotation.status)}`;
800
+ el.title = annotation.comment.slice(0, 60);
801
+ }
802
+ statusClass(status) {
803
+ if (status === "resolved") return "resolved";
804
+ if (status === "dismissed") return "dismissed";
805
+ if (status === "acknowledged") return "acknowledged";
806
+ return "";
807
+ }
808
+ /** Reposition all markers (e.g. after scroll or resize) */
809
+ reposition(annotations) {
810
+ annotations.forEach((annotation) => {
811
+ const marker = this.markers.get(annotation.id);
812
+ if (!marker) return;
813
+ marker.el.style.left = `${annotation.x / 100 * window.innerWidth}px`;
814
+ marker.el.style.top = `${annotation.y - window.scrollY}px`;
815
+ });
816
+ }
817
+ remove(annotationId) {
818
+ const marker = this.markers.get(annotationId);
819
+ if (!marker) return;
820
+ marker.el.remove();
821
+ this.markers.delete(annotationId);
822
+ }
823
+ destroy() {
824
+ this.container.remove();
825
+ this.markers.clear();
826
+ }
827
+ };
828
+
829
+ // src/selector.ts
830
+ function getElementSelector(el) {
831
+ if (el.id) {
832
+ return `#${CSS.escape(el.id)}`;
833
+ }
834
+ const path = [];
835
+ let current = el;
836
+ while (current && current !== document.documentElement) {
837
+ const tag = current.tagName.toLowerCase();
838
+ const parent = current.parentElement;
839
+ if (!parent) {
840
+ path.unshift(tag);
841
+ break;
842
+ }
843
+ const classes = Array.from(current.classList).filter((c) => !c.match(/^(hover|focus|active|visited|is-|has-)/)).slice(0, 3);
844
+ if (classes.length > 0) {
845
+ const classSelector = `${tag}.${classes.map(CSS.escape).join(".")}`;
846
+ const matches = parent.querySelectorAll(classSelector);
847
+ if (matches.length === 1) {
848
+ path.unshift(classSelector);
849
+ break;
850
+ }
851
+ }
852
+ const siblings = Array.from(parent.children).filter((c) => c.tagName === current.tagName);
853
+ if (siblings.length === 1) {
854
+ path.unshift(tag);
855
+ } else {
856
+ const index = siblings.indexOf(current) + 1;
857
+ path.unshift(`${tag}:nth-of-type(${index})`);
858
+ }
859
+ current = parent;
860
+ }
861
+ return path.join(" > ");
862
+ }
863
+ function getElementName(el) {
864
+ const wireModel = el.getAttribute("wire:model") || el.getAttribute("wire:click");
865
+ if (wireModel) return `wire:${wireModel.split(".")[0]}`;
866
+ const ariaLabel = el.getAttribute("aria-label");
867
+ if (ariaLabel) return ariaLabel;
868
+ const id = el.id;
869
+ if (id) return `#${id}`;
870
+ const tag = el.tagName.toLowerCase();
871
+ const role = el.getAttribute("role");
872
+ if (role) return `${tag}[${role}]`;
873
+ const firstClass = el.classList[0];
874
+ if (firstClass) return `${tag}.${firstClass}`;
875
+ return tag;
876
+ }
877
+ function getNearbyText(el) {
878
+ const text = (el.textContent || "").trim().replace(/\s+/g, " ");
879
+ return text.slice(0, 120);
880
+ }
881
+ function getCssClasses(el) {
882
+ return Array.from(el.classList).filter((c) => !c.match(/^(instruckt-)/)).join(" ");
883
+ }
884
+ function getPageBoundingBox(el) {
885
+ const rect = el.getBoundingClientRect();
886
+ return {
887
+ x: rect.left + window.scrollX,
888
+ y: rect.top + window.scrollY,
889
+ width: rect.width,
890
+ height: rect.height
891
+ };
892
+ }
893
+
894
+ // src/adapters/livewire.ts
895
+ function isAvailable() {
896
+ return typeof window.Livewire !== "undefined";
897
+ }
898
+ function detect(el) {
899
+ let node = el;
900
+ while (node && node !== document.documentElement) {
901
+ const wireId = node.getAttribute("wire:id");
902
+ if (wireId) return wireId;
903
+ node = node.parentElement;
904
+ }
905
+ return null;
906
+ }
907
+ function getContext(el) {
908
+ var _a, _b;
909
+ if (!isAvailable()) return null;
910
+ const wireId = detect(el);
911
+ if (!wireId) return null;
912
+ const component = window.Livewire.find(wireId);
913
+ if (!component) return null;
914
+ const snapshotData = (_b = (_a = component.snapshot) == null ? void 0 : _a.data) != null ? _b : {};
915
+ const data = {};
916
+ for (const key of Object.keys(snapshotData)) {
917
+ try {
918
+ data[key] = component.get(key);
919
+ } catch (e) {
920
+ }
921
+ }
922
+ return {
923
+ framework: "livewire",
924
+ component: component.name,
925
+ wire_id: wireId,
926
+ data
927
+ };
928
+ }
929
+
930
+ // src/adapters/vue.ts
931
+ function detect2(el) {
932
+ var _a;
933
+ let node = el;
934
+ while (node && node !== document.documentElement) {
935
+ const instance = (_a = node.__vueParentComponent) != null ? _a : node.__vue__;
936
+ if (instance) return instance;
937
+ node = node.parentElement;
938
+ }
939
+ return null;
940
+ }
941
+ function getContext2(el) {
942
+ var _a, _b, _c, _d, _e, _f, _g, _h;
943
+ const instance = detect2(el);
944
+ if (!instance) return null;
945
+ const name = (_h = (_g = (_e = (_c = (_a = instance.$options) == null ? void 0 : _a.name) != null ? _c : (_b = instance.$options) == null ? void 0 : _b.__name) != null ? _e : (_d = instance.type) == null ? void 0 : _d.name) != null ? _g : (_f = instance.type) == null ? void 0 : _f.__name) != null ? _h : "Anonymous";
946
+ const data = {};
947
+ if (instance.props) {
948
+ Object.assign(data, instance.props);
949
+ }
950
+ if (instance.setupState) {
951
+ for (const [key, value] of Object.entries(instance.setupState)) {
952
+ if (!key.startsWith("_") && typeof value !== "function") {
953
+ try {
954
+ data[key] = JSON.parse(JSON.stringify(value));
955
+ } catch (e) {
956
+ data[key] = String(value);
957
+ }
958
+ }
959
+ }
960
+ }
961
+ return {
962
+ framework: "vue",
963
+ component: name,
964
+ component_uid: instance.uid !== void 0 ? String(instance.uid) : void 0,
965
+ data
966
+ };
967
+ }
968
+
969
+ // src/adapters/svelte.ts
970
+ function detect3(el) {
971
+ let node = el;
972
+ while (node && node !== document.documentElement) {
973
+ if (node.__svelte_meta) return node.__svelte_meta;
974
+ node = node.parentElement;
975
+ }
976
+ return null;
977
+ }
978
+ function getContext3(el) {
979
+ var _a, _b, _c, _d;
980
+ const meta = detect3(el);
981
+ if (!meta) return null;
982
+ const filePath = (_b = (_a = meta.loc) == null ? void 0 : _a.file) != null ? _b : "";
983
+ const component = filePath ? (_d = (_c = filePath.split("/").pop()) == null ? void 0 : _c.replace(/\.svelte$/, "")) != null ? _d : "Unknown" : "Unknown";
984
+ return {
985
+ framework: "svelte",
986
+ component,
987
+ data: filePath ? { file: filePath } : void 0
988
+ };
989
+ }
990
+
991
+ // src/adapters/react.ts
992
+ function getFiberKey(el) {
993
+ for (const key of Object.keys(el)) {
994
+ if (key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$")) {
995
+ return key;
996
+ }
997
+ }
998
+ return null;
999
+ }
1000
+ function getComponentName(fiber) {
1001
+ let node = fiber;
1002
+ while (node) {
1003
+ const { type } = node;
1004
+ if (typeof type === "function" && type.name) {
1005
+ const name = type.name;
1006
+ if (name[0] === name[0].toUpperCase() && name.length > 1) return name;
1007
+ }
1008
+ if (typeof type === "object" && type !== null && type.displayName) {
1009
+ return type.displayName;
1010
+ }
1011
+ node = node.return;
1012
+ }
1013
+ return "Component";
1014
+ }
1015
+ function getProps(fiber) {
1016
+ var _a, _b;
1017
+ const props = (_b = (_a = fiber.memoizedProps) != null ? _a : fiber.pendingProps) != null ? _b : {};
1018
+ const result = {};
1019
+ for (const [k, v] of Object.entries(props)) {
1020
+ if (k === "children" || typeof v === "function") continue;
1021
+ try {
1022
+ result[k] = JSON.parse(JSON.stringify(v));
1023
+ } catch (e) {
1024
+ result[k] = String(v);
1025
+ }
1026
+ }
1027
+ return result;
1028
+ }
1029
+ function getContext4(el) {
1030
+ let node = el;
1031
+ while (node && node !== document.documentElement) {
1032
+ const key = getFiberKey(node);
1033
+ if (key) {
1034
+ const fiber = node[key];
1035
+ if (fiber) {
1036
+ const component = getComponentName(fiber);
1037
+ const data = getProps(fiber);
1038
+ return { framework: "react", component, data };
1039
+ }
1040
+ }
1041
+ node = node.parentElement;
1042
+ }
1043
+ return null;
1044
+ }
1045
+
1046
+ // src/instruckt.ts
1047
+ var SESSION_KEY = "instruckt_session";
1048
+ var Instruckt = class {
1049
+ constructor(config) {
1050
+ this.sse = null;
1051
+ this.toolbar = null;
1052
+ this.highlight = null;
1053
+ this.popup = null;
1054
+ this.markers = null;
1055
+ this.annotations = [];
1056
+ this.session = null;
1057
+ this.isAnnotating = false;
1058
+ this.isFrozen = false;
1059
+ this.frozenStyleEl = null;
1060
+ this.rafId = null;
1061
+ this.pendingMouseTarget = null;
1062
+ this.mutationObserver = null;
1063
+ // ── Event listeners ───────────────────────────────────────────
1064
+ this.boundMouseMove = (e) => {
1065
+ this.pendingMouseTarget = e.target;
1066
+ if (this.rafId === null) {
1067
+ this.rafId = requestAnimationFrame(() => {
1068
+ var _a, _b;
1069
+ this.rafId = null;
1070
+ if (this.pendingMouseTarget && !this.isInstruckt(this.pendingMouseTarget)) {
1071
+ (_a = this.highlight) == null ? void 0 : _a.show(this.pendingMouseTarget);
1072
+ } else {
1073
+ (_b = this.highlight) == null ? void 0 : _b.hide();
1074
+ }
1075
+ });
1076
+ }
1077
+ };
1078
+ this.boundMouseLeave = () => {
1079
+ var _a;
1080
+ (_a = this.highlight) == null ? void 0 : _a.hide();
1081
+ };
1082
+ this.boundClick = (e) => {
1083
+ var _a, _b, _c;
1084
+ const target = e.target;
1085
+ if (this.isInstruckt(target)) return;
1086
+ e.preventDefault();
1087
+ e.stopPropagation();
1088
+ const selectedText = ((_a = window.getSelection()) == null ? void 0 : _a.toString().trim()) || void 0;
1089
+ const elementPath = getElementSelector(target);
1090
+ const elementName = getElementName(target);
1091
+ const cssClasses = getCssClasses(target);
1092
+ const nearbyText = getNearbyText(target) || void 0;
1093
+ const boundingBox = getPageBoundingBox(target);
1094
+ const framework = (_b = this.detectFramework(target)) != null ? _b : void 0;
1095
+ const pending = {
1096
+ element: target,
1097
+ elementPath,
1098
+ elementName,
1099
+ cssClasses,
1100
+ boundingBox,
1101
+ x: e.clientX,
1102
+ y: e.clientY,
1103
+ selectedText,
1104
+ nearbyText,
1105
+ framework
1106
+ };
1107
+ (_c = this.popup) == null ? void 0 : _c.showNew(pending, {
1108
+ onSubmit: (result) => this.submitAnnotation(pending, result),
1109
+ onCancel: () => {
1110
+ }
1111
+ });
1112
+ };
1113
+ this.config = __spreadValues({
1114
+ adapters: ["livewire", "vue", "svelte", "react"],
1115
+ theme: "auto",
1116
+ position: "bottom-right"
1117
+ }, config);
1118
+ this.api = new InstrucktApi(config.endpoint);
1119
+ this.boundKeydown = this.onKeydown.bind(this);
1120
+ this.boundScroll = this.onScrollResize.bind(this);
1121
+ this.boundResize = this.onScrollResize.bind(this);
1122
+ this.init();
1123
+ }
1124
+ async init() {
1125
+ injectGlobalStyles();
1126
+ if (this.config.theme !== "auto") {
1127
+ document.documentElement.setAttribute("data-instruckt-theme", this.config.theme);
1128
+ }
1129
+ this.toolbar = new Toolbar(this.config.position, {
1130
+ onToggleAnnotate: (active) => this.setAnnotating(active),
1131
+ onFreezeAnimations: (frozen) => this.setFrozen(frozen),
1132
+ onCopy: () => this.copyAnnotations()
1133
+ });
1134
+ this.highlight = new ElementHighlight();
1135
+ this.popup = new AnnotationPopup();
1136
+ this.markers = new AnnotationMarkers((annotation) => this.onMarkerClick(annotation));
1137
+ document.addEventListener("keydown", this.boundKeydown);
1138
+ window.addEventListener("scroll", this.boundScroll, { passive: true });
1139
+ window.addEventListener("resize", this.boundResize, { passive: true });
1140
+ this.setupMutationObserver();
1141
+ await this.connectSession();
1142
+ }
1143
+ // ── Session ───────────────────────────────────────────────────
1144
+ async connectSession() {
1145
+ var _a, _b, _c, _d;
1146
+ const stored = sessionStorage.getItem(SESSION_KEY);
1147
+ if (stored) {
1148
+ try {
1149
+ const data = await this.api.getSession(stored);
1150
+ this.session = data;
1151
+ this.annotations = (_a = data.annotations) != null ? _a : [];
1152
+ this.syncMarkersFromAnnotations();
1153
+ (_b = this.toolbar) == null ? void 0 : _b.setAnnotationCount(this.pendingCount());
1154
+ this.connectSSE(stored);
1155
+ return;
1156
+ } catch (e) {
1157
+ sessionStorage.removeItem(SESSION_KEY);
1158
+ }
1159
+ }
1160
+ try {
1161
+ this.session = await this.api.createSession(window.location.href);
1162
+ sessionStorage.setItem(SESSION_KEY, this.session.id);
1163
+ (_d = (_c = this.config).onSessionCreate) == null ? void 0 : _d.call(_c, this.session);
1164
+ this.connectSSE(this.session.id);
1165
+ } catch (e) {
1166
+ console.warn("[instruckt] Could not connect to server \u2014 running offline.");
1167
+ }
1168
+ }
1169
+ connectSSE(sessionId) {
1170
+ this.sse = new InstrucktSSE(this.config.endpoint, sessionId, (annotation) => {
1171
+ this.onAnnotationUpdated(annotation);
1172
+ });
1173
+ this.sse.connect();
1174
+ }
1175
+ // ── Annotate mode ─────────────────────────────────────────────
1176
+ setAnnotating(active) {
1177
+ var _a;
1178
+ if (active && this.isFrozen) {
1179
+ this.setFrozen(false);
1180
+ }
1181
+ this.isAnnotating = active;
1182
+ if (active) {
1183
+ this.attachAnnotateListeners();
1184
+ } else {
1185
+ this.detachAnnotateListeners();
1186
+ (_a = this.highlight) == null ? void 0 : _a.hide();
1187
+ if (this.rafId !== null) {
1188
+ cancelAnimationFrame(this.rafId);
1189
+ this.rafId = null;
1190
+ }
1191
+ }
1192
+ }
1193
+ setFrozen(frozen) {
1194
+ var _a, _b;
1195
+ if (frozen && this.isAnnotating) {
1196
+ this.setAnnotating(false);
1197
+ (_a = this.toolbar) == null ? void 0 : _a.setMode("idle");
1198
+ }
1199
+ this.isFrozen = frozen;
1200
+ if (frozen) {
1201
+ this.frozenStyleEl = document.createElement("style");
1202
+ this.frozenStyleEl.id = "instruckt-freeze";
1203
+ this.frozenStyleEl.textContent = `
1204
+ *, *::before, *::after { animation-play-state: paused !important; transition: none !important; }
1205
+ video { filter: none !important; }
1206
+ `;
1207
+ document.head.appendChild(this.frozenStyleEl);
1208
+ } else {
1209
+ (_b = this.frozenStyleEl) == null ? void 0 : _b.remove();
1210
+ this.frozenStyleEl = null;
1211
+ }
1212
+ }
1213
+ attachAnnotateListeners() {
1214
+ document.addEventListener("mousemove", this.boundMouseMove);
1215
+ document.addEventListener("mouseleave", this.boundMouseLeave);
1216
+ document.addEventListener("click", this.boundClick, true);
1217
+ }
1218
+ detachAnnotateListeners() {
1219
+ document.removeEventListener("mousemove", this.boundMouseMove);
1220
+ document.removeEventListener("mouseleave", this.boundMouseLeave);
1221
+ document.removeEventListener("click", this.boundClick, true);
1222
+ }
1223
+ isInstruckt(el) {
1224
+ return el.closest("[data-instruckt]") !== null;
1225
+ }
1226
+ // ── Framework detection ───────────────────────────────────────
1227
+ detectFramework(el) {
1228
+ var _a;
1229
+ const adapters = (_a = this.config.adapters) != null ? _a : [];
1230
+ if (adapters.includes("livewire")) {
1231
+ const ctx = getContext(el);
1232
+ if (ctx) return ctx;
1233
+ }
1234
+ if (adapters.includes("vue")) {
1235
+ const ctx = getContext2(el);
1236
+ if (ctx) return ctx;
1237
+ }
1238
+ if (adapters.includes("svelte")) {
1239
+ const ctx = getContext3(el);
1240
+ if (ctx) return ctx;
1241
+ }
1242
+ if (adapters.includes("react")) {
1243
+ const ctx = getContext4(el);
1244
+ if (ctx) return ctx;
1245
+ }
1246
+ return null;
1247
+ }
1248
+ // ── Submit ────────────────────────────────────────────────────
1249
+ async submitAnnotation(pending, result) {
1250
+ var _a, _b, _c, _d;
1251
+ if (!this.session) {
1252
+ await this.connectSession();
1253
+ if (!this.session) {
1254
+ console.warn("[instruckt] No session \u2014 annotation not saved.");
1255
+ return;
1256
+ }
1257
+ }
1258
+ const payload = {
1259
+ x: pending.x / window.innerWidth * 100,
1260
+ y: pending.y + window.scrollY,
1261
+ comment: result.comment,
1262
+ element: pending.elementName,
1263
+ elementPath: pending.elementPath,
1264
+ cssClasses: pending.cssClasses,
1265
+ boundingBox: pending.boundingBox,
1266
+ selectedText: pending.selectedText,
1267
+ nearbyText: pending.nearbyText,
1268
+ intent: result.intent,
1269
+ severity: result.severity,
1270
+ framework: pending.framework,
1271
+ url: window.location.href
1272
+ };
1273
+ try {
1274
+ const annotation = await this.api.addAnnotation(this.session.id, payload);
1275
+ this.annotations.push(annotation);
1276
+ (_a = this.markers) == null ? void 0 : _a.upsert(annotation, this.annotations.length);
1277
+ (_b = this.toolbar) == null ? void 0 : _b.setAnnotationCount(this.pendingCount());
1278
+ (_d = (_c = this.config).onAnnotationAdd) == null ? void 0 : _d.call(_c, annotation);
1279
+ } catch (err) {
1280
+ console.error("[instruckt] Failed to save annotation:", err);
1281
+ }
1282
+ }
1283
+ // ── Marker click — show thread ────────────────────────────────
1284
+ onMarkerClick(annotation) {
1285
+ var _a;
1286
+ (_a = this.popup) == null ? void 0 : _a.showThread(annotation, {
1287
+ onResolve: async (a) => {
1288
+ try {
1289
+ const updated = await this.api.updateAnnotation(a.id, { status: "resolved" });
1290
+ this.onAnnotationUpdated(updated);
1291
+ } catch (err) {
1292
+ console.error("[instruckt] Failed to resolve annotation:", err);
1293
+ }
1294
+ },
1295
+ onReply: async (a, content) => {
1296
+ try {
1297
+ const updated = await this.api.addReply(a.id, content, "human");
1298
+ this.onAnnotationUpdated(updated);
1299
+ } catch (err) {
1300
+ console.error("[instruckt] Failed to add reply:", err);
1301
+ }
1302
+ }
1303
+ });
1304
+ }
1305
+ // ── SSE updates ───────────────────────────────────────────────
1306
+ onAnnotationUpdated(updated) {
1307
+ var _a, _b, _c, _d, _e;
1308
+ const idx = this.annotations.findIndex((a) => a.id === updated.id);
1309
+ if (idx >= 0) {
1310
+ this.annotations[idx] = updated;
1311
+ (_a = this.markers) == null ? void 0 : _a.update(updated);
1312
+ } else {
1313
+ this.annotations.push(updated);
1314
+ (_b = this.markers) == null ? void 0 : _b.upsert(updated, this.annotations.length);
1315
+ }
1316
+ (_c = this.toolbar) == null ? void 0 : _c.setAnnotationCount(this.pendingCount());
1317
+ (_e = (_d = this.config).onAnnotationResolve) == null ? void 0 : _e.call(_d, updated);
1318
+ }
1319
+ // ── MutationObserver — handles Livewire/Vue DOM teardown ──────
1320
+ setupMutationObserver() {
1321
+ this.mutationObserver = new MutationObserver((mutations) => {
1322
+ var _a;
1323
+ const anyRemoved = mutations.some((m) => m.removedNodes.length > 0);
1324
+ if (!anyRemoved) return;
1325
+ (_a = this.markers) == null ? void 0 : _a.reposition(this.annotations);
1326
+ });
1327
+ this.mutationObserver.observe(document.body, {
1328
+ childList: true,
1329
+ subtree: true
1330
+ });
1331
+ }
1332
+ // ── Scroll/resize — reposition markers ───────────────────────
1333
+ onScrollResize() {
1334
+ var _a;
1335
+ (_a = this.markers) == null ? void 0 : _a.reposition(this.annotations);
1336
+ }
1337
+ // ── Keyboard ──────────────────────────────────────────────────
1338
+ onKeydown(e) {
1339
+ var _a, _b, _c;
1340
+ const target = e.target;
1341
+ if (["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName)) return;
1342
+ if (target.closest('[contenteditable="true"]')) return;
1343
+ if (e.key === "a" && !e.metaKey && !e.ctrlKey && !e.altKey) {
1344
+ const next = !this.isAnnotating;
1345
+ (_a = this.toolbar) == null ? void 0 : _a.setMode(next ? "annotating" : "idle");
1346
+ this.setAnnotating(next);
1347
+ }
1348
+ if (e.key === "f" && !e.metaKey && !e.ctrlKey && !e.altKey) {
1349
+ const next = !this.isFrozen;
1350
+ (_b = this.toolbar) == null ? void 0 : _b.setMode(next ? "frozen" : "idle");
1351
+ this.setFrozen(next);
1352
+ }
1353
+ if (e.key === "Escape") {
1354
+ if (this.isAnnotating) {
1355
+ (_c = this.toolbar) == null ? void 0 : _c.setMode("idle");
1356
+ this.setAnnotating(false);
1357
+ }
1358
+ }
1359
+ }
1360
+ // ── Helpers ───────────────────────────────────────────────────
1361
+ pendingCount() {
1362
+ return this.annotations.filter((a) => a.status === "pending" || a.status === "acknowledged").length;
1363
+ }
1364
+ syncMarkersFromAnnotations() {
1365
+ this.annotations.forEach((a, i) => {
1366
+ var _a;
1367
+ return (_a = this.markers) == null ? void 0 : _a.upsert(a, i + 1);
1368
+ });
1369
+ }
1370
+ // ── Copy / export ─────────────────────────────────────────────
1371
+ copyAnnotations() {
1372
+ const md = this.exportMarkdown();
1373
+ navigator.clipboard.writeText(md).catch(() => {
1374
+ const el = document.createElement("textarea");
1375
+ el.value = md;
1376
+ el.style.cssText = "position:fixed;left:-9999px";
1377
+ document.body.appendChild(el);
1378
+ el.select();
1379
+ document.execCommand("copy");
1380
+ el.remove();
1381
+ });
1382
+ }
1383
+ exportMarkdown() {
1384
+ const pending = this.annotations.filter((a) => a.status === "pending" || a.status === "acknowledged");
1385
+ if (pending.length === 0) {
1386
+ return `## Instruckt Feedback \u2014 ${window.location.href}
1387
+
1388
+ No open annotations.`;
1389
+ }
1390
+ const lines = [
1391
+ `## Instruckt Feedback \u2014 ${window.location.href}`,
1392
+ `> ${pending.length} open annotation${pending.length === 1 ? "" : "s"}`,
1393
+ ""
1394
+ ];
1395
+ pending.forEach((a, i) => {
1396
+ const severityIcon = a.severity === "blocking" ? "\u{1F534}" : a.severity === "important" ? "\u{1F7E0}" : "\u{1F7E1}";
1397
+ const intentLabel = a.intent === "fix" ? "Fix" : a.intent === "change" ? "Change" : a.intent === "question" ? "Question" : "Approve";
1398
+ lines.push(`### ${i + 1}. ${a.element} \u2014 ${intentLabel} ${severityIcon}`);
1399
+ lines.push("");
1400
+ lines.push(a.comment);
1401
+ lines.push("");
1402
+ lines.push(`**Selector**: \`${a.elementPath}\``);
1403
+ if (a.framework) {
1404
+ const fw = a.framework;
1405
+ const label = fw.framework === "livewire" ? `Livewire \u2014 ${fw.component}` : fw.framework === "vue" ? `Vue \u2014 ${fw.component}` : fw.framework === "react" ? `React \u2014 ${fw.component}` : fw.component;
1406
+ lines.push(`**Component**: ${label}`);
1407
+ }
1408
+ if (a.selectedText) lines.push(`**Selected text**: "${a.selectedText}"`);
1409
+ if (a.thread && a.thread.length > 0) {
1410
+ lines.push("");
1411
+ lines.push("**Thread:**");
1412
+ a.thread.forEach((m) => {
1413
+ lines.push(`- **${m.role === "agent" ? "Agent" : "You"}**: ${m.content}`);
1414
+ });
1415
+ }
1416
+ lines.push("");
1417
+ lines.push("---");
1418
+ lines.push("");
1419
+ });
1420
+ return lines.join("\n");
1421
+ }
1422
+ // ── Public API ────────────────────────────────────────────────
1423
+ getAnnotations() {
1424
+ return [...this.annotations];
1425
+ }
1426
+ getSession() {
1427
+ return this.session;
1428
+ }
1429
+ destroy() {
1430
+ var _a, _b, _c, _d, _e, _f;
1431
+ this.setAnnotating(false);
1432
+ this.setFrozen(false);
1433
+ document.removeEventListener("keydown", this.boundKeydown);
1434
+ window.removeEventListener("scroll", this.boundScroll);
1435
+ window.removeEventListener("resize", this.boundResize);
1436
+ (_a = this.mutationObserver) == null ? void 0 : _a.disconnect();
1437
+ (_b = this.sse) == null ? void 0 : _b.disconnect();
1438
+ (_c = this.toolbar) == null ? void 0 : _c.destroy();
1439
+ (_d = this.highlight) == null ? void 0 : _d.destroy();
1440
+ (_e = this.popup) == null ? void 0 : _e.destroy();
1441
+ (_f = this.markers) == null ? void 0 : _f.destroy();
1442
+ if (this.rafId !== null) cancelAnimationFrame(this.rafId);
1443
+ }
1444
+ };
1445
+
1446
+ // src/index.ts
1447
+ function init(config) {
1448
+ return new Instruckt(config);
1449
+ }
1450
+ //# sourceMappingURL=instruckt.cjs.js.map