@validation-os/dashboard 0.7.1 → 0.8.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,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { Collection, AnyRecord, Relation } from '@validation-os/core';
3
+ import { Collection, AnyRecord, Result, Relation, BarLine } from '@validation-os/core';
4
4
  import { PortfolioProgress, StageExperimentInput, MoveKind, NextMoveInput, BeliefStage, JourneyEvent, NextMove, JourneyEventKind, ConfSign, StageKey, Progress, MoverKind, TrajectoryPoint } from '@validation-os/core/derivation';
5
5
  export { StageKey } from '@validation-os/core/derivation';
6
6
 
@@ -384,6 +384,60 @@ interface SidebarNavProps {
384
384
  */
385
385
  declare function SidebarNav({ route, onNavigate, counts, needsHuman, registers, }: SidebarNavProps): react.JSX.Element;
386
386
 
387
+ /**
388
+ * The "Connect Claude Code" command composer (OPS-1349). A person signed into
389
+ * the dashboard mints a personal token and gets back ONE ready-to-paste command
390
+ * that points their own Claude Code at this hosted register through the
391
+ * `remote-api` connector. Everything environmental — the API URL, the env-var
392
+ * name — is baked in here; the person supplies only their minted token.
393
+ *
394
+ * Pure and deterministic so it can be unit-tested exactly (prior art:
395
+ * `route.ts`). The config keys it writes (`api_base_url`, `token_env`) are the
396
+ * `remote-api` connector's contract, fixed by the spec — so this does not
397
+ * depend on the connector doc or the setup wizard shipping first.
398
+ */
399
+ /** The env var the `remote-api` connector reads the bearer token from. */
400
+ declare const DEFAULT_TOKEN_ENV = "VALIDATION_OS_TOKEN";
401
+ interface ConnectCommandInput {
402
+ /** The personal bearer token the deployment minted for the signed-in user. */
403
+ token: string;
404
+ /** The hosted API root, baked in by the deployment. */
405
+ apiBaseUrl: string;
406
+ /** Override the token env-var name; defaults to {@link DEFAULT_TOKEN_ENV}. */
407
+ tokenEnv?: string;
408
+ }
409
+ /**
410
+ * Build the paste command: export the token into its env var, then write a
411
+ * `remote-api` `validation-os.config.yaml` at the workspace root. Running it
412
+ * leaves the workspace ready for `/setup-validation-os` to confirm, or for the
413
+ * skills to use directly.
414
+ */
415
+ declare function composeConnectCommand(input: ConnectCommandInput): string;
416
+
417
+ /**
418
+ * The "Connect Claude Code" page (OPS-1349). A person already signed into the
419
+ * dashboard mints a personal token and gets one ready-to-paste command that
420
+ * points their own Claude Code at this hosted register.
421
+ *
422
+ * Auth-vendor-free by construction: token minting is INJECTED (`mintToken`), so
423
+ * this package never imports Clerk — the deployment wires a mint function that
424
+ * creates the signed-in user's personal API key and hands back the token
425
+ * (mirrors how the API's `authenticate` is injected). The command itself is the
426
+ * pure `composeConnectCommand`; this component is only the chrome around it.
427
+ */
428
+ interface ConnectClaudeCodeProps {
429
+ /** The hosted API root, baked into the emitted command. */
430
+ apiBaseUrl: string;
431
+ /** Override the token env-var name written into the config. */
432
+ tokenEnv?: string;
433
+ /**
434
+ * Mint a personal bearer token for the signed-in user. Injected by the
435
+ * deployment (e.g. create a Clerk API key whose subject is the user's ID).
436
+ */
437
+ mintToken: () => Promise<string>;
438
+ }
439
+ declare function ConnectClaudeCode({ apiBaseUrl, tokenEnv, mintToken, }: ConnectClaudeCodeProps): react.JSX.Element;
440
+
387
441
  interface RecordPageProps {
388
442
  /** The record being drilled into (`#record/<id>`). */
389
443
  recordId: string;
@@ -574,6 +628,75 @@ interface MovePresentation {
574
628
  /** The front-door copy + step-in modality for one act. */
575
629
  declare function movePresentation(kind: MoveKind): MovePresentation;
576
630
 
631
+ /**
632
+ * The per-belief cycles view-model (OPS-1347) — the validation loop's rounds,
633
+ * not just the belief's flat dated event log (`journey.ts`) or the
634
+ * strongest-push-first attribution list (`understanding.ts`). Grounded
635
+ * directly in the registry model (`registry-schema.md`):
636
+ *
637
+ * - a **cycle** is one round of the loop — an Experiment designed against
638
+ * this belief, the (dated) Readings it produced, and the per-belief
639
+ * bar-line verdict it settles at closure. That is literally
640
+ * "experiment → readings → re-score", the round the operator asked to
641
+ * see, and it is the only unit the data model pre-registers as a trial:
642
+ * the bar line's `We're right if` / `We're wrong if` are written *before*
643
+ * any Reading exists, so its verdict is a real round boundary, not one we
644
+ * invent;
645
+ * - readings with **no** Experiment (bare/found — a Market-rung commitment
646
+ * or desk research dropped in directly) carry no pre-registered bar, so
647
+ * they are not a "round" in that strict sense. They still move the
648
+ * number, so they collect into one closing, kind-`"direct"` entry rather
649
+ * than disappearing from the picture.
650
+ *
651
+ * Ordered chronologically (oldest first) — the point of this view is watching
652
+ * the belief move round by round, which `understanding.ts`'s magnitude-first
653
+ * ranking deliberately does not show.
654
+ *
655
+ * No new maths: each round's push on Confidence is read off
656
+ * `confidenceAttribution`'s per-experiment mover — the same decomposition
657
+ * `understanding.ts` shows, just regrouped by time instead of by size, so a
658
+ * cycle's push and the drawer's push always agree. Computed fresh on read,
659
+ * out of the OPS-1251 on-write recompute.
660
+ */
661
+
662
+ /** The round's key: the experiment id, or `"direct"` for the bare-reading bucket. */
663
+ declare const DIRECT_CYCLE_KEY = "direct";
664
+ /** One reading's place inside a round, reduced to what the timeline draws. */
665
+ interface CycleReadingView {
666
+ id: string;
667
+ date: string | null;
668
+ result: Result | null;
669
+ }
670
+ /** One round of the loop. */
671
+ interface CycleView {
672
+ key: string;
673
+ kind: "experiment" | "direct";
674
+ /** The experiment's title; null for the direct bucket. */
675
+ title: string | null;
676
+ /** The experiment's lifecycle status; null for the direct bucket. */
677
+ status: string | null;
678
+ /** The round's anchor date: the experiment's own `Date`, or (falling back)
679
+ * its earliest reading's date. Null only when neither is known. */
680
+ date: string | null;
681
+ /** This belief's bar-line verdict, once judged; null pre-closure and always
682
+ * null for the direct bucket (it carries no bar line). */
683
+ barVerdict: Result | null;
684
+ /** The round's readings, oldest first. */
685
+ readings: CycleReadingView[];
686
+ /** Signed push on Confidence this round contributed (sums, across every
687
+ * round, to the belief's Confidence). */
688
+ contribution: number;
689
+ /** |contribution| — how hard this round moved the number. */
690
+ magnitude: number;
691
+ }
692
+ /**
693
+ * Build one belief's cycles from its readings and the Experiments register.
694
+ * Both arrays are the raw records (Title-cased fields) — the same shape
695
+ * `understanding.ts`/`journey.ts` take — so a caller loading the registers
696
+ * once can hand them straight through.
697
+ */
698
+ declare function buildCycles(assumptionId: string, readings: AnyRecord[], experiments: AnyRecord[]): CycleView[];
699
+
577
700
  /** A journey event with its front-door copy attached. */
578
701
  interface JourneyEventView extends JourneyEvent {
579
702
  /** Plain-language label the story renders. */
@@ -587,6 +710,9 @@ interface JourneyView {
587
710
  stage: BeliefStage;
588
711
  /** The story: the belief's life, oldest first, `now` last. */
589
712
  events: JourneyEventView[];
713
+ /** The loop's rounds, oldest first (OPS-1347) — the same history the story
714
+ * tells, regrouped by Experiment run instead of by dated event. */
715
+ cycles: CycleView[];
590
716
  /** The ranked next move for this belief; null once it is resolved. */
591
717
  nextMove: NextMove | null;
592
718
  /** Killed (Invalidated) / moot / null — the drill-in's terminal state. */
@@ -626,7 +752,9 @@ declare function buildJourney(assumptionId: string, records: NextMoveRecords, no
626
752
  * rail is pure status: nothing to act on there;
627
753
  * - it **expands into the chronological story** through the record page's
628
754
  * existing "Why?" reveal idiom — the dated event log (bet → score →
629
- * experiment → readings → confidence-cross → now), ending in the ranked
755
+ * experiment → readings → confidence-cross → now), the same history
756
+ * regrouped into **rounds** (OPS-1347 — one card per Experiment run, plus
757
+ * any bare/direct evidence, oldest first), and ending in the ranked
630
758
  * next-move card (OPS-1292).
631
759
  *
632
760
  * **Step-in is story-only** (OPS-1297): the next-move card and the per-event
@@ -839,6 +967,10 @@ interface FieldEditor {
839
967
  options?: readonly string[];
840
968
  /** An empty input clears the field to `null` rather than "". */
841
969
  nullable?: boolean;
970
+ /** For `number`: the inclusive range a hand-scored value must sit in
971
+ * (e.g. Impact 0–100, `registry-schema.md`). Omitted = unbounded. */
972
+ min?: number;
973
+ max?: number;
842
974
  }
843
975
  /** The fields a register lets you edit, in render order. */
844
976
  declare function editableFields(register: Collection): FieldEditor[];
@@ -865,15 +997,21 @@ declare function hasEdits(register: Collection, original: AnyRecord, draft: Draf
865
997
  * surfaces editing one register should never drift into two different forms.
866
998
  * Derived numbers are never here — they are computed on write (OPS-1251).
867
999
  */
868
- declare function EditFields({ register, draft, onField, }: {
1000
+ declare function EditFields({ register, draft, errors, onField, }: {
869
1001
  register: Collection;
870
1002
  draft: Draft;
1003
+ /** Per-field validation messages (keyed by field), e.g. Impact out of
1004
+ * 0–100 — `edit.ts`'s `draftErrors`. Absent/empty = every field is valid. */
1005
+ errors?: Record<string, string>;
871
1006
  onField: (key: string, value: string) => void;
872
1007
  }): react.JSX.Element;
873
1008
  /** One editable field, as the control its `kind` calls for. */
874
- declare function FieldInput({ field, value, onChange, }: {
1009
+ declare function FieldInput({ field, value, error, onChange, }: {
875
1010
  field: FieldEditor;
876
1011
  value: string | undefined;
1012
+ /** A validation message shown under the control, e.g. "Impact must be at
1013
+ * most 100." — never blocks typing, only the Save the caller gates on it. */
1014
+ error?: string;
877
1015
  onChange: (value: string) => void;
878
1016
  }): react.JSX.Element;
879
1017
 
@@ -913,6 +1051,117 @@ interface RegisterTableProps {
913
1051
  */
914
1052
  declare function RegisterTable({ register, records, onRowClick, selectedId, }: RegisterTableProps): react.JSX.Element;
915
1053
 
1054
+ /**
1055
+ * The record-page view-model (OPS-1286) — the pure join behind the canonical
1056
+ * full record page. Given a record and the related registers, it computes the
1057
+ * header lane/queue pills (all derived), the leading-score meters per register,
1058
+ * the genuine human-input free-text remainder, and the backlink panels grouped
1059
+ * by relation (each row carrying a glance-readable score chip, an empty relation
1060
+ * kept as a "none yet" panel rather than dropped). DOM-free and unit-tested at
1061
+ * this seam; `RecordPage` renders what it returns and the understanding layer
1062
+ * supplies the "Why?" attribution unchanged.
1063
+ */
1064
+
1065
+ /** The registers a record page reads to resolve its relations. All optional —
1066
+ * a panel whose register is absent simply resolves to "none yet". */
1067
+ interface RelatedSet {
1068
+ assumptions?: AnyRecord[];
1069
+ experiments?: AnyRecord[];
1070
+ readings?: AnyRecord[];
1071
+ decisions?: AnyRecord[];
1072
+ glossary?: AnyRecord[];
1073
+ }
1074
+ interface RecordPageOptions {
1075
+ /** ISO date "now" for the Overdue pill; omitted → nothing reads overdue. */
1076
+ asOf?: string;
1077
+ }
1078
+ /** A header pill: a short derived label toned by meaning. */
1079
+ interface Pill {
1080
+ label: string;
1081
+ tone: Tone;
1082
+ }
1083
+ /**
1084
+ * The derived lane/queue pills for a record header (story 2). Every register
1085
+ * leads with its Status pill; assumptions add Moot / Kill lane / Testing,
1086
+ * experiments add Concluded / Overdue, decisions mark Standing. All derived —
1087
+ * none is a stored field.
1088
+ */
1089
+ declare function headerPills(register: Collection, record: AnyRecord, related?: RelatedSet, options?: RecordPageOptions): Pill[];
1090
+ /** A leading-score meter. `bar`/`signed` carry a number in a min…max domain;
1091
+ * `pill` carries a categorical value shown as a toned pill. */
1092
+ interface Meter {
1093
+ key: string;
1094
+ label: string;
1095
+ kind: "bar" | "signed" | "pill";
1096
+ /** Number (bar/signed) or category (pill); null when the record has no value. */
1097
+ value: number | string | null;
1098
+ tone: Tone;
1099
+ /** For bar/signed: the domain the fill maps onto. */
1100
+ min?: number;
1101
+ max?: number;
1102
+ /** True when a "Why?" attribution is available (Confidence only). */
1103
+ hasWhy?: boolean;
1104
+ }
1105
+ /**
1106
+ * The register's leading scores as meters (story 4). Reads only what the
1107
+ * migrated schema actually stores — a derived number becomes a bar/signed meter,
1108
+ * a categorical leading field becomes a pill; nothing is invented where the
1109
+ * schema carries no number. Confidence is flagged `hasWhy` — the understanding
1110
+ * layer decomposes it.
1111
+ */
1112
+ declare function leadingMeters(register: Collection, record: AnyRecord): Meter[];
1113
+ /** The genuine human-input free-text remainder — clearly separated from what
1114
+ * the system computed. Auto-linked against the glossary at render time. */
1115
+ interface HumanText {
1116
+ key: string;
1117
+ label: string;
1118
+ text: string;
1119
+ }
1120
+ /** The human-input fields a register carries text for (empty ones dropped). */
1121
+ declare function humanInputFields(register: Collection, record: AnyRecord): HumanText[];
1122
+ /** A glance-readable score chip for a linked record (story 9). */
1123
+ interface ScoreChip {
1124
+ label: string;
1125
+ value: string;
1126
+ tone: Tone;
1127
+ }
1128
+ interface BacklinkItem {
1129
+ id: string;
1130
+ register: Collection;
1131
+ title: string;
1132
+ chip: ScoreChip;
1133
+ }
1134
+ /** A relation panel — the inbound/outbound edges of one relation, grouped and
1135
+ * labelled. Kept even when empty (story 10). */
1136
+ interface RelationPanel {
1137
+ id: string;
1138
+ label: string;
1139
+ register: Collection;
1140
+ items: BacklinkItem[];
1141
+ }
1142
+ /** The linked record's headline score, glance-readable (story 9). */
1143
+ declare function scoreChip(register: Collection, record: AnyRecord): ScoreChip;
1144
+ /** The backlink panels for a record, grouped by relation, each row carrying a
1145
+ * score chip. Empty relations are kept (items: []) so a missing connection reads
1146
+ * as "none yet", not an absent section (story 8/10). */
1147
+ declare function backlinkPanels(register: Collection, record: AnyRecord, related?: RelatedSet): RelationPanel[];
1148
+ type RecordTabId = "overview" | "evidence" | "connections" | "history";
1149
+ interface RecordPageModel {
1150
+ register: Collection;
1151
+ title: string;
1152
+ pills: Pill[];
1153
+ meters: Meter[];
1154
+ humanText: HumanText[];
1155
+ panels: RelationPanel[];
1156
+ /** The tabs this record shows, in order (story 3 + 11). */
1157
+ tabs: RecordTabId[];
1158
+ /** True when the belief journey drill-in can mount here (story 13). */
1159
+ hasJourney: boolean;
1160
+ }
1161
+ /** Assemble the whole record-page model. Pure: same record + related always
1162
+ * gives the same page. */
1163
+ declare function buildRecordPage(register: Collection, record: AnyRecord, related?: RelatedSet, options?: RecordPageOptions): RecordPageModel;
1164
+
916
1165
  interface RecordDrawerProps {
917
1166
  register: Collection;
918
1167
  /** The open record, or null while loading / when nothing is selected. */
@@ -931,6 +1180,17 @@ interface RecordDrawerProps {
931
1180
  /** Open this record's canonical full page (story 12). When set, the header
932
1181
  * shows a "Full page" link; the drawer stays the quick read/edit peek. */
933
1182
  onOpenFull?: () => void;
1183
+ /**
1184
+ * Open *any* linked record's full page (OPS-1345) — the same navigation the
1185
+ * canonical record page's Connections tab uses, reused here so a relation
1186
+ * field or a bar line's assumption is a click, not inert text. Falls back
1187
+ * to plain (unclickable) titles when omitted.
1188
+ */
1189
+ onOpenRecord?: (id: string) => void;
1190
+ /** The other registers' rows, loaded so a relation field / bar line can
1191
+ * resolve to a title instead of a raw id (OPS-1345). Omitted relations
1192
+ * simply fall back to showing the id. */
1193
+ related?: RelatedSet;
934
1194
  /** Extra content below the fields in read mode — e.g. the relation editor. */
935
1195
  children?: ReactNode;
936
1196
  }
@@ -945,7 +1205,7 @@ interface RecordDrawerProps {
945
1205
  * the relation editor (`children`). Chrome is shared via `DrawerShell`; styled
946
1206
  * with the package's own token sheet, no host Tailwind.
947
1207
  */
948
- declare function RecordDrawer({ register, record, loading, error, open, onClose, basePath, onChanged, onOpenFull, children, }: RecordDrawerProps): react.JSX.Element;
1208
+ declare function RecordDrawer({ register, record, loading, error, open, onClose, basePath, onChanged, onOpenFull, onOpenRecord, related, children, }: RecordDrawerProps): react.JSX.Element;
949
1209
 
950
1210
  interface RecordFormProps {
951
1211
  register: Collection;
@@ -1418,115 +1678,62 @@ interface GlossaryTextProps {
1418
1678
  declare function GlossaryText({ text, terms, selfId, onOpenTerm, }: GlossaryTextProps): react.JSX.Element;
1419
1679
 
1420
1680
  /**
1421
- * The record-page view-model (OPS-1286) — the pure join behind the canonical
1422
- * full record page. Given a record and the related registers, it computes the
1423
- * header lane/queue pills (all derived), the leading-score meters per register,
1424
- * the genuine human-input free-text remainder, and the backlink panels grouped
1425
- * by relation (each row carrying a glance-readable score chip, an empty relation
1426
- * kept as a "none yet" panel rather than dropped). DOM-free and unit-tested at
1427
- * this seam; `RecordPage` renders what it returns and the understanding layer
1428
- * supplies the "Why?" attribution unchanged.
1681
+ * The record drawer's generic field list (OPS-1345) — the pure view-model
1682
+ * behind the "everything else" rows a record carries beyond its meters/
1683
+ * human-text/panels. `RecordDrawer` iterates a record's own keys (skipping
1684
+ * provenance/meta), and this module decides how each one reads: a relation
1685
+ * id/id-list (`Depends on`, `Readings`, `Assumption`…) resolves to the linked
1686
+ * record's title, `Owner`/`Agreed by` (stored as dashboard-user objects,
1687
+ * `{id, name}` `connectors/nosql-schema.md`) reads as name(s) only, and
1688
+ * `barLines` (the embedded per-belief pre-registration on an experiment)
1689
+ * reads as structured rows with a linked assumption title — never the raw
1690
+ * stored JSON or an internal id, per this project's "explain visually, hide
1691
+ * complexity" bias. Everything else still formats through `columns.ts`.
1692
+ * DOM-free and unit-tested at this seam; `record-drawer.tsx` renders what it
1693
+ * returns.
1429
1694
  */
1430
1695
 
1431
- /** The registers a record page reads to resolve its relations. All optional —
1432
- * a panel whose register is absent simply resolves to "none yet". */
1433
- interface RelatedSet {
1434
- assumptions?: AnyRecord[];
1435
- experiments?: AnyRecord[];
1436
- readings?: AnyRecord[];
1437
- decisions?: AnyRecord[];
1438
- glossary?: AnyRecord[];
1439
- }
1440
- interface RecordPageOptions {
1441
- /** ISO date "now" for the Overdue pill; omitted → nothing reads overdue. */
1442
- asOf?: string;
1443
- }
1444
- /** A header pill: a short derived label toned by meaning. */
1445
- interface Pill {
1446
- label: string;
1447
- tone: Tone;
1448
- }
1449
- /**
1450
- * The derived lane/queue pills for a record header (story 2). Every register
1451
- * leads with its Status pill; assumptions add Moot / Kill lane / Testing,
1452
- * experiments add Concluded / Overdue, decisions mark Standing. All derived —
1453
- * none is a stored field.
1454
- */
1455
- declare function headerPills(register: Collection, record: AnyRecord, related?: RelatedSet, options?: RecordPageOptions): Pill[];
1456
- /** A leading-score meter. `bar`/`signed` carry a number in a min…max domain;
1457
- * `pill` carries a categorical value shown as a toned pill. */
1458
- interface Meter {
1459
- key: string;
1460
- label: string;
1461
- kind: "bar" | "signed" | "pill";
1462
- /** Number (bar/signed) or category (pill); null when the record has no value. */
1463
- value: number | string | null;
1464
- tone: Tone;
1465
- /** For bar/signed: the domain the fill maps onto. */
1466
- min?: number;
1467
- max?: number;
1468
- /** True when a "Why?" attribution is available (Confidence only). */
1469
- hasWhy?: boolean;
1470
- }
1471
- /**
1472
- * The register's leading scores as meters (story 4). Reads only what the
1473
- * migrated schema actually stores — a derived number becomes a bar/signed meter,
1474
- * a categorical leading field becomes a pill; nothing is invented where the
1475
- * schema carries no number. Confidence is flagged `hasWhy` — the understanding
1476
- * layer decomposes it.
1477
- */
1478
- declare function leadingMeters(register: Collection, record: AnyRecord): Meter[];
1479
- /** The genuine human-input free-text remainder — clearly separated from what
1480
- * the system computed. Auto-linked against the glossary at render time. */
1481
- interface HumanText {
1482
- key: string;
1483
- label: string;
1484
- text: string;
1485
- }
1486
- /** The human-input fields a register carries text for (empty ones dropped). */
1487
- declare function humanInputFields(register: Collection, record: AnyRecord): HumanText[];
1488
- /** A glance-readable score chip for a linked record (story 9). */
1489
- interface ScoreChip {
1490
- label: string;
1491
- value: string;
1492
- tone: Tone;
1493
- }
1494
- interface BacklinkItem {
1696
+ type DetailRowKind = "text" | "relation" | "owner" | "bar-lines";
1697
+ /** One resolved relation target a title to show, an id to navigate to. */
1698
+ interface DetailRelationItem {
1495
1699
  id: string;
1496
1700
  register: Collection;
1497
1701
  title: string;
1498
- chip: ScoreChip;
1499
1702
  }
1500
- /** A relation panel the inbound/outbound edges of one relation, grouped and
1501
- * labelled. Kept even when empty (story 10). */
1502
- interface RelationPanel {
1503
- id: string;
1504
- label: string;
1505
- register: Collection;
1506
- items: BacklinkItem[];
1507
- }
1508
- /** The linked record's headline score, glance-readable (story 9). */
1509
- declare function scoreChip(register: Collection, record: AnyRecord): ScoreChip;
1510
- /** The backlink panels for a record, grouped by relation, each row carrying a
1511
- * score chip. Empty relations are kept (items: []) so a missing connection reads
1512
- * as "none yet", not an absent section (story 8/10). */
1513
- declare function backlinkPanels(register: Collection, record: AnyRecord, related?: RelatedSet): RelationPanel[];
1514
- type RecordTabId = "overview" | "evidence" | "connections" | "history";
1515
- interface RecordPageModel {
1516
- register: Collection;
1517
- title: string;
1518
- pills: Pill[];
1519
- meters: Meter[];
1520
- humanText: HumanText[];
1521
- panels: RelationPanel[];
1522
- /** The tabs this record shows, in order (story 3 + 11). */
1523
- tabs: RecordTabId[];
1524
- /** True when the belief journey drill-in can mount here (story 13). */
1525
- hasJourney: boolean;
1703
+ /** One bar line, human-readable: the belief it tests resolved to a title. */
1704
+ interface ResolvedBarLine {
1705
+ rightIf: string;
1706
+ wrongIf: string | null;
1707
+ plannedRung: string;
1708
+ barVerdict: string | null;
1709
+ assumption: DetailRelationItem | null;
1526
1710
  }
1527
- /** Assemble the whole record-page model. Pure: same record + related always
1528
- * gives the same page. */
1529
- declare function buildRecordPage(register: Collection, record: AnyRecord, related?: RelatedSet, options?: RecordPageOptions): RecordPageModel;
1711
+ interface DetailRow {
1712
+ key: string;
1713
+ label: string;
1714
+ kind: DetailRowKind;
1715
+ /** `kind: "text"` — the formatted display string. */
1716
+ text?: string;
1717
+ /** `kind: "relation"` — the resolved link targets, in stored order. */
1718
+ items?: DetailRelationItem[];
1719
+ /** `kind: "owner"` — the dashboard-user name(s), never the id/object. */
1720
+ names?: string[];
1721
+ /** `kind: "bar-lines"` — the embedded pre-registration, resolved. */
1722
+ bars?: ResolvedBarLine[];
1723
+ }
1724
+ /** The dashboard-user name(s) off an `Owner`/`Agreed by` value — the stored
1725
+ * shape is `{id, name}[]`; a bare string is tolerated as a fallback. */
1726
+ declare function ownerNames(value: unknown): string[];
1727
+ /** Bar lines resolved against the loaded assumptions — shared by the drawer's
1728
+ * generic list and the record page's Evidence tab so the two never drift. */
1729
+ declare function resolveBarLines(bars: BarLine[], related: RelatedSet): ResolvedBarLine[];
1730
+ /**
1731
+ * The record's own fields, beyond its meta/derived tuple, as rows a drawer
1732
+ * can render directly — relation fields resolved to a title + navigate
1733
+ * target, `Owner`/`Agreed by` to plain names, `barLines` to structured rows.
1734
+ * Everything else still formats through `formatValue` (spec stories 1–3).
1735
+ */
1736
+ declare function detailRows(register: Collection, record: AnyRecord, related?: RelatedSet): DetailRow[];
1530
1737
 
1531
1738
  /** Plain-language labels — the register is a surface a non-technical
1532
1739
  * teammate meets, so no jargon and no code-y plurals. */
@@ -1548,4 +1755,4 @@ declare const REGISTER_GROUPS: {
1548
1755
  registers: Collection[];
1549
1756
  }[];
1550
1757
 
1551
- export { type BacklinkItem, BeliefJourney, type BeliefJourneyProps, CONFLICT_MESSAGE, type CellKind, type ColdStart, type ColumnDef, ConfidenceCell, type Counts, type DashboardConfig, type Draft, EditBeliefForm, type EditBeliefFormProps, EditFields, type ExperimentView, FIRST_RUN_LINE, type FieldEditor, FieldInput, type FieldKind, type FormField, type GlossaryTerm, GlossaryText, type GlossaryTextProps, type GroupBucket, type GroupByAxis, type HumanText, type JourneyColdState, type JourneyEventView, type JourneyView, type LinkArgs, type LinkChoice, type LinkifyNode, type LinkifyOptions, type Meter, type MeterKind, type MovePresentation, type NeedsHumanByRegister, type NeedsHumanCounts, type NeighbourChip, type NestedGroup, type NextColdStart, type NextMoveRecords, NextMoveSurface, type NextMoveSurfaceProps, type OtherMover, type Pill, type PipelineColdStart, type PipelineRow, PipelineSurface, type PipelineView, REGISTER_GROUPS, REGISTER_ICON, REGISTER_LABEL, REGISTER_ORDER, REGISTER_SINGULAR, REGISTER_SUBTITLE, RISK_CRIT, RISK_WARN, RecordDrawer, type RecordDrawerProps, RecordForm, type RecordFormProps, RecordPage, type RecordPageModel, type RecordPageProps, type RecordTabId, RegisterBrowser, type RegisterBrowserProps, type RegisterContext, RegisterCounts, type RegisterCountsProps, RegisterTable, type RegisterTableProps, type RelatedSet, RelationEditor, type RelationEditorProps, type RelationPanel, type ResolvedRow, type RiskBand, RiskBar, type Route, type SaveResult, type SavedView, type ScoreChip, ScoreImpactForm, type ScoreImpactFormProps, type ShapedRegister, SidebarNav, type SidebarNavProps, type SortSpec, Sparkline, type StageMeterInput, type StageMeterView, StatTile, StatusPill, type StepInForm, type StoryStepIn, SurfacePlaceholder, type SurfacePlaceholderProps, type TabDef, type TermPreview, type Tone, type Understanding, UnderstandingPanel, type UseCountsResult, type UseCreateResult, type UseLinkResult, type UseListResult, type UseNeedsHumanResult, type UseRecordResult, type UseSavedViewsResult, type UseUpdateResult, ValidationOSDashboard, type ValidationOSDashboardProps, type ViewDescriptor, WriteDecisionForm, type WriteDecisionFormProps, backlinkPanels, buildJourney, buildPatch, buildPipeline, buildRecordPage, buildUnderstanding, cellValue, coldStartFor, columnsFor, confidenceTone, configureRiskBands, defaultTabId, dontConfuseWith, draftFrom, editableFields, emptyDraft, eventStepIn, eventTone, filterRecords, formFieldsFor, formatCount, formatRoute, formatSigned, formatValue, groupByAxesFor, groupRecords, hasEdits, headerPills, humanInputFields, interpretSave, journeyColdState, leadingMeters, linkChoicesFrom, linkify, missingRequired, movePresentation, needsHumanCounts, nestReadingsByPlan, parseRoute, primaryLabel, riskBand, riskFraction, riskLevel, scoreChip, shapeRegister, sortRecords, sparklinePath, stageMeters, statusTone, tabsFor, toCreatePayload, toGlossaryTerms, toNextMoveInput, toStageExperimentInput, useCounts, useCreate, useLink, useList, useNeedsHuman, useRecord, useSavedViews, useUpdate, weekOverWeekDelta };
1758
+ export { type BacklinkItem, BeliefJourney, type BeliefJourneyProps, CONFLICT_MESSAGE, type CellKind, type ColdStart, type ColumnDef, ConfidenceCell, ConnectClaudeCode, type ConnectClaudeCodeProps, type ConnectCommandInput, type Counts, type CycleReadingView, type CycleView, DEFAULT_TOKEN_ENV, DIRECT_CYCLE_KEY, type DashboardConfig, type DetailRelationItem, type DetailRow, type DetailRowKind, type Draft, EditBeliefForm, type EditBeliefFormProps, EditFields, type ExperimentView, FIRST_RUN_LINE, type FieldEditor, FieldInput, type FieldKind, type FormField, type GlossaryTerm, GlossaryText, type GlossaryTextProps, type GroupBucket, type GroupByAxis, type HumanText, type JourneyColdState, type JourneyEventView, type JourneyView, type LinkArgs, type LinkChoice, type LinkifyNode, type LinkifyOptions, type Meter, type MeterKind, type MovePresentation, type NeedsHumanByRegister, type NeedsHumanCounts, type NeighbourChip, type NestedGroup, type NextColdStart, type NextMoveRecords, NextMoveSurface, type NextMoveSurfaceProps, type OtherMover, type Pill, type PipelineColdStart, type PipelineRow, PipelineSurface, type PipelineView, REGISTER_GROUPS, REGISTER_ICON, REGISTER_LABEL, REGISTER_ORDER, REGISTER_SINGULAR, REGISTER_SUBTITLE, RISK_CRIT, RISK_WARN, RecordDrawer, type RecordDrawerProps, RecordForm, type RecordFormProps, RecordPage, type RecordPageModel, type RecordPageProps, type RecordTabId, RegisterBrowser, type RegisterBrowserProps, type RegisterContext, RegisterCounts, type RegisterCountsProps, RegisterTable, type RegisterTableProps, type RelatedSet, RelationEditor, type RelationEditorProps, type RelationPanel, type ResolvedBarLine, type ResolvedRow, type RiskBand, RiskBar, type Route, type SaveResult, type SavedView, type ScoreChip, ScoreImpactForm, type ScoreImpactFormProps, type ShapedRegister, SidebarNav, type SidebarNavProps, type SortSpec, Sparkline, type StageMeterInput, type StageMeterView, StatTile, StatusPill, type StepInForm, type StoryStepIn, SurfacePlaceholder, type SurfacePlaceholderProps, type TabDef, type TermPreview, type Tone, type Understanding, UnderstandingPanel, type UseCountsResult, type UseCreateResult, type UseLinkResult, type UseListResult, type UseNeedsHumanResult, type UseRecordResult, type UseSavedViewsResult, type UseUpdateResult, ValidationOSDashboard, type ValidationOSDashboardProps, type ViewDescriptor, WriteDecisionForm, type WriteDecisionFormProps, backlinkPanels, buildCycles, buildJourney, buildPatch, buildPipeline, buildRecordPage, buildUnderstanding, cellValue, coldStartFor, columnsFor, composeConnectCommand, confidenceTone, configureRiskBands, defaultTabId, detailRows, dontConfuseWith, draftFrom, editableFields, emptyDraft, eventStepIn, eventTone, filterRecords, formFieldsFor, formatCount, formatRoute, formatSigned, formatValue, groupByAxesFor, groupRecords, hasEdits, headerPills, humanInputFields, interpretSave, journeyColdState, leadingMeters, linkChoicesFrom, linkify, missingRequired, movePresentation, needsHumanCounts, nestReadingsByPlan, ownerNames, parseRoute, primaryLabel, resolveBarLines, riskBand, riskFraction, riskLevel, scoreChip, shapeRegister, sortRecords, sparklinePath, stageMeters, statusTone, tabsFor, toCreatePayload, toGlossaryTerms, toNextMoveInput, toStageExperimentInput, useCounts, useCreate, useLink, useList, useNeedsHuman, useRecord, useSavedViews, useUpdate, weekOverWeekDelta };