@geminilight/mindos 0.5.34 → 0.5.36

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.
@@ -76,6 +76,7 @@ body {
76
76
  --radius: 0.5rem;
77
77
  --amber: #c8873a;
78
78
  --amber-dim: rgba(200, 135, 58, 0.12);
79
+ --amber-subtle: rgba(200, 135, 30, 0.08);
79
80
  --amber-foreground: #131210;
80
81
  --success: #7aad80;
81
82
  --error: #c85050;
@@ -110,6 +111,7 @@ body {
110
111
  --ring: var(--amber);
111
112
  --amber: #d4954a;
112
113
  --amber-dim: rgba(212, 149, 74, 0.12);
114
+ --amber-subtle: rgba(212, 149, 74, 0.10);
113
115
  --amber-foreground: #131210;
114
116
  --success: #7aad80;
115
117
  --error: #c85050;
@@ -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"
@@ -39,7 +39,6 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
39
39
  setGuideState(gs);
40
40
  if (gs.step1Done) setBrowsedCount(1);
41
41
  } else {
42
- // Guide inactive or dismissed — clear local state
43
42
  setGuideState(null);
44
43
  }
45
44
  })
@@ -49,13 +48,9 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
49
48
  useEffect(() => {
50
49
  fetchGuideState();
51
50
 
52
- // Listen for walkthrough-triggered first visit (WalkthroughProvider owns ?welcome=1 detection)
53
- const handleFirstVisit = () => {
54
- setIsFirstVisit(true);
55
- };
51
+ const handleFirstVisit = () => { setIsFirstVisit(true); };
56
52
  window.addEventListener('mindos:first-visit', handleFirstVisit);
57
53
 
58
- // Re-fetch when guide state is updated (e.g. after AskFab patches askedAI)
59
54
  const handleGuideUpdate = () => fetchGuideState();
60
55
  window.addEventListener('focus', handleGuideUpdate);
61
56
  window.addEventListener('guide-state-updated', handleGuideUpdate);
@@ -66,14 +61,12 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
66
61
  };
67
62
  }, [fetchGuideState]);
68
63
 
69
- // Auto-expand KB task on first visit
70
64
  useEffect(() => {
71
65
  if (isFirstVisit && guideState && !guideState.step1Done) {
72
66
  setExpanded('kb');
73
67
  }
74
68
  }, [isFirstVisit, guideState]);
75
69
 
76
- // Patch guideState to backend
77
70
  const patchGuide = useCallback((patch: Partial<GuideState>) => {
78
71
  setGuideState(prev => prev ? { ...prev, ...patch } : prev);
79
72
  fetch('/api/setup', {
@@ -93,7 +86,6 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
93
86
  if (browsedCount === 0) {
94
87
  setBrowsedCount(1);
95
88
  patchGuide({ step1Done: true });
96
- // Collapse after a beat
97
89
  setTimeout(() => setExpanded(null), 300);
98
90
  }
99
91
  }, [browsedCount, patchGuide, onNavigate]);
@@ -109,8 +101,6 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
109
101
  const isEmpty = gs?.template === 'empty';
110
102
  const prompt = isEmpty ? g.ai.promptEmpty : g.ai.prompt;
111
103
  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
104
  }, [guideState, g]);
115
105
 
116
106
  const handleNextStepClick = useCallback(() => {
@@ -119,13 +109,11 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
119
109
  const steps = g.done.steps;
120
110
  if (idx < steps.length) {
121
111
  openAskModal(steps[idx].prompt, 'guide-next');
122
- // Optimistic local update — AskFab will persist to backend on first message
123
112
  patchGuide({ nextStepIndex: idx + 1 });
124
113
  }
125
114
  }, [guideState, g, patchGuide]);
126
115
 
127
116
  const handleSyncClick = useCallback(() => {
128
- // Dispatch ⌘, to open Settings modal
129
117
  window.dispatchEvent(new KeyboardEvent('keydown', { key: ',', metaKey: true, bubbles: true }));
130
118
  }, []);
131
119
 
@@ -144,10 +132,8 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
144
132
  return () => { if (autoDismissRef.current) clearTimeout(autoDismissRef.current); };
145
133
  }, [allNextDone_, handleDismiss]);
146
134
 
147
- // Don't render if no active guide
148
135
  if (!guideState) return null;
149
136
 
150
- // Hide GuideCard while walkthrough is active
151
137
  const walkthroughActive = guideState.walkthroughStep !== undefined
152
138
  && guideState.walkthroughStep >= 0
153
139
  && guideState.walkthroughStep < walkthroughSteps.length
@@ -165,21 +151,18 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
165
151
  // After all next-steps done → final state (auto-dismisses after 8s)
166
152
  if (allDone && allNextDone) {
167
153
  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)' }}>
154
+ <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 bg-[var(--amber-subtle)]">
155
+ <Sparkles size={16} className="animate-spin-slow text-[var(--amber)]" />
156
+ <span className="text-sm font-semibold flex-1 text-foreground">
172
157
  ✨ {g.done.titleFinal}
173
158
  </span>
174
159
  <Link
175
160
  href="/explore"
176
- className="text-xs font-medium transition-colors hover:opacity-80"
177
- style={{ color: 'var(--amber)' }}
161
+ className="text-xs font-medium text-[var(--amber)] transition-colors hover:opacity-80"
178
162
  >
179
163
  {t.walkthrough.exploreCta}
180
164
  </Link>
181
- <button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors"
182
- style={{ color: 'var(--muted-foreground)' }}>
165
+ <button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors text-muted-foreground">
183
166
  <X size={14} />
184
167
  </button>
185
168
  </div>
@@ -190,23 +173,20 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
190
173
  if (allDone) {
191
174
  const step = nextSteps[nextIdx];
192
175
  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)' }}>
176
+ <div className="mb-6 rounded-xl border border-[var(--amber)] px-5 py-4 animate-in fade-in duration-300 bg-[var(--amber-subtle)]">
195
177
  <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)' }}>
178
+ <Sparkles size={16} className="text-[var(--amber)]" />
179
+ <span className="text-sm font-semibold flex-1 text-foreground">
198
180
  🎉 {g.done.title}
199
181
  </span>
200
- <button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors"
201
- style={{ color: 'var(--muted-foreground)' }}>
182
+ <button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors text-muted-foreground">
202
183
  <X size={14} />
203
184
  </button>
204
185
  </div>
205
186
  {step && (
206
187
  <button
207
188
  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)' }}
189
+ 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
190
  >
211
191
  <ChevronRight size={14} />
212
192
  <span>{step.hint}</span>
@@ -218,60 +198,31 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
218
198
 
219
199
  // Main guide card with 3 tasks
220
200
  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)' }}>
201
+ <div className="mb-6 rounded-xl border border-[var(--amber)] overflow-hidden bg-[var(--amber-subtle)]">
223
202
 
224
203
  {/* Header */}
225
204
  <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)' }}>
205
+ <Sparkles size={16} className="text-[var(--amber)]" />
206
+ <span className="text-sm font-semibold flex-1 font-display text-foreground">
228
207
  {g.title}
229
208
  </span>
230
- <button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors"
231
- style={{ color: 'var(--muted-foreground)' }}>
209
+ <button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors text-muted-foreground">
232
210
  <X size={14} />
233
211
  </button>
234
212
  </div>
235
213
 
236
214
  {/* Task cards */}
237
215
  <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
- />
216
+ <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')} />
217
+ <TaskCard icon={<MessageCircle size={16} />} title={g.ai.title} cta={g.ai.cta} done={step2Done} active={expanded === 'ai'} onClick={() => { if (!step2Done) handleStartAI(); }} />
218
+ <TaskCard icon={<RefreshCw size={16} />} title={g.sync.title} cta={g.sync.cta} done={false} optional={g.sync.optional} active={false} onClick={handleSyncClick} />
268
219
  </div>
269
220
 
270
221
  {/* Expanded content: Explore KB */}
271
222
  {expanded === 'kb' && !step1Done && (
272
223
  <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)' }}>
224
+ <div className="rounded-lg border border-border bg-card p-4">
225
+ <p className="text-xs mb-3 text-muted-foreground">
275
226
  {isEmptyTemplate ? g.kb.emptyDesc : g.kb.fullDesc}
276
227
  </p>
277
228
 
@@ -279,12 +230,11 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
279
230
  <div className="flex flex-col gap-1.5">
280
231
  {EMPTY_FILES.map(file => (
281
232
  <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)' }}>
233
+ 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
234
  📄 {(g.kb.emptyFiles as Record<string, string>)[file.split('.')[0].toLowerCase()] || file}
285
235
  </button>
286
236
  ))}
287
- <p className="text-xs mt-2" style={{ color: 'var(--muted-foreground)', opacity: 0.7 }}>
237
+ <p className="text-xs mt-2 text-muted-foreground opacity-70">
288
238
  {g.kb.emptyHint}
289
239
  </p>
290
240
  </div>
@@ -293,29 +243,27 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
293
243
  <div className="grid grid-cols-2 sm:grid-cols-3 gap-1.5">
294
244
  {Object.entries(DIR_ICONS).map(([key, icon]) => (
295
245
  <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)' }}>
246
+ 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
247
  <span className="mr-1.5">{icon}</span>
299
248
  <span className="capitalize">{key}</span>
300
- <span className="block text-2xs mt-0.5" style={{ color: 'var(--muted-foreground)' }}>
249
+ <span className="block text-2xs mt-0.5 text-muted-foreground">
301
250
  {(g.kb.dirs as Record<string, string>)[key]}
302
251
  </span>
303
252
  </button>
304
253
  ))}
305
254
  </div>
306
- <p className="text-xs mt-3" style={{ color: 'var(--amber)' }}>
255
+ <p className="text-xs mt-3 text-[var(--amber)]">
307
256
  💡 {g.kb.instructionHint}
308
257
  </p>
309
258
  </>
310
259
  )}
311
260
 
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)' }}>
261
+ <div className="flex items-center justify-between mt-3 pt-3 border-t border-border">
262
+ <span className="text-xs text-muted-foreground">
314
263
  {g.kb.progress(browsedCount)}
315
264
  </span>
316
265
  <button onClick={handleSkipKB}
317
- className="text-xs px-3 py-1 rounded-lg transition-colors hover:bg-muted"
318
- style={{ color: 'var(--muted-foreground)' }}>
266
+ className="text-xs px-3 py-1 rounded-lg text-muted-foreground transition-colors hover:bg-muted">
319
267
  {g.skip}
320
268
  </button>
321
269
  </div>
@@ -326,10 +274,8 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
326
274
  );
327
275
  }
328
276
 
329
- // Expose callback for AskModal integration
330
277
  export { type GuideCardProps };
331
278
 
332
- // Reusable sub-component
333
279
  function TaskCard({ icon, title, cta, done, active, optional, onClick }: {
334
280
  icon: React.ReactNode;
335
281
  title: string;
@@ -348,25 +294,22 @@ function TaskCard({ icon, title, cta, done, active, optional, onClick }: {
348
294
  transition-all duration-150
349
295
  ${done ? 'opacity-60' : 'hover:border-amber-500/30 hover:bg-muted/50 cursor-pointer'}
350
296
  ${active ? 'border-amber-500/40 bg-muted/50' : ''}
297
+ ${done || active ? 'border-[var(--amber)]' : 'border-border'}
351
298
  `}
352
- style={{ borderColor: done || active ? 'var(--amber)' : 'var(--border)' }}
353
299
  >
354
- <span
355
- className={done ? 'animate-in zoom-in-50 duration-300' : ''}
356
- style={{ color: done ? 'var(--success)' : 'var(--amber)' }}
357
- >
300
+ <span className={`${done ? 'animate-in zoom-in-50 duration-300 text-success' : 'text-[var(--amber)]'}`}>
358
301
  {done ? <Check size={16} /> : icon}
359
302
  </span>
360
- <span className="text-xs font-medium" style={{ color: 'var(--foreground)' }}>
303
+ <span className="text-xs font-medium text-foreground">
361
304
  {title}
362
305
  </span>
363
306
  {optional && (
364
- <span className="text-2xs px-1.5 py-0.5 rounded-full" style={{ background: 'var(--muted)', color: 'var(--muted-foreground)' }}>
307
+ <span className="text-2xs px-1.5 py-0.5 rounded-full bg-muted text-muted-foreground">
365
308
  {optional}
366
309
  </span>
367
310
  )}
368
311
  {!done && !optional && (
369
- <span className="text-2xs" style={{ color: 'var(--amber)' }}>
312
+ <span className="text-2xs text-[var(--amber)]">
370
313
  {cta} →
371
314
  </span>
372
315
  )}
@@ -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">
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,13 +211,11 @@ 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}
241
- className="transition-transform duration-200"
242
- style={{ transform: showAll ? 'rotate(180deg)' : undefined }}
218
+ className={`transition-transform duration-200 ${showAll ? 'rotate-180' : ''}`}
243
219
  />
244
220
  <span>{showAll ? t.home.showLess : t.home.showMore}</span>
245
221
  </button>
@@ -250,8 +226,8 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
250
226
  })()}
251
227
 
252
228
  {/* 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)' }} />
229
+ <div className="mt-16 flex items-center gap-1.5 text-xs font-display text-muted-foreground opacity-60">
230
+ <Sparkles size={10} className="text-[var(--amber)]" />
255
231
  <span>{t.app.footer}</span>
256
232
  </div>
257
233
  </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.'}</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/app/next-env.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference types="next" />
2
2
  /// <reference types="next/image-types/global" />
3
- import "./.next/dev/types/routes.d.ts";
3
+ import "./.next/types/routes.d.ts";
4
4
 
5
5
  // NOTE: This file should not be edited
6
6
  // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geminilight/mindos",
3
- "version": "0.5.34",
3
+ "version": "0.5.36",
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",
@@ -1,63 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useEffect } from 'react';
4
- import { X, Sparkles } from 'lucide-react';
5
- import { useLocale } from '@/lib/LocaleContext';
6
-
7
- export default function WelcomeBanner() {
8
- const { t } = useLocale();
9
- const s = t.setup;
10
- const [visible, setVisible] = useState(false);
11
-
12
- useEffect(() => {
13
- // Show banner if ?welcome=1 is in the URL
14
- const params = new URLSearchParams(window.location.search);
15
- if (params.get('welcome') === '1') {
16
- setVisible(true);
17
- // Remove ?welcome=1 from URL without reloading
18
- const url = new URL(window.location.href);
19
- url.searchParams.delete('welcome');
20
- const newUrl = url.pathname + (url.searchParams.size > 0 ? '?' + url.searchParams.toString() : '');
21
- window.history.replaceState({}, '', newUrl);
22
- }
23
- }, []);
24
-
25
- if (!visible) return null;
26
-
27
- return (
28
- <div className="mb-6 rounded-xl border px-5 py-4 flex items-start gap-4"
29
- style={{ background: 'var(--amber-subtle, rgba(200,135,30,0.08))', borderColor: 'var(--amber)' }}>
30
- <Sparkles size={18} className="mt-0.5 shrink-0" style={{ color: 'var(--amber)' }} />
31
- <div className="flex-1 min-w-0">
32
- <p className="text-sm font-semibold mb-1" style={{ color: 'var(--foreground)' }}>
33
- {s.welcomeTitle}
34
- </p>
35
- <p className="text-xs leading-relaxed mb-3" style={{ color: 'var(--muted-foreground)' }}>
36
- {s.welcomeDesc}
37
- </p>
38
- <div className="flex flex-wrap gap-2">
39
- <a href="/setup?force=1" className="text-xs px-3 py-1.5 rounded-lg border transition-colors"
40
- style={{ borderColor: 'var(--amber)', color: 'var(--amber)' }}>
41
- {s.welcomeLinkReconfigure}
42
- </a>
43
- <button onClick={() => window.dispatchEvent(new KeyboardEvent('keydown', { key: '/', metaKey: true, bubbles: true }))}
44
- className="text-xs px-3 py-1.5 rounded-lg border transition-colors"
45
- style={{ borderColor: 'var(--border)', color: 'var(--muted-foreground)' }}>
46
- {s.welcomeLinkAskAI}
47
- </button>
48
- <button
49
- onClick={() => window.dispatchEvent(new KeyboardEvent('keydown', { key: ',', metaKey: true, bubbles: true }))}
50
- className="text-xs px-3 py-1.5 rounded-lg border transition-colors"
51
- style={{ borderColor: 'var(--border)', color: 'var(--muted-foreground)' }}>
52
- {s.welcomeLinkMCP}
53
- </button>
54
- </div>
55
- </div>
56
- <button onClick={() => setVisible(false)}
57
- className="p-1 rounded hover:bg-muted transition-colors shrink-0"
58
- style={{ color: 'var(--muted-foreground)' }}>
59
- <X size={14} />
60
- </button>
61
- </div>
62
- );
63
- }