fix(confidence-engine): reopen completed focused investigation
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user