feat(confidence-engine): use server investigation persistence

This commit is contained in:
2026-09-08 19:21:05 +01:00
parent 6dd447e56a
commit d6df1d210e
15 changed files with 425 additions and 114 deletions
@@ -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);
+16 -3
View File
@@ -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.
</p>
{existing ? (
{!hydrated ? (
<p className="text-sm text-gray-500">Loading investigation</p>
) : existing ? (
<ScenarioForm
investigationId={routeId}
existingSnapshot={existing}
+19 -6
View File
@@ -15,9 +15,20 @@ export default function ReportPage({ params }) {
const generationAttempted = useRef(false);
useEffect(() => {
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 };
});
}
+28 -5
View File
@@ -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
</button>
<button
onClick={() => {
onClick={async () => {
setShowRestartConfirm(null);
try { restartInvestigation(summary.id); } catch (_) { /* storage must not crash caller */ }
setSummaries(listInvestigations());
try {
await restartInvestigation(summary.id);
setSummaries(await listInvestigations());
} catch (error) {
setLoadError(error);
}
}}
className="rounded-lg border border-red-400 bg-white px-4 py-2 text-sm font-medium text-red-700 hover:bg-red-50 transition"
>
@@ -116,7 +133,13 @@ function Portfolio() {
)}
{/* No investigations */}
{summaries.length === 0 && (
{!hydrated && (
<section className="mb-10"><h2 className="mb-4 text-[13px] font-bold tracking-[.18em] uppercase text-teal-700/80">Investigations</h2><p className="text-sm text-gray-500 italic">Loading investigations</p></section>
)}
{hydrated && loadError && (
<section className="mb-10"><h2 className="mb-4 text-[13px] font-bold tracking-[.18em] uppercase text-teal-700/80">Investigations</h2><p className="text-sm text-red-600">Unable to load investigations.</p></section>
)}
{hydrated && !loadError && summaries.length === 0 && (
<section className="mb-10">
<h2 className="mb-4 text-[13px] font-bold tracking-[.18em] uppercase text-teal-700/80">
Investigations