@geminilight/mindos 0.5.64 → 0.5.66

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 (86) hide show
  1. package/README.md +4 -0
  2. package/README_zh.md +4 -0
  3. package/app/app/api/ask/route.ts +12 -0
  4. package/app/app/api/file/route.ts +9 -0
  5. package/app/app/api/mcp/agents/route.ts +27 -1
  6. package/app/app/api/skills/route.ts +18 -2
  7. package/app/app/api/tree-version/route.ts +8 -0
  8. package/app/components/ActivityBar.tsx +2 -2
  9. package/app/components/Backlinks.tsx +5 -5
  10. package/app/components/CreateSpaceModal.tsx +3 -2
  11. package/app/components/DirPicker.tsx +1 -1
  12. package/app/components/DirView.tsx +2 -3
  13. package/app/components/EditorWrapper.tsx +3 -3
  14. package/app/components/FileTree.tsx +25 -10
  15. package/app/components/GuideCard.tsx +4 -4
  16. package/app/components/HomeContent.tsx +6 -11
  17. package/app/components/MarkdownView.tsx +2 -2
  18. package/app/components/OnboardingView.tsx +1 -1
  19. package/app/components/Panel.tsx +1 -1
  20. package/app/components/RightAgentDetailPanel.tsx +1 -1
  21. package/app/components/RightAskPanel.tsx +1 -1
  22. package/app/components/SearchModal.tsx +10 -2
  23. package/app/components/SidebarLayout.tsx +35 -10
  24. package/app/components/ThemeToggle.tsx +1 -1
  25. package/app/components/agents/AgentDetailContent.tsx +454 -59
  26. package/app/components/agents/AgentsContentPage.tsx +70 -5
  27. package/app/components/agents/AgentsMcpSection.tsx +474 -159
  28. package/app/components/agents/AgentsOverviewSection.tsx +418 -59
  29. package/app/components/agents/AgentsPrimitives.tsx +335 -0
  30. package/app/components/agents/AgentsSkillsSection.tsx +739 -121
  31. package/app/components/agents/SkillDetailPopover.tsx +416 -0
  32. package/app/components/agents/agents-content-model.ts +292 -10
  33. package/app/components/ask/AskContent.tsx +34 -5
  34. package/app/components/ask/FileChip.tsx +1 -0
  35. package/app/components/ask/MentionPopover.tsx +13 -1
  36. package/app/components/ask/MessageList.tsx +5 -7
  37. package/app/components/ask/ToolCallBlock.tsx +4 -4
  38. package/app/components/changes/ChangesBanner.tsx +1 -2
  39. package/app/components/echo/EchoHero.tsx +10 -24
  40. package/app/components/echo/EchoInsightCollapsible.tsx +52 -43
  41. package/app/components/echo/EchoPageSections.tsx +13 -9
  42. package/app/components/echo/EchoSegmentNav.tsx +14 -11
  43. package/app/components/echo/EchoSegmentPageClient.tsx +64 -43
  44. package/app/components/explore/ExploreContent.tsx +3 -7
  45. package/app/components/explore/UseCaseCard.tsx +4 -15
  46. package/app/components/panels/AgentsPanel.tsx +12 -104
  47. package/app/components/panels/AgentsPanelAgentDetail.tsx +2 -2
  48. package/app/components/panels/AgentsPanelAgentGroups.tsx +3 -7
  49. package/app/components/panels/AgentsPanelAgentListRow.tsx +9 -11
  50. package/app/components/panels/EchoPanel.tsx +8 -10
  51. package/app/components/panels/PanelNavRow.tsx +9 -2
  52. package/app/components/panels/PluginsPanel.tsx +2 -2
  53. package/app/components/renderers/agent-inspector/AgentInspectorRenderer.tsx +30 -8
  54. package/app/components/renderers/agent-inspector/manifest.ts +3 -3
  55. package/app/components/renderers/todo/manifest.ts +1 -0
  56. package/app/components/settings/AiTab.tsx +3 -3
  57. package/app/components/settings/AppearanceTab.tsx +2 -2
  58. package/app/components/settings/KnowledgeTab.tsx +3 -3
  59. package/app/components/settings/McpAgentInstall.tsx +3 -6
  60. package/app/components/settings/McpSkillCreateForm.tsx +2 -3
  61. package/app/components/settings/McpSkillRow.tsx +2 -3
  62. package/app/components/settings/McpSkillsSection.tsx +2 -2
  63. package/app/components/settings/McpTab.tsx +12 -13
  64. package/app/components/settings/MonitoringTab.tsx +13 -13
  65. package/app/components/settings/PluginsTab.tsx +2 -2
  66. package/app/components/settings/Primitives.tsx +3 -4
  67. package/app/components/settings/SettingsContent.tsx +3 -3
  68. package/app/components/settings/SyncTab.tsx +11 -17
  69. package/app/components/settings/UpdateTab.tsx +18 -21
  70. package/app/components/settings/types.ts +14 -0
  71. package/app/components/setup/StepKB.tsx +1 -1
  72. package/app/hooks/useMcpData.tsx +4 -2
  73. package/app/hooks/useMention.ts +25 -8
  74. package/app/lib/agent/log.ts +15 -18
  75. package/app/lib/agent/prompt.ts +17 -29
  76. package/app/lib/agent/stream-consumer.ts +3 -0
  77. package/app/lib/agent/to-agent-messages.ts +6 -4
  78. package/app/lib/core/agent-audit-log.ts +280 -0
  79. package/app/lib/core/index.ts +11 -0
  80. package/app/lib/fs.ts +9 -0
  81. package/app/lib/i18n-en.ts +259 -33
  82. package/app/lib/i18n-zh.ts +258 -32
  83. package/app/lib/mcp-agents.ts +231 -2
  84. package/app/lib/types.ts +2 -0
  85. package/package.json +1 -1
  86. package/scripts/migrate-agent-audit-log.js +170 -0
@@ -103,7 +103,7 @@ export function EchoInsightCollapsible({
103
103
  const generateDisabled = aiLoading || !aiReady || streaming;
104
104
 
105
105
  return (
106
- <div className="mt-10 overflow-hidden rounded-xl border border-border bg-card shadow-sm transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--amber)]/15 hover:shadow-md">
106
+ <div className="mt-10 overflow-hidden rounded-xl border border-border bg-card shadow-sm transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--amber)]/15 hover:shadow">
107
107
  <button
108
108
  id={btnId}
109
109
  type="button"
@@ -129,56 +129,65 @@ export function EchoInsightCollapsible({
129
129
  />
130
130
  <span className="sr-only">{open ? hideLabel : showLabel}</span>
131
131
  </button>
132
- {open ? (
133
- <div
134
- id={panelId}
135
- role="region"
136
- aria-labelledby={btnId}
137
- className="border-t border-border/60 px-5 pb-5 pt-4"
138
- >
139
- <p className="font-sans text-sm leading-relaxed text-muted-foreground">{hint}</p>
140
- <div className="mt-4 flex flex-wrap items-center gap-2">
141
- <button
142
- type="button"
143
- disabled={generateDisabled}
144
- onClick={runGenerate}
145
- className="inline-flex items-center gap-2 rounded-lg bg-[var(--amber)] px-3 py-2 font-sans text-sm font-medium text-white transition-opacity duration-150 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
146
- >
147
- {streaming ? <Loader2 size={16} className="animate-spin shrink-0" aria-hidden /> : null}
148
- {streaming ? generatingLabel : generateLabel}
149
- </button>
150
- {err ? (
132
+ <div
133
+ id={panelId}
134
+ role="region"
135
+ aria-labelledby={btnId}
136
+ className={cn(
137
+ 'grid transition-[grid-template-rows] duration-250 ease-out',
138
+ open ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',
139
+ )}
140
+ >
141
+ <div className="overflow-hidden">
142
+ <div className="border-t border-border/60 px-5 pb-5 pt-4">
143
+ <p className="font-sans text-sm leading-relaxed text-muted-foreground">{hint}</p>
144
+ <div className="mt-4 flex flex-wrap items-center gap-2">
151
145
  <button
152
146
  type="button"
147
+ disabled={generateDisabled}
153
148
  onClick={runGenerate}
154
- disabled={streaming || !aiReady}
155
- className="font-sans text-sm text-[var(--amber)] underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
149
+ className="inline-flex items-center gap-2 rounded-lg bg-[var(--amber)] px-3 py-2 font-sans text-sm font-medium text-[var(--amber-foreground)] transition-opacity duration-150 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
156
150
  >
157
- {retryLabel}
151
+ {streaming ? (
152
+ <Loader2 size={16} className="animate-spin shrink-0" aria-hidden />
153
+ ) : (
154
+ <Sparkles size={15} className="shrink-0" aria-hidden />
155
+ )}
156
+ {streaming ? generatingLabel : generateLabel}
158
157
  </button>
159
- ) : null}
160
- </div>
161
- {!aiLoading && !aiReady ? (
162
- <p className="mt-2 font-sans text-2xs text-muted-foreground">{noAiHint}</p>
163
- ) : null}
164
- {err ? (
165
- <p className="mt-3 font-sans text-sm text-error" role="alert">
166
- {errorPrefix} {err}
167
- </p>
168
- ) : null}
169
- {insightMd ? (
170
- <div className={cn(proseInsight, 'mt-4 border-t border-border/60 pt-4')}>
171
- <ReactMarkdown remarkPlugins={[remarkGfm]}>{insightMd}</ReactMarkdown>
172
- {streaming ? (
173
- <span
174
- className="ml-0.5 inline-block h-3.5 w-1 animate-pulse rounded-sm bg-[var(--amber)] align-middle"
175
- aria-hidden
176
- />
158
+ {err ? (
159
+ <button
160
+ type="button"
161
+ onClick={runGenerate}
162
+ disabled={streaming || !aiReady}
163
+ className="font-sans text-sm text-[var(--amber)] underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
164
+ >
165
+ {retryLabel}
166
+ </button>
177
167
  ) : null}
178
168
  </div>
179
- ) : null}
169
+ {!aiLoading && !aiReady ? (
170
+ <p className="mt-2 font-sans text-2xs text-muted-foreground">{noAiHint}</p>
171
+ ) : null}
172
+ {err ? (
173
+ <p className="mt-3 font-sans text-sm text-error" role="alert">
174
+ {errorPrefix} {err}
175
+ </p>
176
+ ) : null}
177
+ {insightMd ? (
178
+ <div className={cn(proseInsight, 'mt-4 border-t border-border/60 pt-4')}>
179
+ <ReactMarkdown remarkPlugins={[remarkGfm]}>{insightMd}</ReactMarkdown>
180
+ {streaming ? (
181
+ <span
182
+ className="ml-0.5 inline-block h-3.5 w-1 animate-pulse rounded-sm bg-[var(--amber)] align-middle"
183
+ aria-hidden
184
+ />
185
+ ) : null}
186
+ </div>
187
+ ) : null}
188
+ </div>
180
189
  </div>
181
- ) : null}
190
+ </div>
182
191
  </div>
183
192
  );
184
193
  }
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import type { ReactNode } from 'react';
4
- import { Library } from 'lucide-react';
4
+ import { Library, FileText, CircleCheck } from 'lucide-react';
5
5
 
6
6
  export function EchoFactSnapshot({
7
7
  headingId,
@@ -9,6 +9,7 @@ export function EchoFactSnapshot({
9
9
  snapshotBadge,
10
10
  emptyTitle,
11
11
  emptyBody,
12
+ icon,
12
13
  actions,
13
14
  }: {
14
15
  headingId: string;
@@ -16,12 +17,12 @@ export function EchoFactSnapshot({
16
17
  snapshotBadge: string;
17
18
  emptyTitle: string;
18
19
  emptyBody: string;
19
- /** e.g. continue-in-Agent CTA — inside this card so it is not orphaned between sections */
20
+ icon?: ReactNode;
20
21
  actions?: ReactNode;
21
22
  }) {
22
23
  return (
23
24
  <section
24
- className="rounded-xl border border-border bg-card p-5 shadow-sm transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--amber)]/20 hover:shadow-md sm:p-6"
25
+ className="rounded-xl border border-border bg-card p-5 shadow-sm transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--amber)]/20 hover:shadow sm:p-6"
25
26
  aria-labelledby={headingId}
26
27
  >
27
28
  <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
@@ -30,7 +31,7 @@ export function EchoFactSnapshot({
30
31
  className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--amber-dim)] text-[var(--amber)]"
31
32
  aria-hidden
32
33
  >
33
- <Library size={18} strokeWidth={1.75} />
34
+ {icon ?? <Library size={18} strokeWidth={1.75} />}
34
35
  </span>
35
36
  <div>
36
37
  <h2
@@ -67,9 +68,12 @@ export function EchoContinuedGroups({
67
68
  subEmptyHint: string;
68
69
  footer?: ReactNode;
69
70
  }) {
70
- const cell = (label: string) => (
71
- <div className="flex min-h-[5.75rem] flex-col justify-center rounded-xl border border-dashed border-border/80 bg-muted/10 px-4 py-4">
72
- <h3 className="font-sans text-sm font-medium text-foreground">{label}</h3>
71
+ const cell = (label: string, icon: ReactNode) => (
72
+ <div className="flex min-h-[5.75rem] flex-col justify-center rounded-xl border border-dashed border-border/80 bg-muted/10 px-4 py-4 transition-colors duration-150 hover:border-[var(--amber)]/25 hover:bg-[var(--amber-dim)]/15">
73
+ <div className="flex items-center gap-2">
74
+ <span className="shrink-0 text-muted-foreground" aria-hidden>{icon}</span>
75
+ <h3 className="font-sans text-sm font-medium text-foreground">{label}</h3>
76
+ </div>
73
77
  <p className="mt-2 font-sans text-2xs leading-relaxed text-muted-foreground">{subEmptyHint}</p>
74
78
  </div>
75
79
  );
@@ -77,8 +81,8 @@ export function EchoContinuedGroups({
77
81
  return (
78
82
  <div className="space-y-4">
79
83
  <div className="grid gap-3 sm:grid-cols-2">
80
- {cell(draftsLabel)}
81
- {cell(todosLabel)}
84
+ {cell(draftsLabel, <FileText size={15} strokeWidth={1.75} />)}
85
+ {cell(todosLabel, <CircleCheck size={15} strokeWidth={1.75} />)}
82
86
  </div>
83
87
  {footer ? <div className="border-t border-border/60 pt-4">{footer}</div> : null}
84
88
  </div>
@@ -1,25 +1,27 @@
1
1
  'use client';
2
2
 
3
+ import type { ReactNode } from 'react';
3
4
  import Link from 'next/link';
5
+ import { UserRound, Bookmark, Sun, History, Brain } from 'lucide-react';
4
6
  import { useLocale } from '@/lib/LocaleContext';
5
7
  import { cn } from '@/lib/utils';
6
8
  import { ECHO_SEGMENT_HREF, ECHO_SEGMENT_ORDER, type EchoSegment } from '@/lib/echo-segments';
7
9
 
8
- function labelForSegment(
10
+ function segmentMeta(
9
11
  segment: EchoSegment,
10
12
  echo: ReturnType<typeof useLocale>['t']['panels']['echo'],
11
- ): string {
13
+ ): { label: string; icon: ReactNode } {
12
14
  switch (segment) {
13
15
  case 'about-you':
14
- return echo.aboutYouTitle;
16
+ return { label: echo.aboutYouTitle, icon: <UserRound size={13} /> };
15
17
  case 'continued':
16
- return echo.continuedTitle;
18
+ return { label: echo.continuedTitle, icon: <Bookmark size={13} /> };
17
19
  case 'daily':
18
- return echo.dailyEchoTitle;
20
+ return { label: echo.dailyEchoTitle, icon: <Sun size={13} /> };
19
21
  case 'past-you':
20
- return echo.pastYouTitle;
22
+ return { label: echo.pastYouTitle, icon: <History size={13} /> };
21
23
  case 'growth':
22
- return echo.intentGrowthTitle;
24
+ return { label: echo.intentGrowthTitle, icon: <Brain size={13} /> };
23
25
  }
24
26
  }
25
27
 
@@ -29,11 +31,11 @@ export default function EchoSegmentNav({ activeSegment }: { activeSegment: EchoS
29
31
  const aria = t.echoPages.segmentNavAria;
30
32
 
31
33
  return (
32
- <nav aria-label={aria} className="mt-6 font-sans">
33
- <ul className="-mx-1 flex snap-x snap-mandatory gap-1.5 overflow-x-auto px-1 pb-1 [scrollbar-width:thin]">
34
+ <nav aria-label={aria} className="mt-5 border-t border-border/30 pt-4 font-sans">
35
+ <ul className="flex snap-x snap-mandatory gap-1.5 overflow-x-auto pb-0.5 [scrollbar-width:thin]">
34
36
  {ECHO_SEGMENT_ORDER.map((segment) => {
35
37
  const href = ECHO_SEGMENT_HREF[segment];
36
- const label = labelForSegment(segment, echo);
38
+ const { label, icon } = segmentMeta(segment, echo);
37
39
  const isActive = segment === activeSegment;
38
40
  return (
39
41
  <li key={segment} className="snap-start shrink-0">
@@ -41,12 +43,13 @@ export default function EchoSegmentNav({ activeSegment }: { activeSegment: EchoS
41
43
  href={href}
42
44
  aria-current={isActive ? 'page' : undefined}
43
45
  className={cn(
44
- 'inline-flex min-h-9 max-w-[11rem] items-center rounded-full border px-3 py-1.5 text-sm transition-[background-color,border-color,color] duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
46
+ 'inline-flex min-h-9 max-w-[11rem] items-center gap-1.5 rounded-full border px-3 py-1.5 text-sm transition-[background-color,border-color,color] duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
45
47
  isActive
46
48
  ? 'border-[var(--amber)]/45 bg-[var(--amber-dim)]/50 font-medium text-foreground'
47
49
  : 'border-transparent bg-muted/35 text-muted-foreground hover:bg-muted/55 hover:text-foreground',
48
50
  )}
49
51
  >
52
+ <span className="shrink-0" aria-hidden>{icon}</span>
50
53
  <span className="truncate">{label}</span>
51
54
  </Link>
52
55
  </li>
@@ -1,6 +1,8 @@
1
1
  'use client';
2
2
 
3
- import { useCallback, useEffect, useId, useMemo, useState } from 'react';
3
+ import type { ReactNode } from 'react';
4
+ import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
5
+ import { ArrowUpRight, Bookmark, Brain, Check, History, Sun, UserRound } from 'lucide-react';
4
6
  import type { EchoSegment } from '@/lib/echo-segments';
5
7
  import { buildEchoInsightUserPrompt } from '@/lib/echo-insight-prompt';
6
8
  import type { Locale, Messages } from '@/lib/i18n';
@@ -44,12 +46,20 @@ function segmentLead(segment: EchoSegment, p: ReturnType<typeof useLocale>['t'][
44
46
  }
45
47
  }
46
48
 
49
+ const SEGMENT_ICON: Record<EchoSegment, ReactNode> = {
50
+ 'about-you': <UserRound size={18} strokeWidth={1.75} />,
51
+ continued: <Bookmark size={18} strokeWidth={1.75} />,
52
+ daily: <Sun size={18} strokeWidth={1.75} />,
53
+ 'past-you': <History size={18} strokeWidth={1.75} />,
54
+ growth: <Brain size={18} strokeWidth={1.75} />,
55
+ };
56
+
47
57
  const fieldLabelClass =
48
58
  'block font-sans text-2xs font-semibold uppercase tracking-wide text-muted-foreground';
49
59
  const inputClass =
50
- 'mt-2 w-full min-h-[5rem] resize-y rounded-lg border border-border bg-background px-3 py-2.5 font-sans text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring';
60
+ 'mt-2 w-full min-h-[5rem] resize-y rounded-lg border border-border bg-background px-3 py-2.5 font-sans text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:border-[var(--amber)]/40';
51
61
  const cardSectionClass =
52
- 'rounded-xl border border-border bg-card p-5 shadow-sm transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--amber)]/20 hover:shadow-md sm:p-6';
62
+ 'rounded-xl border border-border bg-card p-5 shadow-sm transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--amber)]/20 hover:shadow sm:p-6';
53
63
 
54
64
  function echoSnapshotCopy(segment: EchoSegment, p: Messages['echoPages']): { title: string; body: string } {
55
65
  switch (segment) {
@@ -77,6 +87,15 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
77
87
 
78
88
  const [dailyLine, setDailyLine] = useState('');
79
89
  const [growthIntent, setGrowthIntent] = useState('');
90
+ const [dailySaved, setDailySaved] = useState(false);
91
+ const [growthSaved, setGrowthSaved] = useState(false);
92
+ const dailySavedTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
93
+ const growthSavedTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
94
+
95
+ useEffect(() => () => {
96
+ clearTimeout(dailySavedTimer.current);
97
+ clearTimeout(growthSavedTimer.current);
98
+ }, []);
80
99
 
81
100
  const snapshot = useMemo(() => echoSnapshotCopy(segment, p), [segment, p]);
82
101
 
@@ -97,6 +116,9 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
97
116
  } catch {
98
117
  /* ignore */
99
118
  }
119
+ clearTimeout(dailySavedTimer.current);
120
+ setDailySaved(true);
121
+ dailySavedTimer.current = setTimeout(() => setDailySaved(false), 1800);
100
122
  }, [dailyLine]);
101
123
 
102
124
  const persistGrowth = useCallback(() => {
@@ -105,6 +127,9 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
105
127
  } catch {
106
128
  /* ignore */
107
129
  }
130
+ clearTimeout(growthSavedTimer.current);
131
+ setGrowthSaved(true);
132
+ growthSavedTimer.current = setTimeout(() => setGrowthSaved(false), 1800);
108
133
  }, [growthIntent]);
109
134
 
110
135
  const openDailyAsk = useCallback(() => {
@@ -150,7 +175,14 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
150
175
  );
151
176
 
152
177
  const secondaryBtnClass =
153
- 'inline-flex items-center rounded-lg border border-border bg-background px-4 py-2.5 font-sans text-sm font-medium text-foreground transition-colors duration-150 hover:border-[var(--amber)]/35 hover:bg-[var(--amber-dim)]/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring';
178
+ 'inline-flex items-center gap-1.5 rounded-lg border border-border bg-background px-4 py-2.5 font-sans text-sm font-medium text-foreground transition-colors duration-150 hover:border-[var(--amber)]/35 hover:bg-[var(--amber-dim)]/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring';
179
+
180
+ const agentBtn = (onClick: () => void) => (
181
+ <button type="button" onClick={onClick} className={secondaryBtnClass}>
182
+ {p.continueAgent}
183
+ <ArrowUpRight size={14} className="shrink-0 text-muted-foreground" aria-hidden />
184
+ </button>
185
+ );
154
186
 
155
187
  return (
156
188
  <article
@@ -158,16 +190,13 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
158
190
  aria-labelledby={pageTitleId}
159
191
  >
160
192
  <EchoHero
161
- breadcrumbNav={p.breadcrumbNav}
162
- parentHref="/echo/about-you"
163
- parent={p.parent}
164
193
  heroKicker={p.heroKicker}
165
194
  pageTitle={title}
166
195
  lead={lead}
167
196
  titleId={pageTitleId}
168
- />
169
-
170
- <EchoSegmentNav activeSegment={segment} />
197
+ >
198
+ <EchoSegmentNav activeSegment={segment} />
199
+ </EchoHero>
171
200
 
172
201
  <div className="mt-6 space-y-6 sm:mt-8">
173
202
  <EchoFactSnapshot
@@ -176,24 +205,15 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
176
205
  snapshotBadge={p.snapshotBadge}
177
206
  emptyTitle={snapshot.title}
178
207
  emptyBody={snapshot.body}
179
- actions={
180
- segment === 'about-you' ? (
181
- <button type="button" onClick={openSegmentAsk} className={secondaryBtnClass}>
182
- {p.continueAgent}
183
- </button>
184
- ) : undefined
185
- }
208
+ icon={SEGMENT_ICON[segment]}
209
+ actions={segment === 'about-you' ? agentBtn(openSegmentAsk) : undefined}
186
210
  />
187
211
  {segment === 'continued' ? (
188
212
  <EchoContinuedGroups
189
213
  draftsLabel={p.continuedDrafts}
190
214
  todosLabel={p.continuedTodos}
191
215
  subEmptyHint={p.subEmptyHint}
192
- footer={
193
- <button type="button" onClick={openSegmentAsk} className={secondaryBtnClass}>
194
- {p.continueAgent}
195
- </button>
196
- }
216
+ footer={agentBtn(openSegmentAsk)}
197
217
  />
198
218
  ) : null}
199
219
  </div>
@@ -212,11 +232,14 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
212
232
  placeholder={p.dailyLinePlaceholder}
213
233
  className={inputClass}
214
234
  />
215
- <p className="mt-3 font-sans text-2xs text-muted-foreground">{p.dailySavedNote}</p>
235
+ <p className="mt-3 flex items-center gap-2 font-sans text-2xs text-muted-foreground">
236
+ <span>{p.dailySavedNote}</span>
237
+ <span className="inline-flex items-center gap-1 text-[var(--success)]" aria-live="polite">
238
+ {dailySaved ? <><Check size={12} aria-hidden /> {p.savedFlash}</> : null}
239
+ </span>
240
+ </p>
216
241
  <div className="mt-4">
217
- <button type="button" onClick={openDailyAsk} className={secondaryBtnClass}>
218
- {p.continueAgent}
219
- </button>
242
+ {agentBtn(openDailyAsk)}
220
243
  </div>
221
244
  </section>
222
245
  ) : null}
@@ -235,31 +258,29 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
235
258
  placeholder={p.growthIntentPlaceholder}
236
259
  className={`${inputClass} min-h-[6.5rem]`}
237
260
  />
238
- <p className="mt-3 font-sans text-2xs text-muted-foreground">{p.growthSavedNote}</p>
261
+ <p className="mt-3 flex items-center gap-2 font-sans text-2xs text-muted-foreground">
262
+ <span>{p.growthSavedNote}</span>
263
+ <span className="inline-flex items-center gap-1 text-[var(--success)]" aria-live="polite">
264
+ {growthSaved ? <><Check size={12} aria-hidden /> {p.savedFlash}</> : null}
265
+ </span>
266
+ </p>
239
267
  <div className="mt-4 border-t border-border/60 pt-4">
240
- <button type="button" onClick={openSegmentAsk} className={secondaryBtnClass}>
241
- {p.continueAgent}
242
- </button>
268
+ {agentBtn(openSegmentAsk)}
243
269
  </div>
244
270
  </section>
245
271
  ) : null}
246
272
 
247
273
  {segment === 'past-you' ? (
248
274
  <section className={`${cardSectionClass} mt-6`}>
249
- <label className={fieldLabelClass}>{p.pastYouDrawLabel}</label>
250
- <button
251
- type="button"
252
- disabled
253
- title={p.pastYouDisabledHint}
254
- className="mt-2 inline-flex cursor-not-allowed items-center rounded-lg border border-dashed border-border bg-muted/20 px-4 py-2.5 font-sans text-sm text-muted-foreground opacity-85"
255
- >
256
- {p.pastYouAnother}
257
- </button>
258
- <p className="mt-3 font-sans text-2xs text-muted-foreground">{p.pastYouDisabledHint}</p>
275
+ <div className="flex items-center gap-3">
276
+ <span className={fieldLabelClass}>{p.pastYouDrawLabel}</span>
277
+ <span className="rounded-full bg-muted px-2 py-0.5 font-sans text-2xs font-medium text-muted-foreground">
278
+ {p.pastYouComingSoon}
279
+ </span>
280
+ </div>
281
+ <p className="mt-3 font-sans text-sm leading-relaxed text-muted-foreground">{p.pastYouDisabledHint}</p>
259
282
  <div className="mt-4 border-t border-border/60 pt-4">
260
- <button type="button" onClick={openSegmentAsk} className={secondaryBtnClass}>
261
- {p.continueAgent}
262
- </button>
283
+ {agentBtn(openSegmentAsk)}
263
284
  </div>
264
285
  </section>
265
286
  ) : null}
@@ -31,17 +31,13 @@ export default function ExploreContent() {
31
31
  {/* Header */}
32
32
  <div className="mb-8">
33
33
  <div className="flex items-center gap-2 mb-3">
34
- <div className="w-1 h-5 rounded-full" style={{ background: 'var(--amber)' }} />
35
- <h1
36
- className="text-2xl font-semibold tracking-tight font-display"
37
- style={{ color: 'var(--foreground)' }}
38
- >
34
+ <div className="w-1 h-5 rounded-full bg-[var(--amber)]" />
35
+ <h1 className="text-2xl font-semibold tracking-tight font-display text-foreground">
39
36
  {e.title}
40
37
  </h1>
41
38
  </div>
42
39
  <p
43
- className="text-sm leading-relaxed"
44
- style={{ color: 'var(--muted-foreground)', paddingLeft: '1rem' }}
40
+ className="text-sm leading-relaxed text-muted-foreground pl-4"
45
41
  >
46
42
  {e.subtitle}
47
43
  </p>
@@ -13,35 +13,24 @@ interface UseCaseCardProps {
13
13
  export default function UseCaseCard({ icon, title, description, prompt, tryItLabel }: UseCaseCardProps) {
14
14
  return (
15
15
  <div
16
- className="group flex flex-col gap-3 p-4 rounded-xl border transition-all duration-150 hover:border-amber-500/30 hover:bg-muted/50"
17
- style={{ borderColor: 'var(--border)', background: 'var(--card)' }}
16
+ className="group flex flex-col gap-3 p-4 rounded-xl border border-border bg-card transition-all duration-150 hover:border-[var(--amber)]/30 hover:bg-muted/50"
18
17
  >
19
18
  <div className="flex items-start gap-3">
20
19
  <span className="text-xl leading-none shrink-0 mt-0.5" suppressHydrationWarning>
21
20
  {icon}
22
21
  </span>
23
22
  <div className="flex-1 min-w-0">
24
- <h3
25
- className="text-sm font-semibold font-display truncate"
26
- style={{ color: 'var(--foreground)' }}
27
- >
23
+ <h3 className="text-sm font-semibold font-display truncate text-foreground">
28
24
  {title}
29
25
  </h3>
30
- <p
31
- className="text-xs leading-relaxed mt-1 line-clamp-2"
32
- style={{ color: 'var(--muted-foreground)' }}
33
- >
26
+ <p className="text-xs leading-relaxed mt-1 line-clamp-2 text-muted-foreground">
34
27
  {description}
35
28
  </p>
36
29
  </div>
37
30
  </div>
38
31
  <button
39
32
  onClick={() => openAskModal(prompt, 'user')}
40
- className="self-start inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-all duration-150 hover:opacity-80 cursor-pointer"
41
- style={{
42
- background: 'var(--amber-dim)',
43
- color: 'var(--amber)',
44
- }}
33
+ className="self-start inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-all duration-150 hover:opacity-80 cursor-pointer bg-[var(--amber-dim)] text-[var(--amber)]"
45
34
  >
46
35
  {tryItLabel} →
47
36
  </button>