From ad42f67800a8b73937f217686a881fe13b730ea1 Mon Sep 17 00:00:00 2001 From: robbond Date: Thu, 20 Aug 2026 05:55:25 +0100 Subject: [PATCH] test(ui): explore passive branch result indication --- components/experimental/branch-switcher.jsx | 123 ++++++++++++++++++++ components/scenario-form.jsx | 104 ++++++++++++----- 2 files changed, 198 insertions(+), 29 deletions(-) create mode 100644 components/experimental/branch-switcher.jsx diff --git a/components/experimental/branch-switcher.jsx b/components/experimental/branch-switcher.jsx new file mode 100644 index 0000000..02cfe7c --- /dev/null +++ b/components/experimental/branch-switcher.jsx @@ -0,0 +1,123 @@ +/** + * Experimental branch switcher — RTO.25A + * + * Smallest branch representation needed to test passive late-result indication. + * Does NOT replace production branch navigation. Temporary fixture only. + */ + +"use client"; + +import React, { useState, useEffect } from "react"; + +/* ── Keyframes (injected once via +); + +/* ── Status dot (passive new-result indicator) ─────────── */ + +function NewIndicator({ visible }) { + if (!visible) return null; + + return ( + + + + + ); +} + +/* ── Single branch row ─────────────────────────────────── */ + +function BranchRow({ id, label, active, isNew, onClick }) { + const isActive = Boolean(active); + + return ( + + ); +} + +/* ── Card wrapper ────────────────────────────────────────── */ + +export default function ExperimentalBranchSwitcher({ + branches = [], + activeBranchId, + branchNewResults = {}, + onBranchSelect, +}) { + if (!branches.length) return null; + + return ( +
+ {/* Label — clearly experimental */} +

+ Branches{" "} + (exp) +

+

+ Browse branches. Current focus is preserved. +

+ +
+ {branches.map((branch) => ( + onBranchSelect?.(branch.id)} + /> + ))} +
+
+ ); +} + +export { PulseStyle }; diff --git a/components/scenario-form.jsx b/components/scenario-form.jsx index 14d91c9..9b6ab1c 100644 --- a/components/scenario-form.jsx +++ b/components/scenario-form.jsx @@ -4,6 +4,7 @@ import React, { useEffect } from "react"; import { useState, useRef, useMemo } from "react"; 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"; /* Compile-time env resolution — NEXT_PUBLIC_ vars are injected by Next.js at build */ @@ -221,6 +222,28 @@ export default function ScenarioForm() { const [hideFacilitatorOnLanding, setHideFacilitatorOnLanding] = useState(false); const textareaRef = useRef(null); + /* ── RTO.25A — passive late-result branch switcher (experimental) ── */ + + const BRANCHES = [ + { id: "branch-a", label: "Competitor development" }, + { id: "branch-b", label: "Customer demand" }, + ]; + + const [activeBranchId, setActiveBranchId] = useState("branch-b"); + const [branchNewResults, setBranchNewResults] = useState({}); + + // 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(() => { + setBranchNewResults((prev) => ({ ...prev, "branch-a": true })); + }, 2000); + return () => clearTimeout(timer); + }, [status, branchNewResults]); + /* Restore persisted session on mount (Phase 3) ─────────── */ useEffect(() => { if (typeof window === "undefined") return; @@ -507,35 +530,58 @@ export default function ScenarioForm() { {/* ── Main result workspace ─────────────────────── */} {((status === "success" || status === "error") && status !== "loading") && ( - { - clearSession(); - setStatus("idle"); - setResult(null); - setAnswer(""); - setUpdateStatus("idle"); - setUpdateResult(null); - setLastSubmittedAnswer(""); - setCurrentUnderstanding(null); - setUpdateError(null); - }} - /> + <> + +
+ {/* Workspace (3/4) */} +
+ { + clearSession(); + setStatus("idle"); + setResult(null); + setAnswer(""); + setUpdateStatus("idle"); + setUpdateResult(null); + setLastSubmittedAnswer(""); + setCurrentUnderstanding(null); + setUpdateError(null); + }} + /> +
+ + {/* Branch switcher (1/4 sidebar) */} +
+ { + /* User intentionally navigates — nothing more */ + if (id !== activeBranchId) { + setActiveBranchId(id); + } + }} + /> +
+
+ )} {/* ── Continue later banner when session was restored ── */}