groove-dev 0.22.31 → 0.24.0

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 (52) hide show
  1. package/node_modules/@groove-dev/cli/src/setup.js +7 -9
  2. package/node_modules/@groove-dev/daemon/src/api.js +87 -4
  3. package/node_modules/@groove-dev/daemon/src/process.js +1 -0
  4. package/node_modules/@groove-dev/daemon/src/teams.js +77 -5
  5. package/node_modules/@groove-dev/daemon/src/validate.js +1 -0
  6. package/node_modules/@groove-dev/daemon/test/teams.test.js +5 -5
  7. package/node_modules/@groove-dev/gui/dist/assets/index-CqdQP7yG.js +587 -0
  8. package/node_modules/@groove-dev/gui/dist/assets/index-DvcNOnKP.css +1 -0
  9. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  10. package/node_modules/@groove-dev/gui/src/components/agents/agent-mdfiles.jsx +139 -0
  11. package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +4 -1
  12. package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +16 -14
  13. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +105 -0
  14. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +106 -38
  15. package/node_modules/@groove-dev/gui/src/components/dashboard/header-bar.jsx +28 -9
  16. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +269 -0
  17. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +35 -9
  18. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +121 -0
  19. package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +152 -34
  20. package/node_modules/@groove-dev/gui/src/lib/hooks/use-dashboard.js +28 -8
  21. package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +7 -0
  22. package/node_modules/@groove-dev/gui/src/stores/groove.js +4 -2
  23. package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +97 -52
  24. package/package.json +1 -1
  25. package/packages/cli/src/setup.js +7 -9
  26. package/packages/daemon/src/api.js +87 -4
  27. package/packages/daemon/src/process.js +1 -0
  28. package/packages/daemon/src/teams.js +77 -5
  29. package/packages/daemon/src/validate.js +1 -0
  30. package/packages/gui/dist/assets/index-CqdQP7yG.js +587 -0
  31. package/packages/gui/dist/assets/index-DvcNOnKP.css +1 -0
  32. package/packages/gui/dist/index.html +2 -2
  33. package/packages/gui/src/components/agents/agent-mdfiles.jsx +139 -0
  34. package/packages/gui/src/components/agents/agent-panel.jsx +4 -1
  35. package/packages/gui/src/components/dashboard/activity-feed.jsx +16 -14
  36. package/packages/gui/src/components/dashboard/cache-ring.jsx +105 -0
  37. package/packages/gui/src/components/dashboard/fleet-panel.jsx +106 -38
  38. package/packages/gui/src/components/dashboard/header-bar.jsx +28 -9
  39. package/packages/gui/src/components/dashboard/intel-panel.jsx +269 -0
  40. package/packages/gui/src/components/dashboard/kpi-card.jsx +35 -9
  41. package/packages/gui/src/components/dashboard/routing-chart.jsx +121 -0
  42. package/packages/gui/src/components/dashboard/token-chart.jsx +152 -34
  43. package/packages/gui/src/lib/hooks/use-dashboard.js +28 -8
  44. package/packages/gui/src/lib/theme-hex.js +7 -0
  45. package/packages/gui/src/stores/groove.js +4 -2
  46. package/packages/gui/src/views/dashboard.jsx +97 -52
  47. package/node_modules/@groove-dev/gui/dist/assets/index-CL4GvVoL.css +0 -1
  48. package/node_modules/@groove-dev/gui/dist/assets/index-D_tSBDCx.js +0 -577
  49. package/node_modules/@groove-dev/gui/src/components/dashboard/savings-panel.jsx +0 -122
  50. package/packages/gui/dist/assets/index-CL4GvVoL.css +0 -1
  51. package/packages/gui/dist/assets/index-D_tSBDCx.js +0 -577
  52. package/packages/gui/src/components/dashboard/savings-panel.jsx +0 -122
@@ -0,0 +1,121 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+ import { useRef, useEffect, memo } from 'react';
3
+ import { HEX, hexAlpha } from '../../lib/theme-hex';
4
+ import { fmtNum, fmtDollar } from '../../lib/format';
5
+
6
+ const TIER_COLORS = {
7
+ heavy: HEX.danger,
8
+ medium: HEX.warning,
9
+ light: HEX.success,
10
+ };
11
+
12
+ const TIER_LABELS = {
13
+ heavy: 'Heavy',
14
+ medium: 'Medium',
15
+ light: 'Light',
16
+ };
17
+
18
+ const RoutingChart = memo(function RoutingChart({ routing, size = 120 }) {
19
+ const canvasRef = useRef(null);
20
+ if (!routing) return null;
21
+
22
+ const { byTier = {}, costByTier = {}, totalDecisions = 0, autoRoutedCount = 0 } = routing;
23
+ const tiers = ['heavy', 'medium', 'light'];
24
+ const total = tiers.reduce((s, t) => s + (byTier[t] || 0), 0);
25
+
26
+ useEffect(() => {
27
+ const canvas = canvasRef.current;
28
+ if (!canvas) return;
29
+ const dpr = window.devicePixelRatio || 1;
30
+ canvas.width = size * dpr;
31
+ canvas.height = size * dpr;
32
+ const ctx = canvas.getContext('2d');
33
+ ctx.scale(dpr, dpr);
34
+ ctx.clearRect(0, 0, size, size);
35
+
36
+ const cx = size / 2;
37
+ const cy = size / 2;
38
+ const radius = (size - 12) / 2;
39
+ const strokeWidth = 5;
40
+
41
+ // Full 360-degree donut
42
+ if (total === 0) {
43
+ // Empty state — full gray ring
44
+ ctx.beginPath();
45
+ ctx.arc(cx, cy, radius, 0, Math.PI * 2);
46
+ ctx.strokeStyle = HEX.surface4;
47
+ ctx.lineWidth = strokeWidth;
48
+ ctx.stroke();
49
+ } else {
50
+ let angle = -Math.PI / 2; // Start at top
51
+ for (const tier of tiers) {
52
+ const count = byTier[tier] || 0;
53
+ if (count === 0) continue;
54
+ const sweep = (count / total) * Math.PI * 2;
55
+ ctx.beginPath();
56
+ ctx.arc(cx, cy, radius, angle, angle + sweep);
57
+ ctx.strokeStyle = TIER_COLORS[tier];
58
+ ctx.lineWidth = strokeWidth;
59
+ ctx.lineCap = 'butt';
60
+ ctx.stroke();
61
+ angle += sweep;
62
+ }
63
+ }
64
+
65
+ // Center text
66
+ ctx.textAlign = 'center';
67
+ ctx.textBaseline = 'middle';
68
+ ctx.font = `600 ${size * 0.17}px 'JetBrains Mono Variable', monospace`;
69
+ ctx.fillStyle = HEX.text0;
70
+ ctx.fillText(fmtNum(totalDecisions), cx, cy - 3);
71
+
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);
75
+ }, [routing, size, total, totalDecisions]);
76
+
77
+ return (
78
+ <div className="flex flex-col items-center justify-center h-full px-3 py-3">
79
+ <canvas
80
+ ref={canvasRef}
81
+ className="flex-shrink-0"
82
+ style={{ width: size, height: size }}
83
+ />
84
+
85
+ {/* Tier breakdown */}
86
+ <div className="w-full mt-3 space-y-2 max-w-[180px]">
87
+ {tiers.map((tier) => {
88
+ const count = byTier[tier] || 0;
89
+ const cost = costByTier[tier] || 0;
90
+ const pct = total > 0 ? (count / total) * 100 : 0;
91
+ return (
92
+ <div key={tier} className="space-y-0.5">
93
+ <div className="flex items-center gap-2 text-[9px] font-mono">
94
+ <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>
99
+ </div>
100
+ <div className="h-[2px] bg-[#1a1e25] rounded-full overflow-hidden ml-3.5">
101
+ <div
102
+ className="h-full rounded-full transition-all duration-500"
103
+ style={{ width: `${Math.min(pct, 100)}%`, background: TIER_COLORS[tier] }}
104
+ />
105
+ </div>
106
+ </div>
107
+ );
108
+ })}
109
+ </div>
110
+
111
+ {/* Auto-routed badge */}
112
+ {autoRoutedCount > 0 && (
113
+ <div className="mt-2.5 text-[8px] font-mono text-[#505862] uppercase tracking-wider">
114
+ {autoRoutedCount} auto-routed
115
+ </div>
116
+ )}
117
+ </div>
118
+ );
119
+ });
120
+
121
+ export { RoutingChart };
@@ -1,10 +1,27 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
- import { useRef, useEffect } from 'react';
3
- import { HEX } from '../../lib/theme-hex';
4
- import { fmtNum, fmtDollar } from '../../lib/format';
2
+ import { useRef, useEffect, useState, useCallback, memo } from 'react';
3
+ import { HEX, hexAlpha } from '../../lib/theme-hex';
4
+ import { fmtNum, fmtDollar, fmtPct } from '../../lib/format';
5
5
 
6
- export function TokenChart({ data, width, height }) {
6
+ const TokenChart = memo(function TokenChart({ data, width, height }) {
7
7
  const canvasRef = useRef(null);
8
+ const [hover, setHover] = useState(null); // { x, index }
9
+
10
+ const pad = { top: 16, right: 62, bottom: 30, left: 62 };
11
+ const w = width - pad.left - pad.right;
12
+ const h = height - pad.top - pad.bottom;
13
+
14
+ const onMouseMove = useCallback((e) => {
15
+ const canvas = canvasRef.current;
16
+ if (!canvas || !data?.length) return;
17
+ const rect = canvas.getBoundingClientRect();
18
+ const x = e.clientX - rect.left - pad.left;
19
+ if (x < 0 || x > w) { setHover(null); return; }
20
+ const index = Math.round((x / w) * (data.length - 1));
21
+ setHover({ x: pad.left + (index / (data.length - 1)) * w, index });
22
+ }, [data, w, pad.left]);
23
+
24
+ const onMouseLeave = useCallback(() => setHover(null), []);
8
25
 
9
26
  useEffect(() => {
10
27
  const canvas = canvasRef.current;
@@ -17,17 +34,15 @@ export function TokenChart({ data, width, height }) {
17
34
  ctx.scale(dpr, dpr);
18
35
  ctx.clearRect(0, 0, width, height);
19
36
 
20
- const pad = { top: 12, right: 60, bottom: 28, left: 60 };
21
- const w = width - pad.left - pad.right;
22
- const h = height - pad.top - pad.bottom;
23
-
24
37
  const tokens = data.map((d) => d.tokens || 0);
25
38
  const costs = data.map((d) => d.costUsd || 0);
39
+ const caches = data.map((d) => d.cacheHitRate ?? null);
26
40
  const maxT = Math.max(...tokens, 1);
27
41
  const maxC = Math.max(...costs, 0.01);
42
+ const hasCacheData = caches.some((c) => c !== null && c > 0);
28
43
 
29
44
  // Grid lines
30
- ctx.strokeStyle = HEX.surface4;
45
+ ctx.strokeStyle = HEX.surface3;
31
46
  ctx.lineWidth = 0.5;
32
47
  for (let i = 0; i <= 4; i++) {
33
48
  const y = pad.top + (h / 4) * i;
@@ -37,19 +52,23 @@ export function TokenChart({ data, width, height }) {
37
52
  ctx.stroke();
38
53
  }
39
54
 
40
- // Token area
55
+ // Helper: map data index to x
56
+ const xAt = (i) => pad.left + (i / (data.length - 1)) * w;
57
+ const yToken = (v) => pad.top + h - (v / maxT) * h;
58
+ const yCost = (v) => pad.top + h - (v / maxC) * h;
59
+ const yCache = (v) => pad.top + h - (v * h);
60
+
61
+ // Token area fill
41
62
  ctx.beginPath();
42
63
  ctx.moveTo(pad.left, pad.top + h);
43
- data.forEach((d, i) => {
44
- const x = pad.left + (i / (data.length - 1)) * w;
45
- const y = pad.top + h - ((d.tokens || 0) / maxT) * h;
46
- ctx.lineTo(x, y);
47
- });
48
- ctx.lineTo(pad.left + w, pad.top + h);
64
+ for (let i = 0; i < data.length; i++) {
65
+ ctx.lineTo(xAt(i), yToken(tokens[i]));
66
+ }
67
+ ctx.lineTo(xAt(data.length - 1), pad.top + h);
49
68
  ctx.closePath();
50
69
  const grad = ctx.createLinearGradient(0, pad.top, 0, pad.top + h);
51
- grad.addColorStop(0, 'rgba(51, 175, 188, 0.2)');
52
- grad.addColorStop(1, 'rgba(51, 175, 188, 0.01)');
70
+ grad.addColorStop(0, hexAlpha(HEX.accent, 0.12));
71
+ grad.addColorStop(1, hexAlpha(HEX.accent, 0.01));
53
72
  ctx.fillStyle = grad;
54
73
  ctx.fill();
55
74
 
@@ -57,29 +76,48 @@ export function TokenChart({ data, width, height }) {
57
76
  ctx.beginPath();
58
77
  ctx.strokeStyle = HEX.accent;
59
78
  ctx.lineWidth = 1.5;
60
- data.forEach((d, i) => {
61
- const x = pad.left + (i / (data.length - 1)) * w;
62
- const y = pad.top + h - ((d.tokens || 0) / maxT) * h;
79
+ for (let i = 0; i < data.length; i++) {
80
+ const x = xAt(i);
81
+ const y = yToken(tokens[i]);
63
82
  i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
64
- });
83
+ }
65
84
  ctx.stroke();
66
85
 
67
- // Cost line
86
+ // Cost line (dashed)
68
87
  ctx.beginPath();
69
88
  ctx.strokeStyle = HEX.warning;
70
89
  ctx.lineWidth = 1;
71
90
  ctx.setLineDash([4, 3]);
72
- data.forEach((d, i) => {
73
- const x = pad.left + (i / (data.length - 1)) * w;
74
- const y = pad.top + h - ((d.costUsd || 0) / maxC) * h;
91
+ for (let i = 0; i < data.length; i++) {
92
+ const x = xAt(i);
93
+ const y = yCost(costs[i]);
75
94
  i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
76
- });
95
+ }
77
96
  ctx.stroke();
78
97
  ctx.setLineDash([]);
79
98
 
99
+ // Cache hit rate line (dotted, if data available)
100
+ if (hasCacheData) {
101
+ ctx.beginPath();
102
+ ctx.strokeStyle = hexAlpha(HEX.info, 0.5);
103
+ ctx.lineWidth = 1;
104
+ ctx.setLineDash([2, 3]);
105
+ let started = false;
106
+ for (let i = 0; i < data.length; i++) {
107
+ const c = caches[i];
108
+ if (c === null || c === undefined) continue;
109
+ const x = xAt(i);
110
+ const y = yCache(c);
111
+ if (!started) { ctx.moveTo(x, y); started = true; }
112
+ else ctx.lineTo(x, y);
113
+ }
114
+ ctx.stroke();
115
+ ctx.setLineDash([]);
116
+ }
117
+
80
118
  // Left axis labels (tokens)
81
119
  ctx.fillStyle = HEX.text3;
82
- ctx.font = '10px var(--font-mono)';
120
+ ctx.font = "10px 'JetBrains Mono Variable', monospace";
83
121
  ctx.textAlign = 'right';
84
122
  for (let i = 0; i <= 4; i++) {
85
123
  const val = (maxT / 4) * (4 - i);
@@ -95,18 +133,98 @@ export function TokenChart({ data, width, height }) {
95
133
  }
96
134
 
97
135
  // Legend
98
- ctx.font = '10px sans-serif';
136
+ ctx.font = "9px 'Inter Variable', sans-serif";
137
+ ctx.textAlign = 'left';
138
+ const legendY = height - 8;
139
+ let lx = pad.left;
140
+
99
141
  ctx.fillStyle = HEX.accent;
100
- ctx.fillText('● Tokens', pad.left, height - 6);
142
+ ctx.fillRect(lx, legendY - 3, 8, 1.5);
143
+ lx += 11;
144
+ ctx.fillStyle = HEX.text3;
145
+ ctx.fillText('Tokens', lx, legendY);
146
+ lx += 44;
147
+
101
148
  ctx.fillStyle = HEX.warning;
102
- ctx.fillText('● Cost', pad.left + 70, height - 6);
103
- }, [data, width, height]);
149
+ ctx.setLineDash([3, 2]);
150
+ ctx.beginPath();
151
+ ctx.moveTo(lx, legendY - 2);
152
+ ctx.lineTo(lx + 8, legendY - 2);
153
+ ctx.strokeStyle = HEX.warning;
154
+ ctx.lineWidth = 1;
155
+ ctx.stroke();
156
+ ctx.setLineDash([]);
157
+ lx += 11;
158
+ ctx.fillStyle = HEX.text3;
159
+ ctx.fillText('Cost', lx, legendY);
160
+
161
+ if (hasCacheData) {
162
+ lx += 36;
163
+ ctx.fillStyle = hexAlpha(HEX.info, 0.5);
164
+ ctx.setLineDash([2, 2]);
165
+ ctx.beginPath();
166
+ ctx.moveTo(lx, legendY - 2);
167
+ ctx.lineTo(lx + 8, legendY - 2);
168
+ ctx.strokeStyle = hexAlpha(HEX.info, 0.5);
169
+ ctx.lineWidth = 1;
170
+ ctx.stroke();
171
+ ctx.setLineDash([]);
172
+ lx += 11;
173
+ ctx.fillStyle = HEX.text3;
174
+ ctx.fillText('Cache', lx, legendY);
175
+ }
176
+
177
+ // Hover crosshair
178
+ if (hover && hover.index >= 0 && hover.index < data.length) {
179
+ const hx = hover.x;
180
+ ctx.beginPath();
181
+ ctx.moveTo(hx, pad.top);
182
+ ctx.lineTo(hx, pad.top + h);
183
+ ctx.strokeStyle = hexAlpha(HEX.text2, 0.3);
184
+ ctx.lineWidth = 1;
185
+ ctx.setLineDash([]);
186
+ ctx.stroke();
187
+
188
+ // Tooltip background
189
+ const d = data[hover.index];
190
+ const lines = [
191
+ `${fmtNum(d.tokens || 0)} tok`,
192
+ `${fmtDollar(d.costUsd || 0)}`,
193
+ ];
194
+ if (d.cacheHitRate != null) lines.push(`${fmtPct(d.cacheHitRate * 100)} cache`);
195
+
196
+ const tooltipW = 80;
197
+ const tooltipH = lines.length * 14 + 8;
198
+ let tx = hx + 8;
199
+ if (tx + tooltipW > width - 4) tx = hx - tooltipW - 8;
200
+ const ty = pad.top + 8;
201
+
202
+ ctx.fillStyle = hexAlpha(HEX.surface1, 0.95);
203
+ ctx.strokeStyle = HEX.surface4;
204
+ ctx.lineWidth = 1;
205
+ ctx.beginPath();
206
+ ctx.roundRect(tx, ty, tooltipW, tooltipH, 3);
207
+ ctx.fill();
208
+ ctx.stroke();
209
+
210
+ ctx.font = "9px 'JetBrains Mono Variable', monospace";
211
+ ctx.fillStyle = HEX.text1;
212
+ ctx.textAlign = 'left';
213
+ lines.forEach((line, i) => {
214
+ ctx.fillText(line, tx + 6, ty + 14 + i * 14);
215
+ });
216
+ }
217
+ }, [data, width, height, hover, w, h, pad.left, pad.top, pad.right, pad.bottom]);
104
218
 
105
219
  return (
106
220
  <canvas
107
221
  ref={canvasRef}
108
222
  style={{ width, height }}
109
- className="block"
223
+ className="block cursor-crosshair"
224
+ onMouseMove={onMouseMove}
225
+ onMouseLeave={onMouseLeave}
110
226
  />
111
227
  );
112
- }
228
+ });
229
+
230
+ export { TokenChart };
@@ -9,7 +9,10 @@ export function useDashboard() {
9
9
 
10
10
  const [data, setData] = useState(null);
11
11
  const [loading, setLoading] = useState(true);
12
- const [kpiHistory, setKpiHistory] = useState({ tokens: [], cost: [], saved: [], efficiency: [], cache: [] });
12
+ const [kpiHistory, setKpiHistory] = useState({
13
+ tokens: [], cost: [], saved: [], efficiency: [],
14
+ cache: [], inputOutput: [], agents: [], turns: [],
15
+ });
13
16
  const lastFetch = useRef(0);
14
17
 
15
18
  useEffect(() => {
@@ -28,15 +31,20 @@ export function useDashboard() {
28
31
  setKpiHistory((prev) => {
29
32
  const now = Date.now();
30
33
  const add = (arr, val) => [...arr.slice(-59), { t: now, v: val || 0 }];
34
+ const totalUsed = d.tokens?.totalTokens || 0;
35
+ const totalSaved = d.tokens?.savings?.total || 0;
36
+ const hypothetical = totalUsed + totalSaved;
37
+ const input = d.tokens?.totalInputTokens || 0;
38
+ const output = d.tokens?.totalOutputTokens || 0;
31
39
  return {
32
- tokens: add(prev.tokens, d.tokens?.totalUsed),
40
+ tokens: add(prev.tokens, totalUsed),
33
41
  cost: add(prev.cost, d.tokens?.totalCostUsd),
34
- saved: add(prev.saved, d.tokens?.totalSaved),
35
- efficiency: add(prev.efficiency, (() => {
36
- const h = (d.tokens?.totalUsed || 0) + (d.tokens?.totalSaved || 0);
37
- return h > 0 ? ((d.tokens?.totalSaved || 0) / h) * 100 : 0;
38
- })()),
42
+ saved: add(prev.saved, totalSaved),
43
+ efficiency: add(prev.efficiency, hypothetical > 0 ? (totalSaved / hypothetical) * 100 : 0),
39
44
  cache: add(prev.cache, d.tokens?.cacheHitRate),
45
+ inputOutput: add(prev.inputOutput, output > 0 ? input / output : 0),
46
+ agents: add(prev.agents, d.agents?.running || 0),
47
+ turns: add(prev.turns, d.tokens?.totalTurns),
40
48
  };
41
49
  });
42
50
  } catch {
@@ -49,5 +57,17 @@ export function useDashboard() {
49
57
  return () => { alive = false; clearInterval(interval); };
50
58
  }, [connected]);
51
59
 
52
- return { data, loading, agents, connected, kpiHistory, lastFetch: lastFetch.current };
60
+ // Derive enriched sub-objects from data
61
+ const agentBreakdown = data?.agents?.breakdown || [];
62
+ const routing = data?.routing || null;
63
+ const rotation = data?.rotation || null;
64
+ const adaptive = data?.adaptive || [];
65
+ const journalist = data?.journalist || null;
66
+ const rotating = rotation?.rotating || [];
67
+
68
+ return {
69
+ data, loading, agents, connected, kpiHistory,
70
+ lastFetch: lastFetch.current,
71
+ agentBreakdown, routing, rotation, adaptive, journalist, rotating,
72
+ };
53
73
  }
@@ -1,6 +1,13 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
2
  // Raw hex values for Canvas 2D rendering (CSS vars don't work in canvas)
3
3
 
4
+ export function hexAlpha(hex, alpha) {
5
+ const r = parseInt(hex.slice(1, 3), 16);
6
+ const g = parseInt(hex.slice(3, 5), 16);
7
+ const b = parseInt(hex.slice(5, 7), 16);
8
+ return `rgba(${r},${g},${b},${alpha})`;
9
+ }
10
+
4
11
  export const HEX = {
5
12
  // Surfaces
6
13
  surface0: '#1a1e25',
@@ -310,9 +310,11 @@ export const useGrooveStore = create((set, get) => ({
310
310
  localStorage.setItem('groove:activeTeamId', id);
311
311
  },
312
312
 
313
- async createTeam(name) {
313
+ async createTeam(name, workingDir) {
314
314
  try {
315
- const team = await api.post('/teams', { name });
315
+ const body = { name };
316
+ if (workingDir) body.workingDir = workingDir;
317
+ const team = await api.post('/teams', body);
316
318
  // Only set activeTeamId — the WS team:created handler adds to the teams array
317
319
  set({ activeTeamId: team.id });
318
320
  localStorage.setItem('groove:activeTeamId', team.id);