fix(confidence-engine): preserve focused learning across turns

This commit is contained in:
2026-08-26 14:07:16 +01:00
parent b26ea7d0ba
commit ac5fbe7896
+74 -12
View File
@@ -167,6 +167,7 @@ function FocusedQuestionBody({
setFocusedPresentationItemId, setFocusedPresentationItemId,
setDoneForNowIds, setDoneForNowIds,
setFollowUpQuestion, setFollowUpQuestion,
focusedContributions,
}) { }) {
return ( return (
<> <>
@@ -199,22 +200,33 @@ function FocusedQuestionBody({
{focused?.result && ( {focused?.result && (
<> <>
{/* Prior accumulated learning (prior turns, current turn excluded — shown above) */}
<PriorContributionsSummary nodeId={nodeId} contributions={focusedContributions || []} />
<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">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">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> <div><h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">Questions this raises</h3>
{(focused.result.possibleFollowUpQuestions || []).length > 0 ? ( {(focused.result.possibleFollowUpQuestions || []).length > 0 ? (
<div className="space-y-1 mt-1"> <div className="space-y-1 mt-1">
{focused.result.possibleFollowUpQuestions.map((q, i) => ( {focused.result.possibleFollowUpQuestions.map((q, i) => {
<button const isCurrentQuestion = q === focused?.question;
key={i} return (
onClick={(e) => { e.stopPropagation(); setFollowUpQuestion(q); }} <button
style={{ cursor: "pointer" }} key={i}
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" onClick={(e) => { if (!isCurrentQuestion) { e.stopPropagation(); setFollowUpQuestion(q); } }}
data-testid="follow-up-question" style={{ cursor: isCurrentQuestion ? "default" : "pointer" }}
> className={`w-full text-left rounded-lg border px-3 py-2.5 text-sm leading-relaxed transition ${
{q} pick this question isCurrentQuestion
</button> ? "border-gray-200 bg-gray-100/60 text-gray-400 cursor-default"
))} : "border-blue-200/60 bg-blue-50/40 text-gray-800 hover:border-blue-300 hover:bg-blue-100/60"
}`}
data-testid="follow-up-question"
>
{q}
{isCurrentQuestion ? " (current question)" : " → pick this question"}
</button>
);
})}
</div> </div>
) : ( ) : (
<p className="text-xs text-gray-400">None yet</p> <p className="text-xs text-gray-400">None yet</p>
@@ -403,7 +415,55 @@ function EvidenceLimitCard({ summary }) {
); );
} }
// ── Per-thread contribution badge (compact indicator) ────────────── // ── Prior contribution summary (embedded within FocusedQuestionBody) ───
function PriorContributionsSummary({ nodeId, contributions }) {
const threadContribs = contributions.filter((c) => c.targetNodeId === nodeId);
if (!threadContribs.length) return null;
// Exclude the most recent contribution — it is already shown as the current result above.
const priorContribs = threadContribs.slice(0, -1);
if (!priorContribs.length) return null;
return (
<div className="rounded-lg border border-gray-200/60 bg-gray-50/40 px-4 py-3">
<h4 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">
Previous learning
</h4>
{priorContribs.map((c, idx) => (
<details key={c.id || idx} className="mb-2 border-b border-gray-200/40 last:border-0 pb-2 last:pb-0" open={idx === 0}>
<summary className="cursor-pointer text-xs font-medium text-gray-500 hover:text-gray-700 select-none py-1">
Turn {c.sequence || idx + 1} contribution ({c.observations?.length ?? 0} observations, {c.uncertainties?.length ?? 0} unclear)
</summary>
<div className="pt-2 space-y-3">
{c.observations?.length ? (
<div>
<h5 className="text-[10px] font-semibold tracking-widest uppercase text-gray-500">What this tells us</h5>
<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}
{c.uncertainties?.length ? (
<div>
<h5 className="text-[10px] font-semibold tracking-widest uppercase text-gray-500">Still unclear</h5>
<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}
</div>
</details>
))}
</div>
);
}
// ── Thread contributions badge (standalone — used outside focused body) ───
function ThreadContributionsBadge({ nodeId, contributions }) { function ThreadContributionsBadge({ nodeId, contributions }) {
const threadContribs = contributions.filter((c) => c.targetNodeId === nodeId); const threadContribs = contributions.filter((c) => c.targetNodeId === nodeId);
@@ -965,6 +1025,7 @@ function OpenQuestionsPanel({
setFocusedPresentationItemId={setFocusedPresentationItemId} setFocusedPresentationItemId={setFocusedPresentationItemId}
setDoneForNowIds={setDoneForNowIds} setDoneForNowIds={setDoneForNowIds}
setFollowUpQuestion={setFollowUpQuestion} setFollowUpQuestion={setFollowUpQuestion}
focusedContributions={focusedContributions}
/> />
{/* Thread contributions for this node */} {/* Thread contributions for this node */}
@@ -1575,6 +1636,7 @@ export default function ReasoningWorkspace({
setFocusedPresentationItemId={setFocusedPresentationItemId} setFocusedPresentationItemId={setFocusedPresentationItemId}
setDoneForNowIds={setDoneForNowIds} setDoneForNowIds={setDoneForNowIds}
setFollowUpQuestion={setFollowUpQuestion} setFollowUpQuestion={setFollowUpQuestion}
focusedContributions={focusedContributions}
/> />
); );
})()} })()}