@fasl-work/caos-app-shell 0.6.8 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,105 +1,11 @@
1
+ export { A as AppShell, a as ArchTab, b as ArchitectureConfig, c as ArchitectureModal, L as LANG_STORAGE_KEY, S as STANDARD_ROUTES, d as ShellConfig, e as ShellRoute, T as THEME_BOOT_SCRIPT, f as THEME_STORAGE_KEY, v as validateArchitectureConfig } from './keys-CnvqkckF.js';
1
2
  import * as react from 'react';
2
- import { ReactNode } from 'react';
3
+ import { ReactNode, Component, ErrorInfo } from 'react';
3
4
  import * as zustand from 'zustand';
4
-
5
- interface ArchTab {
6
- id: string;
7
- /** tab label. */
8
- en: string;
9
- es: string;
10
- /** the explanation body (paragraphs separated by a blank line). */
11
- body_en: string;
12
- body_es: string;
13
- /**
14
- * an inline '<svg…>…</svg>' string OR a path under the app's public/ (fetched + inlined).
15
- *
16
- * ONE file carries BOTH languages (ADR-0058). Tag each translatable `<text>` twice at the same
17
- * coordinates, `class="… l-en"` and `class="… l-es"`; the modal wrapper sets `data-arch-lang`
18
- * and the shell stylesheet shows exactly one. Two files would be two things to keep in step, and
19
- * the one that is not on screen is the one that goes stale. Anything language-neutral (a number,
20
- * a file name, an identifier) needs no pair.
21
- */
22
- svg: string;
23
- }
24
- interface ArchitectureConfig {
25
- /** modal title override (defaults to "Architecture / How it works"). */
26
- title_en?: string;
27
- title_es?: string;
28
- tabs: ArchTab[];
29
- }
30
- declare function ArchitectureModal({ config, onClose }: {
31
- config: ArchitectureConfig;
32
- onClose: () => void;
33
- }): react.JSX.Element | null;
34
-
35
- interface ShellRoute {
36
- path: string;
37
- en: string;
38
- es: string;
39
- }
40
- interface ShellConfig {
41
- /** Brand: product name + optional lucide icon element (defaults to a generic mark). */
42
- product: {
43
- name: string;
44
- mark?: ReactNode;
45
- };
46
- /** Top-level routes for the header nav. 0–1 entries → nav hidden (e.g. the Faena hub). */
47
- routes?: ShellRoute[];
48
- /** External links. personal/portfolio default to Felipe's canonical URLs (kept identical app-to-app). */
49
- links: {
50
- github: string;
51
- personal?: string;
52
- portfolio?: string;
53
- };
54
- /** Human version string (X.XX.XXX) shown in the footer. */
55
- version: string;
56
- /** In-app Architecture / "How it works" modal (ADR-0058). When present, an ⓘ button appears in the header. */
57
- architecture?: ArchitectureConfig;
58
- /** Footer provenance + honesty (ADR-0016 §2): the real data/engine source with citation +
59
- * license (e.g. "Engine: lingbot-map (arXiv:2604.14141, Apache-2.0)") and the one-line honest
60
- * disclaimer of how the app runs. Both optional, both bilingual. */
61
- footer?: {
62
- /** Explicit product attribution, or false when the product excludes personal attribution. */
63
- attribution?: {
64
- en: string;
65
- es: string;
66
- } | false;
67
- /** The consuming product's license; the shell's MIT license is not inherited by applications. */
68
- license?: {
69
- en: string;
70
- es: string;
71
- };
72
- provenance?: {
73
- en: string;
74
- es: string;
75
- };
76
- disclaimer?: {
77
- en: string;
78
- es: string;
79
- };
80
- };
81
- /**
82
- * Routes whose surface IS the viewport (ADR-0071 rule 1): on these the shell adds
83
- * `.app-shell.fixed`, which sizes the page to 100dvh and lets flex distribute the
84
- * height, so the ONE container that owns long content scrolls and the document
85
- * never does. A doc route is not listed and keeps the document scroll. Matching is
86
- * exact, or by prefix for a non-root path (`/lab` matches `/lab/case-1`).
87
- *
88
- * The stylesheet has carried `.app-shell.fixed` with the note "apps that fill the
89
- * viewport add it" since the containment fix, and no app could: this component
90
- * rendered a fixed class string. Measured on Porvenir before this field existed:
91
- * every tab of the App route scrolled the document by 200 to 770px.
92
- */
93
- fixedRoutes?: string[];
94
- /** Every route fixed: a single-surface app (a hub, a one-page tool). */
95
- fixed?: boolean;
96
- }
97
- /** The shared CAOS/Faena app shell: sticky header (brand + nav + icon-links + lang/theme) + footer.
98
- * Wrap your <Routes> (or single landing) in it, inside a Router. */
99
- declare function AppShell({ config, children }: {
100
- config: ShellConfig;
101
- children: ReactNode;
102
- }): react.JSX.Element;
5
+ import { F as FormatOptions } from './tokens-BCqyHYx2.js';
6
+ export { N as NOT_AVAILABLE, a as SHELL_TOKENS, S as ShellToken, f as formatNumber, r as resolveToken, u as useFormat, b as useThemeTokens } from './tokens-BCqyHYx2.js';
7
+ import { L as Lang, B as BiText } from './text-BlY47nIt.js';
8
+ export { p as pick, u as useLangStore, a as useShellLang } from './text-BlY47nIt.js';
103
9
 
104
10
  /** One authenticated workbench destination rendered by the shared responsive navigation frame. */
105
11
  interface WorkbenchRoute {
@@ -131,6 +37,22 @@ interface WorkbenchShellProps {
131
37
  /** Shared CAOS frame for authenticated consoles with desktop sidebar and mobile bottom navigation. */
132
38
  declare function WorkbenchShell({ brand, routes, navigationLabel, children, sidebarFooter, headerLead, headerActions, overlays, mainId, className, }: WorkbenchShellProps): react.JSX.Element;
133
39
 
40
+ interface FocusShellProps {
41
+ stage: ReactNode;
42
+ rail: ReactNode;
43
+ title: string;
44
+ description: string;
45
+ hud: Array<{
46
+ label: string;
47
+ value: string;
48
+ }>;
49
+ onExit: () => void;
50
+ exitLabel: string;
51
+ stageLabel: string;
52
+ }
53
+ /** Full-viewport, scenario-scoped analysis frame. The product supplies only its instrument and controls. */
54
+ declare function FocusShell({ stage, rail, title, description, hud, onExit, exitLabel, stageLabel }: FocusShellProps): react.JSX.Element;
55
+
134
56
  /** Light/dark icon toggle. Shows the icon of the theme you would switch TO. */
135
57
  declare function ThemeToggle(): react.JSX.Element;
136
58
 
@@ -147,15 +69,25 @@ interface ThemeState {
147
69
  }
148
70
  declare const useThemeStore: zustand.UseBoundStore<zustand.StoreApi<ThemeState>>;
149
71
 
150
- type Lang = 'en' | 'es';
151
- interface LangState {
152
- lang: Lang;
153
- setLang: (l: Lang) => void;
154
- toggle: () => void;
72
+ interface Props {
73
+ /** Which view this boundary protects; logged and shown so the failure is located, not guessed. */
74
+ panel: string;
75
+ children: ReactNode;
76
+ }
77
+ interface State {
78
+ error: Error | null;
79
+ }
80
+ /**
81
+ * An error boundary around one view. A failing chart or panel shows an inline message in its own frame instead of
82
+ * blanking the whole page, and the failure is logged with its panel id, which the measured gate fails on (failure
83
+ * class 20 of the 2026-10-04 history: white screens and silent failures).
84
+ */
85
+ declare class PanelBoundary extends Component<Props, State> {
86
+ state: State;
87
+ static getDerivedStateFromError(error: Error): State;
88
+ componentDidCatch(error: Error, info: ErrorInfo): void;
89
+ render(): ReactNode;
155
90
  }
156
- declare const useLangStore: zustand.UseBoundStore<zustand.StoreApi<LangState>>;
157
- /** Current language, for app pages that render language-branched long-form content. */
158
- declare const useShellLang: () => Lang;
159
91
 
160
92
  /** Return `false` from a frame to end the current pass (the animation is complete); return `true`
161
93
  * or nothing to keep going. `dt` = ms since the previous frame, `elapsed` = ms since this pass
@@ -167,13 +99,13 @@ interface VizLoopOptions {
167
99
  /** Auto-complete the pass once `elapsed` reaches this many ms (a hard cap so a frame that never
168
100
  * returns false still stops). The final frame is drawn clamped to exactly this value. */
169
101
  durationMs?: number;
170
- /** Fired once each time the loop stops on its own (a completed pass with loop=false) — not on a
102
+ /** Fired once each time the loop stops on its own (a completed pass with loop=false), not on a
171
103
  * manual pause and not on a visibility halt. */
172
104
  onComplete?: () => void;
173
105
  /** Notified whenever the running state changes, so a React wrapper can mirror it into state. */
174
106
  onPlayingChange?: (playing: boolean) => void;
175
107
  }
176
- /** Injected side effects — real ones in the hook, fakes in tests. The frame timestamp comes from
108
+ /** Injected side effects, real ones in the hook, fakes in tests. The frame timestamp comes from
177
109
  * the rAF callback argument, so no separate clock is needed. */
178
110
  interface VizLoopDeps {
179
111
  raf: (cb: (now: number) => void) => number;
@@ -206,7 +138,7 @@ interface PausedVizController {
206
138
  restart: () => void;
207
139
  }
208
140
  interface UsePausedVizOptions extends VizLoopOptions {
209
- /** Begin playing on mount. Default false — the mandatory no-autoplay default: a page must never
141
+ /** Begin playing on mount. Default false, the mandatory no-autoplay default: a page must never
210
142
  * burn CPU unattended. Opt in only for a view the user explicitly expects to move on arrival. */
211
143
  autoStart?: boolean;
212
144
  }
@@ -224,7 +156,7 @@ declare function usePausedViz(frame: VizFrame, options?: UsePausedVizOptions): P
224
156
 
225
157
  type CaseKind = 'synthetic' | 'real' | 'uploaded';
226
158
  interface CaseDef {
227
- /** stable slug — the value used in the ?case= deep link and reported to onSelect. */
159
+ /** stable slug, the value used in the ?case= deep link and reported to onSelect. */
228
160
  id: string;
229
161
  /** short human name shown ON the chip next to the id (never demoted to a tooltip-only string). */
230
162
  name: string;
@@ -232,9 +164,9 @@ interface CaseDef {
232
164
  category?: string;
233
165
  /** which source lane this case belongs to. Defaults to 'synthetic'. */
234
166
  kind?: CaseKind;
235
- /** validation anchor (e.g. "published optimum 26,086,899") — surfaced in the chip tooltip. */
167
+ /** validation anchor (e.g. "published optimum 26,086,899"), surfaced in the chip tooltip. */
236
168
  anchor?: string;
237
- /** expected-range hint — surfaced in the chip tooltip. */
169
+ /** expected-range hint, surfaced in the chip tooltip. */
238
170
  expectedBand?: string;
239
171
  disabled?: boolean;
240
172
  }
@@ -272,7 +204,7 @@ interface CaseSelectorProps {
272
204
  selectedId: string;
273
205
  onSelect: (id: string) => void;
274
206
  /** When provided, renders the first-level source control and filters cases to the active lane.
275
- * Controlled — pair with onSourceChange. Omit for a single-lane deck (no source control). */
207
+ * Controlled, pair with onSourceChange. Omit for a single-lane deck (no source control). */
276
208
  source?: CaseKind;
277
209
  onSourceChange?: (k: CaseKind) => void;
278
210
  /** Overrides the default locked-knobs explanation shown for a non-synthetic source. */
@@ -301,7 +233,7 @@ interface CaseSelectorProps {
301
233
  layout?: 'chips' | 'select';
302
234
  }
303
235
  /**
304
- * CaseSelector v2 — the shared source + case picker. Renders (1) an optional first-level source
236
+ * CaseSelector v2, the shared source + case picker. Renders (1) an optional first-level source
305
237
  * segmented control (Synthetic | Real | Uploaded, only the lanes present), (2) a labelled group per
306
238
  * case category, each case a chip showing "ID · name" with its validation anchor as the tooltip and
307
239
  * a source tag, (3) a locked-knobs explanation when a non-synthetic source is active, and (4) a
@@ -317,25 +249,72 @@ interface TabDef {
317
249
  label: ReactNode;
318
250
  content: ReactNode;
319
251
  }
320
- /** Accessible roving-tabindex tab strip (arrow-key nav, aria wiring). Accent-soft pill for active. */
321
- declare function Tabs({ tabs, initial, ariaLabel }: {
252
+ /** At most about six peers, then group by the question (ADR-0071 rule 5). */
253
+ declare const MAX_PEER_TABS = 6;
254
+ interface TabsProps {
322
255
  tabs: TabDef[];
256
+ /** Uncontrolled: the first selected tab. */
323
257
  initial?: string;
258
+ /** Controlled: the selected tab, owned by the app (for example a view held in the URL). */
259
+ value?: string;
260
+ /** Called on every selection, controlled or not. */
261
+ onChange?: (id: string) => void;
324
262
  ariaLabel?: string;
325
- }): react.JSX.Element;
263
+ }
264
+ /**
265
+ * Accessible roving-tabindex tab strip on ONE row (ADR-0071 rule 4): arrow keys, Home and End; only the active panel
266
+ * is rendered, inside an error boundary; a row wider than its box fades the hidden end. Controlled when `value` is
267
+ * given (known shell defect 6). More than six peers is reported (rule 5).
268
+ */
269
+ declare function Tabs({ tabs, initial, value, onChange, ariaLabel }: TabsProps): react.JSX.Element;
326
270
 
327
271
  interface SubTabDef {
328
272
  id: string;
329
273
  label: ReactNode;
330
274
  content: ReactNode;
331
275
  }
332
- /** Second-level tabs nested inside a Tabs panel. Lighter chips; optional vertical left rail. */
333
- declare function SubTabs({ tabs, initial, ariaLabel, orientation, }: {
276
+ interface SubTabsProps {
334
277
  tabs: SubTabDef[];
335
278
  initial?: string;
279
+ /** Controlled selection (known shell defect 6). */
280
+ value?: string;
281
+ onChange?: (id: string) => void;
336
282
  ariaLabel?: string;
337
283
  orientation?: 'horizontal' | 'vertical';
338
- }): react.JSX.Element;
284
+ }
285
+ /**
286
+ * Second-level tabs nested inside a Tabs panel: lighter chips, or a vertical left rail for deep content. Only the
287
+ * active panel is rendered, inside an error boundary; controlled when `value` is given; more than six peers is
288
+ * reported (ADR-0071 rule 5).
289
+ */
290
+ declare function SubTabs({ tabs, initial, value, onChange, ariaLabel, orientation }: SubTabsProps): react.JSX.Element;
291
+
292
+ interface TabGroupDef {
293
+ id: string;
294
+ /** The question the group answers (ADR-0071 rule 5: group by the user's question). */
295
+ label: ReactNode;
296
+ tabs: SubTabDef[];
297
+ }
298
+ interface TabGroupsProps {
299
+ groups: TabGroupDef[];
300
+ /** Controlled selection as `group` and `tab` ids (for example held in the URL). */
301
+ value?: {
302
+ group: string;
303
+ tab?: string;
304
+ };
305
+ onChange?: (value: {
306
+ group: string;
307
+ tab: string;
308
+ }) => void;
309
+ ariaLabel?: string;
310
+ }
311
+ /**
312
+ * Grouped navigation for more than six views: one row of at most six groups, then the sub-tabs of the open group
313
+ * only, each its own single row (ADR-0071 rules 4 and 5). No hover menus: every view is reached by plain clicks on
314
+ * visible tabs, which a pointer and a keyboard can both operate (rule 9; menus that swallowed the first click were a
315
+ * recorded failure).
316
+ */
317
+ declare function TabGroups({ groups, value, onChange, ariaLabel }: TabGroupsProps): react.JSX.Element;
339
318
 
340
319
  /** A left-bordered note box. `honest` = amber (caveats/limits), `strong` = accent, `note` = neutral. */
341
320
  declare function Callout({ variant, title, children, }: {
@@ -344,7 +323,8 @@ declare function Callout({ variant, title, children, }: {
344
323
  children: ReactNode;
345
324
  }): react.JSX.Element;
346
325
 
347
- /** A display equation rendered with KaTeX, with an optional caption. */
326
+ /** A display equation rendered with KaTeX and its caption. ADR-0017 s2 requires every display equation to carry a
327
+ * bilingual caption that defines its symbols; a missing one is reported on the console, which the gate fails on. */
348
328
  declare function Equation({ tex, caption }: {
349
329
  tex: string;
350
330
  caption?: ReactNode;
@@ -363,8 +343,9 @@ declare function Figure({ caption, children }: {
363
343
 
364
344
  interface Citation {
365
345
  id: string;
366
- /** Short inline label, e.g. "Little 1961". */
367
- label: string;
346
+ /** Short inline label, e.g. "Little 1961"; `{ en, es }` when it has words to translate ("Laplante and
347
+ * Staunton" / "Laplante y Staunton"), known shell defect 9. The bibliographic record stays verbatim. */
348
+ label: BiText;
368
349
  /** Full bibliographic string for the reference list. */
369
350
  citation: string;
370
351
  doi?: string;
@@ -385,10 +366,324 @@ declare function Refs({ ids, label }: {
385
366
  ids: string[];
386
367
  label: string;
387
368
  }): react.JSX.Element;
388
- /** Full ordered bibliography. Omit `ids` to render every provided citation. */
369
+ /** Full ordered bibliography. Omit `ids` to render every provided citation.
370
+ * @deprecated ADR-0017 s4 bans a bottom-of-page bibliography; end each section with `Refs` (or use `DocSection`).
371
+ * Kept so existing products still compile; it reports itself on the console, which the gate fails on. */
389
372
  declare function ReferenceList({ ids, heading }: {
390
373
  ids?: string[];
391
374
  heading?: string;
392
375
  }): react.JSX.Element;
393
376
 
394
- export { AppShell, type ArchTab, type ArchitectureConfig, ArchitectureModal, Callout, type CaseDef, type CaseGroup, type CaseKind, CaseSelector, type CaseSelectorProps, type CaseSelectorText, type Citation, CitationsProvider, Cite, Equation, Figure, InlineMath, type Lang, LanguageToggle, type PausedVizController, ReferenceList, Refs, type ShellConfig, type ShellRoute, type SubTabDef, SubTabs, type TabDef, Tabs, type Theme, ThemeToggle, type UsePausedVizOptions, type VizFrame, type VizLoop, type VizLoopDeps, type VizLoopOptions, type WorkbenchRoute, WorkbenchShell, type WorkbenchShellProps, applyTheme, caseKind, caseTooltip, casesInSource, createVizLoop, groupByCategory, readCaseParam, readTheme, sourcesPresent, useLangStore, usePausedViz, useShellLang, useThemeStore, withCaseParam };
377
+ /**
378
+ * A documentation route (Introduction, Methodology, Implementation, Experiments, Benchmark): the page head with
379
+ * its title and lede, then sections. `wide` for a route with a sub-tab rail (ADR-0071 rule 2, amended
380
+ * 2026-09-04); a single prose column keeps the reading width and is centred (ADR-0017 §1).
381
+ */
382
+ declare function DocPage({ title, lede, wide, children }: {
383
+ title: BiText;
384
+ lede: ReactNode;
385
+ wide?: boolean;
386
+ children: ReactNode;
387
+ }): react.JSX.Element;
388
+ interface DocSectionProps {
389
+ title: BiText;
390
+ id?: string;
391
+ /** Citation ids for this section only, rendered as one inline `Refs` row at its end (ADR-0017 §4). */
392
+ refs?: string[];
393
+ /** Required when a section cites nothing, so the absence is a stated choice the gate can read. */
394
+ noRefsReason?: BiText;
395
+ children: ReactNode;
396
+ }
397
+ /** One section of a documentation route, ending in its own references. */
398
+ declare function DocSection({ title, id, refs, noRefsReason, children }: DocSectionProps): react.JSX.Element;
399
+
400
+ /** One section of the control rail. A rail whose content does not fit is SPLIT into sections, one shown at a
401
+ * time, never scrolled (ADR-0071 rule 6: a panel must show its own controls). */
402
+ interface RailSection {
403
+ id: string;
404
+ label: BiText;
405
+ content: ReactNode;
406
+ }
407
+ interface WorkbenchLayoutProps {
408
+ /** The control rail: case and variant pickers, parameters, the live readout. Either one node or sections. */
409
+ rail: ReactNode | RailSection[];
410
+ /** Shown above the rail (and above its sections): the case and variant pickers, which every section shares. */
411
+ railHead?: ReactNode;
412
+ /** The instrument: the views of the selected case. It takes the rest of the viewport (ADR-0071 rules 2, 8). */
413
+ children: ReactNode;
414
+ /** Accessible name of the rail. */
415
+ railLabel?: BiText;
416
+ className?: string;
417
+ }
418
+ /**
419
+ * The App route of a CAOS product: a sized control rail beside the instrument, on the full viewport.
420
+ *
421
+ * Renders `.page-body.wide.caos-wb`: no reading-width cap (a workbench is not prose, ADR-0071 rule 2),
422
+ * `min-width: 0` down the tree so no row pushes the page wider than the screen (rule 3), a rail with an
423
+ * explicit width that never scrolls (rule 6), and an instrument marked `data-instrument` so the measured gate
424
+ * can check that it covers at least half of the viewport (rule 8). Pair with `ShellConfig.contain`.
425
+ */
426
+ declare function WorkbenchLayout({ rail, railHead, children, railLabel, className }: WorkbenchLayoutProps): react.JSX.Element;
427
+
428
+ /** Where a value on screen came from: observed data, a generator, or a published reference. */
429
+ type Provenance = 'real' | 'synthetic' | 'published';
430
+ /** The selection the instrument shows, and the key that identifies it (S10 of the 2026-10-04 requirements). */
431
+ interface WorkbenchState {
432
+ stateKey: string;
433
+ caseId?: string;
434
+ variantId?: string;
435
+ source?: string;
436
+ }
437
+ declare const WorkbenchStateContext: react.Context<WorkbenchState | null>;
438
+ /** The current workbench selection, or null outside a `CaseWorkbench`. */
439
+ declare function useWorkbenchState(): WorkbenchState | null;
440
+ /**
441
+ * A short, deterministic key for a selection: case, variant, source and every control value. A view that shows data
442
+ * computed for another key is stale, and says so (failure classes 8 and 17: views that silently kept an old result
443
+ * after a control changed). FNV-1a over a canonical serialisation.
444
+ */
445
+ declare function makeStateKey(parts: Record<string, unknown>): string;
446
+ /** True when a view's data key is known and differs from the workbench's current key. */
447
+ declare function isStale(dataKey: string | undefined, current: WorkbenchState | null): boolean;
448
+
449
+ /** Where a value on screen was computed (ADR-0057 lanes, ADR-0069 s5). */
450
+ type Lane = 'live' | 'replay' | 'offline';
451
+ /** Badges that state honestly which lane produced what is shown (recomputed live, replayed from a committed
452
+ * artifact, or available only offline) and, when given, whether it rests on real, synthetic or published data
453
+ * (failure class 17 of the 2026-10-04 history: live versus replay honesty). */
454
+ declare function LaneBadge({ lane, provenance, label }: {
455
+ lane: Lane;
456
+ provenance?: Provenance;
457
+ label?: BiText;
458
+ }): react.JSX.Element;
459
+
460
+ /** One configured regime of a case: a full parameter vector, never a cosmetic label (ADR-0016 §9). */
461
+ interface VariantDef {
462
+ id: string;
463
+ label: BiText;
464
+ /** One line: what this regime shows. */
465
+ note?: BiText;
466
+ /** The lane of this variant's result, when it differs from the bar's. */
467
+ lane?: Lane;
468
+ disabled?: boolean;
469
+ }
470
+ interface VariantBarProps {
471
+ variants: VariantDef[];
472
+ activeId: string;
473
+ onSelect: (id: string) => void;
474
+ title?: BiText;
475
+ lane?: Lane;
476
+ }
477
+ /** The regimes of the selected case on ONE scrollable row (ADR-0071 rule 4), the active regime's lane, and the
478
+ * one-line note of what it shows. Selecting a regime must change every view of the case. */
479
+ declare function VariantBar({ variants, activeId, onSelect, title, lane }: VariantBarProps): react.JSX.Element;
480
+
481
+ /** One group of views of the selected case, named for the question the user is asking (ADR-0071 rule 5). */
482
+ interface WorkbenchGroup {
483
+ id: string;
484
+ label: BiText;
485
+ /** Which lane produced this group's views, and what data they rest on. Each view shows its own badges (PlotCard,
486
+ * Readout); these are written on the panel as `data-lane` and `data-provenance`. */
487
+ lane?: Lane;
488
+ provenance?: Provenance;
489
+ content: ReactNode;
490
+ }
491
+ interface CaseWorkbenchProps {
492
+ /** The selected case (written as `data-case`). */
493
+ caseId: string;
494
+ /** The case picker, shown first in the rail. Omit only for a workbench with a single case. */
495
+ cases?: CaseSelectorProps;
496
+ /** The data source of the case (for example `real` or `synthetic`), written as `data-source`. */
497
+ source?: string;
498
+ /** Every control value of the instrument; with the case and variant it forms the state key (S10). */
499
+ controls?: Record<string, unknown>;
500
+ /** A workbench whose views have no live control declares it, so the reactivity gate does not expect one. */
501
+ replayOnly?: boolean;
502
+ /** The regimes of the case, shown in the rail under the case picker; omit only for a case with no meaningful
503
+ * parametric or temporal family. */
504
+ variants?: VariantBarProps;
505
+ /** The rest of the rail: the case's parameters (Knob, ChipGroup) and its live values (Readout, Gauge, Verdict),
506
+ * as one node or as sections shown one at a time (ADR-0071 rule 6). */
507
+ rail?: ReactNode | RailSection[];
508
+ railLabel?: BiText;
509
+ /** The instrument: the case's own question groups (for example Model, Validation, Impact, Findings). */
510
+ groups: WorkbenchGroup[];
511
+ /** The comparison across the case's variants (one metric per regime; selecting one loads it). */
512
+ compare?: {
513
+ label?: BiText;
514
+ lane?: Lane;
515
+ provenance?: Provenance;
516
+ content: ReactNode;
517
+ };
518
+ /** The deep write-up of the case: problem, variables, equations, assumptions, what each variant shows, how to read
519
+ * the views (ADR-0016 s9.B). Always present. */
520
+ context: {
521
+ label?: BiText;
522
+ content: ReactNode;
523
+ };
524
+ /** Controlled group selection (for example held in the URL). */
525
+ group?: string;
526
+ onGroupChange?: (id: string) => void;
527
+ ariaLabel?: BiText;
528
+ }
529
+ declare const MAX_WORKBENCH_GROUPS = 6;
530
+ /**
531
+ * The App route of a product (ADR-0016 s9, ADR-0071 as amended 2026-10-04): the control rail beside the instrument on
532
+ * the full viewport. The rail holds, in order, the case picker, the variants of the case, and the product's
533
+ * parameters and live values; the instrument holds ONE row of at most six question groups in a fixed order (the
534
+ * instrument groups, the variant comparison, the context write-up) and the open group's views, which take the
535
+ * height that row leaves. Pickers, parameters and numbers sit in the rail so the drawing keeps the instrument
536
+ * (rule 8: at least half the viewport is drawn).
537
+ *
538
+ * It provides the selection's state key to every `PlotCard` and `Readout` in the rail and the instrument, so a view
539
+ * still showing data for an earlier selection says so, and it writes `data-case`, `data-variant`, `data-source` and
540
+ * `data-state-key` on the instrument so the gate can check that the instrument shows what was asked for.
541
+ */
542
+ declare function CaseWorkbench(props: CaseWorkbenchProps): react.JSX.Element;
543
+
544
+ type Tone = 'good' | 'warn' | 'bad' | 'accent' | 'neutral';
545
+ interface ReadoutItem {
546
+ label: BiText;
547
+ /** A number, or `null`/`undefined` when it is not available. NaN is reported and shown as not available. */
548
+ value?: number | null;
549
+ /** A categorical value shown as text instead of a number. */
550
+ text?: BiText;
551
+ /** The unit, bilingual when it is a word (people / personas), a plain string when it is a symbol (d, %). Required
552
+ * unless `unitless` is set (failure class 23: readouts without units). */
553
+ unit?: BiText;
554
+ unitless?: boolean;
555
+ /** What the quantity is, in one line (ADR-0017 s3.8: every variable is self-explanatory). */
556
+ hint?: BiText;
557
+ /** An explicit tone, or a tone derived from `better` and the `good`/`bad` thresholds, never from the sign. */
558
+ tone?: Tone;
559
+ better?: 'higher' | 'lower';
560
+ good?: number;
561
+ bad?: number;
562
+ format?: FormatOptions;
563
+ }
564
+ interface ReadoutProps {
565
+ items: ReadoutItem[];
566
+ title?: BiText;
567
+ /** Which lane computed these values, and what data they rest on (required, failure class 17). */
568
+ lane: Lane;
569
+ provenance: Provenance;
570
+ /** The workbench state key these values were computed for; differing from the current key shows them as stale. */
571
+ dataKey?: string;
572
+ }
573
+ /** The live readout: labelled values with units that update with every control (ADR-0017 s3.3). */
574
+ declare function Readout({ items, title, lane, provenance, dataKey }: ReadoutProps): react.JSX.Element;
575
+ interface GaugeZone {
576
+ from: number;
577
+ to: number;
578
+ tone: Tone;
579
+ label: BiText;
580
+ }
581
+ interface GaugeProps {
582
+ title: BiText;
583
+ value: number | null | undefined;
584
+ min: number;
585
+ max: number;
586
+ zones: GaugeZone[];
587
+ unit?: BiText;
588
+ unitless?: boolean;
589
+ format?: FormatOptions;
590
+ }
591
+ /** A value against labelled zones on a linear scale, the needle at the value (ADR-0017 s3.3). */
592
+ declare function Gauge({ title, value, min, max, zones, unit, unitless, format }: GaugeProps): react.JSX.Element;
593
+ type VerdictProps = {
594
+ title: BiText;
595
+ /** Title and verdict on one row, for a verdict that sits above a drawing and must not take its height. */
596
+ compact?: boolean;
597
+ children?: ReactNode;
598
+ } & ({
599
+ code: string;
600
+ messages: Record<string, {
601
+ tone: Tone;
602
+ text: BiText;
603
+ }>;
604
+ } | {
605
+ tone: Tone;
606
+ verdict: BiText;
607
+ });
608
+ /** The verdict panel: the current diagnosis in one line, coloured by its tone. A status code goes through a
609
+ * `messages` map, and an unmapped code is reported (failure class 23: raw status codes on screen). */
610
+ declare function Verdict(props: VerdictProps): react.JSX.Element;
611
+ interface PlotCardProps {
612
+ title: BiText;
613
+ /** Which lane produced this view and what data it rests on (required, failure class 17). */
614
+ lane: Lane;
615
+ provenance: Provenance;
616
+ /** The workbench state key the data was computed for; a different key shows the view as stale. */
617
+ dataKey?: string;
618
+ actions?: ReactNode;
619
+ note?: BiText;
620
+ /** The view takes the height its panel leaves (a chart or stage that should fill the instrument, ADR-0071 rule 8);
621
+ * views marked `fill` in one panel share that height. A `Stage` or `UPlotChart height="fill"` inside it gets a
622
+ * real size from it instead of a guessed one (failure class 4). */
623
+ fill?: boolean;
624
+ children: ReactNode;
625
+ }
626
+ /** A framed view with a title row, its lane and provenance, the unit every chart or table sits in. Its body is
627
+ * protected by an error boundary, so one failing chart never blanks the page (failure class 20). */
628
+ declare function PlotCard({ title, lane, provenance, dataKey, actions, note, fill, children }: PlotCardProps): react.JSX.Element;
629
+
630
+ interface KnobProps {
631
+ /** Stable id, written as `data-control` so the gate can enumerate and move every control. */
632
+ id: string;
633
+ label: BiText;
634
+ value: number;
635
+ min: number;
636
+ max: number;
637
+ step: number;
638
+ unit?: BiText;
639
+ /** What the parameter is (ADR-0017 s3.8). */
640
+ hint?: BiText;
641
+ format?: FormatOptions;
642
+ disabled?: boolean;
643
+ onChange: (value: number) => void;
644
+ }
645
+ /**
646
+ * A continuous parameter of the case (ADR-0017 s3.2): a labelled slider with its value and unit shown. Registered as
647
+ * a control (`data-control`), so the gate can change it and require that some view's state key changes (failure
648
+ * class 8: controls that change nothing).
649
+ */
650
+ declare function Knob({ id, label, value, min, max, step, unit, hint, format, disabled, onChange }: KnobProps): react.JSX.Element;
651
+ interface ChipOption {
652
+ id: string;
653
+ label: BiText;
654
+ hint?: BiText;
655
+ disabled?: boolean;
656
+ }
657
+ interface ChipGroupProps {
658
+ id: string;
659
+ label: BiText;
660
+ options: ChipOption[];
661
+ value: string;
662
+ onChange: (id: string) => void;
663
+ }
664
+ /** A small one-of-N choice as chips on one row, registered as a control. For a categorised or long list use the
665
+ * `CaseSelector` select mode instead (ADR-0071 rule 7). */
666
+ declare function ChipGroup({ id, label, options, value, onChange }: ChipGroupProps): react.JSX.Element;
667
+
668
+ interface StageSize {
669
+ width: number;
670
+ height: number;
671
+ }
672
+ /**
673
+ * The size of an element, observed through a callback ref and a ResizeObserver, so a canvas is sized from its real
674
+ * box and never from viewport units, and is not built before it has a size (failure class 4: instruments drawn at
675
+ * zero or at a guessed size).
676
+ */
677
+ declare function useStageSize(): [(el: HTMLElement | null) => void, StageSize];
678
+ /**
679
+ * The drawing surface of an instrument. It renders its children only once it has a non-zero size, passes that size
680
+ * down, and declares what it drew (`data-drawn`, `data-width`, `data-height`) so the gate measures the drawing, not
681
+ * the host. A stage that never gets a size is reported.
682
+ */
683
+ declare function Stage({ label, children, className }: {
684
+ label: BiText;
685
+ children: (size: StageSize) => ReactNode;
686
+ className?: string;
687
+ }): react.JSX.Element;
688
+
689
+ export { BiText, Callout, type CaseDef, type CaseGroup, type CaseKind, CaseSelector, type CaseSelectorProps, type CaseSelectorText, CaseWorkbench, type CaseWorkbenchProps, ChipGroup, type ChipGroupProps, type ChipOption, type Citation, CitationsProvider, Cite, DocPage, DocSection, type DocSectionProps, Equation, Figure, FocusShell, type FocusShellProps, FormatOptions, Gauge, type GaugeProps, type GaugeZone, InlineMath, Knob, type KnobProps, type Lane, LaneBadge, Lang, LanguageToggle, MAX_PEER_TABS, MAX_WORKBENCH_GROUPS, PanelBoundary, type PausedVizController, PlotCard, type PlotCardProps, type Provenance, type RailSection, Readout, type ReadoutItem, type ReadoutProps, ReferenceList, Refs, Stage, type StageSize, type SubTabDef, SubTabs, type SubTabsProps, type TabDef, type TabGroupDef, TabGroups, type TabGroupsProps, Tabs, type TabsProps, type Theme, ThemeToggle, type Tone, type UsePausedVizOptions, VariantBar, type VariantBarProps, type VariantDef, Verdict, type VerdictProps, type VizFrame, type VizLoop, type VizLoopDeps, type VizLoopOptions, type WorkbenchGroup, WorkbenchLayout, type WorkbenchLayoutProps, type WorkbenchRoute, WorkbenchShell, type WorkbenchShellProps, type WorkbenchState, WorkbenchStateContext, applyTheme, caseKind, caseTooltip, casesInSource, createVizLoop, groupByCategory, isStale, makeStateKey, readCaseParam, readTheme, sourcesPresent, usePausedViz, useStageSize, useThemeStore, useWorkbenchState, withCaseParam };