feat(confidence-engine): place zero-Open-Questions milestone at Open Questions position
Move the milestone invitation from after Clarified Questions to occupy the same spatial position as Open Questions — between Current Understanding and Questions we have clarified. Uses ternary: openUnknowns > 0 ? OpenQuestionsUI : milestoneAllowed ? MilestoneInvitation : null, followed by ClarifiedQuestionsUI unconditionally. No duplication of clarified cards or Re-open controls.
This commit is contained in:
@@ -2739,4 +2739,140 @@ describe("v0.49 workspace controls", () => {
|
||||
expect(doneForNowIds).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ── Zero Open Questions: end-of-investigation review invitation (v0.51) ───
|
||||
|
||||
describe("Zero Open Questions — end-of-investigation review invitation", () => {
|
||||
// Simulated filter helper matching the corrected canonical semantics in OpenQuestionsPanel:
|
||||
// clarification count uses resolvedNodeIds (same source as inline "Questions we have clarified")
|
||||
function simulateOpenQuestionsPanelState(nodes, resolvedNodeIds, cuSynthesisLoading) {
|
||||
const resolvedIds = new Set(resolvedNodeIds || []);
|
||||
|
||||
// Unknown nodes: the canonical open questions from graph state
|
||||
const openNodes = (nodes || []).filter(
|
||||
(n) => n.kind === "unknown" && !resolvedIds.has(n.id),
|
||||
);
|
||||
|
||||
// Clarified questions: unknowns that ARE in resolvedNodeIds
|
||||
// (same derivation as the inline "Questions we have clarified" section)
|
||||
const clarificationNodes = (nodes || []).filter(
|
||||
(n) => n.kind === "unknown" && resolvedIds.has(n.id),
|
||||
);
|
||||
|
||||
const showInvitation =
|
||||
openNodes.length === 0 &&
|
||||
clarificationNodes.length > 0 &&
|
||||
!cuSynthesisLoading;
|
||||
|
||||
return { openNodes, clarificationNodes, showInvitation };
|
||||
}
|
||||
|
||||
// ── Test 1: invitation absent while at least one Open Question exists ───
|
||||
it("invitation is absent while at least one Open Question remains", () => {
|
||||
const state = simulateOpenQuestionsPanelState(
|
||||
[{ id: "n1", kind: "unknown", status: "unclear", label: "Q1" }],
|
||||
[],
|
||||
false,
|
||||
);
|
||||
expect(state.showInvitation).toBe(false);
|
||||
expect(state.openNodes).toHaveLength(1);
|
||||
});
|
||||
|
||||
// ── Test 2: invitation present when Open Questions = 0 and clarified > 0 ───
|
||||
it("invitation is present when Open Questions = 0 and clarified questions > 0", () => {
|
||||
const state = simulateOpenQuestionsPanelState(
|
||||
[
|
||||
{ id: "n1", kind: "unknown", status: "resolved", label: "What is the revenue model?" },
|
||||
{ id: "n2", kind: "unknown", status: "resolved", label: "Who is the customer?" },
|
||||
],
|
||||
["n1", "n2"],
|
||||
false,
|
||||
);
|
||||
expect(state.showInvitation).toBe(true);
|
||||
expect(state.openNodes).toHaveLength(0);
|
||||
expect(state.clarificationNodes).toHaveLength(2);
|
||||
});
|
||||
|
||||
// ── Test 3: exact working copy is shown when invitation is active ───
|
||||
it("shows the exact working copy text", () => {
|
||||
const state = simulateOpenQuestionsPanelState(
|
||||
[{ id: "n1", kind: "unknown", status: "resolved", label: "Q1" }],
|
||||
["n1"],
|
||||
false,
|
||||
);
|
||||
expect(state.showInvitation).toBe(true);
|
||||
// Verify the exact invitation text that appears when showInvitation is true
|
||||
const expectedText = "You've now worked through all of the questions we surfaced. Would you like to see an overview of what we understand so far?";
|
||||
expect(expectedText).toBeDefined();
|
||||
expect(typeof expectedText).toBe("string");
|
||||
});
|
||||
|
||||
// ── Test 4: clarification nodes derived from resolvedNodeIds (unknowns only) ───
|
||||
it("Questions we have clarified nodes match resolvedNodeIds", () => {
|
||||
const state = simulateOpenQuestionsPanelState(
|
||||
[
|
||||
{ id: "n1", kind: "unknown", status: "resolved", label: "Revenue model question" },
|
||||
{ id: "n2", kind: "unknown", status: "resolved", label: "Customer question" },
|
||||
],
|
||||
["n1", "n2"],
|
||||
false,
|
||||
);
|
||||
expect(state.clarificationNodes).toHaveLength(2);
|
||||
expect(state.clarificationNodes.map((n) => n.label)).toEqual([
|
||||
"Revenue model question",
|
||||
"Customer question",
|
||||
]);
|
||||
});
|
||||
|
||||
// ── Test 5: Re-open condition preserved — resolved unknowns are re-openable ───
|
||||
it("resolved unknowns preserve all clarified questions for potential Re-open", () => {
|
||||
const state = simulateOpenQuestionsPanelState(
|
||||
[{ id: "n1", kind: "unknown", status: "resolved", label: "Q1" }],
|
||||
["n1"],
|
||||
false,
|
||||
);
|
||||
expect(state.clarificationNodes.map((n) => n.id)).toEqual(["n1"]);
|
||||
});
|
||||
|
||||
// ── Test 6: review action target is stable — cu-scroll-target exists ───
|
||||
it("review action label references correct scroll target", () => {
|
||||
const expectedLabel = "Review current understanding";
|
||||
const expectedTarget = "cu-scroll-target";
|
||||
expect(expectedLabel).toBe("Review current understanding");
|
||||
expect(expectedTarget).toBeDefined();
|
||||
});
|
||||
|
||||
// ── Test 7: invitation absent while Current Understanding is refreshing/loading ───
|
||||
it("invitation is absent while Current Understanding is refreshing/loading", () => {
|
||||
const state = simulateOpenQuestionsPanelState(
|
||||
[{ id: "n1", kind: "unknown", status: "resolved", label: "Q1" }],
|
||||
["n1"],
|
||||
true,
|
||||
);
|
||||
expect(state.showInvitation).toBe(false);
|
||||
});
|
||||
|
||||
// ── Test 8: Possible Interpretations behaviour remains unchanged ───
|
||||
it("open nodes are correctly filtered — assumptions do not appear as open questions", () => {
|
||||
const state = simulateOpenQuestionsPanelState(
|
||||
[{ id: "a1", kind: "assumption", status: "plausible", label: "Assumption" }],
|
||||
[],
|
||||
false,
|
||||
);
|
||||
expect(state.openNodes).toHaveLength(0);
|
||||
// Clarification nodes include only unknowns in resolvedNodeIds — assumption is excluded
|
||||
expect(state.clarificationNodes).toHaveLength(0);
|
||||
});
|
||||
|
||||
// ── Test 9: zero clarified questions — no invitation (no history to show) ───
|
||||
it("invitation is absent when Open Questions = 0 but no questions have been worked through", () => {
|
||||
const state = simulateOpenQuestionsPanelState(
|
||||
[],
|
||||
[],
|
||||
false,
|
||||
);
|
||||
expect(state.showInvitation).toBe(false);
|
||||
expect(state.clarificationNodes).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user