feat(theme): dark/light mode with a Radix Colors-based palette
Adds a working Dark/Light toggle (next to the language switcher, same row
as EN/VI) and re-themes the whole dashboard, not just the handful of
components that already used semantic tokens.
- Tailwind darkMode:"class" + CSS-variable color tokens (client/src/index.css,
tailwind.config.js): surface.0-5, border/border-light, accent/accent-hover,
fg.primary/secondary/muted, status.success/danger/warning. One class flip
on <html> re-themes everything — no per-element dark: variant pairs.
- useTheme() hook: localStorage-persisted, defaults to dark, no
prefers-color-scheme fallback (client/src/hooks/useTheme.ts).
- Mechanical, table-driven migration (scripts/migrate-color-tokens.mjs,
scripts/tokenize-status-colors.mjs, scripts/darken-status-colors.mjs) of
every raw neutral/gray/slate + emerald/red/amber Tailwind utility across
client/src onto the new tokens, so every badge/button/component pulls the
same shade per status/role instead of each picking its own.
- Palette values are the literal Radix Colors (radix-ui.com/colors) scale
constants — slate/blue/green/red/amber steps 1-12 — adopted after three
rounds of hand-picked values that kept overshooting (flat, then too dark,
then glaring); see docs/superpowers/specs/2026-07-31-color-redesign-
dark-light-mode-design.md for the full history and role mapping.
- PipelineMap: done/current/failed/passed-no-evidence/detected share one
visual language (border + text + translucent wash of the same status
color); `current` alone stays a solid accent fill, the one state that
gets to look bolder ("you are here").
- LaneCard: removed the stage/kind/auto-stage chips that duplicated the
Workspace lane-detail header already showing them.
Categorical/decorative hues (violet, indigo, cyan, teal, sky, rose, pink,
orange, yellow, and blue where it plays a role-coloring part e.g. message
bubbles) are deliberately out of scope — collapsing those onto shared
tokens would erase the distinction between different kinds of thing, not
a status.
This commit is contained in:
+112
-108
@@ -278,28 +278,28 @@ function SystemHealthTab() {
|
||||
<div className="card p-5 flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Server className="w-4 h-4 text-emerald-400" />
|
||||
<span className="text-xs text-gray-500 uppercase tracking-wider">Runtime</span>
|
||||
<Server className="w-4 h-4 text-status-success" />
|
||||
<span className="text-xs text-fg-muted uppercase tracking-wider">Runtime</span>
|
||||
</div>
|
||||
<span className="text-[10px] font-mono text-gray-500">
|
||||
<span className="text-[10px] font-mono text-fg-muted">
|
||||
{info.server.cpus} cores · {info.server.arch}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2.5">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs text-gray-400 w-28 flex-shrink-0">Uptime</span>
|
||||
<span className="text-xs text-gray-200 font-mono ml-auto">
|
||||
<span className="text-xs text-fg-secondary w-28 flex-shrink-0">Uptime</span>
|
||||
<span className="text-xs text-fg-secondary font-mono ml-auto">
|
||||
{formatUptime(info.server.uptime)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs text-gray-400 w-28 flex-shrink-0">CPU (1/5/15m)</span>
|
||||
<span className="text-xs text-fg-secondary w-28 flex-shrink-0">CPU (1/5/15m)</span>
|
||||
<div className="flex gap-1 ml-auto">
|
||||
{(info.server.cpu_load || []).slice(0, 3).map((load, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className={`text-[10px] font-mono px-1.5 py-0.5 rounded ${i === 0 && load > info.server.cpus ? "bg-red-500/20 text-red-400" : "bg-surface-3 text-gray-300"}`}
|
||||
className={`text-[10px] font-mono px-1.5 py-0.5 rounded ${i === 0 && load > info.server.cpus ? "bg-status-danger/20 text-status-danger" : "bg-surface-3 text-fg-secondary"}`}
|
||||
>
|
||||
{load.toFixed(2)}
|
||||
</span>
|
||||
@@ -307,8 +307,8 @@ function SystemHealthTab() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs text-gray-400 w-28 flex-shrink-0">Node RSS</span>
|
||||
<span className="text-xs text-gray-200 font-mono ml-auto">
|
||||
<span className="text-xs text-fg-secondary w-28 flex-shrink-0">Node RSS</span>
|
||||
<span className="text-xs text-fg-secondary font-mono ml-auto">
|
||||
{formatBytes(info.server.memory.rss)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -321,12 +321,12 @@ function SystemHealthTab() {
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between text-[10px]">
|
||||
<span className="text-gray-500">Host Memory</span>
|
||||
<span className="text-gray-400 font-mono">{memUsedPct.toFixed(0)}%</span>
|
||||
<span className="text-fg-muted">Host Memory</span>
|
||||
<span className="text-fg-secondary font-mono">{memUsedPct.toFixed(0)}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-surface-3 rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full transition-all duration-700 ${memUsedPct > 90 ? "bg-red-500" : memUsedPct > 70 ? "bg-amber-500" : "bg-emerald-500"}`}
|
||||
className={`h-2 rounded-full transition-all duration-700 ${memUsedPct > 90 ? "bg-status-danger" : memUsedPct > 70 ? "bg-status-warning" : "bg-status-success"}`}
|
||||
style={{ width: `${memUsedPct}%` }}
|
||||
/>
|
||||
</div>
|
||||
@@ -338,12 +338,12 @@ function SystemHealthTab() {
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between text-[10px]">
|
||||
<span className="text-gray-500">V8 Heap</span>
|
||||
<span className="text-gray-400 font-mono">{heapUsedPct.toFixed(0)}%</span>
|
||||
<span className="text-fg-muted">V8 Heap</span>
|
||||
<span className="text-fg-secondary font-mono">{heapUsedPct.toFixed(0)}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-surface-3 rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full transition-all duration-700 ${heapUsedPct > 85 ? "bg-red-500" : heapUsedPct > 60 ? "bg-amber-500" : "bg-blue-500"}`}
|
||||
className={`h-2 rounded-full transition-all duration-700 ${heapUsedPct > 85 ? "bg-status-danger" : heapUsedPct > 60 ? "bg-status-warning" : "bg-blue-600"}`}
|
||||
style={{ width: `${heapUsedPct}%` }}
|
||||
/>
|
||||
</div>
|
||||
@@ -356,13 +356,13 @@ function SystemHealthTab() {
|
||||
<div className="card p-5 flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Database className="w-4 h-4 text-blue-400" />
|
||||
<span className="text-xs text-gray-500 uppercase tracking-wider">Storage</span>
|
||||
<Database className="w-4 h-4 text-blue-500" />
|
||||
<span className="text-xs text-fg-muted uppercase tracking-wider">Storage</span>
|
||||
</div>
|
||||
<Tip
|
||||
raw={`Write velocity (events):\n5 min: ${info.db.load_stats?.m5 ?? 0}\n15 min: ${info.db.load_stats?.m15 ?? 0}\n1 hr: ${info.db.load_stats?.h1 ?? 0}`}
|
||||
>
|
||||
<span className="text-[10px] font-mono text-emerald-400 bg-emerald-500/5 px-2 py-0.5 rounded border border-emerald-500/10 cursor-default">
|
||||
<span className="text-[10px] font-mono text-status-success bg-status-success/5 px-2 py-0.5 rounded border border-status-success/10 cursor-default">
|
||||
⚡ {info.db.load_stats?.m5 ?? 0}/{info.db.load_stats?.m15 ?? 0}/
|
||||
{info.db.load_stats?.h1 ?? 0}
|
||||
</span>
|
||||
@@ -370,8 +370,8 @@ function SystemHealthTab() {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs text-gray-400 w-28 flex-shrink-0">Database</span>
|
||||
<span className="text-xs text-gray-200 font-mono ml-auto">
|
||||
<span className="text-xs text-fg-secondary w-28 flex-shrink-0">Database</span>
|
||||
<span className="text-xs text-fg-secondary font-mono ml-auto">
|
||||
{formatBytes(info.db.size)} · {info.db.pragmas?.journal_mode?.toUpperCase() || "WAL"}
|
||||
</span>
|
||||
</div>
|
||||
@@ -425,7 +425,7 @@ function SystemHealthTab() {
|
||||
y="46"
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
className="fill-gray-300"
|
||||
className="fill-fg-secondary"
|
||||
fontSize="12"
|
||||
fontWeight="700"
|
||||
fontFamily="monospace"
|
||||
@@ -437,7 +437,7 @@ function SystemHealthTab() {
|
||||
y="60"
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
className="fill-gray-600"
|
||||
className="fill-fg-muted"
|
||||
fontSize="8"
|
||||
>
|
||||
total
|
||||
@@ -475,8 +475,8 @@ function SystemHealthTab() {
|
||||
className="w-2.5 h-2.5 rounded-sm flex-shrink-0"
|
||||
style={{ backgroundColor: item.color }}
|
||||
/>
|
||||
<span className="text-gray-400">{item.label}</span>
|
||||
<span className="text-gray-500 ml-auto pl-3 font-mono">
|
||||
<span className="text-fg-secondary">{item.label}</span>
|
||||
<span className="text-fg-muted ml-auto pl-3 font-mono">
|
||||
{Math.round(item.pct)}%
|
||||
</span>
|
||||
</div>
|
||||
@@ -490,13 +490,13 @@ function SystemHealthTab() {
|
||||
<div className="card p-5 flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<ShieldCheck className="w-4 h-4 text-emerald-400" />
|
||||
<span className="text-xs text-gray-500 uppercase tracking-wider">Health Score</span>
|
||||
<ShieldCheck className="w-4 h-4 text-status-success" />
|
||||
<span className="text-xs text-fg-muted uppercase tracking-wider">Health Score</span>
|
||||
</div>
|
||||
<Tip
|
||||
raw={`Composite Index formula:\n0.4 × Success Rate (${successRate.toFixed(1)}%)\n+ 0.25 × Cache Hit (${cacheHitRate.toFixed(1)}%)\n+ 0.25 × (100 − Error Rate) (${(100 - errorRate).toFixed(1)}%)\n+ 0.10 × (100 − Heap%) (${(100 - heapUsedPct).toFixed(1)}%)\n= ${healthScore.toFixed(1)}`}
|
||||
>
|
||||
<span className="text-[10px] text-gray-500 cursor-help border-b border-dashed border-gray-700">
|
||||
<span className="text-[10px] text-fg-muted cursor-help border-b border-dashed border-border-light">
|
||||
ⓘ Formula
|
||||
</span>
|
||||
</Tip>
|
||||
@@ -526,7 +526,7 @@ function SystemHealthTab() {
|
||||
y="57"
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
className="fill-gray-100"
|
||||
className="fill-fg-primary"
|
||||
fontSize="24"
|
||||
fontWeight="800"
|
||||
fontFamily="monospace"
|
||||
@@ -538,7 +538,7 @@ function SystemHealthTab() {
|
||||
y="76"
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
className="fill-gray-600"
|
||||
className="fill-fg-muted"
|
||||
fontSize="9"
|
||||
fontWeight="500"
|
||||
>
|
||||
@@ -555,8 +555,8 @@ function SystemHealthTab() {
|
||||
raw={`Cache Hit Rate: ${cacheHitRate.toFixed(1)}%\nHits: ${info.transcript_cache?.hits ?? 0}\nMisses: ${info.transcript_cache?.misses ?? 0}`}
|
||||
>
|
||||
<div className="text-center cursor-default">
|
||||
<p className="text-[9px] text-gray-600 uppercase">Cache</p>
|
||||
<p className="text-xs font-mono font-bold text-blue-400">
|
||||
<p className="text-[9px] text-fg-muted uppercase">Cache</p>
|
||||
<p className="text-xs font-mono font-bold text-blue-500">
|
||||
{cacheHitRate.toFixed(0)}%
|
||||
</p>
|
||||
</div>
|
||||
@@ -566,9 +566,9 @@ function SystemHealthTab() {
|
||||
raw={`Error Rate: ${errorRate.toFixed(2)}%\n<5% = healthy, 5-15% = warning, >15% = critical`}
|
||||
>
|
||||
<div className="text-center cursor-default">
|
||||
<p className="text-[9px] text-gray-600 uppercase">Errors</p>
|
||||
<p className="text-[9px] text-fg-muted uppercase">Errors</p>
|
||||
<p
|
||||
className={`text-xs font-mono font-bold ${errorRate < 5 ? "text-emerald-400" : errorRate < 15 ? "text-amber-400" : "text-red-400"}`}
|
||||
className={`text-xs font-mono font-bold ${errorRate < 5 ? "text-status-success" : errorRate < 15 ? "text-status-warning" : "text-status-danger"}`}
|
||||
>
|
||||
{errorRate.toFixed(1)}%
|
||||
</p>
|
||||
@@ -579,7 +579,7 @@ function SystemHealthTab() {
|
||||
raw={`Transcript compactions: ${workflow.compaction?.totalCompactions ?? 0}\nReduces context window by summarizing turns.`}
|
||||
>
|
||||
<div className="text-center cursor-default">
|
||||
<p className="text-[9px] text-gray-600 uppercase">Compact</p>
|
||||
<p className="text-[9px] text-fg-muted uppercase">Compact</p>
|
||||
<p className="text-xs font-mono font-bold text-violet-400">
|
||||
{workflow.compaction?.totalCompactions ?? 0}
|
||||
</p>
|
||||
@@ -590,8 +590,8 @@ function SystemHealthTab() {
|
||||
raw={`Tokens recovered: ${(workflow.compaction?.tokensRecovered ?? 0).toLocaleString()}\nFreed by compaction.`}
|
||||
>
|
||||
<div className="text-center cursor-default">
|
||||
<p className="text-[9px] text-gray-600 uppercase">Saved</p>
|
||||
<p className="text-xs font-mono font-bold text-emerald-400">
|
||||
<p className="text-[9px] text-fg-muted uppercase">Saved</p>
|
||||
<p className="text-xs font-mono font-bold text-status-success">
|
||||
{((workflow.compaction?.tokensRecovered ?? 0) / 1000).toFixed(1)}K
|
||||
</p>
|
||||
</div>
|
||||
@@ -606,10 +606,10 @@ function SystemHealthTab() {
|
||||
<div className="card p-5 flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Bot className="w-4 h-4 text-blue-400" />
|
||||
<span className="text-xs text-gray-500 uppercase tracking-wider">Token Usage</span>
|
||||
<Bot className="w-4 h-4 text-blue-500" />
|
||||
<span className="text-xs text-fg-muted uppercase tracking-wider">Token Usage</span>
|
||||
</div>
|
||||
<span className="text-[10px] font-mono text-gray-500">
|
||||
<span className="text-[10px] font-mono text-fg-muted">
|
||||
{(totalTokens / 1000).toFixed(1)}K total
|
||||
</span>
|
||||
</div>
|
||||
@@ -619,10 +619,10 @@ function SystemHealthTab() {
|
||||
const pct =
|
||||
totalTokens > 0 ? ((m.input_tokens + m.output_tokens) / totalTokens) * 100 : 0;
|
||||
const colors = [
|
||||
"bg-blue-400",
|
||||
"bg-blue-500",
|
||||
"bg-violet-400",
|
||||
"bg-emerald-400",
|
||||
"bg-amber-400",
|
||||
"bg-status-success",
|
||||
"bg-status-warning",
|
||||
"bg-pink-400",
|
||||
"bg-cyan-400",
|
||||
];
|
||||
@@ -634,7 +634,7 @@ function SystemHealthTab() {
|
||||
>
|
||||
<div className="flex items-center gap-3 cursor-default">
|
||||
<span
|
||||
className="text-xs text-gray-400 w-28 truncate flex-shrink-0"
|
||||
className="text-xs text-fg-secondary w-28 truncate flex-shrink-0"
|
||||
title={formatModelName(m.model) ?? m.model}
|
||||
>
|
||||
{formatModelName(m.model) ?? m.model}
|
||||
@@ -645,7 +645,7 @@ function SystemHealthTab() {
|
||||
style={{ width: `${pct}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-xs text-gray-500 w-12 text-right flex-shrink-0 font-mono">
|
||||
<span className="text-xs text-fg-muted w-12 text-right flex-shrink-0 font-mono">
|
||||
{pct.toFixed(1)}%
|
||||
</span>
|
||||
</div>
|
||||
@@ -653,7 +653,7 @@ function SystemHealthTab() {
|
||||
);
|
||||
})}
|
||||
{modelStats.length === 0 && (
|
||||
<p className="text-xs text-gray-600 text-center py-4">No model data</p>
|
||||
<p className="text-xs text-fg-muted text-center py-4">No model data</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -663,9 +663,9 @@ function SystemHealthTab() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<BarChart3 className="w-4 h-4 text-violet-400" />
|
||||
<span className="text-xs text-gray-500 uppercase tracking-wider">Concurrency</span>
|
||||
<span className="text-xs text-fg-muted uppercase tracking-wider">Concurrency</span>
|
||||
</div>
|
||||
<span className="text-[10px] font-mono text-gray-500">{lanes.length} intervals</span>
|
||||
<span className="text-[10px] font-mono text-fg-muted">{lanes.length} intervals</span>
|
||||
</div>
|
||||
|
||||
{/* Sparkline-style bar chart matching Analytics sparkline */}
|
||||
@@ -698,7 +698,7 @@ function SystemHealthTab() {
|
||||
);
|
||||
})}
|
||||
{lanes.length === 0 && (
|
||||
<p className="text-xs text-gray-600 text-center w-full self-center">
|
||||
<p className="text-xs text-fg-muted text-center w-full self-center">
|
||||
No concurrency data
|
||||
</p>
|
||||
)}
|
||||
@@ -708,8 +708,8 @@ function SystemHealthTab() {
|
||||
<div className="grid grid-cols-3 gap-3 border-t border-border/40 pt-3">
|
||||
<Tip block raw={`Peak: ${maxLaneCount} sessions running simultaneously.`}>
|
||||
<div className="text-center cursor-default">
|
||||
<p className="text-[9px] text-gray-600 uppercase">Peak</p>
|
||||
<p className="text-sm font-mono font-bold text-gray-200">{maxLaneCount}</p>
|
||||
<p className="text-[9px] text-fg-muted uppercase">Peak</p>
|
||||
<p className="text-sm font-mono font-bold text-fg-secondary">{maxLaneCount}</p>
|
||||
</div>
|
||||
</Tip>
|
||||
<Tip
|
||||
@@ -717,16 +717,16 @@ function SystemHealthTab() {
|
||||
raw={`${lanes.filter((l) => l.count > 0).length} of ${lanes.length} intervals have active sessions.`}
|
||||
>
|
||||
<div className="text-center cursor-default">
|
||||
<p className="text-[9px] text-gray-600 uppercase">Active</p>
|
||||
<p className="text-sm font-mono font-bold text-emerald-400">
|
||||
<p className="text-[9px] text-fg-muted uppercase">Active</p>
|
||||
<p className="text-sm font-mono font-bold text-status-success">
|
||||
{lanes.filter((l) => l.count > 0).length}
|
||||
</p>
|
||||
</div>
|
||||
</Tip>
|
||||
<Tip block raw={`Average concurrency across all intervals.`}>
|
||||
<div className="text-center cursor-default">
|
||||
<p className="text-[9px] text-gray-600 uppercase">Avg</p>
|
||||
<p className="text-sm font-mono font-bold text-blue-400">
|
||||
<p className="text-[9px] text-fg-muted uppercase">Avg</p>
|
||||
<p className="text-sm font-mono font-bold text-blue-500">
|
||||
{lanes.length > 0
|
||||
? (lanes.reduce((s, l) => s + l.count, 0) / lanes.length).toFixed(1)
|
||||
: "0"}
|
||||
@@ -743,23 +743,23 @@ function SystemHealthTab() {
|
||||
<div className="card p-5 flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Zap className="w-4 h-4 text-amber-400" />
|
||||
<span className="text-xs text-gray-500 uppercase tracking-wider">Tool Usage</span>
|
||||
<Zap className="w-4 h-4 text-status-warning" />
|
||||
<span className="text-xs text-fg-muted uppercase tracking-wider">Tool Usage</span>
|
||||
</div>
|
||||
<span className="text-[10px] font-mono text-gray-500">top {topTools.length}</span>
|
||||
<span className="text-[10px] font-mono text-fg-muted">top {topTools.length}</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{topTools.map((tool, i) => {
|
||||
const pct = maxToolCount > 0 ? Math.round((tool.count / maxToolCount) * 100) : 0;
|
||||
const colors = [
|
||||
"bg-amber-400",
|
||||
"bg-blue-400",
|
||||
"bg-emerald-400",
|
||||
"bg-status-warning",
|
||||
"bg-blue-500",
|
||||
"bg-status-success",
|
||||
"bg-violet-400",
|
||||
"bg-pink-400",
|
||||
"bg-cyan-400",
|
||||
"bg-red-400",
|
||||
"bg-status-danger",
|
||||
"bg-indigo-400",
|
||||
];
|
||||
return (
|
||||
@@ -770,7 +770,7 @@ function SystemHealthTab() {
|
||||
>
|
||||
<div className="flex items-center gap-3 cursor-default">
|
||||
<span
|
||||
className="text-xs text-gray-400 w-28 truncate flex-shrink-0"
|
||||
className="text-xs text-fg-secondary w-28 truncate flex-shrink-0"
|
||||
title={tool.tool_name}
|
||||
>
|
||||
{tool.tool_name}
|
||||
@@ -781,7 +781,7 @@ function SystemHealthTab() {
|
||||
style={{ width: `${pct}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-xs text-gray-500 w-10 text-right flex-shrink-0 font-mono">
|
||||
<span className="text-xs text-fg-muted w-10 text-right flex-shrink-0 font-mono">
|
||||
{tool.count > 999 ? `${(tool.count / 1000).toFixed(1)}K` : tool.count}
|
||||
</span>
|
||||
</div>
|
||||
@@ -789,7 +789,7 @@ function SystemHealthTab() {
|
||||
);
|
||||
})}
|
||||
{topTools.length === 0 && (
|
||||
<p className="text-xs text-gray-600 text-center py-6">No tool data yet</p>
|
||||
<p className="text-xs text-fg-muted text-center py-6">No tool data yet</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -799,7 +799,7 @@ function SystemHealthTab() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<GitBranch className="w-4 h-4 text-violet-400" />
|
||||
<span className="text-xs text-gray-500 uppercase tracking-wider">
|
||||
<span className="text-xs text-fg-muted uppercase tracking-wider">
|
||||
Subagent Effectiveness
|
||||
</span>
|
||||
</div>
|
||||
@@ -809,10 +809,10 @@ function SystemHealthTab() {
|
||||
{effectiveness.map((item, i) => {
|
||||
const color =
|
||||
item.successRate >= 90
|
||||
? "bg-emerald-400"
|
||||
? "bg-status-success"
|
||||
: item.successRate >= 70
|
||||
? "bg-amber-400"
|
||||
: "bg-red-400";
|
||||
? "bg-status-warning"
|
||||
: "bg-status-danger";
|
||||
return (
|
||||
<Tip
|
||||
block
|
||||
@@ -820,7 +820,7 @@ function SystemHealthTab() {
|
||||
raw={`${item.subagent_type || "default"}\nSuccess: ${item.successRate.toFixed(1)}%\nTotal: ${item.total} · OK: ${item.completed} · Errors: ${item.errors}`}
|
||||
>
|
||||
<div className="flex items-center gap-3 cursor-default">
|
||||
<span className="text-xs text-gray-400 w-28 truncate flex-shrink-0">
|
||||
<span className="text-xs text-fg-secondary w-28 truncate flex-shrink-0">
|
||||
{item.subagent_type || "default"}
|
||||
</span>
|
||||
<div className="flex-1 bg-surface-3 rounded-full h-2">
|
||||
@@ -830,7 +830,7 @@ function SystemHealthTab() {
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
className={`text-xs w-12 text-right flex-shrink-0 font-mono ${item.successRate >= 90 ? "text-emerald-400" : item.successRate >= 70 ? "text-amber-400" : "text-red-400"}`}
|
||||
className={`text-xs w-12 text-right flex-shrink-0 font-mono ${item.successRate >= 90 ? "text-status-success" : item.successRate >= 70 ? "text-status-warning" : "text-status-danger"}`}
|
||||
>
|
||||
{item.successRate.toFixed(0)}%
|
||||
</span>
|
||||
@@ -839,7 +839,7 @@ function SystemHealthTab() {
|
||||
);
|
||||
})}
|
||||
{effectiveness.length === 0 && (
|
||||
<p className="text-xs text-gray-600 text-center py-6">No subagent data yet</p>
|
||||
<p className="text-xs text-fg-muted text-center py-6">No subagent data yet</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -851,11 +851,11 @@ function SystemHealthTab() {
|
||||
<div className="card p-5 flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Plug className="w-4 h-4 text-amber-400" />
|
||||
<span className="text-xs text-gray-500 uppercase tracking-wider">Integration</span>
|
||||
<Plug className="w-4 h-4 text-status-warning" />
|
||||
<span className="text-xs text-fg-muted uppercase tracking-wider">Integration</span>
|
||||
</div>
|
||||
<span
|
||||
className={`text-[10px] font-mono px-2 py-0.5 rounded ${info.hooks.installed ? "bg-emerald-500/10 text-emerald-400 border border-emerald-500/20" : "bg-surface-3 text-gray-500 border border-border"}`}
|
||||
className={`text-[10px] font-mono px-2 py-0.5 rounded ${info.hooks.installed ? "bg-status-success/10 text-status-success border border-status-success/20" : "bg-surface-3 text-fg-muted border border-border"}`}
|
||||
>
|
||||
{info.hooks.installed ? "Active" : "Offline"}
|
||||
</span>
|
||||
@@ -871,9 +871,9 @@ function SystemHealthTab() {
|
||||
>
|
||||
<div className="flex items-center gap-3 bg-surface-2/50 px-3 py-2 rounded-lg border border-border/30 cursor-default">
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full flex-shrink-0 ${active ? "bg-emerald-400" : "bg-gray-600"}`}
|
||||
className={`w-2 h-2 rounded-full flex-shrink-0 ${active ? "bg-status-success" : "bg-surface-4"}`}
|
||||
/>
|
||||
<span className="text-xs text-gray-300 truncate font-mono">
|
||||
<span className="text-xs text-fg-secondary truncate font-mono">
|
||||
{cwd.split("/").pop() || cwd}
|
||||
</span>
|
||||
</div>
|
||||
@@ -882,8 +882,8 @@ function SystemHealthTab() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-6 border border-dashed border-border/40 rounded-lg">
|
||||
<Search className="w-4 h-4 text-gray-600 mb-2" />
|
||||
<p className="text-xs text-gray-500">No project hooks registered</p>
|
||||
<Search className="w-4 h-4 text-fg-muted mb-2" />
|
||||
<p className="text-xs text-fg-muted">No project hooks registered</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -891,11 +891,11 @@ function SystemHealthTab() {
|
||||
block
|
||||
raw={`WebSocket connections: ${info.server.ws_connections}\nProtocol: RFC 6455`}
|
||||
>
|
||||
<div className="flex items-center gap-3 bg-emerald-500/5 px-3 py-2.5 rounded-lg border border-emerald-500/10 cursor-default">
|
||||
<Activity className="w-3.5 h-3.5 text-emerald-400 animate-pulse" />
|
||||
<div className="flex items-center gap-3 bg-status-success/5 px-3 py-2.5 rounded-lg border border-status-success/10 cursor-default">
|
||||
<Activity className="w-3.5 h-3.5 text-status-success animate-pulse" />
|
||||
<div>
|
||||
<p className="text-[10px] text-emerald-400 font-medium">WebSocket Active</p>
|
||||
<p className="text-[10px] text-gray-500">
|
||||
<p className="text-[10px] text-status-success font-medium">WebSocket Active</p>
|
||||
<p className="text-[10px] text-fg-muted">
|
||||
{info.server.ws_connections} connection
|
||||
{info.server.ws_connections !== 1 ? "s" : ""}
|
||||
</p>
|
||||
@@ -909,9 +909,9 @@ function SystemHealthTab() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Cpu className="w-4 h-4 text-cyan-400" />
|
||||
<span className="text-xs text-gray-500 uppercase tracking-wider">Platform</span>
|
||||
<span className="text-xs text-fg-muted uppercase tracking-wider">Platform</span>
|
||||
</div>
|
||||
<span className="text-[10px] font-mono text-gray-500">{info.server.node_version}</span>
|
||||
<span className="text-[10px] font-mono text-fg-muted">{info.server.node_version}</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
@@ -935,16 +935,18 @@ function SystemHealthTab() {
|
||||
{ label: "Platform", value: `${info.server.platform} / ${info.server.arch}` },
|
||||
].map((row) => (
|
||||
<div key={row.label} className="flex items-center gap-3">
|
||||
<span className="text-xs text-gray-400 w-28 flex-shrink-0">{row.label}</span>
|
||||
<span className="text-xs text-gray-200 font-mono ml-auto">{row.value}</span>
|
||||
<span className="text-xs text-fg-secondary w-28 flex-shrink-0">{row.label}</span>
|
||||
<span className="text-xs text-fg-secondary font-mono ml-auto">{row.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Tip block raw={info.db.path}>
|
||||
<div className="flex items-center gap-2 bg-surface-2/50 px-3 py-2 rounded-lg border border-border/30 cursor-default">
|
||||
<HardDrive className="w-3 h-3 text-gray-500 flex-shrink-0" />
|
||||
<span className="text-[10px] text-gray-400 font-mono truncate">{info.db.path}</span>
|
||||
<HardDrive className="w-3 h-3 text-fg-muted flex-shrink-0" />
|
||||
<span className="text-[10px] text-fg-secondary font-mono truncate">
|
||||
{info.db.path}
|
||||
</span>
|
||||
</div>
|
||||
</Tip>
|
||||
</div>
|
||||
@@ -1145,8 +1147,8 @@ export function Dashboard() {
|
||||
if (error) {
|
||||
return (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-red-400 mb-2">{t("failedConnect")}</p>
|
||||
<p className="text-sm text-gray-500">{error}</p>
|
||||
<p className="text-status-danger mb-2">{t("failedConnect")}</p>
|
||||
<p className="text-sm text-fg-muted">{error}</p>
|
||||
<button onClick={load} className="btn-primary mt-4">
|
||||
{t("common:retry")}
|
||||
</button>
|
||||
@@ -1163,20 +1165,20 @@ export function Dashboard() {
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="text-lg font-semibold text-gray-100">{t("title")}</h1>
|
||||
<h1 className="text-lg font-semibold text-fg-primary">{t("title")}</h1>
|
||||
{wsConnected ? (
|
||||
<span className="flex items-center gap-1.5 text-[11px] text-emerald-400 bg-emerald-500/10 border border-emerald-500/20 px-2 py-0.5 rounded-full">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse-dot" />
|
||||
<span className="flex items-center gap-1.5 text-[11px] text-status-success bg-status-success/10 border border-status-success/20 px-2 py-0.5 rounded-full">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-status-success animate-pulse-dot" />
|
||||
{t("common:live")}
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center gap-1.5 text-[11px] text-gray-400 bg-gray-500/10 border border-gray-500/20 px-2 py-0.5 rounded-full">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-gray-400" />
|
||||
<span className="flex items-center gap-1.5 text-[11px] text-fg-secondary bg-surface-4/10 border border-border-light/20 px-2 py-0.5 rounded-full">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-surface-4" />
|
||||
{t("common:offline")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">{t("subtitle")}</p>
|
||||
<p className="text-xs text-fg-muted">{t("subtitle")}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -1187,7 +1189,7 @@ export function Dashboard() {
|
||||
className={`px-2.5 py-1.5 rounded-md text-xs font-medium transition-all flex items-center gap-2 ${
|
||||
activeTab === "monitor"
|
||||
? "bg-accent/15 text-accent shadow-sm"
|
||||
: "text-gray-500 hover:text-gray-300"
|
||||
: "text-fg-muted hover:text-fg-secondary"
|
||||
}`}
|
||||
>
|
||||
<Activity className="w-3.5 h-3.5" /> Monitor
|
||||
@@ -1197,7 +1199,7 @@ export function Dashboard() {
|
||||
className={`px-2.5 py-1.5 rounded-md text-xs font-medium transition-all flex items-center gap-2 ${
|
||||
activeTab === "health"
|
||||
? "bg-accent/15 text-accent shadow-sm"
|
||||
: "text-gray-500 hover:text-gray-300"
|
||||
: "text-fg-muted hover:text-fg-secondary"
|
||||
}`}
|
||||
>
|
||||
<Server className="w-3.5 h-3.5" /> Health
|
||||
@@ -1225,7 +1227,7 @@ export function Dashboard() {
|
||||
label={t("activeAgents")}
|
||||
value={stats?.active_agents ?? ""}
|
||||
icon={Bot}
|
||||
accentColor="text-emerald-400"
|
||||
accentColor="text-status-success"
|
||||
loading={!stats}
|
||||
/>
|
||||
<StatCard
|
||||
@@ -1261,7 +1263,7 @@ export function Dashboard() {
|
||||
: undefined
|
||||
}
|
||||
icon={DollarSign}
|
||||
accentColor="text-emerald-400"
|
||||
accentColor="text-status-success"
|
||||
loading={totalCost === null}
|
||||
/>
|
||||
</div>
|
||||
@@ -1270,7 +1272,9 @@ export function Dashboard() {
|
||||
{/* Active agents */}
|
||||
<div ref={agentsContainerRef} className="min-w-0 overflow-y-auto pr-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-sm font-medium text-gray-300">{t("activeAgentsSection")}</h3>
|
||||
<h3 className="text-sm font-medium text-fg-secondary">
|
||||
{t("activeAgentsSection")}
|
||||
</h3>
|
||||
<button onClick={() => navigate("/kanban")} className="btn-ghost text-xs">
|
||||
{t("viewBoard")} <ArrowRight className="w-3 h-3" />
|
||||
</button>
|
||||
@@ -1312,7 +1316,7 @@ export function Dashboard() {
|
||||
{hasChildren && (
|
||||
<button
|
||||
onClick={toggleExpanded}
|
||||
className="p-1 text-gray-500 hover:text-gray-300 transition-colors flex-shrink-0"
|
||||
className="p-1 text-fg-muted hover:text-fg-secondary transition-colors flex-shrink-0"
|
||||
aria-label={isExpanded ? "Collapse subagents" : "Expand subagents"}
|
||||
aria-expanded={isExpanded}
|
||||
>
|
||||
@@ -1375,7 +1379,7 @@ export function Dashboard() {
|
||||
>
|
||||
{t("common:subagent_label", { count: totalDesc })}
|
||||
{activeDesc > 0 && (
|
||||
<span className="text-emerald-400 ml-1">
|
||||
<span className="text-status-success ml-1">
|
||||
({activeDesc} {t("common:active")})
|
||||
</span>
|
||||
)}
|
||||
@@ -1436,7 +1440,7 @@ export function Dashboard() {
|
||||
{/* Recent activity */}
|
||||
<div ref={activityContainerRef} className="min-w-0 overflow-y-auto pl-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-sm font-medium text-gray-300">{t("recentActivity")}</h3>
|
||||
<h3 className="text-sm font-medium text-fg-secondary">{t("recentActivity")}</h3>
|
||||
<button onClick={() => navigate("/activity")} className="btn-ghost text-xs">
|
||||
{t("viewAll")} <ArrowRight className="w-3 h-3" />
|
||||
</button>
|
||||
@@ -1469,7 +1473,7 @@ export function Dashboard() {
|
||||
: "waiting"
|
||||
}
|
||||
/>
|
||||
<span className="text-sm text-gray-300 truncate flex-1">
|
||||
<span className="text-sm text-fg-secondary truncate flex-1">
|
||||
{event.summary || event.event_type}
|
||||
</span>
|
||||
{(() => {
|
||||
@@ -1479,7 +1483,7 @@ export function Dashboard() {
|
||||
const isAuto = /^Session [0-9a-f]{8}$/i.test(sname);
|
||||
return (
|
||||
<span
|
||||
className="text-[11px] text-gray-500 truncate max-w-[9rem] flex-shrink-0"
|
||||
className="text-[11px] text-fg-muted truncate max-w-[9rem] flex-shrink-0"
|
||||
title={event.session_id}
|
||||
>
|
||||
{sname && !isAuto ? (
|
||||
@@ -1491,11 +1495,11 @@ export function Dashboard() {
|
||||
);
|
||||
})()}
|
||||
{event.tool_name && (
|
||||
<span className="text-[11px] text-gray-500 font-mono">
|
||||
<span className="text-[11px] text-fg-muted font-mono">
|
||||
{event.tool_name}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-[11px] text-gray-600 flex-shrink-0">
|
||||
<span className="text-[11px] text-fg-muted flex-shrink-0">
|
||||
{timeAgo(event.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user