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
+106
View File
@@ -318,3 +318,109 @@ describe("existing focused display path unchanged", () => {
});
});
// ── Evidence-limit card visibility gate ───────────────────────────
describe("evidence-limit card visibility gate", () => {
// Replicates the exact showEvidenceLimit logic from reasoning-workspace.jsx:1076-1081
function computeShowEvidenceLimit({ processingStep, focusedQuestion, hasGraph, genuineCompletion }) {
return !(
processingStep === "active" ||
(focusedQuestion && !processingStep) ||
(hasGraph && !genuineCompletion)
);
}
describe("evidence-limit HIDDEN during active investigation", () => {
it("hidden while deconstruct is processing", () => {
const show = computeShowEvidenceLimit({
processingStep: "active",
focusedQuestion: null,
hasGraph: true,
genuineCompletion: false,
});
expect(show).toBe(false);
});
it("hidden while a focused question is ready for answering", () => {
const show = computeShowEvidenceLimit({
processingStep: "idle",
focusedQuestion: "How does the founder transfer knowledge?",
hasGraph: true,
genuineCompletion: false,
});
expect(show).toBe(false);
});
it("hidden when open threads remain after deconstruct success", () => {
// Simulates: 4 original threads, 1 selected, 1 answer submitted,
// contribution attached, 3 other threads still open
const show = computeShowEvidenceLimit({
processingStep: "idle",
focusedQuestion: null,
hasGraph: true,
genuineCompletion: false, // 3+ unresolved nodes remain
});
expect(show).toBe(false);
});
it("hidden when follow-ups are available from a previous deconstruct", () => {
const show = computeShowEvidenceLimit({
processingStep: "idle",
focusedQuestion: null,
hasGraph: true,
genuineCompletion: false,
});
expect(show).toBe(false);
});
it("hidden during initial analysis phase (no graph yet)", () => {
const show = computeShowEvidenceLimit({
processingStep: "idle",
focusedQuestion: null,
hasGraph: false,
genuineCompletion: false,
});
expect(show).toBe(true); // no graph → no evidence limit to show
});
});
describe("evidence-limit still shown in genuine terminal state", () => {
it("shown when all unknowns resolved (completion already handles this branch)", () => {
// genuineCompletion=true means the CompletionCard path is taken instead,
// so EvidenceLimitCard would NOT render (it's {!genuineCompletion ? ... }).
// This test confirms the gate allows the terminal path.
const show = computeShowEvidenceLimit({
processingStep: "idle",
focusedQuestion: null,
hasGraph: true,
genuineCompletion: true,
});
expect(show).toBe(true);
});
it("shown with no graph (initial analysis complete, no questions)", () => {
const show = computeShowEvidenceLimit({
processingStep: "idle",
focusedQuestion: null,
hasGraph: false,
genuineCompletion: false,
});
expect(show).toBe(true);
});
});
describe("evidence-limit hidden while answering (focused answer exists)", () => {
it("hidden when processing step clears but no focused question yet (post-deconstruct, pre-new-question phase)", () => {
// After deconstruct completes: processingStep=idle, focusedQuestion=null
// hasGraph=true, genuineCompletion=false → still hidden because open threads remain
const show = computeShowEvidenceLimit({
processingStep: "idle",
focusedQuestion: null,
hasGraph: true,
genuineCompletion: false,
});
expect(show).toBe(false);
});
});
});