feat(ui): surface initial semantic reconstruction

This commit is contained in:
2026-08-21 10:34:22 +01:00
parent 412551c968
commit 86287bebe8
2 changed files with 89 additions and 19 deletions
+70 -2
View File
@@ -866,6 +866,10 @@ export default function ReasoningWorkspace({
const [focusedPresentationItemId, setFocusedPresentationItemId] = useState(null);
const [doneForNowIds, setDoneForNowIds] = useState([]);
// ── RTO.29D — post-Analyse initial reflection surface ─────────
const [initialReflectionActive, setInitialReflectionActive] = useState(false);
const [postAnalyseStatus, setPostAnalyseStatus] = useState(null);
const { saveSession, loadSession } = useSessionPersistence();
// Persist workspace state on every successful update (Phase 3)
@@ -1110,6 +1114,28 @@ export default function ReasoningWorkspace({
const hasCurrentSummaryCondition =
Boolean(propUnderstanding || graph?.currentSummary || result?.updatedSituationGraph?.currentSummary) || !hasSelectedQuestion;
// ── RTO.29D — manage initial reflection lifecycle ────────────
useEffect(() => {
const entering = status === "success" && hasGraph && propUnderstanding && !hasSelectedQuestion;
if (entering) {
setPostAnalyseStatus("success");
setInitialReflectionActive(true);
} else if (!entering && initialReflectionActive) {
// Leave initial reflection when something changes (user interacts, status shifts)
setInitialReflectionActive(false);
setPostAnalyseStatus(null);
}
}, [status, result, hasGraph]);
// Deactivate initial reflection when user does anything meaningful
useEffect(() => {
if (!initialReflectionActive) return;
if (selectedPresentationItemId || focusedPresentationItemId || investigationHistory.length > 0 || formulationStep !== "idle" || processingStep !== "idle") {
setInitialReflectionActive(false);
setPostAnalyseStatus(null);
}
}, [selectedPresentationItemId, focusedPresentationItemId, investigationHistory, formulationStep, processingStep]);
return (
<div className="space-y-6" data-testid="reasoning-workspace">
{/* ── Loading overlays ─────────────────────────────── */}
@@ -1150,6 +1176,46 @@ export default function ReasoningWorkspace({
</div>
) : (
<>
{/* ── RTO.29D — initial post-Analyse reflection ──────── */}
{postAnalyseStatus === "success" && (
<div className="space-y-6" data-testid="initial-reflection-surface">
{/* What I understood */}
<div className="rounded-lg border border-blue-200/60 bg-blue-50/40 px-6 pt-5 pb-6">
<h2 className="mb-3 text-[11px] font-semibold tracking-widest uppercase text-gray-500">
What I understood
</h2>
<p className="text-sm leading-relaxed text-gray-700">{propUnderstanding}</p>
</div>
{/* Initial proposed findings */}
<div className="space-y-3" data-testid="initial-proposed-findings">
<h2 className="text-[11px] font-semibold tracking-widest uppercase text-gray-500">
Initial proposed findings
</h2>
{(() => {
const resolvedIds = new Set(graph?.resolvedNodeIds || []);
return (
graph?.nodes?.map((node) =>
node.kind === "unknown" && node.status !== "resolved" && !resolvedIds.has(node.id) ? (
<button
key={node.id}
onClick={() => startFocused(node.id)}
style={{ cursor: "pointer" }}
className="w-full text-left rounded-lg border border-gray-200 bg-white px-5 py-4 transition hover:border-gray-300 hover:bg-gray-50"
>
<span className="block text-sm leading-relaxed text-gray-900">{node.label}</span>
{node.description && node.description !== node.label && (
<p className="mt-1.5 text-xs leading-snug text-gray-500">{node.description}</p>
)}
</button>
) : null,
) || []
);
})()}
</div>
</div>
)}
{/* ── Workspace grid: persistent whenever a graph exists ─── */}
{(hasGraph || experimentalBranches) && (
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
@@ -1219,7 +1285,9 @@ export default function ReasoningWorkspace({
{/* Supporting context within conversation lane */}
{hasCurrentSummaryCondition && (
<>
<CurrentUnderstandingCard currentSummary={graph?.currentSummary || result?.updatedSituationGraph?.currentSummary} plainLanguage={propUnderstanding || null} />
{postAnalyseStatus !== "success" && (
<CurrentUnderstandingCard currentSummary={graph?.currentSummary || result?.updatedSituationGraph?.currentSummary} plainLanguage={propUnderstanding || null} />
)}
{/* ── Experiment 12: progress panel A / B / C toggle (temporary experimental UI) — de-emphasised by branch-as-context experiment RTO.25D */}
{hasGraph && (
<div className="hidden space-y-2">
@@ -1291,7 +1359,7 @@ export default function ReasoningWorkspace({
</p>
</div>
)}
{graph && <OriginalSituation scenario={scenario} centralStatement={graph?.centralStatement} />}
{graph && postAnalyseStatus !== "success" && <OriginalSituation scenario={scenario} centralStatement={graph?.centralStatement} />}
{/* RTO.25B — temporarily hidden to reduce competing navigation while branch-experiment is active */}
<div className="hidden">
<InvestigationMap turnCount={investigationHistory.length} />
+19 -17
View File
@@ -852,23 +852,25 @@ export default function ScenarioForm() {
})()}
</div>
{/* Branch switcher (1/4 sidebar) */}
<div className="lg:col-span-1">
<ExperimentalBranchSwitcher
branches={BRANCHES}
activeBranchId={activeBranchId}
branchNewResults={branchNewResults}
branchPauseState={doneForNowBranchIds}
onBranchSelect={(id) => {
if (id === activeBranchId) return;
// Reopen: if the selected branch is paused, clear its pause state
if (doneForNowBranchIds.includes(id)) {
setDoneForNowBranchIds(prev => prev.filter(x => x !== id));
}
setActiveBranchId(id);
}}
/>
</div>
{/* Branch switcher (1/4 sidebar) — suppressed during initial reflection */}
{!((status === "success" && result?.situationGraph && !result?.selectedQuestion)) && (
<div className="lg:col-span-1">
<ExperimentalBranchSwitcher
branches={BRANCHES}
activeBranchId={activeBranchId}
branchNewResults={branchNewResults}
branchPauseState={doneForNowBranchIds}
onBranchSelect={(id) => {
if (id === activeBranchId) return;
// Reopen: if the selected branch is paused, clear its pause state
if (doneForNowBranchIds.includes(id)) {
setDoneForNowBranchIds(prev => prev.filter(x => x !== id));
}
setActiveBranchId(id);
}}
/>
</div>
)}
</div>
</>
)}