test(ui): explore branch notebook composition

This commit is contained in:
2026-08-20 08:42:43 +01:00
parent e7a1bc689c
commit df0e3b5a9b
2 changed files with 325 additions and 305 deletions
+315 -304
View File
@@ -310,6 +310,157 @@ function EvidenceLimitCard({ summary }) {
);
}
// ── Quiet facilitator state (experiment mode) ─────────────────────
function QuietStateCard() {
return (
<div className="rounded-lg border border-gray-200/60 bg-gray-50/40 px-5 py-5 text-center">
<p className="text-sm leading-relaxed text-gray-500 mb-1">
Nothing more to add here right now
</p>
<p className="text-xs leading-relaxed text-gray-400">
You can continue with another question, switch branches, or return when
you have more information.
</p>
</div>
);
}
// ── New connection / late-result indicator ────────────────────────
function UpdatedConnectionCard({ update }) {
return (
<div className="space-y-2">
<h2 className="text-[11px] font-semibold tracking-widest uppercase text-gray-500">
New update
</h2>
<div className="rounded-lg border border-blue-200/60 bg-blue-50/40 px-4 py-3">
<p className="text-sm leading-relaxed text-blue-800/80">{update.text}</p>
</div>
</div>
);
}
// ── Branch notebook content (RTO.27A) ────────────────────────────
// Smallest useful branch-scoped composition from fixture data.
// Only renders sections grounded in existing records.
function BranchNotebookContent({
branchContext,
contributions,
openQuestions,
lateResults,
inactiveBranchNewResults,
}) {
const hasOpenItems = openQuestions && openQuestions.length > 0;
const hasContributions = contributions && contributions.length > 0;
return (
<div className="space-y-6">
{/* Why this branch exists */}
{branchContext && (
<div className="rounded-lg border border-blue-200/60 bg-blue-50/40 px-5 py-4">
<h2 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">
Current branch
</h2>
<p className="text-base font-medium leading-tight text-gray-900">{branchContext.label}</p>
{branchContext.origin && (
<p className="mt-2 text-sm leading-relaxed text-gray-600">
Trying to understand: {branchContext.origin}
</p>
)}
</div>
)}
{/* What we know here */}
{hasContributions && (
<div className="space-y-2">
<h2 className="text-[11px] font-semibold tracking-widest uppercase text-gray-500">
What we know here
</h2>
<div className="space-y-2">
{contributions.map((c, i) => (
<div key={i} className="rounded-lg border border-gray-200/80 bg-gray-50/60 px-4 py-3">
<p className="text-sm leading-relaxed text-gray-700">{c.text}</p>
</div>
))}
</div>
</div>
)}
{/* Still uncertain here */}
{hasOpenItems && (
<div className="space-y-2">
<h2 className="text-[11px] font-semibold tracking-widest uppercase text-gray-500">
Still uncertain / open questions
</h2>
<div className="space-y-1">
{openQuestions.map((q, i) => (
<QuestionRow key={q.id} question={q} />
))}
</div>
</div>
)}
{/* New connection / updates (active branch) */}
{lateResults && lateResults.length > 0 && (
<UpdatedConnectionCard update={lateResults[0]} />
)}
{/* Quiet state — only when there are no open items and no updates */}
{!hasOpenItems && !lateResults?.length && (
<QuietStateCard />
)}
</div>
);
}
// ── Question row (renders clickable card with optional focused content) ─
function QuestionRow({ question }) {
const [isFocused, setIsFocused] = useState(false);
if (!isFocused) {
return (
<button
onClick={() => setIsFocused(true)}
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"
>
<p className="text-sm leading-snug text-gray-600">{question.label}</p>
</button>
);
}
return (
<div className="rounded-lg border border-gray-200 bg-white px-4 py-4 space-y-3">
<p className="text-sm font-medium text-gray-900 leading-snug">{question.label}</p>
<div className="space-y-2">
<p className="text-sm leading-relaxed text-gray-500">
We have not explored this yet. Do you want to work through it?
</p>
<button
onClick={(e) => {
e.stopPropagation();
// Placeholder — production would call API for focused investigation
}}
style={{ cursor: "pointer" }}
className="rounded-lg border border-blue-600 bg-white px-4 py-2 text-sm font-medium text-blue-700 hover:bg-blue-50 transition"
>
Work through this
</button>
</div>
<button
onClick={() => setIsFocused(false)}
style={{ cursor: "pointer" }}
className="text-xs text-gray-400 underline hover:text-gray-600 transition"
>
Back to open questions
</button>
</div>
);
}
// ── Current understanding card ────────────────────────────────
function CurrentUnderstandingCard({ currentSummary, plainLanguage }) {
if (plainLanguage) return <PlainLanguageCard summary={plainLanguage} />;
@@ -510,6 +661,132 @@ function getErrorType(errorStr, stage, hasGraph) {
return null;
}
// ── Open questions panel (production, non-experiment mode) ────────
function OpenQuestionsPanel({
graph, selectedPresentationItemId, focusedPresentationItemId, hasFocusedContent,
focused, formulationStep, formulateMsg, processingStep, deconstructMsg, doneForNowIds,
startFocused, handleDeconstructSubmit, retryFormulation, setSelectedPresentationItemId,
setFocusedPresentationItemId, setFocusedAnswer, focusedAnswer, setDoneForNowIds,
}) {
const openNodes = (graph?.nodes || []).filter(
(n) => n.kind === "unknown" && n.status !== "resolved" && !doneForNowIds.includes(n.id),
);
if (openNodes.length <= 1) return null;
return (
<div className="space-y-4">
<h2 className="text-[11px] font-semibold tracking-widest uppercase text-gray-500">
Open questions
</h2>
<div className="space-y-1">
{openNodes.map((node) => {
const isSelected = selectedPresentationItemId === node.id;
const isFocused = focusedPresentationItemId === node.id;
return (
<div key={node.id}>
<div
onClick={() => {
if (!focused?.question?.trim() || (focused && !hasFocusedContent())) {
setSelectedPresentationItemId(
selectedPresentationItemId === node.id ? null : node.id,
);
}
}}
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"
>
<p className={`leading-snug ${isSelected ? "text-sm font-medium text-gray-900" : "text-sm text-gray-600"}`}>
{node.label}
</p>
{isSelected && !hasFocusedContent() && (
<div className="mt-3 space-y-3">
<p className="text-sm leading-relaxed text-gray-500">
We have not explored this yet. Do you want to work through it?
</p>
<button
onClick={(e) => { e.stopPropagation(); startFocused(node.id); }}
style={{ cursor: "pointer" }}
className="rounded-lg border border-blue-600 bg-white px-4 py-2 text-sm font-medium text-blue-700 hover:bg-blue-50 transition"
>
Work through this
</button>
</div>
)}
{isFocused && (() => {
const focusedNode = graph?.nodes.find((n) => n.id === node.id);
return (
<div className="mt-4 space-y-4">
{hasFocusedContent() && (
<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-${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 we learned</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>
{focused.result.assumptions?.length > 0 && <div><h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">Assumptions in this response</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>}
{focused.result.relationships?.length > 0 && <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.result.possibleFollowUpQuestions?.length > 0 && <div><h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">Questions this raises</h3><ul className="list-disc pl-5 space-y-1">{focused.result.possibleFollowUpQuestions.map((q, i) => (<li key={i} className="text-sm leading-relaxed text-gray-700">{q}</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>
);
})()}
</div>
</div>
);
})}
</div>
{doneForNowIds.length > 0 && (
<div className="pt-3 border-t border-gray-200">
<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">
<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>
</div>
))}
</div>
</div>
)}
</div>
);
}
export default function ReasoningWorkspace({
scenario,
status,
@@ -526,6 +803,7 @@ export default function ReasoningWorkspace({
experimentalBranches,
branchLocalQuestions,
branchLocalContributions,
branchLocalLateResults,
inactiveBranchNewResults,
}) {
const [investigationHistory, setInvestigationHistory] = useState([]);
@@ -829,313 +1107,41 @@ export default function ReasoningWorkspace({
<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'}`}>
{/* RTO.26Bexperiment header when using branch-scoped fixture */}
{/* RTO.27Abranch notebook content (replaces question-queue when in experiment mode) */}
{experimentalBranches && experimentalBranches.length > 0 && (
<div className="rounded-lg border border-purple-300/60 bg-purple-50/30 px-4 py-2">
<p className="text-[10px] font-semibold tracking-widest uppercase text-purple-400/70">
RTO.26B branch-scoped reasoning (experimental fixture)
</p>
</div>
)}
{/* Active branch context (experiment RTO.25D) */}
{branchContext && (hasGraph || experimentalBranches) && (
<div className="rounded-lg border border-blue-200/60 bg-blue-50/40 px-5 py-4">
<h2 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">
Current branch
</h2>
<p className="text-base font-medium leading-tight text-gray-900">{branchContext.label}</p>
{branchContext.origin && (
<p className="mt-2 text-sm leading-relaxed text-gray-600">
Trying to understand: {branchContext.origin}
</p>
)}
</div>
<BranchNotebookContent
branchContext={branchContext}
contributions={branchLocalContributions || []}
openQuestions={branchLocalQuestions || []}
lateResults={branchLocalLateResults || []}
inactiveBranchNewResults={inactiveBranchNewResults || {}}
/>
)}
{/* ── Open questions (case workspace) — no ranking bias ── */}
{(() => {
/* RTO.26B: when experimental branch-scoped data is present, use it as the source of truth */
const openNodes = experimentalBranches && branchLocalQuestions && branchLocalQuestions.length > 0
? branchLocalQuestions
: ((graph?.nodes || []).filter(
(n) => n.kind === "unknown" && n.status !== "resolved" && !doneForNowIds.includes(n.id),
));
if (openNodes.length <= 1) return null;
return (
<div className="space-y-4">
<h2 className="text-[11px] font-semibold tracking-widest uppercase text-gray-500">
In this branch Open questions
</h2>
<div className="space-y-1">
{openNodes.map((node) => {
const isSelected = selectedPresentationItemId === node.id;
const isFocused = focusedPresentationItemId === node.id;
const hasReasoningSupport = result?.selectedQuestion?.nodeId === node.id && node.id === focusedPresentationItemId;
return (
<div key={node.id}>
{/* One invariant outer shell — same border/padding/position across all states */}
<div
onClick={() => {
if (result?.selectedQuestion?.nodeId !== node.id) {
setSelectedPresentationItemId(
selectedPresentationItemId === node.id
? null
: node.id,
);
}
}}
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"
>
<p className={`leading-snug ${isSelected ? "text-sm font-medium text-gray-900" : "text-sm text-gray-600"}`}>
{node.label}
</p>
{/* Invitation — shows when selected but no content yet */}
{isSelected && !hasFocusedContent() && (
<div className="mt-3 space-y-3">
{/* Branch-local contribution scoped to this question */}
{(experimentalBranchContributions?.length ?? 0) > 0 && (
(() => {
const qContribs = experimentalBranchContributions.filter(
c => node.id === c.questionId,
);
if (!qContribs.length) return null;
return (
<div className="rounded-lg border border-gray-200/80 bg-gray-50/60 px-4 py-3">
<h3 className="mb-1 text-[10px] font-semibold tracking-widest uppercase text-gray-400">
Contribution
</h3>
<p className="text-sm leading-relaxed text-gray-700">{qContribs[0].text}</p>
</div>
);
})()
)}
<p className="text-sm leading-relaxed text-gray-500">
We have not explored this yet. Do you want to work through it?
</p>
<button
onClick={(e) => {
e.stopPropagation();
startFocused(node.id);
}}
style={{ cursor: "pointer" }}
className="rounded-lg border border-blue-600 bg-white px-4 py-2 text-sm font-medium text-blue-700 hover:bg-blue-50 transition"
>
Work through this
</button>
</div>
)}
{/* Focused state (Work through this clicked) */}
{isFocused && (() => {
const focusedNode = graph?.nodes.find((n) => n.id === node.id);
return (
<div className="mt-4 space-y-4">
{/* Real focused investigation content */}
{hasFocusedContent() && (() => {
return null;
})()}
{hasFocusedContent() && (
<div className="space-y-4 rounded-lg border border-gray-200 bg-white p-5">
{/* Formulated question */}
{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}
{/* Answer textarea (hidden while processing) */}
{focused?.question?.trim() && processingStep !== "active" && focused.status === "formulated" && (
<div>
<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();
const targetNodeId = focusedPresentationItemId;
handleDeconstructSubmit(targetNodeId, 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>
)}
{/* Deconstruction loading */}
{processingStep === "active" && (
<p className="text-sm text-blue-600/70">{deconstructMsg}</p>
)}
{/* Focused result — structured response */}
{focused?.result && (
<>
<div>
<h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">
What we learned
</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>
{focused.result.assumptions && focused.result.assumptions.length > 0 && (
<div>
<h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">
Assumptions in this response
</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>
)}
{focused.result.relationships && focused.result.relationships.length > 0 && (
<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.result.possibleFollowUpQuestions && focused.result.possibleFollowUpQuestions.length > 0 && (
<div>
<h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">
Questions this raises
</h3>
<ul className="list-disc pl-5 space-y-1">
{focused.result.possibleFollowUpQuestions.map((q, i) => (
<li key={i} className="text-sm leading-relaxed text-gray-700">{q}</li>
))}
</ul>
</div>
)}
</>
)}
{/* Formulation or deconstruction failure */}
{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>
)}
{/* Footer controls — horizontally separated (RTO.26B) */}
<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>
);
})()}
</div>
</div>
);
})}
</div>
{/* Done for now — separate visual area */}
{doneForNowIds.length > 0 && (
<div className="pt-3 border-t border-gray-200">
<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">
<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>
</div>
))}
</div>
</div>
)}
</div>
);
})()}
{/* Only shown when NOT in experiment mode (experiment uses BranchNotebookContent) */}
{!experimentalBranches || experimentalBranches.length === 0 ? (
<OpenQuestionsPanel
graph={graph}
selectedPresentationItemId={selectedPresentationItemId}
focusedPresentationItemId={focusedPresentationItemId}
hasFocusedContent={hasFocusedContent}
focused={getFocusedInvestigation()}
formulationStep={formulationStep}
formulateMsg={formulateMsg}
processingStep={processingStep}
deconstructMsg={deconstructMsg}
doneForNowIds={doneForNowIds}
startFocused={startFocused}
handleDeconstructSubmit={handleDeconstructSubmit}
retryFormulation={retryFormulation}
setSelectedPresentationItemId={setSelectedPresentationItemId}
setFocusedPresentationItemId={setFocusedPresentationItemId}
setFocusedAnswer={setFocusedAnswer}
focusedAnswer={focusedAnswer}
setDoneForNowIds={setDoneForNowIds}
/>
) : null}
{/* Terminal state */}
{status === "success" && !hasSelectedQuestion && (
@@ -1144,7 +1150,12 @@ export default function ReasoningWorkspace({
<CompletionCard summary={resolveCurrentSummary(propUnderstanding || graph?.currentSummary || result?.updatedSituationGraph?.currentSummary)} />
)}
{!genuineCompletion && (
<EvidenceLimitCard summary={resolveCurrentSummary(propUnderstanding || graph?.currentSummary || result?.updatedSituationGraph?.currentSummary)} />
experimentalBranches && experimentalBranches.length > 0 ? (
// In experiment mode: quiet facilitator state, not evidence limit verdict
<QuietStateCard />
) : (
<EvidenceLimitCard summary={resolveCurrentSummary(propUnderstanding || graph?.currentSummary || result?.updatedSituationGraph?.currentSummary)} />
)
)}
</>
)}
+10 -1
View File
@@ -226,7 +226,8 @@ export default function ScenarioForm() {
/* ── RTO.25A — passive late-result branch switcher (experimental) ── */
const [activeBranchId, setActiveBranchId] = useState("branch-b");
const [branchNewResults, setBranchNewResults] = useState({});
// Pre-seed Competitor development with a late result for RTO.27A testing
const [branchNewResults, setBranchNewResults] = useState({ "branch-a": true });
/* ── RTO.26B — experimental branch-scoped reasoning fixture ───── */
@@ -262,6 +263,12 @@ export default function ScenarioForm() {
return branchScoped.getBranchContributions(activeBranch.id);
}, [activeBranch, branchScoped]);
// RTO.27A — late results for active branch
const experimentalBranchLateResults = useMemo(() => {
if (!activeBranch) return [];
return (branchScoped.getBranchLateResults?.(activeBranch.id) || []).map(lr => ({ text: lr.text }));
}, [activeBranch, branchScoped]);
// Determine which non-active branches have new results for passive indicator
const inactiveBranchNewResults = useMemo(() => {
const result = {};
@@ -463,6 +470,7 @@ export default function ScenarioForm() {
experimentalBranches={BRANCHES.length > 0 ? BRANCHES : undefined}
branchLocalQuestions={experimentalBranchQuestions.length > 0 ? experimentalBranchQuestions : undefined}
branchLocalContributions={experimentalBranchContributions.length > 0 ? experimentalBranchContributions : undefined}
branchLocalLateResults={experimentalBranchLateResults.length > 0 ? experimentalBranchLateResults : undefined}
inactiveBranchNewResults={Object.keys(inactiveBranchNewResults).length > 0 ? inactiveBranchNewResults : undefined}
onRestart={() => { setStatus("idle"); setResult(null); setScenario(""); }}
/>
@@ -659,6 +667,7 @@ export default function ScenarioForm() {
experimentalBranches={BRANCHES.length > 0 ? BRANCHES : undefined}
branchLocalQuestions={experimentalBranchQuestions.length > 0 ? experimentalBranchQuestions : undefined}
branchLocalContributions={experimentalBranchContributions.length > 0 ? experimentalBranchContributions : undefined}
branchLocalLateResults={experimentalBranchLateResults.length > 0 ? experimentalBranchLateResults : undefined}
inactiveBranchNewResults={Object.keys(inactiveBranchNewResults).length > 0 ? inactiveBranchNewResults : undefined}
onRestart={() => {
clearSession();