groove-dev 0.24.0 → 0.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/node_modules/@groove-dev/gui/dist/assets/index-AXS9JOJD.css +1 -0
  2. package/node_modules/@groove-dev/gui/dist/assets/index-z5gtKpHQ.js +587 -0
  3. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  4. package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +9 -9
  5. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +10 -13
  6. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +13 -14
  7. package/node_modules/@groove-dev/gui/src/components/dashboard/header-bar.jsx +11 -11
  8. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +44 -44
  9. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +7 -7
  10. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +13 -17
  11. package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +48 -25
  12. package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +18 -39
  13. package/package.json +1 -1
  14. package/packages/gui/dist/assets/index-AXS9JOJD.css +1 -0
  15. package/packages/gui/dist/assets/index-z5gtKpHQ.js +587 -0
  16. package/packages/gui/dist/index.html +2 -2
  17. package/packages/gui/src/components/dashboard/activity-feed.jsx +9 -9
  18. package/packages/gui/src/components/dashboard/cache-ring.jsx +10 -13
  19. package/packages/gui/src/components/dashboard/fleet-panel.jsx +13 -14
  20. package/packages/gui/src/components/dashboard/header-bar.jsx +11 -11
  21. package/packages/gui/src/components/dashboard/intel-panel.jsx +44 -44
  22. package/packages/gui/src/components/dashboard/kpi-card.jsx +7 -7
  23. package/packages/gui/src/components/dashboard/routing-chart.jsx +13 -17
  24. package/packages/gui/src/components/dashboard/token-chart.jsx +48 -25
  25. package/packages/gui/src/views/dashboard.jsx +18 -39
  26. package/node_modules/@groove-dev/gui/dist/assets/index-CqdQP7yG.js +0 -587
  27. package/node_modules/@groove-dev/gui/dist/assets/index-DvcNOnKP.css +0 -1
  28. package/packages/gui/dist/assets/index-CqdQP7yG.js +0 -587
  29. package/packages/gui/dist/assets/index-DvcNOnKP.css +0 -1
@@ -5,12 +5,12 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" type="image/png" href="/favicon.png" />
7
7
  <title>Groove GUI</title>
8
- <script type="module" crossorigin src="/assets/index-CqdQP7yG.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-z5gtKpHQ.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/vendor-C0HXlhrU.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/reactflow-BQPfi37R.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/codemirror-BBL3i_JW.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/xterm--7_ns2zW.js">
13
- <link rel="stylesheet" crossorigin href="/assets/index-DvcNOnKP.css">
13
+ <link rel="stylesheet" crossorigin href="/assets/index-AXS9JOJD.css">
14
14
  </head>
15
15
  <body>
16
16
  <div id="root"></div>
@@ -12,17 +12,17 @@ const ICONS = {
12
12
  };
13
13
 
14
14
  const ICON_COLORS = {
15
- spawn: '#33afbc',
16
- rotation: '#c678dd',
17
- completion: '#4ae168',
18
- error: '#e06c75',
19
- default: '#505862',
15
+ spawn: 'text-accent',
16
+ rotation: 'text-purple',
17
+ completion: 'text-success',
18
+ error: 'text-danger',
19
+ default: 'text-text-3',
20
20
  };
21
21
 
22
22
  const ActivityFeed = memo(function ActivityFeed({ events = [] }) {
23
23
  if (!events.length) {
24
24
  return (
25
- <div className="text-[9px] text-[#3a3f4b] font-mono py-2.5 text-center">
25
+ <div className="text-xs text-text-3 font-mono py-2.5 text-center">
26
26
  No recent activity
27
27
  </div>
28
28
  );
@@ -35,9 +35,9 @@ const ActivityFeed = memo(function ActivityFeed({ events = [] }) {
35
35
  const color = ICON_COLORS[event.type] || ICON_COLORS.default;
36
36
  return (
37
37
  <div key={i} className="flex items-center gap-1.5 flex-shrink-0">
38
- <Icon size={10} style={{ color }} />
39
- <span className="text-[9px] font-sans text-[#6e7681] whitespace-nowrap">{event.text}</span>
40
- <span className="text-[8px] font-mono text-[#3a3f4b]">{timeAgo(event.timestamp || event.t)}</span>
38
+ <Icon size={11} className={color} />
39
+ <span className="text-xs font-sans text-text-2 whitespace-nowrap">{event.text}</span>
40
+ <span className="text-2xs font-mono text-text-4">{timeAgo(event.timestamp || event.t)}</span>
41
41
  </div>
42
42
  );
43
43
  })}
@@ -1,7 +1,7 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
2
  import { useRef, useEffect, memo } from 'react';
3
- import { HEX, hexAlpha } from '../../lib/theme-hex';
4
- import { fmtNum, fmtPct } from '../../lib/format';
3
+ import { HEX } from '../../lib/theme-hex';
4
+ import { fmtNum } from '../../lib/format';
5
5
 
6
6
  const CacheRing = memo(function CacheRing({ cacheRead = 0, cacheCreation = 0, totalInput = 0, size = 140 }) {
7
7
  const canvasRef = useRef(null);
@@ -23,7 +23,6 @@ const CacheRing = memo(function CacheRing({ cacheRead = 0, cacheCreation = 0, to
23
23
  const radius = (size - 12) / 2;
24
24
  const strokeWidth = 5;
25
25
 
26
- // Arc spans 270 degrees (135° to 405°)
27
26
  const startAngle = (135 * Math.PI) / 180;
28
27
  const endAngle = (405 * Math.PI) / 180;
29
28
  const sweep = endAngle - startAngle;
@@ -40,7 +39,6 @@ const CacheRing = memo(function CacheRing({ cacheRead = 0, cacheCreation = 0, to
40
39
  const readPct = cacheRead / total;
41
40
  const createPct = cacheCreation / total;
42
41
 
43
- // Segment 1: Cache Read (accent)
44
42
  if (readPct > 0) {
45
43
  const segEnd = startAngle + sweep * readPct;
46
44
  ctx.beginPath();
@@ -51,7 +49,6 @@ const CacheRing = memo(function CacheRing({ cacheRead = 0, cacheCreation = 0, to
51
49
  ctx.stroke();
52
50
  }
53
51
 
54
- // Segment 2: Cache Creation (purple)
55
52
  if (createPct > 0) {
56
53
  const segStart = startAngle + sweep * readPct;
57
54
  const segEnd = segStart + sweep * createPct;
@@ -64,16 +61,16 @@ const CacheRing = memo(function CacheRing({ cacheRead = 0, cacheCreation = 0, to
64
61
  }
65
62
  }
66
63
 
67
- // Center text — hit rate
64
+ // Center text
68
65
  ctx.textAlign = 'center';
69
66
  ctx.textBaseline = 'middle';
70
- ctx.font = `600 ${size * 0.18}px 'JetBrains Mono Variable', monospace`;
67
+ ctx.font = `600 ${size * 0.2}px 'JetBrains Mono Variable', monospace`;
71
68
  ctx.fillStyle = HEX.text0;
72
69
  ctx.fillText(`${Math.round(hitRate)}%`, cx, cy - 3);
73
70
 
74
- ctx.font = `500 ${size * 0.07}px 'JetBrains Mono Variable', monospace`;
75
- ctx.fillStyle = HEX.text4;
76
- ctx.fillText('CACHE', cx, cy + size * 0.12);
71
+ ctx.font = `500 ${size * 0.08}px 'JetBrains Mono Variable', monospace`;
72
+ ctx.fillStyle = HEX.text3;
73
+ ctx.fillText('CACHE', cx, cy + size * 0.13);
77
74
  }, [cacheRead, cacheCreation, totalInput, size, total, hitRate]);
78
75
 
79
76
  return (
@@ -94,10 +91,10 @@ const CacheRing = memo(function CacheRing({ cacheRead = 0, cacheCreation = 0, to
94
91
 
95
92
  function StatRow({ color, label, value }) {
96
93
  return (
97
- <div className="flex items-center gap-2 text-[9px] font-mono">
94
+ <div className="flex items-center gap-2 text-xs font-mono">
98
95
  <span className="w-1.5 h-1.5 rounded-full flex-shrink-0" style={{ background: color }} />
99
- <span className="text-[#505862] uppercase tracking-wider flex-1">{label}</span>
100
- <span className="text-[#8b929e] tabular-nums">{value}</span>
96
+ <span className="text-text-3 uppercase tracking-wider flex-1">{label}</span>
97
+ <span className="text-text-1 tabular-nums">{value}</span>
101
98
  </div>
102
99
  );
103
100
  }
@@ -15,7 +15,7 @@ const AgentRow = memo(function AgentRow({ agent, isRotating }) {
15
15
  const successRate = quality?.toolSuccessRate != null ? Math.round(quality.toolSuccessRate * 100) : null;
16
16
 
17
17
  return (
18
- <div className="flex items-center gap-2 px-3 py-1.5 hover:bg-[#24282f] transition-colors">
18
+ <div className="flex items-center gap-2 px-3 py-1.5 hover:bg-surface-3 transition-colors">
19
19
  {/* Status square */}
20
20
  <span className="relative flex-shrink-0 w-[6px] h-[6px]">
21
21
  <span className="absolute inset-0 rounded-sm" style={{ background: sColor }} />
@@ -29,26 +29,26 @@ const AgentRow = memo(function AgentRow({ agent, isRotating }) {
29
29
 
30
30
  {/* Name + role/model */}
31
31
  <div className="flex-1 min-w-0">
32
- <div className="text-[11px] font-semibold text-[#e6e6e6] font-sans truncate leading-none">{agent.name}</div>
32
+ <div className="text-xs font-semibold text-text-0 font-sans truncate leading-none">{agent.name}</div>
33
33
  <div className="flex items-center gap-1 mt-0.5">
34
- <span className="text-[8px] font-mono text-[#505862] uppercase tracking-wider">{agent.role}</span>
35
- <span className="text-[8px] text-[#2a2e36]">/</span>
36
- <span className="text-[8px] font-mono text-[#3a3f4b]">{shortModel(agent.model)}</span>
34
+ <span className="text-2xs font-mono text-text-3 uppercase tracking-wider">{agent.role}</span>
35
+ <span className="text-2xs text-text-4">/</span>
36
+ <span className="text-2xs font-mono text-text-3">{shortModel(agent.model)}</span>
37
37
  </div>
38
38
  </div>
39
39
 
40
40
  {/* Tokens + cost */}
41
41
  <div className="text-right flex-shrink-0">
42
- <div className="text-[11px] font-mono text-[#bcc2cd] tabular-nums leading-none">{fmtNum(agent.tokens || 0)}</div>
42
+ <div className="text-xs font-mono text-text-1 tabular-nums leading-none">{fmtNum(agent.tokens || 0)}</div>
43
43
  {(agent.costUsd || 0) > 0 && (
44
- <div className="text-[8px] font-mono text-[#3a3f4b] mt-0.5">{fmtDollar(agent.costUsd)}</div>
44
+ <div className="text-2xs font-mono text-text-3 mt-0.5">{fmtDollar(agent.costUsd)}</div>
45
45
  )}
46
46
  </div>
47
47
 
48
48
  {/* Quality / tool success */}
49
49
  {successRate != null && (
50
50
  <span
51
- className="text-[7px] font-mono font-bold uppercase px-1 py-px rounded-sm flex-shrink-0"
51
+ className="text-2xs font-mono font-bold uppercase px-1 py-px rounded-sm flex-shrink-0"
52
52
  style={{
53
53
  color: successRate >= 90 ? '#4ae168' : successRate >= 70 ? '#e5c07b' : '#e06c75',
54
54
  background: successRate >= 90 ? 'rgba(74,225,104,0.1)' : successRate >= 70 ? 'rgba(229,192,123,0.1)' : 'rgba(224,108,117,0.1)',
@@ -60,7 +60,7 @@ const AgentRow = memo(function AgentRow({ agent, isRotating }) {
60
60
 
61
61
  {/* Cost source badge */}
62
62
  {agent.costSource && agent.costSource !== 'actual' && (
63
- <span className="text-[7px] font-mono text-[#3a3f4b] uppercase tracking-wider flex-shrink-0">
63
+ <span className="text-2xs font-mono text-text-4 uppercase tracking-wider flex-shrink-0">
64
64
  {COST_SOURCE_LABEL[agent.costSource] || ''}
65
65
  </span>
66
66
  )}
@@ -68,9 +68,9 @@ const AgentRow = memo(function AgentRow({ agent, isRotating }) {
68
68
  {/* Context bar */}
69
69
  <div className="w-12 flex-shrink-0">
70
70
  <div className="flex items-center justify-end gap-1 mb-0.5">
71
- <span className="text-[8px] font-mono text-[#505862] tabular-nums">{contextPct}%</span>
71
+ <span className="text-2xs font-mono text-text-2 tabular-nums">{contextPct}%</span>
72
72
  </div>
73
- <div className="h-[2px] bg-[#1a1e25] rounded-full overflow-hidden">
73
+ <div className="h-[2px] bg-surface-0 rounded-full overflow-hidden">
74
74
  <div
75
75
  className="h-full rounded-full transition-all duration-700"
76
76
  style={{
@@ -95,13 +95,12 @@ function shortModel(id) {
95
95
  const FleetPanel = memo(function FleetPanel({ agentBreakdown, rotating = [] }) {
96
96
  if (!agentBreakdown?.length) {
97
97
  return (
98
- <div className="flex-1 flex items-center justify-center text-[9px] text-[#3a3f4b] font-mono p-4">
98
+ <div className="flex-1 flex items-center justify-center text-xs text-text-3 font-mono p-4">
99
99
  No agents
100
100
  </div>
101
101
  );
102
102
  }
103
103
 
104
- // Group by team
105
104
  const teams = {};
106
105
  for (const a of agentBreakdown) {
107
106
  const team = a.teamId || 'ungrouped';
@@ -117,7 +116,7 @@ const FleetPanel = memo(function FleetPanel({ agentBreakdown, rotating = [] }) {
117
116
  {Object.entries(teams).map(([team, members]) => (
118
117
  <div key={team}>
119
118
  <div className="px-3 pt-2 pb-1">
120
- <span className="text-[8px] font-mono text-[#3a3f4b] uppercase tracking-widest">{team}</span>
119
+ <span className="text-2xs font-mono text-text-3 uppercase tracking-widest">{team}</span>
121
120
  </div>
122
121
  {members.map((a) => (
123
122
  <AgentRow key={a.id} agent={a} isRotating={rotatingSet.has(a.id)} />
@@ -6,31 +6,31 @@ import { RefreshCw } from 'lucide-react';
6
6
 
7
7
  const DashboardHeader = memo(function DashboardHeader({ connected, runningCount, totalCount, uptime, lastFetch, activeTeam }) {
8
8
  return (
9
- <div className="flex items-center gap-4 px-4 py-2 bg-surface-1 border-b border-[#262a32]">
10
- <h2 className="text-[12px] font-semibold text-[#e6e6e6] font-sans tracking-wide uppercase">Command Center</h2>
9
+ <div className="flex items-center gap-4 px-4 py-2 bg-surface-1 border-b border-border">
10
+ <h2 className="text-xs font-semibold text-text-0 font-sans tracking-wide uppercase">Command Center</h2>
11
11
 
12
12
  {activeTeam && (
13
13
  <>
14
- <span className="text-[#2a2e36]">/</span>
15
- <span className="text-[9px] font-mono text-[#505862]">{activeTeam.name}</span>
14
+ <span className="text-text-4">/</span>
15
+ <span className="text-xs font-mono text-text-2">{activeTeam.name}</span>
16
16
  </>
17
17
  )}
18
18
 
19
19
  <div className="flex-1" />
20
20
 
21
21
  {connected && (
22
- <div className="flex items-center gap-3.5 text-[9px] font-mono text-[#505862]">
22
+ <div className="flex items-center gap-3.5 text-xs font-mono text-text-2">
23
23
  <span>
24
- <span className="text-[#8b929e]">{runningCount}</span>
25
- <span className="text-[#3a3f4b]">/{totalCount}</span>
26
- <span className="ml-1">agents</span>
24
+ <span className="text-text-1">{runningCount}</span>
25
+ <span className="text-text-3">/{totalCount}</span>
26
+ <span className="ml-1 text-text-3">agents</span>
27
27
  </span>
28
28
  {uptime > 0 && (
29
- <span>Up {fmtUptime(uptime)}</span>
29
+ <span className="text-text-3">Up {fmtUptime(uptime)}</span>
30
30
  )}
31
31
  {lastFetch > 0 && (
32
- <span className="flex items-center gap-1 text-[#3a3f4b]">
33
- <RefreshCw size={8} />
32
+ <span className="flex items-center gap-1 text-text-4">
33
+ <RefreshCw size={9} />
34
34
  <span>{timeAgo(lastFetch)}</span>
35
35
  </span>
36
36
  )}
@@ -23,7 +23,7 @@ function TinySparkline({ data, color = HEX.accent, width = 60, height = 16 }) {
23
23
 
24
24
  return (
25
25
  <svg width={width} height={height} className="flex-shrink-0">
26
- <polyline points={points} fill="none" stroke={color} strokeWidth="1" strokeLinejoin="round" strokeOpacity="0.6" />
26
+ <polyline points={points} fill="none" stroke={color} strokeWidth="1" strokeLinejoin="round" strokeOpacity="0.7" />
27
27
  </svg>
28
28
  );
29
29
  }
@@ -33,11 +33,11 @@ function SavingsBar({ label, value, total, color }) {
33
33
  const pct = total > 0 ? (value / total) * 100 : 0;
34
34
  return (
35
35
  <div className="space-y-0.5">
36
- <div className="flex items-center justify-between text-[9px] font-mono">
37
- <span className="text-[#6e7681]">{label}</span>
38
- <span className="text-[#8b929e] tabular-nums">{fmtNum(value)}</span>
36
+ <div className="flex items-center justify-between text-xs font-mono">
37
+ <span className="text-text-2">{label}</span>
38
+ <span className="text-text-1 tabular-nums">{fmtNum(value)}</span>
39
39
  </div>
40
- <div className="h-[2px] bg-[#1a1e25] rounded-full overflow-hidden">
40
+ <div className="h-[2px] bg-surface-0 rounded-full overflow-hidden">
41
41
  <div
42
42
  className="h-full rounded-full transition-all duration-500"
43
43
  style={{ width: `${Math.min(pct, 100)}%`, background: color }}
@@ -60,18 +60,18 @@ function RotationTab({ tokens, rotation }) {
60
60
  {/* Big numbers */}
61
61
  <div className="flex gap-4">
62
62
  <div>
63
- <div className="text-[8px] font-mono text-[#3a3f4b] uppercase tracking-widest mb-0.5">Rotations</div>
64
- <div className="text-[18px] font-mono font-semibold text-[#bcc2cd] tabular-nums leading-none">
63
+ <div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-0.5">Rotations</div>
64
+ <div className="text-xl font-mono font-semibold text-text-0 tabular-nums leading-none">
65
65
  {rotation?.totalRotations || 0}
66
66
  </div>
67
67
  </div>
68
68
  <div>
69
- <div className="text-[8px] font-mono text-[#3a3f4b] uppercase tracking-widest mb-0.5">Saved</div>
70
- <div className="text-[18px] font-mono font-semibold text-[#4ae168] tabular-nums leading-none">
69
+ <div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-0.5">Saved</div>
70
+ <div className="text-xl font-mono font-semibold text-success tabular-nums leading-none">
71
71
  {fmtNum(totalSaved)}
72
72
  </div>
73
73
  {hypothetical > 0 && (
74
- <div className="text-[8px] font-mono text-[#505862] mt-0.5">
74
+ <div className="text-2xs font-mono text-text-3 mt-0.5">
75
75
  {fmtPct((totalSaved / hypothetical) * 100)} of total
76
76
  </div>
77
77
  )}
@@ -88,13 +88,13 @@ function RotationTab({ tokens, rotation }) {
88
88
  {/* Rotation history */}
89
89
  {rotation?.history?.length > 0 && (
90
90
  <div>
91
- <div className="text-[8px] font-mono text-[#3a3f4b] uppercase tracking-widest mb-1.5">Recent</div>
91
+ <div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1.5">Recent</div>
92
92
  <div className="space-y-1">
93
93
  {rotation.history.slice(-8).reverse().map((r, i) => (
94
- <div key={i} className="flex items-center gap-2 text-[9px] font-mono px-2 py-1 bg-[#1a1e25] rounded">
95
- <span className="text-[#8b929e] font-medium capitalize truncate flex-1">{r.agentName || r.role}</span>
96
- <span className="text-[#505862] tabular-nums">{fmtPct((r.contextUsage || 0) * 100)}</span>
97
- <span className="text-[#3a3f4b]">{timeAgo(r.timestamp)}</span>
94
+ <div key={i} className="flex items-center gap-2 text-xs font-mono px-2 py-1 bg-surface-0 rounded">
95
+ <span className="text-text-1 font-medium capitalize truncate flex-1">{r.agentName || r.role}</span>
96
+ <span className="text-text-3 tabular-nums">{fmtPct((r.contextUsage || 0) * 100)}</span>
97
+ <span className="text-text-4">{timeAgo(r.timestamp)}</span>
98
98
  </div>
99
99
  ))}
100
100
  </div>
@@ -109,7 +109,7 @@ function RotationTab({ tokens, rotation }) {
109
109
  function AdaptiveTab({ adaptive }) {
110
110
  if (!adaptive?.length) {
111
111
  return (
112
- <div className="flex-1 flex items-center justify-center text-[9px] text-[#3a3f4b] font-mono p-4">
112
+ <div className="flex-1 flex items-center justify-center text-xs text-text-3 font-mono p-4">
113
113
  No adaptive profiles
114
114
  </div>
115
115
  );
@@ -119,19 +119,19 @@ function AdaptiveTab({ adaptive }) {
119
119
  <ScrollArea className="flex-1">
120
120
  <div className="p-3 space-y-3">
121
121
  {adaptive.map((p) => (
122
- <div key={p.key} className="bg-[#1a1e25] rounded px-2.5 py-2 space-y-1.5">
122
+ <div key={p.key} className="bg-surface-0 rounded px-2.5 py-2 space-y-1.5">
123
123
  {/* Profile header */}
124
124
  <div className="flex items-center gap-2">
125
- <span className="text-[9px] font-mono text-[#8b929e] flex-1 truncate">{p.key}</span>
126
- <span className="text-[10px] font-mono font-semibold text-[#bcc2cd] tabular-nums">
125
+ <span className="text-xs font-mono text-text-1 flex-1 truncate">{p.key}</span>
126
+ <span className="text-xs font-mono font-semibold text-text-0 tabular-nums">
127
127
  {fmtPct(p.threshold * 100)}
128
128
  </span>
129
129
  <span
130
130
  className={cn(
131
- 'text-[7px] font-mono font-bold uppercase px-1 py-px rounded-sm',
131
+ 'text-2xs font-mono font-bold uppercase px-1 py-px rounded-sm',
132
132
  p.converged
133
- ? 'text-[#4ae168] bg-[rgba(74,225,104,0.1)]'
134
- : 'text-[#505862] bg-[rgba(80,88,98,0.1)]',
133
+ ? 'text-success bg-success/10'
134
+ : 'text-text-3 bg-surface-3',
135
135
  )}
136
136
  >
137
137
  {p.converged ? 'CONV' : `${p.adjustments} adj`}
@@ -141,7 +141,7 @@ function AdaptiveTab({ adaptive }) {
141
141
  {/* Threshold drift sparkline */}
142
142
  {p.thresholdHistory?.length > 1 && (
143
143
  <div className="flex items-center gap-2">
144
- <span className="text-[7px] font-mono text-[#3a3f4b] uppercase tracking-wider">Drift</span>
144
+ <span className="text-2xs font-mono text-text-3 uppercase tracking-wider">Drift</span>
145
145
  <TinySparkline
146
146
  data={p.thresholdHistory.map((h) => h.v)}
147
147
  color={p.converged ? HEX.success : HEX.accent}
@@ -154,7 +154,7 @@ function AdaptiveTab({ adaptive }) {
154
154
  {/* Quality scores sparkline */}
155
155
  {p.recentScores?.length > 1 && (
156
156
  <div className="flex items-center gap-2">
157
- <span className="text-[7px] font-mono text-[#3a3f4b] uppercase tracking-wider">Quality</span>
157
+ <span className="text-2xs font-mono text-text-3 uppercase tracking-wider">Quality</span>
158
158
  <TinySparkline
159
159
  data={p.recentScores}
160
160
  color={HEX.warning}
@@ -174,7 +174,7 @@ function AdaptiveTab({ adaptive }) {
174
174
  function JournalistTab({ journalist }) {
175
175
  if (!journalist) {
176
176
  return (
177
- <div className="flex-1 flex items-center justify-center text-[9px] text-[#3a3f4b] font-mono p-4">
177
+ <div className="flex-1 flex items-center justify-center text-xs text-text-3 font-mono p-4">
178
178
  Journalist inactive
179
179
  </div>
180
180
  );
@@ -186,19 +186,19 @@ function JournalistTab({ journalist }) {
186
186
  {/* Status row */}
187
187
  <div className="flex items-center gap-3">
188
188
  <div>
189
- <div className="text-[8px] font-mono text-[#3a3f4b] uppercase tracking-widest mb-0.5">Cycles</div>
190
- <div className="text-[16px] font-mono font-semibold text-[#bcc2cd] tabular-nums leading-none">
189
+ <div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-0.5">Cycles</div>
190
+ <div className="text-lg font-mono font-semibold text-text-0 tabular-nums leading-none">
191
191
  {journalist.cycleCount || 0}
192
192
  </div>
193
193
  </div>
194
194
  {journalist.lastCycleAt && (
195
195
  <div>
196
- <div className="text-[8px] font-mono text-[#3a3f4b] uppercase tracking-widest mb-0.5">Last</div>
197
- <div className="text-[10px] font-mono text-[#6e7681]">{timeAgo(journalist.lastCycleAt)}</div>
196
+ <div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-0.5">Last</div>
197
+ <div className="text-xs font-mono text-text-2">{timeAgo(journalist.lastCycleAt)}</div>
198
198
  </div>
199
199
  )}
200
200
  {journalist.synthesizing && (
201
- <span className="text-[7px] font-mono font-bold text-[#33afbc] uppercase tracking-wider animate-pulse">
201
+ <span className="text-2xs font-mono font-bold text-accent uppercase tracking-wider animate-pulse">
202
202
  Synthesizing
203
203
  </span>
204
204
  )}
@@ -207,8 +207,8 @@ function JournalistTab({ journalist }) {
207
207
  {/* Last summary */}
208
208
  {journalist.lastSummary && (
209
209
  <div>
210
- <div className="text-[8px] font-mono text-[#3a3f4b] uppercase tracking-widest mb-1">Summary</div>
211
- <div className="text-[9px] font-sans text-[#6e7681] leading-relaxed bg-[#1a1e25] rounded px-2.5 py-2 max-h-32 overflow-y-auto">
210
+ <div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1">Summary</div>
211
+ <div className="text-xs font-sans text-text-2 leading-relaxed bg-surface-0 rounded px-2.5 py-2 max-h-32 overflow-y-auto">
212
212
  {journalist.lastSummary}
213
213
  </div>
214
214
  </div>
@@ -217,13 +217,13 @@ function JournalistTab({ journalist }) {
217
217
  {/* Recent history */}
218
218
  {journalist.recentHistory?.length > 0 && (
219
219
  <div>
220
- <div className="text-[8px] font-mono text-[#3a3f4b] uppercase tracking-widest mb-1.5">History</div>
220
+ <div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1.5">History</div>
221
221
  <div className="space-y-1">
222
222
  {journalist.recentHistory.slice().reverse().map((h, i) => (
223
- <div key={i} className="flex items-center gap-2 text-[9px] font-mono px-2 py-1 bg-[#1a1e25] rounded">
224
- <span className="text-[#505862]">#{h.cycle}</span>
225
- <span className="text-[#6e7681] flex-1 truncate">{h.agentCount} agents</span>
226
- <span className="text-[#3a3f4b]">{timeAgo(h.timestamp)}</span>
223
+ <div key={i} className="flex items-center gap-2 text-xs font-mono px-2 py-1 bg-surface-0 rounded">
224
+ <span className="text-text-3">#{h.cycle}</span>
225
+ <span className="text-text-2 flex-1 truncate">{h.agentCount} agents</span>
226
+ <span className="text-text-4">{timeAgo(h.timestamp)}</span>
227
227
  </div>
228
228
  ))}
229
229
  </div>
@@ -238,17 +238,17 @@ function JournalistTab({ journalist }) {
238
238
  const IntelPanel = memo(function IntelPanel({ tokens, rotation, adaptive, journalist }) {
239
239
  return (
240
240
  <Tabs defaultValue="rotation" className="flex flex-col h-full">
241
- <TabsList className="flex-shrink-0 px-1 border-b border-[#262a32]">
242
- <TabsTrigger value="rotation" className="text-[9px] px-2.5 py-1.5 gap-1">
243
- <RotateCw size={10} />
241
+ <TabsList className="flex-shrink-0 px-1">
242
+ <TabsTrigger value="rotation" className="text-xs px-2.5 py-1.5 gap-1">
243
+ <RotateCw size={11} />
244
244
  Rotation
245
245
  </TabsTrigger>
246
- <TabsTrigger value="adaptive" className="text-[9px] px-2.5 py-1.5 gap-1">
247
- <Brain size={10} />
246
+ <TabsTrigger value="adaptive" className="text-xs px-2.5 py-1.5 gap-1">
247
+ <Brain size={11} />
248
248
  Adaptive
249
249
  </TabsTrigger>
250
- <TabsTrigger value="journalist" className="text-[9px] px-2.5 py-1.5 gap-1">
251
- <Radio size={10} />
250
+ <TabsTrigger value="journalist" className="text-xs px-2.5 py-1.5 gap-1">
251
+ <Radio size={11} />
252
252
  Journalist
253
253
  </TabsTrigger>
254
254
  </TabsList>
@@ -22,12 +22,12 @@ function MiniSparkline({ data, color = HEX.accent, width = 72, height = 22 }) {
22
22
  <svg width={width} height={height} className="flex-shrink-0">
23
23
  <defs>
24
24
  <linearGradient id={gradId} x1="0" y1="0" x2="0" y2="1">
25
- <stop offset="0%" stopColor={color} stopOpacity="0.15" />
25
+ <stop offset="0%" stopColor={color} stopOpacity="0.2" />
26
26
  <stop offset="100%" stopColor={color} stopOpacity="0" />
27
27
  </linearGradient>
28
28
  </defs>
29
29
  <polygon points={`0,${height} ${points} ${width},${height}`} fill={`url(#${gradId})`} />
30
- <polyline points={points} fill="none" stroke={color} strokeWidth="1.5" strokeLinejoin="round" strokeOpacity="0.7" />
30
+ <polyline points={points} fill="none" stroke={color} strokeWidth="1.5" strokeLinejoin="round" strokeOpacity="0.8" />
31
31
  </svg>
32
32
  );
33
33
  }
@@ -40,8 +40,8 @@ const KpiCard = memo(function KpiCard({ label, value, sparkData, color = HEX.acc
40
40
  className,
41
41
  )}>
42
42
  <div className="flex-1 min-w-0">
43
- <div className="text-[8px] font-mono text-[#3a3f4b] uppercase tracking-widest mb-0.5 truncate">{label}</div>
44
- <div className="text-[15px] font-semibold font-mono text-[#bcc2cd] tabular-nums leading-none">{value}</div>
43
+ <div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-0.5 truncate">{label}</div>
44
+ <div className="text-base font-semibold font-mono text-text-0 tabular-nums leading-none">{value}</div>
45
45
  </div>
46
46
  <MiniSparkline data={sparkData} color={color} />
47
47
  </div>
@@ -50,8 +50,8 @@ const KpiCard = memo(function KpiCard({ label, value, sparkData, color = HEX.acc
50
50
 
51
51
  export function KpiStrip({ kpis }) {
52
52
  return (
53
- <div className="flex flex-wrap" style={{ background: '#1a1e25' }}>
54
- {kpis.map((kpi, i) => (
53
+ <div className="flex flex-wrap border-b border-border" style={{ background: 'var(--color-surface-0)' }}>
54
+ {kpis.map((kpi) => (
55
55
  <KpiCard
56
56
  key={kpi.label}
57
57
  label={kpi.label}
@@ -60,7 +60,7 @@ export function KpiStrip({ kpis }) {
60
60
  color={kpi.color}
61
61
  className={cn(
62
62
  'flex-1 basis-[12.5%] min-w-[140px]',
63
- 'border-b border-r border-[#262a32]',
63
+ 'border-b border-r border-border',
64
64
  )}
65
65
  />
66
66
  ))}
@@ -1,6 +1,6 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
2
  import { useRef, useEffect, memo } from 'react';
3
- import { HEX, hexAlpha } from '../../lib/theme-hex';
3
+ import { HEX } from '../../lib/theme-hex';
4
4
  import { fmtNum, fmtDollar } from '../../lib/format';
5
5
 
6
6
  const TIER_COLORS = {
@@ -38,16 +38,14 @@ const RoutingChart = memo(function RoutingChart({ routing, size = 120 }) {
38
38
  const radius = (size - 12) / 2;
39
39
  const strokeWidth = 5;
40
40
 
41
- // Full 360-degree donut
42
41
  if (total === 0) {
43
- // Empty state — full gray ring
44
42
  ctx.beginPath();
45
43
  ctx.arc(cx, cy, radius, 0, Math.PI * 2);
46
44
  ctx.strokeStyle = HEX.surface4;
47
45
  ctx.lineWidth = strokeWidth;
48
46
  ctx.stroke();
49
47
  } else {
50
- let angle = -Math.PI / 2; // Start at top
48
+ let angle = -Math.PI / 2;
51
49
  for (const tier of tiers) {
52
50
  const count = byTier[tier] || 0;
53
51
  if (count === 0) continue;
@@ -65,13 +63,13 @@ const RoutingChart = memo(function RoutingChart({ routing, size = 120 }) {
65
63
  // Center text
66
64
  ctx.textAlign = 'center';
67
65
  ctx.textBaseline = 'middle';
68
- ctx.font = `600 ${size * 0.17}px 'JetBrains Mono Variable', monospace`;
66
+ ctx.font = `600 ${size * 0.19}px 'JetBrains Mono Variable', monospace`;
69
67
  ctx.fillStyle = HEX.text0;
70
68
  ctx.fillText(fmtNum(totalDecisions), cx, cy - 3);
71
69
 
72
- ctx.font = `500 ${size * 0.08}px 'JetBrains Mono Variable', monospace`;
73
- ctx.fillStyle = HEX.text4;
74
- ctx.fillText('ROUTES', cx, cy + size * 0.12);
70
+ ctx.font = `500 ${size * 0.09}px 'JetBrains Mono Variable', monospace`;
71
+ ctx.fillStyle = HEX.text3;
72
+ ctx.fillText('ROUTES', cx, cy + size * 0.13);
75
73
  }, [routing, size, total, totalDecisions]);
76
74
 
77
75
  return (
@@ -82,7 +80,6 @@ const RoutingChart = memo(function RoutingChart({ routing, size = 120 }) {
82
80
  style={{ width: size, height: size }}
83
81
  />
84
82
 
85
- {/* Tier breakdown */}
86
83
  <div className="w-full mt-3 space-y-2 max-w-[180px]">
87
84
  {tiers.map((tier) => {
88
85
  const count = byTier[tier] || 0;
@@ -90,14 +87,14 @@ const RoutingChart = memo(function RoutingChart({ routing, size = 120 }) {
90
87
  const pct = total > 0 ? (count / total) * 100 : 0;
91
88
  return (
92
89
  <div key={tier} className="space-y-0.5">
93
- <div className="flex items-center gap-2 text-[9px] font-mono">
90
+ <div className="flex items-center gap-2 text-xs font-mono">
94
91
  <span className="w-1.5 h-1.5 rounded-full flex-shrink-0" style={{ background: TIER_COLORS[tier] }} />
95
- <span className="text-[#505862] uppercase tracking-wider flex-1">{TIER_LABELS[tier]}</span>
96
- <span className="text-[#8b929e] tabular-nums">{count}</span>
97
- <span className="text-[#3a3f4b]">/</span>
98
- <span className="text-[#6e7681] tabular-nums">{fmtDollar(cost)}</span>
92
+ <span className="text-text-3 uppercase tracking-wider flex-1">{TIER_LABELS[tier]}</span>
93
+ <span className="text-text-1 tabular-nums">{count}</span>
94
+ <span className="text-text-4">/</span>
95
+ <span className="text-text-2 tabular-nums">{fmtDollar(cost)}</span>
99
96
  </div>
100
- <div className="h-[2px] bg-[#1a1e25] rounded-full overflow-hidden ml-3.5">
97
+ <div className="h-[2px] bg-surface-0 rounded-full overflow-hidden ml-3.5">
101
98
  <div
102
99
  className="h-full rounded-full transition-all duration-500"
103
100
  style={{ width: `${Math.min(pct, 100)}%`, background: TIER_COLORS[tier] }}
@@ -108,9 +105,8 @@ const RoutingChart = memo(function RoutingChart({ routing, size = 120 }) {
108
105
  })}
109
106
  </div>
110
107
 
111
- {/* Auto-routed badge */}
112
108
  {autoRoutedCount > 0 && (
113
- <div className="mt-2.5 text-[8px] font-mono text-[#505862] uppercase tracking-wider">
109
+ <div className="mt-2.5 text-2xs font-mono text-text-3 uppercase tracking-wider">
114
110
  {autoRoutedCount} auto-routed
115
111
  </div>
116
112
  )}