@geminilight/mindos 0.5.40 → 0.5.42

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.
@@ -1,16 +1,15 @@
1
1
  'use client';
2
2
 
3
3
  import Link from 'next/link';
4
- import { FileText, Table, Clock, Sparkles, ArrowRight, FilePlus, Search, ChevronDown, Compass, Folder, Puzzle, Brain, Plus, Loader2 } from 'lucide-react';
5
- import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
4
+ import { FileText, Table, Clock, Sparkles, ArrowRight, FilePlus, Search, ChevronDown, Compass, Folder, Puzzle, Brain, Plus } from 'lucide-react';
5
+ import { useState, useEffect, useMemo } from 'react';
6
6
  import { useLocale } from '@/lib/LocaleContext';
7
7
  import { encodePath, relativeTime } from '@/lib/utils';
8
8
  import { getAllRenderers } from '@/lib/renderers/registry';
9
9
  import '@/lib/renderers/index'; // registers all renderers
10
10
  import OnboardingView from './OnboardingView';
11
11
  import GuideCard from './GuideCard';
12
- import { useRouter } from 'next/navigation';
13
- import { createSpaceAction } from '@/lib/actions';
12
+ import CreateSpaceModal from './CreateSpaceModal';
14
13
  import type { SpaceInfo } from '@/app/page';
15
14
 
16
15
  interface RecentFile {
@@ -78,22 +77,35 @@ function stripEmoji(name: string): string {
78
77
  }
79
78
 
80
79
  /* ── Section Title component (shared across all three sections) ── */
81
- function SectionTitle({ icon, children }: { icon: React.ReactNode; children: React.ReactNode }) {
80
+ interface SectionTitleProps {
81
+ icon: React.ReactNode;
82
+ children: React.ReactNode;
83
+ /** Item count badge — only rendered when > 0 */
84
+ count?: number;
85
+ /** Right-aligned action slot (e.g. "View all" button) */
86
+ action?: React.ReactNode;
87
+ }
88
+
89
+ function SectionTitle({ icon, children, count, action }: SectionTitleProps) {
82
90
  return (
83
91
  <div className="flex items-center gap-2 mb-4">
84
92
  <span className="text-[var(--amber)]">{icon}</span>
85
- <h2 className="text-xs font-semibold uppercase tracking-[0.08em] font-display text-muted-foreground">
93
+ <h2 className="text-sm font-semibold font-display text-foreground">
86
94
  {children}
87
95
  </h2>
96
+ {count != null && count > 0 && (
97
+ <span className="text-xs tabular-nums text-muted-foreground font-display">{count}</span>
98
+ )}
99
+ {action ? <div className="ml-auto">{action}</div> : null}
88
100
  </div>
89
101
  );
90
102
  }
91
103
 
92
104
  const FILES_PER_GROUP = 3;
93
105
  const SPACES_PER_ROW = 6; // 3 cols × 2 rows on desktop, show 1 row initially
94
- const PLUGINS_INITIAL = 6;
106
+ const PLUGINS_INITIAL = 4;
95
107
 
96
- export default function HomeContent({ recent, existingFiles, spaces }: { recent: RecentFile[]; existingFiles?: string[]; spaces?: SpaceInfo[] }) {
108
+ export default function HomeContent({ recent, existingFiles, spaces, dirPaths }: { recent: RecentFile[]; existingFiles?: string[]; spaces?: SpaceInfo[]; dirPaths?: string[] }) {
97
109
  const { t } = useLocale();
98
110
  const [showAll, setShowAll] = useState(false);
99
111
  const [showAllSpaces, setShowAllSpaces] = useState(false);
@@ -208,10 +220,88 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
208
220
  </div>
209
221
  </div>
210
222
 
211
- {/* ── Section 1: Plugins ── */}
223
+ {/* ── Section 1: Spaces ── */}
224
+ <section className="mb-8">
225
+ <SectionTitle
226
+ icon={<Brain size={13} />}
227
+ count={spaceList.length > 0 ? spaceList.length : undefined}
228
+ action={<CreateSpaceButton t={t} />}
229
+ >
230
+ {t.home.spaces}
231
+ </SectionTitle>
232
+ {spaceList.length > 0 ? (
233
+ <>
234
+ <div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
235
+ {(showAllSpaces ? spaceList : spaceList.slice(0, SPACES_PER_ROW)).map(s => {
236
+ const emoji = extractEmoji(s.name);
237
+ const label = stripEmoji(s.name);
238
+ const isEmpty = s.fileCount === 0;
239
+ return (
240
+ <Link
241
+ key={s.name}
242
+ href={`/view/${encodePath(s.path)}`}
243
+ className={`flex items-start gap-3 px-3.5 py-3 rounded-xl border transition-all duration-150 hover:translate-x-0.5 ${
244
+ isEmpty
245
+ ? 'border-dashed border-border/50 opacity-50 hover:opacity-70'
246
+ : 'border-border hover:border-amber-500/30 hover:bg-muted/40'
247
+ }`}
248
+ >
249
+ {emoji ? (
250
+ <span className="text-lg leading-none shrink-0 mt-0.5" suppressHydrationWarning>{emoji}</span>
251
+ ) : (
252
+ <Folder size={16} className="shrink-0 text-[var(--amber)] mt-0.5" />
253
+ )}
254
+ <div className="min-w-0 flex-1">
255
+ <span className="text-sm font-medium truncate block text-foreground">{label}</span>
256
+ {s.description && (
257
+ <span className="text-xs text-muted-foreground line-clamp-1 mt-0.5" suppressHydrationWarning>{s.description}</span>
258
+ )}
259
+ <span className="text-xs text-muted-foreground opacity-50 mt-0.5 block">
260
+ {t.home.nFiles(s.fileCount)}
261
+ </span>
262
+ </div>
263
+ </Link>
264
+ );
265
+ })}
266
+ </div>
267
+ {spaceList.length > SPACES_PER_ROW && (
268
+ <button
269
+ onClick={() => setShowAllSpaces(v => !v)}
270
+ className="flex items-center gap-1.5 mt-2 text-xs font-medium text-[var(--amber)] transition-colors hover:opacity-80 cursor-pointer font-display"
271
+ >
272
+ <ChevronDown size={12} className={`transition-transform duration-200 ${showAllSpaces ? 'rotate-180' : ''}`} />
273
+ <span>{showAllSpaces ? t.home.showLess : t.home.showMore}</span>
274
+ </button>
275
+ )}
276
+ </>
277
+ ) : (
278
+ <p className="text-xs text-muted-foreground py-2">
279
+ {t.home.noSpacesYet ?? 'No spaces yet. Create one to organize your knowledge.'}
280
+ </p>
281
+ )}
282
+ <CreateSpaceModal t={t} dirPaths={dirPaths ?? []} />
283
+ </section>
284
+
285
+ {/* ── Section 2: Extensions ── */}
212
286
  {availablePlugins.length > 0 && (
213
287
  <section className="mb-8">
214
- <SectionTitle icon={<Puzzle size={13} />}>{t.home.plugins}</SectionTitle>
288
+ <SectionTitle
289
+ icon={<Puzzle size={13} />}
290
+ count={availablePlugins.length}
291
+ action={
292
+ availablePlugins.length > PLUGINS_INITIAL ? (
293
+ <button
294
+ onClick={() => setShowAllPlugins(v => !v)}
295
+ className="flex items-center gap-1 text-xs font-medium text-[var(--amber)] transition-colors hover:opacity-80 cursor-pointer font-display"
296
+ >
297
+ <span>{showAllPlugins ? t.home.showLess : t.home.viewAll}</span>
298
+ <ChevronDown size={12} className={`transition-transform duration-200 ${showAllPlugins ? 'rotate-180' : ''}`} />
299
+ </button>
300
+ ) : undefined
301
+ }
302
+ >
303
+ {t.home.plugins}
304
+ </SectionTitle>
215
305
  <div className="flex flex-wrap gap-2">
216
306
  {(showAllPlugins ? availablePlugins : availablePlugins.slice(0, PLUGINS_INITIAL)).map(r => (
217
307
  <Link
@@ -224,85 +314,13 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
224
314
  </Link>
225
315
  ))}
226
316
  </div>
227
- {availablePlugins.length > PLUGINS_INITIAL && (
228
- <button
229
- onClick={() => setShowAllPlugins(v => !v)}
230
- className="flex items-center gap-1.5 mt-2 text-xs font-medium text-[var(--amber)] transition-colors hover:opacity-80 cursor-pointer font-display"
231
- >
232
- <ChevronDown size={12} className={`transition-transform duration-200 ${showAllPlugins ? 'rotate-180' : ''}`} />
233
- <span>{showAllPlugins ? t.home.showLess : t.home.showMore}</span>
234
- </button>
235
- )}
236
- </section>
237
- )}
238
-
239
- {/* ── Section 2: Spaces ── */}
240
- {spaceList.length > 0 ? (
241
- <section className="mb-8">
242
- <SectionTitle icon={<Brain size={13} />}>{t.home.spaces}</SectionTitle>
243
- <div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
244
- {(showAllSpaces ? spaceList : spaceList.slice(0, SPACES_PER_ROW)).map(s => {
245
- const emoji = extractEmoji(s.name);
246
- const label = stripEmoji(s.name);
247
- const isEmpty = s.fileCount === 0;
248
- return (
249
- <Link
250
- key={s.name}
251
- href={`/view/${encodePath(s.path)}`}
252
- className={`flex items-start gap-3 px-3.5 py-3 rounded-xl border transition-all duration-150 hover:translate-x-0.5 ${
253
- isEmpty
254
- ? 'border-dashed border-border/50 opacity-50 hover:opacity-70'
255
- : 'border-border hover:border-amber-500/30 hover:bg-muted/40'
256
- }`}
257
- >
258
- {emoji ? (
259
- <span className="text-lg leading-none shrink-0 mt-0.5" suppressHydrationWarning>{emoji}</span>
260
- ) : (
261
- <Folder size={16} className="shrink-0 text-[var(--amber)] mt-0.5" />
262
- )}
263
- <div className="min-w-0 flex-1">
264
- <span className="text-sm font-medium truncate block text-foreground">{label}</span>
265
- {s.description && (
266
- <span className="text-xs text-muted-foreground line-clamp-1 mt-0.5" suppressHydrationWarning>{s.description}</span>
267
- )}
268
- <span className="text-xs text-muted-foreground opacity-50 mt-0.5 block">
269
- {t.home.nFiles(s.fileCount)}
270
- </span>
271
- </div>
272
- </Link>
273
- );
274
- })}
275
- {/* Show "+" card inline only when it won't be orphaned on its own row */}
276
- {(showAllSpaces || spaceList.length < SPACES_PER_ROW) && <CreateSpaceCard t={t} />}
277
- </div>
278
- {spaceList.length > SPACES_PER_ROW && (
279
- <div className="flex items-center gap-3 mt-2">
280
- <button
281
- onClick={() => setShowAllSpaces(v => !v)}
282
- className="flex items-center gap-1.5 text-xs font-medium text-[var(--amber)] transition-colors hover:opacity-80 cursor-pointer font-display"
283
- >
284
- <ChevronDown size={12} className={`transition-transform duration-200 ${showAllSpaces ? 'rotate-180' : ''}`} />
285
- <span>{showAllSpaces ? t.home.showLess : t.home.showMore}</span>
286
- </button>
287
- {/* When collapsed and "+" card is hidden from grid, show as text link */}
288
- {!showAllSpaces && <CreateSpaceTextLink t={t} />}
289
- </div>
290
- )}
291
- </section>
292
- ) : (
293
- /* Show create card even when no spaces exist */
294
- <section className="mb-8">
295
- <SectionTitle icon={<Brain size={13} />}>{t.home.spaces}</SectionTitle>
296
- <div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
297
- <CreateSpaceCard t={t} />
298
- </div>
299
317
  </section>
300
318
  )}
301
319
 
302
320
  {/* ── Section 3: Recently Edited ── */}
303
321
  {recent.length > 0 && (
304
322
  <section className="mb-12">
305
- <SectionTitle icon={<Clock size={13} />}>{t.home.recentlyEdited}</SectionTitle>
323
+ <SectionTitle icon={<Clock size={13} />} count={recent.length}>{t.home.recentlyEdited}</SectionTitle>
306
324
 
307
325
  {groups.length > 0 ? (
308
326
  /* Space-Grouped View */
@@ -463,150 +481,16 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
463
481
  );
464
482
  }
465
483
 
466
- /* ── Create Space inline card ── */
467
- function CreateSpaceCard({ t }: { t: ReturnType<typeof useLocale>['t'] }) {
468
- const router = useRouter();
469
- const [editing, setEditing] = useState(false);
470
- const [name, setName] = useState('');
471
- const [description, setDescription] = useState('');
472
- const [loading, setLoading] = useState(false);
473
- const [error, setError] = useState('');
474
- const inputRef = useRef<HTMLInputElement>(null);
475
-
476
- const open = useCallback(() => {
477
- setEditing(true);
478
- setError('');
479
- setTimeout(() => inputRef.current?.focus(), 50);
480
- }, []);
481
-
482
- const close = useCallback(() => {
483
- setEditing(false);
484
- setName('');
485
- setDescription('');
486
- setError('');
487
- }, []);
488
-
489
- const handleCreate = useCallback(async () => {
490
- if (!name.trim() || loading) return;
491
- setLoading(true);
492
- setError('');
493
- const result = await createSpaceAction(name, description);
494
- setLoading(false);
495
- if (result.success) {
496
- close();
497
- router.refresh();
498
- } else {
499
- setError(result.error ?? 'Failed to create space');
500
- }
501
- }, [name, description, loading, close, router]);
502
-
503
- const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
504
- if (e.key === 'Escape') close();
505
- if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleCreate(); }
506
- }, [close, handleCreate]);
507
-
508
- if (!editing) {
509
- return (
510
- <button
511
- onClick={open}
512
- aria-label={t.home.newSpace}
513
- className="flex items-center justify-center gap-2 px-3.5 py-3 rounded-xl border border-dashed border-border/50 text-muted-foreground opacity-60 transition-all duration-150 hover:opacity-100 hover:border-amber-500/30 cursor-pointer"
514
- >
515
- <Plus size={16} />
516
- <span className="text-sm font-medium">{t.home.newSpace}</span>
517
- </button>
518
- );
519
- }
520
-
521
- return (
522
- <div className="flex flex-col gap-2 px-3.5 py-3 rounded-xl border border-[var(--amber)] bg-[var(--amber-subtle)]" onKeyDown={handleKeyDown}>
523
- <input
524
- ref={inputRef}
525
- type="text"
526
- value={name}
527
- onChange={e => { setName(e.target.value); setError(''); }}
528
- placeholder={t.home.spaceName}
529
- maxLength={80}
530
- className="text-sm font-medium bg-transparent border-b border-border pb-1 outline-none placeholder:text-muted-foreground/50 text-foreground focus:border-[var(--amber)]"
531
- />
532
- <input
533
- type="text"
534
- value={description}
535
- onChange={e => setDescription(e.target.value)}
536
- placeholder={t.home.spaceDescription}
537
- maxLength={200}
538
- className="text-xs bg-transparent border-b border-border/50 pb-1 outline-none placeholder:text-muted-foreground/40 text-muted-foreground focus:border-[var(--amber)]"
539
- />
540
- {error && <span className="text-xs text-error">{error}</span>}
541
- <div className="flex items-center gap-2 mt-1">
542
- <button
543
- onClick={handleCreate}
544
- disabled={!name.trim() || loading}
545
- className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium bg-[var(--amber)] text-white transition-colors hover:opacity-90 disabled:opacity-40 disabled:cursor-not-allowed"
546
- >
547
- {loading && <Loader2 size={12} className="animate-spin" />}
548
- {t.home.createSpace}
549
- </button>
550
- <button
551
- onClick={close}
552
- className="px-3 py-1.5 rounded-lg text-xs font-medium text-muted-foreground transition-colors hover:bg-muted"
553
- >
554
- {t.home.cancelCreate}
555
- </button>
556
- </div>
557
- </div>
558
- );
559
- }
560
-
561
- /** Compact "+ New Space" text link shown next to "Show more" when grid is collapsed */
562
- function CreateSpaceTextLink({ t }: { t: ReturnType<typeof useLocale>['t'] }) {
563
- const router = useRouter();
564
- const [editing, setEditing] = useState(false);
565
- const [name, setName] = useState('');
566
- const [loading, setLoading] = useState(false);
567
- const inputRef = useRef<HTMLInputElement>(null);
568
-
569
- if (!editing) {
570
- return (
571
- <button
572
- onClick={() => { setEditing(true); setTimeout(() => inputRef.current?.focus(), 50); }}
573
- className="flex items-center gap-1 text-xs font-medium text-muted-foreground transition-colors hover:text-[var(--amber)] cursor-pointer"
574
- >
575
- <Plus size={11} />
576
- <span>{t.home.newSpace}</span>
577
- </button>
578
- );
579
- }
580
-
484
+ /* ── Create Space: title-bar button ── */
485
+ function CreateSpaceButton({ t }: { t: ReturnType<typeof useLocale>['t'] }) {
581
486
  return (
582
- <form
583
- className="flex items-center gap-2"
584
- onSubmit={async (e) => {
585
- e.preventDefault();
586
- if (!name.trim() || loading) return;
587
- setLoading(true);
588
- const result = await createSpaceAction(name, '');
589
- setLoading(false);
590
- if (result.success) { setEditing(false); setName(''); router.refresh(); }
591
- }}
487
+ <button
488
+ onClick={() => window.dispatchEvent(new Event('mindos:create-space'))}
489
+ className="flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-medium bg-[var(--amber)] text-white transition-colors hover:opacity-90 cursor-pointer"
592
490
  >
593
- <input
594
- ref={inputRef}
595
- type="text"
596
- value={name}
597
- onChange={e => setName(e.target.value)}
598
- onKeyDown={e => { if (e.key === 'Escape') { setEditing(false); setName(''); } }}
599
- placeholder={t.home.spaceName}
600
- maxLength={80}
601
- className="text-xs bg-transparent border-b border-border pb-0.5 outline-none w-28 placeholder:text-muted-foreground/40 text-foreground focus:border-[var(--amber)]"
602
- />
603
- <button
604
- type="submit"
605
- disabled={!name.trim() || loading}
606
- className="text-xs font-medium text-[var(--amber)] disabled:opacity-40"
607
- >
608
- {loading ? '...' : t.home.createSpace}
609
- </button>
610
- </form>
491
+ <Plus size={12} />
492
+ <span>{t.home.newSpace}</span>
493
+ </button>
611
494
  );
612
495
  }
496
+
@@ -28,6 +28,7 @@ const DEFAULT_PANEL_WIDTH: Record<PanelId, number> = {
28
28
  search: 280,
29
29
  plugins: 280,
30
30
  agents: 280,
31
+ discover: 280,
31
32
  };
32
33
 
33
34
  const MIN_PANEL_WIDTH = 240;
@@ -11,6 +11,7 @@ import Logo from './Logo';
11
11
  import SearchPanel from './panels/SearchPanel';
12
12
  import PluginsPanel from './panels/PluginsPanel';
13
13
  import AgentsPanel from './panels/AgentsPanel';
14
+ import DiscoverPanel from './panels/DiscoverPanel';
14
15
  import RightAskPanel from './RightAskPanel';
15
16
  import AskFab from './AskFab';
16
17
  import SyncPopover from './panels/SyncPopover';
@@ -211,6 +212,9 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
211
212
  <div className={`flex flex-col h-full ${lp.activePanel === 'agents' ? '' : 'hidden'}`}>
212
213
  <AgentsPanel active={lp.activePanel === 'agents'} maximized={lp.panelMaximized} onMaximize={lp.handlePanelMaximize} />
213
214
  </div>
215
+ <div className={`flex flex-col h-full ${lp.activePanel === 'discover' ? '' : 'hidden'}`}>
216
+ <DiscoverPanel active={lp.activePanel === 'discover'} maximized={lp.panelMaximized} onMaximize={lp.handlePanelMaximize} />
217
+ </div>
214
218
  </Panel>
215
219
 
216
220
  {/* ── Right-side Ask AI Panel ── */}
@@ -14,7 +14,7 @@ export default function ThemeToggle() {
14
14
  const stored = localStorage.getItem('theme');
15
15
  return stored ? stored === 'dark' : window.matchMedia('(prefers-color-scheme: dark)').matches;
16
16
  },
17
- () => true,
17
+ () => document.documentElement.classList.contains('dark'),
18
18
  );
19
19
 
20
20
  const toggle = () => {
@@ -15,7 +15,6 @@ export default function UpdateBanner() {
15
15
  const [info, setInfo] = useState<UpdateInfo | null>(null);
16
16
 
17
17
  useEffect(() => {
18
- // Don't check for updates on setup or login pages
19
18
  if (typeof window !== 'undefined') {
20
19
  const path = window.location.pathname;
21
20
  if (path === '/setup' || path === '/login') return;
@@ -30,10 +29,12 @@ export default function UpdateBanner() {
30
29
  if (data.latest === dismissed) return;
31
30
 
32
31
  setInfo({ latest: data.latest, current: data.current });
32
+ // Broadcast for ActivityBar & Settings tab badges
33
+ window.dispatchEvent(new CustomEvent('mindos:update-available', { detail: { latest: data.latest } }));
33
34
  } catch {
34
35
  // Network error / API failure — silent
35
36
  }
36
- }, 3000); // Check 3s after page load, don't block first paint
37
+ }, 3000);
37
38
 
38
39
  return () => clearTimeout(timer);
39
40
  }, []);
@@ -43,53 +44,44 @@ export default function UpdateBanner() {
43
44
  const handleDismiss = () => {
44
45
  localStorage.setItem('mindos_update_dismissed', info.latest);
45
46
  setInfo(null);
47
+ window.dispatchEvent(new Event('mindos:update-dismissed'));
46
48
  };
47
49
 
48
50
  const handleOpenUpdate = () => {
49
51
  window.dispatchEvent(new CustomEvent('mindos:open-settings', { detail: { tab: 'update' } }));
50
- // Dismiss banner once user engages with Update tab
51
52
  localStorage.setItem('mindos_update_dismissed', info.latest);
52
53
  setInfo(null);
54
+ window.dispatchEvent(new Event('mindos:update-dismissed'));
53
55
  };
54
56
 
55
- const updateT = t.updateBanner;
57
+ const u = t.updateBanner;
56
58
 
57
59
  return (
58
- <div
59
- className="flex items-center justify-between gap-3 px-4 py-2 text-xs"
60
- style={{ background: 'var(--amber-subtle, rgba(200,135,30,0.08))', borderBottom: '1px solid var(--border)' }}
61
- >
62
- <div className="flex items-center gap-2 min-w-0">
63
- <span className="font-medium" style={{ color: 'var(--amber)' }}>
64
- {updateT?.newVersion
65
- ? updateT.newVersion(info.latest, info.current)
60
+ <div className="flex items-center justify-between gap-3 px-4 py-2 text-xs bg-[var(--amber-subtle)] border-b border-border">
61
+ <div className="flex items-center gap-2 min-w-0 flex-wrap">
62
+ <span className="font-medium text-[var(--amber)]">
63
+ {u?.newVersion
64
+ ? u.newVersion(info.latest, info.current)
66
65
  : `MindOS v${info.latest} available (current: v${info.current})`}
67
66
  </span>
68
67
  <button
69
68
  onClick={handleOpenUpdate}
70
- className="px-2 py-0.5 rounded-md text-xs font-medium text-white transition-colors hover:opacity-90"
71
- style={{ background: 'var(--amber)' }}
69
+ className="px-2 py-0.5 rounded-md text-xs font-medium bg-[var(--amber)] text-white transition-colors hover:opacity-90"
72
70
  >
73
- {updateT?.updateNow ?? 'Update'}
71
+ {u?.updateNow ?? 'Update'}
74
72
  </button>
75
- <span className="text-muted-foreground hidden sm:inline">
76
- {updateT?.orSee ? (
77
- <>
78
- {updateT.orSee}{' '}
79
- <a href="https://github.com/GeminiLight/mindos/releases" target="_blank" rel="noopener noreferrer" className="underline hover:text-foreground transition-colors">{updateT.releaseNotes}</a>
80
- </>
81
- ) : (
82
- <>
83
- or{' '}
84
- <a href="https://github.com/GeminiLight/mindos/releases" target="_blank" rel="noopener noreferrer" className="underline hover:text-foreground transition-colors">release notes</a>
85
- </>
86
- )}
87
- </span>
73
+ <a
74
+ href="https://github.com/GeminiLight/mindos/releases"
75
+ target="_blank"
76
+ rel="noopener noreferrer"
77
+ className="text-muted-foreground underline hover:text-foreground transition-colors hidden sm:inline"
78
+ >
79
+ {u?.releaseNotes ?? 'Release notes'}
80
+ </a>
88
81
  </div>
89
82
  <button
90
83
  onClick={handleDismiss}
91
- className="p-0.5 rounded hover:bg-muted transition-colors shrink-0"
92
- style={{ color: 'var(--muted-foreground)' }}
84
+ className="p-0.5 rounded hover:bg-muted transition-colors shrink-0 text-muted-foreground"
93
85
  title="Dismiss"
94
86
  >
95
87
  <X size={14} />