fix(confidence-engine): render focused investigation in current presentation
This commit is contained in:
@@ -148,6 +148,99 @@ function useSessionPersistence() {
|
|||||||
return { saveSession, loadSession, clearSession, sessionReady: true };
|
return { saveSession, loadSession, clearSession, sessionReady: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Shared focused-question body (extracted from OpenQuestionsPanel) ──
|
||||||
|
|
||||||
|
function FocusedQuestionBody({
|
||||||
|
nodeId,
|
||||||
|
isFocused,
|
||||||
|
hasCompletedInvestigation: hasCompleted,
|
||||||
|
focused,
|
||||||
|
formulationStep,
|
||||||
|
formulateMsg,
|
||||||
|
processingStep,
|
||||||
|
deconstructMsg,
|
||||||
|
focusedAnswer,
|
||||||
|
handleDeconstructSubmit,
|
||||||
|
retryFormulation,
|
||||||
|
setFocusedAnswer,
|
||||||
|
setSelectedPresentationItemId,
|
||||||
|
setFocusedPresentationItemId,
|
||||||
|
setDoneForNowIds,
|
||||||
|
setFollowUpQuestion,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isFocused && (() => {
|
||||||
|
const hasContent = focused?.question?.trim() || formulationStep === "active" || processingStep === "active" || focused?.error;
|
||||||
|
|
||||||
|
if (!hasContent) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-4 space-y-4">
|
||||||
|
<div className="space-y-4 rounded-lg border border-gray-200 bg-white p-5">
|
||||||
|
{focused?.question?.trim() ? (
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">Question</h3>
|
||||||
|
<p className="text-base font-medium leading-relaxed text-gray-900">{focused.question}</p>
|
||||||
|
</div>
|
||||||
|
) : formulationStep === "active" ? (
|
||||||
|
<p className="text-sm text-blue-600/70">{formulateMsg}</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{focused?.question?.trim() && processingStep !== "active" && focused.status === "formulated" && (
|
||||||
|
<div>
|
||||||
|
<label htmlFor={`rw-answer-${nodeId}`} className="mb-2 block text-sm font-medium text-gray-700">Your response</label>
|
||||||
|
<textarea id={`rw-answer-${nodeId}`} value={focusedAnswer} onChange={(e) => setFocusedAnswer(e.target.value)} rows={4} data-testid="response-textarea" className="w-full rounded-lg border border-gray-300 px-4 py-3 text-sm focus:border-gray-500 focus:outline-none focus:ring-2 focus:ring-gray-400 disabled:cursor-not-allowed disabled:opacity-60" placeholder="What do you know about this?" />
|
||||||
|
<button onClick={(e) => { e.stopPropagation(); handleDeconstructSubmit(nodeId, focusedAnswer); }} disabled={!focusedAnswer.trim() || processingStep === "active"} style={{ cursor: !focusedAnswer.trim() || processingStep === "active" ? "not-allowed" : "pointer" }} className="mt-3 rounded-lg border border-green-600 bg-white px-4 py-2 text-sm font-medium text-green-700 hover:bg-green-50 transition disabled:opacity-50">Submit response</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{processingStep === "active" && <p className="text-sm text-blue-600/70">{deconstructMsg}</p>}
|
||||||
|
|
||||||
|
{focused?.result && (
|
||||||
|
<>
|
||||||
|
<div><h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">What this tells us</h3><ul className="list-disc pl-5 space-y-1">{(focused.result.observations || []).map((o, i) => (<li key={i} className="text-sm leading-relaxed text-gray-700">{o}</li>))}</ul></div>
|
||||||
|
<div><h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">Still unclear</h3><ul className="list-disc pl-5 space-y-1">{(focused.result.uncertainties || []).map((u, i) => (<li key={i} className="text-sm leading-relaxed text-gray-700">{u}</li>))}</ul></div>
|
||||||
|
<div><h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">Questions this raises</h3>
|
||||||
|
{(focused.result.possibleFollowUpQuestions || []).length > 0 ? (
|
||||||
|
<div className="space-y-1 mt-1">
|
||||||
|
{focused.result.possibleFollowUpQuestions.map((q, i) => (
|
||||||
|
<button
|
||||||
|
key={i}
|
||||||
|
onClick={(e) => { e.stopPropagation(); setFollowUpQuestion(q); }}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
className="w-full text-left rounded-lg border border-blue-200/60 bg-blue-50/40 px-3 py-2.5 text-sm leading-relaxed text-gray-800 hover:border-blue-300 hover:bg-blue-100/60 transition"
|
||||||
|
data-testid="follow-up-question"
|
||||||
|
>
|
||||||
|
{q} → pick this question
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-xs text-gray-400">None yet</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div><h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">Assumptions</h3><ul className="list-disc pl-5 space-y-1">{(focused.result.assumptions || []).map((a, i) => (<li key={i} className="text-sm leading-relaxed text-gray-700">{a}</li>))}</ul></div>
|
||||||
|
<div><h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">Connections</h3><ul className="list-disc pl-5 space-y-1">{(focused.result.relationships || []).map((r, i) => (<li key={i} className="text-sm leading-relaxed text-gray-700">{r.from} → {r.to} ({r.type})</li>))}</ul></div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{focused?.error && processingStep !== "active" && (
|
||||||
|
<div className="rounded-md border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-800">We were unable to process your request right now. Please try again later.<button onClick={(e) => { e.stopPropagation(); retryFormulation(); }} className="ml-2 font-medium underline">Retry</button></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 mb-3 flex items-center justify-between gap-4">
|
||||||
|
<button onClick={(e) => { e.stopPropagation(); setFocusedPresentationItemId(null); }} style={{ cursor: "pointer" }} className="text-sm text-gray-400 underline hover:text-gray-600 transition whitespace-nowrap">Back to open questions</button>
|
||||||
|
<button onClick={(e) => { e.stopPropagation(); setFocusedPresentationItemId(null); setDoneForNowIds((prev) => [...prev, nodeId]); }} style={{ cursor: "pointer" }} className="rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-600 hover:bg-gray-50 transition whitespace-nowrap">Done for now</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Current understanding card ────────────────────────────────
|
// ── Current understanding card ────────────────────────────────
|
||||||
|
|
||||||
// Evidence-limit text that must not appear inside Current understanding
|
// Evidence-limit text that must not appear inside Current understanding
|
||||||
@@ -855,72 +948,24 @@ function OpenQuestionsPanel({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isFocused && (() => {
|
<FocusedQuestionBody
|
||||||
const focusedNode = graph?.nodes.find((n) => n.id === node.id);
|
nodeId={node.id}
|
||||||
return (
|
isFocused={isFocused}
|
||||||
<div className="mt-4 space-y-4">
|
hasCompletedInvestigation={() => hasCompletedInvestigation(node.id)}
|
||||||
{hasFocusedContent() && (
|
focused={focused}
|
||||||
<div className="space-y-4 rounded-lg border border-gray-200 bg-white p-5">
|
formulationStep={formulationStep}
|
||||||
{focused?.question?.trim() ? (
|
formulateMsg={formulateMsg}
|
||||||
<div>
|
processingStep={processingStep}
|
||||||
<h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">Question</h3>
|
deconstructMsg={deconstructMsg}
|
||||||
<p className="text-base font-medium leading-relaxed text-gray-900">{focused.question}</p>
|
focusedAnswer={focusedAnswer}
|
||||||
</div>
|
handleDeconstructSubmit={handleDeconstructSubmit}
|
||||||
) : formulationStep === "active" ? (
|
retryFormulation={retryFormulation}
|
||||||
<p className="text-sm text-blue-600/70">{formulateMsg}</p>
|
setFocusedAnswer={setFocusedAnswer}
|
||||||
) : null}
|
setSelectedPresentationItemId={setSelectedPresentationItemId}
|
||||||
|
setFocusedPresentationItemId={setFocusedPresentationItemId}
|
||||||
{focused?.question?.trim() && processingStep !== "active" && focused.status === "formulated" && (
|
setDoneForNowIds={setDoneForNowIds}
|
||||||
<div>
|
setFollowUpQuestion={setFollowUpQuestion}
|
||||||
<label htmlFor={`rw-answer-${node.id}`} className="mb-2 block text-sm font-medium text-gray-700">Your response</label>
|
/>
|
||||||
<textarea id={`rw-answer-${node.id}`} value={focusedAnswer} onChange={(e) => setFocusedAnswer(e.target.value)} rows={4} data-testid="response-textarea" className="w-full rounded-lg border border-gray-300 px-4 py-3 text-sm focus:border-gray-500 focus:outline-none focus:ring-2 focus:ring-gray-400 disabled:cursor-not-allowed disabled:opacity-60" placeholder="What do you know about this?" />
|
|
||||||
<button onClick={(e) => { e.stopPropagation(); handleDeconstructSubmit(focusedPresentationItemId, focusedAnswer); }} disabled={!focusedAnswer.trim() || processingStep === "active"} style={{ cursor: !focusedAnswer.trim() || processingStep === "active" ? "not-allowed" : "pointer" }} className="mt-3 rounded-lg border border-green-600 bg-white px-4 py-2 text-sm font-medium text-green-700 hover:bg-green-50 transition disabled:opacity-50">Submit response</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{processingStep === "active" && <p className="text-sm text-blue-600/70">{deconstructMsg}</p>}
|
|
||||||
|
|
||||||
{focused?.result && (
|
|
||||||
<>
|
|
||||||
<div><h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">What this tells us</h3><ul className="list-disc pl-5 space-y-1">{(focused.result.observations || []).map((o, i) => (<li key={i} className="text-sm leading-relaxed text-gray-700">{o}</li>))}</ul></div>
|
|
||||||
<div><h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">Still unclear</h3><ul className="list-disc pl-5 space-y-1">{(focused.result.uncertainties || []).map((u, i) => (<li key={i} className="text-sm leading-relaxed text-gray-700">{u}</li>))}</ul></div>
|
|
||||||
<div><h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">Questions this raises</h3>
|
|
||||||
{(focused.result.possibleFollowUpQuestions || []).length > 0 ? (
|
|
||||||
<div className="space-y-1 mt-1">
|
|
||||||
{focused.result.possibleFollowUpQuestions.map((q, i) => (
|
|
||||||
<button
|
|
||||||
key={i}
|
|
||||||
onClick={(e) => { e.stopPropagation(); setFollowUpQuestion(q); }}
|
|
||||||
style={{ cursor: "pointer" }}
|
|
||||||
className="w-full text-left rounded-lg border border-blue-200/60 bg-blue-50/40 px-3 py-2.5 text-sm leading-relaxed text-gray-800 hover:border-blue-300 hover:bg-blue-100/60 transition"
|
|
||||||
data-testid="follow-up-question"
|
|
||||||
>
|
|
||||||
{q} → pick this question
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<p className="text-xs text-gray-400">None yet</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div><h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">Assumptions</h3><ul className="list-disc pl-5 space-y-1">{(focused.result.assumptions || []).map((a, i) => (<li key={i} className="text-sm leading-relaxed text-gray-700">{a}</li>))}</ul></div>
|
|
||||||
<div><h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">Connections</h3><ul className="list-disc pl-5 space-y-1">{(focused.result.relationships || []).map((r, i) => (<li key={i} className="text-sm leading-relaxed text-gray-700">{r.from} → {r.to} ({r.type})</li>))}</ul></div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{focused?.error && processingStep !== "active" && (
|
|
||||||
<div className="rounded-md border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-800">We were unable to process your request right now. Please try again later.<button onClick={(e) => { e.stopPropagation(); retryFormulation(); }} className="ml-2 font-medium underline">Retry</button></div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="mt-4 mb-3 flex items-center justify-between gap-4">
|
|
||||||
<button onClick={(e) => { e.stopPropagation(); setFocusedPresentationItemId(null); }} style={{ cursor: "pointer" }} className="text-sm text-gray-400 underline hover:text-gray-600 transition whitespace-nowrap">Back to open questions</button>
|
|
||||||
<button onClick={(e) => { e.stopPropagation(); setFocusedPresentationItemId(null); setDoneForNowIds((prev) => [...prev, node.id]); }} style={{ cursor: "pointer" }} className="rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-600 hover:bg-gray-50 transition whitespace-nowrap">Done for now</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
|
|
||||||
{/* Thread contributions for this node */}
|
{/* Thread contributions for this node */}
|
||||||
<ThreadContributionsBadge nodeId={node.id} contributions={focusedContributions || []} />
|
<ThreadContributionsBadge nodeId={node.id} contributions={focusedContributions || []} />
|
||||||
@@ -1469,20 +1514,70 @@ export default function ReasoningWorkspace({
|
|||||||
<h2 className="text-[11px] font-semibold tracking-widest uppercase text-gray-500">
|
<h2 className="text-[11px] font-semibold tracking-widest uppercase text-gray-500">
|
||||||
Open Questions
|
Open Questions
|
||||||
</h2>
|
</h2>
|
||||||
{openUnknowns.map((node) => (
|
{openUnknowns.map((node) => {
|
||||||
|
const isFocused = focusedPresentationItemId === node.id;
|
||||||
|
const hasCompleted = Boolean(
|
||||||
|
focusedInvestigations[node.id] &&
|
||||||
|
focusedInvestigations[node.id].question
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleNodeClick(n) {
|
||||||
|
if (hasCompleted && selectedPresentationItemId === n.id) {
|
||||||
|
setFocusedPresentationItemId(n.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
startFocused(n.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<button
|
<button
|
||||||
key={node.id}
|
key={node.id}
|
||||||
onClick={() => startFocused(node.id)}
|
onClick={() => handleNodeClick(node)}
|
||||||
style={{ cursor: "pointer" }}
|
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"
|
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>
|
<span className={`block leading-snug ${isFocused ? "text-sm font-medium text-gray-900" : "text-sm text-gray-600"}`}>{node.label}</span>
|
||||||
{node.description && node.description !== node.label && (
|
{node.description && node.description !== node.label && (
|
||||||
<p className="mt-1.5 text-xs leading-snug text-gray-500">{node.description}</p>
|
<p className="mt-1.5 text-xs leading-snug text-gray-500">{node.description}</p>
|
||||||
)}
|
)}
|
||||||
<span className="mt-2 block text-[10px] uppercase tracking-wider text-gray-400">Unclear</span>
|
{!isFocused && <span className="mt-2 block text-[10px] uppercase tracking-wider text-gray-400">Unclear</span>}
|
||||||
</button>
|
</button>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* Focused content — rendered inline in the initial reflection surface */}
|
||||||
|
{(() => {
|
||||||
|
const activeForFocus = openUnknowns.find((n) => focusedPresentationItemId === n.id);
|
||||||
|
if (!activeForFocus) return null;
|
||||||
|
|
||||||
|
const node = activeForFocus;
|
||||||
|
const hasCompleted = Boolean(
|
||||||
|
focusedInvestigations[node.id] &&
|
||||||
|
focusedInvestigations[node.id].question
|
||||||
|
);
|
||||||
|
const isFocused = focusedPresentationItemId === node.id;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FocusedQuestionBody
|
||||||
|
nodeId={node.id}
|
||||||
|
isFocused={isFocused}
|
||||||
|
hasCompletedInvestigation={() => hasCompleted}
|
||||||
|
focused={getFocusedInvestigation()}
|
||||||
|
formulationStep={formulationStep}
|
||||||
|
formulateMsg={formulateMsg}
|
||||||
|
processingStep={processingStep}
|
||||||
|
deconstructMsg={deconstructMsg}
|
||||||
|
focusedAnswer={focusedAnswer}
|
||||||
|
handleDeconstructSubmit={handleDeconstructSubmit}
|
||||||
|
retryFormulation={retryFormulation}
|
||||||
|
setFocusedAnswer={setFocusedAnswer}
|
||||||
|
setSelectedPresentationItemId={setSelectedPresentationItemId}
|
||||||
|
setFocusedPresentationItemId={setFocusedPresentationItemId}
|
||||||
|
setDoneForNowIds={setDoneForNowIds}
|
||||||
|
setFollowUpQuestion={setFollowUpQuestion}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
// ── Simulated Run A initial reflection surface focused content lifecycle ──
|
||||||
|
// Validates that clicking an Open Question in post-Analyse → visible formulation
|
||||||
|
// → deconstruction → result follows the exact same state machine as OpenQuestionsPanel.
|
||||||
|
|
||||||
|
// ── State helpers that mirror ReasoningWorkspace logic ───────────
|
||||||
|
|
||||||
|
function simulateInitialFocus(props) {
|
||||||
|
const { focusedInvestigations = {}, focusedPresentationItemId, postAnalyseStatus } = props;
|
||||||
|
const focusedObj = focusedPresentationItemId ? (focusedInvestigations[focusedPresentationItemId] ?? null) : null;
|
||||||
|
return {
|
||||||
|
surfaceVisible: postAnalyseStatus === "success",
|
||||||
|
focusedItem: focusedPresentationItemId ?? null,
|
||||||
|
focusedObj,
|
||||||
|
hasContent: Boolean(
|
||||||
|
focusedObj?.question?.trim() ||
|
||||||
|
focusedObj?.status === "formulating" ||
|
||||||
|
props.processingStep === "active" ||
|
||||||
|
focusedObj?.error
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function simulateFormulationSuccess(focusedInvestigations, nodeId, questionText) {
|
||||||
|
return {
|
||||||
|
...focusedInvestigations,
|
||||||
|
[nodeId]: {
|
||||||
|
status: "formulated",
|
||||||
|
question: questionText,
|
||||||
|
answer: null,
|
||||||
|
result: null,
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function simulateAnswerSubmission(focusedInvestigations, nodeId, answerText) {
|
||||||
|
const existing = focusedInvestigations[nodeId];
|
||||||
|
if (!existing) return focusedInvestigations;
|
||||||
|
return {
|
||||||
|
...focusedInvestigations,
|
||||||
|
[nodeId]: { ...existing, answer: answerText },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function simulateDeconstructionSuccess(focusedInvestigations, nodeId, resultData) {
|
||||||
|
const existing = focusedInvestigations[nodeId];
|
||||||
|
if (!existing) return focusedInvestigations;
|
||||||
|
return {
|
||||||
|
...focusedInvestigations,
|
||||||
|
[nodeId]: {
|
||||||
|
...existing,
|
||||||
|
status: "formulated",
|
||||||
|
result: resultData,
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Tests ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("Run A focused content — formulation visible in initial reflection surface", () => {
|
||||||
|
it("collapsed question card shows UNCLEAR label before click", () => {
|
||||||
|
const state = simulateInitialFocus({
|
||||||
|
focusedInvestigations: {},
|
||||||
|
focusedPresentationItemId: null,
|
||||||
|
postAnalyseStatus: "success",
|
||||||
|
});
|
||||||
|
expect(state.surfaceVisible).toBe(true);
|
||||||
|
expect(state.focusedItem).toBeNull();
|
||||||
|
expect(state.hasContent).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formulation loading state — UNCLEAR hidden, focused card visible", () => {
|
||||||
|
const withFormulating = { q1: { status: "formulating", question: "", answer: null, result: null, error: null } };
|
||||||
|
const stateWithLoading = simulateInitialFocus({
|
||||||
|
focusedInvestigations: withFormulating,
|
||||||
|
focusedPresentationItemId: "q1",
|
||||||
|
postAnalyseStatus: "success",
|
||||||
|
processingStep: "idle",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(stateWithLoading.focusedItem).toBe("q1");
|
||||||
|
expect(stateWithLoading.hasContent).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formulated question — exact question text visible via focused object", () => {
|
||||||
|
const inv = simulateFormulationSuccess({}, "q1", "What would clarify this?");
|
||||||
|
const state = simulateInitialFocus({
|
||||||
|
focusedInvestigations: inv,
|
||||||
|
focusedPresentationItemId: "q1",
|
||||||
|
postAnalyseStatus: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(state.focusedItem).toBe("q1");
|
||||||
|
expect(state.hasContent).toBe(true);
|
||||||
|
expect(state.focusedObj.question).toBe("What would clarify this?");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Your response textarea visible when status is formulated and no processing active", () => {
|
||||||
|
const inv = simulateFormulationSuccess({}, "q1", "What would clarify this?");
|
||||||
|
const state = simulateInitialFocus({
|
||||||
|
focusedInvestigations: inv,
|
||||||
|
focusedPresentationItemId: "q1",
|
||||||
|
postAnalyseStatus: "success",
|
||||||
|
processingStep: "idle",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(state.focusedObj.status).toBe("formulated");
|
||||||
|
expect(state.hasContent).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deconstruction loading — processing text visible in same card", () => {
|
||||||
|
const inv = simulateAnswerSubmission(
|
||||||
|
simulateFormulationSuccess({}, "q1", "What would clarify this?"),
|
||||||
|
"q1",
|
||||||
|
"I believe the key factor is X"
|
||||||
|
);
|
||||||
|
|
||||||
|
const state = simulateInitialFocus({
|
||||||
|
focusedInvestigations: inv,
|
||||||
|
focusedPresentationItemId: "q1",
|
||||||
|
postAnalyseStatus: "success",
|
||||||
|
processingStep: "active",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(state.focusedItem).toBe("q1");
|
||||||
|
expect(state.hasContent).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("completed result — What this tells us, Still unclear, Questions this raises visible", () => {
|
||||||
|
const inv = simulateDeconstructionSuccess(
|
||||||
|
simulateAnswerSubmission(
|
||||||
|
simulateFormulationSuccess({}, "q1", "What would clarify this?"),
|
||||||
|
"q1",
|
||||||
|
"I believe the key factor is X"
|
||||||
|
),
|
||||||
|
"q1",
|
||||||
|
{
|
||||||
|
observations: ["Factor A is confirmed"],
|
||||||
|
uncertainties: ["Timing unknown"],
|
||||||
|
assumptions: [],
|
||||||
|
relationships: [],
|
||||||
|
possibleFollowUpQuestions: ["How does timing affect cost?"],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const state = simulateInitialFocus({
|
||||||
|
focusedInvestigations: inv,
|
||||||
|
focusedPresentationItemId: "q1",
|
||||||
|
postAnalyseStatus: "success",
|
||||||
|
processingStep: "idle",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(state.focusedItem).toBe("q1");
|
||||||
|
expect(state.hasContent).toBe(true);
|
||||||
|
expect(state.focusedObj.result.observations).toEqual(["Factor A is confirmed"]);
|
||||||
|
expect(state.focusedObj.result.uncertainties).toEqual(["Timing unknown"]);
|
||||||
|
expect(state.focusedObj.result.possibleFollowUpQuestions).toContain("How does timing affect cost?");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reopen same completed question — previous result visible again after Back", () => {
|
||||||
|
// Step 1: complete investigation
|
||||||
|
const inv = simulateDeconstructionSuccess(
|
||||||
|
simulateAnswerSubmission(
|
||||||
|
simulateFormulationSuccess({}, "nbfaikr", "What would clarify distinction between blockers and assumptions?"),
|
||||||
|
"nbfaikr",
|
||||||
|
"It is a capability issue"
|
||||||
|
),
|
||||||
|
"nbfaikr",
|
||||||
|
{
|
||||||
|
observations: ["Structural blocker confirmed"],
|
||||||
|
uncertainties: [],
|
||||||
|
assumptions: [],
|
||||||
|
relationships: [],
|
||||||
|
possibleFollowUpQuestions: [],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 2: user clicks "Back to open questions" — cleared focused but kept inv data
|
||||||
|
const postBack = simulateInitialFocus({
|
||||||
|
focusedInvestigations: inv,
|
||||||
|
focusedPresentationItemId: null,
|
||||||
|
postAnalyseStatus: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(postBack.focusedItem).toBeNull();
|
||||||
|
|
||||||
|
// Step 3: user clicks same question again — result reopens
|
||||||
|
const reopened = simulateInitialFocus({
|
||||||
|
focusedInvestigations: inv,
|
||||||
|
focusedPresentationItemId: "nbfaikr",
|
||||||
|
postAnalyseStatus: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(reopened.focusedItem).toBe("nbfaikr");
|
||||||
|
expect(reopened.hasContent).toBe(true);
|
||||||
|
expect(reopened.focusedObj.result.observations).toContain("Structural blocker confirmed");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Run B green Investigation card NOT rendered during focused lifecycle", () => {
|
||||||
|
const state = simulateInitialFocus({
|
||||||
|
focusedInvestigations: {},
|
||||||
|
focusedPresentationItemId: null,
|
||||||
|
postAnalyseStatus: "success",
|
||||||
|
});
|
||||||
|
expect(state.surfaceVisible).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Run B OpenQuestionsPanel does NOT replace Run A during focused lifecycle", () => {
|
||||||
|
const state = simulateInitialFocus({
|
||||||
|
focusedInvestigations: {},
|
||||||
|
focusedPresentationItemId: null,
|
||||||
|
postAnalyseStatus: "success",
|
||||||
|
});
|
||||||
|
expect(state.surfaceVisible).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user