diff --git a/components/reasoning-workspace.jsx b/components/reasoning-workspace.jsx index 15636fb..c3f0c4d 100644 --- a/components/reasoning-workspace.jsx +++ b/components/reasoning-workspace.jsx @@ -522,6 +522,11 @@ export default function ReasoningWorkspace({ lastSubmittedAnswer, onRestart, branchContext, + // ── RTO.26B — branch-local reasoning (explicit provenance) ── + experimentalBranches, + branchLocalQuestions, + branchLocalContributions, + inactiveBranchNewResults, }) { const [investigationHistory, setInvestigationHistory] = useState([]); const turnCounter = useRef(0); @@ -811,7 +816,7 @@ export default function ReasoningWorkspace({ )} {/* ── No graph produced after initial analysis ───────── */} - {(status === "success" || status === "error") && !graph ? ( + {(status === "success" || status === "error") && !graph && !(experimentalBranches && experimentalBranches.length > 0) ? (
{diagnostics?.noQuestionReason ? "Validation failed — no structured graph output was produced." @@ -820,12 +825,21 @@ export default function ReasoningWorkspace({ ) : ( <> {/* ── Workspace grid: persistent whenever a graph exists ─── */} - {hasGraph && ( + {(hasGraph || experimentalBranches) && (
{/* ── Left lane: active conversation & notebook ───────── */} -
+
+ {/* RTO.26B — experiment header when using branch-scoped fixture */} + {experimentalBranches && experimentalBranches.length > 0 && ( +
+

+ RTO.26B — branch-scoped reasoning (experimental fixture) +

+
+ )} + {/* Active branch context (experiment RTO.25D) */} - {branchContext && hasGraph && ( + {branchContext && (hasGraph || experimentalBranches) && (

Current branch @@ -841,9 +855,12 @@ export default function ReasoningWorkspace({ {/* ── Open questions (case workspace) — no ranking bias ── */} {(() => { - const openNodes = graph.nodes.filter( - (n) => n.kind === "unknown" && n.status !== "resolved" && !doneForNowIds.includes(n.id) - ); + /* RTO.26B: when experimental branch-scoped data is present, use it as the source of truth */ + const openNodes = experimentalBranches && branchLocalQuestions && branchLocalQuestions.length > 0 + ? branchLocalQuestions + : ((graph?.nodes || []).filter( + (n) => n.kind === "unknown" && n.status !== "resolved" && !doneForNowIds.includes(n.id), + )); if (openNodes.length <= 1) return null; return ( @@ -880,6 +897,23 @@ export default function ReasoningWorkspace({ {/* Invitation — shows when selected but no content yet */} {isSelected && !hasFocusedContent() && (
+ {/* Branch-local contribution scoped to this question */} + {(experimentalBranchContributions?.length ?? 0) > 0 && ( + (() => { + const qContribs = experimentalBranchContributions.filter( + c => node.id === c.questionId, + ); + if (!qContribs.length) return null; + return ( +
+

+ Contribution +

+

{qContribs[0].text}

+
+ ); + })() + )}

We have not explored this yet. Do you want to work through it?

@@ -1040,28 +1074,31 @@ export default function ReasoningWorkspace({
)} - + {/* Footer controls — horizontally separated (RTO.26B) */} +
+ - + +

); })()} @@ -1180,7 +1217,18 @@ export default function ReasoningWorkspace({ {/* ── Right lane: stable supporting reference ───────── */} {hasCurrentSummaryCondition && (
- + {/* RTO.26B — situation card from scenario text when no graph */} + {!graph && scenario && ( +
+

+ Situation +

+

+ {scenario} +

+
+ )} + {graph && } {/* RTO.25B — temporarily hidden to reduce competing navigation while branch-experiment is active */}
diff --git a/components/scenario-form.jsx b/components/scenario-form.jsx index 4f37ea8..29e9290 100644 --- a/components/scenario-form.jsx +++ b/components/scenario-form.jsx @@ -6,6 +6,7 @@ import DiagnosticsView from "@/components/diagnostics-view"; import ReasoningWorkspace, { LoadingOverlay, ContinueLaterBanner } from "@/components/reasoning-workspace"; import ExperimentalBranchSwitcher, { PulseStyle } from "@/components/experimental/branch-switcher"; import { mockFetch, AVAILABLE_SCENARIOS } from "@/lib/mocks/confidence-engine/mock-client"; +import { useBranchScopedFixture } from "@/lib/fixtures/rto26b-branch-scoped.mjs"; /* Compile-time env resolution — NEXT_PUBLIC_ vars are injected by Next.js at build */ const MOCK_ENABLED = process.env.NEXT_PUBLIC_CONFIDENCE_ENGINE_MOCKS === "true"; @@ -224,26 +225,23 @@ export default function ScenarioForm() { /* ── RTO.25A — passive late-result branch switcher (experimental) ── */ - const BRANCHES = [ - { - id: "branch-a", - label: "Competitor development", - origin: "Whether competitors are developing similar products and when they might release them", - }, - { - id: "branch-b", - label: "Customer demand", - origin: "How many customers will buy the product, and what revenue that represents", - }, - ]; - const [activeBranchId, setActiveBranchId] = useState("branch-b"); const [branchNewResults, setBranchNewResults] = useState({}); + /* ── RTO.26B — experimental branch-scoped reasoning fixture ───── */ + + const branchScoped = useBranchScopedFixture(); + // Toggle to control when the experiment view is visible vs production + const [showExperimentView, setShowExperimentView] = useState( + typeof window !== "undefined" ? (window.sessionStorage?.getItem("ce-show-experiment") !== "false") : true, + ); + + // Use fixture branches as the authoritative source when available + const BRANCHES = useMemo(() => branchScoped.getBranches(), [branchScoped]); + // Simulate a late semantic result arriving on Branch A ~2s after a graph loads useEffect(() => { if (status !== "success" && status !== "error") return; - // Only simulate once per graph load if (branchNewResults["branch-a"]) return; const timer = setTimeout(() => { @@ -252,6 +250,29 @@ export default function ScenarioForm() { return () => clearTimeout(timer); }, [status, branchNewResults]); + // Compute branch-local data for the active branch (explicit provenance) + const activeBranch = BRANCHES.find(b => b.id === activeBranchId); + const experimentalBranchQuestions = useMemo(() => { + if (!activeBranch) return []; + return branchScoped.getBranchQuestions(activeBranch.id); + }, [activeBranch, branchScoped]); + + const experimentalBranchContributions = useMemo(() => { + if (!activeBranch) return []; + return branchScoped.getBranchContributions(activeBranch.id); + }, [activeBranch, branchScoped]); + + // Determine which non-active branches have new results for passive indicator + const inactiveBranchNewResults = useMemo(() => { + const result = {}; + BRANCHES.forEach(b => { + if (b.id !== activeBranchId && b.id in branchNewResults) { + result[b.id] = true; + } + }); + return result; + }, [activeBranchId, BRANCHES, branchNewResults]); + /* Restore persisted session on mount (Phase 3) ─────────── */ useEffect(() => { if (typeof window === "undefined") return; @@ -417,7 +438,75 @@ export default function ScenarioForm() { return (
- {status === "idle" && ( + {/* ── RTO.26B — standalone branch-scoped experimental view (shown when experiment is active) ───── */} + {showExperimentView && status !== "loading" && !result?.situationGraph && !result?.updatedSituationGraph && ( + <> + +
+ {/* Workspace (3/4) — branch-scoped fixture only, no API needed */} +
+ {(() => { + const activeBranch = BRANCHES.find(b => b.id === activeBranchId); + const branchContext = activeBranch ? { label: activeBranch.label, origin: activeBranch.origin } : null; + return ( + {}} + onAnswerSubmit={async (e) => e.preventDefault()} + lastSubmittedAnswer="" + branchContext={branchContext} + experimentalBranches={BRANCHES.length > 0 ? BRANCHES : undefined} + branchLocalQuestions={experimentalBranchQuestions.length > 0 ? experimentalBranchQuestions : undefined} + branchLocalContributions={experimentalBranchContributions.length > 0 ? experimentalBranchContributions : undefined} + inactiveBranchNewResults={Object.keys(inactiveBranchNewResults).length > 0 ? inactiveBranchNewResults : undefined} + onRestart={() => { setStatus("idle"); setResult(null); setScenario(""); }} + /> + ); + })()} +
+ + {/* Branch switcher (1/4 sidebar) */} +
+ { + if (id !== activeBranchId) { + setActiveBranchId(id); + } + }} + /> +
+
+ + )} + + {/* ── Experiment toggle (visible when experiment is NOT shown) ─ */} + {!showExperimentView && status !== "loading" && !result?.updatedSituationGraph && ( +
+

+ Production view active. +

+ +
+ )} + + {/* ── Idle form for scenario input (shown only when experiment is off) ─ */} + {!showExperimentView && !result?.situationGraph && status === "idle" && (
{/* Two-column landing workspace */} @@ -450,7 +539,7 @@ export default function ScenarioForm() { className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
@@ -459,7 +548,7 @@ export default function ScenarioForm() { {/* Right panel — Workspace (2/3 on desktop) */}
-

Tell me what's happening

+

What's the situation