// ========================================================================== // Shell: left rail (threads + agents), composer (with real file input), // canvas header. All driven from the store. // ========================================================================== function LeftRail() { const { me, threads, currentThread, route, navigate, createThread, deleteThread, resetAll, briefings, agents } = useStore(); const [hoverThreadId, setHoverThreadId] = React.useState(null); const handleReset = async () => { if (!confirm('Reset everything? This wipes:\n- All threads, messages, artifacts (localStorage)\n- All server-side CD sessions and CDBriefs\n\nThe page will reload.')) return; await resetAll(); }; // Editorial issue line — the "Studio №" masthead reads as a magazine issue. // Volume rotates by month; folio counts the threads as the catalogue. const today = new Date(); const volume = `Vol. ${String(today.getFullYear()).slice(-2)}.${String(today.getMonth() + 1).padStart(2, '0')}`; const folio = String((threads || []).length).padStart(3, '0'); const briefingsBadge = (briefings || []).filter(b => ['in_creative_review', 'in_business_review'].includes(b.status)).length; const agentsBadge = (agents || []).filter(a => a.unread).length; return (
{/* ── Masthead ─────────────────────────────────────────────────── */}
{volume} № {folio}

{/* ── Begin a thread — glass pill with iridescent border ─────── */} {/* ── Threads — Contra-style numbered index ────────────────────── */}
Threads {folio}
{threads.length === 0 && (
No threads yet.
)} {threads.map((t, i) => { const active = route.threadId === t.id; const hovered = hoverThreadId === t.id; return (
setHoverThreadId(t.id)} onMouseLeave={() => setHoverThreadId(null)} style={{ position: 'relative' }}> {hovered && !active && ( )}
); })}
{/* ── Sections ─────────────────────────────────────────────────── */}
navigate('/briefings')} /> navigate('/agents')} /> {/* ── Colophon ─────────────────────────────────────────────────── */}
Studio
{me?.org || '...'}
{me?.name || ''}
); } function SectionLink({ label, active, badge, onClick }) { const [hover, setHover] = React.useState(false); return ( ); } function Composer({ onSend }) { const { route, streaming, uploadFiles, currentMessages, currentArtifacts } = useStore(); const [value, setValue] = React.useState(''); const fileInputRef = React.useRef(null); const disabled = !route.threadId || streaming.active; // Mirror ThreadView's canvasActive: wide until canvas appears const wide = (currentMessages || []).length === 0 && (currentArtifacts || []).length === 0 && !streaming.active; const send = () => { const v = value.trim(); if (!v) return; setValue(''); onSend(v); }; const onFiles = (files) => { const arr = Array.from(files); if (arr.length) uploadFiles(arr); }; return (
{ onFiles(e.target.files); e.target.value = ''; }} />
{ e.preventDefault(); }} onDrop={(e) => { e.preventDefault(); onFiles(e.dataTransfer.files); }} className="glass-strong" style={{ borderRadius: 'var(--radius-lg)', padding: '14px 16px', display: 'flex', flexDirection: 'column', gap: 10, }}>