@pragma-sh/scratchpad 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.
package/dist/ui.cjs ADDED
@@ -0,0 +1,1389 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ function __accessProp(key) {
6
+ return this[key];
7
+ }
8
+ var __reExport = (target, mod, secondTarget) => {
9
+ var keys = __getOwnPropNames(mod);
10
+ for (let key of keys)
11
+ if (!__hasOwnProp.call(target, key) && key !== "default")
12
+ __defProp(target, key, {
13
+ get: __accessProp.bind(mod, key),
14
+ enumerable: true
15
+ });
16
+ if (secondTarget) {
17
+ for (let key of keys)
18
+ if (!__hasOwnProp.call(secondTarget, key) && key !== "default")
19
+ __defProp(secondTarget, key, {
20
+ get: __accessProp.bind(mod, key),
21
+ enumerable: true
22
+ });
23
+ return secondTarget;
24
+ }
25
+ };
26
+ var __toCommonJS = (from) => {
27
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
28
+ if (entry)
29
+ return entry;
30
+ entry = __defProp({}, "__esModule", { value: true });
31
+ if (from && typeof from === "object" || typeof from === "function") {
32
+ for (var key of __getOwnPropNames(from))
33
+ if (!__hasOwnProp.call(entry, key))
34
+ __defProp(entry, key, {
35
+ get: __accessProp.bind(from, key),
36
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
37
+ });
38
+ }
39
+ __moduleCache.set(from, entry);
40
+ return entry;
41
+ };
42
+ var __moduleCache;
43
+ var __returnValue = (v) => v;
44
+ function __exportSetter(name, newValue) {
45
+ this[name] = __returnValue.bind(null, newValue);
46
+ }
47
+ var __export = (target, all) => {
48
+ for (var name in all)
49
+ __defProp(target, name, {
50
+ get: all[name],
51
+ enumerable: true,
52
+ configurable: true,
53
+ set: __exportSetter.bind(all, name)
54
+ });
55
+ };
56
+
57
+ // src/index.ts
58
+ var exports_src = {};
59
+ __export(exports_src, {
60
+ scratchpadBridge: () => scratchpadBridge,
61
+ promptAgent: () => promptAgent
62
+ });
63
+ module.exports = __toCommonJS(exports_src);
64
+ __reExport(exports_src, require("@pragma-sh/sdk"), module.exports);
65
+ function bridge() {
66
+ const value = globalThis.pragmaScratchpad;
67
+ if (!value) {
68
+ throw new Error("@pragma-sh/scratchpad can only prompt agents inside a Pragma scratchpad");
69
+ }
70
+ return value;
71
+ }
72
+ function scratchpadBridge() {
73
+ return bridge();
74
+ }
75
+ async function promptAgent(text, options = {}) {
76
+ const value = text.trim();
77
+ if (!value)
78
+ return false;
79
+ const host = bridge();
80
+ const firstResult = await host.promptAgent(value);
81
+ if (firstResult === "sent")
82
+ return true;
83
+ if (firstResult === "cancelled")
84
+ return false;
85
+ const attach = () => host.requestAgentAttachment();
86
+ const customResult = await options.onMissingAgent?.({ text: value, attach });
87
+ const attached = options.onMissingAgent ? customResult === true : await attach();
88
+ if (!attached)
89
+ return false;
90
+ const retryResult = await host.promptAgent(value);
91
+ if (retryResult === "sent")
92
+ return true;
93
+ if (retryResult === "cancelled")
94
+ return false;
95
+ throw new Error("Selected agent tab is no longer available");
96
+ }
97
+
98
+ // src/primitives.tsx
99
+ var exports_primitives = {};
100
+ __export(exports_primitives, {
101
+ Textarea: () => Textarea,
102
+ Progress: () => Progress,
103
+ Input: () => Input,
104
+ Card: () => Card,
105
+ Button: () => Button,
106
+ Badge: () => Badge
107
+ });
108
+ module.exports = __toCommonJS(exports_primitives);
109
+
110
+ // src/styles.ts
111
+ var SCRATCHPAD_STYLE_ELEMENT_ID = "pragma-scratchpad-styles";
112
+ var SCRATCHPAD_STYLES = `
113
+ .pragma-card {
114
+ display: grid;
115
+ gap: 0.75rem;
116
+ margin: 1rem 0;
117
+ padding: 0.875rem 1rem;
118
+ border: 1px solid var(--border, #3f3f46);
119
+ border-radius: var(--radius-lg, 10px);
120
+ background: var(--card, #18181b);
121
+ color: var(--card-foreground, #fafafa);
122
+ box-shadow: var(--shadow-raised, 0 1px 2px rgb(0 0 0 / 0.3));
123
+ font-family: var(--font-sans, system-ui, sans-serif);
124
+ font-size: 0.8125rem;
125
+ line-height: 1.55;
126
+ }
127
+ .pragma-card > :first-child { margin-top: 0; }
128
+ .pragma-card > :last-child { margin-bottom: 0; }
129
+
130
+ .pragma-eyebrow {
131
+ font-size: 0.6875rem;
132
+ font-weight: 600;
133
+ letter-spacing: 0.06em;
134
+ text-transform: uppercase;
135
+ color: var(--muted-foreground, #a1a1aa);
136
+ }
137
+ .pragma-title {
138
+ font-size: 0.9375rem;
139
+ font-weight: 600;
140
+ line-height: 1.4;
141
+ color: var(--card-foreground, #fafafa);
142
+ }
143
+ .pragma-hint { font-size: 0.75rem; color: var(--muted-foreground, #a1a1aa); }
144
+ .pragma-stack { display: grid; gap: 0.5rem; }
145
+ .pragma-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
146
+ .pragma-row--between { justify-content: space-between; }
147
+ .pragma-row--end { justify-content: flex-end; }
148
+ .pragma-empty {
149
+ padding: 0.75rem;
150
+ border: 1px dashed var(--border, #3f3f46);
151
+ border-radius: var(--radius-md, 8px);
152
+ font-size: 0.75rem;
153
+ color: var(--muted-foreground, #a1a1aa);
154
+ text-align: center;
155
+ }
156
+ .pragma-chip {
157
+ padding: 0.0625rem 0.375rem;
158
+ border-radius: var(--radius-sm, 6px);
159
+ background: var(--muted, #27272a);
160
+ color: var(--muted-foreground, #a1a1aa);
161
+ font-family: var(--font-mono, ui-monospace, monospace);
162
+ font-size: 0.6875rem;
163
+ }
164
+
165
+ .pragma-button {
166
+ display: inline-flex;
167
+ align-items: center;
168
+ justify-content: center;
169
+ gap: 0.375rem;
170
+ min-height: 2rem;
171
+ padding: 0 0.75rem;
172
+ border: 1px solid transparent;
173
+ border-radius: var(--radius-md, 8px);
174
+ font-family: inherit;
175
+ font-size: 0.8125rem;
176
+ font-weight: 500;
177
+ line-height: 1;
178
+ cursor: pointer;
179
+ white-space: nowrap;
180
+ transition: background-color 0.12s ease, border-color 0.12s ease, color 0.12s ease;
181
+ }
182
+ .pragma-button:focus-visible { outline: 2px solid var(--ring, #22d3ee); outline-offset: 2px; }
183
+ .pragma-button:disabled { cursor: not-allowed; opacity: 0.5; }
184
+ .pragma-button--sm { min-height: 1.75rem; padding: 0 0.5rem; font-size: 0.75rem; }
185
+ .pragma-button--primary {
186
+ background: var(--primary, #2563eb);
187
+ color: var(--primary-foreground, #fafafa);
188
+ }
189
+ .pragma-button--primary:hover:not(:disabled) { background: var(--primary-hover, #1d4ed8); }
190
+ .pragma-button--secondary {
191
+ background: var(--secondary, #27272a);
192
+ color: var(--secondary-foreground, #fafafa);
193
+ }
194
+ .pragma-button--secondary:hover:not(:disabled) { background: var(--accent, #3f3f46); }
195
+ .pragma-button--outline {
196
+ border-color: var(--input, #3f3f46);
197
+ background: transparent;
198
+ color: var(--foreground, #fafafa);
199
+ }
200
+ .pragma-button--outline:hover:not(:disabled) {
201
+ background: var(--accent, #27272a);
202
+ color: var(--accent-foreground, #fafafa);
203
+ }
204
+ .pragma-button--ghost { background: transparent; color: var(--muted-foreground, #a1a1aa); }
205
+ .pragma-button--ghost:hover:not(:disabled) {
206
+ background: var(--accent, #27272a);
207
+ color: var(--accent-foreground, #fafafa);
208
+ }
209
+ .pragma-button--danger {
210
+ background: var(--destructive, #ef4444);
211
+ color: var(--destructive-foreground, #fafafa);
212
+ }
213
+ .pragma-button--danger:hover:not(:disabled) { filter: brightness(1.08); }
214
+
215
+ .pragma-input,
216
+ .pragma-textarea {
217
+ width: 100%;
218
+ padding: 0.4375rem 0.625rem;
219
+ border: 1px solid var(--input, #3f3f46);
220
+ border-radius: var(--radius-md, 8px);
221
+ background: var(--background, #09090b);
222
+ color: var(--foreground, #fafafa);
223
+ font-family: inherit;
224
+ font-size: 0.8125rem;
225
+ line-height: 1.5;
226
+ transition: border-color 0.12s ease;
227
+ }
228
+ .pragma-input::placeholder,
229
+ .pragma-textarea::placeholder { color: var(--muted-foreground, #71717a); }
230
+ .pragma-input:focus-visible,
231
+ .pragma-textarea:focus-visible {
232
+ border-color: var(--ring, #22d3ee);
233
+ outline: 2px solid var(--ring, #22d3ee);
234
+ outline-offset: -1px;
235
+ }
236
+ .pragma-input:disabled,
237
+ .pragma-textarea:disabled { cursor: not-allowed; opacity: 0.5; }
238
+ .pragma-textarea { min-height: 5rem; resize: vertical; }
239
+
240
+ /*
241
+ * Choice list shared by every AskQuestion shape: a circular radio for a single
242
+ * answer, a square checkbox for multi-select. The native control is restyled
243
+ * (\`appearance: none\`) rather than replaced, so keyboard roving, labels and
244
+ * form semantics keep working inside the sandboxed frame.
245
+ */
246
+ .pragma-choices { display: grid; gap: 0.25rem; }
247
+ .pragma-choice {
248
+ display: flex;
249
+ align-items: center;
250
+ gap: 0.5rem;
251
+ padding: 0.4375rem 0.5rem;
252
+ border: 1px solid transparent;
253
+ border-radius: var(--radius-md, 8px);
254
+ cursor: pointer;
255
+ transition: background-color 0.12s ease, border-color 0.12s ease;
256
+ }
257
+ .pragma-choice:hover { background: var(--accent, #27272a); }
258
+ .pragma-choice--checked {
259
+ border-color: color-mix(in oklab, var(--primary, #2563eb) 45%, transparent);
260
+ background: color-mix(in oklab, var(--primary, #2563eb) 10%, transparent);
261
+ }
262
+ .pragma-choice:has(:disabled) { cursor: not-allowed; opacity: 0.5; }
263
+ .pragma-choice:has(:disabled):hover { background: transparent; }
264
+ .pragma-choice__control {
265
+ appearance: none;
266
+ -webkit-appearance: none;
267
+ display: grid;
268
+ place-content: center;
269
+ flex: none;
270
+ width: 1rem;
271
+ height: 1rem;
272
+ margin: 0;
273
+ border: 1px solid var(--input, #3f3f46);
274
+ background: var(--background, #09090b);
275
+ cursor: inherit;
276
+ transition: background-color 0.12s ease, border-color 0.12s ease;
277
+ }
278
+ .pragma-choice__control--radio { border-radius: 999px; }
279
+ .pragma-choice__control--check { border-radius: var(--radius-sm, 6px); }
280
+ .pragma-choice__control:focus-visible { outline: 2px solid var(--ring, #22d3ee); outline-offset: 2px; }
281
+ .pragma-choice__control:checked { border-color: var(--primary, #2563eb); }
282
+ .pragma-choice__control--radio:checked::after {
283
+ content: "";
284
+ width: 0.5rem;
285
+ height: 0.5rem;
286
+ border-radius: 999px;
287
+ background: var(--primary, #2563eb);
288
+ }
289
+ .pragma-choice__control--check:checked { background: var(--primary, #2563eb); }
290
+ .pragma-choice__control--check:checked::after {
291
+ content: "";
292
+ width: 0.3125rem;
293
+ height: 0.5625rem;
294
+ margin-top: -0.125rem;
295
+ border: solid var(--primary-foreground, #fafafa);
296
+ border-width: 0 2px 2px 0;
297
+ transform: rotate(45deg);
298
+ }
299
+ .pragma-choice__label { flex: 1; min-width: 0; font-size: 0.8125rem; }
300
+ /* The "Other" row swaps its text for this field, so it has to sit on the row. */
301
+ .pragma-choice__other {
302
+ padding: 0.125rem 0.375rem;
303
+ font-size: 0.8125rem;
304
+ cursor: text;
305
+ }
306
+
307
+ .pragma-progress {
308
+ appearance: none;
309
+ width: 100%;
310
+ height: 0.375rem;
311
+ border: 0;
312
+ border-radius: 999px;
313
+ overflow: hidden;
314
+ background: var(--muted, #27272a);
315
+ color: var(--pragma-tone, var(--primary, #22d3ee));
316
+ }
317
+ .pragma-progress::-webkit-progress-bar { background: var(--muted, #27272a); border-radius: 999px; }
318
+ .pragma-progress::-webkit-progress-value {
319
+ background: var(--pragma-tone, var(--primary, #22d3ee));
320
+ border-radius: 999px;
321
+ transition: width 0.25s ease;
322
+ }
323
+ .pragma-progress::-moz-progress-bar {
324
+ background: var(--pragma-tone, var(--primary, #22d3ee));
325
+ border-radius: 999px;
326
+ }
327
+
328
+ .pragma-badge {
329
+ display: inline-flex;
330
+ align-items: center;
331
+ gap: 0.25rem;
332
+ padding: 0.0625rem 0.4375rem;
333
+ border: 1px solid currentColor;
334
+ border-radius: 999px;
335
+ background: transparent;
336
+ color: var(--muted-foreground, #a1a1aa);
337
+ font-size: 0.6875rem;
338
+ font-weight: 500;
339
+ line-height: 1.5;
340
+ }
341
+ .pragma-badge--primary { color: var(--primary, #22d3ee); }
342
+ .pragma-badge--success { color: var(--success, #22c55e); }
343
+ .pragma-badge--warning { color: var(--warning, #eab308); }
344
+ .pragma-badge--danger { color: var(--destructive, #ef4444); }
345
+
346
+ .pragma-whiteboard__open {
347
+ display: block;
348
+ width: 100%;
349
+ padding: 0;
350
+ border: 0;
351
+ border-radius: var(--radius-md, 8px);
352
+ background: transparent;
353
+ cursor: pointer;
354
+ }
355
+ .pragma-whiteboard__open:focus-visible {
356
+ outline: 2px solid var(--ring, #22d3ee);
357
+ outline-offset: 2px;
358
+ }
359
+ .pragma-whiteboard__open:hover .pragma-whiteboard__image {
360
+ border-color: var(--primary, #22d3ee);
361
+ }
362
+ .pragma-whiteboard__image {
363
+ display: block;
364
+ width: 100%;
365
+ height: auto;
366
+ max-height: 36rem;
367
+ object-fit: contain;
368
+ border: 1px solid var(--border, #3f3f46);
369
+ border-radius: var(--radius-md, 8px);
370
+ background: var(--background, #09090b);
371
+ transition: border-color 0.15s ease;
372
+ }
373
+ .pragma-whiteboard__error {
374
+ margin: 0;
375
+ color: var(--destructive, #ef4444);
376
+ font-size: 0.75rem;
377
+ }
378
+
379
+ /*
380
+ * Side-by-side diff modelled on the desktop's \`@codemirror/merge\` view: one
381
+ * scroll container so both panes stay row-aligned, a line-number gutter per
382
+ * pane, whole-line tinting for inserted/deleted lines and a stronger tint on
383
+ * the words that actually changed (the equivalent of \`cm-changedText\`).
384
+ */
385
+ .pragma-diff {
386
+ display: grid;
387
+ grid-template-columns: repeat(2, minmax(0, 1fr));
388
+ max-height: 24rem;
389
+ overflow-y: auto;
390
+ border: 1px solid var(--border, #3f3f46);
391
+ border-radius: var(--radius-md, 8px);
392
+ background: var(--background, #09090b);
393
+ font-family: var(--font-mono, ui-monospace, monospace);
394
+ font-size: 0.75rem;
395
+ line-height: 1.6;
396
+ --pragma-diff-added: var(--diff-added, #22c55e);
397
+ --pragma-diff-removed: var(--diff-removed, #ef4444);
398
+ }
399
+ @media (max-width: 34rem) { .pragma-diff { grid-template-columns: minmax(0, 1fr); } }
400
+ .pragma-diff__pane { min-width: 0; overflow-x: auto; }
401
+ .pragma-diff__pane--after { border-left: 1px solid var(--border, #3f3f46); }
402
+ @media (max-width: 34rem) {
403
+ .pragma-diff__pane--after {
404
+ border-left: 0;
405
+ border-top: 1px solid var(--border, #3f3f46);
406
+ }
407
+ }
408
+ .pragma-diff__label {
409
+ display: flex;
410
+ justify-content: space-between;
411
+ gap: 0.5rem;
412
+ padding: 0.25rem 0.625rem;
413
+ border-bottom: 1px solid var(--border, #3f3f46);
414
+ background: var(--card, #18181b);
415
+ font-family: var(--font-sans, system-ui, sans-serif);
416
+ font-size: 0.6875rem;
417
+ font-weight: 600;
418
+ letter-spacing: 0.06em;
419
+ text-transform: uppercase;
420
+ }
421
+ .pragma-diff__pane--before .pragma-diff__label { color: var(--pragma-diff-removed); }
422
+ .pragma-diff__pane--after .pragma-diff__label { color: var(--pragma-diff-added); }
423
+
424
+ .pragma-diff__lines { display: grid; padding: 0.25rem 0; min-width: max-content; }
425
+ .pragma-diff__line {
426
+ display: grid;
427
+ grid-template-columns: 2.75rem minmax(0, 1fr);
428
+ min-height: 1.6em;
429
+ white-space: pre;
430
+ }
431
+ .pragma-diff__gutter {
432
+ padding-right: 0.625rem;
433
+ color: var(--muted-foreground, #71717a);
434
+ opacity: 0.7;
435
+ text-align: right;
436
+ user-select: none;
437
+ }
438
+ .pragma-diff__text { padding-right: 0.75rem; font: inherit; }
439
+ .pragma-diff__line--removed {
440
+ background: color-mix(in oklab, var(--pragma-diff-removed) 12%, transparent);
441
+ }
442
+ .pragma-diff__line--added {
443
+ background: color-mix(in oklab, var(--pragma-diff-added) 12%, transparent);
444
+ }
445
+ .pragma-diff__pane--before .pragma-diff__line--changed {
446
+ background: color-mix(in oklab, var(--pragma-diff-removed) 12%, transparent);
447
+ }
448
+ .pragma-diff__pane--after .pragma-diff__line--changed {
449
+ background: color-mix(in oklab, var(--pragma-diff-added) 12%, transparent);
450
+ }
451
+ .pragma-diff__line--spacer { background: var(--muted, #27272a); opacity: 0.35; }
452
+ .pragma-diff__pane--before .pragma-diff__changed {
453
+ background: color-mix(in oklab, var(--pragma-diff-removed) 30%, transparent);
454
+ }
455
+ .pragma-diff__pane--after .pragma-diff__changed {
456
+ background: color-mix(in oklab, var(--pragma-diff-added) 30%, transparent);
457
+ }
458
+
459
+ .pragma-progress-row { display: grid; gap: 0.3125rem; }
460
+ .pragma-progress-row + .pragma-progress-row {
461
+ padding-top: 0.625rem;
462
+ border-top: 1px solid var(--border, #3f3f46);
463
+ }
464
+ .pragma-progress-row__name {
465
+ min-width: 0;
466
+ overflow: hidden;
467
+ font-weight: 500;
468
+ text-overflow: ellipsis;
469
+ white-space: nowrap;
470
+ }
471
+ `;
472
+ function ensureScratchpadStyles() {
473
+ if (typeof document === "undefined")
474
+ return;
475
+ if (document.getElementById(SCRATCHPAD_STYLE_ELEMENT_ID))
476
+ return;
477
+ const element = document.createElement("style");
478
+ element.id = SCRATCHPAD_STYLE_ELEMENT_ID;
479
+ element.textContent = SCRATCHPAD_STYLES;
480
+ document.head.append(element);
481
+ }
482
+
483
+ // src/primitives.tsx
484
+ var jsx_runtime = require("react/jsx-runtime");
485
+ var TONE_VARIABLE = {
486
+ neutral: "var(--muted-foreground)",
487
+ primary: "var(--primary)",
488
+ success: "var(--success)",
489
+ warning: "var(--warning)",
490
+ danger: "var(--destructive)"
491
+ };
492
+ function classNames(...values) {
493
+ return values.filter(Boolean).join(" ");
494
+ }
495
+ function Button({
496
+ className,
497
+ size = "md",
498
+ type = "button",
499
+ variant = "secondary",
500
+ ...rest
501
+ }) {
502
+ ensureScratchpadStyles();
503
+ return /* @__PURE__ */ jsx_runtime.jsx("button", {
504
+ ...rest,
505
+ className: classNames("pragma-button", `pragma-button--${variant}`, size === "sm" && "pragma-button--sm", className),
506
+ type
507
+ });
508
+ }
509
+ function Input({
510
+ className,
511
+ ...rest
512
+ }) {
513
+ ensureScratchpadStyles();
514
+ return /* @__PURE__ */ jsx_runtime.jsx("input", {
515
+ ...rest,
516
+ className: classNames("pragma-input", className)
517
+ });
518
+ }
519
+ function Textarea({
520
+ className,
521
+ ...rest
522
+ }) {
523
+ ensureScratchpadStyles();
524
+ return /* @__PURE__ */ jsx_runtime.jsx("textarea", {
525
+ ...rest,
526
+ className: classNames("pragma-textarea", className)
527
+ });
528
+ }
529
+ function Progress({
530
+ className,
531
+ style,
532
+ tone = "primary",
533
+ ...rest
534
+ }) {
535
+ ensureScratchpadStyles();
536
+ return /* @__PURE__ */ jsx_runtime.jsx("progress", {
537
+ ...rest,
538
+ className: classNames("pragma-progress", className),
539
+ style: { ["--pragma-tone"]: TONE_VARIABLE[tone], ...style }
540
+ });
541
+ }
542
+ function Card({ className, ...rest }) {
543
+ ensureScratchpadStyles();
544
+ return /* @__PURE__ */ jsx_runtime.jsx("section", {
545
+ ...rest,
546
+ className: classNames("pragma-card", className)
547
+ });
548
+ }
549
+ function Badge({ className, tone = "neutral", ...rest }) {
550
+ ensureScratchpadStyles();
551
+ return /* @__PURE__ */ jsx_runtime.jsx("span", {
552
+ ...rest,
553
+ className: classNames("pragma-badge", tone !== "neutral" && `pragma-badge--${tone}`, className)
554
+ });
555
+ }
556
+
557
+ // src/ui.tsx
558
+ var exports_ui = {};
559
+ __export(exports_ui, {
560
+ Whiteboard: () => Whiteboard,
561
+ Textarea: () => Textarea,
562
+ Progress: () => Progress,
563
+ Input: () => Input,
564
+ DiffReview: () => DiffReview,
565
+ Card: () => Card,
566
+ Button: () => Button,
567
+ Badge: () => Badge,
568
+ AskQuestion: () => AskQuestion,
569
+ AgentProgress: () => AgentProgress
570
+ });
571
+ module.exports = __toCommonJS(exports_ui);
572
+ var import_react = require("react");
573
+
574
+ // src/diff.ts
575
+ var MAX_DP_CELLS = 250000;
576
+ function spacer(facing) {
577
+ return { key: `spacer-${facing}`, number: null, kind: "spacer", segments: [] };
578
+ }
579
+ function alignDiffLines(before, after) {
580
+ const rows = [];
581
+ let beforeNumber = 0;
582
+ let afterNumber = 0;
583
+ let removed = [];
584
+ let added = [];
585
+ const flush = () => {
586
+ const paired = Math.min(removed.length, added.length);
587
+ for (let index = 0;index < paired; index += 1) {
588
+ const [left, right] = changedSegments(removed[index] ?? "", added[index] ?? "");
589
+ beforeNumber += 1;
590
+ afterNumber += 1;
591
+ rows.push({
592
+ before: { key: `b${beforeNumber}`, number: beforeNumber, kind: "changed", segments: left },
593
+ after: { key: `a${afterNumber}`, number: afterNumber, kind: "changed", segments: right }
594
+ });
595
+ }
596
+ for (const text of removed.slice(paired)) {
597
+ beforeNumber += 1;
598
+ rows.push({
599
+ before: {
600
+ key: `b${beforeNumber}`,
601
+ number: beforeNumber,
602
+ kind: "removed",
603
+ segments: wholeLine(text)
604
+ },
605
+ after: spacer(beforeNumber)
606
+ });
607
+ }
608
+ for (const text of added.slice(paired)) {
609
+ afterNumber += 1;
610
+ rows.push({
611
+ before: spacer(afterNumber),
612
+ after: {
613
+ key: `a${afterNumber}`,
614
+ number: afterNumber,
615
+ kind: "added",
616
+ segments: wholeLine(text)
617
+ }
618
+ });
619
+ }
620
+ removed = [];
621
+ added = [];
622
+ };
623
+ for (const edit of diffSequences(splitLines(before), splitLines(after))) {
624
+ if (edit.kind === "remove") {
625
+ removed.push(edit.value);
626
+ continue;
627
+ }
628
+ if (edit.kind === "insert") {
629
+ added.push(edit.value);
630
+ continue;
631
+ }
632
+ flush();
633
+ beforeNumber += 1;
634
+ afterNumber += 1;
635
+ rows.push({
636
+ before: {
637
+ key: `b${beforeNumber}`,
638
+ number: beforeNumber,
639
+ kind: "same",
640
+ segments: wholeLine(edit.value)
641
+ },
642
+ after: {
643
+ key: `a${afterNumber}`,
644
+ number: afterNumber,
645
+ kind: "same",
646
+ segments: wholeLine(edit.value)
647
+ }
648
+ });
649
+ }
650
+ flush();
651
+ return rows;
652
+ }
653
+ function splitLines(text) {
654
+ const normalized = text.replace(/\r\n/g, `
655
+ `).replace(/\n$/, "");
656
+ return normalized === "" ? [] : normalized.split(`
657
+ `);
658
+ }
659
+ function wholeLine(text) {
660
+ return text === "" ? [] : [{ text, changed: false }];
661
+ }
662
+ function changedSegments(before, after) {
663
+ const left = [];
664
+ const right = [];
665
+ for (const edit of diffSequences(splitWords(before), splitWords(after))) {
666
+ if (edit.kind !== "insert") {
667
+ push(left, edit.value, edit.kind === "remove");
668
+ }
669
+ if (edit.kind !== "remove") {
670
+ push(right, edit.value, edit.kind === "insert");
671
+ }
672
+ }
673
+ return [left, right];
674
+ }
675
+ function push(segments, text, changed) {
676
+ const last = segments.at(-1);
677
+ if (last && last.changed === changed) {
678
+ last.text += text;
679
+ return;
680
+ }
681
+ segments.push({ text, changed });
682
+ }
683
+ function splitWords(line) {
684
+ return line.split(/(\s+)/).filter((part) => part !== "");
685
+ }
686
+ function diffSequences(a, b) {
687
+ let start = 0;
688
+ while (start < a.length && start < b.length && a[start] === b[start]) {
689
+ start += 1;
690
+ }
691
+ let end = 0;
692
+ while (end < a.length - start && end < b.length - start && a[a.length - 1 - end] === b[b.length - 1 - end]) {
693
+ end += 1;
694
+ }
695
+ const head = a.slice(0, start).map((value) => ({ kind: "equal", value }));
696
+ const tail = a.slice(a.length - end).map((value) => ({ kind: "equal", value }));
697
+ const midA = a.slice(start, a.length - end);
698
+ const midB = b.slice(start, b.length - end);
699
+ return [...head, ...diffMiddle(midA, midB), ...tail];
700
+ }
701
+ function replaceAll(a, b) {
702
+ return [
703
+ ...a.map((value) => ({ kind: "remove", value })),
704
+ ...b.map((value) => ({ kind: "insert", value }))
705
+ ];
706
+ }
707
+ function lcsTable(a, b) {
708
+ const width = b.length + 1;
709
+ const lengths = new Uint32Array((a.length + 1) * width);
710
+ for (let i = a.length - 1;i >= 0; i -= 1) {
711
+ for (let j = b.length - 1;j >= 0; j -= 1) {
712
+ lengths[i * width + j] = a[i] === b[j] ? (lengths[(i + 1) * width + j + 1] ?? 0) + 1 : Math.max(lengths[(i + 1) * width + j] ?? 0, lengths[i * width + j + 1] ?? 0);
713
+ }
714
+ }
715
+ return lengths;
716
+ }
717
+ function diffMiddle(a, b) {
718
+ if (a.length === 0 || b.length === 0 || a.length * b.length > MAX_DP_CELLS) {
719
+ return replaceAll(a, b);
720
+ }
721
+ return walkLcsTable(a, b, lcsTable(a, b));
722
+ }
723
+ function walkLcsTable(a, b, lengths) {
724
+ const width = b.length + 1;
725
+ const edits = [];
726
+ let i = 0;
727
+ let j = 0;
728
+ while (i < a.length && j < b.length) {
729
+ const left = a[i];
730
+ const right = b[j];
731
+ if (left === right) {
732
+ edits.push({ kind: "equal", value: left });
733
+ i += 1;
734
+ j += 1;
735
+ } else if ((lengths[(i + 1) * width + j] ?? 0) >= (lengths[i * width + j + 1] ?? 0)) {
736
+ edits.push({ kind: "remove", value: left });
737
+ i += 1;
738
+ } else {
739
+ edits.push({ kind: "insert", value: right });
740
+ j += 1;
741
+ }
742
+ }
743
+ return [...edits, ...replaceAll(a.slice(i), b.slice(j))];
744
+ }
745
+
746
+ // src/question.ts
747
+ var OTHER_VALUE = "pragma:other";
748
+ function toggleChoice(current, value, multiple) {
749
+ if (!multiple)
750
+ return [value];
751
+ return current.includes(value) ? current.filter((entry) => entry !== value) : [...current, value];
752
+ }
753
+ function composeAnswer(selected, other) {
754
+ return selected.map((value) => value === OTHER_VALUE ? other.trim() : value).filter((value) => value !== "").join(", ");
755
+ }
756
+
757
+ // src/ui.tsx
758
+ var jsx_runtime2 = require("react/jsx-runtime");
759
+ var EMPTY_OPTIONS = [];
760
+ function Whiteboard({ id, refreshIntervalMs = 3000 }) {
761
+ const [snapshot, setSnapshot] = import_react.useState(null);
762
+ const [error, setError] = import_react.useState(null);
763
+ const [loading, setLoading] = import_react.useState(true);
764
+ const versionRef = import_react.useRef(undefined);
765
+ const darkRef = import_react.useRef(undefined);
766
+ const mountedRef = import_react.useRef(true);
767
+ const inFlightRef = import_react.useRef(false);
768
+ const openWhiteboard = globalThis.pragmaScratchpad?.openWhiteboard;
769
+ const open = () => {
770
+ if (!openWhiteboard)
771
+ return;
772
+ openWhiteboard(id).catch((cause) => {
773
+ if (mountedRef.current)
774
+ setError(cause instanceof Error ? cause.message : String(cause));
775
+ });
776
+ };
777
+ const refresh = import_react.useCallback(async () => {
778
+ if (inFlightRef.current)
779
+ return;
780
+ inFlightRef.current = true;
781
+ try {
782
+ const dark = document.documentElement.classList.contains("dark");
783
+ const knownVersion = darkRef.current === dark ? versionRef.current : undefined;
784
+ const next = await scratchpadBridge().getWhiteboardSnapshot(id, knownVersion, dark);
785
+ if (!mountedRef.current)
786
+ return;
787
+ if (next) {
788
+ versionRef.current = next.version;
789
+ darkRef.current = dark;
790
+ setSnapshot(next);
791
+ }
792
+ setError(null);
793
+ } catch (cause) {
794
+ if (mountedRef.current)
795
+ setError(cause instanceof Error ? cause.message : String(cause));
796
+ } finally {
797
+ inFlightRef.current = false;
798
+ if (mountedRef.current)
799
+ setLoading(false);
800
+ }
801
+ }, [id]);
802
+ import_react.useEffect(() => {
803
+ mountedRef.current = true;
804
+ inFlightRef.current = false;
805
+ versionRef.current = undefined;
806
+ darkRef.current = undefined;
807
+ setSnapshot(null);
808
+ setLoading(true);
809
+ refresh();
810
+ const interval = refreshIntervalMs > 0 ? globalThis.setInterval(() => void refresh(), Math.max(1000, refreshIntervalMs)) : null;
811
+ return () => {
812
+ mountedRef.current = false;
813
+ if (interval !== null)
814
+ globalThis.clearInterval(interval);
815
+ };
816
+ }, [refresh, refreshIntervalMs]);
817
+ return /* @__PURE__ */ jsx_runtime2.jsxs(Card, {
818
+ children: [
819
+ /* @__PURE__ */ jsx_runtime2.jsxs("div", {
820
+ className: "pragma-row pragma-row--between",
821
+ children: [
822
+ /* @__PURE__ */ jsx_runtime2.jsxs("div", {
823
+ children: [
824
+ /* @__PURE__ */ jsx_runtime2.jsx("span", {
825
+ className: "pragma-eyebrow",
826
+ children: "Whiteboard"
827
+ }),
828
+ /* @__PURE__ */ jsx_runtime2.jsx("p", {
829
+ className: "pragma-title",
830
+ children: snapshot?.title ?? id
831
+ })
832
+ ]
833
+ }),
834
+ /* @__PURE__ */ jsx_runtime2.jsxs("div", {
835
+ className: "pragma-row",
836
+ children: [
837
+ /* @__PURE__ */ jsx_runtime2.jsx(Badge, {
838
+ children: "Read only"
839
+ }),
840
+ openWhiteboard ? /* @__PURE__ */ jsx_runtime2.jsx(Button, {
841
+ onClick: open,
842
+ size: "sm",
843
+ variant: "ghost",
844
+ children: "Open"
845
+ }) : null,
846
+ /* @__PURE__ */ jsx_runtime2.jsx(Button, {
847
+ disabled: loading,
848
+ onClick: () => void refresh(),
849
+ size: "sm",
850
+ variant: "ghost",
851
+ children: "Refresh"
852
+ })
853
+ ]
854
+ })
855
+ ]
856
+ }),
857
+ error ? /* @__PURE__ */ jsx_runtime2.jsx("p", {
858
+ className: "pragma-whiteboard__error",
859
+ role: "alert",
860
+ children: error
861
+ }) : snapshot ? openWhiteboard ? /* @__PURE__ */ jsx_runtime2.jsx("button", {
862
+ "aria-label": `Open ${snapshot.title} whiteboard`,
863
+ className: "pragma-whiteboard__open",
864
+ onClick: open,
865
+ type: "button",
866
+ children: /* @__PURE__ */ jsx_runtime2.jsx("img", {
867
+ alt: `${snapshot.title} whiteboard`,
868
+ className: "pragma-whiteboard__image",
869
+ src: snapshot.dataUrl
870
+ })
871
+ }) : /* @__PURE__ */ jsx_runtime2.jsx("img", {
872
+ alt: `${snapshot.title} whiteboard`,
873
+ className: "pragma-whiteboard__image",
874
+ src: snapshot.dataUrl
875
+ }) : /* @__PURE__ */ jsx_runtime2.jsx("div", {
876
+ className: "pragma-empty",
877
+ children: "Loading whiteboard..."
878
+ })
879
+ ]
880
+ });
881
+ }
882
+ function useAgentAction(options) {
883
+ const [pending, setPending] = import_react.useState(false);
884
+ const run = async (text, onSent) => {
885
+ if (pending)
886
+ return false;
887
+ setPending(true);
888
+ try {
889
+ const delivered = await promptAgent(text, options);
890
+ if (delivered)
891
+ await onSent();
892
+ return delivered;
893
+ } finally {
894
+ setPending(false);
895
+ }
896
+ };
897
+ return { pending, run };
898
+ }
899
+ function AskQuestion({
900
+ question,
901
+ type = "options",
902
+ options = EMPTY_OPTIONS,
903
+ allowOpenResponse = false,
904
+ submitLabel = "Send answer",
905
+ onAnswer,
906
+ onMissingAgent
907
+ }) {
908
+ const [sent, setSent] = import_react.useState(null);
909
+ const { pending, run } = useAgentAction({ onMissingAgent });
910
+ const submit = (value) => run(`Answer to scratchpad question "${question}": ${value}`, async () => {
911
+ await onAnswer?.(value);
912
+ setSent(value);
913
+ });
914
+ return /* @__PURE__ */ jsx_runtime2.jsxs(Card, {
915
+ children: [
916
+ /* @__PURE__ */ jsx_runtime2.jsxs("div", {
917
+ className: "pragma-row pragma-row--between",
918
+ children: [
919
+ /* @__PURE__ */ jsx_runtime2.jsx("span", {
920
+ className: "pragma-eyebrow",
921
+ children: "Question"
922
+ }),
923
+ sent === null ? null : /* @__PURE__ */ jsx_runtime2.jsx(Badge, {
924
+ tone: "success",
925
+ children: "Answered"
926
+ })
927
+ ]
928
+ }),
929
+ /* @__PURE__ */ jsx_runtime2.jsx("p", {
930
+ className: "pragma-title",
931
+ children: question
932
+ }),
933
+ sent === null ? /* @__PURE__ */ jsx_runtime2.jsx(AskQuestionControls, {
934
+ allowOpenResponse,
935
+ options,
936
+ pending,
937
+ submit,
938
+ submitLabel,
939
+ type
940
+ }) : /* @__PURE__ */ jsx_runtime2.jsx(SettledNotice, {
941
+ detail: `Answered “${sent}”.`,
942
+ label: "Answer again",
943
+ onReset: () => setSent(null)
944
+ })
945
+ ]
946
+ });
947
+ }
948
+ function AskQuestionControls({
949
+ allowOpenResponse,
950
+ options,
951
+ pending,
952
+ submit,
953
+ submitLabel,
954
+ type
955
+ }) {
956
+ if (type === "text") {
957
+ return /* @__PURE__ */ jsx_runtime2.jsx(AskQuestionForm, {
958
+ pending,
959
+ submit,
960
+ submitLabel
961
+ });
962
+ }
963
+ return /* @__PURE__ */ jsx_runtime2.jsx(AskQuestionChoices, {
964
+ allowOpenResponse,
965
+ choices: type === "yes-no" ? YES_NO_OPTIONS : options,
966
+ multiple: type === "multi-select",
967
+ pending,
968
+ submit,
969
+ submitLabel
970
+ });
971
+ }
972
+ var YES_NO_OPTIONS = [{ label: "Yes" }, { label: "No" }];
973
+ var OTHER_OPTION = { label: "Other", value: OTHER_VALUE };
974
+ function choiceValue(option) {
975
+ return option.value ?? option.label;
976
+ }
977
+ function AskQuestionChoices({
978
+ allowOpenResponse,
979
+ choices,
980
+ multiple,
981
+ pending,
982
+ submit,
983
+ submitLabel
984
+ }) {
985
+ const group = import_react.useId();
986
+ const [selected, setSelected] = import_react.useState([]);
987
+ const [other, setOther] = import_react.useState("");
988
+ const items = allowOpenResponse ? [...choices, OTHER_OPTION] : choices;
989
+ const toggle = (value) => setSelected((current) => toggleChoice(current, value, multiple));
990
+ const answer = composeAnswer(selected, other);
991
+ const send = () => {
992
+ if (!answer)
993
+ return;
994
+ submit(answer).then((delivered) => {
995
+ if (delivered) {
996
+ setSelected([]);
997
+ setOther("");
998
+ }
999
+ return delivered;
1000
+ });
1001
+ };
1002
+ return /* @__PURE__ */ jsx_runtime2.jsxs("form", {
1003
+ className: "pragma-stack",
1004
+ onSubmit: (event) => {
1005
+ event.preventDefault();
1006
+ send();
1007
+ },
1008
+ children: [
1009
+ /* @__PURE__ */ jsx_runtime2.jsx("div", {
1010
+ className: "pragma-choices",
1011
+ role: choiceGroupRole(multiple),
1012
+ children: items.map((option) => /* @__PURE__ */ jsx_runtime2.jsx(ChoiceRow, {
1013
+ checked: selected.includes(choiceValue(option)),
1014
+ disabled: pending,
1015
+ group,
1016
+ multiple,
1017
+ onOtherChange: setOther,
1018
+ onSelect: toggle,
1019
+ option,
1020
+ other
1021
+ }, choiceValue(option)))
1022
+ }),
1023
+ /* @__PURE__ */ jsx_runtime2.jsx(ChoiceSubmit, {
1024
+ answer,
1025
+ label: submitLabel,
1026
+ pending
1027
+ })
1028
+ ]
1029
+ });
1030
+ }
1031
+ function choiceGroupRole(multiple) {
1032
+ return multiple ? "group" : "radiogroup";
1033
+ }
1034
+ function ChoiceSubmit({
1035
+ answer,
1036
+ label,
1037
+ pending
1038
+ }) {
1039
+ return /* @__PURE__ */ jsx_runtime2.jsx("div", {
1040
+ className: "pragma-row pragma-row--end",
1041
+ children: /* @__PURE__ */ jsx_runtime2.jsx(Button, {
1042
+ disabled: pending || !answer,
1043
+ type: "submit",
1044
+ variant: "primary",
1045
+ children: pending ? "Sending…" : label
1046
+ })
1047
+ });
1048
+ }
1049
+ function ChoiceRow(props) {
1050
+ const { checked, disabled, group, multiple, onOtherChange, onSelect, option, other } = props;
1051
+ const value = choiceValue(option);
1052
+ const isOtherField = value === OTHER_VALUE && checked;
1053
+ return /* @__PURE__ */ jsx_runtime2.jsx(Choice, {
1054
+ checked,
1055
+ disabled,
1056
+ label: isOtherField ? /* @__PURE__ */ jsx_runtime2.jsx(Input, {
1057
+ className: "pragma-choice__other",
1058
+ disabled,
1059
+ onClick: (event) => event.stopPropagation(),
1060
+ onChange: (event) => onOtherChange(event.target.value),
1061
+ placeholder: "Answer in your own words",
1062
+ value: other
1063
+ }) : option.label,
1064
+ multiple,
1065
+ name: group,
1066
+ onSelect: () => onSelect(value)
1067
+ });
1068
+ }
1069
+ function Choice({
1070
+ checked,
1071
+ disabled,
1072
+ label,
1073
+ multiple,
1074
+ name,
1075
+ onSelect
1076
+ }) {
1077
+ const shape = multiple ? "check" : "radio";
1078
+ return /* @__PURE__ */ jsx_runtime2.jsxs("label", {
1079
+ className: `pragma-choice${checked ? " pragma-choice--checked" : ""}`,
1080
+ children: [
1081
+ /* @__PURE__ */ jsx_runtime2.jsx("input", {
1082
+ checked,
1083
+ className: `pragma-choice__control pragma-choice__control--${shape}`,
1084
+ disabled,
1085
+ name,
1086
+ onChange: onSelect,
1087
+ type: multiple ? "checkbox" : "radio"
1088
+ }),
1089
+ /* @__PURE__ */ jsx_runtime2.jsx("span", {
1090
+ className: "pragma-choice__label",
1091
+ children: label
1092
+ })
1093
+ ]
1094
+ });
1095
+ }
1096
+ function AskQuestionForm({
1097
+ pending,
1098
+ submit,
1099
+ submitLabel
1100
+ }) {
1101
+ const [answer, setAnswer] = import_react.useState("");
1102
+ const send = () => {
1103
+ const value = answer.trim();
1104
+ if (!value)
1105
+ return;
1106
+ submit(value).then((delivered) => {
1107
+ if (delivered)
1108
+ setAnswer("");
1109
+ return delivered;
1110
+ });
1111
+ };
1112
+ return /* @__PURE__ */ jsx_runtime2.jsxs("form", {
1113
+ className: "pragma-stack",
1114
+ onSubmit: (event) => {
1115
+ event.preventDefault();
1116
+ send();
1117
+ },
1118
+ children: [
1119
+ /* @__PURE__ */ jsx_runtime2.jsx(Textarea, {
1120
+ disabled: pending,
1121
+ onChange: (event) => setAnswer(event.target.value),
1122
+ placeholder: "Type your answer",
1123
+ value: answer
1124
+ }),
1125
+ /* @__PURE__ */ jsx_runtime2.jsx("div", {
1126
+ className: "pragma-row pragma-row--end",
1127
+ children: /* @__PURE__ */ jsx_runtime2.jsx(Button, {
1128
+ disabled: pending || !answer.trim(),
1129
+ type: "submit",
1130
+ variant: "primary",
1131
+ children: pending ? "Sending…" : submitLabel
1132
+ })
1133
+ })
1134
+ ]
1135
+ });
1136
+ }
1137
+ function DiffReview({
1138
+ title = "Review change",
1139
+ before,
1140
+ after,
1141
+ file,
1142
+ onDecision,
1143
+ onMissingAgent
1144
+ }) {
1145
+ const [decision, setDecision] = import_react.useState(null);
1146
+ const { pending, run } = useAgentAction({ onMissingAgent });
1147
+ const decide = (accepted) => run(`Scratchpad diff${file ? ` for ${file}` : ""} was ${verdict(accepted)}.`, async () => {
1148
+ await onDecision?.(accepted);
1149
+ setDecision(accepted);
1150
+ });
1151
+ return /* @__PURE__ */ jsx_runtime2.jsxs(Card, {
1152
+ children: [
1153
+ /* @__PURE__ */ jsx_runtime2.jsx(DiffHeader, {
1154
+ decision,
1155
+ file,
1156
+ title
1157
+ }),
1158
+ /* @__PURE__ */ jsx_runtime2.jsx(DiffPanes, {
1159
+ after,
1160
+ before
1161
+ }),
1162
+ decision === null ? /* @__PURE__ */ jsx_runtime2.jsx(DiffActions, {
1163
+ decide,
1164
+ pending
1165
+ }) : /* @__PURE__ */ jsx_runtime2.jsx(SettledNotice, {
1166
+ detail: `Change ${verdict(decision)}.`,
1167
+ label: "Decide again",
1168
+ onReset: () => setDecision(null)
1169
+ })
1170
+ ]
1171
+ });
1172
+ }
1173
+ function DiffHeader({
1174
+ decision,
1175
+ file,
1176
+ title
1177
+ }) {
1178
+ return /* @__PURE__ */ jsx_runtime2.jsxs("div", {
1179
+ className: "pragma-row pragma-row--between",
1180
+ children: [
1181
+ /* @__PURE__ */ jsx_runtime2.jsxs("div", {
1182
+ className: "pragma-row",
1183
+ children: [
1184
+ /* @__PURE__ */ jsx_runtime2.jsx("span", {
1185
+ className: "pragma-title",
1186
+ children: title
1187
+ }),
1188
+ file ? /* @__PURE__ */ jsx_runtime2.jsx("code", {
1189
+ className: "pragma-chip",
1190
+ children: file
1191
+ }) : null
1192
+ ]
1193
+ }),
1194
+ decision === null ? null : /* @__PURE__ */ jsx_runtime2.jsx(Badge, {
1195
+ tone: decision ? "success" : "danger",
1196
+ children: verdictLabel(decision)
1197
+ })
1198
+ ]
1199
+ });
1200
+ }
1201
+ function DiffActions({
1202
+ decide,
1203
+ pending
1204
+ }) {
1205
+ return /* @__PURE__ */ jsx_runtime2.jsxs("div", {
1206
+ className: "pragma-row pragma-row--end",
1207
+ children: [
1208
+ /* @__PURE__ */ jsx_runtime2.jsx(Button, {
1209
+ disabled: pending,
1210
+ onClick: () => void decide(false),
1211
+ variant: "outline",
1212
+ children: "Reject"
1213
+ }),
1214
+ /* @__PURE__ */ jsx_runtime2.jsx(Button, {
1215
+ disabled: pending,
1216
+ onClick: () => void decide(true),
1217
+ variant: "primary",
1218
+ children: "Accept"
1219
+ })
1220
+ ]
1221
+ });
1222
+ }
1223
+ function verdict(accepted) {
1224
+ return accepted ? "accepted" : "rejected";
1225
+ }
1226
+ function verdictLabel(accepted) {
1227
+ return accepted ? "Accepted" : "Rejected";
1228
+ }
1229
+ function DiffPanes({ after, before }) {
1230
+ const rows = import_react.useMemo(() => alignDiffLines(before, after), [before, after]);
1231
+ return /* @__PURE__ */ jsx_runtime2.jsxs("div", {
1232
+ className: "pragma-diff",
1233
+ children: [
1234
+ /* @__PURE__ */ jsx_runtime2.jsx(DiffPane, {
1235
+ label: "Before",
1236
+ lines: rows.map((row) => row.before),
1237
+ side: "before"
1238
+ }),
1239
+ /* @__PURE__ */ jsx_runtime2.jsx(DiffPane, {
1240
+ label: "After",
1241
+ lines: rows.map((row) => row.after),
1242
+ side: "after"
1243
+ })
1244
+ ]
1245
+ });
1246
+ }
1247
+ function segmentNodes(line) {
1248
+ let column = 0;
1249
+ return line.segments.map((segment) => {
1250
+ const key = `${line.key}:${column}`;
1251
+ column += segment.text.length;
1252
+ return /* @__PURE__ */ jsx_runtime2.jsx("span", {
1253
+ className: segment.changed ? "pragma-diff__changed" : undefined,
1254
+ children: segment.text
1255
+ }, key);
1256
+ });
1257
+ }
1258
+ function DiffPane({
1259
+ label,
1260
+ lines,
1261
+ side
1262
+ }) {
1263
+ return /* @__PURE__ */ jsx_runtime2.jsxs("div", {
1264
+ className: `pragma-diff__pane pragma-diff__pane--${side}`,
1265
+ children: [
1266
+ /* @__PURE__ */ jsx_runtime2.jsxs("div", {
1267
+ className: "pragma-diff__label",
1268
+ children: [
1269
+ /* @__PURE__ */ jsx_runtime2.jsx("span", {
1270
+ children: label
1271
+ }),
1272
+ /* @__PURE__ */ jsx_runtime2.jsx("span", {
1273
+ children: side === "before" ? "−" : "+"
1274
+ })
1275
+ ]
1276
+ }),
1277
+ /* @__PURE__ */ jsx_runtime2.jsx("div", {
1278
+ className: "pragma-diff__lines",
1279
+ children: lines.map((line) => /* @__PURE__ */ jsx_runtime2.jsxs("div", {
1280
+ className: `pragma-diff__line pragma-diff__line--${line.kind}`,
1281
+ children: [
1282
+ /* @__PURE__ */ jsx_runtime2.jsx("span", {
1283
+ "aria-hidden": line.number === null,
1284
+ className: "pragma-diff__gutter",
1285
+ children: line.number ?? ""
1286
+ }),
1287
+ /* @__PURE__ */ jsx_runtime2.jsx("code", {
1288
+ className: "pragma-diff__text",
1289
+ children: segmentNodes(line)
1290
+ })
1291
+ ]
1292
+ }, line.key))
1293
+ })
1294
+ ]
1295
+ });
1296
+ }
1297
+ function SettledNotice({
1298
+ detail,
1299
+ label,
1300
+ onReset
1301
+ }) {
1302
+ return /* @__PURE__ */ jsx_runtime2.jsxs("div", {
1303
+ className: "pragma-row pragma-row--between",
1304
+ children: [
1305
+ /* @__PURE__ */ jsx_runtime2.jsx("span", {
1306
+ className: "pragma-hint",
1307
+ children: detail
1308
+ }),
1309
+ /* @__PURE__ */ jsx_runtime2.jsx(Button, {
1310
+ onClick: onReset,
1311
+ size: "sm",
1312
+ variant: "ghost",
1313
+ children: label
1314
+ })
1315
+ ]
1316
+ });
1317
+ }
1318
+ function AgentProgress({
1319
+ tabIds,
1320
+ title = "Agent progress"
1321
+ }) {
1322
+ const [entries, setEntries] = import_react.useState([]);
1323
+ const key = tabIds.join(" ");
1324
+ const tabIdsRef = import_react.useRef(tabIds);
1325
+ tabIdsRef.current = tabIds;
1326
+ import_react.useEffect(() => scratchpadBridge().subscribeAgentProgress(tabIdsRef.current, setEntries), [key]);
1327
+ return /* @__PURE__ */ jsx_runtime2.jsxs(Card, {
1328
+ children: [
1329
+ /* @__PURE__ */ jsx_runtime2.jsxs("div", {
1330
+ className: "pragma-row pragma-row--between",
1331
+ children: [
1332
+ /* @__PURE__ */ jsx_runtime2.jsx("span", {
1333
+ className: "pragma-title",
1334
+ children: title
1335
+ }),
1336
+ /* @__PURE__ */ jsx_runtime2.jsxs("span", {
1337
+ className: "pragma-hint",
1338
+ children: [
1339
+ entries.length,
1340
+ " ",
1341
+ entries.length === 1 ? "tab" : "tabs"
1342
+ ]
1343
+ })
1344
+ ]
1345
+ }),
1346
+ entries.length === 0 ? /* @__PURE__ */ jsx_runtime2.jsx("p", {
1347
+ className: "pragma-empty",
1348
+ children: "No agent tabs are reporting yet."
1349
+ }) : /* @__PURE__ */ jsx_runtime2.jsx("div", {
1350
+ className: "pragma-stack",
1351
+ children: entries.map((entry) => /* @__PURE__ */ jsx_runtime2.jsx(AgentProgressRow, {
1352
+ entry
1353
+ }, entry.tabId))
1354
+ })
1355
+ ]
1356
+ });
1357
+ }
1358
+ function AgentProgressRow({ entry }) {
1359
+ const meta = STATUS_META[entry.status];
1360
+ return /* @__PURE__ */ jsx_runtime2.jsxs("div", {
1361
+ className: "pragma-progress-row",
1362
+ children: [
1363
+ /* @__PURE__ */ jsx_runtime2.jsxs("div", {
1364
+ className: "pragma-row pragma-row--between",
1365
+ children: [
1366
+ /* @__PURE__ */ jsx_runtime2.jsx("span", {
1367
+ className: "pragma-progress-row__name",
1368
+ children: entry.title ?? entry.agent ?? entry.tabId
1369
+ }),
1370
+ /* @__PURE__ */ jsx_runtime2.jsx(Badge, {
1371
+ tone: meta.tone,
1372
+ children: meta.label
1373
+ })
1374
+ ]
1375
+ }),
1376
+ /* @__PURE__ */ jsx_runtime2.jsx(Progress, {
1377
+ max: 100,
1378
+ tone: meta.tone,
1379
+ value: meta.value
1380
+ })
1381
+ ]
1382
+ });
1383
+ }
1384
+ var STATUS_META = {
1385
+ running: { label: "Running", tone: "primary", value: 40 },
1386
+ attention: { label: "Needs attention", tone: "warning", value: 70 },
1387
+ done: { label: "Done", tone: "success", value: 100 },
1388
+ cleared: { label: "Idle", tone: "neutral", value: 100 }
1389
+ };