@geminilight/mindos 0.5.33 → 0.5.35

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.
@@ -27,7 +27,7 @@ export default function AskFab({ onToggle, askPanelOpen }: AskFabProps) {
27
27
  ${askPanelOpen ? 'opacity-0 pointer-events-none translate-y-2' : 'opacity-100 translate-y-0'}
28
28
  `}
29
29
  style={{
30
- background: 'linear-gradient(135deg, #b07c2e 0%, #c8873a 50%, #d4943f 100%)',
30
+ background: 'linear-gradient(135deg, var(--amber), color-mix(in srgb, var(--amber) 80%, white))',
31
31
  }}
32
32
  title="MindOS Agent (⌘/)"
33
33
  aria-label="MindOS Agent"
@@ -15,6 +15,10 @@ const DIR_ICONS: Record<string, string> = {
15
15
 
16
16
  const EMPTY_FILES = ['INSTRUCTION.md', 'README.md', 'CONFIG.json'];
17
17
 
18
+ /* Shared amber-subtle background — used as inline style because Tailwind can't handle
19
+ CSS var() with fallback in arbitrary values: bg-[var(--amber-subtle,rgba(...))] breaks. */
20
+ const amberSubtleBg = 'var(--amber-subtle, rgba(200,135,30,0.08))';
21
+
18
22
  interface GuideCardProps {
19
23
  /** Called when user clicks a file/dir to open it in FileView */
20
24
  onNavigate?: (path: string) => void;
@@ -39,7 +43,6 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
39
43
  setGuideState(gs);
40
44
  if (gs.step1Done) setBrowsedCount(1);
41
45
  } else {
42
- // Guide inactive or dismissed — clear local state
43
46
  setGuideState(null);
44
47
  }
45
48
  })
@@ -49,13 +52,9 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
49
52
  useEffect(() => {
50
53
  fetchGuideState();
51
54
 
52
- // Listen for walkthrough-triggered first visit (WalkthroughProvider owns ?welcome=1 detection)
53
- const handleFirstVisit = () => {
54
- setIsFirstVisit(true);
55
- };
55
+ const handleFirstVisit = () => { setIsFirstVisit(true); };
56
56
  window.addEventListener('mindos:first-visit', handleFirstVisit);
57
57
 
58
- // Re-fetch when guide state is updated (e.g. after AskFab patches askedAI)
59
58
  const handleGuideUpdate = () => fetchGuideState();
60
59
  window.addEventListener('focus', handleGuideUpdate);
61
60
  window.addEventListener('guide-state-updated', handleGuideUpdate);
@@ -66,14 +65,12 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
66
65
  };
67
66
  }, [fetchGuideState]);
68
67
 
69
- // Auto-expand KB task on first visit
70
68
  useEffect(() => {
71
69
  if (isFirstVisit && guideState && !guideState.step1Done) {
72
70
  setExpanded('kb');
73
71
  }
74
72
  }, [isFirstVisit, guideState]);
75
73
 
76
- // Patch guideState to backend
77
74
  const patchGuide = useCallback((patch: Partial<GuideState>) => {
78
75
  setGuideState(prev => prev ? { ...prev, ...patch } : prev);
79
76
  fetch('/api/setup', {
@@ -93,7 +90,6 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
93
90
  if (browsedCount === 0) {
94
91
  setBrowsedCount(1);
95
92
  patchGuide({ step1Done: true });
96
- // Collapse after a beat
97
93
  setTimeout(() => setExpanded(null), 300);
98
94
  }
99
95
  }, [browsedCount, patchGuide, onNavigate]);
@@ -109,8 +105,6 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
109
105
  const isEmpty = gs?.template === 'empty';
110
106
  const prompt = isEmpty ? g.ai.promptEmpty : g.ai.prompt;
111
107
  openAskModal(prompt, 'guide');
112
- // Don't optimistically set askedAI here — wait until user actually sends a message
113
- // AskFab.onFirstMessage will PATCH askedAI:true
114
108
  }, [guideState, g]);
115
109
 
116
110
  const handleNextStepClick = useCallback(() => {
@@ -119,13 +113,11 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
119
113
  const steps = g.done.steps;
120
114
  if (idx < steps.length) {
121
115
  openAskModal(steps[idx].prompt, 'guide-next');
122
- // Optimistic local update — AskFab will persist to backend on first message
123
116
  patchGuide({ nextStepIndex: idx + 1 });
124
117
  }
125
118
  }, [guideState, g, patchGuide]);
126
119
 
127
120
  const handleSyncClick = useCallback(() => {
128
- // Dispatch ⌘, to open Settings modal
129
121
  window.dispatchEvent(new KeyboardEvent('keydown', { key: ',', metaKey: true, bubbles: true }));
130
122
  }, []);
131
123
 
@@ -144,10 +136,8 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
144
136
  return () => { if (autoDismissRef.current) clearTimeout(autoDismissRef.current); };
145
137
  }, [allNextDone_, handleDismiss]);
146
138
 
147
- // Don't render if no active guide
148
139
  if (!guideState) return null;
149
140
 
150
- // Hide GuideCard while walkthrough is active
151
141
  const walkthroughActive = guideState.walkthroughStep !== undefined
152
142
  && guideState.walkthroughStep >= 0
153
143
  && guideState.walkthroughStep < walkthroughSteps.length
@@ -165,21 +155,19 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
165
155
  // After all next-steps done → final state (auto-dismisses after 8s)
166
156
  if (allDone && allNextDone) {
167
157
  return (
168
- <div className="mb-6 rounded-xl border px-5 py-4 flex items-center gap-3 animate-in fade-in duration-300"
169
- style={{ background: 'var(--amber-subtle, rgba(200,135,30,0.08))', borderColor: 'var(--amber)' }}>
170
- <Sparkles size={16} className="animate-spin-slow" style={{ color: 'var(--amber)' }} />
171
- <span className="text-sm font-semibold flex-1" style={{ color: 'var(--foreground)' }}>
158
+ <div className="mb-6 rounded-xl border border-[var(--amber)] px-5 py-4 flex items-center gap-3 animate-in fade-in duration-300"
159
+ style={{ background: amberSubtleBg }}>
160
+ <Sparkles size={16} className="animate-spin-slow text-[var(--amber)]" />
161
+ <span className="text-sm font-semibold flex-1 text-foreground">
172
162
  ✨ {g.done.titleFinal}
173
163
  </span>
174
164
  <Link
175
165
  href="/explore"
176
- className="text-xs font-medium transition-colors hover:opacity-80"
177
- style={{ color: 'var(--amber)' }}
166
+ className="text-xs font-medium text-[var(--amber)] transition-colors hover:opacity-80"
178
167
  >
179
168
  {t.walkthrough.exploreCta}
180
169
  </Link>
181
- <button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors"
182
- style={{ color: 'var(--muted-foreground)' }}>
170
+ <button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors text-muted-foreground">
183
171
  <X size={14} />
184
172
  </button>
185
173
  </div>
@@ -190,23 +178,21 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
190
178
  if (allDone) {
191
179
  const step = nextSteps[nextIdx];
192
180
  return (
193
- <div className="mb-6 rounded-xl border px-5 py-4 animate-in fade-in duration-300"
194
- style={{ background: 'var(--amber-subtle, rgba(200,135,30,0.08))', borderColor: 'var(--amber)' }}>
181
+ <div className="mb-6 rounded-xl border border-[var(--amber)] px-5 py-4 animate-in fade-in duration-300"
182
+ style={{ background: amberSubtleBg }}>
195
183
  <div className="flex items-center gap-3">
196
- <Sparkles size={16} style={{ color: 'var(--amber)' }} />
197
- <span className="text-sm font-semibold flex-1" style={{ color: 'var(--foreground)' }}>
184
+ <Sparkles size={16} className="text-[var(--amber)]" />
185
+ <span className="text-sm font-semibold flex-1 text-foreground">
198
186
  🎉 {g.done.title}
199
187
  </span>
200
- <button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors"
201
- style={{ color: 'var(--muted-foreground)' }}>
188
+ <button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors text-muted-foreground">
202
189
  <X size={14} />
203
190
  </button>
204
191
  </div>
205
192
  {step && (
206
193
  <button
207
194
  onClick={handleNextStepClick}
208
- className="mt-3 flex items-center gap-2 text-sm transition-colors hover:opacity-80 cursor-pointer animate-in fade-in slide-in-from-left-2 duration-300"
209
- style={{ color: 'var(--amber)' }}
195
+ className="mt-3 flex items-center gap-2 text-sm text-[var(--amber)] transition-colors hover:opacity-80 cursor-pointer animate-in fade-in slide-in-from-left-2 duration-300"
210
196
  >
211
197
  <ChevronRight size={14} />
212
198
  <span>{step.hint}</span>
@@ -218,60 +204,32 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
218
204
 
219
205
  // Main guide card with 3 tasks
220
206
  return (
221
- <div className="mb-6 rounded-xl border overflow-hidden"
222
- style={{ background: 'var(--amber-subtle, rgba(200,135,30,0.08))', borderColor: 'var(--amber)' }}>
207
+ <div className="mb-6 rounded-xl border border-[var(--amber)] overflow-hidden"
208
+ style={{ background: amberSubtleBg }}>
223
209
 
224
210
  {/* Header */}
225
211
  <div className="flex items-center gap-3 px-5 pt-4 pb-2">
226
- <Sparkles size={16} style={{ color: 'var(--amber)' }} />
227
- <span className="text-sm font-semibold flex-1 font-display" style={{ color: 'var(--foreground)' }}>
212
+ <Sparkles size={16} className="text-[var(--amber)]" />
213
+ <span className="text-sm font-semibold flex-1 font-display text-foreground">
228
214
  {g.title}
229
215
  </span>
230
- <button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors"
231
- style={{ color: 'var(--muted-foreground)' }}>
216
+ <button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors text-muted-foreground">
232
217
  <X size={14} />
233
218
  </button>
234
219
  </div>
235
220
 
236
221
  {/* Task cards */}
237
222
  <div className="grid grid-cols-1 sm:grid-cols-3 gap-2 px-5 py-3">
238
- {/* Explore KB */}
239
- <TaskCard
240
- icon={<FolderOpen size={16} />}
241
- title={g.kb.title}
242
- cta={g.kb.cta}
243
- done={step1Done}
244
- active={expanded === 'kb'}
245
- onClick={() => step1Done ? null : setExpanded(expanded === 'kb' ? null : 'kb')}
246
- />
247
- {/* ② Chat with AI */}
248
- <TaskCard
249
- icon={<MessageCircle size={16} />}
250
- title={g.ai.title}
251
- cta={g.ai.cta}
252
- done={step2Done}
253
- active={expanded === 'ai'}
254
- onClick={() => {
255
- if (!step2Done) handleStartAI();
256
- }}
257
- />
258
- {/* ③ Sync (optional) */}
259
- <TaskCard
260
- icon={<RefreshCw size={16} />}
261
- title={g.sync.title}
262
- cta={g.sync.cta}
263
- done={false}
264
- optional={g.sync.optional}
265
- active={false}
266
- onClick={handleSyncClick}
267
- />
223
+ <TaskCard icon={<FolderOpen size={16} />} title={g.kb.title} cta={g.kb.cta} done={step1Done} active={expanded === 'kb'} onClick={() => step1Done ? null : setExpanded(expanded === 'kb' ? null : 'kb')} />
224
+ <TaskCard icon={<MessageCircle size={16} />} title={g.ai.title} cta={g.ai.cta} done={step2Done} active={expanded === 'ai'} onClick={() => { if (!step2Done) handleStartAI(); }} />
225
+ <TaskCard icon={<RefreshCw size={16} />} title={g.sync.title} cta={g.sync.cta} done={false} optional={g.sync.optional} active={false} onClick={handleSyncClick} />
268
226
  </div>
269
227
 
270
228
  {/* Expanded content: Explore KB */}
271
229
  {expanded === 'kb' && !step1Done && (
272
230
  <div className="px-5 pb-4 animate-in slide-in-from-top-2 duration-200">
273
- <div className="rounded-lg border p-4" style={{ background: 'var(--card)', borderColor: 'var(--border)' }}>
274
- <p className="text-xs mb-3" style={{ color: 'var(--muted-foreground)' }}>
231
+ <div className="rounded-lg border border-border bg-card p-4">
232
+ <p className="text-xs mb-3 text-muted-foreground">
275
233
  {isEmptyTemplate ? g.kb.emptyDesc : g.kb.fullDesc}
276
234
  </p>
277
235
 
@@ -279,12 +237,11 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
279
237
  <div className="flex flex-col gap-1.5">
280
238
  {EMPTY_FILES.map(file => (
281
239
  <button key={file} onClick={() => handleFileOpen(file)}
282
- className="text-left text-xs px-3 py-2 rounded-lg border transition-colors hover:border-amber-500/30 hover:bg-muted/50"
283
- style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}>
240
+ className="text-left text-xs px-3 py-2 rounded-lg border border-border text-foreground transition-colors hover:border-amber-500/30 hover:bg-muted/50">
284
241
  📄 {(g.kb.emptyFiles as Record<string, string>)[file.split('.')[0].toLowerCase()] || file}
285
242
  </button>
286
243
  ))}
287
- <p className="text-xs mt-2" style={{ color: 'var(--muted-foreground)', opacity: 0.7 }}>
244
+ <p className="text-xs mt-2 text-muted-foreground opacity-70">
288
245
  {g.kb.emptyHint}
289
246
  </p>
290
247
  </div>
@@ -293,29 +250,27 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
293
250
  <div className="grid grid-cols-2 sm:grid-cols-3 gap-1.5">
294
251
  {Object.entries(DIR_ICONS).map(([key, icon]) => (
295
252
  <button key={key} onClick={() => handleFileOpen(key.charAt(0).toUpperCase() + key.slice(1))}
296
- className="text-left text-xs px-3 py-2 rounded-lg border transition-colors hover:border-amber-500/30 hover:bg-muted/50"
297
- style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}>
253
+ className="text-left text-xs px-3 py-2 rounded-lg border border-border text-foreground transition-colors hover:border-amber-500/30 hover:bg-muted/50">
298
254
  <span className="mr-1.5">{icon}</span>
299
255
  <span className="capitalize">{key}</span>
300
- <span className="block text-2xs mt-0.5" style={{ color: 'var(--muted-foreground)' }}>
256
+ <span className="block text-2xs mt-0.5 text-muted-foreground">
301
257
  {(g.kb.dirs as Record<string, string>)[key]}
302
258
  </span>
303
259
  </button>
304
260
  ))}
305
261
  </div>
306
- <p className="text-xs mt-3" style={{ color: 'var(--amber)' }}>
262
+ <p className="text-xs mt-3 text-[var(--amber)]">
307
263
  💡 {g.kb.instructionHint}
308
264
  </p>
309
265
  </>
310
266
  )}
311
267
 
312
- <div className="flex items-center justify-between mt-3 pt-3" style={{ borderTop: '1px solid var(--border)' }}>
313
- <span className="text-xs" style={{ color: 'var(--muted-foreground)' }}>
268
+ <div className="flex items-center justify-between mt-3 pt-3 border-t border-border">
269
+ <span className="text-xs text-muted-foreground">
314
270
  {g.kb.progress(browsedCount)}
315
271
  </span>
316
272
  <button onClick={handleSkipKB}
317
- className="text-xs px-3 py-1 rounded-lg transition-colors hover:bg-muted"
318
- style={{ color: 'var(--muted-foreground)' }}>
273
+ className="text-xs px-3 py-1 rounded-lg text-muted-foreground transition-colors hover:bg-muted">
319
274
  {g.skip}
320
275
  </button>
321
276
  </div>
@@ -326,10 +281,8 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
326
281
  );
327
282
  }
328
283
 
329
- // Expose callback for AskModal integration
330
284
  export { type GuideCardProps };
331
285
 
332
- // Reusable sub-component
333
286
  function TaskCard({ icon, title, cta, done, active, optional, onClick }: {
334
287
  icon: React.ReactNode;
335
288
  title: string;
@@ -348,25 +301,22 @@ function TaskCard({ icon, title, cta, done, active, optional, onClick }: {
348
301
  transition-all duration-150
349
302
  ${done ? 'opacity-60' : 'hover:border-amber-500/30 hover:bg-muted/50 cursor-pointer'}
350
303
  ${active ? 'border-amber-500/40 bg-muted/50' : ''}
304
+ ${done || active ? 'border-[var(--amber)]' : 'border-border'}
351
305
  `}
352
- style={{ borderColor: done || active ? 'var(--amber)' : 'var(--border)' }}
353
306
  >
354
- <span
355
- className={done ? 'animate-in zoom-in-50 duration-300' : ''}
356
- style={{ color: done ? 'var(--success)' : 'var(--amber)' }}
357
- >
307
+ <span className={`${done ? 'animate-in zoom-in-50 duration-300 text-success' : 'text-[var(--amber)]'}`}>
358
308
  {done ? <Check size={16} /> : icon}
359
309
  </span>
360
- <span className="text-xs font-medium" style={{ color: 'var(--foreground)' }}>
310
+ <span className="text-xs font-medium text-foreground">
361
311
  {title}
362
312
  </span>
363
313
  {optional && (
364
- <span className="text-2xs px-1.5 py-0.5 rounded-full" style={{ background: 'var(--muted)', color: 'var(--muted-foreground)' }}>
314
+ <span className="text-2xs px-1.5 py-0.5 rounded-full bg-muted text-muted-foreground">
365
315
  {optional}
366
316
  </span>
367
317
  )}
368
318
  {!done && !optional && (
369
- <span className="text-2xs" style={{ color: 'var(--amber)' }}>
319
+ <span className="text-2xs text-[var(--amber)]">
370
320
  {cta} →
371
321
  </span>
372
322
  )}
@@ -52,8 +52,7 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
52
52
  const formatTime = (mtime: number) => relativeTime(mtime, t.home.relativeTime);
53
53
 
54
54
  // Only show renderers that are available (have entryPath + file exists) as quick-access chips
55
- const renderers = getAllRenderers().filter(r => r.entryPath);
56
- const availablePlugins = renderers.filter(r => r.entryPath && existingSet.has(r.entryPath));
55
+ const availablePlugins = getAllRenderers().filter(r => r.entryPath && existingSet.has(r.entryPath));
57
56
 
58
57
  const lastFile = recent[0];
59
58
 
@@ -63,38 +62,29 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
63
62
  {/* Hero */}
64
63
  <div className="mb-10">
65
64
  <div className="flex items-center gap-2 mb-3">
66
- <div className="w-1 h-5 rounded-full" style={{ background: 'var(--amber)' }} />
67
- <h1 className="text-2xl font-semibold tracking-tight font-display" style={{ color: 'var(--foreground)' }}>
65
+ <div className="w-1 h-5 rounded-full bg-[var(--amber)]" />
66
+ <h1 className="text-2xl font-semibold tracking-tight font-display text-foreground">
68
67
  MindOS
69
68
  </h1>
70
69
  </div>
71
- <p className="text-sm leading-relaxed mb-5" style={{ color: 'var(--muted-foreground)', paddingLeft: '1rem' }}>
70
+ <p className="text-sm leading-relaxed mb-5 text-muted-foreground pl-4">
72
71
  {t.app.tagline}
73
72
  </p>
74
73
 
75
74
  {/* AI-first command bar */}
76
- <div
77
- className="w-full max-w-[620px] flex flex-col sm:flex-row items-stretch sm:items-center gap-2"
78
- style={{
79
- marginLeft: '1rem',
80
- }}
81
- >
75
+ <div className="w-full max-w-[620px] flex flex-col sm:flex-row items-stretch sm:items-center gap-2 ml-4">
82
76
  {/* Ask AI (primary) */}
83
77
  <button
84
78
  onClick={triggerAsk}
85
79
  title="⌘/"
86
80
  data-walkthrough="ask-button"
87
- className="flex-1 flex items-center gap-3 px-4 py-3 rounded-xl border transition-all duration-150 hover:border-amber-500/50 hover:bg-amber-500/8"
88
- style={{ background: 'var(--card)', borderColor: 'var(--border)' }}
81
+ className="flex-1 flex items-center gap-3 px-4 py-3 rounded-xl border border-border bg-card transition-all duration-150 hover:border-amber-500/50 hover:bg-amber-500/8"
89
82
  >
90
- <Sparkles size={15} style={{ color: 'var(--amber)' }} className="shrink-0" />
91
- <span className="text-sm flex-1 text-left" style={{ color: 'var(--foreground)' }}>
83
+ <Sparkles size={15} className="shrink-0 text-[var(--amber)]" />
84
+ <span className="text-sm flex-1 text-left text-foreground" aria-live="polite" aria-atomic="true">
92
85
  {suggestions[suggestionIdx]}
93
86
  </span>
94
- <kbd
95
- className="hidden sm:inline-flex items-center gap-0.5 px-2 py-0.5 rounded text-xs font-mono font-medium"
96
- style={{ background: 'var(--amber-dim)', color: 'var(--amber)' }}
97
- >
87
+ <kbd className="hidden sm:inline-flex items-center gap-0.5 px-2 py-0.5 rounded text-xs font-mono font-medium bg-[var(--amber-dim)] text-[var(--amber)]">
98
88
  ⌘/
99
89
  </kbd>
100
90
  </button>
@@ -103,27 +93,22 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
103
93
  <button
104
94
  onClick={triggerSearch}
105
95
  title="⌘K"
106
- className="flex items-center gap-2 px-3 py-3 rounded-xl border text-sm transition-colors shrink-0 hover:bg-muted"
107
- style={{ borderColor: 'var(--border)', color: 'var(--muted-foreground)' }}
96
+ className="flex items-center gap-2 px-3 py-3 rounded-xl border border-border text-sm text-muted-foreground transition-colors shrink-0 hover:bg-muted"
108
97
  >
109
98
  <Search size={14} />
110
99
  <span className="hidden sm:inline">{t.home.shortcuts.searchFiles}</span>
111
- <kbd className="hidden sm:inline-flex items-center px-1.5 py-0.5 rounded text-2xs font-mono" style={{ background: 'var(--muted)' }}>
100
+ <kbd className="hidden sm:inline-flex items-center px-1.5 py-0.5 rounded text-2xs font-mono bg-muted">
112
101
  ⌘K
113
102
  </kbd>
114
103
  </button>
115
104
  </div>
116
105
 
117
106
  {/* Quick Actions */}
118
- <div className="flex flex-wrap gap-2.5 mt-4" style={{ paddingLeft: '1rem' }}>
107
+ <div className="flex flex-wrap gap-2.5 mt-4 pl-4">
119
108
  {lastFile && (
120
109
  <Link
121
110
  href={`/view/${encodePath(lastFile.path)}`}
122
- className="inline-flex items-center gap-2 px-3.5 py-2 rounded-lg text-sm font-medium transition-all duration-150 hover:translate-x-0.5"
123
- style={{
124
- background: 'var(--amber-dim)',
125
- color: 'var(--amber)',
126
- }}
111
+ className="inline-flex items-center gap-2 px-3.5 py-2 rounded-lg text-sm font-medium transition-all duration-150 hover:translate-x-0.5 bg-[var(--amber-dim)] text-[var(--amber)]"
127
112
  >
128
113
  <ArrowRight size={14} />
129
114
  <span>{t.home.continueEditing}</span>
@@ -134,21 +119,14 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
134
119
  )}
135
120
  <Link
136
121
  href="/view/Untitled.md"
137
- className="inline-flex items-center gap-2 px-3.5 py-2 rounded-lg text-sm font-medium transition-colors"
138
- style={{
139
- background: 'var(--muted)',
140
- color: 'var(--muted-foreground)',
141
- }}
122
+ className="inline-flex items-center gap-2 px-3.5 py-2 rounded-lg text-sm font-medium transition-colors bg-muted text-muted-foreground"
142
123
  >
143
124
  <FilePlus size={14} />
144
125
  <span>{t.home.newNote}</span>
145
126
  </Link>
146
127
  <Link
147
128
  href="/explore"
148
- className="inline-flex items-center gap-2 px-3.5 py-2 rounded-lg text-sm font-medium transition-all duration-150 hover:translate-x-0.5"
149
- style={{
150
- color: 'var(--amber)',
151
- }}
129
+ className="inline-flex items-center gap-2 px-3.5 py-2 rounded-lg text-sm font-medium transition-all duration-150 hover:translate-x-0.5 text-[var(--amber)]"
152
130
  >
153
131
  <Compass size={14} />
154
132
  <span>{t.explore.title}</span>
@@ -157,13 +135,12 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
157
135
 
158
136
  {/* Plugin quick-access chips — only show available plugins */}
159
137
  {availablePlugins.length > 0 && (
160
- <div className="flex flex-wrap gap-1.5 mt-3" style={{ paddingLeft: '1rem' }}>
138
+ <div className="flex flex-wrap gap-1.5 mt-3 pl-4">
161
139
  {availablePlugins.map(r => (
162
140
  <Link
163
141
  key={r.id}
164
142
  href={`/view/${encodePath(r.entryPath!)}`}
165
- className="inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs transition-all duration-100 hover:bg-muted/60"
166
- style={{ color: 'var(--muted-foreground)' }}
143
+ className="inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs text-muted-foreground transition-all duration-100 hover:bg-muted/60"
167
144
  >
168
145
  <span className="text-sm leading-none" suppressHydrationWarning>{r.icon}</span>
169
146
  <span>{r.name}</span>
@@ -182,15 +159,15 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
182
159
  return (
183
160
  <section className="mb-12">
184
161
  <div className="flex items-center gap-2 mb-5">
185
- <Clock size={13} style={{ color: 'var(--amber)' }} />
186
- <h2 className="text-xs font-semibold uppercase tracking-[0.08em] font-display" style={{ color: 'var(--muted-foreground)' }}>
162
+ <Clock size={13} className="text-[var(--amber)]" />
163
+ <h2 className="text-xs font-semibold uppercase tracking-[0.08em] font-display text-muted-foreground">
187
164
  {t.home.recentlyModified}
188
165
  </h2>
189
166
  </div>
190
167
 
191
168
  <div className="relative pl-4">
192
169
  {/* Timeline line */}
193
- <div className="absolute left-0 top-1 bottom-1 w-px" style={{ background: 'var(--border)' }} />
170
+ <div className="absolute left-0 top-1 bottom-1 w-px bg-border" />
194
171
 
195
172
  <div className="flex flex-col gap-0.5">
196
173
  {visibleRecent.map(({ path: filePath, mtime }, idx) => {
@@ -201,6 +178,7 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
201
178
  <div key={filePath} className="relative group">
202
179
  {/* Timeline dot */}
203
180
  <div
181
+ aria-hidden="true"
204
182
  className={`absolute -left-4 top-1/2 -translate-y-1/2 rounded-full transition-all duration-150 group-hover:scale-150 ${idx === 0 ? 'w-2 h-2' : 'w-1.5 h-1.5'}`}
205
183
  style={{
206
184
  background: idx === 0 ? 'var(--amber)' : 'var(--border)',
@@ -212,14 +190,14 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
212
190
  className="flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-100 group-hover:translate-x-0.5 hover:bg-muted"
213
191
  >
214
192
  {isCSV
215
- ? <Table size={13} className="shrink-0" style={{ color: 'var(--success)' }} />
216
- : <FileText size={13} className="shrink-0" style={{ color: 'var(--muted-foreground)' }} />
193
+ ? <Table size={13} className="shrink-0 text-success" />
194
+ : <FileText size={13} className="shrink-0 text-muted-foreground" />
217
195
  }
218
196
  <div className="flex-1 min-w-0">
219
- <span className="text-sm font-medium truncate block" style={{ color: 'var(--foreground)' }} suppressHydrationWarning>{name}</span>
220
- {dir && <span className="text-xs truncate block" style={{ color: 'var(--muted-foreground)', opacity: 0.6 }} suppressHydrationWarning>{dir}</span>}
197
+ <span className="text-sm font-medium truncate block text-foreground" suppressHydrationWarning>{name}</span>
198
+ {dir && <span className="text-xs truncate block text-muted-foreground opacity-60" suppressHydrationWarning>{dir}</span>}
221
199
  </div>
222
- <span className="text-xs shrink-0 tabular-nums font-display" style={{ color: 'var(--muted-foreground)', opacity: 0.5 }} suppressHydrationWarning>
200
+ <span className="text-xs shrink-0 tabular-nums font-display text-muted-foreground opacity-50" suppressHydrationWarning>
223
201
  {formatTime(mtime)}
224
202
  </span>
225
203
  </Link>
@@ -233,8 +211,7 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
233
211
  <button
234
212
  onClick={() => setShowAll(v => !v)}
235
213
  aria-expanded={showAll}
236
- style={{ color: 'var(--amber)' }}
237
- className="flex items-center gap-1.5 mt-2 ml-3 text-xs font-medium transition-colors hover:opacity-80 cursor-pointer font-display"
214
+ className="flex items-center gap-1.5 mt-2 ml-3 text-xs font-medium text-[var(--amber)] transition-colors hover:opacity-80 cursor-pointer font-display"
238
215
  >
239
216
  <ChevronDown
240
217
  size={12}
@@ -250,8 +227,8 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
250
227
  })()}
251
228
 
252
229
  {/* Footer */}
253
- <div className="mt-16 flex items-center gap-1.5 text-xs font-display" style={{ color: 'var(--muted-foreground)', opacity: 0.6 }}>
254
- <Sparkles size={10} style={{ color: 'var(--amber)' }} />
230
+ <div className="mt-16 flex items-center gap-1.5 text-xs font-display text-muted-foreground opacity-60">
231
+ <Sparkles size={10} className="text-[var(--amber)]" />
255
232
  <span>{t.app.footer}</span>
256
233
  </div>
257
234
  </div>
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useState } from 'react';
4
4
  import { useRouter } from 'next/navigation';
5
- import { Sparkles, Globe, BookOpen, FileText, Loader2, GitBranch } from 'lucide-react';
5
+ import { Sparkles, Globe, BookOpen, FileText, Loader2, GitBranch, AlertCircle } from 'lucide-react';
6
6
  import { useLocale } from '@/lib/LocaleContext';
7
7
 
8
8
  type Template = 'en' | 'zh' | 'empty';
@@ -33,11 +33,13 @@ export default function OnboardingView() {
33
33
  const { t } = useLocale();
34
34
  const router = useRouter();
35
35
  const [loading, setLoading] = useState<Template | null>(null);
36
+ const [error, setError] = useState<string | null>(null);
36
37
 
37
38
  const ob = t.onboarding;
38
39
 
39
40
  async function handleSelect(template: Template) {
40
41
  setLoading(template);
42
+ setError(null);
41
43
  try {
42
44
  const res = await fetch('/api/init', {
43
45
  method: 'POST',
@@ -51,6 +53,7 @@ export default function OnboardingView() {
51
53
  router.refresh();
52
54
  } catch (e) {
53
55
  console.error('[Onboarding] init failed:', e);
56
+ setError(e instanceof Error ? e.message : 'Unknown error');
54
57
  setLoading(null);
55
58
  }
56
59
  }
@@ -60,22 +63,33 @@ export default function OnboardingView() {
60
63
  {/* Header */}
61
64
  <div className="text-center mb-10">
62
65
  <div className="inline-flex items-center gap-2 mb-4">
63
- <Sparkles size={18} style={{ color: 'var(--amber)' }} />
64
- <h1
65
- className="text-2xl font-semibold tracking-tight font-display"
66
- style={{ color: 'var(--foreground)' }}
67
- >
66
+ <Sparkles size={18} className="text-[var(--amber)]" />
67
+ <h1 className="text-2xl font-semibold tracking-tight font-display text-foreground">
68
68
  MindOS
69
69
  </h1>
70
70
  </div>
71
- <p
72
- className="text-sm leading-relaxed max-w-md mx-auto"
73
- style={{ color: 'var(--muted-foreground)' }}
74
- >
71
+ <p className="text-sm leading-relaxed max-w-md mx-auto text-muted-foreground">
75
72
  {ob.subtitle}
76
73
  </p>
77
74
  </div>
78
75
 
76
+ {/* Error banner */}
77
+ {error && (
78
+ <div
79
+ role="alert"
80
+ className="max-w-2xl mx-auto mb-6 flex items-center gap-2.5 px-4 py-3 rounded-lg border border-destructive/30 bg-destructive/5 text-sm text-destructive"
81
+ >
82
+ <AlertCircle size={16} className="shrink-0" />
83
+ <span className="flex-1">{ob.initError ?? 'Initialization failed. Please try again.'} ({error})</span>
84
+ <button
85
+ onClick={() => setError(null)}
86
+ className="text-xs underline shrink-0 hover:opacity-80"
87
+ >
88
+ {ob.dismiss ?? 'Dismiss'}
89
+ </button>
90
+ </div>
91
+ )}
92
+
79
93
  {/* Template cards */}
80
94
  <div className="grid grid-cols-1 sm:grid-cols-3 gap-4 max-w-2xl mx-auto mb-10">
81
95
  {TEMPLATES.map((tpl) => {
@@ -86,37 +100,26 @@ export default function OnboardingView() {
86
100
  key={tpl.id}
87
101
  disabled={isDisabled}
88
102
  onClick={() => handleSelect(tpl.id)}
89
- className="group relative flex flex-col items-start gap-3 p-5 rounded-xl border text-left transition-all duration-150 hover:border-amber-500/50 hover:bg-amber-500/5 disabled:opacity-60 disabled:cursor-not-allowed"
90
- style={{ background: 'var(--card)', borderColor: 'var(--border)' }}
103
+ className="group relative flex flex-col items-start gap-3 p-5 rounded-xl border border-border bg-card text-left transition-all duration-150 hover:border-amber-500/50 hover:bg-amber-500/5 disabled:opacity-60 disabled:cursor-not-allowed"
91
104
  >
92
105
  {/* Icon + title */}
93
106
  <div className="flex items-center gap-2.5 w-full">
94
- <span style={{ color: 'var(--amber)' }}>{tpl.icon}</span>
95
- <span
96
- className="text-sm font-semibold"
97
- style={{ color: 'var(--foreground)' }}
98
- >
107
+ <span className="text-[var(--amber)]">{tpl.icon}</span>
108
+ <span className="text-sm font-semibold text-foreground">
99
109
  {ob.templates[tpl.id].title}
100
110
  </span>
101
111
  {isLoading && (
102
- <Loader2 size={14} className="animate-spin ml-auto" style={{ color: 'var(--amber)' }} />
112
+ <Loader2 size={14} className="animate-spin ml-auto text-[var(--amber)]" />
103
113
  )}
104
114
  </div>
105
115
 
106
116
  {/* Description */}
107
- <p className="text-xs leading-relaxed" style={{ color: 'var(--muted-foreground)' }}>
117
+ <p className="text-xs leading-relaxed text-muted-foreground">
108
118
  {ob.templates[tpl.id].desc}
109
119
  </p>
110
120
 
111
121
  {/* Directory preview */}
112
- <div
113
- className="w-full rounded-lg px-3 py-2 text-xs leading-relaxed font-display"
114
- style={{
115
- background: 'var(--muted)',
116
- color: 'var(--muted-foreground)',
117
- opacity: 0.8,
118
- }}
119
- >
122
+ <div className="w-full rounded-lg px-3 py-2 text-xs leading-relaxed font-display bg-muted text-muted-foreground opacity-80">
120
123
  {tpl.dirs.map((d) => (
121
124
  <div key={d}>{d}</div>
122
125
  ))}
@@ -127,27 +130,18 @@ export default function OnboardingView() {
127
130
  </div>
128
131
 
129
132
  {/* Import hint */}
130
- <p
131
- className="text-center text-xs leading-relaxed max-w-sm mx-auto font-display"
132
- style={{ color: 'var(--muted-foreground)', opacity: 0.6 }}
133
- >
133
+ <p className="text-center text-xs leading-relaxed max-w-sm mx-auto font-display text-muted-foreground opacity-60">
134
134
  {ob.importHint}
135
135
  </p>
136
136
 
137
137
  {/* Sync hint card */}
138
- <div
139
- className="max-w-md mx-auto mt-6 flex items-center gap-3 px-4 py-3 rounded-lg border text-left"
140
- style={{ borderColor: 'var(--border)', background: 'var(--card)' }}
141
- >
142
- <GitBranch size={16} style={{ color: 'var(--muted-foreground)', flexShrink: 0 }} />
138
+ <div className="max-w-md mx-auto mt-6 flex items-center gap-3 px-4 py-3 rounded-lg border border-border bg-card text-left">
139
+ <GitBranch size={16} className="text-muted-foreground shrink-0" />
143
140
  <div className="min-w-0">
144
- <p className="text-xs" style={{ color: 'var(--muted-foreground)' }}>
141
+ <p className="text-xs text-muted-foreground">
145
142
  {ob.syncHint ?? 'Want cross-device sync? Run'}
146
143
  {' '}
147
- <code
148
- className="font-mono px-1 py-0.5 rounded select-all"
149
- style={{ background: 'var(--muted)', fontSize: '11px' }}
150
- >
144
+ <code className="font-mono px-1 py-0.5 rounded select-all bg-muted text-[11px]">
151
145
  mindos sync init
152
146
  </code>
153
147
  {' '}
@@ -419,6 +419,8 @@ export const en = {
419
419
  importHint: 'Already have notes? Set MIND_ROOT to your existing directory in Settings.',
420
420
  syncHint: 'Want cross-device sync? Run',
421
421
  syncHintSuffix: 'in the terminal after setup.',
422
+ initError: 'Initialization failed. Please try again.',
423
+ dismiss: 'Dismiss',
422
424
  },
423
425
  shortcuts: [
424
426
  { keys: ['⌘', 'K'], description: 'Search' },
@@ -444,6 +444,8 @@ export const zh = {
444
444
  importHint: '已有笔记?在设置中将 MIND_ROOT 指向已有目录即可。',
445
445
  syncHint: '需要跨设备同步?完成初始化后在终端运行',
446
446
  syncHintSuffix: '即可。',
447
+ initError: '初始化失败,请重试。',
448
+ dismiss: '关闭',
447
449
  },
448
450
  shortcuts: [
449
451
  { keys: ['⌘', 'K'], description: '搜索' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geminilight/mindos",
3
- "version": "0.5.33",
3
+ "version": "0.5.35",
4
4
  "description": "MindOS — Human-Agent Collaborative Mind System. Local-first knowledge base that syncs your mind to all AI Agents via MCP.",
5
5
  "keywords": [
6
6
  "mindos",