feat(confidence-engine): stabilize user-directed investigation flow

Intentional changes in this checkpoint:
- Deconstruct route: use body.targetNodeId (client identity) over raw.model-invented ID
- ThreadContributionsBadge: compact per-thread contribution indicator with expandable history
- Reopen continuation: resume from accumulated contributions instead of reformulating
- showEvidenceLimit gate: hide evidence-limit card during active investigation paths
- Evidence-limit visibility correction in rendering pipeline
- Section ordering: assumptions and connections after 'Still unclear' in focused result
- Prompt v0.3: preserve user-stated alternatives as separate unknowns; no count inflation
- 3 durable regression tests (target identity, contribution persistence, reopen state)
- evidence-limit card visibility gate test suite

Temporary residue removed:
- test-analysis.mjs (scratch diagnostic)
- 5 diagnostic console.log blocks from reasoning-workspace.jsx
This commit is contained in:
2026-08-23 12:05:51 +01:00
parent 96ad0e7915
commit 01c57788ee
7 changed files with 1258 additions and 141 deletions
+264 -138
View File
@@ -310,6 +310,88 @@ function EvidenceLimitCard({ summary }) {
);
}
// ── Per-thread contribution badge (compact indicator) ──────────────
function ThreadContributionsBadge({ nodeId, contributions }) {
const threadContribs = contributions.filter((c) => c.targetNodeId === nodeId);
if (!threadContribs.length) return null;
// Show most recent contribution summary inline
const latest = threadContribs[threadContribs.length - 1];
const nonEmptyGroups = [];
for (const key of ["observations", "uncertainties", "assumptions", "relationships"]) {
const arr = latest[key];
if (Array.isArray(arr) && arr.length > 0) nonEmptyGroups.push(key);
}
return (
<div className="mt-3">
{/* Thread learning indicator — collapsed by default; user can expand to inspect history */}
<details open={false} className="rounded-lg border border-gray-200/80 bg-white/60">
<summary className="cursor-pointer px-3 py-1.5 text-xs font-medium text-gray-600 hover:text-gray-800 select-none">
📝 {threadContribs.length} learned contribution{threadContribs.length !== 1 ? "s" : ""}
</summary>
<div className="px-3 pb-3 pt-1 space-y-4">
{/* All contributions listed in order */}
{threadContribs.map((c, idx) => (
<div key={c.id || idx} className="space-y-2">
{idx > 0 && <div className="text-[9px] text-gray-400 tracking-wider uppercase mt-3">Contribution #{c.sequence || idx + 1}</div>}
{/* What this tells us */}
{c.observations?.length ? (
<div>
<h4 className="text-[10px] font-semibold tracking-widest uppercase text-gray-500">What this tells us</h4>
<ul className="list-disc pl-5 space-y-0.5">
{c.observations.map((o, i) => (
<li key={i} className="text-xs leading-relaxed text-gray-700">{o}</li>
))}
</ul>
</div>
) : null}
{/* Still unclear */}
{c.uncertainties?.length ? (
<div>
<h4 className="text-[10px] font-semibold tracking-widest uppercase text-gray-500">Still unclear</h4>
<ul className="list-disc pl-5 space-y-0.5">
{c.uncertainties.map((u, i) => (
<li key={i} className="text-xs leading-relaxed text-gray-700">{u}</li>
))}
</ul>
</div>
) : null}
{/* Assumptions */}
{c.assumptions?.length ? (
<div>
<h4 className="text-[10px] font-semibold tracking-widest uppercase text-gray-500">Assumptions</h4>
<ul className="list-disc pl-5 space-y-0.5">
{c.assumptions.map((a, i) => (
<li key={i} className="text-xs leading-relaxed text-gray-700">{a}</li>
))}
</ul>
</div>
) : null}
{/* Connections */}
{c.relationships?.length ? (
<div>
<h4 className="text-[10px] font-semibold tracking-widest uppercase text-gray-500">Connections</h4>
<ul className="list-disc pl-5 space-y-0.5">
{c.relationships.map((r, i) => (
<li key={i} className="text-xs leading-relaxed text-gray-700">{r.from} {r.to} ({r.type})</li>
))}
</ul>
</div>
) : null}
</div>
))}
</div>
</details>
</div>
);
}
// ── Quiet facilitator state (experiment mode) ─────────────────────
function QuietStateCard() {
@@ -710,11 +792,12 @@ function OpenQuestionsPanel({
focused, formulationStep, formulateMsg, processingStep, deconstructMsg, doneForNowIds,
startFocused, handleDeconstructSubmit, retryFormulation, setSelectedPresentationItemId,
setFocusedPresentationItemId, setFocusedAnswer, focusedAnswer, setDoneForNowIds,
setFollowUpQuestion,
setFollowUpQuestion, focusedContributions,
}) {
const openNodes = (graph?.nodes || []).filter(
(n) => n.kind === "unknown" && n.status !== "resolved" && !doneForNowIds.includes(n.id),
);
if (openNodes.length <= 1) return null;
return (
@@ -788,8 +871,6 @@ function OpenQuestionsPanel({
<>
<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">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>
<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">
@@ -809,6 +890,8 @@ function OpenQuestionsPanel({
<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>
</>
)}
@@ -825,6 +908,9 @@ function OpenQuestionsPanel({
</div>
);
})()}
{/* Thread contributions for this node */}
<ThreadContributionsBadge nodeId={node.id} contributions={focusedContributions || []} />
</div>
</div>
);
@@ -836,9 +922,10 @@ function OpenQuestionsPanel({
<h3 className="mb-2 text-[11px] font-semibold tracking-widest uppercase text-gray-500">Done for now</h3>
<div className="space-y-1">
{graph.nodes.filter((n) => doneForNowIds.includes(n.id)).map((node) => (
<div key={node.id} className="rounded-lg border border-gray-200/60 bg-gray-50/40 px-4 py-3">
<div key={node.id} className="rounded-lg border border-gray-200/60 bg-gray-50/40 px-4 py-3 space-y-1">
<p className="text-sm text-gray-500 leading-snug">{node.label}</p>
<button onClick={() => setDoneForNowIds(doneForNowIds.filter(id => id !== node.id))} style={{ cursor: "pointer" }} className="mt-2 rounded border border-gray-300 px-3 py-1 text-xs font-medium text-gray-500 hover:bg-white transition">Reopen</button>
<ThreadContributionsBadge nodeId={node.id} contributions={focusedContributions || []} />
<button onClick={() => setDoneForNowIds(doneForNowIds.filter(id => id !== node.id))} style={{ cursor: "pointer" }} className="mt-1 rounded border border-gray-300 px-3 py-1 text-xs font-medium text-gray-500 hover:bg-white transition">Reopen</button>
</div>
))}
</div>
@@ -930,6 +1017,7 @@ export default function ReasoningWorkspace({
const graph = result?.situationGraph ?? null;
const hasGraph = Boolean(graph);
const diagnostics = result?.diagnostics ?? null;
const newlySurfacedNodeIds = result?.newlySurfacedNodeIds || [];
const genuineCompletion = hasGenuineCompletion(graph);
@@ -1020,13 +1108,56 @@ export default function ReasoningWorkspace({
const focused = getFocusedInvestigation();
// Gate evidence-limit card: do NOT show when active investigation paths remain.
const showEvidenceLimit = !(
processingStep === "active" ||
(focused?.question?.trim() && !processingStep) ||
(hasGraph && !genuineCompletion)
);
// ── RTO.13B — workflow handlers ──────────────────────────────
function startFocused(nodeId) {
const target = nodeId || focusedPresentationItemId;
if (!target) return;
const priorContribs = (focusedContributions || []).filter(
(c) => c.targetNodeId === target,
);
setFocusedPresentationItemId(target);
setFocusedAnswer("");
if (priorContribs.length > 0) {
// Reopen path: resume from accumulated contribution history.
// Do NOT call doFormulate — the user's prior investigation direction
// is preserved; only follow-up questions surface for explicit selection.
const latest = priorContribs[priorContribs.length - 1];
setFocusedInvestigations((prev) => ({
...prev,
[target]: {
status: "formulated",
question: latest.question || "",
answer: latest.answer ?? null,
result: latest.possibleFollowUpQuestions
? {
observations: latest.observations || [],
uncertainties: latest.uncertainties || [],
assumptions: latest.assumptions || [],
relationships: latest.relationships || [],
possibleFollowUpQuestions: latest.possibleFollowUpQuestions,
}
: null,
error: null,
},
}));
setFormulationStep("idle");
return;
}
// Fresh thread path — unchanged original behaviour.
setFormulationStep("active");
setFocusedInvestigations((prev) => ({
...prev,
@@ -1103,6 +1234,7 @@ export default function ReasoningWorkspace({
});
setProcessingStep("idle");
setFocusedInvestigations((prev) => ({
...prev,
[targetNodeId]: { ...prev[targetNodeId], result: data, answer: answerText, error: null },
@@ -1230,68 +1362,24 @@ export default function ReasoningWorkspace({
{/* ── RTO.29D — initial post-Analyse reflection ──────── */}
{postAnalyseStatus === "success" && (
<div className="space-y-6" data-testid="initial-reflection-surface">
{/* Current Understanding + Situation — prominent two-column orienting surface */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
<div className={`space-y-6 ${hasGraph ? 'lg:col-span-2' : 'lg:col-span-full'}`}>
{/* Current Understanding — prominent orienting surface */}
<div className="rounded-xl border-[2.5px] border-teal-300/70 bg-gradient-to-b from-teal-50/60 to-white px-8 pt-7 pb-8 shadow-sm">
<h2 className="mb-4 text-[11px] font-bold tracking-[.18em] uppercase text-teal-700/60">
Current Understanding
</h2>
<p className="text-lg leading-relaxed text-gray-800">{propUnderstanding}</p>
</div>
{/* Initial proposed findings — unknowns + plausible interpretations from reconstruction */}
<div className="space-y-3" data-testid="initial-proposed-findings">
<h2 className="text-[11px] font-semibold tracking-widest uppercase text-gray-500">
Open Questions
</h2>
{(() => {
const resolvedIds = new Set(graph?.resolvedNodeIds || []);
// Surface only candidate items from the semantic reconstruction that are worth investigating:
// — unknowns (importantUnknowns from the LLM's reconstruction)
// — assumptions (plausibleInterpretations from the LLM's reconstruction)
// Skips observations, states, relationships, transitions — these are already established facts/context.
// Both kinds check status !== "resolved" and excluded resolvedIds to mirror OpenQuestionsPanel logic.
const candidateKinds = ["unknown", "assumption"];
return (
(graph?.nodes || [])
.filter(
(n) =>
candidateKinds.includes(n.kind) &&
n.status !== "resolved" &&
!resolvedIds.has(n.id),
)
.map((node) => {
const tag = node.kind === "assumption" ? "Plausible interpretation" : "Unclear";
return (
<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>
)}
<span className="mt-2 block text-[10px] uppercase tracking-wider text-gray-400">{tag}</span>
</button>
);
})
);
})()}
</div>
{/* Current Understanding + Situation — independent vertical flow */}
<div className="flex gap-3 items-start flex-wrap">
{/* Current Understanding — prominent orienting surface */}
<div className={`rounded-xl border-[2.5px] border-teal-300/70 bg-gradient-to-b from-teal-50/60 to-white px-8 pt-7 pb-8 shadow-sm flex-1 min-w-0 ${hasGraph ? 'lg:max-w-2xl' : ''}`}>
<h2 className="mb-4 text-[11px] font-bold tracking-[.18em] uppercase text-teal-700/60">
Current Understanding
</h2>
<p className="text-lg leading-relaxed text-gray-800">{propUnderstanding}</p>
</div>
{/* Situation panel during initial reflection */}
{hasGraph && (
<div className="space-y-6 lg:col-span-1">
<div className="space-y-6 lg:max-w-xs">
<OriginalSituation scenario={scenario} centralStatement={graph?.centralStatement} />
</div>
)}
{!hasGraph && scenario && (
<div className="space-y-6 lg:col-span-1">
<div className="space-y-6 lg:max-w-xs">
<div className="rounded-lg border border-gray-200/60 bg-gray-50/40 px-5 py-4">
<h2 className="mb-2 text-[11px] font-semibold tracking-widest uppercase text-gray-500">
Situation
@@ -1303,20 +1391,70 @@ export default function ReasoningWorkspace({
</div>
)}
</div>
</div>
)}
{/* Initial proposed findings — unknowns + plausible interpretations from reconstruction */}
<div className="space-y-3" data-testid="initial-proposed-findings">
<h2 className="text-[11px] font-semibold tracking-widest uppercase text-gray-500">
Open Questions
</h2>
{(() => {
const resolvedIds = new Set(graph?.resolvedNodeIds || []);
// Surface only candidate items from the semantic reconstruction that are worth investigating:
// — unknowns (importantUnknowns from the LLM's reconstruction)
// — assumptions (plausibleInterpretations from the LLM's reconstruction)
// Skips observations, states, relationships, transitions — these are already established facts/context.
// Both kinds check status !== "resolved" and excluded resolvedIds to mirror OpenQuestionsPanel logic.
const candidateKinds = ["unknown", "assumption"];
return (
(graph?.nodes || [])
.filter(
(n) =>
candidateKinds.includes(n.kind) &&
n.status !== "resolved" &&
!resolvedIds.has(n.id),
)
.map((node) => {
const tag = node.kind === "assumption" ? "Plausible interpretation" : "Unclear";
return (
<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>
)}
<span className="mt-2 block text-[10px] uppercase tracking-wider text-gray-400">{tag}</span>
</button>
);
})
);
})()}
</div>
</div>
)}
{/* ── Workspace grid: persistent whenever a graph exists ─── */}
{hasGraph && (
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
{/* ── Left lane: active conversation & notebook ───────── */}
<div className={`space-y-6 ${hasGraph ? 'lg:col-span-2' : 'lg:col-span-full'}`}>
{/* Current Understanding — independent row, full-width of left area (cols 1-2) */}
{propUnderstanding && hasCurrentSummaryCondition && postAnalyseStatus !== "success" && (
<div className="lg:row-start-1 lg:col-span-full rounded-xl border-[2.5px] border-teal-300/70 bg-gradient-to-b from-teal-50/60 to-white px-8 pt-7 pb-8 shadow-sm">
<CurrentUnderstandingCard currentSummary={graph?.currentSummary || result?.updatedSituationGraph?.currentSummary} plainLanguage={propUnderstanding} />
</div>
)}
{/* Left column below Understanding: Investigation + Open Questions */}
<div className="lg:row-start-2 lg:col-start-1 lg:col-span-2 space-y-6">
{/* Current investigation (prominent hero section) */}
{postAnalyseStatus !== "success" && (
<CurrentInvestigationCard selectedQuestion={result?.selectedQuestion} graph={graph} />
)}
{/* ── Open questions (case workspace) — no ranking bias ── */}
{postAnalyseStatus !== "success" && (
<OpenQuestionsPanel
graph={graph}
@@ -1338,11 +1476,12 @@ export default function ReasoningWorkspace({
focusedAnswer={focusedAnswer}
setDoneForNowIds={setDoneForNowIds}
setFollowUpQuestion={setFollowUpQuestion}
focusedContributions={focusedContributions}
/>
)}
{/* Terminal state — suppressed during initial reflection */}
{postAnalyseStatus !== "success" && status === "success" && !hasSelectedQuestion && (
{postAnalyseStatus !== "success" && status === "success" && !hasSelectedQuestion && showEvidenceLimit && (
<>
{genuineCompletion && (
<CompletionCard summary={resolveCurrentSummary(propUnderstanding || graph?.currentSummary || result?.updatedSituationGraph?.currentSummary)} />
@@ -1357,84 +1496,71 @@ export default function ReasoningWorkspace({
<InvestigationHistory turns={investigationHistory} />
)}
{/* Supporting context within conversation lane */}
{hasCurrentSummaryCondition && (
<>
{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">
<div className="flex items-center gap-2" role="radiogroup" aria-label="Progress panel variant">
<button
role="radio"
aria-checked={panelVariant === "a"}
onClick={() => setPanelVariant("a")}
onKeyDown={(e) => {
if (e.key === "ArrowRight") setPanelVariant("b");
if (e.key === "ArrowLeft") setPanelVariant("c");
}}
className={`text-xs transition ${panelVariant === "a" ? "font-medium text-gray-700 underline" : "text-gray-400 hover:text-gray-500"}`}
>
A
</button>
<span className="text-gray-300">/</span>
<button
role="radio"
aria-checked={panelVariant === "b"}
onClick={() => setPanelVariant("b")}
onKeyDown={(e) => {
if (e.key === "ArrowRight") setPanelVariant("c");
if (e.key === "ArrowLeft") setPanelVariant("a");
}}
className={`text-xs transition ${panelVariant === "b" ? "font-medium text-gray-700 underline" : "text-gray-400 hover:text-gray-500"}`}
>
B
</button>
<span className="text-gray-300">/</span>
<button
role="radio"
aria-checked={panelVariant === "c"}
onClick={() => setPanelVariant("c")}
onKeyDown={(e) => {
if (e.key === "ArrowRight") setPanelVariant("a");
if (e.key === "ArrowLeft") setPanelVariant("b");
}}
className={`text-xs transition ${panelVariant === "c" ? "font-medium text-gray-700 underline" : "text-gray-400 hover:text-gray-500"}`}
>
C (exp)
</button>
</div>
<div className="opacity-75">
{panelVariant === "a"
? <InvestigationSummaryPanel graph={graph} selectedQuestion={selectedQ} result={result} updateStatus={updateStatus} />
: panelVariant === "b"
? <InvestigationSummaryPanelV2 graph={graph} selectedQuestion={selectedQ} result={result} updateStatus={updateStatus} />
: <InvestigationSummaryPanelV3 graph={graph} selectedQuestion={selectedQ} result={result} />
}
</div>
</div>
)}
</>
{/* ── 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">
<div className="flex items-center gap-2" role="radiogroup" aria-label="Progress panel variant">
<button
role="radio"
aria-checked={panelVariant === "a"}
onClick={() => setPanelVariant("a")}
onKeyDown={(e) => {
if (e.key === "ArrowRight") setPanelVariant("b");
if (e.key === "ArrowLeft") setPanelVariant("c");
}}
className={`text-xs transition ${panelVariant === "a" ? "font-medium text-gray-700 underline" : "text-gray-400 hover:text-gray-500"}`}
>
A
</button>
<span className="text-gray-300">/</span>
<button
role="radio"
aria-checked={panelVariant === "b"}
onClick={() => setPanelVariant("b")}
onKeyDown={(e) => {
if (e.key === "ArrowRight") setPanelVariant("c");
if (e.key === "ArrowLeft") setPanelVariant("a");
}}
className={`text-xs transition ${panelVariant === "b" ? "font-medium text-gray-700 underline" : "text-gray-400 hover:text-gray-500"}`}
>
B
</button>
<span className="text-gray-300">/</span>
<button
role="radio"
aria-checked={panelVariant === "c"}
onClick={() => setPanelVariant("c")}
onKeyDown={(e) => {
if (e.key === "ArrowRight") setPanelVariant("a");
if (e.key === "ArrowLeft") setPanelVariant("b");
}}
className={`text-xs transition ${panelVariant === "c" ? "font-medium text-gray-700 underline" : "text-gray-400 hover:text-gray-500"}`}
>
C (exp)
</button>
</div>
<div className="opacity-75">
{panelVariant === "a"
? <InvestigationSummaryPanel graph={graph} selectedQuestion={selectedQ} result={result} updateStatus={updateStatus} />
: panelVariant === "b"
? <InvestigationSummaryPanelV2 graph={graph} selectedQuestion={selectedQ} result={result} updateStatus={updateStatus} />
: <InvestigationSummaryPanelV3 graph={graph} selectedQuestion={selectedQ} result={result} />
}
</div>
</div>
)}
</div>
{/* ── Right lane: stable supporting reference ───────── */}
{/* ── Right lane: stable supporting reference (independent column) ───────── */}
{hasCurrentSummaryCondition && (
<div className="space-y-6 lg:col-span-1">
{/* RTO.26B — situation card from scenario text when no graph */}
{!graph && scenario && (
<div className="rounded-lg border border-gray-200/60 bg-gray-50/40 px-5 py-4">
<h2 className="mb-2 text-[11px] font-semibold tracking-widest uppercase text-gray-500">
Situation
</h2>
<p className="whitespace-pre-wrap text-sm leading-relaxed text-gray-600">
{scenario}
</p>
</div>
<div className="lg:row-start-2 lg:col-start-3 space-y-6">
{/* Situation — always here when condition met, independent of left column height */}
{(scenario || graph?.centralStatement) && (
<OriginalSituation scenario={scenario} centralStatement={graph?.centralStatement || scenario} />
)}
{!propUnderstanding && graph && postAnalyseStatus !== "success" && (
<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} />