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