groove-dev 0.27.142 → 0.27.144

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 (187) hide show
  1. package/node_modules/@groove-dev/cli/package.json +1 -1
  2. package/node_modules/@groove-dev/daemon/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/src/api.js +1086 -6532
  4. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
  5. package/node_modules/@groove-dev/daemon/src/index.js +3 -0
  6. package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
  7. package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
  8. package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
  9. package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
  10. package/node_modules/@groove-dev/daemon/src/process.js +2 -2
  11. package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
  12. package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
  13. package/node_modules/@groove-dev/daemon/src/routes/agents.js +889 -0
  14. package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
  15. package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
  16. package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
  17. package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
  18. package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
  19. package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
  20. package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
  21. package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
  22. package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
  23. package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
  24. package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
  25. package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
  26. package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
  27. package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
  28. package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
  29. package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
  30. package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  31. package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  32. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  33. package/node_modules/@groove-dev/gui/package.json +1 -1
  34. package/{packages/gui/src/app.jsx → node_modules/@groove-dev/gui/src/App.jsx} +0 -2
  35. package/node_modules/@groove-dev/gui/src/app.css +35 -0
  36. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
  37. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +144 -31
  38. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
  39. package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
  40. package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
  41. package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
  42. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
  43. package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
  44. package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
  45. package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
  46. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
  47. package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
  48. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  49. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
  50. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
  51. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
  52. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  53. package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
  54. package/node_modules/@groove-dev/gui/src/components/editor/selection-menu.jsx +2 -0
  55. package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +316 -82
  56. package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +187 -32
  57. package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +195 -14
  58. package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +286 -102
  59. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -4
  60. package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +4 -2
  61. package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +137 -108
  62. package/node_modules/@groove-dev/gui/src/components/network/network-health.jsx +2 -2
  63. package/node_modules/@groove-dev/gui/src/components/network/performance-dashboard.jsx +4 -4
  64. package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +81 -99
  65. package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +5 -2
  66. package/node_modules/@groove-dev/gui/src/lib/cron.js +64 -0
  67. package/node_modules/@groove-dev/gui/src/lib/status.js +24 -24
  68. package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
  69. package/node_modules/@groove-dev/gui/src/stores/groove.js +34 -3144
  70. package/node_modules/@groove-dev/gui/src/stores/helpers.js +10 -0
  71. package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +452 -0
  72. package/node_modules/@groove-dev/gui/src/stores/slices/automations-slice.js +96 -0
  73. package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +227 -0
  74. package/node_modules/@groove-dev/gui/src/stores/slices/editor-slice.js +285 -0
  75. package/node_modules/@groove-dev/gui/src/stores/slices/marketplace-slice.js +461 -0
  76. package/node_modules/@groove-dev/gui/src/stores/slices/network-slice.js +361 -0
  77. package/node_modules/@groove-dev/gui/src/stores/slices/preview-slice.js +109 -0
  78. package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +897 -0
  79. package/node_modules/@groove-dev/gui/src/stores/slices/teams-slice.js +413 -0
  80. package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +98 -0
  81. package/node_modules/@groove-dev/gui/src/views/agents.jsx +5 -5
  82. package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +12 -13
  83. package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +191 -3
  84. package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +17 -6
  85. package/node_modules/@groove-dev/gui/src/views/models.jsx +410 -509
  86. package/node_modules/@groove-dev/gui/src/views/network.jsx +3 -3
  87. package/node_modules/@groove-dev/gui/src/views/settings.jsx +81 -94
  88. package/node_modules/@groove-dev/gui/src/views/teams.jsx +40 -483
  89. package/package.json +1 -1
  90. package/packages/cli/package.json +1 -1
  91. package/packages/daemon/package.json +1 -1
  92. package/packages/daemon/src/api.js +1086 -6532
  93. package/packages/daemon/src/gateways/manager.js +35 -1
  94. package/packages/daemon/src/index.js +3 -0
  95. package/packages/daemon/src/journalist.js +23 -13
  96. package/packages/daemon/src/mlx-server.js +365 -0
  97. package/packages/daemon/src/model-lab.js +308 -12
  98. package/packages/daemon/src/pm.js +1 -1
  99. package/packages/daemon/src/process.js +2 -2
  100. package/packages/daemon/src/providers/local.js +36 -8
  101. package/packages/daemon/src/registry.js +21 -5
  102. package/packages/daemon/src/routes/agents.js +889 -0
  103. package/packages/daemon/src/routes/coordination.js +318 -0
  104. package/packages/daemon/src/routes/files.js +751 -0
  105. package/packages/daemon/src/routes/integrations.js +485 -0
  106. package/packages/daemon/src/routes/network.js +1784 -0
  107. package/packages/daemon/src/routes/providers.js +755 -0
  108. package/packages/daemon/src/routes/schedules.js +110 -0
  109. package/packages/daemon/src/routes/teams.js +650 -0
  110. package/packages/daemon/src/scheduler.js +456 -24
  111. package/packages/daemon/src/teams.js +1 -1
  112. package/packages/daemon/src/validate.js +38 -1
  113. package/packages/daemon/templates/mlx-setup.json +12 -0
  114. package/packages/daemon/templates/tgi-setup.json +1 -1
  115. package/packages/daemon/templates/vllm-setup.json +1 -1
  116. package/packages/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  117. package/packages/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  118. package/packages/gui/dist/index.html +2 -2
  119. package/packages/gui/package.json +1 -1
  120. package/{node_modules/@groove-dev/gui/src/app.jsx → packages/gui/src/App.jsx} +0 -2
  121. package/packages/gui/src/app.css +35 -0
  122. package/packages/gui/src/components/agents/agent-config.jsx +1 -128
  123. package/packages/gui/src/components/agents/agent-feed.jsx +144 -31
  124. package/packages/gui/src/components/agents/agent-node.jsx +8 -13
  125. package/packages/gui/src/components/agents/code-review.jsx +159 -122
  126. package/packages/gui/src/components/agents/diff-viewer.jsx +23 -23
  127. package/packages/gui/src/components/agents/journalist-panel.jsx +1 -1
  128. package/packages/gui/src/components/agents/spawn-wizard.jsx +2 -135
  129. package/packages/gui/src/components/automations/automation-card.jsx +274 -0
  130. package/packages/gui/src/components/automations/automation-wizard.jsx +1136 -0
  131. package/packages/gui/src/components/dashboard/activity-feed.jsx +3 -3
  132. package/packages/gui/src/components/dashboard/cache-ring.jsx +5 -5
  133. package/packages/gui/src/components/dashboard/context-gauges.jsx +6 -8
  134. package/packages/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  135. package/packages/gui/src/components/dashboard/intel-panel.jsx +238 -656
  136. package/packages/gui/src/components/dashboard/kpi-card.jsx +3 -3
  137. package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
  138. package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  139. package/packages/gui/src/components/dashboard/token-chart.jsx +4 -4
  140. package/packages/gui/src/components/editor/selection-menu.jsx +2 -0
  141. package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
  142. package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
  143. package/packages/gui/src/components/lab/parameter-panel.jsx +195 -14
  144. package/packages/gui/src/components/lab/runtime-config.jsx +286 -102
  145. package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
  146. package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
  147. package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
  148. package/packages/gui/src/components/network/network-health.jsx +2 -2
  149. package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
  150. package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
  151. package/packages/gui/src/components/ui/sheet.jsx +5 -2
  152. package/packages/gui/src/lib/cron.js +64 -0
  153. package/packages/gui/src/lib/status.js +24 -24
  154. package/packages/gui/src/lib/theme-hex.js +1 -0
  155. package/packages/gui/src/stores/groove.js +34 -3144
  156. package/packages/gui/src/stores/helpers.js +10 -0
  157. package/packages/gui/src/stores/slices/agents-slice.js +452 -0
  158. package/packages/gui/src/stores/slices/automations-slice.js +96 -0
  159. package/packages/gui/src/stores/slices/chat-slice.js +227 -0
  160. package/packages/gui/src/stores/slices/editor-slice.js +285 -0
  161. package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
  162. package/packages/gui/src/stores/slices/network-slice.js +361 -0
  163. package/packages/gui/src/stores/slices/preview-slice.js +109 -0
  164. package/packages/gui/src/stores/slices/providers-slice.js +897 -0
  165. package/packages/gui/src/stores/slices/teams-slice.js +413 -0
  166. package/packages/gui/src/stores/slices/ui-slice.js +98 -0
  167. package/packages/gui/src/views/agents.jsx +5 -5
  168. package/packages/gui/src/views/dashboard.jsx +12 -13
  169. package/packages/gui/src/views/marketplace.jsx +191 -3
  170. package/packages/gui/src/views/model-lab.jsx +17 -6
  171. package/packages/gui/src/views/models.jsx +410 -509
  172. package/packages/gui/src/views/network.jsx +3 -3
  173. package/packages/gui/src/views/settings.jsx +81 -94
  174. package/packages/gui/src/views/teams.jsx +40 -483
  175. package/SECURITY_SWEEP.md +0 -228
  176. package/TRAINING_DATA_v4.md +0 -6
  177. package/node_modules/@groove-dev/gui/dist/assets/index-Bjd91ufV.js +0 -984
  178. package/node_modules/@groove-dev/gui/dist/assets/index-BqdwIFn4.css +0 -1
  179. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -322
  180. package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
  181. package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
  182. package/packages/gui/dist/assets/index-Bjd91ufV.js +0 -984
  183. package/packages/gui/dist/assets/index-BqdwIFn4.css +0 -1
  184. package/packages/gui/src/components/agents/agent-chat.jsx +0 -322
  185. package/packages/gui/src/views/preview.jsx +0 -6
  186. package/packages/gui/src/views/subscription-panel.jsx +0 -327
  187. package/test.py +0 -571
@@ -1,17 +1,34 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
+ //
3
+ // ⚠️ AGENT CONTEXT LANDMINE — READ BEFORE MODIFYING THIS FILE
4
+ //
5
+ // This file's import chain pulls in heavy components (quick-connect → ssh-wizard
6
+ // at 22KB, folder-browser at 7KB, groove.js store at 41KB). Reading those files
7
+ // will blow your context window and cause compaction → stall → freeze.
8
+ //
9
+ // RULES FOR AI AGENTS:
10
+ // 1. Do NOT read groove.js — the store selectors below are the full interface
11
+ // 2. Do NOT read or modify quick-connect.jsx or ssh-wizard.jsx — they are
12
+ // standalone components rendered as-is. Restyle them in their own files.
13
+ // 3. Do NOT read folder-browser.jsx — it's a dialog, just pass it props
14
+ // 4. Do NOT spawn Explore sub-agents to "understand the codebase" — everything
15
+ // you need is in THIS file and app.css (for design tokens / CSS variables)
16
+ // 5. Redesign THIS file only. Never rewrite imported components as a side-effect.
17
+ //
2
18
  import { useState, useEffect } from 'react';
3
19
  import { useGrooveStore } from '../../stores/groove';
4
- import { HEX, hexAlpha } from '../../lib/theme-hex';
5
20
  import { cn } from '../../lib/cn';
6
21
  import {
7
22
  FolderOpen, Radio, X, Plus, ExternalLink, Loader2, Unplug,
23
+ ArrowRight, Clock, Server,
8
24
  } from 'lucide-react';
9
- import { FolderBrowser } from '../agents/folder-browser';
10
- import { QuickConnect } from '../settings/quick-connect';
25
+ import { FolderBrowser } from '../agents/folder-browser'; // dialog — don't read source, just pass props
26
+ import { QuickConnect } from '../settings/quick-connect'; // self-contained — don't read or rewrite
11
27
  import { StatusDot } from '../ui/status-dot';
12
28
  import { ToastContainer } from '../ui/toast';
13
29
 
14
30
  export function WelcomeSplash() {
31
+ // These are ALL the store selectors this component uses — no need to read groove.js
15
32
  const recentProjects = useGrooveStore((s) => s.recentProjects);
16
33
  const setProjectDir = useGrooveStore((s) => s.setProjectDir);
17
34
  const removeRecentProject = useGrooveStore((s) => s.removeRecentProject);
@@ -32,7 +49,7 @@ export function WelcomeSplash() {
32
49
  const visibleProjects = (recentProjects || []).slice(0, 8);
33
50
  const hasRecent = visibleProjects.length > 0;
34
51
  const hasTunnels = savedTunnels.length > 0;
35
- const hasRightContent = hasRecent || hasTunnels;
52
+ const hasContent = hasRecent || hasTunnels;
36
53
 
37
54
  async function handleTunnelClick(server) {
38
55
  if (server.active) {
@@ -53,106 +70,96 @@ export function WelcomeSplash() {
53
70
  }
54
71
 
55
72
  return (
56
- <div
57
- className="fixed inset-0 z-50 flex items-center justify-center"
58
- style={{ background: `radial-gradient(ellipse at 50% 40%, ${hexAlpha(HEX.accent, 0.06)} 0%, transparent 70%) ${HEX.surface0}` }}
59
- >
60
- <div className={cn(
61
- 'w-full max-w-4xl px-8 flex gap-12',
62
- hasRightContent ? 'items-start' : 'items-center justify-center',
63
- 'max-md:flex-col max-md:items-center max-md:gap-8 max-md:overflow-y-auto max-md:max-h-[100vh] max-md:py-12',
64
- )}>
65
- {/* ── Left Panel ─────────────────────────────────────── */}
66
- <div className={cn(
67
- 'flex flex-col',
68
- hasRightContent ? 'w-[55%] max-md:w-full' : 'w-full max-w-lg',
69
- hasRightContent ? 'pt-[10vh]' : 'items-center text-center',
70
- )}>
71
- {/* Hero */}
72
- <div className={cn('flex items-center gap-4 mb-6', !hasRightContent && 'flex-col')}>
73
- <div
74
- className="w-16 h-16 rounded-full flex items-center justify-center flex-shrink-0"
75
- style={{
76
- background: hexAlpha(HEX.accent, 0.08),
77
- border: `1px solid ${hexAlpha(HEX.accent, 0.15)}`,
78
- boxShadow: `0 0 40px ${hexAlpha(HEX.accent, 0.1)}`,
79
- }}
80
- >
81
- <img src="/favicon.png" className="w-9 h-9 rounded-full" alt="Groove" />
82
- </div>
83
- <div>
84
- <h1 className="text-2xl font-bold text-text-0 font-sans tracking-tight">Welcome to Groove</h1>
85
- <p className="text-sm text-text-2 font-sans mt-0.5">Your AI coding team, ready in minutes</p>
86
- </div>
87
- </div>
73
+ <div className="fixed inset-0 z-50 overflow-y-auto welcome-bg">
74
+ <div className="pointer-events-none fixed top-0 left-1/2 -translate-x-1/2 w-[800px] h-[600px] rounded-full bg-accent/[0.03] blur-[120px]" />
88
75
 
89
- {/* Action cards */}
90
- <div className="flex flex-col gap-3 mt-4 w-full">
91
- <button
92
- onClick={() => setBrowsing(true)}
93
- className="w-full flex items-center gap-4 p-5 rounded-lg border border-accent/25 bg-gradient-to-r from-accent/8 to-accent/3 hover:from-accent/14 hover:to-accent/6 hover:border-accent/40 transition-all cursor-pointer group text-left"
94
- >
95
- <div className="w-11 h-11 rounded-lg bg-accent/20 flex items-center justify-center group-hover:scale-110 transition-transform flex-shrink-0">
96
- <FolderOpen size={22} className="text-accent" />
97
- </div>
98
- <div className="flex-1 min-w-0">
99
- <div className="text-base font-semibold text-text-0 font-sans">Open Project</div>
100
- <div className="text-sm text-text-2 font-sans mt-0.5">Browse the filesystem to pick a project</div>
101
- </div>
102
- </button>
76
+ <div className="relative min-h-screen flex flex-col items-center px-8 pt-[14vh] pb-12 max-sm:pt-[8vh] max-sm:px-5">
103
77
 
104
- <button
105
- onClick={toggleQuickConnect}
106
- className="w-full flex items-center gap-4 p-5 rounded-lg border border-border bg-surface-1 hover:bg-surface-2 hover:border-border transition-all cursor-pointer group text-left"
107
- >
108
- <div className="w-11 h-11 rounded-lg bg-surface-4 flex items-center justify-center group-hover:scale-110 transition-transform flex-shrink-0">
109
- <Radio size={22} className="text-text-1" />
110
- </div>
111
- <div className="flex-1 min-w-0">
112
- <div className="text-base font-semibold text-text-0 font-sans">Connect to Remote</div>
113
- <div className="text-sm text-text-2 font-sans mt-0.5">SSH tunnel to a server</div>
114
- </div>
115
- </button>
78
+ {/* ── Hero ──────────────────────────────────────────── */}
79
+ <div className="flex flex-col items-center text-center mb-14">
80
+ <div className="relative mb-8">
81
+ <div className="absolute -inset-10 rounded-full bg-accent/[0.06] blur-3xl animate-welcome-breathe" />
82
+ <div className="absolute -inset-4 rounded-full border border-accent/25 animate-welcome-ring" />
83
+ <div className="absolute -inset-4 rounded-full border border-accent/15 animate-welcome-ring-delayed" />
84
+ <div className="relative w-[88px] h-[88px] rounded-full bg-accent/[0.07] border border-accent/20 flex items-center justify-center welcome-logo-shadow">
85
+ <img src="/favicon.png" className="w-12 h-12 rounded-full" alt="Groove" />
86
+ </div>
116
87
  </div>
117
88
 
118
- {/* Keyboard shortcuts */}
119
- <p className="text-xs text-text-4 font-sans mt-8">
120
- <kbd className="font-mono bg-surface-4 px-1.5 py-0.5 rounded text-text-3">Cmd+K</kbd>
121
- <span className="mx-1.5">command palette</span>
122
- <span className="text-text-4 mx-1">&middot;</span>
123
- <kbd className="font-mono bg-surface-4 px-1.5 py-0.5 rounded text-text-3">Cmd+N</kbd>
124
- <span className="mx-1.5">spawn</span>
125
- <span className="text-text-4 mx-1">&middot;</span>
126
- <kbd className="font-mono bg-surface-4 px-1.5 py-0.5 rounded text-text-3">Cmd+J</kbd>
127
- <span className="mx-1.5">terminal</span>
89
+ <h1 className="text-4xl font-bold text-text-0 tracking-tight mb-3 font-sans max-sm:text-3xl">
90
+ Welcome to Groove
91
+ </h1>
92
+ <p className="text-base text-text-2 font-sans max-w-md leading-relaxed max-sm:text-sm">
93
+ The most powerful agenticOS ever built.
94
+ <br className="max-sm:hidden" />
95
+ <span className="max-sm:hidden"> </span>Spawn fast. Stay aware. Never lose context.
128
96
  </p>
129
97
  </div>
130
98
 
131
- {/* ── Right Panel ────────────────────────────────────── */}
132
- {hasRightContent && (
133
- <div className="w-[45%] max-md:w-full pt-[10vh] max-md:pt-0 min-w-0 max-h-[80vh] overflow-y-auto">
134
- {/* Recent Projects */}
99
+ {/* ── Action Cards ──────────────────────────────────── */}
100
+ <div className="w-full max-w-2xl grid grid-cols-2 gap-4 mb-14 max-sm:grid-cols-1 max-sm:max-w-sm">
101
+ <button
102
+ onClick={() => setBrowsing(true)}
103
+ className="group relative overflow-hidden rounded-xl border border-accent/20 bg-gradient-to-br from-accent/[0.08] via-accent/[0.03] to-transparent p-6 text-left hover:border-accent/40 hover:from-accent/[0.14] hover:via-accent/[0.06] transition-all duration-300 cursor-pointer"
104
+ >
105
+ <div className="w-12 h-12 rounded-xl bg-accent/15 border border-accent/20 flex items-center justify-center mb-4 group-hover:scale-110 transition-transform duration-300">
106
+ <FolderOpen size={24} className="text-accent" />
107
+ </div>
108
+ <div className="text-lg font-semibold text-text-0 font-sans mb-1">Open Project</div>
109
+ <div className="text-sm text-text-2 font-sans">Browse the filesystem to pick a project</div>
110
+ <div className="flex items-center gap-1 text-xs text-accent font-sans mt-4 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
111
+ Browse files <ArrowRight size={12} />
112
+ </div>
113
+ </button>
114
+
115
+ <button
116
+ onClick={toggleQuickConnect}
117
+ className="group relative overflow-hidden rounded-xl border border-border bg-surface-1 p-6 text-left hover:border-accent/30 hover:bg-surface-2 transition-all duration-300 cursor-pointer"
118
+ >
119
+ <div className="w-12 h-12 rounded-xl bg-surface-4 border border-border-subtle flex items-center justify-center mb-4 group-hover:scale-110 group-hover:bg-accent/10 group-hover:border-accent/20 transition-all duration-300">
120
+ <Radio size={24} className="text-text-2 group-hover:text-accent transition-colors duration-300" />
121
+ </div>
122
+ <div className="text-lg font-semibold text-text-0 font-sans mb-1">Connect to Remote</div>
123
+ <div className="text-sm text-text-2 font-sans">SSH tunnel to a server running Groove</div>
124
+ <div className="flex items-center gap-1 text-xs text-accent font-sans mt-4 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
125
+ Setup connection <ArrowRight size={12} />
126
+ </div>
127
+ </button>
128
+ </div>
129
+
130
+ {/* ── Content Grid ──────────────────────────────────── */}
131
+ {hasContent && (
132
+ <div className={cn(
133
+ 'w-full max-w-4xl gap-6 mb-14 max-sm:flex max-sm:flex-col max-sm:gap-6',
134
+ hasRecent && hasTunnels ? 'grid grid-cols-2' : 'flex justify-center',
135
+ )}>
135
136
  {hasRecent && (
136
- <div>
137
- <div className="text-2xs font-mono text-text-3 uppercase tracking-widest mb-2">Recent</div>
138
- <div className="flex flex-col">
137
+ <div className={cn(!hasTunnels && 'w-full max-w-lg')}>
138
+ <div className="flex items-center gap-2 mb-3">
139
+ <Clock size={13} className="text-text-3" />
140
+ <h2 className="text-2xs font-mono text-text-3 uppercase tracking-widest">Recent Projects</h2>
141
+ </div>
142
+ <div className="rounded-xl border border-border-subtle bg-surface-1/50 overflow-hidden divide-y divide-border-subtle">
139
143
  {visibleProjects.map((project) => (
140
144
  <div
141
145
  key={project.path}
142
- className="group flex items-center gap-2 px-2 py-1.5 rounded-sm hover:bg-surface-1 transition-colors"
146
+ className="group flex items-center gap-3 px-4 py-3 hover:bg-surface-2/50 transition-colors"
143
147
  >
148
+ <div className="w-8 h-8 rounded-lg bg-surface-3 flex items-center justify-center flex-shrink-0 group-hover:bg-accent/10 transition-colors">
149
+ <FolderOpen size={14} className="text-text-3 group-hover:text-accent transition-colors" />
150
+ </div>
144
151
  <button
145
152
  onClick={() => setProjectDir(project.path)}
146
153
  className="flex-1 min-w-0 text-left cursor-pointer"
147
154
  >
148
- <div className="text-sm font-medium text-text-1 hover:text-accent truncate transition-colors">
155
+ <div className="text-sm font-medium text-text-1 group-hover:text-accent truncate transition-colors font-sans">
149
156
  {project.name}
150
157
  </div>
151
158
  <div className="text-2xs font-mono text-text-4 truncate">{project.path}</div>
152
159
  </button>
153
160
  <button
154
161
  onClick={(e) => { e.stopPropagation(); removeRecentProject(project.path); }}
155
- className="opacity-0 group-hover:opacity-100 p-0.5 text-text-4 hover:text-danger cursor-pointer transition-all flex-shrink-0"
162
+ className="opacity-0 group-hover:opacity-100 p-1 text-text-4 hover:text-danger cursor-pointer transition-all flex-shrink-0"
156
163
  title="Remove from recent"
157
164
  >
158
165
  <X size={14} />
@@ -163,45 +170,51 @@ export function WelcomeSplash() {
163
170
  </div>
164
171
  )}
165
172
 
166
- {/* Divider */}
167
- {hasRecent && hasTunnels && (
168
- <div className="border-t border-border-subtle my-4" />
169
- )}
170
-
171
- {/* SSH Connections */}
172
173
  {hasTunnels && (
173
- <div>
174
- <div className="text-2xs font-mono text-text-3 uppercase tracking-widest mb-2">SSH Connections</div>
175
- <div className="flex flex-col">
174
+ <div className={cn(!hasRecent && 'w-full max-w-lg')}>
175
+ <div className="flex items-center gap-2 mb-3">
176
+ <Server size={13} className="text-text-3" />
177
+ <h2 className="text-2xs font-mono text-text-3 uppercase tracking-widest">SSH Connections</h2>
178
+ </div>
179
+ <div className="rounded-xl border border-border-subtle bg-surface-1/50 overflow-hidden divide-y divide-border-subtle">
176
180
  {savedTunnels.map((server) => (
177
181
  <div
178
182
  key={server.id}
179
183
  className={cn(
180
- 'group flex items-center gap-2 px-2 py-1.5 rounded-sm hover:bg-surface-1 transition-colors',
184
+ 'group flex items-center gap-3 px-4 py-3 hover:bg-surface-2/50 transition-colors',
181
185
  connectingId === server.id && 'opacity-60 pointer-events-none',
182
186
  )}
183
187
  >
188
+ <div className={cn(
189
+ 'w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 transition-colors',
190
+ server.active ? 'bg-success/10' : 'bg-surface-3 group-hover:bg-accent/10',
191
+ )}>
192
+ <Server size={14} className={cn(
193
+ 'transition-colors',
194
+ server.active ? 'text-success' : 'text-text-3 group-hover:text-accent',
195
+ )} />
196
+ </div>
184
197
  <button
185
198
  onClick={() => handleTunnelClick(server)}
186
199
  disabled={connectingId === server.id}
187
200
  className="flex-1 min-w-0 text-left cursor-pointer"
188
201
  >
189
202
  <div className="flex items-center gap-1.5">
190
- <span className="text-sm font-medium text-text-1 hover:text-accent truncate transition-colors">
203
+ <span className="text-sm font-medium text-text-1 group-hover:text-accent truncate transition-colors font-sans">
191
204
  {server.name}
192
205
  </span>
193
206
  {server.active && <StatusDot status="running" size="sm" />}
194
207
  </div>
195
208
  <div className="text-2xs font-mono text-text-4 truncate">{server.user}@{server.host}</div>
196
209
  </button>
197
- <div className="flex items-center gap-1 flex-shrink-0">
210
+ <div className="flex items-center gap-1.5 flex-shrink-0">
198
211
  {connectingId === server.id ? (
199
212
  <Loader2 size={14} className="text-text-3 animate-spin" />
200
213
  ) : server.active ? (
201
214
  <>
202
215
  <button
203
216
  onClick={() => handleTunnelClick(server)}
204
- className="opacity-0 group-hover:opacity-100 flex items-center gap-0.5 text-2xs text-success hover:text-success/80 cursor-pointer transition-all"
217
+ className="opacity-0 group-hover:opacity-100 flex items-center gap-0.5 text-2xs text-success hover:text-success/80 cursor-pointer transition-all font-sans"
205
218
  >
206
219
  <ExternalLink size={11} /> Open
207
220
  </button>
@@ -210,7 +223,7 @@ export function WelcomeSplash() {
210
223
  await disconnectTunnel(server.id);
211
224
  addToast('info', 'Disconnected', server.name);
212
225
  }}
213
- className="opacity-0 group-hover:opacity-100 p-0.5 text-text-4 hover:text-danger cursor-pointer transition-all"
226
+ className="opacity-0 group-hover:opacity-100 p-1 text-text-4 hover:text-danger cursor-pointer transition-all"
214
227
  title="Disconnect"
215
228
  >
216
229
  <Unplug size={12} />
@@ -219,7 +232,7 @@ export function WelcomeSplash() {
219
232
  ) : null}
220
233
  <button
221
234
  onClick={(e) => { e.stopPropagation(); deleteTunnel(server.id); }}
222
- className="opacity-0 group-hover:opacity-100 p-0.5 text-text-4 hover:text-danger cursor-pointer transition-all flex-shrink-0"
235
+ className="opacity-0 group-hover:opacity-100 p-1 text-text-4 hover:text-danger cursor-pointer transition-all flex-shrink-0"
223
236
  title="Remove connection"
224
237
  >
225
238
  <X size={14} />
@@ -227,24 +240,40 @@ export function WelcomeSplash() {
227
240
  </div>
228
241
  </div>
229
242
  ))}
243
+ <button
244
+ onClick={toggleQuickConnect}
245
+ className="flex items-center gap-1.5 text-2xs text-accent hover:text-accent/80 font-sans font-medium cursor-pointer transition-colors w-full px-4 py-2.5 hover:bg-surface-2/30"
246
+ >
247
+ <Plus size={11} /> Add Connection
248
+ </button>
230
249
  </div>
231
- <button
232
- onClick={toggleQuickConnect}
233
- className="flex items-center gap-1 text-xs text-accent hover:underline font-sans cursor-pointer mt-2 px-2"
234
- >
235
- <Plus size={12} /> Add Connection
236
- </button>
237
250
  </div>
238
251
  )}
239
-
240
- {/* Empty state */}
241
- {!hasRecent && !hasTunnels && (
242
- <p className="text-sm text-text-4 italic px-2">No recent activity</p>
243
- )}
244
252
  </div>
245
253
  )}
254
+
255
+ {/* ── Footer ──────────────────────────────────────── */}
256
+ <div className="mt-auto pt-8 flex flex-col items-center gap-4">
257
+ <div className="flex items-center gap-5 text-xs text-text-4 font-sans">
258
+ <span className="flex items-center gap-1.5">
259
+ <kbd className="font-mono bg-surface-4 px-1.5 py-0.5 rounded text-text-3 text-2xs">⌘K</kbd>
260
+ <span>palette</span>
261
+ </span>
262
+ <span className="text-border">·</span>
263
+ <span className="flex items-center gap-1.5">
264
+ <kbd className="font-mono bg-surface-4 px-1.5 py-0.5 rounded text-text-3 text-2xs">⌘N</kbd>
265
+ <span>spawn</span>
266
+ </span>
267
+ <span className="text-border">·</span>
268
+ <span className="flex items-center gap-1.5">
269
+ <kbd className="font-mono bg-surface-4 px-1.5 py-0.5 rounded text-text-3 text-2xs">⌘J</kbd>
270
+ <span>terminal</span>
271
+ </span>
272
+ </div>
273
+ </div>
246
274
  </div>
247
275
 
276
+ {/* These are self-contained components — do NOT read/rewrite their source files */}
248
277
  <FolderBrowser
249
278
  open={browsing}
250
279
  onOpenChange={setBrowsing}
@@ -43,8 +43,8 @@ export const NetworkHealth = memo(function NetworkHealth() {
43
43
  <span className="absolute inset-0 rounded-sm" style={{ background: signalReachable ? HEX.success : HEX.danger }} />
44
44
  {signalReachable && (
45
45
  <span
46
- className="absolute inset-[-2px] rounded-sm"
47
- style={{ background: HEX.success, opacity: 0.15, animation: 'node-pulse-bar 2s ease-in-out infinite' }}
46
+ className="absolute inset-[-2px] rounded-sm opacity-15 [animation:node-pulse-bar_2s_ease-in-out_infinite]"
47
+ style={{ background: HEX.success }}
48
48
  />
49
49
  )}
50
50
  </span>
@@ -156,7 +156,7 @@ const TpsChart = memo(function TpsChart() {
156
156
  {latestTps > 0 ? `${latestTps.toFixed(1)} t/s` : '\u2014'}
157
157
  </span>
158
158
  </div>
159
- <div ref={containerRef} className="relative flex-1 min-h-0" style={{ minHeight: 120 }}>
159
+ <div ref={containerRef} className="relative flex-1 min-h-[120px]">
160
160
  {chartData.length < 2 ? (
161
161
  <div className="absolute inset-0 flex items-center justify-center">
162
162
  <span className="text-xs font-mono text-text-3">Collecting performance data\u2026</span>
@@ -353,8 +353,8 @@ function GaugeBar({ value, max, peakValue, color, label, unit }) {
353
353
  />
354
354
  {peakPct != null && (
355
355
  <div
356
- className="absolute top-0 h-full w-px"
357
- style={{ left: `${peakPct}%`, background: HEX.danger, opacity: 0.7 }}
356
+ className="absolute top-0 h-full w-px opacity-70"
357
+ style={{ left: `${peakPct}%`, background: HEX.danger }}
358
358
  title={`Peak: ${Math.round(peakValue)} ${unit || ''}`}
359
359
  />
360
360
  )}
@@ -548,7 +548,7 @@ export const PerformanceDashboard = memo(function PerformanceDashboard({ active
548
548
  <div className="p-4 grid grid-cols-1 lg:grid-cols-2 gap-3">
549
549
  {/* Left column */}
550
550
  <div className="flex flex-col gap-3">
551
- <div className="rounded-md border border-border-subtle bg-surface-0 overflow-hidden" style={{ minHeight: 200 }}>
551
+ <div className="rounded-md border border-border-subtle bg-surface-0 overflow-hidden min-h-[200px]">
552
552
  <TpsChart />
553
553
  </div>
554
554