fix(confidence-engine): preserve follow-up progression ownership

This commit is contained in:
2026-08-30 08:28:29 +01:00
parent 17c6048047
commit 8bded90094
4 changed files with 408 additions and 9 deletions
+48 -8
View File
@@ -149,6 +149,20 @@ function FocusedQuestionBody({
// Without this guard, selecting a follow-up question would erase "Previously answered" + "Your response".
const hasCompletedContext = processingStep !== "active" && Boolean(focused?.result);
// ── Source of completed context: latest canonical Contribution when follow-up is active ──
// After setFollowUpQuestion() mutates focused.question/answer, derive from the
// latest completed Contribution so the narrative remains correct.
const hasActiveFollowUp = hasCompletedContext && !hasAnswer
&& (focused.result?.possibleFollowUpQuestions || []).some((q) => q === focused?.question);
const latestCompletedContrib = [...(focusedContributions || [])].reverse().find((c) => c?.question && c?.answer);
const displayedCompletedQuestion = hasActiveFollowUp
? (latestCompletedContrib?.question ?? focused?.question)
: focused?.question;
const displayedCompletedAnswer = hasActiveFollowUp
? (latestCompletedContrib?.answer ?? focused?.answer ?? "")
: focused?.answer;
// ── Local correction state (FQB-owned, not propagated upward) ─
const [editingFindingId, setEditingFindingId] = useState(null);
const [draft, setDraft] = useState("");
@@ -180,15 +194,15 @@ function FocusedQuestionBody({
<div className="space-y-4 rounded-lg border border-gray-200 bg-white p-5">
{(hasAnswer || hasCompletedContext) && focused?.question?.trim() ? (
<div className="space-y-3">
{/* Previously answered question */}
{/* Previously answered question — sourced from contribution when follow-up is active */}
<div>
<h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">Previously answered</h3>
<p className="text-base font-medium leading-relaxed text-gray-900">{focused.question}</p>
<p className="text-base font-medium leading-relaxed text-gray-900">{displayedCompletedQuestion}</p>
</div>
{/* User's verbatim response — distinct provenance from Engine-derived content */}
<div>
<h3 className="mb-1 text-[11px] font-semibold tracking-widest uppercase text-gray-500">Your response</h3>
<p className="text-sm leading-relaxed text-gray-800">{focused.answer}</p>
<p className="text-sm leading-relaxed text-gray-800">{displayedCompletedAnswer}</p>
</div>
</div>
) : focused?.question?.trim() ? (
@@ -200,7 +214,7 @@ function FocusedQuestionBody({
<p className="text-sm text-blue-600/70">{formulateMsg}</p>
) : null}
{focused?.question?.trim() && processingStep !== "active" && focused.status === "formulated" && !hasAnswer && (
{focused?.question?.trim() && processingStep !== "active" && focused.status === "formulated" && !hasAnswer && !hasActiveFollowUp && (
<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?" />
@@ -212,9 +226,8 @@ function FocusedQuestionBody({
{focused?.result && (
<>
{/* Prior accumulated learning (prior turns, current turn excluded — shown above) */}
<PriorContributionsSummary nodeId={nodeId} contributions={focusedContributions || []} findings={findings} />
{/* Prior accumulated learning removed from left pane — SecondaryPreviousLearning on the right owns historical Previous Learning exclusively */}
{/* PriorContributionsSummary was causing duplication in the two-column focused workspace */}
<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-2">{(currentFindings?.length ? currentFindings : (focused.result.observations || [])).map((item, i) => {
const isFinding = typeof item === "object" && item !== null && "id" in item;
const disposition = isFinding ? item.userDisposition : null;
@@ -284,6 +297,30 @@ function FocusedQuestionBody({
) : (
<p className="text-xs text-gray-400">None yet</p>
)}
{/* In-place answer textarea for the active follow-up — renders only when a candidate is selected */}
{hasActiveFollowUp ? (
<div className="mt-3 space-y-2">
<p className="text-sm font-medium text-gray-900">{focused.question}</p>
<textarea
id={`rw-answer-fu-${nodeId}`}
value={focusedAnswer}
onChange={(e) => setFocusedAnswer(e.target.value)}
rows={4}
data-testid="follow-up-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="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>
) : null}
</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>
@@ -1074,7 +1111,7 @@ function FocusedInvestigationWorkspace({
setFocusedPresentationItemId={setFocusedPresentationItemId}
setDoneForNowIds={setDoneForNowIds}
setFollowUpQuestion={setFollowUpQuestion}
focusedContributions={hasResult ? [] : (focusedContributions || [])}
focusedContributions={focusedContributions || []}
currentFindings={currentFindings || []}
onUpdateFindingDisposition={onUpdateFindingDisposition}
onUpdateFindingProposition={onUpdateFindingProposition}
@@ -1208,6 +1245,9 @@ function OpenQuestionsPanel({
);
}
// Export for testability of in-place follow-up ownership repair
export { FocusedQuestionBody, SecondaryPreviousLearning };
export default function ReasoningWorkspace({
scenario,
status,