// App shell: status spine, left rail, notifications slide-over, login. const { Pill, Card } = window.UI; const NAV = [ { id: 'strategy101', label: 'Bot 101', icon: Icon.Hash, hint: '1' }, { id: 'dashboard', label: 'Dashboard', icon: Icon.Dashboard, hint: 'D' }, { id: 'pairs', label: 'Pairs', icon: Icon.Grid, hint: 'P' }, { id: 'trades', label: 'Trades', icon: Icon.Trades, hint: 'T' }, { id: 'config', label: 'Config', icon: Icon.Config, hint: 'C' }, { id: 'control', label: 'Control', icon: Icon.Control, hint: 'O' }, { id: 'logs', label: 'Logs', icon: Icon.Logs, hint: 'L' }, { id: 'replay', label: 'Replay', icon: Icon.Replay, hint: 'R' }, { id: 'onboarding', label: 'How it Works', icon: Icon.Info, hint: 'H' }, ]; // Left rail — pin/unpin toggle persists to localStorage. // // Default behaviour: pinned (rail stays open at w-52). Click the pin // icon to collapse to icon-only (w-12) with hover-to-expand. The // preference survives reloads. Operator-friendly: not everyone wants // the rail eating 200px of dashboard real estate. function Sidebar({ route, setRoute, app }) { const [pinned, setPinned] = React.useState(() => { const v = localStorage.getItem('pb-sidebar-pinned'); return v === null ? true : v === '1'; }); const [hover, setHover] = React.useState(false); const open = pinned || hover; const togglePin = () => { setPinned(p => { const next = !p; localStorage.setItem('pb-sidebar-pinned', next ? '1' : '0'); return next; }); }; return ( ); } // The "status spine" — narrow strip across the very top. // Mode color owns a 2px tint band that runs the full width. function StatusSpine({ app, setApp, openNotif, onLogout }) { const accounts = app.accounts || []; const acct = accounts.find(a => a.id === app.account) || accounts[0] || null; const mode = app.modeOverride || (acct && acct.mode) || ''; const modeBand = { live: 'bg-neg-1', paper: 'bg-amb-1', testnet: 'bg-inf-1' }[mode] || 'bg-line-0'; const [acctOpen, setAcctOpen] = React.useState(false); const acctRef = React.useRef(); React.useEffect(() => { const h = (e) => { if (acctRef.current && !acctRef.current.contains(e.target)) setAcctOpen(false); }; document.addEventListener('mousedown', h); return () => document.removeEventListener('mousedown', h); }, []); const stale = app.tickAge > 5; const halted = app.halted; const unread = app.unreadAlerts; return (
{/* mode tint band */}
{/* main strip */}
{/* account selector */}
{acctOpen && (
accounts
{accounts.length === 0 && (
no accounts configured
)} {accounts.map(a => ( ))}
)}
{mode && {mode}} {/* kill-switch — clickable when halted so the operator lands on the Control page (where Resume lives) with one tap. */} {halted ? ( ) : (
running
)} {/* last tick — seconds since the most recent BBO frame. null means we've never seen one (badge hidden); 0 means "<1s ago" which is actually the most reassuring state. */} {app.tickAge != null && (
tick {app.tickAge < 1 ? '<1s' : `${app.tickAge}s`}
)} {/* scout iter rate — backend publishes 1 SSE 'scout' frame per second carrying iter_per_sec. We hide the badge entirely until the bot has had a chance to scout (post-boot). */} {app.scoutRate > 0 && (
scout {app.scoutRate}/s
)}
{/* clock */}
{window.UI.fmtTs(app.now)}
{/* notifications bell */} {/* user menu */}
); } function UserMenu({ app, onLogout }) { const [open, setOpen] = React.useState(false); const ref = React.useRef(); React.useEffect(() => { const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', h); return () => document.removeEventListener('mousedown', h); }, []); return (
{open && (
{app.user}
single-user · local
)}
); } // Right-side slide-over for unread events / dead-letter / reconcile diffs. function NotificationsPanel({ open, onClose, alerts }) { return ( <>
); } function LoginScreen({ onLogin, authEnabled }) { const [u, setU] = React.useState('operator'); const [p, setP] = React.useState(''); const [remember, setRemember] = React.useState(true); const [err, setErr] = React.useState(''); const [busy, setBusy] = React.useState(false); const submit = async (e) => { e.preventDefault(); if (!p.length && authEnabled !== false) { setErr('password is required'); return; } setBusy(true); setErr(''); try { await onLogin(u, p); } catch (e) { setErr(e.message || 'authentication failed'); setBusy(false); } }; return (
project-bad
v1 · operator

sign in

{authEnabled === false ? 'auth disabled · dev mode · any password works' : 'single-user · local · session cookie'}

{err && (
{err}
)}
no signup · no password reset v0.4.1 · 9a17c4e
); } window.Shell = { Sidebar, StatusSpine, NotificationsPanel, LoginScreen, NAV };