prototype: restore original Core within context shelf alternatives

This commit is contained in:
Codex
2026-09-12 14:00:52 +02:00
parent 41b9fed4d5
commit 840344706f
5 changed files with 175 additions and 10 deletions
+12 -9
View File
@@ -2,11 +2,12 @@ import { useEffect, useRef, useState } from "react";
import { ArrowLeft, ArrowRight, ArrowUpRight, BookOpen, Check, ChevronDown, ChevronRight, Cpu, Database, FileText, FolderClock, Layers, LayoutDashboard, LockKeyhole, Menu, MessageSquare, Play, Search, Settings2, ShieldCheck, SlidersHorizontal, Terminal, Users, X } from "lucide-react";
import { Content, type ContentProps, type ConnectionValues } from "../application-context/Content";
import { Button } from "../application-context/ui";
import { OriginalCorePreview } from "./OriginalCorePreview";
import { inventory, type Item, type Scenario, type Surface, type Variant } from "../administration-review/model";
import { models, modules, readLocation, sessions, variants, workspaces, type Job, type Page, type Route, type Session } from "./model";
// Three complete-app proposals on the established namespaced routes. V3 is a
// disposable simulation, with no API, persistence, authorization or server writes.
// Three context/admin proposals around the unchanged Core layout. V4 is a
// disposable simulation with remembered demo preferences, but no production API writes.
const icons = { core: MessageSquare, sessions: FolderClock, database: Database, memory: BookOpen, evidence: FileText, workspace: Layers, pi: Terminal };
const titles: Record<Page, string> = { core: "Analytical workspace", sessions: "Sessions", database: "Database", memory: "Memory", evidence: "Evidence", workspace: "Workspace", pi: "Pi runtime" };
import { SessionRail } from "./SessionRail";
@@ -25,6 +26,7 @@ export function App() {
const [width, setWidth] = useState("full");
const [portalOpen, setPortalOpen] = useState(false);
const [navOpen, setNavOpen] = useState(false);
const [coreActivityOpen, setCoreActivityOpen] = useState(false);
const [shelfOpen, setShelfOpen] = useState(initialPreferences.invalid);
const [notice, setNotice] = useState(initialPreferences.invalid ? "A remembered choice is no longer available. Choose a valid workspace and model; no alternative has been selected silently." : "");
const [dirty, setDirty] = useState(false);
@@ -41,7 +43,6 @@ export function App() {
const [active, setActive] = useState<Session | null>(null);
const [resumeTarget, setResumeTarget] = useState<Session | null>(null);
const [question, setQuestion] = useState("");
const [artifact, setArtifact] = useState("Question");
const [scenario, setScenario] = useState<Scenario>("ready");
const model = models.find(m => m.id === modelId);
const currentWorkspace = workspaces.find(w => w.id === workspace);
@@ -119,7 +120,7 @@ export function App() {
}
function resume(s: Session) {
if (busy || !ready || s.status === "archived" || availability === "needs-preprocessing" || !canLeave()) return;
saveChoice({ workspace: s.workspace }); setActive(s); setResumeTarget(null); resetDetail(); setQuestion(""); setArtifact("Question");
saveChoice({ workspace: s.workspace }); setActive(s); setResumeTarget(null); resetDetail(); setQuestion("");
writeRoute({ ...route, workspace: s.workspace, page: "core" });
setOperation({ id: Date.now(), label: `Resume: ${s.title}`, workspace: s.workspace, model: modelId, state: "running" });
setNotice("Proposed resume: the original workspace and revision are retained; this run uses the globally selected model. Backend changes are required.");
@@ -150,10 +151,12 @@ export function App() {
const title = question.trim(); setActive({ id: `demo-${Date.now()}`, title, workspace, revision: "a71c9f2", status: "paused", lastModel: modelId, phase: 1 });
setQuestion(""); setDirty(false); startJob(`Analysis: ${title}`, "llm");
}
const core = <>{pageHeader(active ? active.title : "From a question to a trusted answer", active ? "Persisted artifacts and reviewer decisions guide this session." : "Ask about your data. Review the evidence, approve the interpretation, then build the query.")}
{availability === "needs-preprocessing" && <div className="gc-warning"><strong>Prepare this workspace before starting the core</strong><p>Workspace and model are selected. Administration remains available so you can complete preprocessing.</p><Button onClick={() => navigate("workspace")}>Open workspace readiness</Button></div>}
{active ? <div className="gc-analysis"><div className="gc-session-context"><LockKeyhole size={15}/><strong>{currentWorkspace?.name}</strong><span>Session revision {active.revision}</span><span>{active.status === "archived" ? "Read-only archive" : activeJob ? `Run model: ${models.find(m => m.id === activeJob.model)?.name}` : `Next AI operation: ${model?.name}`}</span></div><div className="gc-workflow"><ol>{["Question", "Schema linking", "SQL draft", "Validation"].map((t,i) => <li key={t} className={i === 1 ? "current" : ""}><span>{String(i+1).padStart(2,"0")}</span>{t}</li>)}</ol><small>Workflow excerpt · 8 phases in production</small></div><div className="gc-artifact-layout"><section className="gc-artifact"><nav aria-label="Session artifacts">{["Question","Schema","SQL"].map(t => <button key={t} aria-current={artifact === t ? "page" : undefined} onClick={() => setArtifact(t)}>{t}</button>)}</nav><div><p className="rv-eyebrow">Persisted artifact · example</p><h2>{artifact === "Question" ? "Domanda analitica" : artifact === "Schema" ? "Collegamento allo schema" : "Query proposta"}</h2>{artifact === "SQL" ? <pre><code>{`SELECT COUNT(DISTINCT patient_id)\nFROM ${currentWorkspace?.schema}.visits\nWHERE visited_at >= DATE '2025-01-01'\n AND visited_at < DATE '2026-01-01';`}</code></pre> : <><p lang="it">{artifact === "Question" ? active.title : "La relazione tra pazienti e visite definisce la popolazione osservata. Il conteggio deve mantenere distinti i pazienti."}</p><div className="gc-evidence-line"><FileText size={16}/><span>Evidence: popolazione di riferimento</span></div><p className="rv-small">Synthetic illustration, not generated from a database.</p></>}</div></section><section className="gc-review"><p className="rv-eyebrow">Human review</p><h2>{active.status === "archived" ? "Retained decisions" : activeJob ? "AI operation in progress" : "Confirm the interpretation"}</h2><p>{active.status === "archived" ? "This archive can be inspected, but its workflow cannot currently be resumed." : activeJob ? "Workspace and model are locked until this operation finishes. No other operation can start in the meantime." : "Check the proposed population and time range before proceeding."}</p>{active.status !== "archived" && <Button primary disabled={busy} onClick={() => startJob(`Analysis: ${active.title}`, "llm")}>{activeJob ? "Waiting for result" : `Approve & continue with ${model?.name}`}</Button>}<p className="rv-small">{active.status === "archived" ? "Archived and finalized sessions remain read-only." : "Model output proposes. The reviewer decides."}</p></section></div></div> : <div className="gc-core-start"><section className="gc-question"><div className="gc-question-heading"><span className="gc-symbol"><MessageSquare size={23}/></span><div><h2>Start an analytical question</h2><p>{currentWorkspace?.database} · Evidence and Memory in this workspace</p></div></div><form onSubmit={e => { e.preventDefault(); submitQuestion(); }}><label htmlFor="analysis-question" className="rv-eyebrow">Your question</label><textarea id="analysis-question" rows={4} value={question} placeholder="Quanti pazienti hanno almeno una visita di follow-up nel 2025?" onChange={e => { setQuestion(e.target.value); setDirty(!!e.target.value); }}/><div className="gc-composer-footer"><span><Cpu size={15}/>{model?.name}<small>Global model</small></span><button className="rv-button rv-primary" type="submit" disabled={!question.trim() || availability === "needs-preprocessing" || busy}>Start analysis<ArrowRight size={16}/></button></div></form></section><section className="gc-method"><p className="rv-eyebrow">A reviewable path</p><div><span><strong>01</strong>Ask a question</span><ChevronRight size={15}/><span><strong>02</strong>Review context</span><ChevronRight size={15}/><span><strong>03</strong>Validate the query</span></div><p>Every analytical result is backed by persisted artifacts and explicit review decisions.</p></section></div>}
</>;
const core = <OriginalCorePreview active={active} busy={busy} running={!!activeJob} question={question}
needsPreprocessing={availability === "needs-preprocessing"}
onQuestion={text => { setQuestion(text); setDirty(!!text); }} onSubmit={submitQuestion}
onContinue={() => { if (active) startJob(`Analysis: ${active.title}`, "llm"); }}
onStop={() => { if (operation) setOperation({ ...operation, state: "cancelled" }); }}
onWorkspace={() => navigate("workspace")} onActivity={setCoreActivityOpen} />;
const rows = page === "memory" ? cards[workspace] ?? inventory.memory : admin ? inventory[page] : [];
const selected = selection[scope] ?? rows[0]?.id;
const item = rows.find(r => r.id === selected);
@@ -176,7 +179,7 @@ export function App() {
{page === "workspace" && <p className="gc-footnote">Preprocessing uses the installation embedding model. Selecting an interaction LLM does not change index compatibility.</p>}
{<div className="rv-operations"><div className="gc-operation-intro"><ShieldCheck size={19}/><span>{page === "pi" ? "Installation diagnostics" : `${currentWorkspace?.name} · Operational context`}</span><strong>{scenario === "ready" ? "Ready for review" : scenario}</strong></div><div className="rv-operation-body">{sections}<div className="rv-operation-detail">{hasList && section === 0 && <details className="rv-choose-record"><summary>Choose record<span>{item?.title ?? "New record"}</span></summary>{list}</details>}{detail}</div></div></div>}
</>;
return <><div className={`rv-host rv-host-${width} gc-host cs-host ${portalOpen ? "gc-portal-open" : ""}`}><header className="rv-topbar"><button aria-label="Toggle portal navigation" onClick={() => setPortalOpen(!portalOpen)}><Menu size={20}/></button><strong>Omics <span>Portal</span></strong><span className="rv-topbar-divider"/><span className="rv-topbar-location">Clinical data & research</span><span className="rv-topbar-user">Administrator <span className="rv-avatar">AD</span></span></header><div className="gc-host-body"><aside className="gc-portal" aria-label="Illustrative portal navigation"><p className="rv-eyebrow">Portal</p>{[[LayoutDashboard,"Overview"],[Users,"Patients"],[Database,"Clinical data"],[Settings2,"ThothII"]].map(([Icon,label]) => { const I = Icon as typeof Menu; return <div key={String(label)} className={label === "ThothII" ? "current" : ""}><I size={18}/><span>{String(label)}</span></div>; })}<small>Host shell preview</small></aside><div className="gc-mount"><div className="gc-brandline"><span className="gc-brand">Thoth<span>II</span></span><span>Clinical analytics workbench</span><small>Context shelf review · V4</small></div><div className={`gc-app gc-variant-${variant}`}><div className="gc-global-top">{contextShelf}</div>{operation && <section className="gc-operation-strip" aria-label="Current operation"><div><p className="rv-eyebrow">{busy ? "Operation running" : `Operation ${operation.state}`}</p><strong>{operation.label}</strong><small>{workspaces.find(w => w.id === operation.workspace)?.name ?? "Installation"} · {models.find(m => m.id === operation.model)?.name ?? operation.model}</small><p id="operation-lock">{busy ? "Workspace and model are locked. Finish this operation before starting another." : "Workspace and model are available again."}</p></div>{busy && <div className="rv-actions"><Button onClick={() => setOperation({ ...operation, state: "complete" })}>Demo: finish</Button><Button onClick={() => setOperation({ ...operation, state: "cancelled" })}>Demo: stop</Button><Button onClick={() => setOperation({ ...operation, state: "failed" })}>Demo: fail</Button></div>}</section>}<div className="gc-body"><aside className="gc-rail"><button className="gc-mobile-nav" onClick={() => setNavOpen(!navOpen)} aria-expanded={navOpen}><Menu size={17}/>Sessions & navigation<ChevronDown size={16}/></button><div className={`gc-navigation ${navOpen ? "open" : ""}`}><nav className="cs-core-nav" aria-label="Core navigation">{nav("core")}</nav><SessionRail workspace={workspace} ready={ready} busy={busy} activeId={active?.id} canReadAll={canReadAll} onOpen={s => { if (busy || !ready || !canLeave()) return; setNavOpen(false); if (s.status === "paused") setResumeTarget(s); else { resetDetail(); setQuestion(""); setActive(s); writeRoute({ ...route, page: "core" }); } requestAnimationFrame(() => mainRef.current?.focus()); }}/>{canReadAll && <details className="cs-admin-nav" open={variant === "A" ? true : undefined}><summary><Settings2 size={16}/>Administration<ChevronDown size={14}/></summary><nav aria-label="Administration navigation">{(["database","memory","evidence","workspace","pi"] as Page[]).map(nav)}</nav></details>}</div></aside><main ref={mainRef} tabIndex={-1} className={`gc-main rv-variant-${variant}`} aria-label={ready ? titles[page] : "Context required"}>{notice && <div className="rv-notice" role="status"><span>{notice}</span><button aria-label="Dismiss notice" onClick={() => setNotice("")}><X size={16}/></button></div>}{dirty && <div className="rv-dirty" role="status">Unsaved draft. Save or discard before changing workspace or page.</div>}{!ready ? gate : <>{resumeReview}{page === "core" && active && <div className="gc-new-question"><Button disabled={busy} onClick={() => { if (busy || !canLeave()) return; setActive(null); setQuestion(""); setDirty(false); setNotice("Ready for a new question in the selected workspace."); }}>New question<ArrowRight size={15}/></Button></div>}{page === "core" || page === "sessions" ? core : administration}</>}<footer className="gc-page-footer"><span>Prototype · Synthetic content only</span><span>{ready ? "Context applies across ThothII" : "Core and administration are locked"}</span></footer></main></div></div></div></div></div>
return <><div className={`rv-host rv-host-${width} gc-host cs-host ${portalOpen ? "gc-portal-open" : ""}`}><header className="rv-topbar"><button aria-label="Toggle portal navigation" onClick={() => setPortalOpen(!portalOpen)}><Menu size={20}/></button><strong>Omics <span>Portal</span></strong><span className="rv-topbar-divider"/><span className="rv-topbar-location">Clinical data & research</span><span className="rv-topbar-user">Administrator <span className="rv-avatar">AD</span></span></header><div className="gc-host-body"><aside className="gc-portal" aria-label="Illustrative portal navigation"><p className="rv-eyebrow">Portal</p>{[[LayoutDashboard,"Overview"],[Users,"Patients"],[Database,"Clinical data"],[Settings2,"ThothII"]].map(([Icon,label]) => { const I = Icon as typeof Menu; return <div key={String(label)} className={label === "ThothII" ? "current" : ""}><I size={18}/><span>{String(label)}</span></div>; })}<small>Host shell preview</small></aside><div className="gc-mount"><div className="gc-brandline"><span className="gc-brand">Thoth<span>II</span></span><span>Clinical analytics workbench</span><small>Context shelf review · V4</small></div><div className={`gc-app gc-variant-${variant}`}><div className="gc-global-top">{contextShelf}</div>{operation && <section className="gc-operation-strip" aria-label="Current operation"><div><p className="rv-eyebrow">{busy ? "Operation running" : `Operation ${operation.state}`}</p><strong>{operation.label}</strong><small>{workspaces.find(w => w.id === operation.workspace)?.name ?? "Installation"} · {models.find(m => m.id === operation.model)?.name ?? operation.model}</small><p id="operation-lock">{busy ? "Workspace and model are locked. Finish this operation before starting another." : "Workspace and model are available again."}</p></div>{busy && <div className="rv-actions"><Button onClick={() => setOperation({ ...operation, state: "complete" })}>Demo: finish</Button><Button onClick={() => setOperation({ ...operation, state: "cancelled" })}>Demo: stop</Button><Button onClick={() => setOperation({ ...operation, state: "failed" })}>Demo: fail</Button></div>}</section>}<div className={`gc-body ${coreActivityOpen && !admin ? "cs-activity-open" : ""}`}><aside className="gc-rail"><button className="gc-mobile-nav" onClick={() => setNavOpen(!navOpen)} aria-expanded={navOpen}><Menu size={17}/>Sessions & navigation<ChevronDown size={16}/></button><div className={`gc-navigation ${navOpen ? "open" : ""}`}><nav className="cs-core-nav" aria-label="Core navigation">{nav("core")}</nav><SessionRail workspace={workspace} ready={ready} busy={busy} activeId={active?.id} canReadAll={canReadAll} onOpen={s => { if (busy || !ready || !canLeave()) return; setNavOpen(false); if (s.status === "paused") setResumeTarget(s); else { resetDetail(); setQuestion(""); setActive(s); writeRoute({ ...route, page: "core" }); } requestAnimationFrame(() => mainRef.current?.focus()); }}/>{canReadAll && <details className="cs-admin-nav" open={variant === "A" ? true : undefined}><summary><Settings2 size={16}/>Administration<ChevronDown size={14}/></summary><nav aria-label="Administration navigation">{(["database","memory","evidence","workspace","pi"] as Page[]).map(nav)}</nav></details>}</div></aside><main ref={mainRef} tabIndex={-1} className={`gc-main rv-variant-${variant}`} aria-label={ready ? titles[page] : "Context required"}>{notice && <div className="rv-notice" role="status"><span>{notice}</span><button aria-label="Dismiss notice" onClick={() => setNotice("")}><X size={16}/></button></div>}{dirty && <div className="rv-dirty" role="status">Unsaved draft. Save or discard before changing workspace or page.</div>}{!ready ? gate : <>{resumeReview}{page === "core" && active && <div className="gc-new-question"><Button disabled={busy} onClick={() => { if (busy || !canLeave()) return; setActive(null); setQuestion(""); setDirty(false); setNotice("Ready for a new question in the selected workspace."); }}>New question<ArrowRight size={15}/></Button></div>}{page === "core" || page === "sessions" ? core : administration}</>}<footer className="gc-page-footer"><span>Prototype · Synthetic content only</span><span>{ready ? "Context applies across ThothII" : "Core and administration are locked"}</span></footer></main></div></div></div></div></div>
{import.meta.env.DEV && <aside className="rv-lab gc-lab" aria-label="Prototype review controls"><div className="rv-lab-main"><span className="rv-lab-tag">CONTEXT SHELF · V4</span><button aria-label="Previous variant" onClick={() => cycle(-1)}><ArrowLeft size={17}/></button><select aria-label="Layout variant" value={variant} onChange={e => { if (canLeave()) { writeRoute({ ...route, variant: e.target.value as Variant }, true); resetDetail(); setQuestion(""); setShelfOpen(!ready); } }}>{Object.entries(variants).map(([k,v]) => <option key={k} value={k}>{k} · {v}</option>)}</select><button aria-label="Next variant" onClick={() => cycle(1)}><ArrowRight size={17}/></button><span className="rv-lab-help">No server writes</span></div><details><summary>Review scenarios & state</summary><div className="rv-lab-options"><label>Host width<select aria-label="Host width" value={width} onChange={e => setWidth(e.target.value)}><option value="full">Available width</option><option value="compact">960 px host</option><option value="phone">390 px host</option></select></label><label>Availability<select aria-label="Availability scenario" disabled={busy} value={availability} onChange={e => { if (canLeave()) { setAvailability(e.target.value as Availability); resetDetail(); } }}><option value="available">Available</option><option value="unavailable">Provider unavailable</option><option value="no-workspaces">No workspaces</option><option value="no-models">No models</option><option value="needs-preprocessing">Needs preprocessing</option></select></label><Button disabled={busy} onClick={() => { if (busy || !canLeave()) return; setOperation(null); writeRoute({ ...route, workspace: "" }); setModelId(""); resetDetail(); setActive(null); setQuestion(""); setResumeTarget(null); setShelfOpen(true); }}>Clear current context</Button><Button disabled={busy || !ready} onClick={() => { if (!busy && canLeave()) { setResumeTarget(sessions[2]); setNavOpen(false); } }}>Demo: Genetics session link</Button><Button disabled={busy} onClick={() => { if (busy || !canLeave()) return; if (!forget()) { setNotice("Browser storage could not be cleared."); return; } location.reload(); }}>Forget saved choices</Button><Button disabled={busy} onClick={() => { if (busy || !canLeave()) return; setCanReadAll(!canReadAll); writeRoute({ ...route, page: "core" }); resetDetail(); }}>Demo access: {canReadAll ? "Administrator" : "Analyst"}</Button><p>Workspace source: {sources.workspace} · Model source: {sources.model}</p><p>Page: {page} · Workspace: {workspace || "none"} · Model: {model?.name ?? "none"} · Gate: {ready ? "open" : "closed"} · Draft: {dirty ? "unsaved" : "clean"} · Operation: {operation?.state ?? "idle"}</p><p>Only explicit workspace/model choices persist on this browser. Reload resets simulated operations and drafts. Defaults are illustrative. V1/V2/V3 are preserved on ports 5174/5175/5176.</p></div></details></aside>}
</>;
}
@@ -0,0 +1,126 @@
// Prototype-only adapter: the Core layout is fixed, not a design variant.
// Reuse production styles, phase dots, activity log and response widgets, with
// synthetic state and local callbacks only. The iframe isolates the original CSS
// from the experimental host chrome; it is not an integration proposal.
import { useEffect, useState, type CSSProperties } from "react";
import { createPortal } from "react-dom";
import { ArrowLeft, ArrowRight, CornerDownLeft } from "lucide-react";
import { WorkflowBar } from "../../src/shell/WorkflowBar";
import { ModelActivityPanel } from "../../src/shell/ModelActivityPanel";
import { CentralStatus } from "../../src/shell/CentralStatus";
import { SelectWidget } from "../../src/widgets/SelectWidget";
import { FreetextWidget } from "../../src/widgets/FreetextWidget";
import { useSessionStore } from "../../src/store/sessionStore";
import type { Session } from "./model";
import productCss from "../../src/index.css?inline";
interface Props {
active: Session | null;
busy: boolean;
running: boolean;
question: string;
needsPreprocessing: boolean;
onQuestion: (text: string) => void;
onSubmit: () => void;
onContinue: () => void;
onStop: () => void;
onWorkspace: () => void;
onActivity: (open: boolean) => void;
}
const documentShell = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600;9..144,700&display=swap" rel="stylesheet"></head><body></body></html>`;
export function OriginalCorePreview(props: Props) {
const [body, setBody] = useState<HTMLElement | null>(null);
return <iframe
title="Original ThothII Core"
className="cs-original-core"
srcDoc={documentShell}
onLoad={event => setBody(event.currentTarget.contentDocument?.body ?? null)}
>{body && createPortal(<><style>{productCss}</style><Core {...props} /></>, body)}</iframe>;
}
function Core({ active, busy, running, question, needsPreprocessing, onQuestion, onSubmit, onContinue, onStop, onWorkspace, onActivity }: Props) {
const [showActivity, setShowActivity] = useState(false);
const [response, setResponse] = useState("");
const [thinking, setThinking] = useState("Medium");
const [width, setWidth] = useState(0);
const [root, setRoot] = useState<HTMLDivElement | null>(null);
const archived = active?.status === "archived";
useEffect(() => { onActivity(showActivity); return () => onActivity(false); }, [showActivity, onActivity]);
useEffect(() => {
if (!root) return;
const observer = new ResizeObserver(([entry]) => setWidth(entry.contentRect.width));
observer.observe(root);
return () => observer.disconnect();
}, [root]);
useEffect(() => { setResponse(""); setShowActivity(false); }, [active?.id]);
useEffect(() => {
useSessionStore.setState({
currentPhase: active ? `F${active.phase}` : null,
phaseError: null,
transcript: running ? [{ role: "assistant", text: "Consultazione del contesto e delle evidenze della domanda." }] : [],
activityLog: active ? [
{ kind: "prompt", phase: "F1", text: active.title },
{ kind: "assistant", phase: `F${active.phase}`, text: "Verifico la popolazione di riferimento e l’intervallo temporale prima di procedere.\n\nIl conteggio considera i pazienti distinti con almeno una visita nel periodo indicato." },
...(response ? [{ kind: "prompt" as const, phase: `F${active.phase}`, text: response }] : []),
] : [],
});
}, [active, running, response]);
const split = width >= 820;
const disabled = busy || archived || needsPreprocessing;
function reply(text: string) { if (disabled) return; setResponse(text); onContinue(); }
function submit() {
if (disabled || !question.trim()) return;
if (active) { reply(question.trim()); onQuestion(""); } else onSubmit();
}
return <div ref={setRoot} className="flex h-full min-w-0 bg-background text-foreground" style={{ "--activity-panel-width": "310px", height: "100dvh" } as CSSProperties}>
<style>{`html,body{margin:0;height:100%;overflow:hidden}*{box-sizing:border-box}.core-column{display:flex;flex:1;min-width:0;flex-direction:column}.core-scroll{flex:1;min-height:0;overflow-y:auto}.core-workflow{position:relative;padding:10px 16px 10px 54px;flex-shrink:0}.core-workflow nav{max-width:100%;min-width:0}.core-workflow>div>div{max-width:100%;gap:8px}.core-empty{min-height:100%;display:flex;align-items:center;justify-content:center;flex-direction:column;text-align:center;padding:24px}.core-composer{flex-shrink:0;padding:8px 24px 24px}.core-composer textarea{min-width:0;width:100%}.core-usage{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px;padding:8px 12px;border-top:1px solid oklch(var(--border));font-size:11px;color:oklch(var(--muted-foreground))}.core-usage label{display:flex;align-items:center;gap:6px}.core-usage select{background:transparent;color:inherit}.core-content{max-width:56rem;margin:auto;display:flex;flex-direction:column;gap:24px}.core-activity-overlay{position:fixed;inset:0;background:oklch(var(--foreground)/.12);z-index:20}@media(max-width:540px){.core-composer{padding:8px 12px 16px}.core-scroll{padding:20px 12px}.core-workflow{padding-right:8px;padding-left:42px}.core-workflow nav>div>span.mx-1{width:4px;margin-inline:2px}.core-workflow [role=timer]{display:none}}@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important}}`}</style>
{showActivity && <>
{!split && <button className="core-activity-overlay" aria-label="Dismiss model activity" onClick={() => setShowActivity(false)} />}
<ModelActivityPanel desktopSplit={split} onClose={() => setShowActivity(false)} onOpenWorkspaceManager={onWorkspace} />
</>}
<section className="core-column" aria-label="Question and responses">
<div className="core-workflow border-b border-border/70 bg-background/80">
<button type="button" onClick={() => setShowActivity(!showActivity)} aria-expanded={showActivity}
aria-label={showActivity ? "Hide model activity" : "Show model activity"}
title={showActivity ? "Hide model activity" : "Show model activity"}
className="absolute left-4 top-1/2 grid size-7 -translate-y-1/2 place-items-center rounded-md border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground">
{showActivity ? <ArrowLeft className="size-4" /> : <ArrowRight className="size-4" />}
</button>
<WorkflowBar finalized={archived} running={running} resetKey={active?.id} />
</div>
<div className="core-scroll px-6 py-8">
{!active ? <div className="core-empty">
<span className="font-heading text-5xl font-semibold tracking-tight text-foreground">Thoth<span className="text-primary">II</span></span>
<p className="mt-4 max-w-md text-balance text-[0.95rem] leading-relaxed text-muted-foreground">Build datamarts from your database through a guided, human-in-the-loop workflow. Type your question in the box below to begin a session.</p>
</div> : <div className="core-content">
<header className="border-b border-border/60 pb-3">
<p className="thot-label mb-0.5 text-muted-foreground">Question</p>
<h2 className="font-heading text-[0.95rem] font-semibold leading-snug text-foreground break-words" lang="it">{active.title}</h2>
</header>
<CentralStatus working={running} />
{archived ? <div className="rounded-xl border border-border/70 bg-card p-4"><p className="thot-label">Read-only archive</p><p className="mt-4 text-sm">Session completed. This prototype shows synthetic content only.</p></div> : !running && <fieldset disabled={busy} className="min-w-0 border-0 p-0">
<SelectWidget descriptor={{ id: "prototype-review", widget: "select", title: "Conferma il perimetro della domanda", intro: "Il conteggio considera i pazienti distinti con almeno una visita nel periodo indicato. Vuoi mantenere questo criterio?", options: [{ id: "confirm", label: "Conferma il criterio proposto", recommended: true }, { id: "clarify", label: "Specifica un criterio diverso" }] }} onRespond={answer => { if (answer.choices?.[0] === "clarify") setResponse("clarify"); else reply("Conferma il criterio proposto"); }} />
{response === "clarify" && <div className="mt-4"><FreetextWidget descriptor={{ id: "prototype-freetext", widget: "freetext", title: "Quale criterio vuoi applicare?" }} onRespond={answer => reply(answer.text ?? "")} /></div>}
</fieldset>}
</div>}
</div>
<div className="core-composer"><div className="mx-auto max-w-4xl">
{needsPreprocessing && <p className="mb-3 text-sm text-muted-foreground">Workspace preprocessing is required before starting a session.</p>}
<div className="rounded-2xl border border-border/80 bg-card shadow-md transition-colors focus-within:border-primary/50 focus-within:ring-3 focus-within:ring-ring/15">
<div className="px-2.5 py-2"><div className="flex items-end gap-2">
{active && !archived && <button type="button" aria-label="Stop and save session" title="Stop & save session" onClick={onStop} disabled={!running} className="grid size-7 shrink-0 place-items-center rounded-md border border-border text-primary transition-colors hover:bg-muted"><span className="block size-2.5 rounded-full bg-primary" /></button>}
<textarea rows={1} value={question} onChange={event => onQuestion(event.target.value)} disabled={archived}
onKeyDown={event => { if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); submit(); } }}
placeholder={active ? "Free steering (!)…" : "Ask a question to start a new session…"}
aria-label={active ? "Steering" : "New question"}
className="max-h-40 flex-1 resize-none rounded-lg bg-card px-1 py-1 text-sm leading-relaxed outline-none placeholder:text-muted-foreground" />
<button type="button" onClick={submit} disabled={disabled || !question.trim()} aria-label="Send" title="Send" className="grid size-7 shrink-0 place-items-center rounded-md border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-40"><CornerDownLeft className="size-3.5" /></button>
</div></div>
<div className="core-usage"><span>{active ? "Context: 4.2k / 200k · 2% (demo)" : "Context: 0 tokens"}</span><label>Thinking<select aria-label="Thinking level" disabled={busy} value={thinking} onChange={event => setThinking(event.target.value)}>{["Off", "Low", "Medium", "High"].map(level => <option key={level}>{level}</option>)}</select></label></div>
</div>
</div></div>
</section>
</div>;
}
@@ -4,6 +4,33 @@
Throwaway, development-only UI. No production API calls or server configuration
changes. Existing V1, V2 and V3 prototypes are retained.
## Core restoration, 2026-09-12
The user rejected the unsolicited Core redesign. The review scope is **Administration and the
global workspace/model shelf only**. All three options now keep the same original Core layout:
- Original F1–F8 circular workflow indicators and processing timer at the top.
- Original Model activity log opening on the left. At desktop widths it replaces the right
session rail while open; closing it restores the rail. Narrow widths use the existing drawer.
- Original question heading, central response/review widgets and bottom-pinned composer.
- No replacement hero, four-phase workflow excerpt, artifact tabs, or parallel review column.
- Workspace/model selection exists only in the global shelf. Thinking and context usage remain
in the composer footer; this correction does not redesign those Core controls.
`OriginalCorePreview.tsx` directly reuses the production `WorkflowBar`, `ModelActivityPanel`,
`CentralStatus`, `SelectWidget`, `FreetextWidget` and `src/index.css`. Its local composer callbacks
simulate interaction without API calls. A React portal into a prototype-only iframe isolates the
original styles from the experimental host styles. **This iframe is not a proposed production
architecture**, and no production Core component was changed by this restoration.
To inspect the response state, type a synthetic question, send it, then choose **Demo: finish**.
Open the arrow beside the phase dots to inspect the log. Switching A/B/C preserves the same Core
surface; only the surrounding context/navigation proposal changes.
Verified interactively: new question, operation lock, response widget, activity open/close, all
three variants, and 390-pixel host layout. The prototype entry point typechecks independently.
Earlier designs remain recoverable in the prototype branch history, including V4 at `41b9fed4`.
## Run
From `frontend/`, run `npm run prototype:context-shelf`.
@@ -1,6 +1,13 @@
/* V4: three descendants of C. All context controls expand inline above the full
application; none occupy the session rail. The existing product typography and
red Omics host are retained. Structure changes, not palette swaps. */
.cs-original-core { display: block; width: 100%; height: max(560px, calc(100dvh - 290px)); border: 0; }
.cs-host .gc-main:has(.cs-original-core) { padding: 0; }
.cs-host .gc-main:has(.cs-original-core) > .rv-notice,
.cs-host .gc-main:has(.cs-original-core) > .rv-dirty,
.cs-host .gc-main:has(.cs-original-core) > .gc-resume { margin: 16px; }
.cs-host .gc-main:has(.cs-original-core) > .gc-page-footer { padding: 12px 16px; margin-top: 0; }
.cs-host .gc-new-question { margin: 0; padding: 8px 16px; border-bottom: 1px solid var(--rv-line); }
.cs-host .gc-brandline > small { font-size: .62rem; }
.cs-host .gc-shelf-trigger { min-height: 74px; gap: 20px; }
.cs-host .gc-shelf-trigger > span:nth-child(2) { flex: unset; }
@@ -80,6 +87,8 @@
.cs-host .cs-shelf-C .cs-context-intro h2 { font: 500 1.5rem/1.4 var(--rv-serif); }
}
@container application (min-width: 55rem) {
.cs-host .gc-body.cs-activity-open { grid-template-columns: minmax(0,1fr); }
.cs-host .cs-activity-open > .gc-rail { display: none; }
.cs-host .gc-body, .cs-host .gc-variant-B .gc-body { grid-template-columns: minmax(0,1fr) 310px; }
.cs-host .gc-navigation { display: flex; flex-direction: column; }
.cs-host .gc-navigation .cs-admin-nav nav { grid-template-columns: minmax(0,1fr); }
+1 -1
View File
@@ -3,5 +3,5 @@ import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
export default defineConfig(({ command }) => {
if (command !== "serve") throw new Error("Context-shelf prototype is development-only.");
return { root: path.resolve(__dirname, "prototypes/context-shelf"), plugins: [react()], server: { host: "127.0.0.1", port: 5177, strictPort: true } };
return { root: path.resolve(__dirname, "prototypes/context-shelf"), plugins: [react()], resolve: { alias: { "@": path.resolve(__dirname, "src") } }, server: { host: "127.0.0.1", port: 5177, strictPort: true } };
});