fix(confidence-engine): reopen completed focused investigation

This commit is contained in:
2026-08-26 10:20:45 +01:00
parent 42a7e82d88
commit 81dda77392
2 changed files with 201 additions and 8 deletions
+22 -8
View File
@@ -792,7 +792,7 @@ function OpenQuestionsPanel({
focused, formulationStep, formulateMsg, processingStep, deconstructMsg, doneForNowIds,
startFocused, handleDeconstructSubmit, retryFormulation, setSelectedPresentationItemId,
setFocusedPresentationItemId, setFocusedAnswer, focusedAnswer, setDoneForNowIds,
setFollowUpQuestion, focusedContributions,
setFollowUpQuestion, focusedContributions, focusedInvestigations,
}) {
const openNodes = (graph?.nodes || []).filter(
(n) => n.kind === "unknown" && n.status !== "resolved" && !doneForNowIds.includes(n.id),
@@ -800,6 +800,25 @@ function OpenQuestionsPanel({
if (openNodes.length <= 0) return null;
// Check whether a node has a completed focused result stored locally.
const hasCompletedInvestigation = (nid) => {
const inv = focusedInvestigations?.[nid];
return Boolean(inv && inv.status === "formulated" && inv.result && typeof inv.question === "string" && inv.question.trim());
};
// When a completed result exists and this node is already selected, reopen it.
function handleNodeClick(node) {
if (!focused?.question?.trim() || (focused && !hasFocusedContent())) {
if (hasCompletedInvestigation(node.id) && selectedPresentationItemId === node.id) {
setFocusedPresentationItemId(node.id);
return;
}
setSelectedPresentationItemId(
selectedPresentationItemId === node.id ? null : node.id,
);
}
}
return (
<div className="space-y-4">
<h2 className="text-[11px] font-semibold tracking-widest uppercase text-gray-500">
@@ -813,13 +832,7 @@ function OpenQuestionsPanel({
return (
<div key={node.id}>
<div
onClick={() => {
if (!focused?.question?.trim() || (focused && !hasFocusedContent())) {
setSelectedPresentationItemId(
selectedPresentationItemId === node.id ? null : node.id,
);
}
}}
onClick={() => handleNodeClick(node)}
style={{ cursor: "pointer" }}
className="w-full text-left rounded-lg border border-gray-200 px-4 py-3 transition hover:border-gray-300 hover:bg-white"
>
@@ -1522,6 +1535,7 @@ export default function ReasoningWorkspace({
setDoneForNowIds={setDoneForNowIds}
setFollowUpQuestion={setFollowUpQuestion}
focusedContributions={focusedContributions}
focusedInvestigations={focusedInvestigations}
/>
)}
@@ -565,3 +565,182 @@ describe("Done-for-now must not hide unrelated unresolved Open Questions", () =>
expect(graphWithTwoUnknownsAndAssumptions.nodes.find((n) => n.id === "u1").status).toBe(aStatusBefore);
});
});
// ── Regression: same-node focused result reopen (FOCUSED-REOPEN) ─
describe("same-node focused result reopen", () => {
// Simulates the key state values in ReasoningWorkspace / OpenQuestionsPanel
function simulateFocusedReopenState() {
let selectedPresentationItemId = "u1"; // user had node u1 selected
let focusedPresentationItemId = null; // "Back to open questions" cleared this
let focusedInvestigations = {};
let formulationStep = "idle";
let processingStep = "idle";
function getFocusedInvestigation() {
if (!focusedPresentationItemId) return null;
return focusedInvestigations[focusedPresentationItemId] || null;
}
function hasFocusedContent(focusedItem) {
if (!focusedItem) return false;
const q = focusedItem.question;
return Boolean(q?.trim()) || formulationStep === "active" || processingStep === "active";
}
// Simulate deconstruct success — result populated for u1
function simulateDeconstructComplete() {
focusedInvestigations["u1"] = {
status: "formulated",
question: "What is the impact of X?",
answer: "Some answer",
result: {
observations: ["Obs 1"],
uncertainties: [],
assumptions: [],
relationships: [],
possibleFollowUpQuestions: [],
},
error: null,
};
}
// Simulate user clicking "Back to open questions" (clears focusedPresentationItemId)
function simulateBackToOpen() {
focusedPresentationItemId = null;
}
// The OLD buggy click handler logic
function handleNodeClick_OLD(nodeId) {
const focusedItem = getFocusedInvestigation();
if (!focusedItem?.question?.trim() || (focusedItem && !hasFocusedContent(focusedItem))) {
return selectedPresentationItemId === nodeId ? null : nodeId;
}
return null;
}
// The NEW fixed click handler logic
function hasCompletedInvestigation(nid) {
const inv = focusedInvestigations?.[nid];
return inv && inv.status === "formulated" && inv.result && typeof inv.question === "string" && inv.question.trim();
}
function handleNodeClick_NEW(nodeId) {
const focusedItem = getFocusedInvestigation();
if (!focusedItem?.question?.trim() || (focusedItem && !hasFocusedContent(focusedItem))) {
if (hasCompletedInvestigation(nodeId) && selectedPresentationItemId === nodeId) {
focusedPresentationItemId = nodeId; // reopen!
return "reopened";
}
return selectedPresentationItemId === nodeId ? null : nodeId;
}
return null;
}
return {
getFocusedInvestigation, hasFocusedContent, simulateDeconstructComplete,
simulateBackToOpen, handleNodeClick_OLD, handleNodeClick_NEW,
get state() {
return { selectedPresentationItemId, focusedPresentationItemId, focusedInvestigations };
},
};
}
it("BUG: old handler — clicking same node after panel collapse does nothing", () => {
const s = simulateFocusedReopenState();
// 1. User has u1 selected
s.selectedPresentationItemId = "u1";
expect(s.state.focusedPresentationItemId).toBe(null);
// 2. Submit focused answer — deconstruct completes
s.simulateDeconstructComplete();
// Result data exists but display is collapsed because focusedPresentationItemId was never set during initial selection
// 3. Panel collapse (or user navigates away)
s.simulateBackToOpen();
// 4. Click same node again — OLD handler
const result = s.handleNodeClick_OLD("u1");
// BUG: result is null because selectedPresentationItemId === "u1" → toggles to null
expect(result).toBe(null);
});
it("FIX: new handler — clicking same node after panel collapse reopens completed result", () => {
const s = simulateFocusedReopenState();
// 1. User has u1 selected
s.selectedPresentationItemId = "u1";
s.focusedPresentationItemId = null;
// 2. Deconstruct completes — result exists in focusedInvestigations
s.simulateDeconstructComplete();
// 3. User clicks "Back to open questions" → collapses panel
s.simulateBackToOpen();
// 4. Click same node again — NEW handler
const result = s.handleNodeClick_NEW("u1");
// FIX: should reopen because hasCompletedInvestigation("u1") is true and selected == u1
expect(result).toBe("reopened");
expect(s.state.focusedPresentationItemId).toBe("u1");
// 5. After reopening, focused state is restored
const focused = s.getFocusedInvestigation();
expect(focused).not.toBe(null);
expect(focused.result.observations).toEqual(["Obs 1"]);
});
it("FIX: clicking a different node first then original still works (workaround no longer required)", () => {
const s = simulateFocusedReopenState();
s.selectedPresentationItemId = "u1";
s.focusedPresentationItemId = null;
s.simulateDeconstructComplete();
s.simulateBackToOpen();
// OLD workaround: click u2 first
const resultU2 = s.handleNodeClick_NEW("u2");
expect(resultU2).toBe("u2"); // now selected is u2
// Then click u1 again — should still reopen
const resultU1 = s.handleNodeClick_NEW("u1");
expect(resultU1).toBe("reopened");
expect(s.state.focusedPresentationItemId).toBe("u1");
});
it("FIX: new node with no completed result toggles selection normally", () => {
const s = simulateFocusedReopenState();
s.selectedPresentationItemId = null;
s.focusedPresentationItemId = null;
// Click a fresh node (no results) — should set selection
const result = s.handleNodeClick_NEW("u2");
expect(result).toBe("u2");
});
it("INVARIANT: hasCompletedInvestigation only returns true for formulated+result", () => {
const focusedInvestigations = {};
function hasCompletedInvestigation(nid) {
const inv = focusedInvestigations?.[nid];
return Boolean(inv && inv.status === "formulated" && inv.result && typeof inv.question === "string" && inv.question.trim());
}
// Empty → false
expect(hasCompletedInvestigation("u1")).toBe(false);
// Formulating (not complete) → false
focusedInvestigations["u2"] = { status: "formulating", question: "", result: null };
expect(hasCompletedInvestigation("u2")).toBe(false);
// No result → false
focusedInvestigations["u3"] = { status: "formulated", question: "?", result: null };
expect(hasCompletedInvestigation("u3")).toBe(false);
// Empty question → false
focusedInvestigations["u4"] = { status: "formulated", question: "", result: {} };
expect(hasCompletedInvestigation("u4")).toBe(false);
// Complete → true
focusedInvestigations["u5"] = { status: "formulated", question: "What?", result: { observations: [] } };
expect(hasCompletedInvestigation("u5")).toBe(true);
});
});