From d6df1d210e03d9d867be55b447ad60de667c4b24 Mon Sep 17 00:00:00 2001 From: robbond Date: Tue, 8 Sep 2026 19:21:05 +0100 Subject: [PATCH] feat(confidence-engine): use server investigation persistence --- app/api/investigations/[id]/restart/route.js | 14 +++ app/investigations/[id]/page.jsx | 19 +++- app/investigations/[id]/report/page.jsx | 25 ++++-- app/page.jsx | 33 +++++-- components/scenario-form.jsx | 42 ++++++--- docs/current-handoff.md | 67 ++++++--------- docs/current-project-state.md | 12 +-- lib/storage/investigation-storage.js | 86 ++++++++++++++----- lib/storage/providers/local-storage.js | 14 +-- lib/storage/providers/server-http.js | 41 +++++++++ lib/storage/restart-investigation.js | 13 +++ .../server-investigation-persistence.js | 18 +++- .../server-investigation-persistence.test.js | 37 +++++++- .../investigation-storage-server.test.js | 80 +++++++++++++++++ tests/storage/server-http-provider.test.js | 38 ++++++++ 15 files changed, 425 insertions(+), 114 deletions(-) create mode 100644 app/api/investigations/[id]/restart/route.js create mode 100644 lib/storage/providers/server-http.js create mode 100644 lib/storage/restart-investigation.js create mode 100644 tests/storage/investigation-storage-server.test.js create mode 100644 tests/storage/server-http-provider.test.js diff --git a/app/api/investigations/[id]/restart/route.js b/app/api/investigations/[id]/restart/route.js new file mode 100644 index 0000000..5d030fb --- /dev/null +++ b/app/api/investigations/[id]/restart/route.js @@ -0,0 +1,14 @@ +import { restartInvestigation } from "@/lib/storage/server-investigation-persistence.js"; +import { withAuthenticatedApi } from "@/lib/supabase/api-auth.js"; + +async function post(_request, { params }) { + try { + const snapshot = await restartInvestigation(params.id); + if (!snapshot) return Response.json({ error: "Investigation not found" }, { status: 404 }); + return Response.json({ snapshot }); + } catch { + return Response.json({ error: "Investigation persistence request failed" }, { status: 500 }); + } +} + +export const POST = withAuthenticatedApi(post); \ No newline at end of file diff --git a/app/investigations/[id]/page.jsx b/app/investigations/[id]/page.jsx index 8f945d2..87a42d6 100644 --- a/app/investigations/[id]/page.jsx +++ b/app/investigations/[id]/page.jsx @@ -11,10 +11,21 @@ export default function InvestigationPage({ params }) { const router = useRouter(); const routeId = typeof params?.id === "string" ? params.id : ""; const [existing, setExisting] = useState(null); + const [hydrated, setHydrated] = useState(false); useEffect(() => { - if (!routeId) return; - setExisting(loadInvestigation(routeId)); + let active = true; + setHydrated(false); + if (!routeId) { setHydrated(true); return; } + (async () => { + try { + const snapshot = await loadInvestigation(routeId); + if (active) setExisting(snapshot); + } finally { + if (active) setHydrated(true); + } + })(); + return () => { active = false; }; }, [routeId]); return ( @@ -35,7 +46,9 @@ export default function InvestigationPage({ params }) { evidence-based structured reconstruction. This is a technical vertical slice — not a production system.

- {existing ? ( + {!hydrated ? ( +

Loading investigation…

+ ) : existing ? ( { - setExisting(loadInvestigation(routeId)); - setHydrated(true); - }, []); + let active = true; + setHydrated(false); + (async () => { + try { + const snapshot = await loadInvestigation(routeId); + if (active) setExisting(snapshot); + } catch { + if (active) setGenerationError(true); + } finally { + if (active) setHydrated(true); + } + })(); + return () => { active = false; }; + }, [routeId]); // First-generation: create report when none persists (v0.58) useEffect(() => { @@ -54,7 +65,8 @@ export default function ReportPage({ params }) { const rev = existing?.investigationRevision ?? 0; const reportData = { understanding: data.understanding, plausibleInterpretations: data.plausibleInterpretations, hasPlausibleInterpretations: true, generatedFromRevision: rev }; setExisting((p) => { - saveInvestigation({ ...p, investigationReport: reportData }); + void saveInvestigation({ ...p, investigationReport: reportData }) + .catch((error) => console.error("Investigation report save failed", error)); return { ...p, investigationReport: reportData }; }); } else { @@ -73,7 +85,7 @@ export default function ReportPage({ params }) { if (updateLoading) return; setUpdateLoading(true); - const snap = loadInvestigation(routeId); + const snap = await loadInvestigation(routeId); const situationGraph = snap?.situationGraph; const findings = snap?.findings ?? []; const rev = snap?.investigationRevision ?? 0; @@ -99,7 +111,8 @@ export default function ReportPage({ params }) { if (data.success) { const reportData = { understanding: data.understanding, plausibleInterpretations: data.plausibleInterpretations, hasPlausibleInterpretations: true, generatedFromRevision: rev }; setExisting((p) => { - saveInvestigation({ ...p, investigationReport: reportData }); + void saveInvestigation({ ...p, investigationReport: reportData }) + .catch((error) => console.error("Investigation report save failed", error)); return { ...p, investigationReport: reportData }; }); } diff --git a/app/page.jsx b/app/page.jsx index 2507b76..571d0bd 100644 --- a/app/page.jsx +++ b/app/page.jsx @@ -8,10 +8,23 @@ import { useRouter } from "next/navigation"; function Portfolio() { const router = useRouter(); const [summaries, setSummaries] = React.useState([]); + const [hydrated, setHydrated] = React.useState(false); + const [loadError, setLoadError] = React.useState(null); const [showRestartConfirm, setShowRestartConfirm] = React.useState(false); React.useEffect(() => { - setSummaries(listInvestigations()); + let active = true; + (async () => { + try { + const investigations = await listInvestigations(); + if (active) setSummaries(investigations); + } catch (error) { + if (active) setLoadError(error); + } finally { + if (active) setHydrated(true); + } + })(); + return () => { active = false; }; }, []); return ( @@ -96,10 +109,14 @@ function Portfolio() { Cancel