@validation-os/dashboard 0.2.0 → 0.4.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 +292 -11
- package/dist/index.js +1218 -84
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/labels.ts","../src/register-counts.tsx","../src/columns.ts","../src/register-table.tsx","../src/record-drawer.tsx","../src/register-browser.tsx","../src/use-records.ts","../src/use-counts.ts"],"sourcesContent":["import type { Collection } from \"@validation-os/core\";\n\n/** Plain-language labels — the register is a surface a non-technical\n * teammate meets, so no jargon and no code-y plurals. */\nexport const REGISTER_LABEL: Record<Collection, string> = {\n assumptions: \"Assumptions\",\n experiments: \"Experiments\",\n readings: \"Readings\",\n goals: \"Goals\",\n decisions: \"Decisions\",\n glossary: \"Glossary\",\n people: \"People\",\n};\n\n/** The order tiles read left-to-right, top-to-bottom. */\nexport const REGISTER_ORDER: Collection[] = [\n \"assumptions\",\n \"experiments\",\n \"readings\",\n \"goals\",\n \"decisions\",\n \"glossary\",\n \"people\",\n];\n","import type { Collection } from \"@validation-os/core\";\nimport { REGISTER_LABEL, REGISTER_ORDER } from \"./labels.js\";\nimport type { Counts } from \"./use-counts.js\";\n\nexport interface RegisterCountsProps {\n counts: Counts;\n /** Optional caption under the tiles (e.g. the backend the numbers came from). */\n caption?: string;\n /**\n * Optional link target per register. When supplied, each tile becomes a link\n * (a plain anchor — works with any router), so counts double as navigation\n * into the browse tables.\n */\n hrefFor?: (register: Collection) => string;\n}\n\n/**\n * A glanceable grid of stat tiles — one per register, the register's row count\n * as the hero number. Presentational: the caller reads the counts (server-side,\n * through the adapter) and hands them in. Styled with Tailwind utility classes;\n * the host app provides Tailwind.\n */\nexport function RegisterCounts({ counts, caption, hrefFor }: RegisterCountsProps) {\n const registers = REGISTER_ORDER.filter(\n (r): r is Collection => counts[r] !== undefined,\n );\n return (\n <section aria-label=\"Register counts\">\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4\">\n {registers.map((register) => (\n <StatTile\n key={register}\n label={REGISTER_LABEL[register]}\n value={counts[register] ?? 0}\n href={hrefFor?.(register)}\n />\n ))}\n </div>\n {caption ? (\n <p className=\"mt-4 text-sm text-neutral-500 dark:text-neutral-400\">\n {caption}\n </p>\n ) : null}\n </section>\n );\n}\n\nfunction StatTile({\n label,\n value,\n href,\n}: {\n label: string;\n value: number;\n href?: string;\n}) {\n const body = (\n <>\n <div className=\"text-sm font-medium text-neutral-500 dark:text-neutral-400\">\n {label}\n </div>\n <div className=\"mt-1 text-3xl font-semibold tabular-nums text-neutral-900 dark:text-neutral-50\">\n {value.toLocaleString()}\n </div>\n </>\n );\n const className =\n \"block rounded-xl border border-neutral-200 bg-white p-5 dark:border-neutral-800 dark:bg-neutral-900\";\n return href ? (\n <a\n href={href}\n className={`${className} transition-colors hover:border-neutral-300 hover:bg-neutral-50 dark:hover:border-neutral-700 dark:hover:bg-neutral-800`}\n >\n {body}\n </a>\n ) : (\n <div className={className}>{body}</div>\n );\n}\n","/**\n * Per-register table columns and value formatting — the presentational shape\n * of the browse tables, kept as pure data/functions so it is unit-testable\n * without a DOM. The register is a surface a non-technical teammate meets, so\n * headers are plain language and derived numbers are shown, never hidden.\n */\nimport type { AnyRecord, Collection } from \"@validation-os/core\";\n\nexport interface ColumnDef {\n /** Stable key; also the default field read from the record. */\n key: string;\n /** Plain-language column header. */\n header: string;\n /** Pull the display value from a record (defaults to `record[key]`). */\n accessor?: (record: AnyRecord) => unknown;\n /** Numeric columns right-align for a clean column of figures. */\n align?: \"left\" | \"right\";\n /** Marks a column whose value is computed, never hand-typed (spec story 4). */\n derived?: boolean;\n}\n\n/** Read a nested `derived` number off a record, tolerating a missing tuple. */\nfunction derivedField(field: string): (r: AnyRecord) => unknown {\n return (r) => (r.derived as Record<string, unknown> | undefined)?.[field];\n}\n\n/**\n * The columns each register shows, left-to-right. The first column is always\n * the record's headline (Title, or Name for people); assumptions additionally\n * surface Impact, Confidence and Risk at a glance (spec user story 2).\n */\nconst COLUMNS: Record<Collection, ColumnDef[]> = {\n assumptions: [\n { key: \"Title\", header: \"Assumption\" },\n { key: \"Impact\", header: \"Impact\", align: \"right\" },\n {\n key: \"confidence\",\n header: \"Confidence\",\n align: \"right\",\n derived: true,\n accessor: derivedField(\"confidence\"),\n },\n {\n key: \"risk\",\n header: \"Risk\",\n align: \"right\",\n derived: true,\n accessor: derivedField(\"risk\"),\n },\n { key: \"Status\", header: \"Status\" },\n ],\n experiments: [\n { key: \"Title\", header: \"Experiment\" },\n { key: \"Status\", header: \"Status\" },\n { key: \"Feasibility\", header: \"Feasibility\" },\n ],\n readings: [\n { key: \"Title\", header: \"Reading\" },\n { key: \"Result\", header: \"Result\" },\n { key: \"Rung\", header: \"Rung\" },\n {\n key: \"strength\",\n header: \"Strength\",\n align: \"right\",\n derived: true,\n accessor: derivedField(\"strength\"),\n },\n ],\n goals: [\n { key: \"Title\", header: \"Goal\" },\n { key: \"Status\", header: \"Status\" },\n { key: \"Outcome\", header: \"Outcome\" },\n ],\n decisions: [\n { key: \"Title\", header: \"Decision\" },\n { key: \"Status\", header: \"Status\" },\n ],\n glossary: [\n { key: \"Title\", header: \"Term\" },\n { key: \"Status\", header: \"Status\" },\n ],\n people: [\n { key: \"Name\", header: \"Name\" },\n { key: \"Role\", header: \"Role\" },\n { key: \"Segment\", header: \"Segment\" },\n ],\n};\n\n/** The columns to render for a register. */\nexport function columnsFor(register: Collection): ColumnDef[] {\n return COLUMNS[register];\n}\n\n/** Read a column's raw value from a record (accessor, else `record[key]`). */\nexport function cellValue(column: ColumnDef, record: AnyRecord): unknown {\n return column.accessor ? column.accessor(record) : record[column.key];\n}\n\n/** Format any stored value for display; empty/missing reads as an em dash. */\nexport function formatValue(value: unknown): string {\n if (value === null || value === undefined || value === \"\") return \"—\";\n if (typeof value === \"boolean\") return value ? \"Yes\" : \"No\";\n if (Array.isArray(value)) {\n return value.length ? value.map(formatScalar).join(\", \") : \"—\";\n }\n return formatScalar(value);\n}\n\nfunction formatScalar(value: unknown): string {\n if (value === null || value === undefined) return \"—\";\n if (typeof value === \"number\") return String(value);\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n\n/** The record's headline for a row/drawer: Title, else Name, else its id. */\nexport function primaryLabel(record: AnyRecord): string {\n const title = record.Title ?? record.Name;\n return typeof title === \"string\" && title.trim() ? title : record.id;\n}\n\n// ── Field labels ────────────────────────────────────────────────────────────\n// The register is a surface a non-technical teammate meets, so field names are\n// shown in plain language, never as raw camelCase keys. This is the single\n// source for both the drawer's field list and its computed-numbers block.\n\n/** Labels for the computed `derived` tuple. Both `derivedImpact` (the\n * derivation module's name) and `impact` (the migrated data's name, pending the\n * OPS-1273 schema ripple) map to one label so the drawer reads cleanly either\n * way. */\nconst DERIVED_LABEL: Record<string, string> = {\n confidence: \"Confidence\",\n risk: \"Risk\",\n derivedImpact: \"Derived Impact\",\n impact: \"Derived Impact\",\n sourceQuality: \"Source quality\",\n strength: \"Strength\",\n};\n\n/** Plain-language names for the camelCase id/relation fields; Title-cased\n * fields (Title, Status, Impact, Owner…) already read cleanly and pass through. */\nconst FIELD_LABEL: Record<string, string> = {\n dependsOnIds: \"Depends on\",\n enablesIds: \"Enables\",\n contradictsIds: \"Contradicts\",\n readingIds: \"Readings\",\n assumptionId: \"Assumption\",\n experimentId: \"Experiment\",\n goalId: \"Goal\",\n basedOnIds: \"Based on\",\n resolvesIds: \"Resolves\",\n barLineAssumptionIds: \"Assumptions tested\",\n closureReason: \"Closure reason\",\n magnitudeBand: \"Magnitude band\",\n moot: \"Moot\",\n};\n\n/** A record field's display label — an override, else a humanised key. */\nexport function fieldLabel(key: string): string {\n const override = FIELD_LABEL[key];\n if (override) return override;\n if (/^[A-Z]/.test(key)) return key; // already Title-cased, reads fine\n const spaced = key.replace(/([a-z0-9])([A-Z])/g, \"$1 $2\").toLowerCase();\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\n}\n\n/** A computed (`derived`) field's display label. */\nexport function derivedLabel(key: string): string {\n return DERIVED_LABEL[key] ?? fieldLabel(key);\n}\n","import type { AnyRecord, Collection } from \"@validation-os/core\";\nimport { cellValue, columnsFor, formatValue, primaryLabel } from \"./columns.js\";\n\nexport interface RegisterTableProps {\n register: Collection;\n records: AnyRecord[];\n /** Called with the clicked record's id — opens the read-only drawer. */\n onRowClick?: (id: string) => void;\n /** The id of the currently-open record, highlighted in the list. */\n selectedId?: string | null;\n}\n\n/**\n * A list table for one register — a row per record, the register's key fields\n * as columns (assumptions show Impact, Confidence and Risk). Presentational:\n * the caller supplies the rows. Clicking a row opens the read-only drawer.\n * Styled with Tailwind utilities the host app provides.\n */\nexport function RegisterTable({\n register,\n records,\n onRowClick,\n selectedId,\n}: RegisterTableProps) {\n const columns = columnsFor(register);\n\n if (records.length === 0) {\n return (\n <p className=\"rounded-xl border border-dashed border-neutral-300 p-8 text-center text-sm text-neutral-500 dark:border-neutral-700 dark:text-neutral-400\">\n No records yet.\n </p>\n );\n }\n\n return (\n <div className=\"overflow-x-auto rounded-xl border border-neutral-200 dark:border-neutral-800\">\n <table className=\"w-full border-collapse text-left text-sm\">\n <thead>\n <tr className=\"border-b border-neutral-200 bg-neutral-50 dark:border-neutral-800 dark:bg-neutral-900\">\n {columns.map((c) => (\n <th\n key={c.key}\n scope=\"col\"\n className={`px-4 py-2.5 font-medium text-neutral-500 dark:text-neutral-400 ${\n c.align === \"right\" ? \"text-right tabular-nums\" : \"text-left\"\n }`}\n >\n {c.header}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {records.map((record) => {\n const isSelected = record.id === selectedId;\n return (\n <tr\n key={record.id}\n onClick={() => onRowClick?.(record.id)}\n onKeyDown={\n onRowClick\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onRowClick(record.id);\n }\n }\n : undefined\n }\n tabIndex={onRowClick ? 0 : undefined}\n aria-selected={isSelected}\n className={`border-b border-neutral-100 last:border-0 focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500 dark:border-neutral-900 ${\n onRowClick ? \"cursor-pointer\" : \"\"\n } ${\n isSelected\n ? \"bg-blue-50 dark:bg-blue-950/40\"\n : \"hover:bg-neutral-50 dark:hover:bg-neutral-900/60\"\n }`}\n >\n {columns.map((c, i) => {\n const raw = cellValue(c, record);\n // The headline cell falls back to the record's id so a row is\n // never blank; other cells show an em dash when empty.\n const text =\n i === 0 && (raw === null || raw === undefined || raw === \"\")\n ? primaryLabel(record)\n : formatValue(raw);\n return (\n <td\n key={c.key}\n className={`px-4 py-2.5 ${\n c.align === \"right\"\n ? \"text-right tabular-nums text-neutral-700 dark:text-neutral-300\"\n : \"text-left\"\n } ${\n i === 0\n ? \"font-medium text-neutral-900 dark:text-neutral-100\"\n : \"text-neutral-600 dark:text-neutral-400\"\n }`}\n >\n {text}\n </td>\n );\n })}\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n );\n}\n","import { useEffect, useRef } from \"react\";\nimport type { AnyRecord, Collection } from \"@validation-os/core\";\nimport { REGISTER_LABEL } from \"./labels.js\";\nimport { derivedLabel, fieldLabel, formatValue, primaryLabel } from \"./columns.js\";\n\nexport interface RecordDrawerProps {\n register: Collection;\n /** The open record, or null while loading / when nothing is selected. */\n record: AnyRecord | null;\n /** True while the record is being fetched. */\n loading?: boolean;\n error?: string | null;\n /** Whether the drawer is open (a row is selected). */\n open: boolean;\n onClose: () => void;\n}\n\n/** Provider-owned/meta fields are shown in the footer, not as content rows. */\nconst META_FIELDS = new Set([\"id\", \"version\", \"createdAt\", \"updatedAt\", \"derived\"]);\n\n/**\n * A read-only drawer for one record. Any derived numbers lead as the hero,\n * explicitly marked computed — not editable (spec user story 4), so a reader\n * trusts they follow the formulas; the record's own fields follow. This slice\n * is read-only; editing lands in a later slice.\n */\nexport function RecordDrawer({\n register,\n record,\n loading,\n error,\n open,\n onClose,\n}: RecordDrawerProps) {\n const closeButtonRef = useRef<HTMLButtonElement>(null);\n\n // Escape closes the drawer; focus lands inside it when it opens so keyboard\n // users aren't left behind an aria-modal dialog.\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n document.addEventListener(\"keydown\", onKey);\n closeButtonRef.current?.focus();\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, onClose]);\n\n if (!open) return null;\n\n const derived =\n record && record.derived && typeof record.derived === \"object\"\n ? (record.derived as Record<string, unknown>)\n : null;\n\n const fields = record\n ? Object.keys(record).filter((k) => !META_FIELDS.has(k))\n : [];\n\n return (\n <div className=\"fixed inset-0 z-50 flex justify-end\">\n {/* Overlay — click to dismiss. */}\n <button\n type=\"button\"\n aria-label=\"Close\"\n onClick={onClose}\n className=\"absolute inset-0 bg-neutral-950/30 backdrop-blur-sm\"\n />\n <aside\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={`${REGISTER_LABEL[register]} record`}\n className=\"relative flex h-full w-full max-w-md flex-col overflow-y-auto border-l border-neutral-200 bg-white shadow-xl dark:border-neutral-800 dark:bg-neutral-950\"\n >\n <header className=\"flex items-start justify-between gap-4 border-b border-neutral-200 p-5 dark:border-neutral-800\">\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-neutral-400\">\n {REGISTER_LABEL[register]}\n </p>\n <h2 className=\"mt-1 text-lg font-semibold text-neutral-900 dark:text-neutral-50\">\n {record ? primaryLabel(record) : loading ? \"Loading…\" : \"—\"}\n </h2>\n </div>\n <button\n ref={closeButtonRef}\n type=\"button\"\n onClick={onClose}\n className=\"rounded-md px-2 py-1 text-sm text-neutral-500 hover:bg-neutral-100 dark:hover:bg-neutral-900\"\n >\n Close\n </button>\n </header>\n\n <div className=\"flex-1 p-5\">\n {loading ? (\n <p className=\"text-sm text-neutral-500\">Loading record…</p>\n ) : error ? (\n <p className=\"text-sm text-red-600 dark:text-red-400\">{error}</p>\n ) : !record ? (\n <p className=\"text-sm text-neutral-500\">No record.</p>\n ) : (\n <>\n {derived ? (\n <section className=\"mb-6\">\n <h3 className=\"mb-2 text-xs font-medium uppercase tracking-wide text-neutral-400\">\n Computed · not editable\n </h3>\n <dl className=\"grid grid-cols-3 gap-3\">\n {Object.entries(derived).map(([key, value]) => (\n <div\n key={key}\n className=\"rounded-lg border border-neutral-200 bg-neutral-50 p-3 dark:border-neutral-800 dark:bg-neutral-900\"\n >\n <dt className=\"text-xs text-neutral-500 dark:text-neutral-400\">\n {derivedLabel(key)}\n </dt>\n <dd className=\"mt-1 text-xl font-semibold tabular-nums text-neutral-900 dark:text-neutral-50\">\n {formatValue(value)}\n </dd>\n </div>\n ))}\n </dl>\n </section>\n ) : null}\n\n <dl className=\"divide-y divide-neutral-100 dark:divide-neutral-900\">\n {fields.map((key) => (\n <div key={key} className=\"grid grid-cols-3 gap-3 py-2.5\">\n <dt className=\"text-sm text-neutral-500 dark:text-neutral-400\">\n {fieldLabel(key)}\n </dt>\n <dd className=\"col-span-2 text-sm text-neutral-800 dark:text-neutral-200\">\n {formatValue(record[key])}\n </dd>\n </div>\n ))}\n </dl>\n </>\n )}\n </div>\n\n {record ? (\n <footer className=\"border-t border-neutral-200 p-5 text-xs text-neutral-400 dark:border-neutral-800\">\n {record.id} · version {String(record.version)} · updated{\" \"}\n {formatValue(record.updatedAt)}\n </footer>\n ) : null}\n </aside>\n </div>\n );\n}\n","import { useState } from \"react\";\nimport type { Collection } from \"@validation-os/core\";\nimport { REGISTER_LABEL } from \"./labels.js\";\nimport { RegisterTable } from \"./register-table.js\";\nimport { RecordDrawer } from \"./record-drawer.js\";\nimport { useList, useRecord } from \"./use-records.js\";\n\nexport interface RegisterBrowserProps {\n register: Collection;\n /** API base path (default `/api`). */\n basePath?: string;\n}\n\n/**\n * The browse-and-open surface for one register: a list table that opens a\n * read-only drawer on row click. Reads over HTTP through the Clerk-gated API\n * read routes (list + get), so the browser never touches Firestore directly.\n * The thin host app renders this with a `register` — that's the whole page.\n */\nexport function RegisterBrowser({ register, basePath }: RegisterBrowserProps) {\n const { records, loading, error } = useList(register, basePath);\n const [openId, setOpenId] = useState<string | null>(null);\n const {\n record,\n loading: recordLoading,\n error: recordError,\n } = useRecord(register, openId, basePath);\n\n return (\n <div>\n {loading && !records ? (\n <p className=\"text-sm text-neutral-500\">\n Loading {REGISTER_LABEL[register].toLowerCase()}…\n </p>\n ) : error ? (\n <p className=\"text-sm text-red-600 dark:text-red-400\">{error}</p>\n ) : (\n <RegisterTable\n register={register}\n records={records ?? []}\n onRowClick={setOpenId}\n selectedId={openId}\n />\n )}\n\n <RecordDrawer\n register={register}\n record={record}\n loading={recordLoading}\n error={recordError}\n open={openId !== null}\n onClose={() => setOpenId(null)}\n />\n </div>\n );\n}\n","import { useCallback, useEffect, useState } from \"react\";\nimport type { AnyRecord, Collection } from \"@validation-os/core\";\n\n/**\n * Client hooks that read the register over HTTP through the API read routes\n * (`GET {basePath}/{register}` and `GET {basePath}/{register}/{id}`). The API\n * reaches Firestore server-side through the adapter, so no backend credentials\n * ever reach the browser — the browser only ever speaks to the Clerk-gated API.\n */\n\ninterface AsyncState<T> {\n data: T | null;\n loading: boolean;\n error: string | null;\n}\n\n/** Both read routes wrap their payload in a `{ data }` envelope. */\nfunction pickData<T>(body: unknown): T {\n return (body as { data: T }).data;\n}\n\n/**\n * Fetch one JSON resource, tracking loading/error. A null `url` means \"idle\"\n * (nothing to load yet) — the drawer's get hook uses this until a row is\n * clicked. Returns a `refresh` that re-runs the fetch. This is the shared\n * engine behind both `useList` and `useRecord`.\n */\nfunction useJsonResource<T>(url: string | null): AsyncState<T> & {\n refresh: () => void;\n} {\n const [state, setState] = useState<AsyncState<T>>({\n data: null,\n loading: url !== null,\n error: null,\n });\n const [tick, setTick] = useState(0);\n\n useEffect(() => {\n if (url === null) {\n setState({ data: null, loading: false, error: null });\n return;\n }\n let live = true;\n setState((s) => ({ ...s, loading: true }));\n fetch(url)\n .then(async (res) => {\n if (!res.ok) throw new Error(`Request to ${url} failed (${res.status})`);\n return pickData<T>(await res.json());\n })\n .then((data) => live && setState({ data, loading: false, error: null }))\n .catch(\n (e: unknown) =>\n live &&\n setState({\n data: null,\n loading: false,\n error: e instanceof Error ? e.message : \"Failed to load\",\n }),\n );\n return () => {\n live = false;\n };\n }, [url, tick]);\n\n const refresh = useCallback(() => setTick((t) => t + 1), []);\n return { ...state, refresh };\n}\n\nexport interface UseListResult {\n records: AnyRecord[] | null;\n loading: boolean;\n error: string | null;\n refresh: () => void;\n}\n\n/** Fetch every row of a register. */\nexport function useList(register: Collection, basePath = \"/api\"): UseListResult {\n const { data, loading, error, refresh } = useJsonResource<AnyRecord[]>(\n `${basePath}/${register}`,\n );\n return { records: data, loading, error, refresh };\n}\n\nexport interface UseRecordResult {\n record: AnyRecord | null;\n loading: boolean;\n error: string | null;\n}\n\n/**\n * Fetch one record by id. `id` may be null (nothing open) — the hook stays\n * idle until an id is supplied, so it drives a drawer that opens on row click.\n */\nexport function useRecord(\n register: Collection,\n id: string | null,\n basePath = \"/api\",\n): UseRecordResult {\n const url = id\n ? `${basePath}/${register}/${encodeURIComponent(id)}`\n : null;\n const { data, loading, error } = useJsonResource<AnyRecord>(url);\n return { record: data, loading, error };\n}\n","import { useCallback, useEffect, useState } from \"react\";\nimport type { Collection } from \"@validation-os/core\";\n\nexport type Counts = Partial<Record<Collection, number>>;\n\nexport interface UseCountsResult {\n counts: Counts | null;\n loading: boolean;\n error: string | null;\n refresh: () => void;\n}\n\n/**\n * Client hook: fetch per-register counts from the API (`GET {basePath}/counts`).\n * The API reads Firestore server-side through the adapter; this only speaks\n * HTTP, so no backend credentials ever reach the browser.\n */\nexport function useCounts(basePath = \"/api\"): UseCountsResult {\n const [counts, setCounts] = useState<Counts | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [tick, setTick] = useState(0);\n\n useEffect(() => {\n let live = true;\n setLoading(true);\n fetch(`${basePath}/counts`)\n .then(async (res) => {\n if (!res.ok) throw new Error(`Counts request failed (${res.status})`);\n return (await res.json()) as { counts: Counts };\n })\n .then((body) => {\n if (!live) return;\n setCounts(body.counts);\n setError(null);\n })\n .catch((e: unknown) => {\n if (!live) return;\n setError(e instanceof Error ? e.message : \"Failed to load counts\");\n })\n .finally(() => {\n if (live) setLoading(false);\n });\n return () => {\n live = false;\n };\n }, [basePath, tick]);\n\n const refresh = useCallback(() => setTick((t) => t + 1), []);\n return { counts, loading, error, refresh };\n}\n"],"mappings":"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","names":["jsx","jsxs","Fragment","jsx","jsxs","useState","useEffect","jsx","jsxs","useState","useCallback","useEffect","useState"]}
|
|
1
|
+
{"version":3,"sources":["../src/labels.ts","../src/register-counts.tsx","../src/columns.ts","../src/register-table.tsx","../src/record-drawer.tsx","../src/drawer-shell.tsx","../src/edit.ts","../src/use-records.ts","../src/understanding.ts","../src/understanding-panel.tsx","../src/record-form.tsx","../src/form-fields.ts","../src/field-styles.ts","../src/use-mutations.ts","../src/relation-editor.tsx","../src/link-choices.ts","../src/register-browser.tsx","../src/use-counts.ts"],"sourcesContent":["import type { Collection } from \"@validation-os/core\";\n\n/** Plain-language labels — the register is a surface a non-technical\n * teammate meets, so no jargon and no code-y plurals. */\nexport const REGISTER_LABEL: Record<Collection, string> = {\n assumptions: \"Assumptions\",\n experiments: \"Experiments\",\n readings: \"Readings\",\n goals: \"Goals\",\n decisions: \"Decisions\",\n glossary: \"Glossary\",\n people: \"People\",\n};\n\n/** Singular labels — for \"New {thing}\" affordances (never a naive de-pluralise\n * that would read \"New Peopl\" / \"New Glossary\"). */\nexport const REGISTER_SINGULAR: Record<Collection, string> = {\n assumptions: \"Assumption\",\n experiments: \"Experiment\",\n readings: \"Reading\",\n goals: \"Goal\",\n decisions: \"Decision\",\n glossary: \"Glossary term\",\n people: \"Person\",\n};\n\n/** The order tiles read left-to-right, top-to-bottom. */\nexport const REGISTER_ORDER: Collection[] = [\n \"assumptions\",\n \"experiments\",\n \"readings\",\n \"goals\",\n \"decisions\",\n \"glossary\",\n \"people\",\n];\n","import type { Collection } from \"@validation-os/core\";\nimport { REGISTER_LABEL, REGISTER_ORDER } from \"./labels.js\";\nimport type { Counts } from \"./use-counts.js\";\n\nexport interface RegisterCountsProps {\n counts: Counts;\n /** Optional caption under the tiles (e.g. the backend the numbers came from). */\n caption?: string;\n /**\n * Optional link target per register. When supplied, each tile becomes a link\n * (a plain anchor — works with any router), so counts double as navigation\n * into the browse tables.\n */\n hrefFor?: (register: Collection) => string;\n}\n\n/**\n * A glanceable grid of stat tiles — one per register, the register's row count\n * as the hero number. Presentational: the caller reads the counts (server-side,\n * through the adapter) and hands them in. Styled with Tailwind utility classes;\n * the host app provides Tailwind.\n */\nexport function RegisterCounts({ counts, caption, hrefFor }: RegisterCountsProps) {\n const registers = REGISTER_ORDER.filter(\n (r): r is Collection => counts[r] !== undefined,\n );\n return (\n <section aria-label=\"Register counts\">\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4\">\n {registers.map((register) => (\n <StatTile\n key={register}\n label={REGISTER_LABEL[register]}\n value={counts[register] ?? 0}\n href={hrefFor?.(register)}\n />\n ))}\n </div>\n {caption ? (\n <p className=\"mt-4 text-sm text-neutral-500 dark:text-neutral-400\">\n {caption}\n </p>\n ) : null}\n </section>\n );\n}\n\nfunction StatTile({\n label,\n value,\n href,\n}: {\n label: string;\n value: number;\n href?: string;\n}) {\n const body = (\n <>\n <div className=\"text-sm font-medium text-neutral-500 dark:text-neutral-400\">\n {label}\n </div>\n <div className=\"mt-1 text-3xl font-semibold tabular-nums text-neutral-900 dark:text-neutral-50\">\n {value.toLocaleString()}\n </div>\n </>\n );\n const className =\n \"block rounded-xl border border-neutral-200 bg-white p-5 dark:border-neutral-800 dark:bg-neutral-900\";\n return href ? (\n <a\n href={href}\n className={`${className} transition-colors hover:border-neutral-300 hover:bg-neutral-50 dark:hover:border-neutral-700 dark:hover:bg-neutral-800`}\n >\n {body}\n </a>\n ) : (\n <div className={className}>{body}</div>\n );\n}\n","/**\n * Per-register table columns and value formatting — the presentational shape\n * of the browse tables, kept as pure data/functions so it is unit-testable\n * without a DOM. The register is a surface a non-technical teammate meets, so\n * headers are plain language and derived numbers are shown, never hidden.\n */\nimport type { AnyRecord, Collection } from \"@validation-os/core\";\n\nexport interface ColumnDef {\n /** Stable key; also the default field read from the record. */\n key: string;\n /** Plain-language column header. */\n header: string;\n /** Pull the display value from a record (defaults to `record[key]`). */\n accessor?: (record: AnyRecord) => unknown;\n /** Numeric columns right-align for a clean column of figures. */\n align?: \"left\" | \"right\";\n /** Marks a column whose value is computed, never hand-typed (spec story 4). */\n derived?: boolean;\n}\n\n/** Read a nested `derived` number off a record, tolerating a missing tuple. */\nfunction derivedField(field: string): (r: AnyRecord) => unknown {\n return (r) => (r.derived as Record<string, unknown> | undefined)?.[field];\n}\n\n/**\n * The columns each register shows, left-to-right. The first column is always\n * the record's headline (Title, or Name for people); assumptions additionally\n * surface Impact, Confidence and Risk at a glance (spec user story 2).\n */\nconst COLUMNS: Record<Collection, ColumnDef[]> = {\n assumptions: [\n { key: \"Title\", header: \"Assumption\" },\n { key: \"Impact\", header: \"Impact\", align: \"right\" },\n {\n key: \"confidence\",\n header: \"Confidence\",\n align: \"right\",\n derived: true,\n accessor: derivedField(\"confidence\"),\n },\n {\n key: \"risk\",\n header: \"Risk\",\n align: \"right\",\n derived: true,\n accessor: derivedField(\"risk\"),\n },\n { key: \"Status\", header: \"Status\" },\n ],\n experiments: [\n { key: \"Title\", header: \"Experiment\" },\n { key: \"Status\", header: \"Status\" },\n { key: \"Feasibility\", header: \"Feasibility\" },\n ],\n readings: [\n { key: \"Title\", header: \"Reading\" },\n { key: \"Result\", header: \"Result\" },\n { key: \"Rung\", header: \"Rung\" },\n {\n key: \"strength\",\n header: \"Strength\",\n align: \"right\",\n derived: true,\n accessor: derivedField(\"strength\"),\n },\n ],\n goals: [\n { key: \"Title\", header: \"Goal\" },\n { key: \"Status\", header: \"Status\" },\n { key: \"Outcome\", header: \"Outcome\" },\n ],\n decisions: [\n { key: \"Title\", header: \"Decision\" },\n { key: \"Status\", header: \"Status\" },\n ],\n glossary: [\n { key: \"Title\", header: \"Term\" },\n { key: \"Status\", header: \"Status\" },\n ],\n people: [\n { key: \"Name\", header: \"Name\" },\n { key: \"Role\", header: \"Role\" },\n { key: \"Segment\", header: \"Segment\" },\n ],\n};\n\n/** The columns to render for a register. */\nexport function columnsFor(register: Collection): ColumnDef[] {\n return COLUMNS[register];\n}\n\n/** Read a column's raw value from a record (accessor, else `record[key]`). */\nexport function cellValue(column: ColumnDef, record: AnyRecord): unknown {\n return column.accessor ? column.accessor(record) : record[column.key];\n}\n\n/** Format any stored value for display; empty/missing reads as an em dash. */\nexport function formatValue(value: unknown): string {\n if (value === null || value === undefined || value === \"\") return \"—\";\n if (typeof value === \"boolean\") return value ? \"Yes\" : \"No\";\n if (Array.isArray(value)) {\n return value.length ? value.map(formatScalar).join(\", \") : \"—\";\n }\n return formatScalar(value);\n}\n\nfunction formatScalar(value: unknown): string {\n if (value === null || value === undefined) return \"—\";\n if (typeof value === \"number\") return String(value);\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n\n/** The record's headline for a row/drawer: Title, else Name, else its id. */\nexport function primaryLabel(record: AnyRecord): string {\n const title = record.Title ?? record.Name;\n return typeof title === \"string\" && title.trim() ? title : record.id;\n}\n\n// ── Field labels ────────────────────────────────────────────────────────────\n// The register is a surface a non-technical teammate meets, so field names are\n// shown in plain language, never as raw camelCase keys. This is the single\n// source for both the drawer's field list and its computed-numbers block.\n\n/** Labels for the computed `derived` tuple. Both `derivedImpact` (the\n * derivation module's name) and `impact` (the migrated data's name, pending the\n * OPS-1273 schema ripple) map to one label so the drawer reads cleanly either\n * way. */\nconst DERIVED_LABEL: Record<string, string> = {\n confidence: \"Confidence\",\n risk: \"Risk\",\n derivedImpact: \"Derived Impact\",\n impact: \"Derived Impact\",\n sourceQuality: \"Source quality\",\n strength: \"Strength\",\n};\n\n/** Plain-language names for the camelCase id/relation fields; Title-cased\n * fields (Title, Status, Impact, Owner…) already read cleanly and pass through. */\nconst FIELD_LABEL: Record<string, string> = {\n dependsOnIds: \"Depends on\",\n enablesIds: \"Enables\",\n contradictsIds: \"Contradicts\",\n readingIds: \"Readings\",\n assumptionId: \"Assumption\",\n experimentId: \"Experiment\",\n goalId: \"Goal\",\n basedOnIds: \"Based on\",\n resolvesIds: \"Resolves\",\n barLineAssumptionIds: \"Assumptions tested\",\n closureReason: \"Closure reason\",\n magnitudeBand: \"Magnitude band\",\n moot: \"Moot\",\n};\n\n/** A record field's display label — an override, else a humanised key. */\nexport function fieldLabel(key: string): string {\n const override = FIELD_LABEL[key];\n if (override) return override;\n if (/^[A-Z]/.test(key)) return key; // already Title-cased, reads fine\n const spaced = key.replace(/([a-z0-9])([A-Z])/g, \"$1 $2\").toLowerCase();\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\n}\n\n/** A computed (`derived`) field's display label. */\nexport function derivedLabel(key: string): string {\n return DERIVED_LABEL[key] ?? fieldLabel(key);\n}\n","import type { AnyRecord, Collection } from \"@validation-os/core\";\nimport { cellValue, columnsFor, formatValue, primaryLabel } from \"./columns.js\";\n\nexport interface RegisterTableProps {\n register: Collection;\n records: AnyRecord[];\n /** Called with the clicked record's id — opens the read-only drawer. */\n onRowClick?: (id: string) => void;\n /** The id of the currently-open record, highlighted in the list. */\n selectedId?: string | null;\n}\n\n/**\n * A list table for one register — a row per record, the register's key fields\n * as columns (assumptions show Impact, Confidence and Risk). Presentational:\n * the caller supplies the rows. Clicking a row opens the read-only drawer.\n * Styled with Tailwind utilities the host app provides.\n */\nexport function RegisterTable({\n register,\n records,\n onRowClick,\n selectedId,\n}: RegisterTableProps) {\n const columns = columnsFor(register);\n\n if (records.length === 0) {\n return (\n <p className=\"rounded-xl border border-dashed border-neutral-300 p-8 text-center text-sm text-neutral-500 dark:border-neutral-700 dark:text-neutral-400\">\n No records yet.\n </p>\n );\n }\n\n return (\n <div className=\"overflow-x-auto rounded-xl border border-neutral-200 dark:border-neutral-800\">\n <table className=\"w-full border-collapse text-left text-sm\">\n <thead>\n <tr className=\"border-b border-neutral-200 bg-neutral-50 dark:border-neutral-800 dark:bg-neutral-900\">\n {columns.map((c) => (\n <th\n key={c.key}\n scope=\"col\"\n className={`px-4 py-2.5 font-medium text-neutral-500 dark:text-neutral-400 ${\n c.align === \"right\" ? \"text-right tabular-nums\" : \"text-left\"\n }`}\n >\n {c.header}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {records.map((record) => {\n const isSelected = record.id === selectedId;\n return (\n <tr\n key={record.id}\n onClick={() => onRowClick?.(record.id)}\n onKeyDown={\n onRowClick\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onRowClick(record.id);\n }\n }\n : undefined\n }\n tabIndex={onRowClick ? 0 : undefined}\n aria-selected={isSelected}\n className={`border-b border-neutral-100 last:border-0 focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500 dark:border-neutral-900 ${\n onRowClick ? \"cursor-pointer\" : \"\"\n } ${\n isSelected\n ? \"bg-blue-50 dark:bg-blue-950/40\"\n : \"hover:bg-neutral-50 dark:hover:bg-neutral-900/60\"\n }`}\n >\n {columns.map((c, i) => {\n const raw = cellValue(c, record);\n // The headline cell falls back to the record's id so a row is\n // never blank; other cells show an em dash when empty.\n const text =\n i === 0 && (raw === null || raw === undefined || raw === \"\")\n ? primaryLabel(record)\n : formatValue(raw);\n return (\n <td\n key={c.key}\n className={`px-4 py-2.5 ${\n c.align === \"right\"\n ? \"text-right tabular-nums text-neutral-700 dark:text-neutral-300\"\n : \"text-left\"\n } ${\n i === 0\n ? \"font-medium text-neutral-900 dark:text-neutral-100\"\n : \"text-neutral-600 dark:text-neutral-400\"\n }`}\n >\n {text}\n </td>\n );\n })}\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n );\n}\n","import { useEffect, useState, type ReactNode } from \"react\";\nimport type { AnyRecord, Collection } from \"@validation-os/core\";\nimport { DrawerShell } from \"./drawer-shell.js\";\nimport { REGISTER_LABEL } from \"./labels.js\";\nimport { derivedLabel, fieldLabel, formatValue, primaryLabel } from \"./columns.js\";\nimport {\n buildPatch,\n draftFrom,\n editableFields,\n type Draft,\n type FieldEditor,\n} from \"./edit.js\";\nimport { useUpdate } from \"./use-records.js\";\nimport { UnderstandingPanel } from \"./understanding-panel.js\";\n\nexport interface RecordDrawerProps {\n register: Collection;\n /** The open record, or null while loading / when nothing is selected. */\n record: AnyRecord | null;\n /** True while the record is being fetched. */\n loading?: boolean;\n error?: string | null;\n /** Whether the drawer is open (a row is selected). */\n open: boolean;\n onClose: () => void;\n /** API base path (default `/api`). */\n basePath?: string;\n /** Re-fetch the record + its list — called after a save, and the re-fetch\n * path offered when a concurrent edit is detected. */\n onChanged?: () => void;\n /** Extra content below the fields in read mode — e.g. the relation editor. */\n children?: ReactNode;\n}\n\n/** Provider-owned/meta fields are shown in the footer, not as content rows. */\nconst META_FIELDS = new Set([\"id\", \"version\", \"createdAt\", \"updatedAt\", \"derived\"]);\n\n/**\n * A record drawer that reads and edits, and wires relations. Derived numbers\n * always lead as the hero, explicitly marked computed and never editable (spec\n * user story 4) — a \"Why?\" affordance on Confidence explains how the number was\n * earned. Editing recomputes those numbers server-side on save (story 11); a\n * concurrent edit surfaces as a gentle, jargon-free prompt with a re-fetch path\n * (story 12). In read mode the drawer also hosts the relation editor (`children`,\n * story 14). The slide-over chrome is shared with the create drawer via\n * `DrawerShell`.\n */\nexport function RecordDrawer({\n register,\n record,\n loading,\n error,\n open,\n onClose,\n basePath,\n onChanged,\n children,\n}: RecordDrawerProps) {\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState<Draft>({});\n // The record as it was when editing began. Diffing the draft against this\n // (not the live `record`) is what makes a conflict re-fetch safe: only the\n // fields the editor actually changed are written, on top of the latest\n // version — so a teammate's concurrent change to an untouched field survives.\n const [baseline, setBaseline] = useState<AnyRecord | null>(null);\n const [why, setWhy] = useState(false);\n const { save, saving, conflict, error: saveError, reset } = useUpdate(\n register,\n basePath,\n );\n\n // Opening a different record drops any in-progress edit and clears banners.\n const recordId = record?.id ?? null;\n useEffect(() => {\n setEditing(false);\n setBaseline(null);\n setWhy(false);\n reset();\n }, [recordId, reset]);\n\n const derived =\n record && record.derived && typeof record.derived === \"object\"\n ? (record.derived as Record<string, unknown>)\n : null;\n\n const fields = record\n ? Object.keys(record).filter((k) => !META_FIELDS.has(k))\n : [];\n\n function startEditing() {\n if (!record) return;\n setBaseline(record);\n setDraft(draftFrom(register, record));\n reset();\n setEditing(true);\n }\n\n function cancelEditing() {\n setEditing(false);\n reset();\n }\n\n async function onSave() {\n if (!record || !baseline) return;\n // Diff against the baseline (the fields the editor changed), but write\n // against the freshest known version so a reloaded record rebases cleanly.\n const patch = buildPatch(register, baseline, draft);\n patch.version = record.version;\n if (Object.keys(patch).length <= 1) {\n setEditing(false); // only `version` present — nothing actually changed\n return;\n }\n const result = await save(record.id, patch);\n if (result.ok) {\n setEditing(false);\n onChanged?.(); // pull the recomputed record + refreshed list back in\n }\n // On conflict/error the hook holds the message; we stay in edit mode with\n // the draft intact so nothing the editor typed is lost.\n }\n\n function reloadLatest() {\n reset();\n // Re-fetch the latest record; the baseline is kept, so the next save still\n // writes only the fields this editor changed — never the teammate's edits.\n onChanged?.();\n }\n\n const setField = (key: string, value: string) =>\n setDraft((d) => ({ ...d, [key]: value }));\n\n return (\n <DrawerShell\n open={open}\n onClose={onClose}\n ariaLabel={`${REGISTER_LABEL[register]} record`}\n >\n <header className=\"flex items-start justify-between gap-4 border-b border-neutral-200 p-5 dark:border-neutral-800\">\n <div>\n <p className=\"text-xs font-medium uppercase tracking-wide text-neutral-400\">\n {REGISTER_LABEL[register]}\n </p>\n <h2 className=\"mt-1 text-lg font-semibold text-neutral-900 dark:text-neutral-50\">\n {record ? primaryLabel(record) : loading ? \"Loading…\" : \"—\"}\n </h2>\n </div>\n <div className=\"flex items-center gap-2\">\n {record && !editing ? (\n <button\n type=\"button\"\n onClick={startEditing}\n className=\"rounded-md border border-neutral-300 px-2.5 py-1 text-sm font-medium text-neutral-700 hover:bg-neutral-100 dark:border-neutral-700 dark:text-neutral-200 dark:hover:bg-neutral-900\"\n >\n Edit\n </button>\n ) : null}\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-md px-2 py-1 text-sm text-neutral-500 hover:bg-neutral-100 dark:hover:bg-neutral-900\"\n >\n Close\n </button>\n </div>\n </header>\n\n <div className=\"flex-1 p-5\">\n {loading ? (\n <p className=\"text-sm text-neutral-500\">Loading record…</p>\n ) : error ? (\n <p className=\"text-sm text-red-600 dark:text-red-400\">{error}</p>\n ) : !record ? (\n <p className=\"text-sm text-neutral-500\">No record.</p>\n ) : (\n <>\n {derived ? (\n <section className=\"mb-6\">\n <h3 className=\"mb-2 text-xs font-medium uppercase tracking-wide text-neutral-400\">\n Computed · not editable\n </h3>\n <dl className=\"grid grid-cols-3 gap-3\">\n {Object.entries(derived).map(([key, value]) => (\n <div\n key={key}\n className=\"rounded-lg border border-neutral-200 bg-neutral-50 p-3 dark:border-neutral-800 dark:bg-neutral-900\"\n >\n <dt className=\"text-xs text-neutral-500 dark:text-neutral-400\">\n {derivedLabel(key)}\n </dt>\n <dd className=\"mt-1 text-xl font-semibold tabular-nums text-neutral-900 dark:text-neutral-50\">\n {formatValue(value)}\n </dd>\n </div>\n ))}\n </dl>\n {\"confidence\" in derived ? (\n <WhyReveal\n open={why}\n onToggle={() => setWhy((w) => !w)}\n assumption={record}\n basePath={basePath}\n />\n ) : null}\n </section>\n ) : null}\n\n {conflict ? (\n <ConflictBanner message={conflict} onReload={reloadLatest} />\n ) : null}\n {saveError ? (\n <p\n role=\"alert\"\n className=\"mb-4 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700 dark:border-red-900 dark:bg-red-950/40 dark:text-red-300\"\n >\n {saveError}\n </p>\n ) : null}\n\n {editing ? (\n <EditFields\n register={register}\n draft={draft}\n onField={setField}\n />\n ) : (\n <dl className=\"divide-y divide-neutral-100 dark:divide-neutral-900\">\n {fields.map((key) => (\n <div key={key} className=\"grid grid-cols-3 gap-3 py-2.5\">\n <dt className=\"text-sm text-neutral-500 dark:text-neutral-400\">\n {fieldLabel(key)}\n </dt>\n <dd className=\"col-span-2 text-sm text-neutral-800 dark:text-neutral-200\">\n {formatValue(record[key])}\n </dd>\n </div>\n ))}\n </dl>\n )}\n </>\n )}\n </div>\n\n {/* Relation editor (read mode only) — editing keeps the drawer focused. */}\n {record && !loading && !error && !editing ? children : null}\n\n {record && editing ? (\n <footer className=\"flex items-center justify-end gap-2 border-t border-neutral-200 p-5 dark:border-neutral-800\">\n <button\n type=\"button\"\n onClick={cancelEditing}\n disabled={saving}\n className=\"rounded-md px-3 py-1.5 text-sm text-neutral-600 hover:bg-neutral-100 disabled:opacity-50 dark:text-neutral-300 dark:hover:bg-neutral-900\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={onSave}\n disabled={saving}\n className=\"rounded-md bg-neutral-900 px-3 py-1.5 text-sm font-medium text-white hover:bg-neutral-700 disabled:opacity-50 dark:bg-neutral-100 dark:text-neutral-900 dark:hover:bg-neutral-300\"\n >\n {saving ? \"Saving…\" : \"Save\"}\n </button>\n </footer>\n ) : record ? (\n <footer className=\"border-t border-neutral-200 p-5 text-xs text-neutral-400 dark:border-neutral-800\">\n {record.id} · updated {formatValue(record.updatedAt)}\n </footer>\n ) : null}\n </DrawerShell>\n );\n}\n\n/** The \"Why?\" affordance on Confidence — the understanding-layer Reveal\n * (OPS-1276). Confidence is the signed, weighted average of concluded readings;\n * opening this shows which experiments move the number, how close each running\n * experiment is to concluding, and the trajectory over time. The panel mounts\n * only when open, so it loads the evidence lazily and never crowds the hero. */\nfunction WhyReveal({\n open,\n onToggle,\n assumption,\n basePath,\n}: {\n open: boolean;\n onToggle: () => void;\n assumption: AnyRecord;\n basePath?: string;\n}) {\n return (\n <div className=\"mt-3\">\n <button\n type=\"button\"\n onClick={onToggle}\n aria-expanded={open}\n className=\"text-xs font-medium text-neutral-500 underline underline-offset-2 hover:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-100\"\n >\n Why?\n </button>\n {open ? (\n <UnderstandingPanel assumption={assumption} basePath={basePath} />\n ) : null}\n </div>\n );\n}\n\nfunction ConflictBanner({\n message,\n onReload,\n}: {\n message: string;\n onReload: () => void;\n}) {\n return (\n <div\n role=\"alert\"\n className=\"mb-4 rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-900 dark:bg-amber-950/40\"\n >\n <p className=\"text-sm text-amber-800 dark:text-amber-200\">{message}</p>\n <button\n type=\"button\"\n onClick={onReload}\n className=\"mt-2 rounded-md border border-amber-300 px-2.5 py-1 text-xs font-medium text-amber-900 hover:bg-amber-100 dark:border-amber-800 dark:text-amber-100 dark:hover:bg-amber-900/40\"\n >\n Reload the latest\n </button>\n </div>\n );\n}\n\nfunction EditFields({\n register,\n draft,\n onField,\n}: {\n register: Collection;\n draft: Draft;\n onField: (key: string, value: string) => void;\n}) {\n return (\n <div className=\"space-y-4\">\n {editableFields(register).map((field) => (\n <FieldInput\n key={field.key}\n field={field}\n value={draft[field.key]}\n onChange={(v) => onField(field.key, v)}\n />\n ))}\n </div>\n );\n}\n\nconst INPUT_CLASS =\n \"w-full rounded-md border border-neutral-300 bg-white px-2.5 py-1.5 text-sm text-neutral-900 focus:border-neutral-500 focus:outline-none dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-50\";\n\nfunction FieldInput({\n field,\n value,\n onChange,\n}: {\n field: FieldEditor;\n value: string | undefined;\n onChange: (value: string) => void;\n}) {\n // Field keys can contain spaces (\"5 Whys\"); slugify for a valid DOM id.\n const id = `field-${field.key.replace(/\\s+/g, \"-\")}`;\n return (\n <div>\n <label\n htmlFor={id}\n className=\"mb-1 block text-xs font-medium text-neutral-500 dark:text-neutral-400\"\n >\n {field.label}\n </label>\n {field.kind === \"textarea\" ? (\n <textarea\n id={id}\n value={String(value ?? \"\")}\n onChange={(e) => onChange(e.target.value)}\n rows={3}\n className={INPUT_CLASS}\n />\n ) : field.kind === \"select\" ? (\n <select\n id={id}\n value={String(value ?? \"\")}\n onChange={(e) => onChange(e.target.value)}\n className={INPUT_CLASS}\n >\n {field.nullable ? <option value=\"\">—</option> : null}\n {field.options?.map((opt) => (\n <option key={opt} value={opt}>\n {opt}\n </option>\n ))}\n </select>\n ) : (\n <input\n id={id}\n type={field.kind === \"number\" ? \"number\" : \"text\"}\n value={String(value ?? \"\")}\n onChange={(e) => onChange(e.target.value)}\n className={INPUT_CLASS}\n />\n )}\n </div>\n );\n}\n","import { useEffect, useRef, type ReactNode } from \"react\";\n\nexport interface DrawerShellProps {\n /** Whether the drawer is mounted/visible. */\n open: boolean;\n onClose: () => void;\n /** Accessible name for the dialog. */\n ariaLabel: string;\n /**\n * When true the whole panel scrolls (the read drawer); when false the panel\n * clips and an inner element scrolls, so a sticky footer stays put (the\n * create form). Defaults to true.\n */\n scroll?: boolean;\n children: ReactNode;\n}\n\n/**\n * The right-hand slide-over chrome shared by the record drawer and the create\n * drawer: a click-to-dismiss overlay, an `aria-modal` panel, Escape-to-close,\n * and focus moved into the panel on open so keyboard users aren't stranded\n * behind the modal. Everything inside is the caller's content.\n */\nexport function DrawerShell({\n open,\n onClose,\n ariaLabel,\n scroll = true,\n children,\n}: DrawerShellProps) {\n const panelRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n document.addEventListener(\"keydown\", onKey);\n panelRef.current?.focus();\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, onClose]);\n\n if (!open) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex justify-end\">\n {/* Overlay — click to dismiss. */}\n <button\n type=\"button\"\n aria-label=\"Close\"\n onClick={onClose}\n className=\"absolute inset-0 bg-neutral-950/30 backdrop-blur-sm\"\n />\n <div\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={ariaLabel}\n tabIndex={-1}\n className={`relative flex h-full w-full max-w-md flex-col border-l border-neutral-200 bg-white shadow-xl outline-none dark:border-neutral-800 dark:bg-neutral-950 ${\n scroll ? \"overflow-y-auto\" : \"overflow-hidden\"\n }`}\n >\n {children}\n </div>\n </div>\n );\n}\n","/**\n * The pure edit-logic seam — which fields a register lets you edit, how a\n * form draft maps back to a version-guarded patch, and the plain-language\n * conflict copy. Kept as pure data/functions (no DOM) so the edit behaviour\n * is unit-testable exactly like `columns.ts`; the drawer component consumes it.\n *\n * Derived numbers (Confidence, Risk, Derived Impact, Strength) are never\n * editable — they are computed server-side on write (spec user story 4/11),\n * so they never appear here. Relation links are set through `link`, a separate\n * story, so relation-id fields aren't editable inputs either.\n */\nimport type { AnyRecord, Collection } from \"@validation-os/core\";\n\n/** The plain-language conflict prompt — never version jargon (user story 12).\n * The API returns its own copy on a 409; this is the client-side fallback. */\nexport const CONFLICT_MESSAGE =\n \"Someone edited this while you had it open — your changes are safe, \" +\n \"take a look before saving again.\";\n\nexport type FieldKind = \"text\" | \"textarea\" | \"number\" | \"select\";\n\nexport interface FieldEditor {\n /** The record key this editor writes. */\n key: string;\n /** Plain-language label shown beside the input. */\n label: string;\n kind: FieldKind;\n /** For `select`: the allowed options (a leading blank = \"unset\"). */\n options?: readonly string[];\n /** An empty input clears the field to `null` rather than \"\". */\n nullable?: boolean;\n}\n\nconst t = (key: string, label: string): FieldEditor => ({ key, label, kind: \"text\" });\nconst area = (key: string, label: string): FieldEditor => ({\n key,\n label,\n kind: \"textarea\",\n});\nconst num = (key: string, label: string): FieldEditor => ({\n key,\n label,\n kind: \"number\",\n nullable: true,\n});\nconst sel = (\n key: string,\n label: string,\n options: readonly string[],\n nullable = false,\n): FieldEditor => ({ key, label, kind: \"select\", options, nullable });\n\nconst READING_RUNGS = [\n \"Opinion\",\n \"Pitch-deck reaction\",\n \"Anecdotal\",\n \"Desk research\",\n \"Survey at scale\",\n \"Prototype usage\",\n \"Signed intent\",\n \"Paying users\",\n] as const;\nconst SOURCE_QUALITY = [\"1\", \"0.7\", \"0.5\"] as const;\n\n/**\n * The editable fields per register, in the order they render. Hand-scored and\n * descriptive fields only — derived numbers, ids/timestamps, and relation\n * links are deliberately excluded.\n */\nconst EDITORS: Record<Collection, FieldEditor[]> = {\n assumptions: [\n t(\"Title\", \"Assumption\"),\n area(\"Description\", \"Description\"),\n num(\"Impact\", \"Impact\"),\n sel(\"Status\", \"Status\", [\"Draft\", \"Live\", \"Invalidated\"]),\n t(\"Lens\", \"Lens\"),\n area(\"5 Whys\", \"5 Whys\"),\n area(\"Metric for truth\", \"Metric for truth\"),\n area(\"Scoring justification\", \"Scoring justification\"),\n // `moot` is deliberately not editable here — mooting an assumption is a\n // gated business action (see core `relations.ts`), not a free-form toggle.\n ],\n experiments: [\n t(\"Title\", \"Experiment\"),\n t(\"Instrument\", \"Instrument\"),\n sel(\"Feasibility\", \"Feasibility\", [\"High\", \"Medium\", \"Low\"], true),\n sel(\"Status\", \"Status\", [\"Running\", \"Closed\"]),\n sel(\n \"closureReason\",\n \"Closure reason\",\n [\"Completed\", \"Early-stop\", \"Kill\"],\n true,\n ),\n t(\"Date\", \"Date\"),\n ],\n readings: [\n t(\"Title\", \"Reading\"),\n t(\"Source\", \"Source\"),\n sel(\"Rung\", \"Rung\", READING_RUNGS),\n sel(\"Result\", \"Result\", [\"Validated\", \"Invalidated\", \"Inconclusive\"]),\n sel(\"Representativeness\", \"Representativeness\", SOURCE_QUALITY),\n sel(\"Credibility\", \"Credibility\", SOURCE_QUALITY),\n sel(\"magnitudeBand\", \"Magnitude band\", [\"Low\", \"Typical\", \"High\"], true),\n t(\"Date\", \"Date\"),\n ],\n goals: [\n t(\"Title\", \"Goal\"),\n sel(\"Status\", \"Status\", [\"Draft\", \"Active\", \"Closed\"]),\n sel(\"Outcome\", \"Outcome\", [\"Achieved\", \"Missed\", \"Dropped\"], true),\n t(\"Date\", \"Date\"),\n ],\n decisions: [\n t(\"Title\", \"Decision\"),\n sel(\"Status\", \"Status\", [\"Active\", \"Provisional\", \"Superseded\", \"Reversed\"]),\n ],\n glossary: [\n t(\"Title\", \"Term\"),\n sel(\"Status\", \"Status\", [\"Active\", \"Provisional\", \"Superseded\"]),\n ],\n people: [t(\"Name\", \"Name\"), t(\"Role\", \"Role\"), t(\"Segment\", \"Segment\")],\n};\n\n/** The fields a register lets you edit, in render order. */\nexport function editableFields(register: Collection): FieldEditor[] {\n return EDITORS[register];\n}\n\n/** A form draft is a plain string map keyed by field — what the inputs hold. */\nexport type Draft = Record<string, string>;\n\n/** Build the initial form draft from a record: every editable field becomes a\n * string input value, and missing values become empty inputs. */\nexport function draftFrom(register: Collection, record: AnyRecord): Draft {\n const draft: Draft = {};\n for (const f of editableFields(register)) {\n const value = record[f.key];\n draft[f.key] = value === null || value === undefined ? \"\" : String(value);\n }\n return draft;\n}\n\n/** Coerce one draft value back to its stored shape, honouring kind/nullable. */\nfunction coerce(field: FieldEditor, raw: string): unknown {\n const str = String(raw);\n if (field.kind === \"number\") {\n if (str.trim() === \"\") return null;\n const n = Number(str);\n return Number.isNaN(n) ? null : n;\n }\n // An empty select is \"unset\" → null; an empty nullable text field clears too.\n if (str === \"\" && (field.kind === \"select\" || field.nullable)) return null;\n return str;\n}\n\n/** Absent and empty values compare equal, so an untouched field isn't a change. */\nfunction norm(value: unknown): unknown {\n return value === undefined || value === \"\" ? null : value;\n}\n\n/**\n * A version-guarded patch from a draft: the loaded `version` plus only the\n * fields whose value actually changed (so an untouched save doesn't churn\n * unrelated fields). The API rejects a stale version with a 409.\n */\nexport function buildPatch(\n register: Collection,\n original: AnyRecord,\n draft: Draft,\n): Record<string, unknown> {\n const patch: Record<string, unknown> = { version: original.version };\n for (const field of editableFields(register)) {\n const next = coerce(field, draft[field.key] ?? \"\");\n if (norm(next) !== norm(original[field.key])) patch[field.key] = next;\n }\n return patch;\n}\n\n/** Whether a draft differs from the record it was loaded from. */\nexport function hasEdits(\n register: Collection,\n original: AnyRecord,\n draft: Draft,\n): boolean {\n return Object.keys(buildPatch(register, original, draft)).length > 1;\n}\n","import { useCallback, useEffect, useState } from \"react\";\nimport type { AnyRecord, Collection } from \"@validation-os/core\";\nimport { CONFLICT_MESSAGE } from \"./edit.js\";\n\n/**\n * Client hooks that read the register over HTTP through the API read routes\n * (`GET {basePath}/{register}` and `GET {basePath}/{register}/{id}`). The API\n * reaches Firestore server-side through the adapter, so no backend credentials\n * ever reach the browser — the browser only ever speaks to the Clerk-gated API.\n */\n\ninterface AsyncState<T> {\n data: T | null;\n loading: boolean;\n error: string | null;\n}\n\n/** Both read routes wrap their payload in a `{ data }` envelope. */\nfunction pickData<T>(body: unknown): T {\n return (body as { data: T }).data;\n}\n\n/**\n * Fetch one JSON resource, tracking loading/error. A null `url` means \"idle\"\n * (nothing to load yet) — the drawer's get hook uses this until a row is\n * clicked. Returns a `refresh` that re-runs the fetch. This is the shared\n * engine behind both `useList` and `useRecord`.\n */\nfunction useJsonResource<T>(url: string | null): AsyncState<T> & {\n refresh: () => void;\n} {\n const [state, setState] = useState<AsyncState<T>>({\n data: null,\n loading: url !== null,\n error: null,\n });\n const [tick, setTick] = useState(0);\n\n useEffect(() => {\n if (url === null) {\n setState({ data: null, loading: false, error: null });\n return;\n }\n let live = true;\n setState((s) => ({ ...s, loading: true }));\n fetch(url)\n .then(async (res) => {\n if (!res.ok) throw new Error(`Request to ${url} failed (${res.status})`);\n return pickData<T>(await res.json());\n })\n .then((data) => live && setState({ data, loading: false, error: null }))\n .catch(\n (e: unknown) =>\n live &&\n setState({\n data: null,\n loading: false,\n error: e instanceof Error ? e.message : \"Failed to load\",\n }),\n );\n return () => {\n live = false;\n };\n }, [url, tick]);\n\n const refresh = useCallback(() => setTick((t) => t + 1), []);\n return { ...state, refresh };\n}\n\nexport interface UseListResult {\n records: AnyRecord[] | null;\n loading: boolean;\n error: string | null;\n refresh: () => void;\n}\n\n/** Fetch every row of a register. */\nexport function useList(register: Collection, basePath = \"/api\"): UseListResult {\n const { data, loading, error, refresh } = useJsonResource<AnyRecord[]>(\n `${basePath}/${register}`,\n );\n return { records: data, loading, error, refresh };\n}\n\nexport interface UseRecordResult {\n record: AnyRecord | null;\n loading: boolean;\n error: string | null;\n /** Re-fetch the record — the re-fetch path after a save or edit conflict. */\n refresh: () => void;\n}\n\n/**\n * Fetch one record by id. `id` may be null (nothing open) — the hook stays\n * idle until an id is supplied, so it drives a drawer that opens on row click.\n * `refresh` re-fetches: the edit flow calls it after a save, and it's the\n * re-fetch path offered when a write hits a concurrent-edit conflict.\n */\nexport function useRecord(\n register: Collection,\n id: string | null,\n basePath = \"/api\",\n): UseRecordResult {\n const url = id\n ? `${basePath}/${register}/${encodeURIComponent(id)}`\n : null;\n const { data, loading, error, refresh } = useJsonResource<AnyRecord>(url);\n return { record: data, loading, error, refresh };\n}\n\n/** The outcome of a save: the recomputed record, or a rejection with a\n * plain-language message (a concurrent-edit conflict, or another failure). */\nexport type SaveResult =\n | { ok: true; record: AnyRecord }\n | { ok: false; conflict: boolean; message: string };\n\nconst SAVE_FAILED = \"Couldn't save your changes — please try again.\";\n\n/**\n * Map an update response (status + parsed body) to a `SaveResult`. Pure, so\n * the 409/error/ok branching is unit-testable without a DOM. A 409 is a\n * concurrent-edit conflict, surfaced in the API's plain-language copy (or the\n * `CONFLICT_MESSAGE` fallback) — never version jargon (spec user story 12).\n */\nexport function interpretSave(status: number, body: unknown): SaveResult {\n if (status >= 200 && status < 300) {\n return { ok: true, record: pickData<AnyRecord>(body) };\n }\n const message = (body as { message?: unknown } | null)?.message;\n const text = typeof message === \"string\" ? message : null;\n if (status === 409) {\n return { ok: false, conflict: true, message: text ?? CONFLICT_MESSAGE };\n }\n return { ok: false, conflict: false, message: text ?? SAVE_FAILED };\n}\n\nexport interface UseUpdateResult {\n /** PATCH `{ version, ...patch }`; resolves to the recomputed record or a\n * rejection. Never throws — callers branch on `result.ok`. */\n save: (id: string, patch: Record<string, unknown>) => Promise<SaveResult>;\n saving: boolean;\n /** Plain-language conflict prompt when a concurrent edit was detected. */\n conflict: string | null;\n /** Plain-language message for any other save failure. */\n error: string | null;\n /** Clear conflict/error state (e.g. when re-entering edit mode). */\n reset: () => void;\n}\n\n/**\n * Version-guarded update over the API write route. A stale version comes back\n * as a 409, which we surface as a gentle, jargon-free `conflict` message (spec\n * user story 12) — the API sends the copy, and `CONFLICT_MESSAGE` is the\n * fallback. Derived values are recomputed server-side, so the returned record\n * carries the authoritative numbers, never anything the client computed.\n */\nexport function useUpdate(\n register: Collection,\n basePath = \"/api\",\n): UseUpdateResult {\n const [saving, setSaving] = useState(false);\n const [conflict, setConflict] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const save = useCallback(\n async (id: string, patch: Record<string, unknown>): Promise<SaveResult> => {\n setSaving(true);\n setConflict(null);\n setError(null);\n try {\n const res = await fetch(\n `${basePath}/${register}/${encodeURIComponent(id)}`,\n {\n method: \"PATCH\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(patch),\n },\n );\n const body = await res.json().catch(() => null);\n const result = interpretSave(res.status, body);\n if (!result.ok && result.conflict) setConflict(result.message);\n else if (!result.ok) setError(result.message);\n return result;\n } catch (e) {\n const message =\n e instanceof Error ? e.message : \"Couldn't save your changes.\";\n setError(message);\n return { ok: false, conflict: false, message };\n } finally {\n setSaving(false);\n }\n },\n [register, basePath],\n );\n\n const reset = useCallback(() => {\n setConflict(null);\n setError(null);\n }, []);\n\n return { save, saving, conflict, error, reset };\n}\n","/**\n * The understanding layer's data join (OPS-1276). Pure: given an assumption\n * and the readings + experiments registers, it produces everything the Reveal\n * shows — the experiments testing the belief (each with how hard it moves\n * Confidence and how close it is to concluding), the goal/direct evidence that\n * also moves the number, and the Confidence-over-time trajectory.\n *\n * The record → derivation-input mapping is `@validation-os/core`'s shared\n * `toReadingInput`, so a reading is read here exactly as it is server-side.\n */\nimport {\n toReadingInput,\n type AnyRecord,\n type BarLine,\n} from \"@validation-os/core\";\nimport {\n confidenceAttribution,\n confidenceTrajectory,\n experimentProgress,\n isConcluded,\n type MoverKind,\n type Progress,\n type TrajectoryPoint,\n} from \"@validation-os/core/derivation\";\n\n/** An experiment testing this assumption: how hard it moves Confidence, and\n * how close it is to concluding. `contribution` is 0 for a running experiment\n * that has not produced a concluded reading yet — it still shows, so its\n * progress-to-conclusion is visible. */\nexport interface ExperimentView {\n experimentId: string;\n title: string | null;\n status: string | null;\n /** Signed push on Confidence; 0 until a concluded reading lands. */\n contribution: number;\n magnitude: number;\n /** Concluded readings this experiment has produced for the belief. */\n readingCount: number;\n progress: Progress | null;\n /** Concluded/closed — reads as done rather than in-flight. */\n done: boolean;\n}\n\n/** Goal-rung or direct evidence that moves Confidence but is not an experiment. */\nexport interface OtherMover {\n key: string;\n kind: Exclude<MoverKind, \"experiment\">;\n goalId: string | null;\n contribution: number;\n magnitude: number;\n readingCount: number;\n}\n\nexport interface Understanding {\n /** The same Confidence the derived box shows. */\n confidence: number;\n /** Experiments testing this belief, ranked by how hard they push. */\n experiments: ExperimentView[];\n /** Goal/direct evidence that also moves the number. */\n otherMovers: OtherMover[];\n /** Confidence over time; empty when no concluded reading is dated. */\n trajectory: TrajectoryPoint[];\n /** Concluded readings feeding the number, across all sources. */\n readingCount: number;\n}\n\nfunction str(v: unknown): string | null {\n return typeof v === \"string\" && v !== \"\" ? v : null;\n}\n\n/** Does this experiment pre-register a bar line against the given assumption? */\nfunction testsAssumption(exp: AnyRecord, assumptionId: string): boolean {\n const bars = exp.barLines as BarLine[] | undefined;\n if (bars?.some((b) => b.assumptionId === assumptionId)) return true;\n const ids = exp.barLineAssumptionIds as string[] | undefined;\n return ids?.includes(assumptionId) ?? false;\n}\n\nexport function buildUnderstanding(\n assumption: AnyRecord,\n readings: AnyRecord[],\n experiments: AnyRecord[],\n): Understanding {\n const mine = readings.filter((r) => r.assumptionId === assumption.id);\n const inputs = mine.map(toReadingInput);\n const { confidence, movers } = confidenceAttribution(inputs);\n\n const experimentsById = new Map(experiments.map((e) => [e.id, e]));\n const moverByExperiment = new Map(\n movers.filter((m) => m.kind === \"experiment\").map((m) => [m.experimentId!, m]),\n );\n\n // Every experiment testing this belief — whether or not it has moved the\n // number yet — plus any experiment a reading points at that isn't linked via\n // bar lines. So a freshly-started experiment with no readings still shows.\n const experimentIds = new Set<string>([\n ...experiments.filter((e) => testsAssumption(e, assumption.id)).map((e) => e.id),\n ...moverByExperiment.keys(),\n ]);\n\n const experimentViews: ExperimentView[] = [...experimentIds].map((id) => {\n const exp = experimentsById.get(id);\n const mover = moverByExperiment.get(id);\n const bars = (exp?.barLines as BarLine[] | undefined) ?? [];\n const progress = bars.length ? experimentProgress(bars) : null;\n const status = exp ? str(exp.Status) : null;\n return {\n experimentId: id,\n title: exp ? str(exp.Title) : null,\n status,\n contribution: mover?.contribution ?? 0,\n magnitude: mover?.magnitude ?? 0,\n readingCount: mover?.readingCount ?? 0,\n progress,\n done: status === \"Closed\" || progress?.concluded === true,\n };\n });\n // Strongest movers first; among non-movers, in-flight before done, then id.\n experimentViews.sort(\n (a, b) =>\n b.magnitude - a.magnitude ||\n Number(a.done) - Number(b.done) ||\n a.experimentId.localeCompare(b.experimentId),\n );\n\n const otherMovers: OtherMover[] = movers\n .filter((m) => m.kind !== \"experiment\")\n .map((m) => ({\n key: m.key,\n kind: m.kind as OtherMover[\"kind\"],\n goalId: m.goalId,\n contribution: m.contribution,\n magnitude: m.magnitude,\n readingCount: m.readingCount,\n }));\n\n return {\n confidence,\n experiments: experimentViews,\n otherMovers,\n trajectory: confidenceTrajectory(inputs),\n readingCount: inputs.filter((r) => isConcluded(r.result)).length,\n };\n}\n","import type { ReactNode } from \"react\";\nimport type { AnyRecord } from \"@validation-os/core\";\nimport type { TrajectoryPoint } from \"@validation-os/core/derivation\";\nimport { useList } from \"./use-records.js\";\nimport {\n buildUnderstanding,\n type ExperimentView,\n type OtherMover,\n} from \"./understanding.js\";\n\n/**\n * The understanding layer behind the Confidence \"Why?\" (OPS-1276): which\n * experiments move the number (ranked by push) and how close each running\n * experiment is to concluding, the goal/direct evidence that also moves it,\n * and Confidence over time. It lazy-loads the readings + experiments registers\n * (it only mounts when the Reveal is open), then derives everything through the\n * shared derivation module. The derived box stays the hero; this is the tucked-\n * away detail.\n */\nexport function UnderstandingPanel({\n assumption,\n basePath,\n}: {\n assumption: AnyRecord;\n basePath?: string;\n}) {\n const readings = useList(\"readings\", basePath);\n const experiments = useList(\"experiments\", basePath);\n\n if (readings.loading || experiments.loading) {\n return <Muted>Working out what moved the number…</Muted>;\n }\n if (readings.error || experiments.error) {\n return <Muted>Couldn't load the evidence behind this number.</Muted>;\n }\n\n const u = buildUnderstanding(\n assumption,\n readings.records ?? [],\n experiments.records ?? [],\n );\n\n if (u.experiments.length === 0 && u.otherMovers.length === 0) {\n return (\n <Muted>\n No experiments or concluded readings yet — Confidence rests on the\n neutral prior alone.\n </Muted>\n );\n }\n\n // One scale for every push bar so experiments and other evidence compare.\n const maxMagnitude = Math.max(\n ...u.experiments.map((e) => e.magnitude),\n ...u.otherMovers.map((m) => m.magnitude),\n 0.01,\n );\n\n return (\n <div className=\"mt-2 space-y-4 rounded-lg bg-neutral-50 p-3 dark:bg-neutral-900\">\n {u.experiments.length ? (\n <section>\n <Heading>What's moving Confidence</Heading>\n <ul className=\"space-y-2.5\">\n {u.experiments.map((e) => (\n <li key={e.experimentId}>\n <PushRow\n label={e.title ?? `Experiment ${e.experimentId}`}\n contribution={e.contribution}\n magnitude={e.magnitude}\n max={maxMagnitude}\n />\n <p className=\"mt-1 text-xs text-neutral-500 dark:text-neutral-400\">\n {experimentDetail(e)}\n </p>\n </li>\n ))}\n </ul>\n </section>\n ) : null}\n\n {u.otherMovers.length ? (\n <section>\n <Heading>Other evidence</Heading>\n <ul className=\"space-y-2.5\">\n {u.otherMovers.map((m) => (\n <li key={m.key}>\n <PushRow\n label={otherMoverLabel(m)}\n contribution={m.contribution}\n magnitude={m.magnitude}\n max={maxMagnitude}\n />\n </li>\n ))}\n </ul>\n </section>\n ) : null}\n\n <section>\n <Heading>Confidence over time</Heading>\n <Trajectory points={u.trajectory} />\n </section>\n </div>\n );\n}\n\n/** A labelled row with a signed contribution and a push bar (width ∝ push). */\nfunction PushRow({\n label,\n contribution,\n magnitude,\n max,\n}: {\n label: string;\n contribution: number;\n magnitude: number;\n max: number;\n}) {\n const up = contribution >= 0;\n const moving = magnitude > 0;\n return (\n <>\n <div className=\"flex items-baseline justify-between gap-2\">\n <span className=\"truncate text-sm text-neutral-800 dark:text-neutral-200\">\n {label}\n </span>\n {moving ? (\n <span\n className={`shrink-0 text-sm font-semibold tabular-nums ${\n up\n ? \"text-emerald-600 dark:text-emerald-400\"\n : \"text-rose-600 dark:text-rose-400\"\n }`}\n >\n {up ? \"+\" : \"\"}\n {contribution}\n </span>\n ) : (\n <span className=\"shrink-0 text-xs text-neutral-400\">no readings yet</span>\n )}\n </div>\n <div className=\"mt-1 h-1.5 rounded-full bg-neutral-200 dark:bg-neutral-800\">\n <div\n className={`h-full rounded-full ${\n up ? \"bg-emerald-500\" : \"bg-rose-500\"\n }`}\n style={{ width: `${(magnitude / max) * 100}%` }}\n />\n </div>\n </>\n );\n}\n\nfunction Trajectory({ points }: { points: TrajectoryPoint[] }) {\n if (points.length < 2) {\n return (\n <p className=\"text-xs text-neutral-500 dark:text-neutral-400\">\n {points.length === 1\n ? `One dated reading so far — Confidence ${points[0]!.confidence} on ${points[0]!.date}.`\n : \"No dated readings yet to chart a trajectory.\"}\n </p>\n );\n }\n return <Sparkline points={points} />;\n}\n\nconst SPARK_W = 260;\nconst SPARK_H = 44;\n\n/** A tiny signed sparkline: confidence −100…100 with a zero baseline. */\nfunction Sparkline({ points }: { points: TrajectoryPoint[] }) {\n const n = points.length;\n const x = (i: number) => (i / (n - 1)) * SPARK_W;\n const y = (c: number) => ((100 - c) / 200) * SPARK_H; // +100 top, −100 bottom\n const path = points.map((p, i) => `${x(i)},${y(p.confidence)}`).join(\" \");\n const first = points[0]!;\n const last = points[n - 1]!;\n return (\n <div>\n <svg\n viewBox={`0 0 ${SPARK_W} ${SPARK_H}`}\n className=\"w-full\"\n role=\"img\"\n aria-label={`Confidence moved from ${first.confidence} to ${last.confidence}`}\n >\n <line\n x1={0}\n x2={SPARK_W}\n y1={y(0)}\n y2={y(0)}\n className=\"stroke-neutral-300 dark:stroke-neutral-700\"\n strokeWidth={1}\n strokeDasharray=\"3 3\"\n />\n <polyline\n points={path}\n fill=\"none\"\n className=\"stroke-neutral-700 dark:stroke-neutral-200\"\n strokeWidth={1.5}\n strokeLinejoin=\"round\"\n strokeLinecap=\"round\"\n />\n <circle\n cx={x(n - 1)}\n cy={y(last.confidence)}\n r={2.5}\n className=\"fill-neutral-900 dark:fill-neutral-50\"\n />\n </svg>\n <div className=\"mt-1 flex justify-between text-[11px] text-neutral-400\">\n <span>{first.date}</span>\n <span className=\"tabular-nums text-neutral-600 dark:text-neutral-300\">\n now {last.confidence}\n </span>\n </div>\n </div>\n );\n}\n\nfunction experimentDetail(e: ExperimentView): string {\n const evidence =\n e.readingCount === 0\n ? \"No readings yet\"\n : `${e.readingCount} reading${e.readingCount === 1 ? \"\" : \"s\"}`;\n const p = e.progress;\n if (e.done) {\n return p\n ? `Concluded · ${p.total} of ${p.total} bars settled`\n : `Concluded · ${evidence}`;\n }\n if (!p || p.total === 0) return `${evidence} · no pre-registered bars`;\n return `${evidence} · ${p.settled} of ${p.total} bars settled · ${p.toGo} to go`;\n}\n\nfunction otherMoverLabel(m: OtherMover): string {\n if (m.kind === \"goal\") return `Goal ${m.goalId ?? \"\"}`.trim();\n return m.readingCount === 1 ? \"A direct reading\" : \"Direct readings\";\n}\n\nfunction Heading({ children }: { children: ReactNode }) {\n return (\n <h4 className=\"mb-2 text-xs font-medium uppercase tracking-wide text-neutral-400\">\n {children}\n </h4>\n );\n}\n\nfunction Muted({ children }: { children: ReactNode }) {\n return (\n <p className=\"mt-2 rounded-lg bg-neutral-50 p-3 text-xs leading-relaxed text-neutral-600 dark:bg-neutral-900 dark:text-neutral-300\">\n {children}\n </p>\n );\n}\n","import { useMemo, useState } from \"react\";\nimport type { Collection } from \"@validation-os/core\";\nimport { REGISTER_LABEL } from \"./labels.js\";\nimport {\n emptyDraft,\n formFieldsFor,\n missingRequired,\n toCreatePayload,\n type FormField,\n} from \"./form-fields.js\";\nimport { FIELD_CONTROL_CLASS, FIELD_LABEL_CLASS } from \"./field-styles.js\";\nimport { useCreate } from \"./use-mutations.js\";\n\nexport interface RecordFormProps {\n register: Collection;\n basePath?: string;\n /** Called with the new record's id after a successful create. */\n onCreated: (id: string) => void;\n onCancel: () => void;\n}\n\n/**\n * The \"new record\" form for one register (spec user story 13). Editable own-\n * fields only — derived numbers are computed server-side and marked\n * computed-not-editable elsewhere; relations are wired by linking after the\n * record exists. Presence-gap fields (5 Whys, etc.) appear as first-class\n * textareas. On submit it POSTs through the API, which recomputes on write.\n */\nexport function RecordForm({\n register,\n basePath,\n onCreated,\n onCancel,\n}: RecordFormProps) {\n const fields = useMemo(() => formFieldsFor(register), [register]);\n const [draft, setDraft] = useState<Record<string, string>>(() =>\n emptyDraft(register),\n );\n const { create, saving, error } = useCreate(register, basePath);\n\n const missing = missingRequired(register, draft);\n const set = (key: string, value: string) =>\n setDraft((d) => ({ ...d, [key]: value }));\n\n const onSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (missing.length > 0 || saving) return;\n try {\n const created = await create(toCreatePayload(register, draft));\n onCreated(created.id);\n } catch {\n // The hook surfaces the message in `error`; keep the form open to retry.\n }\n };\n\n return (\n <form onSubmit={onSubmit} className=\"flex h-full flex-col\">\n <div className=\"flex-1 space-y-4 overflow-y-auto p-5\">\n {fields.map((field) => (\n <Field\n key={field.key}\n field={field}\n value={draft[field.key] ?? \"\"}\n onChange={(v) => set(field.key, v)}\n />\n ))}\n {error ? (\n <p className=\"text-sm text-red-600 dark:text-red-400\">{error}</p>\n ) : null}\n </div>\n\n <footer className=\"flex items-center justify-end gap-2 border-t border-neutral-200 p-5 dark:border-neutral-800\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"rounded-md px-3 py-2 text-sm text-neutral-600 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={missing.length > 0 || saving}\n title={\n missing.length > 0 ? `Fill in: ${missing.join(\", \")}` : undefined\n }\n className=\"rounded-md bg-blue-600 px-3 py-2 text-sm font-medium text-white hover:bg-blue-500 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {saving ? \"Creating…\" : `Create ${REGISTER_LABEL[register]}`}\n </button>\n </footer>\n </form>\n );\n}\n\nfunction Field({\n field,\n value,\n onChange,\n}: {\n field: FormField;\n value: string;\n onChange: (value: string) => void;\n}) {\n const id = `field-${field.key}`;\n return (\n <div>\n <label htmlFor={id} className={FIELD_LABEL_CLASS}>\n {field.label}\n {field.required ? <span className=\"text-red-500\"> *</span> : null}\n </label>\n {field.kind === \"textarea\" ? (\n <textarea\n id={id}\n value={value}\n rows={3}\n placeholder={field.placeholder}\n onChange={(e) => onChange(e.target.value)}\n className={FIELD_CONTROL_CLASS}\n />\n ) : field.kind === \"select\" ? (\n <select\n id={id}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n className={FIELD_CONTROL_CLASS}\n >\n <option value=\"\">—</option>\n {field.options?.map((opt) => (\n <option key={opt} value={opt}>\n {opt}\n </option>\n ))}\n </select>\n ) : (\n <input\n id={id}\n type={field.kind === \"number\" ? \"number\" : \"text\"}\n value={value}\n placeholder={field.placeholder}\n onChange={(e) => onChange(e.target.value)}\n className={FIELD_CONTROL_CLASS}\n />\n )}\n </div>\n );\n}\n","/**\n * The editable-field spec for the \"new record\" form, per register — kept as\n * pure data + functions (like `columns.ts`) so the create form is unit-testable\n * without a DOM. Only a record's own scalar fields appear here: derived numbers\n * are computed server-side (never typed), and relations are wired separately by\n * linking (see `relations.ts` / the relation editor), so id/relation fields are\n * deliberately absent. Vocabularies mirror `core`'s type unions.\n */\nimport {\n GOAL_RUNG_VALUES,\n TESTING_RUNGS,\n type Collection,\n} from \"@validation-os/core\";\nimport type { FieldKind } from \"./edit.js\";\n\n// The create form and the edit form speak the same field-kind vocabulary\n// (`FieldKind` lives in `edit.js`); the create-side spec adds `required` (a\n// blank blocks submit) and numeric `coerce` (a labelled numeric select → a\n// number), which the edit side doesn't need.\n\nexport interface FormField {\n /** Record field key (what the create payload uses). */\n key: string;\n /** Plain-language label a non-technical teammate reads. */\n label: string;\n kind: FieldKind;\n /** Options for a `select`. */\n options?: readonly string[];\n /** A `select`/`text` value that should be stored as a number. */\n coerce?: \"number\";\n required?: boolean;\n placeholder?: string;\n}\n\nconst ASSUMPTION_STATUS = [\"Draft\", \"Live\", \"Invalidated\"] as const;\nconst EXPERIMENT_STATUS = [\"Running\", \"Closed\"] as const;\nconst CLOSURE_REASON = [\"Completed\", \"Early-stop\", \"Kill\"] as const;\nconst GOAL_STATUS = [\"Draft\", \"Active\", \"Closed\"] as const;\nconst GOAL_OUTCOME = [\"Achieved\", \"Missed\", \"Dropped\"] as const;\nconst DECISION_STATUS = [\"Active\", \"Provisional\", \"Superseded\", \"Reversed\"] as const;\nconst GLOSSARY_STATUS = [\"Active\", \"Provisional\", \"Superseded\"] as const;\nconst RESULT = [\"Validated\", \"Invalidated\", \"Inconclusive\"] as const;\nconst MAGNITUDE = [\"Low\", \"Typical\", \"High\"] as const;\nconst FEASIBILITY = [\"High\", \"Medium\", \"Low\"] as const;\n/** The full evidence ladder, weakest first — the canonical runtime lists. */\nconst RUNGS = [...TESTING_RUNGS, ...GOAL_RUNG_VALUES] as const;\n/** Representativeness / Credibility picks, as labelled numeric options. */\nconst QUALITY = [\"1\", \"0.7\", \"0.5\"] as const;\n\nconst FIELDS: Record<Collection, FormField[]> = {\n assumptions: [\n { key: \"Title\", label: \"Assumption\", kind: \"text\", required: true },\n { key: \"Description\", label: \"Description\", kind: \"textarea\" },\n { key: \"Lens\", label: \"Lens\", kind: \"text\" },\n { key: \"Impact\", label: \"Impact (0–100)\", kind: \"number\" },\n { key: \"Status\", label: \"Status\", kind: \"select\", options: ASSUMPTION_STATUS },\n { key: \"5 Whys\", label: \"5 Whys\", kind: \"textarea\" },\n { key: \"Metric for truth\", label: \"Metric for truth\", kind: \"textarea\" },\n {\n key: \"Scoring justification\",\n label: \"Scoring justification\",\n kind: \"textarea\",\n },\n ],\n experiments: [\n { key: \"Title\", label: \"Experiment\", kind: \"text\", required: true },\n { key: \"Instrument\", label: \"Instrument\", kind: \"text\" },\n { key: \"Feasibility\", label: \"Feasibility\", kind: \"select\", options: FEASIBILITY },\n { key: \"Status\", label: \"Status\", kind: \"select\", options: EXPERIMENT_STATUS },\n {\n key: \"closureReason\",\n label: \"Closure reason\",\n kind: \"select\",\n options: CLOSURE_REASON,\n },\n { key: \"Date\", label: \"Date\", kind: \"text\", placeholder: \"YYYY-MM-DD\" },\n ],\n readings: [\n { key: \"Title\", label: \"Reading\", kind: \"text\", required: true },\n { key: \"Source\", label: \"Source\", kind: \"text\" },\n { key: \"Rung\", label: \"Rung\", kind: \"select\", options: RUNGS, required: true },\n { key: \"Result\", label: \"Result\", kind: \"select\", options: RESULT, required: true },\n {\n key: \"Representativeness\",\n label: \"Representativeness\",\n kind: \"select\",\n options: QUALITY,\n coerce: \"number\",\n },\n {\n key: \"Credibility\",\n label: \"Credibility\",\n kind: \"select\",\n options: QUALITY,\n coerce: \"number\",\n },\n {\n key: \"magnitudeBand\",\n label: \"Magnitude band\",\n kind: \"select\",\n options: MAGNITUDE,\n },\n { key: \"Date\", label: \"Date\", kind: \"text\", placeholder: \"YYYY-MM-DD\" },\n ],\n goals: [\n { key: \"Title\", label: \"Goal\", kind: \"text\", required: true },\n { key: \"Status\", label: \"Status\", kind: \"select\", options: GOAL_STATUS },\n { key: \"Outcome\", label: \"Outcome\", kind: \"select\", options: GOAL_OUTCOME },\n { key: \"Date\", label: \"Date\", kind: \"text\", placeholder: \"YYYY-MM-DD\" },\n ],\n decisions: [\n { key: \"Title\", label: \"Decision\", kind: \"text\", required: true },\n { key: \"Status\", label: \"Status\", kind: \"select\", options: DECISION_STATUS },\n ],\n glossary: [\n { key: \"Title\", label: \"Term\", kind: \"text\", required: true },\n { key: \"Status\", label: \"Status\", kind: \"select\", options: GLOSSARY_STATUS },\n ],\n people: [\n { key: \"Name\", label: \"Name\", kind: \"text\", required: true },\n { key: \"Role\", label: \"Role\", kind: \"text\" },\n { key: \"Segment\", label: \"Segment\", kind: \"text\" },\n ],\n};\n\n/** The editable fields for a register's create form, in display order. */\nexport function formFieldsFor(register: Collection): FormField[] {\n return FIELDS[register];\n}\n\n/** A blank draft: every field keyed to an empty string (form-friendly). */\nexport function emptyDraft(register: Collection): Record<string, string> {\n const draft: Record<string, string> = {};\n for (const f of formFieldsFor(register)) draft[f.key] = \"\";\n return draft;\n}\n\n/** Labels of required fields the draft has left blank (submit-gating). */\nexport function missingRequired(\n register: Collection,\n draft: Record<string, string>,\n): string[] {\n return formFieldsFor(register)\n .filter((f) => f.required && !String(draft[f.key] ?? \"\").trim())\n .map((f) => f.label);\n}\n\n/**\n * Turn a string-keyed form draft into a create payload: numbers coerced, blank\n * optional values dropped so the record stores only what was filled. Derived\n * and relation fields are never here — the server computes the former and\n * linking wires the latter.\n */\nexport function toCreatePayload(\n register: Collection,\n draft: Record<string, string>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const f of formFieldsFor(register)) {\n const raw = String(draft[f.key] ?? \"\").trim();\n if (raw === \"\") continue; // leave unfilled fields unset\n if (f.kind === \"number\" || f.coerce === \"number\") {\n const n = Number(raw);\n if (!Number.isNaN(n)) out[f.key] = n;\n } else {\n out[f.key] = raw;\n }\n }\n return out;\n}\n","/**\n * Shared Tailwind class strings for form controls, so the create form and the\n * relation editor render identical inputs (and any restyle happens in one\n * place). The host app supplies the Tailwind utilities these name.\n */\n\nexport const FIELD_LABEL_CLASS =\n \"block text-xs font-medium uppercase tracking-wide text-neutral-500 dark:text-neutral-400\";\n\nexport const FIELD_CONTROL_CLASS =\n \"mt-1 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-100\";\n","import { useCallback, useState } from \"react\";\nimport type { AnyRecord, Collection, Relation } from \"@validation-os/core\";\n\n/**\n * Client write hooks — the create + link counterparts to `use-records`'\n * read hooks. Both POST through the Clerk-gated API (`POST {basePath}/{register}`\n * and `POST {basePath}/link`), so the browser never touches Firestore and the\n * server always recomputes derived fields. Each returns a `pending`/`error`\n * pair and an action that resolves to the result (or throws), so the caller can\n * refresh the list/record afterwards.\n */\n\n/** Pull the API's `{ data }` envelope, or throw its plain-language message. */\nasync function postJson<T>(url: string, body: unknown): Promise<T> {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n const payload = (await res.json().catch(() => null)) as\n | { data?: T; message?: string; error?: string }\n | null;\n if (!res.ok) {\n const message =\n payload?.message ?? payload?.error ?? `Request failed (${res.status})`;\n throw new Error(message);\n }\n return (payload as { data: T }).data;\n}\n\nexport interface UseCreateResult {\n create: (data: Record<string, unknown>) => Promise<AnyRecord>;\n saving: boolean;\n error: string | null;\n}\n\n/** Create a record in `register`; the server stamps derived fields on write. */\nexport function useCreate(\n register: Collection,\n basePath = \"/api\",\n): UseCreateResult {\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const create = useCallback(\n async (data: Record<string, unknown>) => {\n setSaving(true);\n setError(null);\n try {\n return await postJson<AnyRecord>(`${basePath}/${register}`, data);\n } catch (e) {\n const message = e instanceof Error ? e.message : \"Failed to create\";\n setError(message);\n throw e;\n } finally {\n setSaving(false);\n }\n },\n [register, basePath],\n );\n\n return { create, saving, error };\n}\n\nexport interface LinkArgs {\n relation: Relation;\n from: { register: Collection; id: string };\n to: { register: Collection; id: string };\n}\n\nexport interface UseLinkResult {\n link: (args: LinkArgs) => Promise<void>;\n linking: boolean;\n error: string | null;\n}\n\n/** Wire a relation; the API sets both ends and recomputes derived fields. */\nexport function useLink(basePath = \"/api\"): UseLinkResult {\n const [linking, setLinking] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const link = useCallback(\n async (args: LinkArgs) => {\n setLinking(true);\n setError(null);\n try {\n await postJson<unknown>(`${basePath}/link`, args);\n } catch (e) {\n const message = e instanceof Error ? e.message : \"Failed to link\";\n setError(message);\n throw e;\n } finally {\n setLinking(false);\n }\n },\n [basePath],\n );\n\n return { link, linking, error };\n}\n","import { useMemo, useState } from \"react\";\nimport type { Collection } from \"@validation-os/core\";\nimport { primaryLabel } from \"./columns.js\";\nimport { FIELD_CONTROL_CLASS } from \"./field-styles.js\";\nimport { linkChoicesFrom } from \"./link-choices.js\";\nimport { useList } from \"./use-records.js\";\nimport { useLink } from \"./use-mutations.js\";\n\nexport interface RelationEditorProps {\n /** The register of the open record initiating the link. */\n register: Collection;\n /** The open record's id (the `from` end). */\n recordId: string;\n basePath?: string;\n /** Called after a relation is wired, so the drawer can re-fetch. */\n onLinked: () => void;\n}\n\n/**\n * Wire a relation from the open record (spec user story 14). Pick an edge, then\n * a target record from the register that edge points at; linking sets both ends\n * server-side and recomputes derived fields. Registers with no outbound edges\n * (glossary, people) render nothing.\n */\nexport function RelationEditor({\n register,\n recordId,\n basePath,\n onLinked,\n}: RelationEditorProps) {\n const choices = useMemo(() => linkChoicesFrom(register), [register]);\n const [relation, setRelation] = useState(\"\");\n const [targetId, setTargetId] = useState(\"\");\n const { link, linking, error } = useLink(basePath);\n\n const active = choices.find((c) => c.relation === relation) ?? null;\n const { records } = useList(\n (active?.targetRegister ?? register) as Collection,\n basePath,\n );\n // Never offer to link a record to itself (self-referential registers).\n const targets = (records ?? []).filter(\n (r) => !(active?.targetRegister === register && r.id === recordId),\n );\n\n if (choices.length === 0) return null;\n\n const onLink = async () => {\n if (!active || !targetId || linking) return;\n try {\n await link({\n relation: active.relation,\n from: { register, id: recordId },\n to: { register: active.targetRegister, id: targetId },\n });\n setTargetId(\"\");\n setRelation(\"\");\n onLinked();\n } catch {\n // `error` carries the message; leave the picks in place to retry.\n }\n };\n\n return (\n <section className=\"border-t border-neutral-200 p-5 dark:border-neutral-800\">\n <h3 className=\"mb-2 text-xs font-medium uppercase tracking-wide text-neutral-400\">\n Link a record\n </h3>\n <div className=\"space-y-3\">\n <select\n aria-label=\"Relation\"\n value={relation}\n onChange={(e) => {\n setRelation(e.target.value);\n setTargetId(\"\");\n }}\n className={FIELD_CONTROL_CLASS}\n >\n <option value=\"\">Choose a relation…</option>\n {choices.map((c) => (\n <option key={c.relation} value={c.relation}>\n {c.label}\n </option>\n ))}\n </select>\n\n {active ? (\n <select\n aria-label=\"Target record\"\n value={targetId}\n onChange={(e) => setTargetId(e.target.value)}\n className={FIELD_CONTROL_CLASS}\n >\n <option value=\"\">Choose a record…</option>\n {targets.map((r) => (\n <option key={r.id} value={r.id}>\n {primaryLabel(r)}\n </option>\n ))}\n </select>\n ) : null}\n\n {error ? (\n <p className=\"text-sm text-red-600 dark:text-red-400\">{error}</p>\n ) : null}\n\n <button\n type=\"button\"\n onClick={onLink}\n disabled={!active || !targetId || linking}\n className=\"rounded-md bg-blue-600 px-3 py-2 text-sm font-medium text-white hover:bg-blue-500 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {linking ? \"Linking…\" : \"Link\"}\n </button>\n </div>\n </section>\n );\n}\n","/**\n * Which relations a record of a given register can initiate — the menu the\n * relation editor offers. Derived from `core`'s single `RELATIONS` table (so\n * the two never drift) and turned into plain-language choices, each naming the\n * register whose records are valid link targets.\n */\nimport { RELATIONS, type Collection, type Relation } from \"@validation-os/core\";\n\nexport interface LinkChoice {\n relation: Relation;\n /** Plain-language name of the edge, from the initiating record's side. */\n label: string;\n /** The register to pick a target record from. */\n targetRegister: Collection;\n}\n\n/** Plain-language label for each relation, read from the `from` side. */\nconst RELATION_LABEL: Record<Relation, string> = {\n \"assumption-reading\": \"Add reading\",\n \"assumption-depends-on\": \"Depends on\",\n \"assumption-contradicts\": \"Contradicts\",\n \"reading-experiment\": \"From experiment\",\n \"reading-goal\": \"From goal\",\n \"decision-based-on\": \"Based on assumption\",\n \"decision-resolves\": \"Resolves assumption\",\n \"goal-based-on\": \"Based on assumption\",\n};\n\n/** The relations a record of `register` can initiate, in table order. */\nexport function linkChoicesFrom(register: Collection): LinkChoice[] {\n return (Object.keys(RELATIONS) as Relation[])\n .filter((r) => RELATIONS[r].from.register === register)\n .map((relation) => ({\n relation,\n label: RELATION_LABEL[relation],\n targetRegister: RELATIONS[relation].targetRegister,\n }));\n}\n","import { useState } from \"react\";\nimport type { Collection } from \"@validation-os/core\";\nimport { DrawerShell } from \"./drawer-shell.js\";\nimport { REGISTER_LABEL, REGISTER_SINGULAR } from \"./labels.js\";\nimport { RegisterTable } from \"./register-table.js\";\nimport { RecordDrawer } from \"./record-drawer.js\";\nimport { RecordForm } from \"./record-form.js\";\nimport { RelationEditor } from \"./relation-editor.js\";\nimport { useList, useRecord } from \"./use-records.js\";\n\nexport interface RegisterBrowserProps {\n register: Collection;\n /** API base path (default `/api`). */\n basePath?: string;\n}\n\n/**\n * The browse-create-edit surface for one register: a list table that opens a\n * record drawer on row click, a \"New\" button that opens the create form, and a\n * relation editor in the drawer for wiring links. All reads and writes go over\n * HTTP through the Clerk-gated API (which recomputes derived fields on write),\n * so the browser never touches Firestore directly. After an edit saves, both\n * the drawer's record and the list re-fetch so recomputed derived numbers show\n * everywhere. The thin host app renders this with a `register` — that's the\n * whole page.\n */\nexport function RegisterBrowser({ register, basePath }: RegisterBrowserProps) {\n const { records, loading, error, refresh: refreshList } = useList(\n register,\n basePath,\n );\n const [openId, setOpenId] = useState<string | null>(null);\n const [creating, setCreating] = useState(false);\n const {\n record,\n loading: recordLoading,\n error: recordError,\n refresh: refreshRecord,\n } = useRecord(register, openId, basePath);\n\n return (\n <div>\n <div className=\"mb-3 flex items-center justify-between\">\n <h1 className=\"text-lg font-semibold text-neutral-900 dark:text-neutral-50\">\n {REGISTER_LABEL[register]}\n </h1>\n <button\n type=\"button\"\n onClick={() => setCreating(true)}\n className=\"rounded-md bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-500\"\n >\n New {REGISTER_SINGULAR[register]}\n </button>\n </div>\n\n {loading && !records ? (\n <p className=\"text-sm text-neutral-500\">\n Loading {REGISTER_LABEL[register].toLowerCase()}…\n </p>\n ) : error ? (\n <p className=\"text-sm text-red-600 dark:text-red-400\">{error}</p>\n ) : (\n <RegisterTable\n register={register}\n records={records ?? []}\n onRowClick={setOpenId}\n selectedId={openId}\n />\n )}\n\n <RecordDrawer\n register={register}\n record={record}\n loading={recordLoading}\n error={recordError}\n open={openId !== null}\n onClose={() => setOpenId(null)}\n basePath={basePath}\n onChanged={() => {\n refreshRecord();\n refreshList();\n }}\n >\n {openId ? (\n <RelationEditor\n register={register}\n recordId={openId}\n basePath={basePath}\n onLinked={() => {\n refreshRecord();\n refreshList();\n }}\n />\n ) : null}\n </RecordDrawer>\n\n <DrawerShell\n open={creating}\n onClose={() => setCreating(false)}\n ariaLabel={`New ${REGISTER_SINGULAR[register]} record`}\n scroll={false}\n >\n <header className=\"border-b border-neutral-200 p-5 dark:border-neutral-800\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-neutral-400\">\n New\n </p>\n <h2 className=\"mt-1 text-lg font-semibold text-neutral-900 dark:text-neutral-50\">\n {REGISTER_SINGULAR[register]}\n </h2>\n </header>\n <RecordForm\n register={register}\n basePath={basePath}\n onCreated={(id) => {\n setCreating(false);\n refreshList();\n setOpenId(id);\n }}\n onCancel={() => setCreating(false)}\n />\n </DrawerShell>\n </div>\n );\n}\n","import { useCallback, useEffect, useState } from \"react\";\nimport type { Collection } from \"@validation-os/core\";\n\nexport type Counts = Partial<Record<Collection, number>>;\n\nexport interface UseCountsResult {\n counts: Counts | null;\n loading: boolean;\n error: string | null;\n refresh: () => void;\n}\n\n/**\n * Client hook: fetch per-register counts from the API (`GET {basePath}/counts`).\n * The API reads Firestore server-side through the adapter; this only speaks\n * HTTP, so no backend credentials ever reach the browser.\n */\nexport function useCounts(basePath = \"/api\"): UseCountsResult {\n const [counts, setCounts] = useState<Counts | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [tick, setTick] = useState(0);\n\n useEffect(() => {\n let live = true;\n setLoading(true);\n fetch(`${basePath}/counts`)\n .then(async (res) => {\n if (!res.ok) throw new Error(`Counts request failed (${res.status})`);\n return (await res.json()) as { counts: Counts };\n })\n .then((body) => {\n if (!live) return;\n setCounts(body.counts);\n setError(null);\n })\n .catch((e: unknown) => {\n if (!live) return;\n setError(e instanceof Error ? e.message : \"Failed to load counts\");\n })\n .finally(() => {\n if (live) setLoading(false);\n });\n return () => {\n live = false;\n };\n }, [basePath, tick]);\n\n const refresh = useCallback(() => setTick((t) => t + 1), []);\n return { counts, loading, error, refresh };\n}\n"],"mappings":"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","names":["jsx","jsxs","useEffect","useState","jsx","jsxs","str","useEffect","useEffect","t","Fragment","jsx","jsxs","Fragment","jsx","jsxs","useState","useEffect","useState","useCallback","useState","jsx","jsxs","useState","useMemo","useState","jsx","jsxs","useMemo","useState","useState","jsx","jsxs","useState","useCallback","useEffect","useState","t"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@validation-os/dashboard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "React dashboard components + hooks for validation-os, consuming the DataProvider seam.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@validation-os/core": "0.
|
|
24
|
+
"@validation-os/core": "0.4.0"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"react": ">=18",
|