feat(confidence-engine): support finding corrections
This commit is contained in:
@@ -170,10 +170,35 @@ function FocusedQuestionBody({
|
||||
focusedContributions,
|
||||
currentFindings,
|
||||
onUpdateFindingDisposition,
|
||||
onUpdateFindingProposition,
|
||||
}) {
|
||||
const hasContent = focused?.question?.trim() || formulationStep === "active" || processingStep === "active" || focused?.error;
|
||||
const hasResult = Boolean(focused?.result);
|
||||
|
||||
// ── Local correction state (FQB-owned, not propagated upward) ─
|
||||
const [editingFindingId, setEditingFindingId] = useState(null);
|
||||
const [draft, setDraft] = useState("");
|
||||
|
||||
function startEditing(id, proposition) {
|
||||
setEditingFindingId(id);
|
||||
setDraft(proposition ?? "");
|
||||
}
|
||||
|
||||
function cancelEditing() {
|
||||
setEditingFindingId(null);
|
||||
setDraft("");
|
||||
}
|
||||
|
||||
function saveEditing() {
|
||||
const trimmed = (draft ?? "").trim();
|
||||
if (!trimmed || !editingFindingId) {
|
||||
cancelEditing();
|
||||
return;
|
||||
}
|
||||
onUpdateFindingProposition?.(editingFindingId, trimmed);
|
||||
cancelEditing();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isFocused && hasContent && (
|
||||
@@ -206,14 +231,40 @@ function FocusedQuestionBody({
|
||||
<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;
|
||||
const isEditing = isFinding && editingFindingId === item.id;
|
||||
if (!isFinding) {
|
||||
return (
|
||||
<li key={i} className="text-sm leading-relaxed text-gray-700">{item}</li>
|
||||
);
|
||||
}
|
||||
if (isEditing) {
|
||||
return (
|
||||
<li key={i} className="text-sm leading-relaxed text-gray-700 flex items-start gap-2">
|
||||
<textarea
|
||||
value={draft}
|
||||
onChange={(e) => setDraft(e.target.value)}
|
||||
rows={2}
|
||||
data-testid="proposition-editor"
|
||||
className="flex-1 rounded border border-blue-300 bg-blue-50/40 px-2 py-1 text-sm focus:border-blue-400 focus:outline-none focus:ring-1 focus:ring-blue-300"
|
||||
/>
|
||||
<div className="flex gap-1 shrink-0 mt-[2px]">
|
||||
<button onClick={(e) => { e.stopPropagation(); saveEditing(); }} data-testid="proposition-save" className="text-[10px] font-medium text-blue-600 underline shrink-0 hover:text-blue-700">Save</button>
|
||||
<button onClick={(e) => { e.stopPropagation(); cancelEditing(); }} data-testid="proposition-cancel" className="text-[10px] font-medium text-gray-400 underline shrink-0 hover:text-gray-500">Cancel</button>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<li key={i} className="text-sm leading-relaxed text-gray-700 flex items-start gap-2">
|
||||
<span className="flex-1">{currentFindings?.length ? item.proposition : item}</span>
|
||||
<span className="flex-1">{item.proposition}</span>
|
||||
{onUpdateFindingProposition && (
|
||||
<button onClick={(e) => { e.stopPropagation(); startEditing(item.id, item.proposition); }} data-testid={`not-quite-${item.id}`} className="mt-[2px] text-[10px] font-medium text-amber-500 underline shrink-0 hover:text-amber-600">Not quite</button>
|
||||
)}
|
||||
{isFinding && onUpdateFindingDisposition && (
|
||||
disposition === "not_relevant" ? (
|
||||
<button onClick={(e) => { e.stopPropagation(); onUpdateFindingDisposition(item.id, null); }} className="mt-[2px] text-[10px] font-medium text-teal-600 underline shrink-0 hover:text-teal-700" title="Restore to understanding">restore</button>
|
||||
<button onClick={(e) => { e.stopPropagation(); onUpdateFindingDisposition(item.id, null); }} data-testid={`restore-${item.id}`} className="mt-[2px] text-[10px] font-medium text-teal-600 underline shrink-0 hover:text-teal-700" title="Restore to understanding">restore</button>
|
||||
) : (
|
||||
<button onClick={(e) => { e.stopPropagation(); onUpdateFindingDisposition(item.id, "not_relevant"); }} className="mt-[2px] text-[10px] font-medium text-gray-400 underline shrink-0 hover:text-red-500" title="Remove from understanding">not relevant</button>
|
||||
<button onClick={(e) => { e.stopPropagation(); onUpdateFindingDisposition(item.id, "not_relevant"); }} data-testid={`not-relevant-${item.id}`} className="mt-[2px] text-[10px] font-medium text-gray-400 underline shrink-0 hover:text-red-500" title="Remove from understanding">not relevant</button>
|
||||
)
|
||||
)}
|
||||
</li>
|
||||
@@ -1041,6 +1092,7 @@ function FocusedInvestigationWorkspace({
|
||||
focusedContributions,
|
||||
currentFindings,
|
||||
onUpdateFindingDisposition,
|
||||
onUpdateFindingProposition,
|
||||
}) {
|
||||
const hasResult = Boolean(focused?.result);
|
||||
|
||||
@@ -1070,6 +1122,7 @@ function FocusedInvestigationWorkspace({
|
||||
focusedContributions={hasResult ? [] : (focusedContributions || [])}
|
||||
currentFindings={currentFindings || []}
|
||||
onUpdateFindingDisposition={onUpdateFindingDisposition}
|
||||
onUpdateFindingProposition={onUpdateFindingProposition}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1091,6 +1144,7 @@ function OpenQuestionsPanel({
|
||||
setFocusedPresentationItemId, setFocusedAnswer, focusedAnswer, setDoneForNowIds,
|
||||
setFollowUpQuestion, focusedContributions, focusedInvestigations, setIsFocusedWorkspaceOpen,
|
||||
onUpdateFindingDisposition,
|
||||
onUpdateFindingProposition,
|
||||
}) {
|
||||
const openNodes = (graph?.nodes || []).filter(
|
||||
(n) => n.kind === "unknown" && n.status !== "resolved" && !doneForNowIds.includes(n.id),
|
||||
@@ -1169,6 +1223,7 @@ function OpenQuestionsPanel({
|
||||
setFollowUpQuestion={setFollowUpQuestion}
|
||||
focusedContributions={focusedContributions}
|
||||
onUpdateFindingDisposition={onUpdateFindingDisposition}
|
||||
onUpdateFindingProposition={onUpdateFindingProposition}
|
||||
/>
|
||||
|
||||
{/* Thread contributions for this node */}
|
||||
@@ -1212,6 +1267,7 @@ export default function ReasoningWorkspace({
|
||||
onFocusedContribution,
|
||||
findings,
|
||||
onUpdateFindingDisposition,
|
||||
onUpdateFindingProposition,
|
||||
}) {
|
||||
const [investigationHistory, setInvestigationHistory] = useState([]);
|
||||
const turnCounter = useRef(0);
|
||||
@@ -1816,6 +1872,7 @@ export default function ReasoningWorkspace({
|
||||
focusedContributions={focusedContributions}
|
||||
currentFindings={currentFindings || []}
|
||||
onUpdateFindingDisposition={onUpdateFindingDisposition}
|
||||
onUpdateFindingProposition={onUpdateFindingProposition}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
@@ -1891,6 +1948,7 @@ export default function ReasoningWorkspace({
|
||||
focusedContributions={focusedContributions}
|
||||
focusedInvestigations={focusedInvestigations}
|
||||
setIsFocusedWorkspaceOpen={setIsFocusedWorkspaceOpen}
|
||||
onUpdateFindingProposition={onUpdateFindingProposition}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -2066,6 +2124,7 @@ export default function ReasoningWorkspace({
|
||||
focusedContributions={focusedContributions}
|
||||
currentFindings={currentFindings || []}
|
||||
onUpdateFindingDisposition={onUpdateFindingDisposition}
|
||||
onUpdateFindingProposition={onUpdateFindingProposition}
|
||||
/>
|
||||
{/* Workspace navigation — hidden during formulation/loading states */}
|
||||
{formulationStep !== "active" && (
|
||||
|
||||
@@ -276,6 +276,16 @@ export default function ScenarioForm() {
|
||||
);
|
||||
}
|
||||
|
||||
function updateFindingProposition(findingId, newProposition) {
|
||||
setFindings((prev) =>
|
||||
prev.map((f) =>
|
||||
f.id === findingId
|
||||
? { ...f, proposition: newProposition, userDisposition: null }
|
||||
: f,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function appendFocusedContribution(contribution) {
|
||||
// Derive a single stored contribution object and use it for BOTH
|
||||
// contribution storage AND Finding derivation so the same identity
|
||||
@@ -630,6 +640,7 @@ export default function ScenarioForm() {
|
||||
onFocusedContribution={appendFocusedContribution}
|
||||
findings={findings}
|
||||
onUpdateFindingDisposition={updateFindingDisposition}
|
||||
onUpdateFindingProposition={updateFindingProposition}
|
||||
onRestart={() => {
|
||||
clearSession();
|
||||
setStatus("idle");
|
||||
|
||||
Reference in New Issue
Block a user