@invana/boards 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.
@@ -0,0 +1,1013 @@
1
+ import * as React from 'react';
2
+ import { BlockPatch, AskState, BlockKind, BlockOptionsByKind, SpecStream } from '@invana/blocks';
3
+ import { StatusDotProps, Bound, DiffOp, Layer, Narrowing, LensUsage, LayerPalette, MarkChip, TouchItem } from '@invana/ui';
4
+
5
+ /**
6
+ * How a board changes while it streams. A panel's options stream by the
7
+ * block's own {@link BlockPatch} — the patch a conversation streams the same
8
+ * block by — so a gantt panel and a gantt answer take one stream.
9
+ */
10
+ type BoardPatch =
11
+ /** Stream a panel's options — its block's spec — by a block patch. `panel` is its `id`. */
12
+ {
13
+ op: "patch-panel";
14
+ panel: string;
15
+ patch: BlockPatch;
16
+ }
17
+ /**
18
+ * The panel's own fields — its `aside` as the run moves, its `state` once
19
+ * answered; `null` removes one. Never its `id`, `kind` or `options`.
20
+ */
21
+ | {
22
+ op: "update-panel";
23
+ panel: string;
24
+ fields: {
25
+ [F in keyof Omit<PanelBase, "id" | "render">]?: PanelBase[F] | null;
26
+ };
27
+ };
28
+ type BoardPatchOp = BoardPatch["op"];
29
+ declare class BoardPatchError extends Error {
30
+ }
31
+ /** A board's spec after one patch: the panel it names, wherever it sits — a row, or a tab's row. */
32
+ declare function applyBoardPatch<X extends ExtraPanels>(spec: BoardSpec<X>, patch: BoardPatch): BoardSpec<X>;
33
+ /** Apply patches in order — a recorded stream, replayed. */
34
+ declare function applyBoardPatches<X extends ExtraPanels>(spec: BoardSpec<X>, patches: BoardPatch[]): BoardSpec<X>;
35
+
36
+ /**
37
+ * A board is **data**. Everything in this file is JSON-serialisable, with
38
+ * one deliberate exception noted on {@link PanelSpec.render}.
39
+ *
40
+ * That constraint is the whole design. A spec can come from an API, be stored
41
+ * as `board.yml` beside a plan, be diffed between two runs, and be rendered
42
+ * by a component that has never heard of the record it is describing. So
43
+ * nothing here is a function, a React node, or a class instance — behaviour
44
+ * arrives through {@link BoardProps.onAction} and the panel registry, keyed
45
+ * by strings the JSON carries.
46
+ */
47
+ type Tone = "running" | "success" | "warning" | "error" | "info" | "muted";
48
+ /** A chip in a header or beside a row. */
49
+ interface ChipSpec {
50
+ label: string;
51
+ /** Renders a `BoundChip` instead of a `Badge`. Set `label` or this, not both. */
52
+ bound?: Bound;
53
+ /**
54
+ * The bound's swatch class — `bg-data-7`. `BoundChip` ships no hues, so a
55
+ * spec that names a bound and no swatch draws in the neutral.
56
+ */
57
+ swatch?: string;
58
+ tone?: Tone;
59
+ variant?: "default" | "outline" | "secondary" | "destructive";
60
+ }
61
+ /**
62
+ * Something a person can do. The spec carries only what it is called and what
63
+ * it means; **what it does arrives as `onAction(id)`**, because a function is
64
+ * not JSON and a board that shipped callbacks in its data would not be one.
65
+ */
66
+ interface ActionSpec {
67
+ id: string;
68
+ label?: string;
69
+ /** A key into {@link BoardProps.icons}. Unknown names render nothing. */
70
+ icon?: string;
71
+ variant?: "default" | "outline" | "ghost" | "secondary" | "destructive";
72
+ /** A set of mutually exclusive views — `Board` ⁄ `board.yml`. */
73
+ options?: string[];
74
+ /** Which option is active. Only with `options`. */
75
+ value?: string;
76
+ /**
77
+ * Draw `options` as a dropdown rather than a segmented switch — for a choice
78
+ * among named records (`agent`), too many or too long to lay side by side.
79
+ * Dispatches with `{ option }` exactly as the switch does.
80
+ */
81
+ picker?: boolean;
82
+ /**
83
+ * Draw `options` as a menu behind this action's button — `⋯` opening
84
+ * `Versions · Arguments · Export YAML`. A menu is a list of acts, not a
85
+ * choice: nothing is selected and `value` is ignored. Dispatches with
86
+ * `{ option }`, and `optionLabels` names each item. With an `icon`, the
87
+ * button draws the icon alone and `label` is its accessible name.
88
+ */
89
+ menu?: boolean;
90
+ /**
91
+ * What the reader sees for an option, by option. The option itself is what
92
+ * dispatches, so a picker can show a name and send an id. An option with no
93
+ * label shows as itself.
94
+ */
95
+ optionLabels?: Record<string, string>;
96
+ /**
97
+ * An on/off setting — `Fit`. Set, the action draws as a labelled `Switch` in
98
+ * this state and dispatches with `{ pressed }`, the state it is switched to.
99
+ */
100
+ pressed?: boolean;
101
+ disabled?: boolean;
102
+ }
103
+ /** The record identity across the top — see `RecordHeader`. */
104
+ interface HeaderSpec {
105
+ /** The bar's height on the control scale: `lg` (default) tops a page, `md` a panel or a drawer. */
106
+ size?: "md" | "lg";
107
+ tone?: StatusDotProps["tone"];
108
+ /** Outermost first; the last is the record this board is about. */
109
+ crumbs: string[];
110
+ /**
111
+ * An action per crumb, by index — a crumb with one is a link back to the
112
+ * record it names (`run:7d3184f1` above a step). `undefined` stays text.
113
+ */
114
+ crumbActions?: (string | undefined)[];
115
+ /**
116
+ * The last crumb opens a picker of its siblings — every step of a run. A
117
+ * pick dispatches `action` with `{ itemId }`.
118
+ */
119
+ crumbMenu?: CrumbMenuSpec;
120
+ chips?: ChipSpec[];
121
+ actions?: ActionSpec[];
122
+ /**
123
+ * The record's own description, on one line under the crumbs — see
124
+ * `RecordDescription`. `More` shows the whole of it and {@link details}.
125
+ */
126
+ description?: string;
127
+ /** The record's facts, shown under the description by `More`. */
128
+ details?: Array<{
129
+ label: string;
130
+ value: string;
131
+ mono?: boolean;
132
+ }>;
133
+ }
134
+ /**
135
+ * What is staged and not yet published — drawn as a `StagedBar` between the
136
+ * header and the tabs, on every tab, while a draft is open. Absent draws nothing.
137
+ */
138
+ interface StagedSpec {
139
+ items: Array<{
140
+ id: string;
141
+ op: "add" | "remove" | "change";
142
+ name: string;
143
+ note?: string;
144
+ }>;
145
+ /** Dispatched with `{ itemId }` by an item's `×`. Absent, the items carry none. */
146
+ discardAction?: string;
147
+ /** Dispatched by `Discard all`. Absent, there is no `Discard all`. */
148
+ discardAllAction?: string;
149
+ /** The shortcut that finishes the set — `⌘↵ publish`. */
150
+ hint?: string;
151
+ }
152
+ /** The siblings the last crumb can switch to. */
153
+ interface CrumbMenuSpec {
154
+ action: string;
155
+ /** The field's placeholder over the list — `Jump to a step`. */
156
+ placeholder?: string;
157
+ items: Array<{
158
+ id: string;
159
+ label: string;
160
+ /** Right-aligned, mono — a duration. */
161
+ aside?: string;
162
+ tone?: StatusDotProps["tone"];
163
+ }>;
164
+ /** The item the page is on. */
165
+ selected?: string;
166
+ }
167
+ /**
168
+ * One tab of a board — its own bands under the shared header.
169
+ *
170
+ * Tabs are readings of one record, never other records: a spec with tabs is
171
+ * still one document, which is why a saved report keeps every tab.
172
+ */
173
+ interface TabSpec<X extends ExtraPanels = Record<never, never>> {
174
+ id: string;
175
+ label: string;
176
+ rows: RowSpec<X>[];
177
+ /**
178
+ * Nothing to read here yet — the tab draws dimmed with a lock and cannot be
179
+ * picked. Why it is locked is said where the reader already is, not on the tab.
180
+ */
181
+ locked?: boolean;
182
+ /**
183
+ * Drop the body padding, so the tab's one band meets the tab strip and the
184
+ * page's edges — a canvas, which is a surface and not a card on one.
185
+ */
186
+ flush?: boolean;
187
+ }
188
+ interface JsonOptions {
189
+ /** The document, as a string. Objects are stringified with 2-space indent. */
190
+ value: string | Record<string, unknown>;
191
+ maxHeight?: number;
192
+ }
193
+ interface CodeOptions {
194
+ value: string;
195
+ language?: "python" | "shell" | "javascript" | "json" | "cypher" | "yaml" | "plain";
196
+ maxHeight?: number;
197
+ showLineNumbers?: boolean;
198
+ }
199
+ interface LogOptions {
200
+ lines: Array<{
201
+ time?: string;
202
+ level?: "info" | "warn" | "error" | "debug";
203
+ source?: string;
204
+ message: string;
205
+ }>;
206
+ /** Overrides the `time · LEVEL · source · message` column widths. */
207
+ columnTemplate?: string;
208
+ }
209
+ /** One thing the engine wrote — a task started, a call made, a line logged. */
210
+ interface EventSpec {
211
+ id: string;
212
+ /** From the run's zero, in ms. */
213
+ atMs: number;
214
+ level: "debug" | "info" | "warn" | "error";
215
+ /** The engine's own word — `task_started`, `llm_call`, `retry_scheduled`. Drawn in its level's colour. */
216
+ kind: string;
217
+ /** The task it belongs to, by key. */
218
+ task?: string;
219
+ tookMs?: number;
220
+ message: string;
221
+ /** Everything else it carries, opened under the event in the tree. */
222
+ fields?: Record<string, string>;
223
+ }
224
+ /**
225
+ * The run's event stream, read one of two ways. `log` is every event in
226
+ * order, a page at a time, searched and narrowed by kind, task and level.
227
+ * `tree` is the tasks as a tree, each task's own events under it before
228
+ * the tasks it split into, an event opening into its fields.
229
+ */
230
+ interface EventsOptions {
231
+ events: EventSpec[];
232
+ layout?: "log" | "tree";
233
+ /** The tasks, for `tree` — a task with no `parent` is a root. */
234
+ tasks?: Array<{
235
+ key: string;
236
+ parent?: string;
237
+ status: string;
238
+ summary?: string;
239
+ startMs?: number;
240
+ durationMs?: number;
241
+ }>;
242
+ /** Leave out the task column and its chip — a log already about one task. */
243
+ hideTask?: boolean;
244
+ /** `log` only. Default `25`. */
245
+ pageSize?: number;
246
+ /**
247
+ * Dispatched with `{ panelId, value: <task key> }` when a row is picked — an
248
+ * event's task, or the task itself in the tree. Absent, rows do not pick.
249
+ */
250
+ selectAction?: string;
251
+ }
252
+ interface ListOptions {
253
+ items: Array<{
254
+ id?: string;
255
+ tone?: StatusDotProps["tone"];
256
+ icon?: string;
257
+ title: string;
258
+ /** Right-aligned fact — a duration, a size, a count. */
259
+ meta?: string;
260
+ chip?: ChipSpec;
261
+ mono?: boolean;
262
+ /** Emits `onAction(this, { itemId })` when the row is picked. */
263
+ action?: string;
264
+ }>;
265
+ }
266
+ interface ExchangeOptions {
267
+ /** `PROMPT · 1,204 tokens` over a mono block, twice. */
268
+ blocks: Array<{
269
+ label: string;
270
+ value: string;
271
+ language?: CodeOptions["language"];
272
+ }>;
273
+ }
274
+ interface ParamsOptions {
275
+ params: Array<{
276
+ name: string;
277
+ type?: string;
278
+ source: "literal" | "argument" | "binding";
279
+ value: string;
280
+ note?: string;
281
+ invalid?: boolean;
282
+ disabled?: boolean;
283
+ }>;
284
+ /** Emits `onAction(changeAction, { name, source, value })` on every edit. */
285
+ changeAction?: string;
286
+ }
287
+ interface TextOptions {
288
+ /** Plain paragraphs. No markdown — a board states facts, it does not write. */
289
+ text: string;
290
+ tone?: "default" | "muted" | "success" | "warning" | "error" | "info";
291
+ /** Renders inside an `Alert` rather than as a bare paragraph. */
292
+ callout?: boolean;
293
+ actions?: ActionSpec[];
294
+ }
295
+ /**
296
+ * A panel whose renderer is **not** built in — a canvas, a flow, a map.
297
+ *
298
+ * `@invana/canvas` is the reason this kind exists. The board does not
299
+ * import it: the consumer registers a renderer for `kind: "canvas"` and the
300
+ * spec carries only the data that renderer needs. That keeps PixiJS out of
301
+ * every consumer that only wanted tiles and a log.
302
+ */
303
+ interface CustomOptions {
304
+ [key: string]: unknown;
305
+ }
306
+ /**
307
+ * Kind → the options that kind reads.
308
+ *
309
+ * This map is what makes a spec **checkable**: a `gantt` panel whose task
310
+ * carries a status the Gantt has never heard of is a compile error, not a band
311
+ * that silently renders empty tracks. It did exactly that once, which is why
312
+ * the map exists.
313
+ */
314
+ interface PanelOptionsByKind {
315
+ json: JsonOptions;
316
+ code: CodeOptions;
317
+ exchange: ExchangeOptions;
318
+ log: LogOptions;
319
+ events: EventsOptions;
320
+ list: ListOptions;
321
+ params: ParamsOptions;
322
+ text: TextOptions;
323
+ }
324
+ type BuiltInPanelKind = keyof PanelOptionsByKind;
325
+ type PanelOptions = PanelOptionsByKind[BuiltInPanelKind] | BlockOptionsByKind[BlockKind] | CustomOptions;
326
+ /** Everything a panel carries apart from its kind and its options. */
327
+ interface PanelBase {
328
+ id?: string;
329
+ /**
330
+ * The label bar over the panel. Every panel is drawn in a `PanelBox` — the
331
+ * board frames what it draws, as an answer card frames a block — and
332
+ * `title` only adds the bar. Without it the box frames its content alone.
333
+ */
334
+ title?: string;
335
+ /** The fact on the right of the box header. Ignored without a `title`. */
336
+ aside?: string;
337
+ /** A chip on the right of the box header, instead of `aside`. */
338
+ asideChip?: ChipSpec;
339
+ /**
340
+ * Controls on the right of the box header, after `aside` — a reading of this
341
+ * panel, such as a `Fit` toggle. Dispatched with `{ panelId }`.
342
+ * Ignored without a `title`.
343
+ */
344
+ actions?: ActionSpec[];
345
+ /** Fixed column width in px. Without it the panel takes an equal share. */
346
+ width?: number;
347
+ /** Relative share of the row when several panels grow. Default `1`. */
348
+ grow?: number;
349
+ /** Drop the box padding, so a table or a canvas meets the border. */
350
+ flush?: boolean;
351
+ /**
352
+ * Where a block that returns a value stands — a `form`, a `confirm` — and the
353
+ * value given. The consumer moves it on `reply`, as the API patches an ask
354
+ * turn; unset, the block is open.
355
+ */
356
+ state?: AskState;
357
+ value?: unknown;
358
+ /**
359
+ * **Nobody recorded this band** — draw why, instead of the panel.
360
+ *
361
+ * A rule of the board and not of each renderer, because every kind has
362
+ * the same three ways of having nothing to draw and they are three different
363
+ * facts: `unrecorded` (the document is written when the row settles),
364
+ * `purged` (it existed and aged out) and `declared-none` (the step's contract
365
+ * has no such output). A renderer left to decide for itself reaches for an
366
+ * empty table, which claims the step returned an empty result.
367
+ *
368
+ * A panel with **no title and no options** is better left out of the spec
369
+ * altogether — absence is for a band a reader expects to find.
370
+ */
371
+ absent?: {
372
+ reason: "unrecorded" | "purged" | "declared-none";
373
+ /** Overrides the word — the engine's own term where there is one. */
374
+ label?: string;
375
+ /** Why, in the record's terms. */
376
+ note?: string;
377
+ };
378
+ /**
379
+ * The one escape from JSON: a node rendered in place of a registered kind.
380
+ *
381
+ * It exists because a real surface always has one panel the schema has not
382
+ * caught up with, and the alternative is a fork of the board. A spec that
383
+ * uses it is no longer serialisable — which is the honest cost, and why it is
384
+ * named after what it breaks rather than something comfortable like `content`.
385
+ */
386
+ render?: React.ReactNode;
387
+ }
388
+ /** A panel of a kind the board ships. Its options are checked against the map. */
389
+ type BuiltInPanelSpec = {
390
+ [K in BuiltInPanelKind]: PanelBase & {
391
+ kind: K;
392
+ options: PanelOptionsByKind[K];
393
+ };
394
+ }[BuiltInPanelKind];
395
+ /**
396
+ * A block as a panel: its kind, and the options the same block reads in a
397
+ * conversation turn — `{ kind: "timeseries", title: "Demand", options: { series } }`.
398
+ * A kind the consumer registers itself is theirs, not the block's.
399
+ */
400
+ type BlockPanelSpec<X extends ExtraPanels = Record<never, never>> = {
401
+ [K in Exclude<BlockKind, keyof X>]: PanelBase & {
402
+ kind: K;
403
+ options: BlockOptionsByKind[K];
404
+ };
405
+ }[Exclude<BlockKind, keyof X>];
406
+ /**
407
+ * The extra kinds a consumer registers — `{ canvas: CanvasOptions }`.
408
+ *
409
+ * A board's type is **parametrised by its registry**, which is the only way
410
+ * the built-in kinds stay checked. The obvious design — a catch-all member with
411
+ * `kind: string` — silently checks nothing: a union with one permissive member
412
+ * accepts every object, so a `gantt` panel full of invalid statuses compiles
413
+ * and renders empty tracks. It did. Hence this.
414
+ */
415
+ type ExtraPanels = Record<string, unknown>;
416
+ type RegisteredPanelSpec<X extends ExtraPanels> = {
417
+ [K in keyof X & string]: PanelBase & {
418
+ kind: K;
419
+ options: X[K];
420
+ };
421
+ }[keyof X & string];
422
+ /**
423
+ * A panel. With no type argument this is the built-ins and nothing else, so a
424
+ * `canvas` panel is a compile error until you say what its options are:
425
+ *
426
+ * ```ts
427
+ * const spec: BoardSpec<{ canvas: { nodes: TaskNodeSpec[] } }> = { … }
428
+ * ```
429
+ *
430
+ * For a spec arriving off the wire, where nothing can be checked anyway, use
431
+ * {@link AnyBoardSpec}.
432
+ */
433
+ type PanelSpec<X extends ExtraPanels = Record<never, never>> = BuiltInPanelSpec | BlockPanelSpec<X> | RegisteredPanelSpec<X>;
434
+ interface RowSpec<X extends ExtraPanels = Record<never, never>> {
435
+ id?: string;
436
+ panels: PanelSpec<X>[];
437
+ /** Pin the row's height in px — for a flow, which has no content height. */
438
+ height?: number;
439
+ /**
440
+ * Take the height the tab has left — a canvas, which has no content height
441
+ * and no one right height either. Ignored with {@link height}.
442
+ */
443
+ fill?: boolean;
444
+ /** Gap between panels in px. Defaults to the board's `gap`. */
445
+ gap?: number;
446
+ }
447
+ interface BoardSpec<X extends ExtraPanels = Record<never, never>> {
448
+ /** For the document title and nothing else; the header draws the crumbs. */
449
+ title?: string;
450
+ header?: HeaderSpec;
451
+ /** The staged set, as a bar under the header — see {@link StagedSpec}. */
452
+ staged?: StagedSpec;
453
+ /** The bands. Ignored when `tabs` is set — each tab carries its own. */
454
+ rows: RowSpec<X>[];
455
+ /** Readings of the record, as a tab strip under the header. */
456
+ tabs?: TabSpec<X>[];
457
+ /** The active tab's `id`. Absent reads the first. */
458
+ tab?: string;
459
+ /**
460
+ * Dispatched with `{ option: <tab id> }` when a tab is picked. Absent, the
461
+ * board keeps the tab itself — which is how a frozen report reads.
462
+ */
463
+ tabAction?: string;
464
+ /**
465
+ * Controls on the right of the tab strip that apply to every tab — the
466
+ * window a plan's numbers are read over. Ignored without `tabs`.
467
+ */
468
+ tabActions?: ActionSpec[];
469
+ /**
470
+ * One thing picked out of this board, read beside it — a task of a run
471
+ * beside the run's tree. Its own board (header, tabs, bands) in a column on
472
+ * the right, under this board's header, scrolling on its own, so the record
473
+ * it was picked from stays in view. Absent, the body takes the full width.
474
+ */
475
+ inspector?: InspectorSpec<X>;
476
+ /** Gap between rows and between panels, in px. Default `12`. */
477
+ gap?: number;
478
+ }
479
+ /**
480
+ * The column beside a board. Its header is drawn at `md`, as a panel's is; an
481
+ * `inspector` inside it is ignored — one record beside another, never a chain.
482
+ */
483
+ interface InspectorSpec<X extends ExtraPanels = Record<never, never>> {
484
+ spec: BoardSpec<X>;
485
+ /** In px. Default `440`. */
486
+ width?: number;
487
+ }
488
+ /** What `onAction` is told, beyond the action's own id. */
489
+ interface ActionContext {
490
+ panelId?: string;
491
+ /** Set by `list`'s row select and a crumb or staged item. */
492
+ itemId?: string;
493
+ /** Set by `params` on an edit. */
494
+ param?: {
495
+ name: string;
496
+ source: string;
497
+ value: string;
498
+ };
499
+ /** Set by a segmented action — which option was picked. */
500
+ option?: string;
501
+ /** Set by a switch action — the state it was switched to. */
502
+ pressed?: boolean;
503
+ /** Set by `trace`'s row select — the step a reader picked out of a run. */
504
+ stepId?: string;
505
+ /** Set by `lens`'s row select — the lens a reader picked, by name. */
506
+ lens?: string;
507
+ /** Set by a block panel — what its action carries: a row's key, a form's values. */
508
+ value?: unknown;
509
+ /** Set by `BoardPages` — the page a pick, a close or a board's own action came from. */
510
+ pageId?: string;
511
+ }
512
+ interface PanelRendererProps<O = PanelOptions> {
513
+ /**
514
+ * The panel's own fields. Not a `PanelSpec` — a renderer reads `id`, `title`
515
+ * and `kind`, and receives its options separately and already narrowed, so
516
+ * tying it to the spec union would make every renderer generic for nothing.
517
+ */
518
+ panel: PanelBase & {
519
+ kind: string;
520
+ };
521
+ options: O;
522
+ onAction: (actionId: string, ctx?: ActionContext) => void;
523
+ icons: Record<string, React.ComponentType<{
524
+ className?: string;
525
+ }>>;
526
+ /**
527
+ * The board's own gap, in px.
528
+ *
529
+ * Passed down so a panel that lays out a grid of its own — the tile strip —
530
+ * keeps the surface's rhythm instead of inventing a second one. A board
531
+ * whose bands sit 12px apart and whose tiles sit 6px apart reads as two
532
+ * grids that happen to share a page.
533
+ */
534
+ gap: number;
535
+ }
536
+ type PanelRenderer<O = any> = React.ComponentType<PanelRendererProps<O>>;
537
+ /** Kind → renderer. Consumer entries win over the built-ins. */
538
+ type PanelRegistry = Record<string, PanelRenderer>;
539
+ interface BoardProps<X extends ExtraPanels = Record<never, never>> extends React.HTMLAttributes<HTMLDivElement> {
540
+ spec: BoardSpec<X>;
541
+ /**
542
+ * Everything a person can do. Called with the action's `id` from the spec and
543
+ * whatever context the panel had — see {@link ActionContext}.
544
+ */
545
+ onAction?: (actionId: string, ctx?: ActionContext) => void;
546
+ /**
547
+ * Extra panel kinds, merged over the built-ins. This is where
548
+ * `@invana/canvas` arrives: `{ canvas: MyFlowPanel }`.
549
+ */
550
+ registry?: PanelRegistry;
551
+ /** Icon names the spec may use. Unknown names render nothing. */
552
+ icons?: Record<string, React.ComponentType<{
553
+ className?: string;
554
+ }>>;
555
+ /**
556
+ * Patches to `spec` as they arrive — a panel's block streaming by the same
557
+ * patch a conversation streams it by (`patch-panel`), or its own fields
558
+ * moving (`update-panel`). The board draws `spec` with them applied; a new
559
+ * `spec` starts over from it. Sources and their rules are the block stream's:
560
+ * `fromNdjson`, `fromEventSource`, a generator, or `playScript`, or a
561
+ * function `(signal) => source` kept stable.
562
+ */
563
+ stream?: SpecStream<BoardPatch>;
564
+ /** The stream ended; the spec as it left it. */
565
+ onStreamEnd?: (spec: BoardSpec<X>) => void;
566
+ /** A patch did not apply, or the source failed. The stream stops there. */
567
+ onStreamError?: (error: unknown) => void;
568
+ }
569
+ /**
570
+ * A spec whose panel kinds are not known at compile time — one fetched from an
571
+ * API, or read from a `board.yml`. Nothing about it is checked, which is
572
+ * the truth about JSON off the wire rather than a weakness of the type.
573
+ */
574
+ type AnyBoardSpec = BoardSpec<Record<string, CustomOptions>>;
575
+ /** One page of a {@link BoardPagesSpec}: its tab, and the board behind it. */
576
+ interface BoardPageSpec<X extends ExtraPanels = Record<never, never>> {
577
+ id: string;
578
+ title: string;
579
+ /** A key into {@link BoardPagesProps.icons}. Unknown names render nothing. */
580
+ icon?: string;
581
+ disabled?: boolean;
582
+ /** Draws an `×` on the tab, dispatching `closeAction`. Only with `closeAction`. */
583
+ closable?: boolean;
584
+ board: BoardSpec<X>;
585
+ }
586
+ /**
587
+ * Several boards behind one tab strip — the open boards of a shell. Like a
588
+ * board's own tabs, the pick is the strip's to keep until the spec names
589
+ * `selectAction`; then it is reported with `{ pageId }` and `active` is the host's.
590
+ */
591
+ interface BoardPagesSpec<X extends ExtraPanels = Record<never, never>> {
592
+ pages: BoardPageSpec<X>[];
593
+ /** The page shown. Defaults to the first. */
594
+ active?: string;
595
+ /** Dispatched with `{ pageId }` when a tab is picked. */
596
+ selectAction?: string;
597
+ /** Dispatched with `{ pageId }` by a closable tab's `×`. */
598
+ closeAction?: string;
599
+ /** Draws a `+` that dispatches this. Absent, there is no `+`. */
600
+ addAction?: string;
601
+ /** The `+`'s tooltip — `New board`. */
602
+ addLabel?: string;
603
+ /** Where the tabs sit: over the boards (default) or under them, as a spreadsheet's do. */
604
+ tabPosition?: "top" | "bottom";
605
+ /** Which end of the strip the prev / next / `+` buttons dock at. Default `end`. */
606
+ pagerPosition?: "start" | "end";
607
+ }
608
+ interface BoardPagesProps<X extends ExtraPanels = Record<never, never>> {
609
+ spec: BoardPagesSpec<X>;
610
+ /** Every page's actions and the strip's own, told apart by `pageId`. */
611
+ onAction?: (actionId: string, ctx?: ActionContext) => void;
612
+ registry?: PanelRegistry;
613
+ icons?: Record<string, React.ComponentType<{
614
+ className?: string;
615
+ }>>;
616
+ className?: string;
617
+ }
618
+
619
+ /**
620
+ * The panels only a board has. Everything a conversation also draws — a
621
+ * table, a band of figures, a record, a chart — is a block, and every block
622
+ * kind is a panel kind too (see `BLOCK_PANELS`).
623
+ *
624
+ * Eight, and the number is meant to stay near it. Another belongs here only
625
+ * when two unrelated surfaces both need it and a conversation does not —
626
+ * otherwise it is a block, or a consumer's registry entry. `events` passes:
627
+ * a run page and a telemetry view both read an engine's event stream.
628
+ *
629
+ * Deliberately absent: **`canvas`**. A flow, a map or a graph needs
630
+ * `@invana/canvas`, and importing it here would put PixiJS in the bundle of
631
+ * every consumer that only wanted tiles and a log. It arrives as a registry
632
+ * entry instead — see the `canvas` note in `types.ts`.
633
+ */
634
+ declare const BUILT_IN_PANELS: PanelRegistry;
635
+ /**
636
+ * Every kind a board can draw: the blocks, then the panels only a
637
+ * board has, then the consumer's own. Later entries win, so a registered
638
+ * kind replaces a block of the same name — `RUN_PANELS`' `trace` does.
639
+ */
640
+ declare function resolveRegistry(extra?: PanelRegistry): PanelRegistry;
641
+
642
+ /**
643
+ * A board, assembled from JSON.
644
+ *
645
+ * The spec says what bands there are, what kind each one is and what data it
646
+ * carries; this decides nothing except layout. That split is the point — the
647
+ * same component renders a run, a step, a plan and a draft, and adding a
648
+ * seventh surface is a new document rather than a new screen.
649
+ *
650
+ * **Behaviour is not in the spec.** Actions carry an `id` and arrive back
651
+ * through `onAction`; a gantt row's pick, a list row's click and a
652
+ * parameter edit all come through the same seam. So a spec can be fetched,
653
+ * stored beside a plan as `board.yml`, diffed between two runs, and handed
654
+ * to a renderer that has never heard of the record it describes.
655
+ *
656
+ * **It owns one scroller.** The body scrolls; no band does. A `PanelBox` is
657
+ * content-height by construction, which is what keeps twenty of them in a
658
+ * column from becoming twenty scroll regions.
659
+ */
660
+ declare function Board<X extends ExtraPanels = Record<never, never>>({ spec: given, onAction, registry: extraRegistry, icons, stream, onStreamEnd, onStreamError, className, ...props }: BoardProps<X>): React.JSX.Element;
661
+
662
+ /**
663
+ * Several boards in one {@link Workbook} — the open boards of a shell's
664
+ * main section. Each page is a {@link Board} drawn from its own spec; every
665
+ * page stays mounted, so switching keeps a board's scroll and tab.
666
+ *
667
+ * Uncontrolled unless the spec names `selectAction`, as a board's own tabs
668
+ * are: then the pick is reported and `active` is the host's to change.
669
+ */
670
+ declare function BoardPages<X extends ExtraPanels = Record<never, never>>({ spec, onAction, registry, icons, className, }: BoardPagesProps<X>): React.JSX.Element;
671
+
672
+ /**
673
+ * A block as a panel: the same JSON a conversation turn draws, framed by the
674
+ * panel. The block draws bare — its title and aside are the panel's — and what
675
+ * it does arrives as `onAction(action, { panelId, value })`, so a row picked in
676
+ * a `table` is `select` with the row's key, told apart by the panel it came from;
677
+ * an action its spec declares arrives by its own id — `approve`.
678
+ */
679
+ declare function BlockPanel({ panel, options, onAction }: PanelRendererProps): React.JSX.Element;
680
+ /** Every block kind, drawn by {@link BlockPanel}. A kind with no renderer yet is a labelled placeholder. */
681
+ declare const BLOCK_PANELS: PanelRegistry;
682
+
683
+ interface AttemptRow {
684
+ /** `queued` · `attempt 1` · `attempt 2` · `settled`. */
685
+ label: React.ReactNode;
686
+ /** When it started, on the run's own clock — `+44.5s`. */
687
+ started?: React.ReactNode;
688
+ /** How long it took — `30.0s`, or `4.2s…` while it is still running. */
689
+ took?: React.ReactNode;
690
+ /** What happened, in one line. */
691
+ what?: React.ReactNode;
692
+ tone?: "muted" | "info" | "success" | "warning" | "destructive";
693
+ /** It ran and it did not stick. **Struck, never dropped.** */
694
+ struck?: boolean;
695
+ }
696
+ interface AttemptClockProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
697
+ rows: AttemptRow[];
698
+ /**
699
+ * What the two numbers are — `elapsed 32.4s · working 2.3s · the gap is the
700
+ * attempt that timed out`.
701
+ *
702
+ * **Stated, never left as arithmetic.** A reader who has to subtract two
703
+ * durations to find a 30-second timeout has been handed a sum instead of an
704
+ * answer.
705
+ */
706
+ summary?: React.ReactNode;
707
+ }
708
+ /**
709
+ * A step's clock, attempt by attempt.
710
+ *
711
+ * One duration cannot say what happened to a step that timed out once and then
712
+ * returned: `2.1s` is what it *did*, `32.4s` is what a reader waited, and the
713
+ * gap between them **is** the first attempt. So the clock is a row per attempt
714
+ * — queued, each try, settled — and the try that failed keeps its place, struck.
715
+ *
716
+ * Nothing new is recorded for this: it is the `timing` and `attempt` fields the
717
+ * interpreter already writes, read as a list instead of as a total.
718
+ */
719
+ declare const AttemptClock: React.ForwardRefExoticComponent<AttemptClockProps & React.RefAttributes<HTMLDivElement>>;
720
+
721
+ interface RecordDescriptionDetail {
722
+ label: React.ReactNode;
723
+ value: React.ReactNode;
724
+ /** The value in the mono face — an id, a mode, a date. */
725
+ mono?: boolean;
726
+ }
727
+ interface RecordDescriptionProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
728
+ /** The one field a reader scans — one line, truncated, until `More`. */
729
+ description?: React.ReactNode;
730
+ /** What `More` shows beneath the full description, as label/value pairs. */
731
+ details?: RecordDescriptionDetail[];
732
+ moreLabel?: string;
733
+ lessLabel?: string;
734
+ }
735
+ /**
736
+ * A record's own metadata, on the line under its `RecordHeader`.
737
+ *
738
+ * The description reads on one line, and `More` puts the rest — the whole
739
+ * description, then the record's facts as a `PropertyList` — in place, and
740
+ * `Less` folds it back. Not in a tooltip or a dialog: the reader is already
741
+ * looking at the record.
742
+ *
743
+ * `More` is there only when there is more: details to show, or a description
744
+ * the line cuts.
745
+ */
746
+ declare const RecordDescription: React.ForwardRefExoticComponent<RecordDescriptionProps & React.RefAttributes<HTMLDivElement>>;
747
+
748
+ interface StagedBarItem {
749
+ id: string;
750
+ op: DiffOp;
751
+ /** What changed, in the mono face — `airport.timezone`. */
752
+ name: React.ReactNode;
753
+ /** What the change is — `property`, `integer → float`. */
754
+ note?: React.ReactNode;
755
+ }
756
+ interface StagedBarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
757
+ items: StagedBarItem[];
758
+ /** Discard one change. Left out, the items carry no `×`. */
759
+ onDiscard?: (id: string) => void;
760
+ /** Discard every change. Left out, there is no `Discard all`. */
761
+ onDiscardAll?: () => void;
762
+ /** The keyboard route to what finishes the set — `⌘↵ publish`. */
763
+ hint?: React.ReactNode;
764
+ discardAllLabel?: string;
765
+ }
766
+ /**
767
+ * What is staged and not yet published, as one bar across the surface it
768
+ * changes.
769
+ *
770
+ * The count, then each change as a chip with its sign spelled out — a reader
771
+ * who cannot separate red from green still tells an addition from a removal —
772
+ * and its own `×`; `Discard all` and the shortcut that publishes on the right.
773
+ * It sits under the record's header while a draft is open, and not at all
774
+ * otherwise: a bar reading *0 staged* is noise.
775
+ */
776
+ declare const StagedBar: React.ForwardRefExoticComponent<StagedBarProps & React.RefAttributes<HTMLDivElement>>;
777
+
778
+ /** A chip from its spec — a bound renders as a `BoundChip`, everything else as a `Badge`. */
779
+ declare function SpecChip({ chip }: {
780
+ chip: ChipSpec;
781
+ }): React.JSX.Element;
782
+ declare function SpecChips({ chips }: {
783
+ chips?: ChipSpec[];
784
+ }): React.JSX.Element | null;
785
+ /**
786
+ * An action from its spec.
787
+ *
788
+ * `options` turns it into a segmented switch, because a view switch is one
789
+ * decision with several positions and rendering it as three buttons would let a
790
+ * reader think they could pick two. `picker` draws the same choice as a
791
+ * dropdown, for records too many or too long to lay side by side. `menu` puts
792
+ * them behind the action's own button, as acts rather than a choice.
793
+ */
794
+ declare function SpecAction({ action, onAction, icons, ctx, }: {
795
+ action: ActionSpec;
796
+ onAction: (id: string, ctx?: ActionContext) => void;
797
+ icons: Record<string, React.ComponentType<{
798
+ className?: string;
799
+ }>>;
800
+ ctx?: ActionContext;
801
+ }): React.JSX.Element;
802
+ declare function SpecActions({ actions, onAction, icons, ctx, }: {
803
+ actions?: ActionSpec[];
804
+ onAction: (id: string, ctx?: ActionContext) => void;
805
+ icons: Record<string, React.ComponentType<{
806
+ className?: string;
807
+ }>>;
808
+ ctx?: ActionContext;
809
+ }): React.JSX.Element | null;
810
+
811
+ declare function JsonPanel({ options }: PanelRendererProps<JsonOptions>): React.JSX.Element;
812
+ declare function CodePanel({ options }: PanelRendererProps<CodeOptions>): React.JSX.Element;
813
+ /**
814
+ * A labelled pair of mono blocks — a prompt and what came back.
815
+ *
816
+ * Its own kind rather than two `code` panels, because the two are one record:
817
+ * a completion shown without the prompt that produced it is not evidence.
818
+ */
819
+ declare function ExchangePanel({ options }: PanelRendererProps<ExchangeOptions>): React.JSX.Element;
820
+
821
+ declare function LogPanel({ options }: PanelRendererProps<LogOptions>): React.JSX.Element;
822
+ /**
823
+ * Rows that are not a table: artifacts, the runs of a plan, a version history.
824
+ *
825
+ * A table is for values you compare down a column. These are records you scan
826
+ * and open, which is what `Item` is for — so a list panel is never a
827
+ * one-column table.
828
+ */
829
+ declare function ListPanel({ panel, options, onAction, icons }: PanelRendererProps<ListOptions>): React.JSX.Element;
830
+ declare function ParamsPanel({ panel, options, onAction }: PanelRendererProps<ParamsOptions>): React.JSX.Element;
831
+ declare function TextPanel({ panel, options, onAction, icons }: PanelRendererProps<TextOptions>): React.JSX.Element;
832
+
833
+ /**
834
+ * The run's event stream — see {@link EventsOptions}. Both layouts read the
835
+ * same events, so a log and a tree of one run never disagree.
836
+ */
837
+ declare function EventsPanel(props: PanelRendererProps<EventsOptions>): React.JSX.Element;
838
+
839
+ /**
840
+ * The panels a **run page** is made of.
841
+ *
842
+ * They are not in `BUILT_IN_PANELS`, and that is deliberate: the built-in list
843
+ * is eleven kinds two unrelated surfaces each need, and these six are one
844
+ * surface family's — a run read in order, what it touched, a step's clock, the
845
+ * files it left, what governed it, and the ask it settled. The layers it spent
846
+ * are the `gantt` block — a row per layer, a bar per task that reached it — so
847
+ * a conversation draws them too.
848
+ * Shipping them here rather than in the default registry keeps a consumer that
849
+ * only wanted tiles and a log from carrying the run vocabulary, while keeping
850
+ * the adapter in the kit rather than copied into every product that draws runs.
851
+ *
852
+ * ```ts
853
+ * <Board spec={spec} registry={RUN_PANELS} />
854
+ * ```
855
+ */
856
+ /** One row of a trace — a step, a loop that holds rows, or a gate between them. */
857
+ type TraceEntry = {
858
+ kind?: "step";
859
+ seq?: number | string;
860
+ name: string;
861
+ description?: string;
862
+ layer?: string;
863
+ role?: string;
864
+ duration?: string;
865
+ note?: string;
866
+ /** The exception this row carries — `↺ 2 of 3`, `live`, `↳ delegates`. */
867
+ mark?: string;
868
+ markTone?: React.ComponentProps<typeof MarkChip>["tone"];
869
+ depth?: number;
870
+ dim?: boolean;
871
+ struck?: boolean;
872
+ /** Fired as `selectAction` with `{ stepId }`. */
873
+ id?: string;
874
+ } | {
875
+ kind: "loop";
876
+ label: string;
877
+ summary?: string;
878
+ tone?: "warning" | "info" | "muted";
879
+ rounds: TraceEntry[];
880
+ } | {
881
+ kind: "gate";
882
+ label: string;
883
+ note?: string;
884
+ tone?: "destructive" | "warning" | "muted";
885
+ edge?: "before" | "after";
886
+ };
887
+ interface TraceOptions {
888
+ entries: TraceEntry[];
889
+ palette?: LayerPalette;
890
+ /** Which step is open elsewhere. */
891
+ selectedId?: string | null;
892
+ /** Dispatched with `{ stepId }` when a row is picked. */
893
+ selectAction?: string;
894
+ }
895
+ interface TouchedOptions {
896
+ items: TouchItem[];
897
+ palette?: LayerPalette;
898
+ }
899
+ interface AttemptsOptions {
900
+ rows: AttemptRow[];
901
+ summary?: string;
902
+ }
903
+ /**
904
+ * One file a step left, as **data**.
905
+ *
906
+ * `ArtifactTable`'s own `Artifact` types every cell as a `ReactNode`, which a
907
+ * spec cannot be. These are strings: a board is fetched, stored beside a
908
+ * plan and diffed, and a `ReactNode` survives none of that.
909
+ */
910
+ interface ArtifactSpec {
911
+ name: string;
912
+ kind?: string;
913
+ size?: string;
914
+ /** The address — eight characters, drawn mono ([SR58]). */
915
+ digest?: string;
916
+ written?: string;
917
+ /** Retention took the bytes. The row stays, struck. */
918
+ gone?: boolean;
919
+ }
920
+ interface ArtifactsOptions {
921
+ files: ArtifactSpec[];
922
+ /** Dispatched with `{ digest, name, index }`. Omitted, the column is absent. */
923
+ openAction?: string;
924
+ downloadAction?: string;
925
+ }
926
+ /** One lens row under the layer it narrows. */
927
+ interface LensRowSpec {
928
+ name: string;
929
+ narrows?: Narrowing[];
930
+ usage?: LensUsage;
931
+ }
932
+ /** One participant the world allowed, and what the run did with it ([SR53]). */
933
+ interface ParticipantSpec {
934
+ address: string;
935
+ /** `touched` · `never touched` · `refused` · `miss` — any word the engine writes. */
936
+ verdict: string;
937
+ /** What came back, or why nothing did — `1,284 rows · step 7`. */
938
+ note?: string;
939
+ }
940
+ interface LensSectionSpec {
941
+ layer: Layer;
942
+ summary?: string;
943
+ count?: number;
944
+ /** Nothing narrows this layer. Drawn dim rather than dropped. */
945
+ dim?: boolean;
946
+ /**
947
+ * **A section carries one of two readings, and they are different questions.**
948
+ * `participants` is a *run's* lens — every address the world allowed, each
949
+ * marked with what this execution did with it. `rows` is a *world's* — the
950
+ * lenses that narrow this layer, as they read in the Govern drawer. A section
951
+ * that gives both draws both, participants first.
952
+ */
953
+ participants?: ParticipantSpec[];
954
+ rows?: LensRowSpec[];
955
+ }
956
+ interface LensOptions {
957
+ sections: LensSectionSpec[];
958
+ palette?: LayerPalette;
959
+ /** Which lens is open elsewhere, by name. */
960
+ selected?: string | null;
961
+ /** Dispatched with `{ lens }` when a row is picked. */
962
+ selectAction?: string;
963
+ }
964
+ interface ClarificationOption {
965
+ label: string;
966
+ /** The one that was taken. */
967
+ chosen?: boolean;
968
+ }
969
+ interface ClarificationOptions {
970
+ /** Who asked — `the agent asks`. */
971
+ asker?: string;
972
+ question: string;
973
+ /** Why it had to ask, in the step's own terms. */
974
+ why?: string;
975
+ options?: ClarificationOption[];
976
+ answerer?: string;
977
+ answer?: string;
978
+ /** What it cost and what happened next — `after 41.2s · resumed on round 2`. */
979
+ answerNote?: string;
980
+ }
981
+ /**
982
+ * The six kinds, as a type argument for `BoardSpec`.
983
+ *
984
+ * A `type` and not an `interface`: `ExtraPanels` is `Record<string, unknown>`,
985
+ * which an interface never satisfies — it has no implicit index signature — so
986
+ * an interface here would compile everywhere except at the one call site that
987
+ * matters.
988
+ */
989
+ type RunPanelOptions = {
990
+ trace: TraceOptions;
991
+ touched: TouchedOptions;
992
+ attempts: AttemptsOptions;
993
+ artifacts: ArtifactsOptions;
994
+ lens: LensOptions;
995
+ clarification: ClarificationOptions;
996
+ };
997
+ declare function TracePanel({ panel, options, onAction, }: PanelRendererProps<TraceOptions>): React.JSX.Element;
998
+ declare function TouchedPanel({ options }: PanelRendererProps<TouchedOptions>): React.JSX.Element;
999
+ declare function AttemptsPanel({ options }: PanelRendererProps<AttemptsOptions>): React.JSX.Element;
1000
+ /** Merge into a board's registry to draw a run — see the note above. */
1001
+ declare const RUN_PANELS: {
1002
+ trace: typeof TracePanel;
1003
+ touched: typeof TouchedPanel;
1004
+ attempts: typeof AttemptsPanel;
1005
+ artifacts: typeof ArtifactsPanel;
1006
+ lens: typeof LensPanel;
1007
+ clarification: typeof ClarificationPanel;
1008
+ };
1009
+ declare function ArtifactsPanel({ panel, options, onAction, }: PanelRendererProps<ArtifactsOptions>): React.JSX.Element;
1010
+ declare function LensPanel({ panel, options, onAction }: PanelRendererProps<LensOptions>): React.JSX.Element;
1011
+ declare function ClarificationPanel({ options, }: PanelRendererProps<ClarificationOptions>): React.JSX.Element;
1012
+
1013
+ export { type ActionContext, type ActionSpec, type AnyBoardSpec, type ArtifactSpec, type ArtifactsOptions, ArtifactsPanel, AttemptClock, type AttemptClockProps, type AttemptRow, type AttemptsOptions, AttemptsPanel, BLOCK_PANELS, BUILT_IN_PANELS, BlockPanel, type BlockPanelSpec, Board, type BoardPageSpec, BoardPages, type BoardPagesProps, type BoardPagesSpec, type BoardPatch, BoardPatchError, type BoardPatchOp, type BoardProps, type BoardSpec, type BuiltInPanelKind, type BuiltInPanelSpec, type ChipSpec, type ClarificationOption, type ClarificationOptions, ClarificationPanel, type CodeOptions, CodePanel, type CrumbMenuSpec, type CustomOptions, type EventSpec, type EventsOptions, EventsPanel, type ExchangeOptions, ExchangePanel, type ExtraPanels, type HeaderSpec, type InspectorSpec, type JsonOptions, JsonPanel, type LensOptions, LensPanel, type LensRowSpec, type LensSectionSpec, type ListOptions, ListPanel, type LogOptions, LogPanel, type PanelBase, type PanelOptions, type PanelOptionsByKind, type PanelRegistry, type PanelRenderer, type PanelRendererProps, type PanelSpec, type ParamsOptions, ParamsPanel, type ParticipantSpec, RUN_PANELS, RecordDescription, type RecordDescriptionDetail, type RecordDescriptionProps, type RegisteredPanelSpec, type RowSpec, type RunPanelOptions, SpecAction, SpecActions, SpecChip, SpecChips, StagedBar, type StagedBarItem, type StagedBarProps, type StagedSpec, type TabSpec, type TextOptions, TextPanel, type Tone, type TouchedOptions, TouchedPanel, type TraceEntry, type TraceOptions, TracePanel, applyBoardPatch, applyBoardPatches, resolveRegistry };