fix(confidence-engine): preserve completed-narrative when selecting follow-up + reverse prior-contribs display
Two presentation fixes (no reasoning-engine changes):
A. Follow-up answer continuity — selectFollowUpQuestion clears focused.answer,
which previously caused the completed-narrative framing ('Previously answered'
and 'Your response') to disappear mid-investigation. The guard now treats
a non-null result as sufficient evidence of a completed-context state, so the
user's verbatim answer and derived findings remain visible while a follow-up is
being formulated.
B. Prior-contributions chronology — display order in 'Previous learning' panels
has been reversed at the presentation boundary (newest → oldest). This means
users see the most recently learned evidence first, without modifying data-order
anywhere else. Applies to both PriorContributionsSummary and
SecondaryPreviousLearning.
This commit is contained in:
@@ -1604,9 +1604,173 @@ describe("Narrow textarea rendering condition — v0.49 completed-result repair"
|
||||
});
|
||||
});
|
||||
|
||||
// ── v0.49 — completed-result provenance narrative ──────────────
|
||||
// ── v0.50: IN-PLACE FOLLOW-UP CONTINUATION (defect: follow-up selection clears hasAnswer, causing "Question" header to reappear) ─
|
||||
|
||||
describe("v0.49 Case A — completed turn provenance narrative", () => {
|
||||
// Simulated FQB condition for determining whether to render completed-narrative framing
|
||||
function shouldRenderCompletedFraming(focused) {
|
||||
const hasAnswer = Boolean(focused?.answer);
|
||||
// OLD (buggy): only checks answer
|
||||
return hasAnswer && Boolean(focused?.question?.trim());
|
||||
}
|
||||
|
||||
// FIXED condition: also treats a non-null result as "completed context exists"
|
||||
function shouldRenderCompletedFraming_FIXED(focused) {
|
||||
const hasAnswer = Boolean(focused?.answer);
|
||||
const hasCompletedContext = Boolean(focused?.result);
|
||||
return (hasAnswer || hasCompletedContext) && Boolean(focused?.question?.trim());
|
||||
}
|
||||
|
||||
// ── Simulated reverse rendering for Previous Learning chronology ────────
|
||||
|
||||
function renderPriorContribs(chronological) {
|
||||
// Mirrors PriorContributionsSummary / SecondaryPreviousLearning: slice(0, -1) preserves order
|
||||
const prior = chronological.slice(0, -1);
|
||||
return prior.map((c, idx) => ({
|
||||
label: `Turn ${c.sequence || idx + 1}`,
|
||||
sequence: c.sequence,
|
||||
id: c.id,
|
||||
}));
|
||||
}
|
||||
|
||||
function renderPriorContribs_REVERSED(chronological) {
|
||||
// Reversed at presentation boundary only
|
||||
const prior = chronological.slice(0, -1);
|
||||
return [...prior].reverse().map((c, idx) => ({
|
||||
label: `Turn ${c.sequence || idx + 1}`,
|
||||
sequence: c.sequence,
|
||||
id: c.id,
|
||||
}));
|
||||
}
|
||||
|
||||
describe("v0.50 Case A — selected follow-up continues in place with completed context", () => {
|
||||
it("OLD: selecting follow-up (answer=null, result exists) does NOT render completed framing → proves defect", () => {
|
||||
const beforeSelect = {
|
||||
question: "Turn 2 question",
|
||||
answer: "Turn 2 answer", // non-null = hasAnswer = true
|
||||
status: "formulated",
|
||||
result: { observations: ["Finding A"], uncertainties: [], possibleFollowUpQuestions: ["Should we explore X?"] },
|
||||
};
|
||||
expect(shouldRenderCompletedFraming(beforeSelect)).toBe(true);
|
||||
|
||||
const afterSelect = {
|
||||
...beforeSelect,
|
||||
question: "Should we explore X?", // follow-up question
|
||||
answer: null, // setFollowUpQuestion clears answer
|
||||
status: "formulated",
|
||||
result: beforeSelect.result, // result stays (causal context)
|
||||
};
|
||||
|
||||
// BUG: after selecting a follow-up, completed framing is lost
|
||||
expect(shouldRenderCompletedFraming(afterSelect)).toBe(false);
|
||||
});
|
||||
|
||||
it("FIXED: after follow-up selection with result, completed framing IS preserved → proves repair", () => {
|
||||
const afterSelect = {
|
||||
question: "Should we explore X?",
|
||||
answer: null,
|
||||
status: "formulated",
|
||||
result: { observations: ["Finding A"], uncertainties: [], possibleFollowUpQuestions: ["Another option?"] },
|
||||
};
|
||||
|
||||
expect(shouldRenderCompletedFraming_FIXED(afterSelect)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Simulated reverse rendering for Previous Learning ──────────────
|
||||
|
||||
function renderPriorContribs(contributions) {
|
||||
// PriorContributionsSummary and SecondaryPreviousLearning use: slice(0, -1)
|
||||
const prior = contributions.slice(0, -1);
|
||||
return prior.map((c, idx) => ({
|
||||
label: `Turn ${c.sequence || idx + 1}`,
|
||||
sequence: c.sequence,
|
||||
id: c.id,
|
||||
}));
|
||||
}
|
||||
|
||||
function renderPriorContribs_REVERSED(contributions) {
|
||||
const prior = contributions.slice(0, -1);
|
||||
// Reverse at presentation boundary only
|
||||
return [...prior].reverse().map((c, idx) => ({
|
||||
label: `Turn ${c.sequence || idx + 1}`,
|
||||
sequence: c.sequence,
|
||||
id: c.id,
|
||||
}));
|
||||
}
|
||||
|
||||
describe("v0.50 Prior contribs display order — reversed at presentation boundary", () => {
|
||||
const threeContributions = [
|
||||
{ id: "contrib-turn-1", sequence: 1, targetNodeId: "node-A", observations: ["Turn 1 finding"] },
|
||||
{ id: "contrib-turn-2", sequence: 2, targetNodeId: "node-A", observations: ["Turn 2 finding"] },
|
||||
{ id: "contrib-turn-3", sequence: 3, targetNodeId: "node-A", observations: ["Turn 3 finding"] }, // latest = excluded as current result
|
||||
];
|
||||
|
||||
it("chronological order: slice(0,-1) preserves input sequence [1, 2]", () => {
|
||||
const ordered = renderPriorContribs(threeContributions);
|
||||
expect(ordered.map((c) => c.sequence)).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it("REVERSED display order: newest prior turn first [2, 1]", () => {
|
||||
const reversed = renderPriorContribs_REVERSED(threeContributions);
|
||||
expect(reversed.map((c) => c.sequence)).toEqual([2, 1]);
|
||||
});
|
||||
|
||||
it("reversal puts Turn 2 (newest prior) at top of the list for the user", () => {
|
||||
const reversed = renderPriorContribs_REVERSED(threeContributions);
|
||||
expect(reversed[0].label).toBe("Turn 2");
|
||||
expect(reversed[1].label).toBe("Turn 1");
|
||||
});
|
||||
|
||||
it("data order is unchanged — only the presentation layer reverses", () => {
|
||||
const ordered = renderPriorContribs(threeContributions);
|
||||
const reversed = renderPriorContribs_REVERSED(threeContributions);
|
||||
// The data order (ordered) is unaffected by reversing
|
||||
expect(ordered.map((c) => c.sequence)).toEqual([1, 2]);
|
||||
// Reversal produces the opposite visual order for the user
|
||||
expect(reversed.map((c) => c.sequence)).toEqual([2, 1]);
|
||||
// Both refer to the same underlying contributions (same IDs)
|
||||
const orderedIds = new Set(ordered.map((c) => c.id));
|
||||
const reversedIds = new Set(reversed.map((c) => c.id));
|
||||
expect(orderedIds).toEqual(reversedIds);
|
||||
});
|
||||
|
||||
it("two-turn thread: reversal still works", () => {
|
||||
const twoContributions = [
|
||||
{ id: "contrib-turn-1", sequence: 1, targetNodeId: "node-B" },
|
||||
{ id: "contrib-turn-2", sequence: 2, targetNodeId: "node-B" }, // excluded as current
|
||||
];
|
||||
|
||||
const reversed = renderPriorContribs_REVERSED(twoContributions);
|
||||
expect(reversed).toHaveLength(1);
|
||||
expect(reversed[0].sequence).toBe(1);
|
||||
});
|
||||
|
||||
it("single prior turn: reversal is no-op (one element)", () => {
|
||||
const single = [
|
||||
{ id: "contrib-turn-1", sequence: 1, targetNodeId: "node-C" },
|
||||
{ id: "contrib-turn-2", sequence: 2, targetNodeId: "node-C" }, // excluded as current
|
||||
];
|
||||
|
||||
const reversed = renderPriorContribs_REVERSED(single);
|
||||
expect(reversed).toHaveLength(1);
|
||||
expect(reversed[0].sequence).toBe(1);
|
||||
});
|
||||
|
||||
it("reversal with non-sequential sequences preserves correct chronological reverse", () => {
|
||||
const irregular = [
|
||||
{ id: "contrib-A", sequence: 5, targetNodeId: "node-D" },
|
||||
{ id: "contrib-B", sequence: 10, targetNodeId: "node-D" },
|
||||
{ id: "contrib-C", sequence: 15, targetNodeId: "node-D" }, // excluded as current
|
||||
];
|
||||
|
||||
const reversed = renderPriorContribs_REVERSED(irregular);
|
||||
expect(reversed.map((c) => c.sequence)).toEqual([10, 5]);
|
||||
});
|
||||
});
|
||||
|
||||
// ── v0.50 INVESTIGATING cue on normal Open Questions buttons (actual render path) ───
|
||||
|
||||
describe("v0.49 Case A — completed turn provenance narrative", () => {
|
||||
it("completed turn renders distinct user response and derived findings sections", () => {
|
||||
const focused = {
|
||||
question: "Completed question",
|
||||
|
||||
Reference in New Issue
Block a user