groove-dev 0.27.169 → 0.27.171

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 (40) hide show
  1. package/default/Screenshot_2026-05-29_at_11.16.28_PM.png +0 -0
  2. package/node_modules/@groove-dev/cli/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/package.json +1 -1
  4. package/node_modules/@groove-dev/daemon/src/routes/files.js +18 -5
  5. package/node_modules/@groove-dev/daemon/src/tunnel-manager.js +16 -6
  6. package/node_modules/@groove-dev/gui/dist/assets/index-BrMU-6gi.css +1 -0
  7. package/node_modules/@groove-dev/gui/dist/assets/{index-BcWo4sq-.js → index-BsCp-oqa.js} +226 -221
  8. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  9. package/node_modules/@groove-dev/gui/package.json +1 -1
  10. package/node_modules/@groove-dev/gui/src/components/agents/folder-browser.jsx +39 -11
  11. package/node_modules/@groove-dev/gui/src/components/agents/recommended-team-card.jsx +300 -0
  12. package/node_modules/@groove-dev/gui/src/components/fleet/fleet-content.jsx +18 -4
  13. package/node_modules/@groove-dev/gui/src/components/fleet/fleet-sidebar.jsx +125 -44
  14. package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +4 -4
  15. package/node_modules/@groove-dev/gui/src/components/settings/quick-connect.jsx +64 -33
  16. package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +74 -72
  17. package/node_modules/@groove-dev/gui/src/views/agents.jsx +2 -11
  18. package/node_modules/@groove-dev/gui/src/views/editor.jsx +63 -2
  19. package/node_modules/@groove-dev/gui/src/views/settings.jsx +2 -1
  20. package/package.json +1 -1
  21. package/packages/cli/package.json +1 -1
  22. package/packages/daemon/package.json +1 -1
  23. package/packages/daemon/src/routes/files.js +18 -5
  24. package/packages/daemon/src/tunnel-manager.js +16 -6
  25. package/packages/gui/dist/assets/index-BrMU-6gi.css +1 -0
  26. package/packages/gui/dist/assets/{index-BcWo4sq-.js → index-BsCp-oqa.js} +226 -221
  27. package/packages/gui/dist/index.html +2 -2
  28. package/packages/gui/package.json +1 -1
  29. package/packages/gui/src/components/agents/folder-browser.jsx +39 -11
  30. package/packages/gui/src/components/agents/recommended-team-card.jsx +300 -0
  31. package/packages/gui/src/components/fleet/fleet-content.jsx +18 -4
  32. package/packages/gui/src/components/fleet/fleet-sidebar.jsx +125 -44
  33. package/packages/gui/src/components/layout/breadcrumb-bar.jsx +4 -4
  34. package/packages/gui/src/components/settings/quick-connect.jsx +64 -33
  35. package/packages/gui/src/components/settings/ssh-wizard.jsx +74 -72
  36. package/packages/gui/src/views/agents.jsx +2 -11
  37. package/packages/gui/src/views/editor.jsx +63 -2
  38. package/packages/gui/src/views/settings.jsx +2 -1
  39. package/node_modules/@groove-dev/gui/dist/assets/index-BvXojcnr.css +0 -1
  40. package/packages/gui/dist/assets/index-BvXojcnr.css +0 -1
@@ -1,6 +1,6 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
2
  import { useRef, useCallback, useMemo, useState } from 'react';
3
- import { Search, X, ChevronRight, Plus, Trash2 } from 'lucide-react';
3
+ import { Search, X, ChevronRight, Plus, Trash2, Pencil, Users, Check } from 'lucide-react';
4
4
  import { useGrooveStore } from '../../stores/groove';
5
5
  import { cn } from '../../lib/cn';
6
6
  import { FleetAgentRow } from './fleet-agent-row';
@@ -21,9 +21,16 @@ export function FleetSidebar({ width }) {
21
21
  const toggleCollapsed = useGrooveStore((s) => s.fleetToggleTeamCollapsed);
22
22
  const setSidebarWidth = useGrooveStore((s) => s.fleetSetSidebarWidth);
23
23
  const deleteTeam = useGrooveStore((s) => s.deleteTeam);
24
+ const createTeam = useGrooveStore((s) => s.createTeam);
25
+ const renameTeam = useGrooveStore((s) => s.renameTeam);
24
26
  const openDetail = useGrooveStore((s) => s.openDetail);
27
+ const addToast = useGrooveStore((s) => s.addToast);
25
28
 
26
29
  const [confirmDeleteTeam, setConfirmDeleteTeam] = useState(null);
30
+ const [renamingTeamId, setRenamingTeamId] = useState(null);
31
+ const [renameValue, setRenameValue] = useState('');
32
+ const [creatingTeam, setCreatingTeam] = useState(false);
33
+ const [newTeamName, setNewTeamName] = useState('');
27
34
 
28
35
  const dragging = useRef(false);
29
36
  const startX = useRef(0);
@@ -99,6 +106,31 @@ export function FleetSidebar({ width }) {
99
106
  openDetail({ type: 'spawn', presetTeamId: teamId });
100
107
  }
101
108
 
109
+ async function handleCreateTeam() {
110
+ const name = newTeamName.trim();
111
+ if (!name) return;
112
+ try {
113
+ await createTeam(name);
114
+ setNewTeamName('');
115
+ setCreatingTeam(false);
116
+ } catch { /* toast handles */ }
117
+ }
118
+
119
+ async function handleRename(teamId) {
120
+ const name = renameValue.trim();
121
+ if (!name) { setRenamingTeamId(null); return; }
122
+ try {
123
+ await renameTeam(teamId, name);
124
+ } catch { /* toast handles */ }
125
+ setRenamingTeamId(null);
126
+ }
127
+
128
+ function startRename(e, team) {
129
+ e.stopPropagation();
130
+ setRenamingTeamId(team.id);
131
+ setRenameValue(team.name);
132
+ }
133
+
102
134
  return (
103
135
  <div
104
136
  className="flex-shrink-0 flex flex-col bg-surface-1 border-r border-border relative h-full"
@@ -141,49 +173,73 @@ export function FleetSidebar({ width }) {
141
173
  'w-full flex items-center gap-1 px-2 py-1.5 rounded-md hover:bg-surface-2 transition-colors group',
142
174
  isConfirming && 'bg-danger/10 hover:bg-danger/20',
143
175
  )}>
144
- <button
145
- onClick={() => toggleCollapsed(team.id)}
146
- className="flex items-center gap-1.5 flex-1 min-w-0 cursor-pointer"
147
- >
148
- <ChevronRight
149
- size={14}
150
- className={cn(
151
- 'text-text-4 transition-transform flex-shrink-0',
152
- !isCollapsed && 'rotate-90',
153
- )}
154
- />
155
- <span className={cn(
156
- 'text-xs font-medium font-sans truncate text-left',
157
- isConfirming ? 'text-danger' : 'text-text-1',
158
- )}>
159
- {isConfirming ? 'Click again to delete' : team.name}
160
- </span>
161
- </button>
162
-
163
- {/* Hover actions + meta — stacked in same space */}
164
- <div className="flex items-center gap-0.5 flex-shrink-0">
165
- <button
166
- onClick={(e) => handleSpawnToTeam(e, team.id)}
167
- className="opacity-0 group-hover:opacity-100 p-0.5 rounded text-text-4 hover:text-accent transition-opacity cursor-pointer"
168
- title="Spawn agent to team"
169
- >
170
- <Plus size={14} />
171
- </button>
172
- <button
173
- onClick={(e) => handleDeleteTeam(e, team.id)}
174
- className={cn(
175
- 'opacity-0 group-hover:opacity-100 p-0.5 rounded transition-opacity cursor-pointer',
176
- isConfirming ? 'text-danger' : 'text-text-4 hover:text-danger',
177
- )}
178
- title="Delete team"
179
- >
180
- <Trash2 size={12} />
181
- </button>
182
- <span className="group-hover:opacity-0 text-2xs text-text-4 font-mono transition-opacity">
183
- {allTeamAgents.length}
184
- </span>
185
- <span className={cn('w-1.5 h-1.5 rounded-full flex-shrink-0', teamStatusDot(allTeamAgents))} />
186
- </div>
176
+ {renamingTeamId === team.id ? (
177
+ <div className="flex items-center gap-1.5 flex-1 min-w-0 pl-1">
178
+ <input
179
+ value={renameValue}
180
+ onChange={(e) => setRenameValue(e.target.value)}
181
+ onKeyDown={(e) => { if (e.key === 'Enter') handleRename(team.id); if (e.key === 'Escape') setRenamingTeamId(null); }}
182
+ autoFocus
183
+ className="flex-1 min-w-0 h-6 px-1.5 text-xs bg-surface-3 border border-accent/40 rounded text-text-0 font-sans focus:outline-none"
184
+ />
185
+ <button onClick={() => handleRename(team.id)} className="p-0.5 text-accent cursor-pointer"><Check size={12} /></button>
186
+ <button onClick={() => setRenamingTeamId(null)} className="p-0.5 text-text-4 hover:text-text-1 cursor-pointer"><X size={12} /></button>
187
+ </div>
188
+ ) : (
189
+ <>
190
+ <button
191
+ onClick={() => toggleCollapsed(team.id)}
192
+ onDoubleClick={(e) => startRename(e, team)}
193
+ className="flex items-center gap-1.5 flex-1 min-w-0 cursor-pointer"
194
+ >
195
+ <ChevronRight
196
+ size={14}
197
+ className={cn(
198
+ 'text-text-4 transition-transform flex-shrink-0',
199
+ !isCollapsed && 'rotate-90',
200
+ )}
201
+ />
202
+ <span className={cn(
203
+ 'text-xs font-medium font-sans truncate text-left',
204
+ isConfirming ? 'text-danger' : 'text-text-1',
205
+ )}>
206
+ {isConfirming ? 'Click again to delete' : team.name}
207
+ </span>
208
+ </button>
209
+
210
+ {/* Hover actions + meta — stacked in same space */}
211
+ <div className="flex items-center gap-0.5 flex-shrink-0">
212
+ <button
213
+ onClick={(e) => startRename(e, team)}
214
+ className="opacity-0 group-hover:opacity-100 p-0.5 rounded text-text-4 hover:text-accent transition-opacity cursor-pointer"
215
+ title="Rename team"
216
+ >
217
+ <Pencil size={11} />
218
+ </button>
219
+ <button
220
+ onClick={(e) => handleSpawnToTeam(e, team.id)}
221
+ className="opacity-0 group-hover:opacity-100 p-0.5 rounded text-text-4 hover:text-accent transition-opacity cursor-pointer"
222
+ title="Spawn agent to team"
223
+ >
224
+ <Plus size={14} />
225
+ </button>
226
+ <button
227
+ onClick={(e) => handleDeleteTeam(e, team.id)}
228
+ className={cn(
229
+ 'opacity-0 group-hover:opacity-100 p-0.5 rounded transition-opacity cursor-pointer',
230
+ isConfirming ? 'text-danger' : 'text-text-4 hover:text-danger',
231
+ )}
232
+ title="Delete team"
233
+ >
234
+ <Trash2 size={12} />
235
+ </button>
236
+ <span className="group-hover:opacity-0 text-2xs text-text-4 font-mono transition-opacity">
237
+ {allTeamAgents.length}
238
+ </span>
239
+ <span className={cn('w-1.5 h-1.5 rounded-full flex-shrink-0', teamStatusDot(allTeamAgents))} />
240
+ </div>
241
+ </>
242
+ )}
187
243
  </div>
188
244
 
189
245
  {/* Agent rows */}
@@ -206,6 +262,31 @@ export function FleetSidebar({ width }) {
206
262
  )}
207
263
  </div>
208
264
 
265
+ {/* Create team */}
266
+ <div className="px-2.5 py-2 border-t border-border-subtle flex-shrink-0">
267
+ {creatingTeam ? (
268
+ <div className="flex items-center gap-1.5">
269
+ <input
270
+ value={newTeamName}
271
+ onChange={(e) => setNewTeamName(e.target.value)}
272
+ onKeyDown={(e) => { if (e.key === 'Enter') handleCreateTeam(); if (e.key === 'Escape') { setCreatingTeam(false); setNewTeamName(''); } }}
273
+ placeholder="Team name..."
274
+ autoFocus
275
+ className="flex-1 min-w-0 h-7 px-2 text-xs bg-surface-3 border border-accent/40 rounded text-text-0 font-sans placeholder:text-text-4 focus:outline-none"
276
+ />
277
+ <button onClick={handleCreateTeam} disabled={!newTeamName.trim()} className="p-1 text-accent cursor-pointer disabled:opacity-30"><Check size={13} /></button>
278
+ <button onClick={() => { setCreatingTeam(false); setNewTeamName(''); }} className="p-1 text-text-4 hover:text-text-1 cursor-pointer"><X size={13} /></button>
279
+ </div>
280
+ ) : (
281
+ <button
282
+ onClick={() => setCreatingTeam(true)}
283
+ className="flex items-center gap-1.5 text-xs text-text-3 hover:text-accent font-sans font-medium cursor-pointer transition-colors"
284
+ >
285
+ <Users size={12} /> New Team
286
+ </button>
287
+ )}
288
+ </div>
289
+
209
290
  {/* Resize handle */}
210
291
  <div
211
292
  className="absolute right-0 top-0 bottom-0 w-1 cursor-col-resize hover:bg-accent/30 transition-colors z-10"
@@ -140,13 +140,13 @@ export function BreadcrumbBar({
140
140
  const [instanceName, setInstanceName] = useState(null);
141
141
 
142
142
  useEffect(() => {
143
- if (window.groove?.getInstanceInfo) {
143
+ const urlParam = new URLSearchParams(window.location.search).get('instance');
144
+ if (urlParam) {
145
+ setInstanceName(urlParam);
146
+ } else if (window.groove?.getInstanceInfo) {
144
147
  window.groove.getInstanceInfo().then(info => {
145
148
  if (info?.name) setInstanceName(info.name);
146
149
  });
147
- } else {
148
- const param = new URLSearchParams(window.location.search).get('instance');
149
- if (param) setInstanceName(param);
150
150
  }
151
151
  }, []);
152
152
 
@@ -17,12 +17,14 @@ export function QuickConnect() {
17
17
  const addToast = useGrooveStore((s) => s.addToast);
18
18
  const tunnelStep = useGrooveStore((s) => s.tunnelConnectStep);
19
19
  const [connectingId, setConnectingId] = useState(null);
20
+ const [openingServer, setOpeningServer] = useState(null);
20
21
  const [showWizard, setShowWizard] = useState(false);
21
22
  const wizardTunnelId = useRef(null);
22
23
 
23
24
  useEffect(() => {
24
25
  if (open) {
25
26
  setShowWizard(false);
27
+ setOpeningServer(null);
26
28
  useGrooveStore.getState().fetchTunnels();
27
29
  }
28
30
  }, [open]);
@@ -34,14 +36,15 @@ export function QuickConnect() {
34
36
  try {
35
37
  await useGrooveStore.getState().connectTunnel(id);
36
38
  const tunnel = savedTunnels.find((t) => t.id === id);
39
+ setConnectingId(null);
40
+ setOpeningServer({ name: tunnel?.name || 'Remote' });
37
41
  if (tunnel?.host) {
38
42
  addToast('info', `Add ${tunnel.host} to Federation Whitelist?`, '', {
39
43
  label: 'Add',
40
44
  onClick: () => useGrooveStore.getState().addToWhitelist(tunnel.host),
41
45
  });
42
46
  }
43
- setConnectingId(null);
44
- toggle();
47
+ setTimeout(() => { setOpeningServer(null); toggle(); }, 4000);
45
48
  return;
46
49
  } catch (err) {
47
50
  const detail = err?.message || 'Unknown error';
@@ -90,32 +93,55 @@ export function QuickConnect() {
90
93
  exit={{ opacity: 0, y: -10, scale: 0.98 }}
91
94
  transition={{ duration: 0.15 }}
92
95
  className={cn(
93
- 'fixed top-[15%] left-1/2 -translate-x-1/2 z-50 bg-surface-1 border border-border rounded-lg shadow-2xl overflow-hidden',
94
- showWizard ? 'w-[520px]' : 'w-[400px]',
96
+ 'fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50 bg-[#24282f] border border-[#2c313a] rounded-lg shadow-2xl overflow-hidden',
97
+ showWizard ? 'w-[680px]' : 'w-[480px]',
95
98
  )}
96
99
  >
97
100
  {/* Header */}
98
- <div className="flex items-center justify-between px-4 py-3 border-b border-border-subtle">
99
- <div className="flex items-center gap-2">
101
+ <div className="flex items-center justify-between px-5 py-4 border-b border-[#2c313a]">
102
+ <div className="flex items-center gap-3">
100
103
  {showWizard && (
101
104
  <button
102
105
  onClick={() => setShowWizard(false)}
103
- className="p-1 -ml-1 text-text-4 hover:text-text-1 cursor-pointer transition-colors"
106
+ className="p-1.5 -ml-1 text-[#6e7681] hover:text-[#e6e8ed] cursor-pointer transition-colors"
104
107
  >
105
- <ArrowLeft size={14} />
108
+ <ArrowLeft size={16} />
106
109
  </button>
107
110
  )}
108
- <Radio size={15} className="text-accent" />
109
- <span className="text-sm font-semibold text-text-0 font-sans">
111
+ <Radio size={17} className="text-[#33afbc]" />
112
+ <span className="text-base font-semibold text-[#e6e8ed]" style={{ fontFamily: "-apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif", letterSpacing: '-0.2px' }}>
110
113
  {showWizard ? (wizardTunnelId.current ? 'Connection Setup' : 'Add Connection') : 'Quick Connect'}
111
114
  </span>
112
115
  </div>
113
- <button onClick={handleClose} className="p-1 text-text-4 hover:text-text-1 cursor-pointer transition-colors">
114
- <X size={14} />
116
+ <button onClick={handleClose} className="p-1.5 text-[#6e7681] hover:text-[#e6e8ed] cursor-pointer transition-colors">
117
+ <X size={16} />
115
118
  </button>
116
119
  </div>
117
120
 
118
- {showWizard ? (
121
+ {openingServer ? (
122
+ <div className="px-6 py-12 text-center">
123
+ <div className="relative w-14 h-14 mx-auto mb-5">
124
+ <span className="absolute inset-0 rounded-full border-2 border-[#33afbc]/20 animate-ping" style={{ animationDuration: '2s' }} />
125
+ <span className="absolute inset-0 rounded-full border-2 border-transparent border-t-[#33afbc] animate-spin" style={{ animationDuration: '1s' }} />
126
+ <span className="absolute inset-[6px] rounded-full bg-[#33afbc]/8 flex items-center justify-center">
127
+ <Server size={18} className="text-[#33afbc]" />
128
+ </span>
129
+ </div>
130
+ <p className="text-base font-semibold text-[#e6e8ed] mb-1.5" style={{ fontFamily: "-apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif" }}>
131
+ Opening {openingServer.name}
132
+ </p>
133
+ <p className="text-sm text-[#6e7681]" style={{ fontFamily: "-apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif" }}>
134
+ Loading remote dashboard...
135
+ </p>
136
+ <button
137
+ onClick={() => { setOpeningServer(null); toggle(); }}
138
+ className="mt-6 text-xs text-[#6e7681] hover:text-[#e6e8ed] cursor-pointer transition-colors"
139
+ style={{ fontFamily: "-apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif" }}
140
+ >
141
+ Dismiss
142
+ </button>
143
+ </div>
144
+ ) : showWizard ? (
119
145
  <SSHWizard
120
146
  server={wizardTunnelId.current ? savedTunnels.find((t) => t.id === wizardTunnelId.current) || null : null}
121
147
  onSave={async (data) => {
@@ -145,45 +171,49 @@ export function QuickConnect() {
145
171
  ) : (
146
172
  <>
147
173
  {/* Server list */}
148
- <div className="overflow-y-auto max-h-[320px] py-1">
174
+ <div className="overflow-y-auto max-h-[400px] py-2">
149
175
  {savedTunnels.length === 0 ? (
150
- <div className="px-4 py-8 text-center">
151
- <Server size={24} className="text-text-4 mx-auto mb-2" />
152
- <p className="text-sm text-text-3 font-sans">No saved servers</p>
153
- <p className="text-2xs text-text-4 font-sans mt-1">Add a connection to get started.</p>
154
- <Button
155
- variant="primary"
156
- size="sm"
176
+ <div className="px-6 py-10 text-center">
177
+ <Server size={32} className="text-[#6e7681] mx-auto mb-3" />
178
+ <p className="text-base font-semibold text-[#e6e8ed]" style={{ fontFamily: "-apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif" }}>No saved servers</p>
179
+ <p className="text-xs text-[#6e7681] mt-1.5" style={{ fontFamily: "-apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif" }}>Add a connection to get started.</p>
180
+ <button
157
181
  onClick={() => { wizardTunnelId.current = null; setShowWizard(true); }}
158
- className="h-8 text-xs gap-1.5 mt-3"
182
+ className="inline-flex items-center gap-1.5 h-9 px-5 mt-4 rounded bg-[#33afbc] text-[#0a0c10] text-sm font-semibold cursor-pointer transition-opacity hover:opacity-90"
183
+ style={{ fontFamily: "-apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif" }}
159
184
  >
160
- <Plus size={12} /> Add Connection
161
- </Button>
185
+ <Plus size={14} /> Add Connection
186
+ </button>
162
187
  </div>
163
188
  ) : (
164
189
  savedTunnels.map((server) => (
165
190
  <div
166
191
  key={server.id}
167
192
  className={cn(
168
- 'w-full flex items-center gap-3 px-4 py-2.5 transition-colors',
169
- 'hover:bg-surface-5',
193
+ 'w-full flex items-center gap-4 px-5 py-3.5 transition-colors',
194
+ 'hover:bg-[#2c313a]',
170
195
  connectingId === server.id && 'opacity-60 pointer-events-none',
171
196
  )}
172
197
  >
173
- <Server size={15} className={server.active ? 'text-success' : 'text-text-4'} />
198
+ <div className={cn(
199
+ 'w-10 h-10 rounded flex items-center justify-center flex-shrink-0',
200
+ server.active ? 'bg-[#33afbc]/10' : 'bg-[rgba(255,255,255,0.04)]',
201
+ )}>
202
+ <Server size={18} className={server.active ? 'text-[#33afbc]' : 'text-[#8b95a5]'} />
203
+ </div>
174
204
  <button
175
205
  onClick={() => server.active ? handleOpenRemote(server) : handleConnect(server.id)}
176
206
  disabled={connectingId === server.id}
177
207
  className="flex-1 min-w-0 text-left cursor-pointer"
178
208
  >
179
209
  <div className="flex items-center gap-2">
180
- <span className="text-sm font-medium text-text-0 font-sans truncate">{server.name}</span>
210
+ <span className="text-sm font-semibold text-[#e6e8ed] truncate" style={{ fontFamily: "-apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif", letterSpacing: '-0.2px' }}>{server.name}</span>
181
211
  {server.active && <StatusDot status="running" size="sm" />}
182
212
  {server.remoteVersion && (
183
- <span className="text-2xs font-mono text-text-4 ml-1">v{server.remoteVersion}</span>
213
+ <span className="text-xs text-[#6e7681] ml-1" style={{ fontFamily: "ui-monospace, 'SF Mono', Monaco, monospace" }}>v{server.remoteVersion}</span>
184
214
  )}
185
215
  </div>
186
- <span className="text-2xs text-text-4 font-mono">{server.user}@{server.host}</span>
216
+ <span className="text-xs text-[#6e7681]" style={{ fontFamily: "ui-monospace, 'SF Mono', Monaco, monospace" }}>{server.user}@{server.host}</span>
187
217
  </button>
188
218
  <div className="flex items-center gap-1.5 flex-shrink-0">
189
219
  {connectingId === server.id ? (
@@ -267,12 +297,13 @@ export function QuickConnect() {
267
297
  </div>
268
298
 
269
299
  {/* Footer with Add button */}
270
- <div className="px-4 py-2.5 border-t border-border-subtle">
300
+ <div className="px-5 py-3.5 border-t border-[#2c313a]">
271
301
  <button
272
302
  onClick={() => { wizardTunnelId.current = null; setShowWizard(true); }}
273
- className="flex items-center gap-1.5 text-2xs text-accent hover:text-accent/80 font-sans font-medium cursor-pointer transition-colors"
303
+ className="flex items-center gap-2 text-sm text-[#33afbc] hover:opacity-80 font-semibold cursor-pointer transition-opacity"
304
+ style={{ fontFamily: "-apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif" }}
274
305
  >
275
- <Plus size={10} /> Add new connection
306
+ <Plus size={14} /> Add new connection
276
307
  </button>
277
308
  </div>
278
309
  </>