@invana/blocks 0.0.32

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/index.cjs ADDED
@@ -0,0 +1,2095 @@
1
+ 'use strict';
2
+
3
+ var React18 = require('react');
4
+ var charts = require('@invana/charts');
5
+ var ui = require('@invana/ui');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+ var reactHookForm = require('react-hook-form');
8
+ var forms = require('@invana/forms');
9
+ var tables = require('@invana/tables');
10
+
11
+ function _interopNamespace(e) {
12
+ if (e && e.__esModule) return e;
13
+ var n = Object.create(null);
14
+ if (e) {
15
+ Object.keys(e).forEach(function (k) {
16
+ if (k !== 'default') {
17
+ var d = Object.getOwnPropertyDescriptor(e, k);
18
+ Object.defineProperty(n, k, d.get ? d : {
19
+ enumerable: true,
20
+ get: function () { return e[k]; }
21
+ });
22
+ }
23
+ });
24
+ }
25
+ n.default = e;
26
+ return Object.freeze(n);
27
+ }
28
+
29
+ var React18__namespace = /*#__PURE__*/_interopNamespace(React18);
30
+
31
+ // src/kinds.ts
32
+ var BLOCKS = [
33
+ { id: "confirm", name: "Confirm", returns: "boolean", tier: "today" },
34
+ { id: "single", name: "Single choice", returns: "string", tier: "today" },
35
+ { id: "multi", name: "Multiple choice", returns: "string[]", tier: "today" },
36
+ { id: "quick", name: "Quick pick", returns: "string", tier: "today" },
37
+ { id: "period", name: "Period", returns: "{from, to, label}", tier: "today" },
38
+ { id: "number", name: "Number", returns: "number", tier: "today" },
39
+ { id: "short", name: "Short text", returns: "string", tier: "today" },
40
+ { id: "long", name: "Long text", returns: "string", tier: "today" },
41
+ { id: "entity", name: "Entity pick", returns: "id[]", tier: "next" },
42
+ { id: "scale", name: "Scale", returns: "1\u20135", tier: "today" },
43
+ { id: "multistep", name: "Multi-step", returns: "Record<id, value>", tier: "today" },
44
+ { id: "form", name: "Form", returns: "Record<field, value>", tier: "next" },
45
+ { id: "weights", name: "Weights", returns: "Record<objective, 0\u2013100>", tier: "today" },
46
+ { id: "approval", name: "Approval", returns: "approve | reject", tier: "today" },
47
+ { id: "interpretation", name: "Interpretation", returns: "Record<slot, value>", tier: "today" },
48
+ { id: "fork", name: "Reading fork", returns: "readingId", tier: "next" },
49
+ { id: "plan", name: "Plan preview", returns: "stepId[]", tier: "next" },
50
+ { id: "modelspec", name: "Model spec", returns: "{outcome, predictors[], group?, controls[]}", tier: "next" },
51
+ { id: "hypothesis", name: "Hypothesis", returns: "{test, tails, alpha}", tier: "next" },
52
+ { id: "range", name: "Range", returns: "{min, max}", tier: "next" },
53
+ { id: "suggestions", name: "Suggestions", returns: "string", tier: "today" },
54
+ { id: "narrative", name: "Narrative", tier: "today" },
55
+ { id: "metric", name: "Metric", tier: "today" },
56
+ { id: "grid", name: "Metric grid", tier: "today" },
57
+ { id: "table", name: "Table preview", tier: "today" },
58
+ { id: "attr", name: "Attribute matrix", tier: "today" },
59
+ { id: "record", name: "Record", tier: "today" },
60
+ { id: "ranked", name: "Ranked list", tier: "today" },
61
+ { id: "timeseries", name: "Time series", tier: "today" },
62
+ { id: "bars", name: "Bar comparison", tier: "next" },
63
+ { id: "waterfall", name: "Bridge", tier: "next" },
64
+ { id: "matrix", name: "Matrix", tier: "next" },
65
+ { id: "funnel", name: "Funnel", tier: "next" },
66
+ { id: "histogram", name: "Distribution", tier: "next" },
67
+ { id: "timeline", name: "Timeline", tier: "today" },
68
+ { id: "subgraph", name: "Subgraph", tier: "later" },
69
+ { id: "method", name: "Method", tier: "today" },
70
+ { id: "citations", name: "Citations", tier: "today" },
71
+ { id: "files", name: "Files", tier: "today" },
72
+ { id: "proposal", name: "Proposal", tier: "today" },
73
+ { id: "cannot", name: "Cannot answer", tier: "today" },
74
+ { id: "caveat", name: "Caveat", tier: "today" },
75
+ { id: "scope", name: "Scope line", tier: "today" },
76
+ { id: "checks", name: "Checks", tier: "next" },
77
+ { id: "trace", name: "Progress trace", tier: "today" },
78
+ { id: "activity", name: "Layer activity", tier: "today" },
79
+ { id: "gantt", name: "Task timeline", tier: "today" },
80
+ { id: "heatstrip", name: "Heat strip", tier: "today" },
81
+ { id: "test", name: "Test result", tier: "next" },
82
+ { id: "coef", name: "Coefficients", tier: "next" },
83
+ { id: "forest", name: "Forest plot", tier: "next" },
84
+ { id: "scatter", name: "Scatter", tier: "next" },
85
+ { id: "box", name: "Box plot", tier: "next" },
86
+ { id: "correlation", name: "Correlation", tier: "next" },
87
+ { id: "control", name: "Control chart", tier: "next" },
88
+ { id: "pareto", name: "Pareto", tier: "next" },
89
+ { id: "survival", name: "Survival curve", tier: "next" },
90
+ { id: "tornado", name: "Tornado", tier: "next" },
91
+ { id: "decomposition", name: "Decomposition", tier: "next" },
92
+ { id: "modeleval", name: "Model evaluation", tier: "next" },
93
+ { id: "pivot", name: "Pivot", tier: "next" },
94
+ { id: "profile", name: "Column profile", tier: "next" },
95
+ { id: "evidence", name: "Evidence strength", tier: "next" },
96
+ { id: "dumbbell", name: "Dumbbell", tier: "next" },
97
+ { id: "quantiles", name: "Quantiles", tier: "today" }
98
+ ];
99
+ var ASK_KINDS = BLOCKS.filter((b) => "returns" in b).map((b) => b.id);
100
+ var ANSWER_KINDS = BLOCKS.filter((b) => !("returns" in b)).map((b) => b.id);
101
+
102
+ // src/values.ts
103
+ var VALUE_TYPES = [
104
+ { id: "quantity", name: "Quantity with unit", example: "2,480 kg/ha \xB7 26 d" },
105
+ { id: "currency", name: "Currency", example: "\xA384k \xB7 \u2212\xA3620k \xB7 \xA34.1M" },
106
+ { id: "percent", name: "Share", example: "76% \xB7 8.2%" },
107
+ { id: "pp", name: "Percentage points", example: "+4.8 pts \xB7 \u25BC 1.8 pts" },
108
+ { id: "bp", name: "Basis points", example: "+12 bp" },
109
+ { id: "ratio", name: "Ratio or index", example: "OR 1.29 \xB7 beta 0.38 \xB7 1.3\xD7" },
110
+ { id: "rate", name: "Rate per N", example: "8.2 per 100 discharges" },
111
+ { id: "count", name: "Count with n", example: "1,284 plots \xB7 n = 1,061" },
112
+ { id: "duration", name: "Duration", example: "1.2 s \xB7 3 min \xB7 26 d" },
113
+ { id: "estimate", name: "Estimate with interval", example: "1.29 (95% CI 1.04\u20131.60)" },
114
+ { id: "pvalue", name: "p-value", example: "p = 0.02 \xB7 p < 0.001" },
115
+ { id: "score", name: "Ordinal score", example: "7 / 9 \xB7 4 of 5" },
116
+ { id: "time", name: "Point in time", example: "as of 29 Sep 06:00 \xB7 Q3 2026" }
117
+ ];
118
+ var VARIANT = {
119
+ primary: "default",
120
+ secondary: "outline",
121
+ ghost: "ghost",
122
+ link: "link"
123
+ };
124
+ function ActionRow({
125
+ actions,
126
+ onAction
127
+ }) {
128
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.ClarifyActions, { className: "w-full", children: actions.map((a) => /* @__PURE__ */ jsxRuntime.jsxs(React18__namespace.Fragment, { children: [
129
+ a.push ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "flex-1" }) : null,
130
+ /* @__PURE__ */ jsxRuntime.jsx(
131
+ ui.Button,
132
+ {
133
+ size: "xs",
134
+ variant: VARIANT[a.variant ?? "secondary"],
135
+ onClick: () => onAction(a.id),
136
+ children: a.label
137
+ }
138
+ )
139
+ ] }, a.id)) });
140
+ }
141
+ function strong(text) {
142
+ return text.split(/(\*\*[^*]+\*\*)/).filter(Boolean).map(
143
+ (part, i) => part.startsWith("**") && part.endsWith("**") ? /* @__PURE__ */ jsxRuntime.jsx("strong", { children: part.slice(2, -2) }, i) : part
144
+ );
145
+ }
146
+ var DIRECTION = /([▲▼]\s?[+−-]?[\d.,]+(?:\s?(?:pts|pp|bps|%))?)/;
147
+ var MARKER = /(\[\d+\])/;
148
+ function tone(figure2) {
149
+ return figure2.startsWith("\u25B2") ? "text-primary" : "text-destructive";
150
+ }
151
+ function prose(text, marker) {
152
+ let key = 0;
153
+ const plain = (part) => part.split(MARKER).filter(Boolean).flatMap((piece) => {
154
+ const cite = piece.match(/^\[(\d+)\]$/);
155
+ if (cite) return [/* @__PURE__ */ jsxRuntime.jsx(React18__namespace.Fragment, { children: marker(Number(cite[1])) }, key++)];
156
+ return piece.split(DIRECTION).filter(Boolean).map(
157
+ (bit) => DIRECTION.test(bit) && /^[▲▼]/.test(bit) ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: tone(bit), children: bit }, key++) : /* @__PURE__ */ jsxRuntime.jsx(React18__namespace.Fragment, { children: bit }, key++)
158
+ );
159
+ });
160
+ return text.split(/(\*\*[^*]+\*\*)/).filter(Boolean).flatMap((part) => {
161
+ if (!(part.startsWith("**") && part.endsWith("**"))) return plain(part);
162
+ const figure2 = part.slice(2, -2);
163
+ return [
164
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { className: /^[▲▼]/.test(figure2) ? tone(figure2) : void 0, children: figure2 }, key++)
165
+ ];
166
+ });
167
+ }
168
+ var RATE_TONE = { hot: "text-info", bad: "text-destructive" };
169
+ var lit = (lane) => lane.cells.some((v) => v != null && v > 0);
170
+ function LaneRow({
171
+ lane,
172
+ depth,
173
+ open,
174
+ onToggle,
175
+ stale,
176
+ cell,
177
+ pin,
178
+ onPin
179
+ }) {
180
+ const name = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
181
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate @max-[300px]/activity:hidden", children: lane.label }),
182
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate @min-[301px]/activity:hidden", children: lane.short ?? lane.label })
183
+ ] });
184
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
185
+ onToggle ? /* @__PURE__ */ jsxRuntime.jsxs(
186
+ "button",
187
+ {
188
+ type: "button",
189
+ "aria-expanded": open,
190
+ onClick: onToggle,
191
+ className: "flex min-w-0 items-center gap-1 text-left hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
192
+ children: [
193
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "w-2.5 shrink-0 text-muted-foreground", children: open ? "\u25BE" : "\u25B8" }),
194
+ name
195
+ ]
196
+ }
197
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
198
+ "span",
199
+ {
200
+ className: ui.cn(
201
+ "flex min-w-0 items-center",
202
+ (lane.off || depth > 0) && "text-muted-foreground",
203
+ depth > 0 && "pl-3.5 text-sm"
204
+ ),
205
+ children: name
206
+ }
207
+ ),
208
+ /* @__PURE__ */ jsxRuntime.jsx(
209
+ charts.HeatLane,
210
+ {
211
+ "aria-label": lane.label,
212
+ values: lane.off ? lane.cells.map(() => null) : lane.cells,
213
+ marks: lane.marks,
214
+ stale,
215
+ cellLabel: cell ? (i) => `${lane.label} \xB7 cell ${i + 1} \xB7 ${cell}` : void 0,
216
+ pinned: pin?.lane === lane.id ? pin.at : null,
217
+ onPin: stale ? void 0 : (at) => onPin(at == null ? null : { lane: lane.id, at })
218
+ }
219
+ ),
220
+ /* @__PURE__ */ jsxRuntime.jsx(
221
+ "span",
222
+ {
223
+ className: ui.cn(
224
+ "truncate text-right font-mono text-sm text-muted-foreground tabular-nums",
225
+ !stale && lane.rateTone && RATE_TONE[lane.rateTone]
226
+ ),
227
+ children: stale ? "\u2014" : lane.rate
228
+ }
229
+ )
230
+ ] });
231
+ }
232
+ function ActivityBlock({ spec, onAction }) {
233
+ const stale = spec.state === "stale";
234
+ const [opened, setOpened] = React18__namespace.useState(
235
+ () => new Set(spec.lanes.filter((l) => l.open).map((l) => l.id))
236
+ );
237
+ const [pin, setPin] = React18__namespace.useState(
238
+ spec.pinned ? { lane: spec.pinned.lane, at: spec.pinned.at } : null
239
+ );
240
+ const toggle = (id) => setOpened((s) => {
241
+ const next = new Set(s);
242
+ if (next.has(id)) next.delete(id);
243
+ else next.add(id);
244
+ return next;
245
+ });
246
+ const pinCell = (next) => {
247
+ setPin(next);
248
+ if (next) onAction?.("pin", next);
249
+ };
250
+ const record = spec.pinned && pin && spec.pinned.lane === pin.lane && spec.pinned.at === pin.at ? spec.pinned : null;
251
+ const total = spec.lanes.length;
252
+ const lighted = spec.lanes.filter(lit).length;
253
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "@container/activity flex min-w-0 flex-col gap-2", children: [
254
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-[minmax(0,7rem)_minmax(0,1fr)_minmax(2.5rem,auto)] items-center gap-x-2 gap-y-1.5 @max-[300px]/activity:grid-cols-[4rem_minmax(0,1fr)_minmax(1.5rem,auto)]", children: [
255
+ spec.bands?.length ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
256
+ /* @__PURE__ */ jsxRuntime.jsx("span", {}),
257
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-w-0 gap-px text-xs", children: spec.bands.map((b, i) => /* @__PURE__ */ jsxRuntime.jsx(
258
+ "span",
259
+ {
260
+ style: { flex: b.span },
261
+ className: ui.cn(
262
+ "min-w-0 truncate rounded-[2px] px-1",
263
+ b.current ? "bg-info/15 text-foreground" : "bg-muted text-muted-foreground"
264
+ ),
265
+ children: b.label
266
+ },
267
+ i
268
+ )) }),
269
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-right text-xs text-muted-foreground", children: spec.state === "live" ? "now" : "" })
270
+ ] }) : null,
271
+ spec.lanes.map((lane) => {
272
+ const open = opened.has(lane.id);
273
+ const kids = lane.children?.length ? lane.children : null;
274
+ return /* @__PURE__ */ jsxRuntime.jsxs(React18__namespace.Fragment, { children: [
275
+ /* @__PURE__ */ jsxRuntime.jsx(
276
+ LaneRow,
277
+ {
278
+ lane,
279
+ depth: 0,
280
+ open,
281
+ onToggle: kids ? () => toggle(lane.id) : void 0,
282
+ stale,
283
+ cell: spec.cell,
284
+ pin,
285
+ onPin: pinCell
286
+ }
287
+ ),
288
+ open && kids ? kids.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
289
+ LaneRow,
290
+ {
291
+ lane: child,
292
+ depth: 1,
293
+ open: false,
294
+ stale,
295
+ cell: spec.cell,
296
+ pin,
297
+ onPin: pinCell
298
+ },
299
+ child.id
300
+ )) : null
301
+ ] }, lane.id);
302
+ }),
303
+ spec.axis?.length ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
304
+ /* @__PURE__ */ jsxRuntime.jsx("span", {}),
305
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-w-0 justify-between text-xs text-muted-foreground tabular-nums", children: spec.axis.map((a, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { children: a }, i)) }),
306
+ /* @__PURE__ */ jsxRuntime.jsx("span", {})
307
+ ] }) : null
308
+ ] }),
309
+ record ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5 rounded-control border border-border bg-muted/40 p-2", children: [
310
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-baseline gap-2", children: [
311
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate font-medium", children: record.title }),
312
+ record.time ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 font-mono text-sm text-muted-foreground", children: record.time }) : null
313
+ ] }),
314
+ record.query ? /* @__PURE__ */ jsxRuntime.jsx("code", { className: "truncate font-mono text-sm", children: record.query }) : null,
315
+ record.rows?.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.PropertyList, { children: record.rows.map((r) => /* @__PURE__ */ jsxRuntime.jsx(ui.PropertyRow, { label: r.label, children: r.value }, r.label)) }) : null,
316
+ record.note || record.action ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-2 text-sm text-muted-foreground", children: [
317
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: record.note }),
318
+ record.action ? /* @__PURE__ */ jsxRuntime.jsx(
319
+ ui.Button,
320
+ {
321
+ variant: "link",
322
+ size: "xs",
323
+ className: "h-auto p-0",
324
+ onClick: () => onAction?.("action", record.action.id),
325
+ children: record.action.label
326
+ }
327
+ ) : null
328
+ ] }) : null
329
+ ] }) : null,
330
+ spec.legend ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-3 text-sm text-muted-foreground", children: [
331
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1.5", children: [
332
+ /* @__PURE__ */ jsxRuntime.jsx("i", { "aria-hidden": true, className: "size-2 rounded-[1px] bg-info" }),
333
+ "touched"
334
+ ] }),
335
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1.5", children: [
336
+ /* @__PURE__ */ jsxRuntime.jsx("i", { "aria-hidden": true, className: "size-2 rounded-[1px] bg-destructive" }),
337
+ "refused"
338
+ ] }),
339
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1.5", children: [
340
+ /* @__PURE__ */ jsxRuntime.jsx("i", { "aria-hidden": true, className: "size-2 rounded-[1px] bg-warning" }),
341
+ "left the boundary"
342
+ ] })
343
+ ] }) : null,
344
+ spec.hint ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: ui.cn("text-sm text-muted-foreground", stale && "text-warning"), children: strong(spec.hint) }) : null,
345
+ spec.actions?.length ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-2 border-t border-border pt-1.5 text-sm text-muted-foreground", children: [
346
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "shrink-0 tabular-nums", children: [
347
+ total,
348
+ " layers \xB7 ",
349
+ lighted,
350
+ " lit"
351
+ ] }),
352
+ /* @__PURE__ */ jsxRuntime.jsx(ActionRow, { actions: spec.actions, onAction: (id) => onAction?.("action", id) })
353
+ ] }) : null
354
+ ] });
355
+ }
356
+ var decimals = (v) => (String(v).split(".")[1] ?? "").length;
357
+ var MUTED = "color-mix(in srgb, var(--color-muted-foreground) 45%, transparent)";
358
+ function BarsBlock({ spec }) {
359
+ const single = spec.series.length === 1;
360
+ const places = Math.max(0, ...spec.series.flatMap((s) => s.values.map(decimals)));
361
+ const write = (v) => v.toLocaleString("en-GB", { minimumFractionDigits: places, maximumFractionDigits: places }) + (spec.unit === "%" ? "%" : "");
362
+ const highlight = spec.highlight == null ? -1 : spec.groups.indexOf(spec.highlight);
363
+ const called = single && highlight >= 0;
364
+ return /* @__PURE__ */ jsxRuntime.jsx(
365
+ charts.BarChartV,
366
+ {
367
+ "aria-label": spec.series.map((s) => s.name).join(", ") + (spec.unit ? `, ${spec.unit}` : ""),
368
+ variant: "comparison",
369
+ height: 80,
370
+ labelMode: called ? "last" : "none",
371
+ color: called ? "var(--color-muted-foreground)" : "var(--color-primary)",
372
+ highlightColor: "var(--color-primary)",
373
+ highlightIndex: called ? highlight : null,
374
+ target: spec.target ? { ...spec.target, color: "var(--color-warning)" } : void 0,
375
+ planLabel: spec.plan?.name,
376
+ series: single && !spec.plan ? void 0 : spec.series.map((s) => ({
377
+ name: s.name,
378
+ color: s.muted ? MUTED : "var(--color-primary)"
379
+ })),
380
+ data: spec.groups.map(
381
+ (label, i) => single ? {
382
+ label,
383
+ value: spec.series[0].values[i] ?? 0,
384
+ display: write(spec.series[0].values[i] ?? 0),
385
+ plan: spec.plan?.values[i]
386
+ } : {
387
+ label,
388
+ values: spec.series.map((s) => s.values[i] ?? 0),
389
+ display: spec.series.map((s) => write(s.values[i] ?? 0))
390
+ }
391
+ )
392
+ }
393
+ );
394
+ }
395
+ function Chips({
396
+ items,
397
+ layout,
398
+ sent,
399
+ onSelect
400
+ }) {
401
+ const stack = layout === "stack";
402
+ return /* @__PURE__ */ jsxRuntime.jsx(
403
+ "div",
404
+ {
405
+ className: ui.cn(
406
+ "flex gap-1.5",
407
+ stack ? "flex-col" : "flex-wrap @max-[22rem]:flex-col"
408
+ ),
409
+ children: items.map((text) => {
410
+ const done = sent?.includes(text);
411
+ return /* @__PURE__ */ jsxRuntime.jsxs(
412
+ ui.Button,
413
+ {
414
+ type: "button",
415
+ size: "xs",
416
+ variant: "outline",
417
+ disabled: done,
418
+ className: ui.cn(
419
+ // A follow-up is a whole prompt and can outrun the line: it wraps
420
+ // within the thread rather than running past it.
421
+ "h-auto min-h-control-xs max-w-full items-start whitespace-normal py-0.5 text-left font-normal",
422
+ stack ? "w-full justify-start" : "rounded-full @max-[22rem]:w-full @max-[22rem]:justify-start @max-[22rem]:rounded-control"
423
+ ),
424
+ onClick: () => onSelect?.(text),
425
+ children: [
426
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "text-muted-foreground", children: "\u21B3" }),
427
+ text
428
+ ]
429
+ },
430
+ text
431
+ );
432
+ })
433
+ }
434
+ );
435
+ }
436
+ var SuggestionChips = React18__namespace.forwardRef(
437
+ ({ items, groups, layout, sent, lead, onSelect, className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: ui.cn("@container flex flex-col gap-1.5", className), ...props, children: [
438
+ lead != null ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: lead }) : null,
439
+ groups?.length ? groups.map((group, i) => /* @__PURE__ */ jsxRuntime.jsxs(React18__namespace.Fragment, { children: [
440
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Eyebrow, { children: group.label }),
441
+ /* @__PURE__ */ jsxRuntime.jsx(Chips, { items: group.items, layout, sent, onSelect })
442
+ ] }, i)) : items?.length ? /* @__PURE__ */ jsxRuntime.jsx(Chips, { items, layout, sent, onSelect }) : null
443
+ ] })
444
+ );
445
+ SuggestionChips.displayName = "SuggestionChips";
446
+ function CannotBlock({ spec, onAction }) {
447
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
448
+ /* @__PURE__ */ jsxRuntime.jsx(
449
+ ui.CannotAnswerCard,
450
+ {
451
+ remedy: spec.remedy,
452
+ label: spec.partial ? "answered in part" : void 0,
453
+ tone: spec.partial ? "info" : "default",
454
+ children: spec.reason
455
+ }
456
+ ),
457
+ spec.nearest?.length ? /* @__PURE__ */ jsxRuntime.jsx(
458
+ SuggestionChips,
459
+ {
460
+ lead: "I can answer instead",
461
+ items: spec.nearest,
462
+ onSelect: (text) => onAction?.("prompt", text)
463
+ }
464
+ ) : null
465
+ ] });
466
+ }
467
+ var TONE = {
468
+ warning: "warning",
469
+ info: "info",
470
+ bad: "destructive"
471
+ };
472
+ function Note({
473
+ note,
474
+ onAction
475
+ }) {
476
+ return /* @__PURE__ */ jsxRuntime.jsx(
477
+ ui.CaveatNote,
478
+ {
479
+ label: note.label,
480
+ tone: TONE[note.tone ?? "warning"],
481
+ action: note.action?.label,
482
+ onAction: note.action ? () => onAction(note.action.id) : void 0,
483
+ children: note.text
484
+ }
485
+ );
486
+ }
487
+ function CaveatBlock({ spec, onAction }) {
488
+ const [open, setOpen] = React18__namespace.useState(!spec.folded);
489
+ const act = (id) => onAction?.("action", id);
490
+ if (!spec.items) return /* @__PURE__ */ jsxRuntime.jsx(Note, { note: spec, onAction: act });
491
+ const notes = spec.items.map((note, i) => /* @__PURE__ */ jsxRuntime.jsx(Note, { note, onAction: act }, i));
492
+ if (!spec.folded) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: notes });
493
+ return /* @__PURE__ */ jsxRuntime.jsx(
494
+ ui.ChatSessionDisclosure,
495
+ {
496
+ variant: "inline",
497
+ label: `${spec.items.length} caveats`,
498
+ meta: open ? void 0 : spec.items.map((n) => n.label).join(" \xB7 "),
499
+ open,
500
+ onOpenChange: setOpen,
501
+ children: /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: notes })
502
+ }
503
+ );
504
+ }
505
+
506
+ // src/format.ts
507
+ function figureText(figure2) {
508
+ if (typeof figure2 === "string") return figure2;
509
+ if (typeof figure2 === "number") return figure2.toLocaleString("en-GB");
510
+ const n = figure2.value.toLocaleString("en-GB");
511
+ return figure2.unit ? `${n} ${figure2.unit}` : n;
512
+ }
513
+ function metricValue(value) {
514
+ return value == null ? "\u2014" : figureText(value);
515
+ }
516
+ var CAPTION_TONE = {
517
+ good: "success",
518
+ bad: "error",
519
+ warn: "warning",
520
+ neutral: void 0
521
+ };
522
+ var BADGE_TONE = {
523
+ good: "success",
524
+ bad: "destructive",
525
+ warn: "warning",
526
+ neutral: "muted"
527
+ };
528
+ function gaugeProps(gauge) {
529
+ if (!gauge) return {};
530
+ const min = gauge.min ?? 0;
531
+ const span = gauge.max - min || 1;
532
+ const fill = (gauge.value - min) / span;
533
+ if (gauge.target == null) return { meter: fill };
534
+ const write = (n) => `${n.toLocaleString("en-GB")}${gauge.unit ?? ""}`;
535
+ return {
536
+ gauge: {
537
+ fill,
538
+ mark: (gauge.target - min) / span,
539
+ labels: [write(min), `target ${write(gauge.target)}`, write(gauge.max)]
540
+ }
541
+ };
542
+ }
543
+ function CitationsBlock({ spec, active = spec.active }) {
544
+ const [open, setOpen] = React18__namespace.useState(!spec.folded);
545
+ const counts = spec.sources.map((s) => s.count);
546
+ const total = counts.every((c) => typeof c === "number") ? counts.reduce((a, b) => a + b, 0) : void 0;
547
+ const list = /* @__PURE__ */ jsxRuntime.jsx(ui.CitationList, { note: spec.note, noteTone: total === 0 ? "warning" : "muted", children: spec.sources.map((source, i) => /* @__PURE__ */ jsxRuntime.jsx(
548
+ ui.CitationRow,
549
+ {
550
+ marker: i + 1,
551
+ active: active === i + 1,
552
+ detail: source.detail,
553
+ count: source.count == null ? void 0 : figureText(source.count),
554
+ children: source.label
555
+ },
556
+ i
557
+ )) });
558
+ if (!spec.folded) return list;
559
+ return /* @__PURE__ */ jsxRuntime.jsx(
560
+ ui.ChatSessionDisclosure,
561
+ {
562
+ variant: "inline",
563
+ label: `${spec.sources.length} sources`,
564
+ meta: total != null ? `${total.toLocaleString("en-GB")} records` : void 0,
565
+ open,
566
+ onOpenChange: setOpen,
567
+ children: list
568
+ }
569
+ );
570
+ }
571
+ var figure = (value, tone2) => typeof value === "string" || typeof value === "number" ? /* @__PURE__ */ jsxRuntime.jsx("b", { className: ui.cn("font-mono font-normal text-foreground", tone2 === "warning" && "text-warning"), children: value }) : value;
572
+ var ConfirmCard = React18__namespace.forwardRef(
573
+ ({ question, description, heading, cost, costAs = "line", seamless, caveat, hint, className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: ui.cn("flex flex-col gap-2", className), ...props, children: [
574
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: heading || description ? "font-semibold" : void 0, children: question }),
575
+ description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "-mt-1.5 text-sm text-muted-foreground", children: description }) : null,
576
+ cost?.length && costAs === "strip" ? /* @__PURE__ */ jsxRuntime.jsx(
577
+ "dl",
578
+ {
579
+ className: ui.cn(
580
+ "grid",
581
+ // Seamless: it reaches out by a cell's padding and clips that off,
582
+ // so the outer cells are flush.
583
+ seamless ? "-mx-2 -my-1 [clip-path:inset(0.25rem_0.5rem)]" : "overflow-hidden rounded-md border border-border"
584
+ ),
585
+ style: { gridTemplateColumns: `repeat(${cost.length}, minmax(0, 1fr))` },
586
+ children: cost.map((c, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col border-e border-border/60 px-2 py-1 last:border-e-0", children: [
587
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "truncate text-xs text-muted-foreground", children: c.label }),
588
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: ui.cn("font-mono font-medium tabular-nums", c.tone === "warning" && "text-warning"), children: c.value })
589
+ ] }, i))
590
+ }
591
+ ) : cost?.length ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: cost.map((c, i) => /* @__PURE__ */ jsxRuntime.jsxs(React18__namespace.Fragment, { children: [
592
+ i > 0 ? " \xB7 " : null,
593
+ figure(c.value, c.tone),
594
+ " ",
595
+ c.label
596
+ ] }, i)) }) : null,
597
+ caveat,
598
+ /* @__PURE__ */ jsxRuntime.jsx(ui.ClarifyActions, { children }),
599
+ hint ? /* @__PURE__ */ jsxRuntime.jsx(ui.ClarifyFootnote, { children: hint }) : null
600
+ ] })
601
+ );
602
+ ConfirmCard.displayName = "ConfirmCard";
603
+ var isHeading = (text) => Boolean(text.heading || text.description);
604
+ function AskQuestion({ text, aside }) {
605
+ const question = /* @__PURE__ */ jsxRuntime.jsx("p", { className: isHeading(text) ? "font-semibold" : void 0, children: strong(text.question) });
606
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
607
+ aside ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [
608
+ question,
609
+ aside
610
+ ] }) : question,
611
+ text.description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "-mt-1.5 text-sm text-muted-foreground", children: text.description }) : null
612
+ ] });
613
+ }
614
+ function hintText(text) {
615
+ return text.split(/(\*\*[^*]+\*\*|`[^`]+`)/).filter(Boolean).map((part, i) => {
616
+ if (part.startsWith("**") && part.endsWith("**"))
617
+ return /* @__PURE__ */ jsxRuntime.jsx("b", { className: "font-mono font-normal text-foreground", children: part.slice(2, -2) }, i);
618
+ if (part.startsWith("`") && part.endsWith("`")) return /* @__PURE__ */ jsxRuntime.jsx(ui.Kbd, { children: part.slice(1, -1) }, i);
619
+ return part;
620
+ });
621
+ }
622
+ function AskLink({ children, onClick }) {
623
+ return /* @__PURE__ */ jsxRuntime.jsx(
624
+ "button",
625
+ {
626
+ type: "button",
627
+ onClick,
628
+ className: "shrink-0 whitespace-nowrap font-sans text-xs font-normal text-primary hover:underline focus-visible:underline focus-visible:outline-none",
629
+ children
630
+ }
631
+ );
632
+ }
633
+ function AskHint({ children, tone: tone2 }) {
634
+ if (!children) return null;
635
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.ClarifyFootnote, { className: tone2 === "warning" ? "text-warning" : tone2 === "error" ? "text-destructive" : void 0, children: hintText(children) });
636
+ }
637
+ var FIGURE_TONE = {
638
+ good: "success",
639
+ bad: "error",
640
+ warn: "warning",
641
+ neutral: void 0
642
+ };
643
+ function Icon({ path }) {
644
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.3, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: path }) });
645
+ }
646
+ function choiceParts(o) {
647
+ return {
648
+ props: {
649
+ detail: o.figure ? void 0 : o.note ?? o.detail,
650
+ lead: o.icon ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { path: o.icon }) : o.lead,
651
+ figure: o.figure && { value: o.figure.value, unit: o.figure.unit, tone: o.figure.tone && FIGURE_TONE[o.figure.tone] }
652
+ },
653
+ body: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
654
+ /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireChoiceTitle, { children: o.label }),
655
+ o.description ? /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireChoiceDescription, { children: o.description }) : null
656
+ ] })
657
+ };
658
+ }
659
+ function pickText(o, value) {
660
+ if (!o) return value == null || value === "" ? "\u2014" : String(value);
661
+ if (o.summary) return o.summary;
662
+ return o.detail ? `${o.label} \xB7 ${o.detail}` : o.label;
663
+ }
664
+ function joinPicks(picked) {
665
+ if (!picked.length) return "\u2014";
666
+ const summarised = picked.some((o) => o.summary);
667
+ return picked.map((o) => o.summary ?? o.label).join(summarised ? " \xB7 " : ", ");
668
+ }
669
+ function AskSummary({
670
+ rows,
671
+ change,
672
+ onChange,
673
+ children
674
+ }) {
675
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
676
+ /* @__PURE__ */ jsxRuntime.jsx(ui.PropertyList, { labelWidth: "auto", variant: "summary", children: rows.map((r) => /* @__PURE__ */ jsxRuntime.jsx(ui.PropertyRow, { label: r.label, mono: true, children: r.value }, r.label)) }),
677
+ children,
678
+ change ? /* @__PURE__ */ jsxRuntime.jsx(ui.ClarifyActions, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "xs", variant: "ghost", onClick: onChange, children: change }) }) : null
679
+ ] });
680
+ }
681
+ function costOf(cost) {
682
+ return cost?.map((c) => ({
683
+ label: c.label,
684
+ value: c.value.includes("**") ? hintText(c.value) : c.value,
685
+ tone: c.tone === "warn" || c.tone === "bad" ? "warning" : void 0
686
+ }));
687
+ }
688
+ function ConfirmAsk({ spec, state = "pending", value: given, onAction }) {
689
+ const pending = state === "pending";
690
+ if (!pending) {
691
+ const yes = given === void 0 ? spec.default ?? true : given === true;
692
+ const words = yes ? spec.settled?.yes ?? spec.yes : spec.settled?.no ?? spec.no;
693
+ return /* @__PURE__ */ jsxRuntime.jsx(AskSummary, { rows: [{ label: spec.label ?? "Decision", value: words }], children: /* @__PURE__ */ jsxRuntime.jsx(AskHint, { children: spec.hint }) });
694
+ }
695
+ const chosen = spec.default ?? true;
696
+ const end = spec.align === "end";
697
+ const button = (value) => /* @__PURE__ */ jsxRuntime.jsx(
698
+ ui.Button,
699
+ {
700
+ size: "xs",
701
+ variant: value === chosen ? "default" : value === false && spec.dismiss ? "ghost" : "outline",
702
+ className: end && value === chosen ? "ms-auto" : void 0,
703
+ onClick: () => onAction?.("reply", value),
704
+ children: value ? spec.yes : spec.no
705
+ },
706
+ String(value)
707
+ );
708
+ const order = end ? [!chosen, chosen] : [chosen, !chosen];
709
+ return /* @__PURE__ */ jsxRuntime.jsx(
710
+ ConfirmCard,
711
+ {
712
+ question: strong(spec.question),
713
+ description: spec.description,
714
+ heading: spec.heading,
715
+ cost: costOf(spec.cost),
716
+ costAs: spec.costAs,
717
+ seamless: true,
718
+ caveat: spec.caveat ? /* @__PURE__ */ jsxRuntime.jsx(ui.CaveatNote, { label: spec.caveat.label, children: spec.caveat.text }) : void 0,
719
+ hint: spec.hint ? hintText(spec.hint) : void 0,
720
+ children: order.map(button)
721
+ }
722
+ );
723
+ }
724
+ function FileGlyph() {
725
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "1.3", "aria-hidden": true, children: [
726
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 1.5h5.5L13 5v9.5H4z" }),
727
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9.5 1.5V5H13" })
728
+ ] });
729
+ }
730
+ var noteOf = (file) => file.note ?? file.size;
731
+ function FilesBlock({ spec, onAction }) {
732
+ const download = (file) => onAction?.("download", file.digest ?? file.name);
733
+ if (spec.files.length === 1) {
734
+ const file = spec.files[0];
735
+ return /* @__PURE__ */ jsxRuntime.jsx(
736
+ ui.ArtifactCard,
737
+ {
738
+ file: { name: file.name, size: file.size, digest: file.digest },
739
+ note: noteOf(file),
740
+ aside: file.status ? /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: "soft", size: "xs", tone: BADGE_TONE[file.status.tone ?? "neutral"], className: "font-normal", children: file.status.label }) : spec.download ? /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "button", variant: "outline", size: "xs", onClick: () => download(file), children: "Download" }) : void 0
741
+ }
742
+ );
743
+ }
744
+ return /* @__PURE__ */ jsxRuntime.jsx(
745
+ ui.ArtifactTable,
746
+ {
747
+ variant: "list",
748
+ files: spec.files.map((file) => ({
749
+ name: file.name,
750
+ size: file.size,
751
+ digest: spec.download ? void 0 : file.digest,
752
+ icon: spec.download ? /* @__PURE__ */ jsxRuntime.jsx(FileGlyph, {}) : void 0
753
+ })),
754
+ onDownload: spec.download ? (_, i) => download(spec.files[i]) : void 0
755
+ }
756
+ );
757
+ }
758
+ var FIELD_TYPE = {
759
+ number: "number",
760
+ text: "text",
761
+ date: "text",
762
+ select: "select"
763
+ };
764
+ function boundsRule(f) {
765
+ if (f.above == null && f.below == null) return void 0;
766
+ return {
767
+ validate: (v) => {
768
+ const n = Number(v);
769
+ if (f.above != null && !(n > f.above)) return `Must be above ${f.above}`;
770
+ if (f.below != null && !(n < f.below)) return `Must be below ${f.below}`;
771
+ return true;
772
+ }
773
+ };
774
+ }
775
+ var toField = (f) => ({
776
+ name: f.name,
777
+ type: FIELD_TYPE[f.type],
778
+ label: f.label,
779
+ unit: f.unit,
780
+ aside: f.aside,
781
+ description: f.hint,
782
+ group: f.group,
783
+ rules: boundsRule(f),
784
+ placeholder: ""
785
+ });
786
+ function withUnit(value, unit) {
787
+ if (value == null || value === "") return "\u2014";
788
+ if (!unit) return String(value);
789
+ return /^[A-Za-z]/.test(unit) ? `${value} ${unit}` : `${value}${unit}`;
790
+ }
791
+ function FormAsk({ spec, state = "pending", value: given, onAction }) {
792
+ const auto = React18__namespace.useId();
793
+ const pending = state === "pending";
794
+ const [editing, setEditing] = React18__namespace.useState(false);
795
+ const current = given ?? {};
796
+ const top = spec.labels === "top";
797
+ const defaults = Object.fromEntries(
798
+ spec.fields.map((f) => [f.name, pending ? f.default : current[f.name]])
799
+ );
800
+ const form = reactHookForm.useForm({ defaultValues: { values: defaults }, mode: "onChange" });
801
+ if (!pending && !editing) {
802
+ return /* @__PURE__ */ jsxRuntime.jsx(
803
+ AskSummary,
804
+ {
805
+ rows: spec.fields.map((f) => ({ label: f.label, value: withUnit(current[f.name], f.unit) })),
806
+ change: state === "answered" ? "Change inputs" : void 0,
807
+ onChange: () => setEditing(true)
808
+ }
809
+ );
810
+ }
811
+ return /* @__PURE__ */ jsxRuntime.jsxs(forms.Form, { ...form, children: [
812
+ /* @__PURE__ */ jsxRuntime.jsx(AskQuestion, { text: spec }),
813
+ /* @__PURE__ */ jsxRuntime.jsx(
814
+ "form",
815
+ {
816
+ id: `${auto}-form`,
817
+ onSubmit: form.handleSubmit(({ values }) => {
818
+ onAction?.(editing ? "change" : "reply", values);
819
+ setEditing(false);
820
+ }),
821
+ children: /* @__PURE__ */ jsxRuntime.jsx(
822
+ forms.ObjectField,
823
+ {
824
+ control: form.control,
825
+ name: "values",
826
+ fields: spec.fields.map(toField),
827
+ labelPosition: top ? "top" : "side",
828
+ size: "xs",
829
+ columns: top ? 2 : 1,
830
+ fit: "container",
831
+ groupAs: "section"
832
+ }
833
+ )
834
+ }
835
+ ),
836
+ /* @__PURE__ */ jsxRuntime.jsx(AskHint, { children: spec.hint }),
837
+ /* @__PURE__ */ jsxRuntime.jsx(ui.ClarifyActions, { children: /* @__PURE__ */ jsxRuntime.jsx(
838
+ ui.Button,
839
+ {
840
+ type: "submit",
841
+ form: `${auto}-form`,
842
+ size: "xs",
843
+ className: top ? "ms-auto" : void 0,
844
+ disabled: !form.formState.isValid,
845
+ children: spec.submit ?? "Submit"
846
+ }
847
+ ) })
848
+ ] });
849
+ }
850
+ var openKeys = (tasks) => Object.fromEntries(
851
+ tasks.flatMap((t) => [...t.open ? [[t.key, true]] : [], ...Object.entries(openKeys(t.subtasks ?? []))])
852
+ );
853
+ var allKeys = (tasks) => tasks.flatMap((t) => [t.key, ...allKeys(t.subtasks ?? [])]);
854
+ var toBar = ({ chip, ...bar }) => ({
855
+ ...bar,
856
+ chip: chip ? /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: "soft", tone: BADGE_TONE[chip.tone ?? "neutral"], children: chip.label }) : void 0
857
+ });
858
+ var STEP_TICK = (n) => `step ${Math.round(n)}`;
859
+ var STEPS = (n) => Math.round(n) === 1 ? "1 step" : `${Math.round(n)} steps`;
860
+ var toRow = ({ open: _open, subtasks, segments, ...task }) => ({
861
+ ...task,
862
+ bars: segments?.map(toBar),
863
+ rows: subtasks?.map(toRow)
864
+ });
865
+ function GanttBlock({ spec, onAction }) {
866
+ const [selected, setSelected] = React18__namespace.useState(spec.selected ?? null);
867
+ const [expanded, setExpanded] = React18__namespace.useState(() => openKeys(spec.tasks));
868
+ const keys = allKeys(spec.tasks);
869
+ const flagged = openKeys(spec.tasks);
870
+ const openedAmong = (among) => among.filter((key) => flagged[key]).sort().join("\n");
871
+ const [seen, setSeen] = React18__namespace.useState(() => new Set(keys));
872
+ const [lastOpened, setLastOpened] = React18__namespace.useState(() => openedAmong(keys));
873
+ const fresh = keys.filter((key) => !seen.has(key));
874
+ const reflagged = openedAmong(keys.filter((key) => seen.has(key))) !== lastOpened;
875
+ if (fresh.length || reflagged) {
876
+ setSeen(new Set(keys));
877
+ setLastOpened(openedAmong(keys));
878
+ if (reflagged) setExpanded(flagged);
879
+ else {
880
+ const arrived = fresh.filter((key) => flagged[key]);
881
+ if (arrived.length)
882
+ setExpanded((e) => e === true ? e : { ...e, ...Object.fromEntries(arrived.map((k) => [k, true])) });
883
+ }
884
+ }
885
+ const rows = React18__namespace.useMemo(() => spec.tasks.map(toRow), [spec.tasks]);
886
+ const pick = (key) => {
887
+ setSelected(key);
888
+ onAction?.("select", key);
889
+ };
890
+ return /* @__PURE__ */ jsxRuntime.jsx(
891
+ ui.Gantt,
892
+ {
893
+ rows,
894
+ spanMs: spec.spanMs,
895
+ nowMs: spec.nowMs,
896
+ openEnded: spec.openEnded,
897
+ ticks: spec.ticks,
898
+ formatTick: spec.scale === "seq" ? STEP_TICK : void 0,
899
+ formatDuration: spec.scale === "seq" ? STEPS : void 0,
900
+ labelWidth: spec.labelWidth,
901
+ durationWidth: spec.durationWidth,
902
+ palette: spec.palette,
903
+ seams: spec.seams,
904
+ density: spec.density,
905
+ expanded,
906
+ onExpandedChange: setExpanded,
907
+ selectedKey: selected,
908
+ onSelectRow: onAction ? pick : void 0,
909
+ onSelectBar: onAction ? pick : void 0
910
+ }
911
+ );
912
+ }
913
+ var columnsFor = (n) => n === 4 ? 2 : Math.min(n, 3);
914
+ function GridBlock({ spec }) {
915
+ return /* @__PURE__ */ jsxRuntime.jsx(
916
+ ui.MetricGrid,
917
+ {
918
+ joined: true,
919
+ seamless: true,
920
+ minTileWidth: spec.minTileWidth,
921
+ columns: spec.minTileWidth ? void 0 : columnsFor(spec.tiles.length),
922
+ children: spec.tiles.map((tile) => /* @__PURE__ */ jsxRuntime.jsx(
923
+ ui.MetricTile,
924
+ {
925
+ variant: "figure",
926
+ label: tile.label,
927
+ value: metricValue(tile.value),
928
+ tone: tile.value == null ? "muted" : void 0,
929
+ caption: tile.delta,
930
+ captionTone: tile.tone ? CAPTION_TONE[tile.tone] : void 0,
931
+ flagged: tile.flag,
932
+ ...gaugeProps(tile.gauge)
933
+ },
934
+ tile.label
935
+ ))
936
+ }
937
+ );
938
+ }
939
+ var COLOR = {
940
+ good: "var(--color-success)",
941
+ bad: "var(--color-destructive)",
942
+ warn: "var(--color-warning)",
943
+ neutral: "var(--color-muted-foreground)"
944
+ };
945
+ var toRow2 = ({ id, label, cells, children }) => ({
946
+ key: id,
947
+ label,
948
+ cells,
949
+ children: children?.map(toRow2)
950
+ });
951
+ var openKeys2 = (rows) => Object.fromEntries(
952
+ rows.flatMap((r) => [...r.open ? [[r.id, true]] : [], ...Object.entries(openKeys2(r.children ?? []))])
953
+ );
954
+ function HeatStripBlock({ spec }) {
955
+ const states = React18__namespace.useMemo(
956
+ () => spec.states.map((s) => ({ key: s.key, label: s.label, hollow: s.hollow, color: COLOR[s.tone ?? "neutral"] })),
957
+ [spec.states]
958
+ );
959
+ const rows = React18__namespace.useMemo(() => spec.rows?.map(toRow2), [spec.rows]);
960
+ const defaultExpanded = React18__namespace.useMemo(() => openKeys2(spec.rows ?? []), [spec.rows]);
961
+ return /* @__PURE__ */ jsxRuntime.jsx(
962
+ charts.HeatStrip,
963
+ {
964
+ cells: spec.cells,
965
+ rows,
966
+ states,
967
+ ticks: spec.ticks,
968
+ defaultExpanded
969
+ }
970
+ );
971
+ }
972
+ function MethodBlock({ spec }) {
973
+ const [open, setOpen] = React18__namespace.useState(spec.open ?? false);
974
+ return /* @__PURE__ */ jsxRuntime.jsx(
975
+ ui.ChatSessionDisclosure,
976
+ {
977
+ variant: "inline",
978
+ label: spec.label ?? "method",
979
+ meta: spec.meta ?? (open ? void 0 : spec.code?.replace(/\*\*/g, "")),
980
+ open,
981
+ onOpenChange: setOpen,
982
+ children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
983
+ spec.code ? /* @__PURE__ */ jsxRuntime.jsx(ui.ChatSessionDisclosureCode, { children: strong(spec.code) }) : null,
984
+ spec.facts?.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.PropertyList, { labelWidth: "auto", variant: "summary", children: spec.facts.map((fact) => /* @__PURE__ */ jsxRuntime.jsx(ui.PropertyRow, { label: fact.label, mono: true, children: fact.value }, fact.label)) }) : null,
985
+ spec.steps?.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.ChatSessionDisclosureSteps, { steps: spec.steps }) : null
986
+ ] })
987
+ }
988
+ );
989
+ }
990
+ function MetricBlock({ spec }) {
991
+ return /* @__PURE__ */ jsxRuntime.jsx(
992
+ ui.MetricTile,
993
+ {
994
+ variant: "hero",
995
+ label: spec.label,
996
+ value: metricValue(spec.value),
997
+ tone: spec.value == null ? "muted" : void 0,
998
+ caption: spec.delta,
999
+ captionTone: spec.tone ? CAPTION_TONE[spec.tone] : void 0,
1000
+ ...gaugeProps(spec.gauge),
1001
+ aside: spec.trend?.length ? /* @__PURE__ */ jsxRuntime.jsx(
1002
+ charts.Sparkline,
1003
+ {
1004
+ values: spec.trend,
1005
+ width: 96,
1006
+ height: 32,
1007
+ strokeWidth: 1.4,
1008
+ endMarker: false,
1009
+ color: "color-mix(in srgb, var(--color-foreground) 70%, transparent)",
1010
+ label: `${spec.label}, recent run`
1011
+ }
1012
+ ) : void 0
1013
+ }
1014
+ );
1015
+ }
1016
+ function submitLabel(question, count, submit) {
1017
+ if (submit) return submit.replace("{count}", String(count));
1018
+ const q = question.trim();
1019
+ return q.endsWith("?") ? `Use ${count} selected` : `${q} ${count}`;
1020
+ }
1021
+ function MultiAsk({ spec, state = "pending", value: given, id: idProp, onAction }) {
1022
+ const auto = React18__namespace.useId();
1023
+ const id = idProp ?? auto;
1024
+ const pending = state === "pending";
1025
+ const [editing, setEditing] = React18__namespace.useState(false);
1026
+ const [picked, setPicked] = React18__namespace.useState(
1027
+ pending ? spec.default ?? [] : given ?? []
1028
+ );
1029
+ if (!pending && !editing) {
1030
+ const value = given ?? spec.default ?? [];
1031
+ const text = joinPicks(spec.options.filter((o) => value.includes(o.value)));
1032
+ return /* @__PURE__ */ jsxRuntime.jsx(
1033
+ AskSummary,
1034
+ {
1035
+ rows: [{ label: spec.label ?? "Answer", value: text || "\u2014" }],
1036
+ change: state === "answered" ? "Change answer" : void 0,
1037
+ onChange: () => setEditing(true)
1038
+ }
1039
+ );
1040
+ }
1041
+ const full = spec.max != null && picked.length >= spec.max;
1042
+ const short = spec.min != null && picked.length < spec.min;
1043
+ const toggle = (v) => setPicked((now) => now.includes(v) ? now.filter((x) => x !== v) : [...now, v]);
1044
+ const all = spec.options.filter((o) => !o.disabled).map((o) => o.value);
1045
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1046
+ /* @__PURE__ */ jsxRuntime.jsx(
1047
+ ui.Questionnaire,
1048
+ {
1049
+ id: `${auto}-form`,
1050
+ onSubmit: (e) => {
1051
+ e.preventDefault();
1052
+ if (short) return;
1053
+ const value = spec.options.map((o) => o.value).filter((v) => picked.includes(v));
1054
+ onAction?.(editing ? "change" : "reply", value);
1055
+ setEditing(false);
1056
+ },
1057
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.QuestionnaireItem, { name: id, multiple: true, children: [
1058
+ /* @__PURE__ */ jsxRuntime.jsxs(
1059
+ ui.QuestionnaireTitle,
1060
+ {
1061
+ className: ui.cn(
1062
+ isHeading(spec) && "font-semibold",
1063
+ spec.selectAll && "flex w-full items-baseline justify-between gap-2"
1064
+ ),
1065
+ children: [
1066
+ strong(spec.question),
1067
+ spec.selectAll ? /* @__PURE__ */ jsxRuntime.jsx(AskLink, { onClick: () => setPicked(spec.max != null ? all.slice(0, spec.max) : all), children: "Select all" }) : null
1068
+ ]
1069
+ }
1070
+ ),
1071
+ spec.description ? /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireDescription, { children: spec.description }) : null,
1072
+ /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireChoices, { children: spec.options.map((o) => {
1073
+ const checked = picked.includes(o.value);
1074
+ const { props, body } = choiceParts(o);
1075
+ return /* @__PURE__ */ jsxRuntime.jsx(
1076
+ ui.QuestionnaireChoice,
1077
+ {
1078
+ value: o.value,
1079
+ ...props,
1080
+ disabled: o.disabled || full && !checked,
1081
+ checked,
1082
+ onChange: () => toggle(o.value),
1083
+ children: body
1084
+ },
1085
+ o.value
1086
+ );
1087
+ }) })
1088
+ ] })
1089
+ }
1090
+ ),
1091
+ /* @__PURE__ */ jsxRuntime.jsx(AskHint, { children: spec.hint }),
1092
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.ClarifyActions, { children: [
1093
+ spec.max != null ? full && spec.max < spec.options.length ? /* @__PURE__ */ jsxRuntime.jsx(AskHint, { tone: "warning", children: "Limit reached \xB7 clear one to swap" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: hintText(`**${picked.length}** of ${spec.max}`) }) : null,
1094
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", form: `${auto}-form`, size: "xs", className: "ms-auto", disabled: short, children: submitLabel(spec.question, picked.length, spec.submit) })
1095
+ ] })
1096
+ ] });
1097
+ }
1098
+ function Placeholder({ kind, options, className }) {
1099
+ const { kind: _kind, ...rest } = options ?? {};
1100
+ const json = JSON.stringify(rest, null, 2);
1101
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1102
+ "div",
1103
+ {
1104
+ "data-placeholder": kind,
1105
+ className: ui.cn("flex min-w-0 flex-col gap-1 border border-dashed border-border p-2", className),
1106
+ children: [
1107
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-muted-foreground", children: [
1108
+ "No renderer yet for the block ",
1109
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-foreground", children: kind })
1110
+ ] }),
1111
+ json !== "{}" ? /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "max-h-40 overflow-auto font-mono text-sm text-muted-foreground", children: json }) : null
1112
+ ]
1113
+ }
1114
+ );
1115
+ }
1116
+ function StepTitle({ step }) {
1117
+ if (!("question" in step)) return null;
1118
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1119
+ /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireTitle, { className: "font-semibold", children: strong(step.question ?? "") }),
1120
+ "description" in step && step.description ? /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireDescription, { children: step.description }) : null
1121
+ ] });
1122
+ }
1123
+ function StepItem({
1124
+ step,
1125
+ value,
1126
+ onStatusChange
1127
+ }) {
1128
+ const item = { name: step.id, required: step.required, onStatusChange };
1129
+ switch (step.kind) {
1130
+ case "single":
1131
+ case "multi": {
1132
+ const multiple = step.kind === "multi";
1133
+ const chosen = (v) => multiple ? Array.isArray(value) && value.includes(v) : value === v;
1134
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.QuestionnaireItem, { ...item, multiple, children: [
1135
+ /* @__PURE__ */ jsxRuntime.jsx(StepTitle, { step }),
1136
+ /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireChoices, { children: step.options.map((o) => {
1137
+ const { props, body } = choiceParts(o);
1138
+ return /* @__PURE__ */ jsxRuntime.jsx(
1139
+ ui.QuestionnaireChoice,
1140
+ {
1141
+ value: o.value,
1142
+ ...props,
1143
+ disabled: o.disabled,
1144
+ defaultChecked: chosen(o.value),
1145
+ children: body
1146
+ },
1147
+ o.value
1148
+ );
1149
+ }) })
1150
+ ] });
1151
+ }
1152
+ case "number":
1153
+ case "short":
1154
+ case "long":
1155
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.QuestionnaireItem, { ...item, children: [
1156
+ /* @__PURE__ */ jsxRuntime.jsx(StepTitle, { step }),
1157
+ /* @__PURE__ */ jsxRuntime.jsx(
1158
+ ui.QuestionnaireInput,
1159
+ {
1160
+ type: step.kind === "number" ? "number" : "text",
1161
+ unit: step.kind === "number" ? step.unit : void 0,
1162
+ min: step.kind === "number" ? step.min : void 0,
1163
+ max: step.kind === "number" ? step.max : void 0,
1164
+ step: step.kind === "number" ? step.step : void 0,
1165
+ defaultValue: value == null ? void 0 : String(value),
1166
+ "aria-label": step.question
1167
+ }
1168
+ ),
1169
+ "hint" in step ? /* @__PURE__ */ jsxRuntime.jsx(AskHint, { children: step.hint }) : null
1170
+ ] });
1171
+ default:
1172
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireItem, { ...item, children: /* @__PURE__ */ jsxRuntime.jsx(Placeholder, { kind: step.kind, options: step }) });
1173
+ }
1174
+ }
1175
+ function numberText(value, unit) {
1176
+ const n = typeof value === "number" ? value : Number(value);
1177
+ const text = Number.isFinite(n) ? n.toLocaleString("en-GB") : String(value);
1178
+ if (!unit) return text;
1179
+ return /^[A-Za-z]/.test(unit) ? `${text} ${unit}` : `${text}${unit}`;
1180
+ }
1181
+ function answerText(step, value) {
1182
+ if (value == null || value === "" || Array.isArray(value) && !value.length) return "\u2014";
1183
+ switch (step.kind) {
1184
+ case "single":
1185
+ case "quick": {
1186
+ const o = step.options.find((x) => x.value === value);
1187
+ return o ? "summary" in o && o.summary || o.label : String(value);
1188
+ }
1189
+ case "multi": {
1190
+ const values = Array.isArray(value) ? value : [value];
1191
+ return joinPicks(values.map((v) => step.options.find((x) => x.value === v) ?? { value: String(v), label: String(v) }));
1192
+ }
1193
+ case "number":
1194
+ return numberText(value, step.unit);
1195
+ default:
1196
+ return typeof value === "object" ? JSON.stringify(value) : String(value);
1197
+ }
1198
+ }
1199
+ var labelOf = (step) => "label" in step && step.label || step.id.charAt(0).toUpperCase() + step.id.slice(1);
1200
+ function collect(steps, form) {
1201
+ const data = new FormData(form);
1202
+ const out = {};
1203
+ for (const step of steps) {
1204
+ const all = data.getAll(step.id).map(String).filter(Boolean);
1205
+ if (step.kind === "multi") out[step.id] = all;
1206
+ else if (!all.length) continue;
1207
+ else if (step.kind === "number") out[step.id] = Number(all[0]);
1208
+ else out[step.id] = all[0];
1209
+ }
1210
+ return out;
1211
+ }
1212
+ function MultistepAsk({ spec, state = "pending", value: given, onAction }) {
1213
+ const pending = state === "pending";
1214
+ const [editing, setEditing] = React18__namespace.useState(false);
1215
+ const steps = spec.steps;
1216
+ const [item, setItem] = React18__namespace.useState(steps[0]?.id ?? "");
1217
+ const [status, setStatus] = React18__namespace.useState(
1218
+ () => Object.fromEntries(
1219
+ steps.filter((s) => "default" in s && s.default != null).map((s) => [s.id, "answered"])
1220
+ )
1221
+ );
1222
+ const [review, setReview] = React18__namespace.useState(null);
1223
+ const formRef = React18__namespace.useRef(null);
1224
+ const current = given ?? {};
1225
+ if (!pending && !editing) {
1226
+ return /* @__PURE__ */ jsxRuntime.jsx(
1227
+ AskSummary,
1228
+ {
1229
+ rows: steps.map((step2) => ({ label: labelOf(step2), value: answerText(step2, current[step2.id]) })),
1230
+ change: state === "answered" ? "Change answers" : void 0,
1231
+ onChange: () => setEditing(true)
1232
+ }
1233
+ );
1234
+ }
1235
+ const send = (value) => {
1236
+ onAction?.(editing ? "change" : "reply", value);
1237
+ setEditing(false);
1238
+ setReview(null);
1239
+ };
1240
+ const initial = (step2) => editing ? current[step2.id] : "default" in step2 ? step2.default : void 0;
1241
+ const index = Math.max(0, steps.findIndex((s) => s.id === item));
1242
+ const step = steps[index];
1243
+ const last = index === steps.length - 1;
1244
+ const blocked = Boolean(step?.required && status[step.id] !== "answered");
1245
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1246
+ /* @__PURE__ */ jsxRuntime.jsxs(
1247
+ ui.Questionnaire,
1248
+ {
1249
+ ref: formRef,
1250
+ item,
1251
+ onItemChange: setItem,
1252
+ hidden: review != null,
1253
+ onSubmit: (e) => {
1254
+ e.preventDefault();
1255
+ send(collect(steps, e.currentTarget));
1256
+ },
1257
+ children: [
1258
+ /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireProgress, {}),
1259
+ steps.map((s) => /* @__PURE__ */ jsxRuntime.jsx(
1260
+ StepItem,
1261
+ {
1262
+ step: s,
1263
+ value: initial(s),
1264
+ onStatusChange: (st) => setStatus((now) => ({ ...now, [s.id]: st }))
1265
+ },
1266
+ s.id
1267
+ )),
1268
+ blocked ? /* @__PURE__ */ jsxRuntime.jsx(AskHint, { tone: "error", children: "Pick one to continue \xB7 this step is required" }) : null,
1269
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.QuestionnaireActions, { children: [
1270
+ /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnairePrevious, {}),
1271
+ !last && !step?.required ? /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireSkip, {}) : null,
1272
+ /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireNext, { disabled: blocked }),
1273
+ last && spec.review ? /* @__PURE__ */ jsxRuntime.jsx(
1274
+ ui.Button,
1275
+ {
1276
+ type: "button",
1277
+ size: "xs",
1278
+ className: "col-start-3 row-start-1 justify-self-end",
1279
+ disabled: blocked,
1280
+ onClick: () => formRef.current && setReview(collect(steps, formRef.current)),
1281
+ children: "Review"
1282
+ }
1283
+ ) : /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireSubmit, { disabled: blocked })
1284
+ ] })
1285
+ ]
1286
+ }
1287
+ ),
1288
+ review ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1289
+ /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireProgressBar, { label: "Review", value: 1 }),
1290
+ /* @__PURE__ */ jsxRuntime.jsx(ui.PropertyList, { labelWidth: "auto", variant: "summary", children: steps.map((s) => /* @__PURE__ */ jsxRuntime.jsx(ui.PropertyRow, { label: labelOf(s), mono: true, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-baseline gap-2", children: [
1291
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1", children: answerText(s, review[s.id]) }),
1292
+ /* @__PURE__ */ jsxRuntime.jsx(
1293
+ AskLink,
1294
+ {
1295
+ onClick: () => {
1296
+ setItem(s.id);
1297
+ setReview(null);
1298
+ },
1299
+ children: "Edit"
1300
+ }
1301
+ )
1302
+ ] }) }, s.id)) }),
1303
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.QuestionnaireActions, { children: [
1304
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "button", size: "xs", variant: "outline", className: "justify-self-start", onClick: () => setReview(null), children: "Previous" }),
1305
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "button", size: "xs", className: "col-start-3 justify-self-end", onClick: () => send(review), children: `Submit ${steps.length} answers` })
1306
+ ] })
1307
+ ] }) : null
1308
+ ] });
1309
+ }
1310
+ function NarrativeBlock({ spec, active = spec.active, onActiveChange, trailing }) {
1311
+ const marker = (n) => /* @__PURE__ */ jsxRuntime.jsx(
1312
+ ui.CitationMarker,
1313
+ {
1314
+ "data-active": active === n || void 0,
1315
+ onMouseEnter: () => onActiveChange?.(n),
1316
+ onMouseLeave: () => onActiveChange?.(void 0),
1317
+ children: n
1318
+ },
1319
+ `cite-${n}`
1320
+ );
1321
+ return /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
1322
+ prose(spec.text, marker),
1323
+ spec.cites?.map(marker),
1324
+ trailing
1325
+ ] });
1326
+ }
1327
+ function ProposalBlock({ spec, onAction }) {
1328
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1329
+ ui.ProposalCard,
1330
+ {
1331
+ flush: true,
1332
+ seamless: true,
1333
+ title: spec.heading,
1334
+ done: spec.done?.label,
1335
+ consequence: spec.consequence,
1336
+ actions: spec.actions.length ? /* @__PURE__ */ jsxRuntime.jsx(
1337
+ ActionRow,
1338
+ {
1339
+ actions: spec.actions,
1340
+ onAction: (id) => onAction?.("action", id)
1341
+ }
1342
+ ) : void 0,
1343
+ children: [
1344
+ spec.rows?.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.PropertyList, { labelWidth: "auto", variant: "summary", children: spec.rows.map((row) => /* @__PURE__ */ jsxRuntime.jsx(ui.PropertyRow, { label: row.label, mono: true, children: row.value }, row.label)) }) : null,
1345
+ spec.figures?.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.MetricGrid, { joined: true, seamless: true, minTileWidth: 72, children: spec.figures.map((f) => /* @__PURE__ */ jsxRuntime.jsx(ui.MetricTile, { variant: "figure", label: f.label, value: f.value }, f.label)) }) : null
1346
+ ]
1347
+ }
1348
+ );
1349
+ }
1350
+ function QuickAsk({ spec, state = "pending", value: given, onAction }) {
1351
+ const pending = state === "pending";
1352
+ const [editing, setEditing] = React18__namespace.useState(false);
1353
+ const keyed = Boolean(spec.more?.length);
1354
+ const rows = [
1355
+ { ...spec, key: spec.label ?? spec.question },
1356
+ ...(spec.more ?? []).map((r) => ({ ...r, key: r.id }))
1357
+ ];
1358
+ const settled = () => {
1359
+ const value = given;
1360
+ if (keyed) return value ?? {};
1361
+ return { [rows[0].key]: value ?? spec.default };
1362
+ };
1363
+ const [picked, setPicked] = React18__namespace.useState(
1364
+ () => pending ? Object.fromEntries(rows.map((r) => [r.key, r.default])) : settled()
1365
+ );
1366
+ if (!pending && !editing) {
1367
+ const value = settled();
1368
+ return /* @__PURE__ */ jsxRuntime.jsx(
1369
+ AskSummary,
1370
+ {
1371
+ rows: rows.map((r) => ({
1372
+ label: r.label ?? r.question,
1373
+ value: r.options.find((o) => o.value === value[r.key])?.label ?? value[r.key] ?? "\u2014"
1374
+ })),
1375
+ change: state === "answered" ? keyed ? "Change answers" : "Change answer" : void 0,
1376
+ onChange: () => setEditing(true)
1377
+ }
1378
+ );
1379
+ }
1380
+ const pick = (key, v) => {
1381
+ const next = { ...picked, [key]: v };
1382
+ setPicked(next);
1383
+ if (rows.some((r) => next[r.key] == null)) return;
1384
+ const value = keyed ? next : v;
1385
+ onAction?.(editing ? "change" : "reply", value);
1386
+ setEditing(false);
1387
+ };
1388
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: rows.map((r, i) => /* @__PURE__ */ jsxRuntime.jsxs(React18__namespace.Fragment, { children: [
1389
+ i === 0 ? /* @__PURE__ */ jsxRuntime.jsx(AskQuestion, { text: spec }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1", children: r.question }),
1390
+ /* @__PURE__ */ jsxRuntime.jsx(
1391
+ ui.SegmentedControl,
1392
+ {
1393
+ size: "sm",
1394
+ "aria-label": r.question,
1395
+ variant: "solid",
1396
+ stretch: spec.stretch,
1397
+ options: r.options,
1398
+ defaultValue: picked[r.key] ?? null,
1399
+ onValueChange: (v) => pick(r.key, v)
1400
+ }
1401
+ ),
1402
+ /* @__PURE__ */ jsxRuntime.jsx(AskHint, { children: r.hint })
1403
+ ] }, r.key)) });
1404
+ }
1405
+ function RankedBlock({ spec }) {
1406
+ return /* @__PURE__ */ jsxRuntime.jsx(
1407
+ charts.BarChartH,
1408
+ {
1409
+ variant: "ranked",
1410
+ color: "var(--color-primary)",
1411
+ diverging: spec.diverging,
1412
+ data: spec.items.map((item) => ({
1413
+ label: item.label,
1414
+ value: item.value,
1415
+ display: item.display ?? figureText(item.value),
1416
+ muted: item.muted
1417
+ }))
1418
+ }
1419
+ );
1420
+ }
1421
+ function Rows({ rows }) {
1422
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.PropertyList, { labelWidth: "auto", variant: "summary", children: rows.map((row) => /* @__PURE__ */ jsxRuntime.jsxs(ui.PropertyRow, { label: row.label, mono: row.mono ?? true, children: [
1423
+ row.value,
1424
+ row.source ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
1425
+ " \xB7 ",
1426
+ row.source
1427
+ ] }) : null
1428
+ ] }, row.label)) });
1429
+ }
1430
+ function RecordBlock({ spec }) {
1431
+ const { header } = spec;
1432
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-1.5", children: [
1433
+ header ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1434
+ header.initials ? /* @__PURE__ */ jsxRuntime.jsx(ui.Avatar, { className: "size-7 border border-border", children: /* @__PURE__ */ jsxRuntime.jsx(ui.AvatarFallback, { className: "text-xs font-semibold text-muted-foreground", children: header.initials }) }) : null,
1435
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate font-semibold", children: header.title }),
1436
+ header.status ? /* @__PURE__ */ jsxRuntime.jsx(
1437
+ ui.Badge,
1438
+ {
1439
+ variant: "soft",
1440
+ size: "xs",
1441
+ tone: BADGE_TONE[header.status.tone ?? "neutral"],
1442
+ className: "ms-auto font-normal",
1443
+ children: header.status.label
1444
+ }
1445
+ ) : null
1446
+ ] }) : null,
1447
+ spec.rows?.length ? /* @__PURE__ */ jsxRuntime.jsx(Rows, { rows: spec.rows }) : null,
1448
+ spec.groups?.map((group, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: i > 0 || spec.rows?.length ? "mt-1 flex flex-col gap-1" : "flex flex-col gap-1", children: [
1449
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Eyebrow, { children: group.label }),
1450
+ /* @__PURE__ */ jsxRuntime.jsx(Rows, { rows: group.rows })
1451
+ ] }, group.label))
1452
+ ] });
1453
+ }
1454
+ function ScopeBlock({ spec, fixed, onAction }) {
1455
+ const stale = spec.parts.some((p) => typeof p === "object" && p.mark === "stale");
1456
+ return /* @__PURE__ */ jsxRuntime.jsx(
1457
+ ui.ScopeLine,
1458
+ {
1459
+ parts: spec.parts,
1460
+ fixedParts: fixed,
1461
+ defaultOpenPart: spec.openPart,
1462
+ note: spec.hint,
1463
+ noteTone: stale ? "warning" : "muted",
1464
+ seamless: true,
1465
+ onPartChange: (part, value) => onAction?.("scope", { part, value })
1466
+ }
1467
+ );
1468
+ }
1469
+ var OTHER = "\0other";
1470
+ function SingleAsk({ spec, state = "pending", value: given, id: idProp, onAction }) {
1471
+ const auto = React18__namespace.useId();
1472
+ const id = idProp ?? auto;
1473
+ const pending = state === "pending";
1474
+ const [editing, setEditing] = React18__namespace.useState(false);
1475
+ const [picked, setPicked] = React18__namespace.useState(spec.default);
1476
+ const settledValue = given ?? spec.default;
1477
+ if (!pending && !editing) {
1478
+ const option = spec.options.find((o) => o.value === settledValue);
1479
+ return /* @__PURE__ */ jsxRuntime.jsx(
1480
+ AskSummary,
1481
+ {
1482
+ rows: [{ label: spec.label ?? "Answer", value: pickText(option, settledValue) }],
1483
+ change: state === "answered" ? "Change answer" : void 0,
1484
+ onChange: () => setEditing(true)
1485
+ }
1486
+ );
1487
+ }
1488
+ const send = (value) => {
1489
+ onAction?.(editing ? "change" : "reply", value);
1490
+ setEditing(false);
1491
+ };
1492
+ const pick = (value) => {
1493
+ setPicked(value);
1494
+ if (value !== OTHER && !spec.submit) send(value);
1495
+ };
1496
+ const other = picked === OTHER;
1497
+ const buttons = spec.submit || spec.skippable || other;
1498
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1499
+ /* @__PURE__ */ jsxRuntime.jsx(
1500
+ ui.Questionnaire,
1501
+ {
1502
+ onSubmit: (e) => {
1503
+ e.preventDefault();
1504
+ if (other) {
1505
+ const text = new FormData(e.currentTarget).getAll(id).map(String).find((v) => v !== OTHER);
1506
+ if (text?.trim()) send(text.trim());
1507
+ } else if (picked) send(picked);
1508
+ },
1509
+ id: `${auto}-form`,
1510
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.QuestionnaireItem, { name: id, children: [
1511
+ /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireTitle, { className: isHeading(spec) ? "font-semibold" : void 0, children: strong(spec.question) }),
1512
+ spec.description ? /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireDescription, { children: spec.description }) : null,
1513
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.QuestionnaireChoices, { children: [
1514
+ spec.options.map((o) => {
1515
+ const { props, body } = choiceParts(o);
1516
+ return /* @__PURE__ */ jsxRuntime.jsx(
1517
+ ui.QuestionnaireChoice,
1518
+ {
1519
+ value: o.value,
1520
+ ...props,
1521
+ disabled: o.disabled,
1522
+ checked: picked === o.value,
1523
+ onChange: () => pick(o.value),
1524
+ children: body
1525
+ },
1526
+ o.value
1527
+ );
1528
+ }),
1529
+ spec.other ? /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireChoice, { value: OTHER, checked: other, onChange: () => pick(OTHER), children: /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireChoiceTitle, { children: "Other\u2026" }) }) : null
1530
+ ] }),
1531
+ other ? /* @__PURE__ */ jsxRuntime.jsx(ui.QuestionnaireInput, { "aria-label": "Your answer", autoFocus: true }) : null
1532
+ ] })
1533
+ }
1534
+ ),
1535
+ /* @__PURE__ */ jsxRuntime.jsx(AskHint, { children: spec.hint }),
1536
+ buttons ? /* @__PURE__ */ jsxRuntime.jsxs(ui.ClarifyActions, { align: "end", children: [
1537
+ spec.skippable && !other ? /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "xs", variant: "ghost", onClick: () => onAction?.("skip"), children: "Skip" }) : null,
1538
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", form: `${auto}-form`, size: "xs", disabled: !picked, children: other ? "Use this" : spec.submit ?? "Use this" })
1539
+ ] }) : null
1540
+ ] });
1541
+ }
1542
+ function SuggestionsAsk({ spec, state = "pending", value: given, onAction }) {
1543
+ const open = state === "pending" || state === "answered";
1544
+ const picked = typeof given === "string" ? [given] : [];
1545
+ const all = spec.groups?.length ? spec.groups.flatMap((g) => g.items) : spec.items ?? [];
1546
+ return /* @__PURE__ */ jsxRuntime.jsx(
1547
+ SuggestionChips,
1548
+ {
1549
+ items: spec.items,
1550
+ groups: spec.groups,
1551
+ layout: spec.layout,
1552
+ sent: open ? picked : all,
1553
+ onSelect: (text) => onAction?.("reply", text)
1554
+ }
1555
+ );
1556
+ }
1557
+ var FITS = 5;
1558
+ var COLUMN_MIN = 66;
1559
+ function cellText(cell) {
1560
+ if (cell == null) return void 0;
1561
+ if (typeof cell !== "object" || !("value" in cell) || "type" in cell) return figureText(cell);
1562
+ return figureText(cell.value);
1563
+ }
1564
+ function CellText({ cell }) {
1565
+ if (cell == null) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: "\u2014" });
1566
+ if (typeof cell !== "object" || !("value" in cell) || "type" in cell) {
1567
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: figureText(cell) });
1568
+ }
1569
+ return /* @__PURE__ */ jsxRuntime.jsx(
1570
+ "span",
1571
+ {
1572
+ className: ui.cn(
1573
+ cell.tone === "good" && "text-success",
1574
+ cell.tone === "bad" && "text-destructive",
1575
+ cell.strong && "font-semibold"
1576
+ ),
1577
+ children: figureText(cell.value)
1578
+ }
1579
+ );
1580
+ }
1581
+ function TableBlock({ spec, onAction }) {
1582
+ const truncated = spec.total != null && spec.total > spec.rows.length;
1583
+ const highlighted = new Set(spec.highlight?.map((i) => spec.rows[i]));
1584
+ const data = spec.totals ? [...spec.rows, spec.totals] : spec.rows;
1585
+ const columns = spec.columns.map((c) => {
1586
+ const sorted = spec.sort?.key === c.key ? spec.sort.dir : void 0;
1587
+ return {
1588
+ id: c.key,
1589
+ header: sorted ? () => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground", children: `${c.label} ${sorted === "asc" ? "\u25B4" : "\u25BE"}` }) : c.label,
1590
+ accessorFn: (row) => row[c.key],
1591
+ cell: (ctx) => /* @__PURE__ */ jsxRuntime.jsx(CellText, { cell: ctx.getValue() }),
1592
+ meta: { align: c.align, cellClassName: ui.cn("whitespace-nowrap", c.mono && "font-mono") }
1593
+ };
1594
+ });
1595
+ const { rowKey } = spec;
1596
+ const keyOf = (row) => rowKey != null && row !== spec.totals ? cellText(row[rowKey]) : void 0;
1597
+ const noun = [spec.noun, spec.note].filter(Boolean).join(" \xB7 ") || void 0;
1598
+ return /* @__PURE__ */ jsxRuntime.jsx(
1599
+ tables.DataTable,
1600
+ {
1601
+ columns,
1602
+ data,
1603
+ density: "compact",
1604
+ seamless: true,
1605
+ enableSorting: false,
1606
+ enableColumnVisibility: false,
1607
+ minWidth: spec.columns.length > FITS ? spec.columns.length * COLUMN_MIN : void 0,
1608
+ isRowHighlighted: highlighted.size ? (row) => highlighted.has(row) : void 0,
1609
+ isTotalRow: spec.totals ? (row) => row === spec.totals : void 0,
1610
+ isRowSelected: spec.selected != null ? (row) => keyOf(row) === spec.selected : void 0,
1611
+ onRowClick: rowKey != null && onAction ? (row) => {
1612
+ const key = keyOf(row);
1613
+ if (key != null) onAction("select", key);
1614
+ } : void 0,
1615
+ preview: {
1616
+ total: spec.total,
1617
+ noun,
1618
+ onOpen: truncated && onAction ? () => onAction("open") : void 0
1619
+ }
1620
+ }
1621
+ );
1622
+ }
1623
+ var DOT_TONE = {
1624
+ good: "success",
1625
+ bad: "error",
1626
+ warn: "warning",
1627
+ neutral: "muted"
1628
+ };
1629
+ function Events({ events }) {
1630
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: events.map((event, i) => /* @__PURE__ */ jsxRuntime.jsxs(React18__namespace.Fragment, { children: [
1631
+ event.section && event.section !== events[i - 1]?.section ? /* @__PURE__ */ jsxRuntime.jsx(ui.TimelineSection, { children: event.section }) : null,
1632
+ /* @__PURE__ */ jsxRuntime.jsxs(
1633
+ ui.TimelineEntry,
1634
+ {
1635
+ when: event.when,
1636
+ marker: /* @__PURE__ */ jsxRuntime.jsx(ui.StatusDot, { tone: DOT_TONE[event.tone ?? "neutral"], size: "md" }),
1637
+ highlight: event.highlight ? "error" : void 0,
1638
+ nested: event.children?.length ? /* @__PURE__ */ jsxRuntime.jsx(Events, { events: event.children }) : void 0,
1639
+ defaultOpen: event.open,
1640
+ children: [
1641
+ event.text,
1642
+ event.detail ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: event.detail }) : null
1643
+ ]
1644
+ }
1645
+ )
1646
+ ] }, i)) });
1647
+ }
1648
+ function TimelineBlock({ spec }) {
1649
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.TimelineList, { variant: "compact", children: /* @__PURE__ */ jsxRuntime.jsx(Events, { events: spec.events }) });
1650
+ }
1651
+ var RING_COLOR = {
1652
+ good: "var(--color-success)",
1653
+ bad: "var(--color-destructive)",
1654
+ warn: "var(--color-warning)",
1655
+ neutral: "var(--color-muted-foreground)"
1656
+ };
1657
+ function TimeseriesBlock({ spec }) {
1658
+ const series = spec.series[0];
1659
+ if (!series) return null;
1660
+ const labels = series.points.map(([at2]) => at2);
1661
+ const values = series.points.map(([, value]) => value);
1662
+ const at = (label) => label == null ? -1 : labels.indexOf(label);
1663
+ const forecastFrom = at(spec.forecastFrom);
1664
+ const breaches = spec.reference ? values.flatMap((value, index) => value != null && value > spec.reference.value ? [{ index, color: RING_COLOR.bad }] : []) : [];
1665
+ const format = (value) => {
1666
+ const n = figureText(Math.round(value * 10) / 10);
1667
+ return spec.unit ? `${n} ${spec.unit}` : n;
1668
+ };
1669
+ return /* @__PURE__ */ jsxRuntime.jsx(
1670
+ charts.LineChart,
1671
+ {
1672
+ "aria-label": series.name,
1673
+ values,
1674
+ name: series.name,
1675
+ compare: spec.series.slice(1).map((other) => ({
1676
+ name: other.name,
1677
+ // Aligned on the first series' periods: a period it lacks is a gap.
1678
+ values: labels.map((label) => other.points.find(([at2]) => at2 === label)?.[1] ?? null)
1679
+ })),
1680
+ labels,
1681
+ format,
1682
+ zero: false,
1683
+ color: "var(--color-foreground)",
1684
+ band: spec.band && { ...spec.band, color: "var(--color-primary)" },
1685
+ reference: spec.reference,
1686
+ highlights: [...(spec.marks ?? []).map((mark) => ({ index: at(mark.at), color: RING_COLOR[mark.tone ?? "neutral"] })), ...breaches].filter((mark) => mark.index >= 0),
1687
+ forecastFrom: forecastFrom >= 0 ? forecastFrom : void 0,
1688
+ forecastLabel: spec.forecastLabel
1689
+ }
1690
+ );
1691
+ }
1692
+ function TraceBlock({ spec, onAction }) {
1693
+ const [open, setOpen] = React18__namespace.useState(!spec.folded);
1694
+ const list = /* @__PURE__ */ jsxRuntime.jsx(ui.TraceList, { variant: "progress", children: spec.steps.map((step, i) => /* @__PURE__ */ jsxRuntime.jsx(
1695
+ ui.TraceStep,
1696
+ {
1697
+ name: step.label,
1698
+ duration: step.detail,
1699
+ status: step.state,
1700
+ error: step.error
1701
+ },
1702
+ step.id ?? i
1703
+ )) });
1704
+ const actions = spec.actions?.length ? /* @__PURE__ */ jsxRuntime.jsx(ActionRow, { actions: spec.actions, onAction: (id) => onAction?.("action", id) }) : null;
1705
+ if (!spec.folded) {
1706
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1707
+ list,
1708
+ actions
1709
+ ] });
1710
+ }
1711
+ return /* @__PURE__ */ jsxRuntime.jsx(
1712
+ ui.ChatSessionDisclosure,
1713
+ {
1714
+ variant: "inline",
1715
+ label: `${spec.steps.length} steps`,
1716
+ meta: spec.summary,
1717
+ open,
1718
+ onOpenChange: setOpen,
1719
+ children: list
1720
+ }
1721
+ );
1722
+ }
1723
+
1724
+ // src/registry.ts
1725
+ var BLOCK_RENDERERS = {
1726
+ confirm: ConfirmAsk,
1727
+ single: SingleAsk,
1728
+ multi: MultiAsk,
1729
+ quick: QuickAsk,
1730
+ period: null,
1731
+ number: null,
1732
+ short: null,
1733
+ long: null,
1734
+ entity: null,
1735
+ scale: null,
1736
+ multistep: MultistepAsk,
1737
+ form: FormAsk,
1738
+ weights: null,
1739
+ approval: null,
1740
+ interpretation: null,
1741
+ fork: null,
1742
+ plan: null,
1743
+ modelspec: null,
1744
+ hypothesis: null,
1745
+ range: null,
1746
+ suggestions: SuggestionsAsk,
1747
+ narrative: NarrativeBlock,
1748
+ metric: MetricBlock,
1749
+ grid: GridBlock,
1750
+ table: TableBlock,
1751
+ attr: null,
1752
+ record: RecordBlock,
1753
+ ranked: RankedBlock,
1754
+ timeseries: TimeseriesBlock,
1755
+ bars: BarsBlock,
1756
+ waterfall: null,
1757
+ matrix: null,
1758
+ funnel: null,
1759
+ histogram: null,
1760
+ timeline: TimelineBlock,
1761
+ subgraph: null,
1762
+ method: MethodBlock,
1763
+ citations: CitationsBlock,
1764
+ files: FilesBlock,
1765
+ proposal: ProposalBlock,
1766
+ cannot: CannotBlock,
1767
+ caveat: CaveatBlock,
1768
+ scope: ScopeBlock,
1769
+ checks: null,
1770
+ trace: TraceBlock,
1771
+ activity: ActivityBlock,
1772
+ gantt: GanttBlock,
1773
+ heatstrip: HeatStripBlock,
1774
+ test: null,
1775
+ coef: null,
1776
+ forest: null,
1777
+ scatter: null,
1778
+ box: null,
1779
+ correlation: null,
1780
+ control: null,
1781
+ pareto: null,
1782
+ survival: null,
1783
+ tornado: null,
1784
+ decomposition: null,
1785
+ modeleval: null,
1786
+ pivot: null,
1787
+ profile: null,
1788
+ evidence: null,
1789
+ dumbbell: null,
1790
+ quantiles: null
1791
+ };
1792
+
1793
+ // src/stream/patch.ts
1794
+ var BlockPatchError = class extends Error {
1795
+ };
1796
+ var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1797
+ function merge(target, fields) {
1798
+ const next = { ...target, ...fields };
1799
+ for (const [name, value] of Object.entries(fields)) if (value === null) delete next[name];
1800
+ return next;
1801
+ }
1802
+ var identity = (item) => isObj(item) ? item.key ?? item.id : void 0;
1803
+ var where = (path, depth) => JSON.stringify(path.slice(0, depth));
1804
+ function indexOn(list, segment, path, depth) {
1805
+ const at = typeof segment === "number" ? segment : list.findIndex((item) => identity(item) === segment);
1806
+ if (at < 0 || at >= list.length) throw new BlockPatchError(`No item ${JSON.stringify(segment)} at ${where(path, depth)}.`);
1807
+ return at;
1808
+ }
1809
+ function update(node, path, depth, fn) {
1810
+ if (depth === path.length) return fn(node);
1811
+ const segment = path[depth];
1812
+ if (Array.isArray(node)) {
1813
+ const at = indexOn(node, segment, path, depth);
1814
+ const next = node.slice();
1815
+ next[at] = update(node[at], path, depth + 1, fn);
1816
+ return next;
1817
+ }
1818
+ if (isObj(node) && typeof segment === "string") {
1819
+ return { ...node, [segment]: update(node[segment], path, depth + 1, fn) };
1820
+ }
1821
+ throw new BlockPatchError(`Nothing to read ${JSON.stringify(segment)} from at ${where(path, depth)}.`);
1822
+ }
1823
+ function patchValue(value, patch) {
1824
+ const at = JSON.stringify(patch.at ?? []);
1825
+ switch (patch.op) {
1826
+ case "set":
1827
+ if (!isObj(value)) throw new BlockPatchError(`"set": ${at} is not an object.`);
1828
+ return merge(value, patch.fields);
1829
+ case "append":
1830
+ if (value !== void 0 && typeof value !== "string") throw new BlockPatchError(`"append": ${at} is not text.`);
1831
+ return (value ?? "") + patch.text;
1832
+ case "push":
1833
+ if (value !== void 0 && !Array.isArray(value)) throw new BlockPatchError(`"push": ${at} is not a list.`);
1834
+ return [...value ?? [], ...patch.items];
1835
+ case "upsert": {
1836
+ if (value !== void 0 && !Array.isArray(value)) throw new BlockPatchError(`"upsert": ${at} is not a list.`);
1837
+ const id = identity(patch.item);
1838
+ if (id === void 0) throw new BlockPatchError(`"upsert": the item at ${at} has no key or id.`);
1839
+ const list = value ?? [];
1840
+ const found = list.findIndex((item) => identity(item) === id);
1841
+ if (found < 0) return [...list, merge({}, patch.item)];
1842
+ return list.map((item, i) => i === found ? merge(item, patch.item) : item);
1843
+ }
1844
+ }
1845
+ }
1846
+ function applyBlockPatch(spec, patch) {
1847
+ const next = update(spec, patch.at ?? [], 0, (value) => patchValue(value, patch));
1848
+ return "kind" in spec ? { ...next, kind: spec.kind } : next;
1849
+ }
1850
+ function applyBlockPatches(spec, patches) {
1851
+ return patches.reduce(applyBlockPatch, spec);
1852
+ }
1853
+
1854
+ // src/stream/source.ts
1855
+ var toArray = (chunk) => Array.isArray(chunk) ? chunk : [chunk];
1856
+ async function* patchesOf(source) {
1857
+ for await (const chunk of source) yield toArray(chunk);
1858
+ }
1859
+ async function* fromNdjson(input) {
1860
+ const body = input instanceof Response ? input.body : input;
1861
+ if (!body) return;
1862
+ const reader = body.getReader();
1863
+ const decoder = new TextDecoder();
1864
+ let buffered = "";
1865
+ try {
1866
+ for (; ; ) {
1867
+ const { value, done } = await reader.read();
1868
+ buffered += decoder.decode(value, { stream: !done });
1869
+ let newline = buffered.indexOf("\n");
1870
+ while (newline >= 0) {
1871
+ const line = buffered.slice(0, newline).trim();
1872
+ buffered = buffered.slice(newline + 1);
1873
+ if (line) yield JSON.parse(line);
1874
+ newline = buffered.indexOf("\n");
1875
+ }
1876
+ if (done) break;
1877
+ }
1878
+ const rest = buffered.trim();
1879
+ if (rest) yield JSON.parse(rest);
1880
+ } finally {
1881
+ reader.releaseLock();
1882
+ }
1883
+ }
1884
+ async function* fromEventSource(source, { event = "message", done = "done", close = true } = {}) {
1885
+ const queue = [];
1886
+ let finished = false;
1887
+ let failure;
1888
+ let wake;
1889
+ const notify = () => {
1890
+ wake?.();
1891
+ wake = void 0;
1892
+ };
1893
+ const onData = (e) => {
1894
+ try {
1895
+ queue.push(JSON.parse(e.data));
1896
+ } catch (error) {
1897
+ failure = error;
1898
+ }
1899
+ notify();
1900
+ };
1901
+ const onDone = () => {
1902
+ finished = true;
1903
+ notify();
1904
+ };
1905
+ const onError = () => {
1906
+ if (source.readyState === EventSource.CLOSED) onDone();
1907
+ };
1908
+ source.addEventListener(event, onData);
1909
+ source.addEventListener(done, onDone);
1910
+ source.addEventListener("error", onError);
1911
+ try {
1912
+ for (; ; ) {
1913
+ if (failure) throw failure;
1914
+ const next = queue.shift();
1915
+ if (next) {
1916
+ yield next;
1917
+ continue;
1918
+ }
1919
+ if (finished) return;
1920
+ await new Promise((resolve) => wake = resolve);
1921
+ }
1922
+ } finally {
1923
+ source.removeEventListener(event, onData);
1924
+ source.removeEventListener(done, onDone);
1925
+ source.removeEventListener("error", onError);
1926
+ if (close) source.close();
1927
+ }
1928
+ }
1929
+ function sleep(ms, signal) {
1930
+ return new Promise((resolve) => {
1931
+ if (signal?.aborted || ms <= 0) return resolve();
1932
+ const timer = setTimeout(done, ms);
1933
+ function done() {
1934
+ clearTimeout(timer);
1935
+ signal?.removeEventListener("abort", done);
1936
+ resolve();
1937
+ }
1938
+ signal?.addEventListener("abort", done, { once: true });
1939
+ });
1940
+ }
1941
+ function scriptFrames(script) {
1942
+ const frames = [];
1943
+ for (const step of [...script].sort((a, b) => a.at - b.at)) {
1944
+ const last = frames[frames.length - 1];
1945
+ if (last?.at === step.at) last.patches.push(...toArray(step.patch));
1946
+ else frames.push({ at: step.at, patches: toArray(step.patch) });
1947
+ }
1948
+ return frames;
1949
+ }
1950
+ async function* playScript(script, { speed = 1, signal } = {}) {
1951
+ let clock = 0;
1952
+ for (const frame of scriptFrames(script)) {
1953
+ if (signal?.aborted) return;
1954
+ await sleep((frame.at - clock) / speed, signal);
1955
+ if (signal?.aborted) return;
1956
+ clock = frame.at;
1957
+ yield frame.patches;
1958
+ }
1959
+ }
1960
+ var offsetScript = (script, ms) => script.map((s) => ({ ...s, at: s.at + ms }));
1961
+ var scriptLength = (script) => script.reduce((m, s) => Math.max(m, s.at), 0);
1962
+ function useStreamedSpec(spec, stream, apply, { onEnd, onError, stopPatches } = {}) {
1963
+ const [base, setBase] = React18__namespace.useState(spec);
1964
+ const [live, setLive] = React18__namespace.useState(spec);
1965
+ if (stream && base !== spec) {
1966
+ setBase(spec);
1967
+ setLive(spec);
1968
+ }
1969
+ const current = React18__namespace.useRef(spec);
1970
+ const abort = React18__namespace.useRef(null);
1971
+ const latest = React18__namespace.useRef({ apply, onEnd, onError, stopPatches });
1972
+ React18__namespace.useLayoutEffect(() => {
1973
+ latest.current = { apply, onEnd, onError, stopPatches };
1974
+ current.current = live;
1975
+ });
1976
+ const set = React18__namespace.useCallback((next) => {
1977
+ current.current = next;
1978
+ setLive(next);
1979
+ }, []);
1980
+ React18__namespace.useEffect(() => {
1981
+ if (!stream) return;
1982
+ const controller = new AbortController();
1983
+ abort.current = controller;
1984
+ void (async () => {
1985
+ try {
1986
+ const source = typeof stream === "function" ? stream(controller.signal) : stream;
1987
+ for await (const batch of patchesOf(source)) {
1988
+ if (controller.signal.aborted) return;
1989
+ set(latest.current.apply(current.current, batch));
1990
+ }
1991
+ if (!controller.signal.aborted) latest.current.onEnd?.(current.current);
1992
+ } catch (error) {
1993
+ if (!controller.signal.aborted) latest.current.onError?.(error);
1994
+ } finally {
1995
+ if (abort.current === controller) abort.current = null;
1996
+ }
1997
+ })();
1998
+ return () => controller.abort();
1999
+ }, [stream, set]);
2000
+ const stop = React18__namespace.useCallback(() => {
2001
+ if (!abort.current) return;
2002
+ abort.current.abort();
2003
+ abort.current = null;
2004
+ const { apply: apply2, stopPatches: stopPatches2 } = latest.current;
2005
+ if (stopPatches2) set(apply2(current.current, stopPatches2(current.current)));
2006
+ }, [set]);
2007
+ return { live: stream ? live : spec, stop };
2008
+ }
2009
+ function useBlockStream(spec, stream, options) {
2010
+ return useStreamedSpec(spec, stream, applyBlockPatches, options);
2011
+ }
2012
+ function Block({ spec, stream, onStreamEnd, onStreamError, ...props }) {
2013
+ const { live } = useBlockStream(spec, stream, { onEnd: onStreamEnd, onError: onStreamError });
2014
+ const Renderer = BLOCK_RENDERERS[live.kind];
2015
+ if (!Renderer) return /* @__PURE__ */ jsxRuntime.jsx(Placeholder, { kind: live.kind, options: live });
2016
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { spec: live, ...props });
2017
+ }
2018
+ var Page = React18__namespace.forwardRef(({ spec, onAction, className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs("article", { ref, className: ui.cn("flex min-w-0 flex-col gap-6", className), ...props, children: [
2019
+ spec.title || spec.description ? /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex flex-col gap-1", children: [
2020
+ spec.title ? /* @__PURE__ */ jsxRuntime.jsx(ui.TypographyH3, { children: spec.title }) : null,
2021
+ spec.description ? /* @__PURE__ */ jsxRuntime.jsx(ui.TypographyMuted, { children: spec.description }) : null
2022
+ ] }) : null,
2023
+ spec.sections.map((section, s) => /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "flex min-w-0 flex-col gap-3", children: [
2024
+ section.title ? /* @__PURE__ */ jsxRuntime.jsx(ui.SectionHeader, { title: section.title }) : null,
2025
+ section.description ? /* @__PURE__ */ jsxRuntime.jsx(ui.TypographyMuted, { children: section.description }) : null,
2026
+ section.blocks.map((block, b) => /* @__PURE__ */ jsxRuntime.jsx(
2027
+ Block,
2028
+ {
2029
+ spec: block,
2030
+ onAction: onAction ? (action, value) => onAction(action, value === void 0 ? { section: s, block: b } : { section: s, block: b, value }) : void 0
2031
+ },
2032
+ b
2033
+ ))
2034
+ ] }, s))
2035
+ ] }));
2036
+ Page.displayName = "Page";
2037
+
2038
+ exports.ANSWER_KINDS = ANSWER_KINDS;
2039
+ exports.ASK_KINDS = ASK_KINDS;
2040
+ exports.ActionRow = ActionRow;
2041
+ exports.ActivityBlock = ActivityBlock;
2042
+ exports.BADGE_TONE = BADGE_TONE;
2043
+ exports.BLOCKS = BLOCKS;
2044
+ exports.BLOCK_RENDERERS = BLOCK_RENDERERS;
2045
+ exports.BarsBlock = BarsBlock;
2046
+ exports.Block = Block;
2047
+ exports.BlockPatchError = BlockPatchError;
2048
+ exports.CAPTION_TONE = CAPTION_TONE;
2049
+ exports.CannotBlock = CannotBlock;
2050
+ exports.CaveatBlock = CaveatBlock;
2051
+ exports.CitationsBlock = CitationsBlock;
2052
+ exports.ConfirmAsk = ConfirmAsk;
2053
+ exports.ConfirmCard = ConfirmCard;
2054
+ exports.FilesBlock = FilesBlock;
2055
+ exports.FormAsk = FormAsk;
2056
+ exports.GanttBlock = GanttBlock;
2057
+ exports.GridBlock = GridBlock;
2058
+ exports.HeatStripBlock = HeatStripBlock;
2059
+ exports.MethodBlock = MethodBlock;
2060
+ exports.MetricBlock = MetricBlock;
2061
+ exports.MultiAsk = MultiAsk;
2062
+ exports.MultistepAsk = MultistepAsk;
2063
+ exports.NarrativeBlock = NarrativeBlock;
2064
+ exports.Page = Page;
2065
+ exports.Placeholder = Placeholder;
2066
+ exports.ProposalBlock = ProposalBlock;
2067
+ exports.QuickAsk = QuickAsk;
2068
+ exports.RankedBlock = RankedBlock;
2069
+ exports.RecordBlock = RecordBlock;
2070
+ exports.ScopeBlock = ScopeBlock;
2071
+ exports.SingleAsk = SingleAsk;
2072
+ exports.SuggestionChips = SuggestionChips;
2073
+ exports.SuggestionsAsk = SuggestionsAsk;
2074
+ exports.TableBlock = TableBlock;
2075
+ exports.TimelineBlock = TimelineBlock;
2076
+ exports.TimeseriesBlock = TimeseriesBlock;
2077
+ exports.TraceBlock = TraceBlock;
2078
+ exports.VALUE_TYPES = VALUE_TYPES;
2079
+ exports.applyBlockPatch = applyBlockPatch;
2080
+ exports.applyBlockPatches = applyBlockPatches;
2081
+ exports.figureText = figureText;
2082
+ exports.fromEventSource = fromEventSource;
2083
+ exports.fromNdjson = fromNdjson;
2084
+ exports.metricValue = metricValue;
2085
+ exports.offsetScript = offsetScript;
2086
+ exports.patchesOf = patchesOf;
2087
+ exports.playScript = playScript;
2088
+ exports.prose = prose;
2089
+ exports.scriptFrames = scriptFrames;
2090
+ exports.scriptLength = scriptLength;
2091
+ exports.strong = strong;
2092
+ exports.useBlockStream = useBlockStream;
2093
+ exports.useStreamedSpec = useStreamedSpec;
2094
+ //# sourceMappingURL=index.cjs.map
2095
+ //# sourceMappingURL=index.cjs.map