core/workbench-ui/src/app/Shell.tsx
Shay 4cba6f488c feat(workbench): Wave M Phase C legibility — pipeline record, contemplation, identity continuity
Lands the Phase C "make cognition legible" slice plus Phase A residue, all
backend-reader-first over real engine data (no theater, read-only doctrine
intact, zero serving-path imports).

C1-a — Cognitive pipeline record (persistence-first, per #729 worthiness edit):
  - workbench/pipeline_record.py: curated CognitivePipelineRecord over the real
    CognitiveTurnResult (input → intent → proposition_graph → articulation_target
    → realizer → walk_telemetry → trace_hash). Raw field multivectors are
    DELIBERATELY excluded; _assert_no_raw_field_payload recursively rejects raw
    field keys, and validate_pipeline_record fails closed on missing/duplicate
    stages, non-recorded status, or dangling edges — the UI can never receive a
    partial record that claims to be complete.
  - test_workbench_pipeline_record.py: non-vacuous guards — missing stage,
    monkeypatched new required stage, and injected raw {"F": [...]} each raise.

C2-a — Contemplation as a process: /contemplation route over real persisted
  contemplation/runs/*.json (glob reader; honest-empty when absent).

C4-a — Identity continuity (L10/L11): RunDetail.identity_continuity + Runs
  Identity tab, sourced from the real core.engine_identity (engine_identity /
  parent_engine_identity lineage relation, re-derived to verify).

Demo Theater: renders backend-owned proof-promotion + entailment DAGs.

Phase A residue: density preference wired end-to-end (settings → shell → tokens);
  cross-route consistency touch-ups.

Infra: local API CORS now echoes only validated 127.0.0.1/localhost origins
  (hostname-checked, not arbitrary reflection) so Vite fallback ports work.
  Route chunk-split keeps the build warning-free.

Cleanup: corrected the stale ADR-0175 practice-lane assertions (build_report is
  6 correct / 0 wrong / 44 refused after the current serving lane; wrong=0 held)
  and the two registry-derived count tests (LeftNav + CommandPalette 12 → 13 for
  the new Contemplation route).

Docs: runtime_contracts.md (pipeline-record contract), UI-UX-GUIDE,
  api-contract-v1, data-shapes-v1, wave-M-worthiness, phase-a-residue-ledger.

Validation: 106 workbench/practice Python tests green (incl. wrong=0 lane +
  pipeline-record fail-closed guards); 459/459 frontend; pnpm build clean;
  git diff --check clean. No generate.derivation / reliability_gate / stream /
  field.propagate / vault.store imports.
2026-06-13 15:44:31 -07:00

133 lines
4 KiB
TypeScript

import { useEffect, useState, useCallback } from "react";
import { Outlet } from "react-router-dom";
import { TopBar } from "./TopBar";
import { LeftNav } from "./LeftNav";
import { StatusFooter } from "./StatusFooter";
import { RightInspector } from "./RightInspector";
import { ApiErrorBoundary } from "./ApiErrorBoundary";
import { EvidenceProvider, useEvidenceSubject } from "./evidenceContext";
import { EvidenceUrlSync } from "./evidenceUrlSync";
import { isAddressable, subjectToUrl } from "./evidenceAddress";
import { KeyboardHelp } from "./KeyboardHelp";
import { useGlobalKeyboard } from "./useGlobalKeyboard";
import { useCommandRegistry } from "./commandRegistry";
import { useWorkbenchPrefs } from "./workbenchPrefs";
import { SplitPane } from "../design/components/SplitPane/SplitPane";
function ShellInner() {
const { subject, inspectorOpen, toggleInspector, notifyAddressCopied } =
useEvidenceSubject();
const prefs = useWorkbenchPrefs();
const [helpOpen, setHelpOpen] = useState(false);
const [paletteOpen, setPaletteOpen] = useState(false);
const onTogglePalette = useCallback(() => {
setPaletteOpen((v) => !v);
}, []);
const onShowHelp = useCallback(() => {
setHelpOpen(true);
}, []);
const onCopyEvidenceLink = useCallback(() => {
if (!isAddressable(subject)) return;
if (!navigator.clipboard?.writeText) return;
const url = window.location.origin + subjectToUrl(subject);
navigator.clipboard.writeText(url).then(
() => notifyAddressCopied(),
(err) => console.error("Evidence link copy failed:", err),
);
}, [subject, notifyAddressCopied]);
useGlobalKeyboard({
onTogglePalette,
onToggleInspector: toggleInspector,
onShowHelp,
onCopyEvidenceLink,
});
// Action verbs in the palette (Wave R brief R0d): registered by the
// component that owns the behavior, unregistered on unmount.
const { register, unregister } = useCommandRegistry();
useEffect(() => {
register([
{
id: "action-toggle-inspector",
label: "Toggle inspector",
section: "Actions",
kind: "action",
shortcut: "\u2318I",
action: toggleInspector,
},
{
id: "action-copy-evidence-link",
label: "Copy evidence link",
section: "Actions",
kind: "action",
shortcut: "\u2318\u21E7C",
action: onCopyEvidenceLink,
},
]);
return () => unregister(["action-toggle-inspector", "action-copy-evidence-link"]);
}, [register, unregister, toggleInspector, onCopyEvidenceLink]);
const mainSurface = (
<main
data-region="main"
className="h-full overflow-y-auto bg-[var(--color-surface-base)] p-[var(--density-main-padding)]"
>
<ApiErrorBoundary>
<Outlet />
</ApiErrorBoundary>
</main>
);
return (
<div
className="grid h-screen"
data-density={prefs.densityMode}
style={{
gridTemplateAreas:
'"topbar topbar" "leftnav center" "footer footer"',
gridTemplateRows: "auto 1fr auto",
gridTemplateColumns: "12rem 1fr",
}}
>
<div style={{ gridArea: "topbar" }}>
<TopBar paletteOpen={paletteOpen} onPaletteOpenChange={setPaletteOpen} />
</div>
<div style={{ gridArea: "leftnav" }}>
<LeftNav />
</div>
<div style={{ gridArea: "center" }} className="min-h-0">
{inspectorOpen ? (
// Resizable main/inspector split; width persists via the
// SplitPane id (guarded storage access inside SplitPane).
<SplitPane direction="horizontal" id="inspector" defaultSplit={72} minSize={240}>
{mainSurface}
<RightInspector />
</SplitPane>
) : (
mainSurface
)}
</div>
<div style={{ gridArea: "footer" }}>
<StatusFooter />
</div>
<KeyboardHelp open={helpOpen} onOpenChange={setHelpOpen} />
</div>
);
}
export function Shell() {
return (
<EvidenceProvider>
<EvidenceUrlSync />
<ShellInner />
</EvidenceProvider>
);
}