@geminilight/mindos 0.6.17 → 0.6.19

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.
@@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation';
5
5
  import {
6
6
  Check, X, Loader2, Sparkles, AlertCircle, Undo2,
7
7
  ChevronDown, FilePlus, FileEdit, ExternalLink,
8
+ Maximize2, Minimize2, FileIcon,
8
9
  } from 'lucide-react';
9
10
  import { useLocale } from '@/lib/LocaleContext';
10
11
  import type { useAiOrganize } from '@/hooks/useAiOrganize';
@@ -84,6 +85,7 @@ export default function OrganizeToast({
84
85
  const { elapsed, displayHint } = useOrganizeTimer(isOrganizing, aiOrganize.stageHint);
85
86
 
86
87
  const [expanded, setExpanded] = useState(false);
88
+ const [maximized, setMaximized] = useState(false);
87
89
  const [undoing, setUndoing] = useState(false);
88
90
  const dismissTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
89
91
  const historyIdRef = useRef<string | null>(null);
@@ -197,7 +199,211 @@ export default function OrganizeToast({
197
199
 
198
200
  if (!isActive) return null;
199
201
 
200
- // Expanded panel (file list with per-file undo)
202
+ // ── Shared file-change row renderer ──
203
+ function renderChangeRow(c: typeof aiOrganize.changes[number], idx: number) {
204
+ const wasUndone = c.undone;
205
+ const undoable = aiOrganize.canUndo(c.path);
206
+ const fileName = c.path.split('/').pop() ?? c.path;
207
+ return (
208
+ <div
209
+ key={`${c.path}-${idx}`}
210
+ className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm transition-colors ${wasUndone ? 'bg-muted/30 opacity-50' : 'bg-muted/50'}`}
211
+ >
212
+ {wasUndone ? (
213
+ <Undo2 size={14} className="text-muted-foreground shrink-0" />
214
+ ) : c.action === 'create' ? (
215
+ <FilePlus size={14} className="text-success shrink-0" />
216
+ ) : (
217
+ <FileEdit size={14} className="text-[var(--amber)] shrink-0" />
218
+ )}
219
+ <span className={`truncate flex-1 ${wasUndone ? 'line-through text-muted-foreground' : 'text-foreground'}`}>
220
+ {fileName}
221
+ </span>
222
+ {wasUndone ? (
223
+ <span className="text-xs text-muted-foreground shrink-0">{fi.organizeUndone as string}</span>
224
+ ) : (
225
+ <span className={`text-xs shrink-0 ${c.ok ? 'text-muted-foreground' : 'text-error'}`}>
226
+ {!c.ok ? fi.organizeFailed as string
227
+ : c.action === 'create' ? fi.organizeCreated as string
228
+ : fi.organizeUpdated as string}
229
+ </span>
230
+ )}
231
+ {undoable && (
232
+ <button
233
+ type="button"
234
+ onClick={() => handleUndoOne(c.path)}
235
+ disabled={undoing}
236
+ className="text-2xs text-muted-foreground/60 hover:text-foreground transition-colors shrink-0 px-1 disabled:opacity-40"
237
+ title={fi.organizeUndoOne as string}
238
+ >
239
+ <Undo2 size={12} />
240
+ </button>
241
+ )}
242
+ {c.ok && !c.undone && (
243
+ <button
244
+ type="button"
245
+ onClick={() => handleViewFile(c.path)}
246
+ className="text-2xs text-muted-foreground/60 hover:text-[var(--amber)] transition-colors shrink-0 px-1"
247
+ title={fi.organizeViewFile as string}
248
+ >
249
+ <ExternalLink size={12} />
250
+ </button>
251
+ )}
252
+ </div>
253
+ );
254
+ }
255
+
256
+ // ── Shared footer actions ──
257
+ function renderActions() {
258
+ return (
259
+ <div className="flex items-center justify-end gap-3 px-4 py-3 border-t border-border">
260
+ {isDone && aiOrganize.hasAnyUndoable && (
261
+ <button
262
+ onClick={handleUndoAll}
263
+ disabled={undoing}
264
+ className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors px-2 py-1.5 disabled:opacity-50"
265
+ >
266
+ {undoing ? <Loader2 size={12} className="animate-spin" /> : <Undo2 size={12} />}
267
+ {fi.organizeUndoAll as string}
268
+ </button>
269
+ )}
270
+ <button
271
+ onClick={handleDismiss}
272
+ className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium bg-[var(--amber)] text-[var(--amber-foreground)] hover:opacity-90 transition-all"
273
+ >
274
+ <Check size={12} />
275
+ {fi.organizeDone as string}
276
+ </button>
277
+ </div>
278
+ );
279
+ }
280
+
281
+ // ═══════════════════════════════════════════════════════════════════════════
282
+ // Maximized Modal — full detail view with source files, summary, changes
283
+ // ═══════════════════════════════════════════════════════════════════════════
284
+ if (maximized) {
285
+ return (
286
+ <div
287
+ className="fixed inset-0 z-50 overlay-backdrop flex items-center justify-center p-4"
288
+ onClick={(e) => { if (e.target === e.currentTarget) setMaximized(false); }}
289
+ >
290
+ <div
291
+ className="w-full max-w-xl max-h-[80vh] flex flex-col bg-card rounded-xl shadow-xl border border-border animate-in fade-in-0 zoom-in-95 duration-200"
292
+ onClick={handleUserAction}
293
+ role="dialog"
294
+ aria-modal="true"
295
+ aria-label={fi.organizeDetailTitle as string}
296
+ >
297
+ {/* Header */}
298
+ <div className="flex items-center justify-between px-5 pt-5 pb-3 shrink-0">
299
+ <div className="flex items-center gap-2">
300
+ {isOrganizing ? (
301
+ <div className="relative shrink-0">
302
+ <Sparkles size={16} className="text-[var(--amber)]" />
303
+ <Loader2 size={10} className="absolute -bottom-0.5 -right-0.5 text-[var(--amber)] animate-spin" />
304
+ </div>
305
+ ) : isDone ? (
306
+ <Check size={16} className="text-success" />
307
+ ) : (
308
+ <AlertCircle size={16} className="text-error" />
309
+ )}
310
+ <h2 className="text-base font-semibold text-foreground">
311
+ {fi.organizeDetailTitle as string}
312
+ </h2>
313
+ {isOrganizing && (
314
+ <span className="text-xs text-muted-foreground/60 tabular-nums">
315
+ {(fi.organizeElapsed as (s: number) => string)(elapsed)}
316
+ </span>
317
+ )}
318
+ </div>
319
+ <div className="flex items-center gap-1">
320
+ <button
321
+ type="button"
322
+ onClick={() => setMaximized(false)}
323
+ className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
324
+ title={fi.organizeMinimizeModal as string}
325
+ >
326
+ <Minimize2 size={14} />
327
+ </button>
328
+ <button
329
+ type="button"
330
+ onClick={handleDismiss}
331
+ className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
332
+ >
333
+ <X size={14} />
334
+ </button>
335
+ </div>
336
+ </div>
337
+
338
+ {/* Scrollable body */}
339
+ <div className="flex-1 min-h-0 overflow-y-auto px-5 pb-2 space-y-4">
340
+ {/* Live progress during organizing */}
341
+ {isOrganizing && (
342
+ <div className="flex items-center gap-2 px-3 py-2.5 rounded-lg bg-[var(--amber-subtle)] border border-[var(--amber-dim)]">
343
+ <Loader2 size={14} className="text-[var(--amber)] animate-spin shrink-0" />
344
+ <span className="text-xs text-foreground">{stageText(t, displayHint)}</span>
345
+ </div>
346
+ )}
347
+
348
+ {/* Source files */}
349
+ {aiOrganize.sourceFileNames.length > 0 && (
350
+ <div>
351
+ <h3 className="text-xs font-medium text-muted-foreground mb-1.5">
352
+ {fi.organizeSourceFiles as string}
353
+ </h3>
354
+ <div className="flex flex-wrap gap-1.5">
355
+ {aiOrganize.sourceFileNames.map((name, i) => (
356
+ <span key={i} className="inline-flex items-center gap-1 px-2 py-1 rounded-md bg-muted/60 text-xs text-foreground">
357
+ <FileIcon size={11} className="text-muted-foreground shrink-0" />
358
+ {name}
359
+ </span>
360
+ ))}
361
+ </div>
362
+ </div>
363
+ )}
364
+
365
+ {/* AI Summary */}
366
+ {(aiOrganize.summary || isOrganizing) && (
367
+ <div>
368
+ <h3 className="text-xs font-medium text-muted-foreground mb-1.5">
369
+ {fi.organizeSummaryLabel as string}
370
+ </h3>
371
+ <div className="px-3 py-2.5 rounded-lg bg-muted/30 border border-border text-sm text-foreground leading-relaxed whitespace-pre-wrap">
372
+ {aiOrganize.summary || (fi.organizeNoSummary as string)}
373
+ </div>
374
+ </div>
375
+ )}
376
+
377
+ {/* File changes */}
378
+ {aiOrganize.changes.length > 0 && (
379
+ <div>
380
+ <h3 className="text-xs font-medium text-muted-foreground mb-1.5">
381
+ {(fi.organizeChangesLabel as (n: number) => string)(aiOrganize.changes.length)}
382
+ </h3>
383
+ <div className="space-y-0.5">
384
+ {aiOrganize.changes.map((c, idx) => renderChangeRow(c, idx))}
385
+ </div>
386
+ </div>
387
+ )}
388
+
389
+ {/* Error detail */}
390
+ {isError && (
391
+ <div className="px-3 py-2.5 rounded-lg bg-error/5 border border-error/20 text-xs text-error">
392
+ {aiOrganize.error}
393
+ </div>
394
+ )}
395
+ </div>
396
+
397
+ {/* Footer actions */}
398
+ {(isDone || isError) && renderActions()}
399
+ </div>
400
+ </div>
401
+ );
402
+ }
403
+
404
+ // ═══════════════════════════════════════════════════════════════════════════
405
+ // Expanded panel (file list with per-file undo) — bottom toast size
406
+ // ═══════════════════════════════════════════════════════════════════════════
201
407
  if (expanded && (isDone || isError)) {
202
408
  return (
203
409
  <div
@@ -212,71 +418,29 @@ export default function OrganizeToast({
212
418
  {isDone ? fi.organizeReviewTitle as string : fi.organizeErrorTitle as string}
213
419
  </span>
214
420
  </div>
215
- <button
216
- type="button"
217
- onClick={() => setExpanded(false)}
218
- className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
219
- >
220
- <ChevronDown size={14} />
221
- </button>
421
+ <div className="flex items-center gap-1">
422
+ <button
423
+ type="button"
424
+ onClick={() => { setExpanded(false); setMaximized(true); }}
425
+ className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
426
+ title={fi.organizeDetailTitle as string}
427
+ >
428
+ <Maximize2 size={13} />
429
+ </button>
430
+ <button
431
+ type="button"
432
+ onClick={() => setExpanded(false)}
433
+ className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
434
+ >
435
+ <ChevronDown size={14} />
436
+ </button>
437
+ </div>
222
438
  </div>
223
439
 
224
440
  {/* File list */}
225
441
  {isDone && (
226
442
  <div className="max-h-[240px] overflow-y-auto p-2 space-y-0.5">
227
- {aiOrganize.changes.map((c, idx) => {
228
- const wasUndone = c.undone;
229
- const undoable = aiOrganize.canUndo(c.path);
230
- const fileName = c.path.split('/').pop() ?? c.path;
231
-
232
- return (
233
- <div
234
- key={`${c.path}-${idx}`}
235
- className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm transition-colors ${wasUndone ? 'bg-muted/30 opacity-50' : 'bg-muted/50'}`}
236
- >
237
- {wasUndone ? (
238
- <Undo2 size={14} className="text-muted-foreground shrink-0" />
239
- ) : c.action === 'create' ? (
240
- <FilePlus size={14} className="text-success shrink-0" />
241
- ) : (
242
- <FileEdit size={14} className="text-[var(--amber)] shrink-0" />
243
- )}
244
- <span className={`truncate flex-1 ${wasUndone ? 'line-through text-muted-foreground' : 'text-foreground'}`}>
245
- {fileName}
246
- </span>
247
- {wasUndone ? (
248
- <span className="text-xs text-muted-foreground shrink-0">{fi.organizeUndone as string}</span>
249
- ) : (
250
- <span className={`text-xs shrink-0 ${c.ok ? 'text-muted-foreground' : 'text-error'}`}>
251
- {!c.ok ? fi.organizeFailed as string
252
- : c.action === 'create' ? fi.organizeCreated as string
253
- : fi.organizeUpdated as string}
254
- </span>
255
- )}
256
- {undoable && (
257
- <button
258
- type="button"
259
- onClick={() => handleUndoOne(c.path)}
260
- disabled={undoing}
261
- className="text-2xs text-muted-foreground/60 hover:text-foreground transition-colors shrink-0 px-1 disabled:opacity-40"
262
- title={fi.organizeUndoOne as string}
263
- >
264
- <Undo2 size={12} />
265
- </button>
266
- )}
267
- {c.ok && !c.undone && (
268
- <button
269
- type="button"
270
- onClick={() => handleViewFile(c.path)}
271
- className="text-2xs text-muted-foreground/60 hover:text-[var(--amber)] transition-colors shrink-0 px-1"
272
- title={fi.organizeViewFile as string}
273
- >
274
- <ExternalLink size={12} />
275
- </button>
276
- )}
277
- </div>
278
- );
279
- })}
443
+ {aiOrganize.changes.map((c, idx) => renderChangeRow(c, idx))}
280
444
  </div>
281
445
  )}
282
446
 
@@ -286,31 +450,14 @@ export default function OrganizeToast({
286
450
  </div>
287
451
  )}
288
452
 
289
- {/* Actions */}
290
- <div className="flex items-center justify-end gap-3 px-4 py-3 border-t border-border">
291
- {isDone && aiOrganize.hasAnyUndoable && (
292
- <button
293
- onClick={handleUndoAll}
294
- disabled={undoing}
295
- className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors px-2 py-1.5 disabled:opacity-50"
296
- >
297
- {undoing ? <Loader2 size={12} className="animate-spin" /> : <Undo2 size={12} />}
298
- {fi.organizeUndoAll as string}
299
- </button>
300
- )}
301
- <button
302
- onClick={handleDismiss}
303
- className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium bg-[var(--amber)] text-[var(--amber-foreground)] hover:opacity-90 transition-all"
304
- >
305
- <Check size={12} />
306
- {fi.organizeDone as string}
307
- </button>
308
- </div>
453
+ {renderActions()}
309
454
  </div>
310
455
  );
311
456
  }
312
457
 
313
- // Compact toast bar
458
+ // ═══════════════════════════════════════════════════════════════════════════
459
+ // Compact toast bar
460
+ // ═══════════════════════════════════════════════════════════════════════════
314
461
  return (
315
462
  <div
316
463
  className="fixed bottom-4 left-1/2 -translate-x-1/2 z-50 flex items-center gap-3 bg-card border border-border rounded-xl shadow-lg px-4 py-3 max-w-md animate-in fade-in-0 slide-in-from-bottom-2 duration-200"
@@ -372,6 +519,14 @@ export default function OrganizeToast({
372
519
  <span className="text-xs text-muted-foreground/60 tabular-nums shrink-0">
373
520
  {(fi.organizeElapsed as (s: number) => string)(elapsed)}
374
521
  </span>
522
+ <button
523
+ type="button"
524
+ onClick={() => { setMaximized(true); handleUserAction(); }}
525
+ className="text-muted-foreground/50 hover:text-muted-foreground transition-colors shrink-0"
526
+ title={fi.organizeDetailTitle as string}
527
+ >
528
+ <Maximize2 size={13} />
529
+ </button>
375
530
  <button
376
531
  type="button"
377
532
  onClick={handleDismiss}
@@ -30,7 +30,6 @@ const DEFAULT_PANEL_WIDTH: Record<PanelId, number> = {
30
30
  echo: 280,
31
31
  agents: 280,
32
32
  discover: 280,
33
- history: 280,
34
33
  };
35
34
 
36
35
  const MIN_PANEL_WIDTH = 240;
@@ -108,11 +107,12 @@ export default function Panel({
108
107
  };
109
108
  }, [newPopover]);
110
109
 
111
- // Double-click hint: show only until user has used it once
112
- const [dblHintSeen, setDblHintSeen] = useState(() => {
113
- if (typeof window === 'undefined') return false;
114
- return localStorage.getItem('mindos-tree-dblclick-hint') === '1';
115
- });
110
+ // Double-click hint: show only until user has used it once.
111
+ // Initialize false to match SSR; hydrate from localStorage in useEffect.
112
+ const [dblHintSeen, setDblHintSeen] = useState(false);
113
+ useEffect(() => {
114
+ try { if (localStorage.getItem('mindos-tree-dblclick-hint') === '1') setDblHintSeen(true); } catch { /* ignore */ }
115
+ }, []);
116
116
  const markDblHintSeen = useCallback(() => {
117
117
  if (!dblHintSeen) {
118
118
  setDblHintSeen(true);
@@ -345,12 +345,22 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
345
345
  <ActivityBar
346
346
  activePanel={railActivePanel}
347
347
  onPanelChange={lp.setActivePanel}
348
+ onEchoClick={() => {
349
+ const wasActive = lp.activePanel === 'echo';
350
+ lp.setActivePanel(wasActive ? null : 'echo');
351
+ if (!wasActive) router.push('/echo/about-you');
352
+ }}
348
353
  onAgentsClick={() => {
349
354
  const wasActive = lp.activePanel === 'agents';
350
355
  lp.setActivePanel(wasActive ? null : 'agents');
351
356
  if (!wasActive) router.push('/agents');
352
357
  setAgentDetailKey(null);
353
358
  }}
359
+ onDiscoverClick={() => {
360
+ const wasActive = lp.activePanel === 'discover';
361
+ lp.setActivePanel(wasActive ? null : 'discover');
362
+ if (!wasActive) router.push('/explore');
363
+ }}
354
364
  syncStatus={syncStatus}
355
365
  expanded={lp.railExpanded}
356
366
  onExpandedChange={handleExpandedChange}
@@ -97,21 +97,21 @@ export default function UpdateOverlay() {
97
97
  >
98
98
  {done ? (
99
99
  <>
100
- <CheckCircle2 size={32} style={{ color: '#7aad80', marginBottom: 12 }} />
101
- <div style={{ color: '#e8e4dc', fontSize: 18, fontWeight: 600 }}>
100
+ <CheckCircle2 size={32} style={{ color: 'var(--success)', marginBottom: 12 }} />
101
+ <div style={{ color: 'var(--foreground)', fontSize: 18, fontWeight: 600 }}>
102
102
  {zh ? '更新成功!' : 'Update Complete!'}
103
103
  </div>
104
- <div style={{ color: '#8a8275', fontSize: 13, marginTop: 6 }}>
104
+ <div style={{ color: 'var(--muted-foreground)', fontSize: 13, marginTop: 6 }}>
105
105
  {zh ? '正在刷新页面...' : 'Reloading...'}
106
106
  </div>
107
107
  </>
108
108
  ) : (
109
109
  <>
110
- <Loader2 size={32} style={{ color: '#d4954a', marginBottom: 12, animation: 'spin 1s linear infinite' }} />
111
- <div style={{ color: '#e8e4dc', fontSize: 18, fontWeight: 600 }}>
110
+ <Loader2 size={32} style={{ color: 'var(--amber)', marginBottom: 12, animation: 'spin 1s linear infinite' }} />
111
+ <div style={{ color: 'var(--foreground)', fontSize: 18, fontWeight: 600 }}>
112
112
  {zh ? 'MindOS 正在更新...' : 'MindOS is Updating...'}
113
113
  </div>
114
- <div style={{ color: '#8a8275', fontSize: 13, marginTop: 6, textAlign: 'center', maxWidth: 300, lineHeight: 1.5 }}>
114
+ <div style={{ color: 'var(--muted-foreground)', fontSize: 13, marginTop: 6, textAlign: 'center', maxWidth: 300, lineHeight: 1.5 }}>
115
115
  {zh
116
116
  ? '服务正在重启,请勿关闭此页面。完成后将自动刷新。'
117
117
  : 'The server is restarting. Please do not close this page. It will auto-reload when ready.'}
@@ -243,9 +243,9 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
243
243
  <h1 className="text-lg font-semibold tracking-tight font-display text-foreground truncate">{agent.name}</h1>
244
244
  <div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 mt-0.5 text-2xs text-muted-foreground/60">
245
245
  <span className={`font-medium px-1.5 py-px rounded-full ${
246
- status === 'connected' ? 'bg-success/10 text-success'
246
+ status === 'connected' ? 'bg-muted text-muted-foreground'
247
247
  : status === 'detected' ? 'bg-[var(--amber-subtle)] text-[var(--amber-text)]'
248
- : 'bg-muted text-muted-foreground'
248
+ : 'bg-error/10 text-error'
249
249
  }`}>{status}</span>
250
250
  <span className="font-mono">{agent.transport ?? agent.preferredTransport}</span>
251
251
  <span className="text-muted-foreground/25" aria-hidden="true">·</span>
@@ -296,7 +296,7 @@ function ByAgentView({
296
296
  const mcpServers = agent.configuredMcpServers ?? [];
297
297
  const nativeSkillCount = (agent.installedSkillNames ?? []).length;
298
298
  return (
299
- <div key={agent.key} className={`rounded-xl border bg-card group hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)] transition-all duration-150 ${status === 'connected' ? 'border-l-2 border-l-[var(--success)] border-border' : status === 'detected' ? 'border-l-2 border-l-[var(--amber)] border-border' : 'border-border'}`}>
299
+ <div key={agent.key} className={`rounded-xl border bg-card group hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)] transition-all duration-150 ${status === 'detected' ? 'border-l-2 border-l-[var(--amber)] border-border' : status === 'notFound' ? 'border-l-2 border-l-error border-border' : 'border-border'}`}>
300
300
  {/* Card header with avatar */}
301
301
  <div className="flex items-center gap-3 p-3">
302
302
  <AgentAvatar name={agent.name} status={status} />
@@ -306,7 +306,7 @@ function ByAgentView({
306
306
  {agent.name}
307
307
  </Link>
308
308
  <span className="text-2xs text-muted-foreground font-mono shrink-0">{agent.transport ?? agent.preferredTransport}</span>
309
- <span className={`text-2xs px-1.5 py-0.5 rounded shrink-0 ${status === 'connected' ? 'bg-success/10 text-success' : status === 'detected' ? 'bg-[var(--amber-dim)] text-[var(--amber-text)]' : 'bg-muted text-muted-foreground'}`}>
309
+ <span className={`text-2xs px-1.5 py-0.5 rounded shrink-0 ${status === 'connected' ? 'bg-muted text-muted-foreground' : status === 'detected' ? 'bg-[var(--amber-dim)] text-[var(--amber-text)]' : 'bg-error/10 text-error'}`}>
310
310
  {copy.status[status]}
311
311
  </span>
312
312
  </div>
@@ -269,15 +269,15 @@ function StatCell({
269
269
  : 'text-muted-foreground';
270
270
  const iconColor =
271
271
  tone === 'ok'
272
- ? 'text-emerald-500/70'
272
+ ? 'text-[var(--success)]/70'
273
273
  : tone === 'warn'
274
- ? 'text-amber-500/70'
274
+ ? 'text-[var(--amber)]/70'
275
275
  : 'text-muted-foreground/40';
276
276
  const hoverBg =
277
277
  tone === 'ok'
278
- ? 'hover:bg-emerald-500/[0.04]'
278
+ ? 'hover:bg-muted/20'
279
279
  : tone === 'warn'
280
- ? 'hover:bg-amber-500/[0.04]'
280
+ ? 'hover:bg-[var(--amber)]/[0.04]'
281
281
  : 'hover:bg-muted/20';
282
282
 
283
283
  return (
@@ -335,8 +335,8 @@ function QuickNavCard({
335
335
  <span
336
336
  className={`text-2xs px-2 py-0.5 rounded-full font-medium select-none ${
337
337
  statTone === 'ok'
338
- ? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
339
- : 'bg-amber-500/10 text-amber-600 dark:text-amber-400'
338
+ ? 'bg-muted text-muted-foreground'
339
+ : 'bg-[var(--amber-dim)] text-[var(--amber-text)]'
340
340
  }`}
341
341
  >
342
342
  {stat}
@@ -373,10 +373,10 @@ function AgentCard({
373
373
  status === 'connected' ? copy.connected : status === 'detected' ? copy.detected : copy.notFound;
374
374
  const statusColor =
375
375
  status === 'connected'
376
- ? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
376
+ ? 'bg-muted text-muted-foreground'
377
377
  : status === 'detected'
378
- ? 'bg-amber-500/10 text-amber-600 dark:text-amber-400'
379
- : 'bg-zinc-500/10 text-zinc-500';
378
+ ? 'bg-[var(--amber-dim)] text-[var(--amber-text)]'
379
+ : 'bg-error/10 text-error';
380
380
 
381
381
  return (
382
382
  <Link
@@ -410,10 +410,10 @@ function AgentCard({
410
410
  <span className="flex-1 min-w-[4px]" />
411
411
  {hasRuntime && (
412
412
  <span
413
- className="flex items-center gap-1 text-emerald-600 dark:text-emerald-400"
413
+ className="flex items-center gap-1 text-[var(--success)]"
414
414
  title={copy.runtimeActive}
415
415
  >
416
- <span className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" aria-hidden="true" />
416
+ <span className="w-1.5 h-1.5 rounded-full bg-[var(--success)] animate-pulse" aria-hidden="true" />
417
417
  <span className="text-2xs font-medium">{copy.runtimeActive}</span>
418
418
  </span>
419
419
  )}
@@ -27,7 +27,7 @@ export function StatusDot({ tone, label, count }: { tone: 'ok' | 'warn' | 'neutr
27
27
  const countCls = tone === 'ok' ? 'text-foreground' : tone === 'warn' ? 'text-[var(--amber)]' : 'text-muted-foreground';
28
28
  return (
29
29
  <span className="inline-flex items-center gap-1.5 text-muted-foreground">
30
- <span className={`w-2 h-2 rounded-full ${dotCls} ${tone === 'ok' ? 'ring-2 ring-[var(--success)]/20' : ''}`} aria-hidden="true" />
30
+ <span className={`w-2 h-2 rounded-full ${dotCls}`} aria-hidden="true" />
31
31
  <span className="text-xs">{label}</span>
32
32
  <span className={`tabular-nums font-medium ${countCls}`}>{count}</span>
33
33
  </span>
@@ -131,20 +131,20 @@ export function EmptyState({ message, icon, className }: { message: string; icon
131
131
 
132
132
  /* ────────── Agent Avatar ────────── */
133
133
 
134
- /** Soft pastel palette: [bg, border, text] — watercolor aesthetic */
134
+ /** Dual-mode palette: soft pastels in light, muted tones in dark — [bg, border, text] */
135
135
  const AVATAR_PALETTES: [string, string, string][] = [
136
- ['bg-rose-100/70', 'border-rose-300/50', 'text-rose-600/80'],
137
- ['bg-violet-100/70', 'border-violet-300/50', 'text-violet-600/80'],
138
- ['bg-emerald-100/70', 'border-emerald-300/50', 'text-emerald-600/80'],
139
- ['bg-sky-100/70', 'border-sky-300/50', 'text-sky-600/80'],
140
- ['bg-amber-100/70', 'border-amber-300/50', 'text-amber-700/80'],
141
- ['bg-teal-100/70', 'border-teal-300/50', 'text-teal-600/80'],
142
- ['bg-pink-100/70', 'border-pink-300/50', 'text-pink-600/80'],
143
- ['bg-indigo-100/70', 'border-indigo-300/50', 'text-indigo-600/80'],
144
- ['bg-lime-100/70', 'border-lime-300/50', 'text-lime-700/80'],
145
- ['bg-fuchsia-100/70', 'border-fuchsia-300/50', 'text-fuchsia-600/80'],
146
- ['bg-cyan-100/70', 'border-cyan-300/50', 'text-cyan-600/80'],
147
- ['bg-orange-100/70', 'border-orange-300/50', 'text-orange-600/80'],
136
+ ['bg-rose-100/70 dark:bg-rose-900/30', 'border-rose-300/50 dark:border-rose-700/40', 'text-rose-600/80 dark:text-rose-400/80'],
137
+ ['bg-violet-100/70 dark:bg-violet-900/30', 'border-violet-300/50 dark:border-violet-700/40', 'text-violet-600/80 dark:text-violet-400/80'],
138
+ ['bg-emerald-100/70 dark:bg-emerald-900/30', 'border-emerald-300/50 dark:border-emerald-700/40','text-emerald-600/80 dark:text-emerald-400/80'],
139
+ ['bg-sky-100/70 dark:bg-sky-900/30', 'border-sky-300/50 dark:border-sky-700/40', 'text-sky-600/80 dark:text-sky-400/80'],
140
+ ['bg-amber-100/70 dark:bg-amber-900/30', 'border-amber-300/50 dark:border-amber-700/40', 'text-amber-700/80 dark:text-amber-400/80'],
141
+ ['bg-teal-100/70 dark:bg-teal-900/30', 'border-teal-300/50 dark:border-teal-700/40', 'text-teal-600/80 dark:text-teal-400/80'],
142
+ ['bg-pink-100/70 dark:bg-pink-900/30', 'border-pink-300/50 dark:border-pink-700/40', 'text-pink-600/80 dark:text-pink-400/80'],
143
+ ['bg-indigo-100/70 dark:bg-indigo-900/30', 'border-indigo-300/50 dark:border-indigo-700/40', 'text-indigo-600/80 dark:text-indigo-400/80'],
144
+ ['bg-lime-100/70 dark:bg-lime-900/30', 'border-lime-300/50 dark:border-lime-700/40', 'text-lime-700/80 dark:text-lime-400/80'],
145
+ ['bg-fuchsia-100/70 dark:bg-fuchsia-900/30', 'border-fuchsia-300/50 dark:border-fuchsia-700/40','text-fuchsia-600/80 dark:text-fuchsia-400/80'],
146
+ ['bg-cyan-100/70 dark:bg-cyan-900/30', 'border-cyan-300/50 dark:border-cyan-700/40', 'text-cyan-600/80 dark:text-cyan-400/80'],
147
+ ['bg-orange-100/70 dark:bg-orange-900/30', 'border-orange-300/50 dark:border-orange-700/40', 'text-orange-600/80 dark:text-orange-400/80'],
148
148
  ];
149
149
 
150
150
  function hashName(str: string): number {
@@ -696,7 +696,7 @@ function AgentCard({
696
696
  </Link>
697
697
  {skillMode && (
698
698
  <span className={`text-2xs px-1.5 py-0.5 rounded shrink-0 ${
699
- skillMode === 'universal' ? 'bg-success/10 text-success'
699
+ skillMode === 'universal' ? 'bg-muted text-muted-foreground'
700
700
  : skillMode === 'additional' ? 'bg-[var(--amber-dim)] text-[var(--amber-text)]'
701
701
  : 'bg-muted text-muted-foreground'
702
702
  }`}>
@@ -422,7 +422,7 @@ function MetaCard({
422
422
  tone?: 'ok' | 'muted' | 'default';
423
423
  }) {
424
424
  const valueColor =
425
- tone === 'ok' ? 'text-emerald-600 dark:text-emerald-400'
425
+ tone === 'ok' ? 'text-[var(--success)]'
426
426
  : tone === 'muted' ? 'text-muted-foreground'
427
427
  : 'text-foreground';
428
428
  return (
@@ -18,7 +18,7 @@ function UserMessageContent({ content, skillName }: { content: string; skillName
18
18
  const rest = prefixMatch ? content.slice(prefixMatch[0].length) : content;
19
19
  return (
20
20
  <>
21
- <span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[11px] font-medium bg-[var(--amber-foreground)]/15 text-[var(--amber-foreground)]/90 mr-1 align-middle">
21
+ <span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[11px] font-medium bg-[var(--amber)]/15 text-[var(--amber)] mr-1 align-middle">
22
22
  <Zap size={10} className="shrink-0" />
23
23
  {resolved}
24
24
  </span>
@@ -19,12 +19,12 @@ export default function EchoPanel({ active, maximized, onMaximize }: EchoPanelPr
19
19
  const e = t.panels.echo;
20
20
  const pathname = usePathname() ?? '';
21
21
 
22
- const rowBySegment: Record<EchoSegment, { icon: ReactNode; title: string; subtitle: string }> = {
23
- 'about-you': { icon: <UserRound size={14} />, title: e.aboutYouTitle, subtitle: e.aboutYouDesc },
24
- continued: { icon: <Bookmark size={14} />, title: e.continuedTitle, subtitle: e.continuedDesc },
25
- daily: { icon: <Sun size={14} />, title: e.dailyEchoTitle, subtitle: e.dailyDesc },
26
- 'past-you': { icon: <History size={14} />, title: e.pastYouTitle, subtitle: e.pastYouDesc },
27
- growth: { icon: <Brain size={14} />, title: e.intentGrowthTitle, subtitle: e.growthDesc },
22
+ const rowBySegment: Record<EchoSegment, { icon: ReactNode; title: string }> = {
23
+ 'about-you': { icon: <UserRound size={14} />, title: e.aboutYouTitle },
24
+ continued: { icon: <Bookmark size={14} />, title: e.continuedTitle },
25
+ daily: { icon: <Sun size={14} />, title: e.dailyEchoTitle },
26
+ 'past-you': { icon: <History size={14} />, title: e.pastYouTitle },
27
+ growth: { icon: <Brain size={14} />, title: e.intentGrowthTitle },
28
28
  };
29
29
 
30
30
  return (
@@ -37,7 +37,7 @@ export default function EchoPanel({ active, maximized, onMaximize }: EchoPanelPr
37
37
  const href = ECHO_SEGMENT_HREF[segment];
38
38
  const isActive = pathname === href || pathname.startsWith(`${href}/`);
39
39
  return (
40
- <PanelNavRow key={segment} href={href} icon={row.icon} title={row.title} subtitle={row.subtitle} active={isActive} />
40
+ <PanelNavRow key={segment} href={href} icon={row.icon} title={row.title} active={isActive} />
41
41
  );
42
42
  })}
43
43
  </div>