// Projects index, single project, stats, health, settings
const ProjectsScreen = () => {
const { go } = useRouter();
const cols = '1fr 90px 90px 110px 110px 90px';
return (
<>
<div className="subbar">
<span className="mono muted">{PROJECTS.length} projects · ranked by recent activity</span>
<span style={{ flex: 1 }} />
<FilterChip dim="since" value="30d" onChange={() => {}} />
</div>
<div className="thead" style={{ gridTemplateColumns: cols }}>
<span>cwd</span><span className="right">sessions</span><span className="right">tok</span>
<span className="right">last</span><span>top tool</span><span className="right">activity</span>
</div>
<div className="body">
{PROJECTS.map((p, i) => (
<div key={p.cwd} className="row" style={{ gridTemplateColumns: cols }}
onClick={() => go({ name: 'project', cwd: p.cwd })}>
<span className="mono">{p.cwd}</span>
<span className="right mono">{p.sessions}</span>
<span className="right mono muted">{p.tok}</span>
<span className="right mono muted">{p.last}</span>
<span><ToolTag tool={p.topTool} /></span>
<span className="right"><Spark w={70} h={12} seed={i + 3} accent={i === 0} /></span>
</div>
))}
</div>
</>
);
};
const ProjectScreen = () => {
const { go, route } = useRouter();
const cwd = route.cwd || '~/code/tt-bundle';
const meta = PROJECTS.find(p => p.cwd === cwd) || { sessions: 0, tok: '0', topTool: 'claude-code' };
const lastSeg = cwd.split('/').filter(Boolean).pop() || cwd;
const parent = cwd.slice(0, cwd.length - lastSeg.length);
const sessionList = SESSIONS.filter(s => s.cwd === cwd);
const cols = '90px 110px 70px 1fr 50px 60px';
return (
<>
<div style={{ padding: '10px 14px', borderBottom: '1px solid var(--rule-2)', background: 'var(--paper-3)', flex: 'none' }}>
<div className="mono muted" style={{ fontSize: 11 }}>{parent}</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 12, flexWrap: 'wrap' }}>
<span className="mono" style={{ fontSize: 18, fontWeight: 600 }}>{lastSeg}</span>
<span className="tag accent">{meta.sessions} sessions</span>
<span className="tag">{meta.tok} tok</span>
{Array.from(new Set(sessionList.map(s => s.host))).map(h => <HostTag key={h} host={h} />)}
<span style={{ flex: 1 }} />
<Spark w={120} h={18} accent />
</div>
</div>
{sessionList.length > 0 && (
<div className="subbar">
<span className="mono muted">★ saved:</span>
{SAVED_SEARCHES.map(s => (
<span key={s} className="tag click">★ {s}</span>
))}
<span className="tag dashed">+ new</span>
</div>
)}
<div className="thead" style={{ gridTemplateColumns: cols }}>
<span>date</span><span>tool</span><span>host</span><span>session</span>
<span className="right">turns</span><span className="right">tok</span>
</div>
<div className="body">
{sessionList.length === 0 ? (
<Empty glyph="∅" title={`No sessions yet in ${lastSeg}.`}
hint="run a CLI assistant in this cwd and they'll appear here within 30s" />
) : sessionList.map((s) => (
<div key={s.id} className="row" style={{ gridTemplateColumns: cols }}
onClick={() => go({ name: 'session', id: s.id })}>
<span className="mono muted">{s.when.replace(/^Today /, '').replace(/^Yest\. /, 'Yest ')}</span>
<span><ToolTag tool={s.tool} /></span>
<span><HostTag host={s.host} /></span>
<span className="truncate">{s.q}</span>
<span className="right mono">{s.turns}</span>
<span className="right mono muted">{s.tok}</span>
</div>
))}
</div>
</>
);
};
const StatsScreen = () => {
const { go } = useRouter();
const [range, setRange] = React.useState('30d');
const [groupBy, setGroupBy] = React.useState('tool');
return (
<>
<div className="subbar">
<span className="mono">range:</span>
{['7d', '30d', '90d', 'all'].map(r => (
<span key={r} className={'tag click' + (range === r ? ' accent' : '')}
onClick={() => setRange(r)}>{r}</span>
))}
<span style={{ flex: 1 }} />
<span className="mono muted">group by:</span>
{['tool', 'host', 'project', 'model'].map(g => (
<span key={g} className={'tag click' + (groupBy === g ? ' accent' : '')}
onClick={() => setGroupBy(g)}>{g}</span>
))}
</div>
<div className="body">
<div className="stats-grid">
{/* per-tool strip */}
<div className="full">
<div className="uppercase-mono" style={{ marginBottom: 6 }}>per tool · last {range}</div>
<div className="card" style={{ padding: 0 }}>
{TOOL_ROLLUPS.map((r, i) => (
<div key={r.tool} style={{
display: 'grid',
gridTemplateColumns: '140px 70px 70px 70px 1fr 60px',
gap: 10, alignItems: 'center',
padding: '6px 12px',
borderBottom: i < TOOL_ROLLUPS.length - 1 ? '1px solid var(--rule-2)' : 'none',
background: i % 2 ? 'var(--paper-3)' : 'var(--paper-4)'
}}>
<span style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
<ToolDot tool={r.tool} /> <span className="mono">{r.tool}</span>
</span>
<span className="right mono">{r.turns.toLocaleString()}</span>
<span className="right mono muted">{r.ktok}k tok</span>
<span className="right mono">{r.cost > 0 ? `$${r.cost.toFixed(2)}` : '—'}</span>
<span style={{ paddingLeft: 12 }}>
<Spark w="100%" h={16} seed={i + 2} accent={i === 0} />
</span>
<span className="right mono muted">{(r.share * 100).toFixed(0)}%</span>
</div>
))}
</div>
</div>
{/* turns/day stacked */}
<div className="card full">
<div className="card-head"><span>turns/day · stacked by tool</span>
<span style={{ flex: 1 }} />
<span className="flex" style={{ gap: 10, fontSize: 10 }}>
<span className="flex" style={{ gap: 4 }}><span className="tooldot" style={{ background: 'var(--accent)' }} /> claude-code</span>
<span className="flex" style={{ gap: 4 }}><span className="tooldot" style={{ background: '#3b6e3b' }} /> opencode</span>
<span className="flex" style={{ gap: 4 }}><span className="tooldot" style={{ background: '#7a4ea8' }} /> crush</span>
</span>
</div>
<div className="card-body">
<svg viewBox="0 0 600 130" style={{ width: '100%', height: 130 }} preserveAspectRatio="none">
{/* Y axis grid */}
{[0, 30, 60, 90, 120].map(y => (
<line key={y} x1="0" x2="600" y1={130 - y} y2={130 - y} stroke="var(--rule-2)" strokeWidth="0.5" />
))}
{Array.from({ length: 60 }).map((_, i) => {
const claude = 8 + Math.abs(Math.sin(i * 0.5)) * 60;
const oc = 2 + Math.abs(Math.cos(i * 0.9)) * 7;
const crush = 1 + Math.abs(Math.sin(i * 1.2)) * 3;
const x = i * 10 + 2;
return (
<g key={i}>
<rect x={x} y={130 - claude} width={8} height={claude} fill="var(--accent)" opacity="0.85" />
<rect x={x} y={130 - claude - oc} width={8} height={oc} fill="#3b6e3b" opacity="0.75" />
<rect x={x} y={130 - claude - oc - crush} width={8} height={crush} fill="#7a4ea8" opacity="0.7" />
</g>
);
})}
</svg>
<div className="flex" style={{ justifyContent: 'space-between', marginTop: 4, fontSize: 9.5 }}>
<span className="mono muted">60d ago</span>
<span className="mono muted">today</span>
</div>
</div>
</div>
{/* heatmap */}
<div className="card">
<div className="card-head">activity · 12 weeks</div>
<div className="card-body">
<svg viewBox="0 0 250 80" style={{ width: '100%', height: 80 }}>
{Array.from({ length: 12 * 7 }).map((_, i) => {
const v = (Math.sin(i * 0.6) + Math.cos(i * 0.3) + 1.5) / 3;
const w = Math.floor(i / 7), d = i % 7;
const op = 0.1 + v * 0.85;
return <rect key={i} x={w * 20} y={d * 11} width={18} height={9} fill="var(--accent)" opacity={op} rx="1.5" />;
})}
</svg>
<div className="flex muted mono" style={{ marginTop: 6, fontSize: 9.5, gap: 4 }}>
<span>less</span>
{[0.15, 0.35, 0.55, 0.8, 1].map(o => (
<span key={o} style={{ width: 10, height: 10, background: 'var(--accent)', opacity: o, borderRadius: 1 }} />
))}
<span>more</span>
</div>
</div>
</div>
{/* top cwd */}
<div className="card">
<div className="card-head">top cwd</div>
<div className="card-body">
{[
['~/code/tt-bundle', 41, 1.0],
['~/work/scarlet-svc', 28, 0.68],
['~/work/atelier/migrations', 14, 0.34],
['~/code/dotfiles', 7, 0.17],
['~/notes', 5, 0.12],
].map(([p, n, r]) => (
<div key={p} className="click"
style={{ display: 'grid', gridTemplateColumns: '1fr 90px 30px',
gap: 8, padding: '4px 0', borderBottom: '1px dotted var(--rule-2)', alignItems: 'center' }}
onClick={() => go({ name: 'project', cwd: p })}>
<span className="mono truncate" style={{ fontSize: 11 }}>{p}</span>
<span style={{ height: 5, background: 'var(--paper-2)', borderRadius: 2 }}>
<div style={{ width: `${r * 100}%`, height: '100%', background: 'var(--accent)', borderRadius: 2 }} />
</span>
<span className="right mono muted">{n}</span>
</div>
))}
</div>
</div>
{/* hour-of-day */}
<div className="card">
<div className="card-head">turns by hour</div>
<div className="card-body">
<svg viewBox="0 0 240 70" style={{ width: '100%', height: 70 }} preserveAspectRatio="none">
{Array.from({ length: 24 }).map((_, i) => {
const v = i < 7 ? 4 + Math.sin(i) * 3
: i < 12 ? 30 + Math.sin(i * 0.7) * 18
: i < 18 ? 45 + Math.cos(i * 0.6) * 14
: 18 + Math.sin(i * 1.1) * 10;
const h = Math.max(2, v);
return <rect key={i} x={i * 10 + 1} y={70 - h} width={8} height={h}
fill="var(--accent)" opacity={i < 9 || i > 19 ? 0.45 : 0.85} />;
})}
</svg>
<div className="flex muted mono" style={{ justifyContent: 'space-between', fontSize: 9.5, marginTop: 4 }}>
<span>00</span><span>06</span><span>12</span><span>18</span><span>24</span>
</div>
</div>
</div>
{/* host split */}
<div className="card">
<div className="card-head">by host</div>
<div className="card-body">
{[
['laptop', 1240, 0.72, '#3b6e3b'],
['workpc', 480, 0.28, 'var(--accent)'],
].map(([h, n, r, c]) => (
<div key={h} style={{ marginBottom: 8 }}>
<div className="flex" style={{ justifyContent: 'space-between', marginBottom: 3 }}>
<HostTag host={h} />
<span className="mono" style={{ fontSize: 11 }}>{n.toLocaleString()} <span className="muted">({(r * 100).toFixed(0)}%)</span></span>
</div>
<div style={{ height: 6, background: 'var(--paper-2)', borderRadius: 3 }}>
<div style={{ width: `${r * 100}%`, height: '100%', background: c, borderRadius: 3 }} />
</div>
</div>
))}
<div className="muted mono" style={{ fontSize: 10, marginTop: 8 }}>1,720 turns · 30d</div>
</div>
</div>
</div>
</div>
</>
);
};
const HealthScreen = () => {
const okCount = COLLECTORS.filter(c => c.status === 'ok').length;
const warnCount = COLLECTORS.filter(c => c.status === 'warn').length;
const staleCount = COLLECTORS.filter(c => c.status === 'stale').length;
const cols = '20px 90px 130px 1fr 60px 70px 80px 80px';
return (
<>
<div className="subbar">
<span className="mono" style={{ fontWeight: 600 }}>collectors</span>
<span className="tag ok">● {okCount} ok</span>
{warnCount > 0 && <span className="tag warn">● {warnCount} warn</span>}
{staleCount > 0 && <span className="tag err">● {staleCount} stale</span>}
<span style={{ flex: 1 }} />
<span className="mono muted">poll: 30s · ingesting as <span className="mono" style={{ color: 'var(--accent-ink)' }}>{ME.user}</span> via {ME.via} → 127.0.0.1:8401</span>
</div>
<div className="thead" style={{ gridTemplateColumns: cols }}>
<span></span><span>host</span><span>tool</span><span>source</span>
<span className="right">lag</span><span className="right">outbox</span>
<span className="right">last ok</span><span className="right">events 24h</span>
</div>
<div className="body">
{COLLECTORS.map((r, i) => (
<div key={i} className="row no-click" style={{ gridTemplateColumns: cols, cursor: 'default' }}>
<StatusDot status={r.status} />
<span className="mono">{r.host}</span>
<span><ToolTag tool={r.tool} /></span>
<span className="mono muted truncate">{r.src}</span>
<span className={'right mono ' + (r.status === 'stale' ? 'accent-c' : (r.status === 'warn' ? '' : 'muted'))}
style={r.status === 'stale' ? { color: 'var(--err)', fontWeight: 600 } : {}}>{r.lag}</span>
<span className={'right mono ' + (r.out > 0 ? '' : 'muted')}
style={r.out > 0 ? { color: 'var(--warn)', fontWeight: 600 } : {}}>{r.out}</span>
<span className="right mono muted">{r.last}</span>
<span className="right mono">{r.ev}</span>
</div>
))}
</div>
<div className="footstrip">
<span className="mono muted">backfill:</span>
<span className="mono">claude-code/workpc</span>
<span className="mono muted">6/9 files</span>
<span style={{ width: 200, height: 6, background: 'var(--paper-2)', borderRadius: 3 }}>
<div style={{ width: '67%', height: '100%', background: 'var(--accent)', borderRadius: 3 }} />
</span>
<span className="mono">67%</span>
<span className="mono muted">~3m</span>
<span style={{ flex: 1 }} />
<span className="mono" style={{ color: 'var(--err)' }}>● last error: 09:08 crush "tool_call_v2" → metadata fallback</span>
</div>
</>
);
};
const SettingsScreen = () => {
const [section, setSection] = React.useState('Sources');
const sections = ['Sources', 'Display', 'Auth', 'Backup', 'Export', 'Tags', 'Saved searches'];
return (
<div className="settings-grid">
<aside className="settings-nav">
{sections.map(s => (
<div key={s} className={'item' + (section === s ? ' active' : '')}
onClick={() => setSection(s)}>{s}</div>
))}
</aside>
<main>
{section === 'Sources' && <SettingsSources />}
{section === 'Display' && <SettingsDisplay />}
{section === 'Auth' && <SettingsAuth />}
{section === 'Backup' && <SettingsStub title="Backup" subtitle="Daily sqlite3 .backup snapshot via cron — documented in README; not configured in-app." />}
{section === 'Export' && <SettingsStub title="Export" subtitle="Bulk-export sessions to JSONL or markdown." />}
{section === 'Tags' && <SettingsStub title="Tags" subtitle="Manage custom tags applied to sessions." />}
{section === 'Saved searches' && <SettingsStub title="Saved searches" subtitle="Manage starred queries across all projects." />}
</main>
</div>
);
};
const SettingsSources = () => {
const cols = '14px 110px 1fr 60px 90px 70px 50px';
return (
<>
<div className="mono" style={{ fontSize: 13, fontWeight: 600, marginBottom: 6 }}>Sources</div>
<div className="muted" style={{ fontSize: 11.5, marginBottom: 10 }}>
<span className="mono">~/.config/assistant-log/config.toml</span> · per-host
</div>
<div className="card" style={{ padding: 0 }}>
<div className="thead" style={{ gridTemplateColumns: cols, borderRadius: '4px 4px 0 0' }}>
<span></span><span>tool</span><span>path</span>
<span className="right">poll</span><span className="right">events</span>
<span className="right">last ok</span><span></span>
</div>
{[
['ok', 'claude-code', '~/.claude/projects', '30s', 12418, 'now'],
['ok', 'opencode', '~/.local/share/opencode', '30s', 312, '12s'],
['ok', 'crush', '~/.cache/crush', '60s', 88, '28s'],
['ok', 'pi', '~/.config/pi/history', '60s', 47, '1m'],
['warn', 'kimi', '~/.kimi-cli/history.jsonl', '60s', 12, '2m'],
].map(([s, t, p, poll, ev, last], i, arr) => (
<div key={t} style={{
display: 'grid', gridTemplateColumns: cols, gap: 10,
padding: '5px 14px', alignItems: 'center',
borderBottom: i < arr.length - 1 ? '1px solid var(--rule-2)' : 'none',
fontSize: 11.5
}}>
<StatusDot status={s} />
<span><ToolTag tool={t} /></span>
<span className="mono muted truncate">{p}</span>
<span className="right mono">{poll}</span>
<span className="right mono">{ev.toLocaleString()}</span>
<span className="right mono muted">{last}</span>
<span className="right mono click" style={{ fontSize: 11, color: 'var(--accent-ink)' }}>edit</span>
</div>
))}
</div>
<div style={{ marginTop: 8 }}>
<span className="tag dashed">+ add source</span>
</div>
<div className="card" style={{ marginTop: 20 }}>
<div className="card-head">server</div>
<div className="card-body" style={{ display: 'grid', gridTemplateColumns: '140px 1fr', rowGap: 6, columnGap: 14, fontSize: 11.5 }}>
<span className="muted mono">module</span><span className="mono">sourcecraft.dev/bigbes/lethe</span>
<span className="muted mono">bind</span><span className="mono">{AUTH_CONFIG.bind} <span className="muted">· loopback-only, behind reverse proxy</span></span>
<span className="muted mono">db</span><span className="mono">~/.local/share/lethe/store.sqlite (412 MB) · WAL · busy_timeout=5s</span>
<span className="muted mono">fts</span><span className="mono">turns_fts · tool_outputs_fts · 24,118 turns indexed</span>
<span className="muted mono">migrations</span><span className="mono">0001_init · applied on startup via embed.FS</span>
<span className="muted mono">api</span><span className="mono">/api/v1 · /healthz · /readyz · /metrics</span>
<span className="muted mono">uptime</span><span className="mono">14d 03:12 · since boot</span>
</div>
</div>
</>
);
};
const SettingsAuth = () => {
const a = AUTH_CONFIG;
return (
<>
<div className="mono" style={{ fontSize: 13, fontWeight: 600, marginBottom: 4 }}>Auth</div>
<div className="muted" style={{ fontSize: 11.5, marginBottom: 12 }}>
Two independent paths, both gated by the same allowlist. Server binds <span className="mono">127.0.0.1</span> only —
a reverse proxy on phoebe terminates TLS and forwards. Editing requires rewriting <span className="mono">config.yaml</span> and restarting.
</div>
<div className="card" style={{ marginBottom: 14 }}>
<div className="card-head">allowlist · <span className="muted">auth.allowed_users</span></div>
<div className="card-body" style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
{a.allowedUsers.map(u => (
<span key={u} className="tag" style={{ fontSize: 11 }}>
{u}{a.admins.includes(u) && <span style={{ marginLeft: 4, fontSize: 9, padding: '0 4px', borderRadius: 2, background: 'var(--accent)', color: 'var(--accent-on)', fontWeight: 700, letterSpacing: '0.05em' }}>ADMIN</span>}
</span>
))}
<span className="tag dashed">+ add</span>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 14 }}>
<div className="card">
<div className="card-head">
<span>forward-auth (header trust)</span>
<span style={{ flex: 1 }} />
<span className={'tag ' + (a.forwardAuth.enabled ? 'ok' : 'outline')}>{a.forwardAuth.enabled ? '● enabled' : '○ disabled'}</span>
</div>
<div className="card-body" style={{ display: 'grid', gridTemplateColumns: '110px 1fr', rowGap: 5, columnGap: 12, fontSize: 11 }}>
<span className="muted mono">user header</span><span className="mono">{a.forwardAuth.userHeader}</span>
<span className="muted mono">trust source</span><span className="mono">Caddy → Authelia forward-auth</span>
<span className="muted mono">used by</span><span>browser sessions w/ Authelia cookie</span>
</div>
<div className="card-body" style={{ borderTop: '1px solid var(--rule-2)', paddingTop: 8, fontSize: 10.5 }}>
<div className="uppercase-mono" style={{ marginBottom: 4 }}>caddy snippet</div>
<pre className="mono muted" style={{ margin: 0, fontSize: 10, lineHeight: 1.5, whiteSpace: 'pre-wrap' }}>{`forward_auth authelia.internal:9091 {
uri /api/verify?rd=https://auth/
copy_headers Remote-User Remote-Email
}
reverse_proxy 127.0.0.1:8401`}</pre>
</div>
</div>
<div className="card">
<div className="card-head">
<span>oidc bearer</span>
<span style={{ flex: 1 }} />
<span className={'tag ' + (a.oidc.enabled ? 'ok' : 'outline')}>{a.oidc.enabled ? '● enabled' : '○ disabled'}</span>
</div>
<div className="card-body" style={{ display: 'grid', gridTemplateColumns: '110px 1fr', rowGap: 5, columnGap: 12, fontSize: 11 }}>
<span className="muted mono">issuer</span><span className="mono truncate">{a.oidc.issuer}</span>
<span className="muted mono">audience</span><span className="mono">{a.oidc.audience}</span>
<span className="muted mono">claim</span><span className="mono">{a.oidc.usernameClaim} <span className="muted">→ fallback sub</span></span>
<span className="muted mono">jwks</span><span className="mono">cached · last fetch {a.oidc.jwksLastFetch}</span>
<span className="muted mono">used by</span><span>collector, scripted clients</span>
</div>
<div className="card-body" style={{ borderTop: '1px solid var(--rule-2)', paddingTop: 8, fontSize: 10.5 }}>
<div className="uppercase-mono" style={{ marginBottom: 4 }}>resolution order</div>
<div className="mono muted" style={{ fontSize: 10, lineHeight: 1.55 }}>
1. <span className="mono">Authorization: Bearer …</span> validated → user from JWT<br/>
2. else <span className="mono">{a.forwardAuth.userHeader}</span> taken from proxy<br/>
3. else 401 · invalid bearer never falls back (fail-closed)
</div>
</div>
</div>
</div>
<div className="card" style={{ padding: 0 }}>
<div className="card-head" style={{ padding: '6px 12px' }}>recent auth events</div>
<div className="thead" style={{ gridTemplateColumns: '70px 80px 100px 1fr 50px 1fr', borderRadius: 0 }}>
<span>time</span><span>user</span><span>via</span><span>path</span><span className="right">code</span><span>note</span>
</div>
{AUTH_EVENTS.map((e, i) => (
<div key={i} style={{
display: 'grid', gridTemplateColumns: '70px 80px 100px 1fr 50px 1fr', gap: 10,
padding: '4px 14px', alignItems: 'center', fontSize: 11,
borderBottom: i < AUTH_EVENTS.length - 1 ? '1px solid var(--rule-2)' : 'none',
background: e.status >= 400 ? 'var(--err-bg)' : 'transparent',
}}>
<span className="mono muted">{e.t}</span>
<span className="mono">{e.user}</span>
<span className="mono"><span className={'tag ' + (e.via === 'forward-auth' ? 'outline' : 'host')} style={{ fontSize: 9 }}>{e.via}</span></span>
<span className="mono truncate">{e.path}</span>
<span className="right mono" style={{ color: e.status >= 400 ? 'var(--err)' : 'var(--ok)' }}>{e.status}</span>
<span className="muted mono" style={{ fontSize: 10.5 }}>{e.note || ''}</span>
</div>
))}
</div>
<div style={{ marginTop: 14, padding: 10, background: 'var(--paper-3)', border: '1px dashed var(--rule)', borderRadius: 4, fontSize: 11 }}>
<div className="uppercase-mono" style={{ marginBottom: 4 }}>trust model</div>
<div className="muted">
<span className="mono">owner</span> is server-derived from the authenticated user on every ingest write.
The wire format in <span className="mono">internal/shared/wire/</span> has no <span className="mono">owner</span> field — collectors cannot impersonate.
Read endpoints filter to <span className="mono">owner = current_user</span>; admins (<span className="mono">{a.admins.join(', ')}</span>) may pass <span className="mono">?owner=<user></span> or <span className="mono">?owner=*</span> to override. Non-admins passing <span className="mono">?owner=</span> at all → 403.
A session belonging to another owner returns 404 (existence is never leaked).
</div>
</div>
</>
);
};
const SettingsDisplay = () => (
<>
<div className="mono" style={{ fontSize: 13, fontWeight: 600, marginBottom: 6 }}>Display</div>
<div className="muted" style={{ fontSize: 11.5, marginBottom: 10 }}>Mirror of the Tweaks panel — these settings sync.</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 10 }}>
{[
['density', ['compact', 'comfortable']],
['tool calls', ['expanded', 'collapsed']],
['accent color', ['on', 'off']],
['hour format', ['24h', '12h']],
].map(([l, opts]) => (
<div key={l} className="card" style={{ padding: 10 }}>
<div className="uppercase-mono" style={{ marginBottom: 5 }}>{l}</div>
<div style={{ display: 'flex', gap: 4 }}>
{opts.map((o, j) => (
<span key={o} className={'tag click' + (j === 0 ? ' accent' : '')}>{o}</span>
))}
</div>
</div>
))}
</div>
</>
);
const SettingsStub = ({ title, subtitle }) => (
<>
<div className="mono" style={{ fontSize: 13, fontWeight: 600, marginBottom: 6 }}>{title}</div>
<div className="muted" style={{ fontSize: 11.5, marginBottom: 14 }}>{subtitle}</div>
<div className="empty" style={{ padding: 40, border: '1px dashed var(--rule)', borderRadius: 4 }}>
<div className="glyph">⚙</div>
<div>Detailed config UI · stubbed for prototype</div>
</div>
</>
);
Object.assign(window, {
ProjectsScreen, ProjectScreen, StatsScreen, HealthScreen, SettingsScreen,
});