/** * App — the citation-evidence MVP shell. * * Composes the two top-level layouts: * * - Review mode (CE-WP-0002): collection list / viewer / evidence sidebar. * - Forms mode (CE-WP-0003): form renderer / viewer / evidence strip, * with click-to-link interaction. * * Mode selection is driven by `location.hash`: `#/forms/demo` lands in * Forms mode; anything else (including empty) lands in Review mode. The * top bar toggles between them. We keep the hash sync so reload + deep * links work; T08's E2E asserts the `/forms/demo` navigation path. * * Engine and binder providers are both mounted at the App root so * evidence/annotations/links survive switching tabs. */ import { useEffect, useState } from "react"; import { BinderProvider } from "@binder/index"; import { EngineProvider, useEngine, } from "@work/index"; import { FormsApp } from "./forms/FormsApp"; import { ReviewLayout } from "./ReviewLayout"; type Mode = "review" | "forms"; const FORMS_HASH = "#/forms/demo"; function readModeFromHash(): Mode { if (typeof window === "undefined") return "review"; return window.location.hash === FORMS_HASH ? "forms" : "review"; } function writeModeToHash(mode: Mode) { if (typeof window === "undefined") return; const target = mode === "forms" ? FORMS_HASH : ""; if (window.location.hash !== target) { if (target) { window.location.hash = target; } else { // Clear hash without leaving "#" trailing in the URL bar. history.replaceState(null, "", window.location.pathname + window.location.search); } } } function ModeRouter() { const [mode, setMode] = useState(() => readModeFromHash()); useEffect(() => { function onHash() { setMode(readModeFromHash()); } window.addEventListener("hashchange", onHash); return () => window.removeEventListener("hashchange", onHash); }, []); const handleModeChange = (next: Mode) => { writeModeToHash(next); setMode(next); }; return (
{mode === "review" ? : }
); } function TopBar({ mode, onModeChange }: { mode: Mode; onModeChange: (m: Mode) => void }) { return (
citation-evidence
); } function tabStyle(active: boolean) { return { padding: "4px 12px", fontSize: 12, border: "1px solid #ccc", borderBottom: active ? "2px solid #0050b3" : "1px solid #ccc", background: active ? "#e8f0ff" : "white", cursor: "pointer" as const, }; } function AppInner() { const engine = useEngine(); return ( ); } export function App() { return ( ); }