@geminilight/mindos 0.6.8 โ†’ 0.6.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +2 -0
  2. package/README_zh.md +2 -0
  3. package/app/app/api/mcp/install/route.ts +4 -1
  4. package/app/app/api/setup/check-path/route.ts +2 -7
  5. package/app/app/api/setup/ls/route.ts +3 -9
  6. package/app/app/api/setup/path-utils.ts +8 -0
  7. package/app/app/api/setup/route.ts +2 -7
  8. package/app/app/api/uninstall/route.ts +47 -0
  9. package/app/app/globals.css +11 -0
  10. package/app/components/ActivityBar.tsx +10 -3
  11. package/app/components/AskFab.tsx +7 -3
  12. package/app/components/CreateSpaceModal.tsx +1 -1
  13. package/app/components/DirView.tsx +1 -1
  14. package/app/components/FileTree.tsx +30 -23
  15. package/app/components/GuideCard.tsx +1 -1
  16. package/app/components/HomeContent.tsx +137 -109
  17. package/app/components/ImportModal.tsx +16 -477
  18. package/app/components/MarkdownView.tsx +3 -0
  19. package/app/components/OnboardingView.tsx +1 -1
  20. package/app/components/OrganizeToast.tsx +386 -0
  21. package/app/components/Panel.tsx +23 -2
  22. package/app/components/Sidebar.tsx +1 -1
  23. package/app/components/SidebarLayout.tsx +44 -1
  24. package/app/components/agents/AgentDetailContent.tsx +33 -12
  25. package/app/components/agents/AgentsMcpSection.tsx +1 -1
  26. package/app/components/agents/AgentsOverviewSection.tsx +3 -4
  27. package/app/components/agents/AgentsPrimitives.tsx +2 -2
  28. package/app/components/agents/AgentsSkillsSection.tsx +2 -2
  29. package/app/components/agents/SkillDetailPopover.tsx +24 -8
  30. package/app/components/ask/AskContent.tsx +124 -75
  31. package/app/components/ask/HighlightMatch.tsx +14 -0
  32. package/app/components/ask/MentionPopover.tsx +5 -3
  33. package/app/components/ask/MessageList.tsx +39 -11
  34. package/app/components/ask/SlashCommandPopover.tsx +4 -2
  35. package/app/components/changes/ChangesBanner.tsx +20 -2
  36. package/app/components/changes/ChangesContentPage.tsx +10 -2
  37. package/app/components/echo/EchoHero.tsx +1 -1
  38. package/app/components/echo/EchoInsightCollapsible.tsx +1 -1
  39. package/app/components/echo/EchoPageSections.tsx +1 -1
  40. package/app/components/explore/UseCaseCard.tsx +1 -1
  41. package/app/components/panels/DiscoverPanel.tsx +29 -25
  42. package/app/components/panels/ImportHistoryPanel.tsx +195 -0
  43. package/app/components/panels/PluginsPanel.tsx +2 -2
  44. package/app/components/settings/AiTab.tsx +24 -0
  45. package/app/components/settings/KnowledgeTab.tsx +1 -1
  46. package/app/components/settings/McpSkillCreateForm.tsx +1 -1
  47. package/app/components/settings/McpSkillRow.tsx +1 -1
  48. package/app/components/settings/McpSkillsSection.tsx +2 -2
  49. package/app/components/settings/McpTab.tsx +2 -2
  50. package/app/components/settings/PluginsTab.tsx +1 -1
  51. package/app/components/settings/Primitives.tsx +118 -6
  52. package/app/components/settings/SettingsContent.tsx +5 -2
  53. package/app/components/settings/UninstallTab.tsx +179 -0
  54. package/app/components/settings/UpdateTab.tsx +17 -5
  55. package/app/components/settings/types.ts +2 -1
  56. package/app/components/setup/StepDots.tsx +2 -2
  57. package/app/components/ui/dialog.tsx +1 -1
  58. package/app/hooks/useAiOrganize.ts +122 -10
  59. package/app/hooks/useMention.ts +21 -3
  60. package/app/hooks/useSlashCommand.ts +18 -4
  61. package/app/lib/agent/reconnect.ts +40 -0
  62. package/app/lib/core/backlinks.ts +2 -2
  63. package/app/lib/core/git.ts +14 -10
  64. package/app/lib/fs.ts +2 -1
  65. package/app/lib/i18n-en.ts +46 -2
  66. package/app/lib/i18n-zh.ts +46 -2
  67. package/app/lib/organize-history.ts +74 -0
  68. package/app/lib/settings.ts +2 -0
  69. package/app/lib/types.ts +2 -0
  70. package/app/next.config.ts +23 -5
  71. package/bin/cli.js +6 -9
  72. package/bin/lib/mcp-build.js +74 -0
  73. package/bin/lib/mcp-spawn.js +8 -5
  74. package/bin/lib/port.js +17 -2
  75. package/bin/lib/stop.js +12 -2
  76. package/mcp/dist/index.cjs +43 -43
  77. package/mcp/src/index.ts +58 -12
  78. package/package.json +1 -1
  79. package/scripts/setup.js +2 -2
package/README.md CHANGED
@@ -272,9 +272,11 @@ Join our WeChat group for early access, feedback, and AI workflow discussions:
272
272
  ## ๐Ÿ‘ฅ Contributors
273
273
 
274
274
  <a href="https://github.com/GeminiLight"><img src="https://github.com/GeminiLight.png" width="60" style="border-radius:50%" alt="GeminiLight" /></a>
275
+ <a href="https://github.com/U-rara"><img src="https://github.com/U-rara.png" width="60" style="border-radius:50%" alt="U-rara" /></a>
275
276
  <a href="https://github.com/yeahjack"><img src="https://github.com/yeahjack.png" width="60" style="border-radius:50%" alt="yeahjack" /></a>
276
277
  <a href="https://github.com/USTChandsomeboy"><img src="https://github.com/USTChandsomeboy.png" width="60" style="border-radius:50%" alt="USTChandsomeboy" /></a>
277
278
  <a href="https://github.com/ppsmk388"><img src="https://github.com/ppsmk388.png" width="60" style="border-radius:50%" alt="ppsmk388" /></a>
279
+ <a href="https://github.com/one2piece2hello"><img src="https://github.com/one2piece2hello.png" width="60" style="border-radius:50%" alt="one2piece2hello" /></a>
278
280
 
279
281
  ### ๐Ÿ™ Acknowledgements
280
282
 
package/README_zh.md CHANGED
@@ -275,6 +275,8 @@ MindOS/
275
275
  <a href="https://github.com/yeahjack"><img src="https://github.com/yeahjack.png" width="60" style="border-radius:50%" alt="yeahjack" /></a>
276
276
  <a href="https://github.com/USTChandsomeboy"><img src="https://github.com/USTChandsomeboy.png" width="60" style="border-radius:50%" alt="USTChandsomeboy" /></a>
277
277
  <a href="https://github.com/ppsmk388"><img src="https://github.com/ppsmk388.png" width="60" style="border-radius:50%" alt="ppsmk388" /></a>
278
+ <a href="https://github.com/U-rara"><img src="https://github.com/U-rara.png" width="60" style="border-radius:50%" alt="U-rara" /></a>
279
+ <a href="https://github.com/one2piece2hello"><img src="https://github.com/one2piece2hello.png" width="60" style="border-radius:50%" alt="one2piece2hello" /></a>
278
280
 
279
281
  ### ๐Ÿ™ ่‡ด่ฐข
280
282
 
@@ -3,6 +3,7 @@ import { NextRequest, NextResponse } from 'next/server';
3
3
  import fs from 'fs';
4
4
  import path from 'path';
5
5
  import { MCP_AGENTS, expandHome } from '@/lib/mcp-agents';
6
+ import { readSettings } from '@/lib/settings';
6
7
 
7
8
  /** Parse JSONC โ€” strips single-line (//) and block comments before JSON.parse */
8
9
  function parseJsonc(text: string): Record<string, unknown> {
@@ -104,7 +105,9 @@ function buildEntry(transport: string, url?: string, token?: string) {
104
105
  if (transport === 'stdio') {
105
106
  return { type: 'stdio', command: 'mindos', args: ['mcp'], env: { MCP_TRANSPORT: 'stdio' } };
106
107
  }
107
- const entry: Record<string, unknown> = { url: url || 'http://localhost:8781/mcp' };
108
+ // Resolve MCP port from env โ†’ config โ†’ default, not hardcoded
109
+ const fallbackPort = Number(process.env.MINDOS_MCP_PORT) || readSettings().mcpPort || 8781;
110
+ const entry: Record<string, unknown> = { url: url || `http://localhost:${fallbackPort}/mcp` };
108
111
  if (token) entry.headers = { Authorization: `Bearer ${token}` };
109
112
  return entry;
110
113
  }
@@ -1,12 +1,7 @@
1
1
  export const dynamic = 'force-dynamic';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
3
  import { existsSync, readdirSync } from 'node:fs';
4
- import { homedir } from 'node:os';
5
- import { resolve } from 'node:path';
6
-
7
- function expandHome(p: string): string {
8
- return p.startsWith('~/') ? resolve(homedir(), p.slice(2)) : p;
9
- }
4
+ import { expandSetupPathHome } from '../path-utils';
10
5
 
11
6
  export async function POST(req: NextRequest) {
12
7
  try {
@@ -14,7 +9,7 @@ export async function POST(req: NextRequest) {
14
9
  if (!path || typeof path !== 'string') {
15
10
  return NextResponse.json({ error: 'Invalid path' }, { status: 400 });
16
11
  }
17
- const abs = expandHome(path.trim());
12
+ const abs = expandSetupPathHome(path.trim());
18
13
  const exists = existsSync(abs);
19
14
  let empty = true;
20
15
  let count = 0;
@@ -1,14 +1,8 @@
1
1
  export const dynamic = 'force-dynamic';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
3
  import { existsSync, readdirSync, statSync } from 'node:fs';
4
- import { homedir } from 'node:os';
5
- import { resolve, join } from 'node:path';
6
-
7
- function expandHome(p: string): string {
8
- if (p === '~') return homedir();
9
- if (p.startsWith('~/') || p.startsWith('~\\')) return resolve(homedir(), p.slice(2));
10
- return p;
11
- }
4
+ import { join } from 'node:path';
5
+ import { expandSetupPathHome } from '../path-utils';
12
6
 
13
7
  export async function POST(req: NextRequest) {
14
8
  try {
@@ -16,7 +10,7 @@ export async function POST(req: NextRequest) {
16
10
  if (!path || typeof path !== 'string') {
17
11
  return NextResponse.json({ dirs: [] });
18
12
  }
19
- const abs = expandHome(path.trim());
13
+ const abs = expandSetupPathHome(path.trim());
20
14
  if (!existsSync(abs)) {
21
15
  return NextResponse.json({ dirs: [] });
22
16
  }
@@ -0,0 +1,8 @@
1
+ import { homedir } from 'node:os';
2
+ import { resolve } from 'node:path';
3
+
4
+ export function expandSetupPathHome(p: string): string {
5
+ if (p === '~') return homedir();
6
+ if (p.startsWith('~/') || p.startsWith('~\\')) return resolve(homedir(), p.slice(2));
7
+ return p;
8
+ }
@@ -4,6 +4,7 @@ import fs from 'fs';
4
4
  import os from 'os';
5
5
  import { readSettings, writeSettings, ServerSettings } from '@/lib/settings';
6
6
  import { applyTemplate } from '@/lib/template';
7
+ import { expandSetupPathHome } from './path-utils';
7
8
 
8
9
  function maskApiKey(key: string): string {
9
10
  if (!key || key.length < 6) return key ? '***' : '';
@@ -40,12 +41,6 @@ export async function GET() {
40
41
  }
41
42
  }
42
43
 
43
- function expandHome(p: string): string {
44
- if (p.startsWith('~/')) return p.replace('~', os.homedir());
45
- if (p === '~') return os.homedir();
46
- return p;
47
- }
48
-
49
44
  export async function POST(req: NextRequest) {
50
45
  try {
51
46
  const body = await req.json();
@@ -56,7 +51,7 @@ export async function POST(req: NextRequest) {
56
51
  return NextResponse.json({ error: 'mindRoot is required' }, { status: 400 });
57
52
  }
58
53
 
59
- const resolvedRoot = expandHome(mindRoot.trim());
54
+ const resolvedRoot = expandSetupPathHome(mindRoot.trim());
60
55
 
61
56
  // Validate ports
62
57
  const webPort = typeof port === 'number' ? port : 3456;
@@ -0,0 +1,47 @@
1
+ export const dynamic = 'force-dynamic';
2
+ import { NextResponse } from 'next/server';
3
+ import { spawn } from 'node:child_process';
4
+ import { resolve } from 'node:path';
5
+
6
+ /**
7
+ * POST /api/uninstall
8
+ *
9
+ * Accepts JSON body: { removeConfig?: boolean }
10
+ *
11
+ * Always: stops services + removes daemon + npm uninstall -g.
12
+ * Optionally: removes ~/.mindos/ config directory.
13
+ * Knowledge base is NEVER touched from the Web UI.
14
+ */
15
+ export async function POST(req: Request) {
16
+ try {
17
+ const body = await req.json().catch(() => ({}));
18
+ const removeConfig = body.removeConfig !== false; // default true
19
+
20
+ const cliPath = process.env.MINDOS_CLI_PATH || resolve(process.env.MINDOS_PROJECT_ROOT || process.cwd(), '..', 'bin', 'cli.js');
21
+ const nodeBin = process.env.MINDOS_NODE_BIN || process.execPath;
22
+
23
+ // Build stdin answers for the interactive CLI prompts:
24
+ // 1. "Proceed with uninstall?" โ†’ always Y
25
+ // 2. "Remove config directory?" โ†’ Y or N based on option
26
+ // 3. "Remove knowledge base?" โ†’ always N (never delete KB from Web UI)
27
+ const answers = `Y\n${removeConfig ? 'Y' : 'N'}\nN\n`;
28
+
29
+ const child = spawn(nodeBin, [cliPath, 'uninstall'], {
30
+ detached: true,
31
+ stdio: ['pipe', 'ignore', 'ignore'],
32
+ env: { ...process.env },
33
+ });
34
+
35
+ if (child.stdin) {
36
+ child.stdin.write(answers);
37
+ child.stdin.end();
38
+ }
39
+
40
+ child.unref();
41
+
42
+ return NextResponse.json({ ok: true });
43
+ } catch (err) {
44
+ const message = err instanceof Error ? err.message : String(err);
45
+ return NextResponse.json({ error: message }, { status: 500 });
46
+ }
47
+ }
@@ -75,6 +75,7 @@ body {
75
75
  --ring: var(--amber);
76
76
  --radius: 0.5rem;
77
77
  --amber: #c8873a;
78
+ --amber-text: #9a6a2b;
78
79
  --amber-dim: rgba(200, 135, 58, 0.18);
79
80
  --amber-subtle: rgba(200, 135, 30, 0.08);
80
81
  --amber-foreground: #131210;
@@ -110,6 +111,7 @@ body {
110
111
  --input: rgba(232, 228, 220, 0.1);
111
112
  --ring: var(--amber);
112
113
  --amber: #d4954a;
114
+ --amber-text: #e0a85e;
113
115
  --amber-dim: rgba(212, 149, 74, 0.20);
114
116
  --amber-subtle: rgba(212, 149, 74, 0.10);
115
117
  --amber-foreground: #131210;
@@ -304,6 +306,15 @@ body {
304
306
  background: rgba(0, 0, 0, 0.65);
305
307
  }
306
308
 
309
+ .overlay-backdrop {
310
+ background: rgba(10, 9, 6, 0.35);
311
+ backdrop-filter: blur(2px);
312
+ -webkit-backdrop-filter: blur(2px);
313
+ }
314
+ .dark .overlay-backdrop {
315
+ background: rgba(0, 0, 0, 0.4);
316
+ }
317
+
307
318
  /* Micro type scale: text-2xs = 10px (between nothing and text-xs 12px) */
308
319
  @layer utilities {
309
320
  .text-2xs { font-size: 10px; line-height: 1.4; }
@@ -2,13 +2,13 @@
2
2
 
3
3
  import { useRef, useCallback, useState, useEffect } from 'react';
4
4
  import Link from 'next/link';
5
- import { FolderTree, Search, Settings, RefreshCw, Bot, Compass, HelpCircle, ChevronLeft, ChevronRight, Radio } from 'lucide-react';
5
+ import { FolderTree, Search, Settings, RefreshCw, Bot, Compass, HelpCircle, ChevronLeft, ChevronRight, Radio, History } from 'lucide-react';
6
6
  import { useLocale } from '@/lib/LocaleContext';
7
7
  import { DOT_COLORS, getStatusLevel } from './SyncStatusBar';
8
8
  import type { SyncStatus } from './settings/SyncTab';
9
9
  import Logo from './Logo';
10
10
 
11
- export type PanelId = 'files' | 'search' | 'echo' | 'agents' | 'discover';
11
+ export type PanelId = 'files' | 'search' | 'echo' | 'agents' | 'discover' | 'history';
12
12
 
13
13
  export const RAIL_WIDTH_COLLAPSED = 48;
14
14
  export const RAIL_WIDTH_EXPANDED = 180;
@@ -129,9 +129,15 @@ export default function ActivityBar({
129
129
  setHasUpdate(true);
130
130
  } catch { /* silent */ }
131
131
  }, 5000);
132
+ const onAvail = () => setHasUpdate(true);
132
133
  const onDismiss = () => setHasUpdate(false);
134
+ window.addEventListener('mindos:update-available', onAvail);
133
135
  window.addEventListener('mindos:update-dismissed', onDismiss);
134
- return () => { clearTimeout(timer); window.removeEventListener('mindos:update-dismissed', onDismiss); };
136
+ return () => {
137
+ clearTimeout(timer);
138
+ window.removeEventListener('mindos:update-available', onAvail);
139
+ window.removeEventListener('mindos:update-dismissed', onDismiss);
140
+ };
135
141
  }, []);
136
142
 
137
143
  /** Debounce rapid clicks (300ms) โ€” shared across all Rail buttons */
@@ -192,6 +198,7 @@ export default function ActivityBar({
192
198
  walkthroughId="agents-panel"
193
199
  />
194
200
  <RailButton icon={<Compass size={18} />} label={t.sidebar.discover} active={activePanel === 'discover'} expanded={expanded} onClick={() => toggle('discover')} />
201
+ <RailButton icon={<History size={18} />} label={t.sidebar.history} active={activePanel === 'history'} expanded={expanded} onClick={() => toggle('history')} />
195
202
  </div>
196
203
 
197
204
  {/* โ”€โ”€ Spacer โ”€โ”€ */}
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { Sparkles } from 'lucide-react';
4
+ import { useLocale } from '@/lib/LocaleContext';
4
5
 
5
6
  interface AskFabProps {
6
7
  /** Toggle the right-side Ask AI panel */
@@ -10,6 +11,9 @@ interface AskFabProps {
10
11
  }
11
12
 
12
13
  export default function AskFab({ onToggle, askPanelOpen }: AskFabProps) {
14
+ const { t } = useLocale();
15
+ const label = `${t.ask?.fabLabel ?? 'Ask AI'} (โŒ˜/)`;
16
+
13
17
  return (
14
18
  <button
15
19
  onClick={onToggle}
@@ -29,8 +33,8 @@ export default function AskFab({ onToggle, askPanelOpen }: AskFabProps) {
29
33
  style={{
30
34
  background: 'linear-gradient(135deg, var(--amber), color-mix(in srgb, var(--amber) 80%, white))',
31
35
  }}
32
- title="MindOS Agent (โŒ˜/)"
33
- aria-label="MindOS Agent"
36
+ title={label}
37
+ aria-label={label}
34
38
  >
35
39
  <Sparkles size={16} className="relative z-10 shrink-0" />
36
40
  <span className="
@@ -40,7 +44,7 @@ export default function AskFab({ onToggle, askPanelOpen }: AskFabProps) {
40
44
  transition-all duration-200 ease-out
41
45
  whitespace-nowrap overflow-hidden
42
46
  ">
43
- MindOS Agent
47
+ {t.ask?.fabLabel ?? 'Ask AI'}
44
48
  </span>
45
49
  </button>
46
50
  );
@@ -158,7 +158,7 @@ export default function CreateSpaceModal({ t, dirPaths }: { t: ReturnType<typeof
158
158
  return createPortal(
159
159
  <div className="fixed inset-0 z-50 flex items-center justify-center" onKeyDown={handleKeyDown}>
160
160
  {/* Backdrop */}
161
- <div className="absolute inset-0 bg-black/40 dark:bg-black/60" onClick={close} />
161
+ <div className="absolute inset-0 overlay-backdrop" onClick={close} />
162
162
  {/* Dialog */}
163
163
  <div
164
164
  role="dialog"
@@ -76,7 +76,7 @@ function SpacePreviewCard({ icon, title, lines, viewAllHref, viewAllLabel }: {
76
76
  </div>
77
77
  <div className="space-y-1">
78
78
  {lines.map((line, i) => (
79
- <p key={i} className="text-sm text-muted-foreground/80 leading-relaxed">
79
+ <p key={i} className="text-sm text-muted-foreground/80 leading-relaxed" suppressHydrationWarning>
80
80
  ยท {line}
81
81
  </p>
82
82
  ))}
@@ -301,7 +301,12 @@ function DirectoryNode({ node, depth, currentPath, onNavigate, maxOpenDepth, onI
301
301
  return;
302
302
  }
303
303
  if (prevMaxOpenDepth.current !== maxOpenDepth) {
304
- setOpen(depth <= maxOpenDepth);
304
+ const enteringControlled = prevMaxOpenDepth.current === null || prevMaxOpenDepth.current === undefined;
305
+ if (enteringControlled) {
306
+ if (depth > maxOpenDepth) setOpen(false);
307
+ } else {
308
+ setOpen(depth <= maxOpenDepth);
309
+ }
305
310
  prevMaxOpenDepth.current = maxOpenDepth;
306
311
  }
307
312
  }, [maxOpenDepth, depth]);
@@ -466,29 +471,31 @@ function DirectoryNode({ node, depth, currentPath, onNavigate, maxOpenDepth, onI
466
471
  </div>
467
472
 
468
473
  <div
469
- className={`overflow-hidden transition-all duration-200 ${showBorder ? 'border-l-2 ml-[18px]' : ''}`}
470
- style={{
471
- maxHeight: open ? '9999px' : '0px',
472
- ...(showBorder ? { borderColor: 'color-mix(in srgb, var(--amber) 30%, transparent)' } : {}),
473
- }}
474
+ className={`grid transition-[grid-template-rows] duration-200 ease-out ${open ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'}`}
474
475
  >
475
- {node.children && (
476
- <FileTree
477
- nodes={node.children}
478
- depth={showBorder ? 1 : depth + 1}
479
- onNavigate={onNavigate}
480
- maxOpenDepth={maxOpenDepth}
481
- parentIsSpace={isSpace}
482
- onImport={onImport}
483
- />
484
- )}
485
- {showNewFile && (
486
- <NewFileInline
487
- dirPath={node.path}
488
- depth={showBorder ? 0 : depth}
489
- onDone={() => setShowNewFile(false)}
490
- />
491
- )}
476
+ <div
477
+ className={`overflow-hidden ${showBorder ? 'border-l-2 ml-[18px]' : ''}`}
478
+ style={showBorder ? { borderColor: 'color-mix(in srgb, var(--amber) 30%, transparent)' } : undefined}
479
+ {...(!open && { inert: true } as React.HTMLAttributes<HTMLDivElement>)}
480
+ >
481
+ {node.children && (
482
+ <FileTree
483
+ nodes={node.children}
484
+ depth={showBorder ? 1 : depth + 1}
485
+ onNavigate={onNavigate}
486
+ maxOpenDepth={maxOpenDepth}
487
+ parentIsSpace={isSpace}
488
+ onImport={onImport}
489
+ />
490
+ )}
491
+ {showNewFile && (
492
+ <NewFileInline
493
+ dirPath={node.path}
494
+ depth={showBorder ? 0 : depth}
495
+ onDone={() => setShowNewFile(false)}
496
+ />
497
+ )}
498
+ </div>
492
499
  </div>
493
500
 
494
501
  {contextMenu && (isSpace ? (
@@ -388,7 +388,7 @@ function TaskCard({ icon, title, cta, done, active, dimmed, onClick }: {
388
388
  {title}
389
389
  </span>
390
390
  {!done && !dimmed && (
391
- <span className="text-2xs text-[var(--amber)]">
391
+ <span className="text-2xs text-[var(--amber-text)]">
392
392
  {cta} โ†’
393
393
  </span>
394
394
  )}