ltcai 4.3.3 → 4.5.1

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 (138) hide show
  1. package/README.md +53 -20
  2. package/docs/CHANGELOG.md +122 -0
  3. package/docs/V4_4_0_EXTRACTION_REPORT.md +239 -0
  4. package/docs/V4_5_0_GEMMA_RUNTIME_COMPATIBILITY_REPORT.md +49 -0
  5. package/docs/V4_5_0_GRAPH_UX_REPORT.md +34 -0
  6. package/docs/V4_5_0_MODEL_RUNTIME_UX_REPORT.md +40 -0
  7. package/docs/V4_5_0_ONBOARDING_REPORT.md +31 -0
  8. package/docs/V4_5_0_PRODUCT_EXPERIENCE_RECOVERY_REPORT.md +49 -0
  9. package/docs/V4_5_0_VALIDATION_REPORT.md +60 -0
  10. package/docs/V4_5_1_GRAPH_EXPERIENCE_REPORT.md +33 -0
  11. package/docs/V4_5_1_MODEL_EXPERIENCE_REPORT.md +37 -0
  12. package/docs/V4_5_1_NAVIGATION_REPORT.md +37 -0
  13. package/docs/V4_5_1_ONBOARDING_REPORT.md +29 -0
  14. package/docs/V4_5_1_PRODUCT_REIMAGINING_REPORT.md +61 -0
  15. package/docs/V4_5_1_RC_ARTIFACTS.md +44 -0
  16. package/docs/V4_5_1_UX_REPORT.md +45 -0
  17. package/docs/V4_5_1_VALIDATION_REPORT.md +54 -0
  18. package/docs/V4_5_1_VISUAL_DESIGN_REPORT.md +30 -0
  19. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +16 -16
  20. package/docs/architecture.md +8 -4
  21. package/frontend/src/App.tsx +152 -91
  22. package/frontend/src/api/client.ts +83 -1
  23. package/frontend/src/components/FirstRunGuide.tsx +99 -0
  24. package/frontend/src/components/primitives.tsx +131 -25
  25. package/frontend/src/components/ui/badge.tsx +2 -2
  26. package/frontend/src/components/ui/button.tsx +7 -7
  27. package/frontend/src/components/ui/card.tsx +5 -5
  28. package/frontend/src/components/ui/input.tsx +1 -1
  29. package/frontend/src/components/ui/textarea.tsx +1 -1
  30. package/frontend/src/pages/Act.tsx +58 -28
  31. package/frontend/src/pages/Ask.tsx +51 -19
  32. package/frontend/src/pages/Brain.tsx +60 -42
  33. package/frontend/src/pages/Capture.tsx +24 -24
  34. package/frontend/src/pages/Library.tsx +222 -32
  35. package/frontend/src/pages/System.tsx +56 -34
  36. package/frontend/src/routes.ts +15 -13
  37. package/frontend/src/store/appStore.ts +8 -1
  38. package/frontend/src/styles.css +666 -36
  39. package/lattice_brain/__init__.py +38 -23
  40. package/lattice_brain/_kg_common.py +11 -1
  41. package/lattice_brain/context.py +212 -2
  42. package/lattice_brain/conversations.py +234 -1
  43. package/lattice_brain/discovery.py +11 -1
  44. package/lattice_brain/documents.py +11 -1
  45. package/lattice_brain/graph/__init__.py +28 -0
  46. package/lattice_brain/graph/_kg_common.py +1123 -0
  47. package/lattice_brain/graph/curator.py +473 -0
  48. package/lattice_brain/graph/discovery.py +1455 -0
  49. package/lattice_brain/graph/documents.py +218 -0
  50. package/lattice_brain/graph/identity.py +175 -0
  51. package/lattice_brain/graph/ingest.py +644 -0
  52. package/lattice_brain/graph/network.py +205 -0
  53. package/lattice_brain/graph/projection.py +571 -0
  54. package/lattice_brain/graph/provenance.py +401 -0
  55. package/lattice_brain/graph/retrieval.py +1341 -0
  56. package/lattice_brain/graph/schema.py +640 -0
  57. package/lattice_brain/graph/store.py +237 -0
  58. package/lattice_brain/graph/write_master.py +225 -0
  59. package/lattice_brain/identity.py +11 -13
  60. package/lattice_brain/ingest.py +11 -1
  61. package/lattice_brain/ingestion.py +318 -0
  62. package/lattice_brain/memory.py +100 -1
  63. package/lattice_brain/network.py +11 -1
  64. package/lattice_brain/portability.py +431 -0
  65. package/lattice_brain/projection.py +11 -1
  66. package/lattice_brain/provenance.py +11 -1
  67. package/lattice_brain/retrieval.py +11 -1
  68. package/lattice_brain/runtime/__init__.py +32 -0
  69. package/lattice_brain/runtime/agent_runtime.py +569 -0
  70. package/lattice_brain/runtime/hooks.py +754 -0
  71. package/lattice_brain/runtime/multi_agent.py +795 -0
  72. package/lattice_brain/schema.py +11 -1
  73. package/lattice_brain/store.py +10 -2
  74. package/lattice_brain/workflow.py +461 -0
  75. package/lattice_brain/write_master.py +11 -1
  76. package/latticeai/__init__.py +1 -1
  77. package/latticeai/api/agents.py +2 -2
  78. package/latticeai/api/browser.py +1 -1
  79. package/latticeai/api/chat.py +1 -1
  80. package/latticeai/api/computer_use.py +1 -1
  81. package/latticeai/api/hooks.py +2 -2
  82. package/latticeai/api/mcp.py +1 -1
  83. package/latticeai/api/models.py +107 -18
  84. package/latticeai/api/tools.py +1 -1
  85. package/latticeai/api/workflow_designer.py +2 -2
  86. package/latticeai/app_factory.py +4 -4
  87. package/latticeai/brain/__init__.py +24 -6
  88. package/latticeai/brain/_kg_common.py +11 -1117
  89. package/latticeai/brain/context.py +12 -208
  90. package/latticeai/brain/conversations.py +12 -231
  91. package/latticeai/brain/discovery.py +13 -1451
  92. package/latticeai/brain/documents.py +13 -214
  93. package/latticeai/brain/identity.py +11 -169
  94. package/latticeai/brain/ingest.py +13 -640
  95. package/latticeai/brain/memory.py +12 -97
  96. package/latticeai/brain/network.py +12 -200
  97. package/latticeai/brain/projection.py +13 -567
  98. package/latticeai/brain/provenance.py +13 -397
  99. package/latticeai/brain/retrieval.py +13 -1337
  100. package/latticeai/brain/schema.py +12 -635
  101. package/latticeai/brain/store.py +13 -233
  102. package/latticeai/brain/write_master.py +13 -221
  103. package/latticeai/core/agent.py +1 -1
  104. package/latticeai/core/agent_registry.py +2 -2
  105. package/latticeai/core/builtin_hooks.py +2 -2
  106. package/latticeai/core/graph_curator.py +6 -468
  107. package/latticeai/core/hooks.py +6 -749
  108. package/latticeai/core/marketplace.py +1 -1
  109. package/latticeai/core/model_compat.py +250 -0
  110. package/latticeai/core/multi_agent.py +6 -790
  111. package/latticeai/core/workflow_engine.py +6 -456
  112. package/latticeai/core/workspace_os.py +1 -1
  113. package/latticeai/models/router.py +136 -32
  114. package/latticeai/services/agent_runtime.py +6 -564
  115. package/latticeai/services/ingestion.py +6 -313
  116. package/latticeai/services/kg_portability.py +6 -426
  117. package/latticeai/services/model_catalog.py +2 -2
  118. package/latticeai/services/model_recommendation.py +8 -1
  119. package/latticeai/services/model_runtime.py +18 -3
  120. package/latticeai/services/platform_runtime.py +3 -3
  121. package/latticeai/services/run_executor.py +1 -1
  122. package/latticeai/services/upload_service.py +1 -1
  123. package/p_reinforce.py +1 -1
  124. package/package.json +1 -1
  125. package/scripts/build_frontend_assets.mjs +12 -1
  126. package/scripts/bump_version.py +1 -1
  127. package/scripts/wheel_smoke.py +7 -0
  128. package/src-tauri/Cargo.lock +1 -1
  129. package/src-tauri/Cargo.toml +1 -1
  130. package/src-tauri/tauri.conf.json +1 -1
  131. package/static/app/asset-manifest.json +5 -5
  132. package/static/app/assets/index-3G8qcrIS.js +336 -0
  133. package/static/app/assets/index-3G8qcrIS.js.map +1 -0
  134. package/static/app/assets/index-C0wYZp7k.css +2 -0
  135. package/static/app/index.html +2 -2
  136. package/static/app/assets/index-CHHal8Zl.css +0 -2
  137. package/static/app/assets/index-pdzil9ac.js +0 -333
  138. package/static/app/assets/index-pdzil9ac.js.map +0 -1
@@ -1,12 +1,13 @@
1
1
  import * as React from "react";
2
2
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3
- import { ImagePlus, MessageSquare, Send, Trash2 } from "lucide-react";
3
+ import { ImagePlus, MessageSquare, Send, Sparkles, Trash2 } from "lucide-react";
4
4
  import { latticeApi } from "@/api/client";
5
5
  import { DataPanel, EmptyState, EntityList, SourceBadge, StructuredView } from "@/components/primitives";
6
6
  import { Badge } from "@/components/ui/badge";
7
7
  import { Button } from "@/components/ui/button";
8
8
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
9
9
  import { Textarea } from "@/components/ui/textarea";
10
+ import { useAppStore } from "@/store/appStore";
10
11
  import { asArray } from "@/lib/utils";
11
12
 
12
13
  type Msg = { role?: string; content?: string; timestamp?: string };
@@ -81,19 +82,25 @@ export function AskPage() {
81
82
  });
82
83
 
83
84
  return (
84
- <div className="grid min-h-[calc(100vh-7rem)] gap-4 xl:grid-cols-[18rem_minmax(0,1fr)_22rem]">
85
+ <div className="space-y-5">
86
+ <header className="page-hero">
87
+ <div className="page-kicker"><MessageSquare className="h-4 w-4" /> Ask</div>
88
+ <h1 className="page-title">Think out loud with Lattice.</h1>
89
+ <p className="page-copy">Ask in plain language, attach an image, and let your private memory shape the answer.</p>
90
+ </header>
91
+ <div className="grid min-h-[calc(100vh-13rem)] gap-4 xl:grid-cols-[18rem_minmax(0,1fr)_22rem]">
85
92
  <Card className="overflow-hidden">
86
93
  <CardHeader>
87
94
  <CardTitle className="flex items-center gap-2"><MessageSquare className="h-4 w-4" /> Conversations</CardTitle>
88
- <CardDescription>Durable history from the backend conversation store.</CardDescription>
95
+ <CardDescription>Pick up where you left off.</CardDescription>
89
96
  </CardHeader>
90
- <CardContent className="space-y-2">
97
+ <CardContent className="soft-scrollbar max-h-[42rem] space-y-2 overflow-auto">
91
98
  <SourceBadge result={history.data} />
92
99
  {asArray<Record<string, unknown>>(history.data?.data).length ? asArray<Record<string, unknown>>(history.data?.data).map((item) => (
93
100
  <button
94
101
  key={String(item.id)}
95
102
  onClick={() => setConversationId(String(item.id))}
96
- className="block w-full rounded-md border border-border bg-background p-3 text-left text-sm transition hover:bg-muted"
103
+ className="block w-full rounded-lg border border-border bg-background/55 p-3 text-left text-sm transition hover:bg-muted"
97
104
  >
98
105
  <div className="font-medium">{String(item.title || item.id)}</div>
99
106
  <div className="mt-1 flex items-center justify-between gap-2 text-xs text-muted-foreground">
@@ -111,28 +118,35 @@ export function AskPage() {
111
118
  </CardContent>
112
119
  </Card>
113
120
 
114
- <section className="flex min-h-[40rem] flex-col rounded-lg border border-border bg-card">
115
- <div className="flex flex-wrap items-center justify-between gap-3 border-b border-border p-4">
121
+ <section className="premium-surface flex min-h-[42rem] flex-col overflow-hidden rounded-lg">
122
+ <div className="flex flex-wrap items-center justify-between gap-3 border-b border-border p-5">
116
123
  <div>
117
- <h1 className="text-xl font-semibold">Ask</h1>
118
- <p className="text-sm text-muted-foreground">Streams through `/chat`; no local answer is fabricated when the model is unavailable.</p>
124
+ <h2 className="text-xl font-semibold">New conversation</h2>
125
+ <p className="text-sm text-muted-foreground">Lattice answers only when a real model is available.</p>
119
126
  </div>
120
127
  <div className="flex flex-wrap items-center gap-2">
121
128
  <Badge variant="muted">{String((models.data?.data as Record<string, unknown>)?.current || "no model loaded")}</Badge>
122
129
  <SourceBadge result={models.data} />
123
130
  </div>
124
131
  </div>
125
- <div className="scrollbar-thin flex-1 space-y-3 overflow-auto p-4">
132
+ <div className="soft-scrollbar flex-1 space-y-4 overflow-auto p-5">
126
133
  {messages.length ? messages.map((msg, index) => (
127
134
  <div key={index} className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}>
128
- <div className={`max-w-[78%] rounded-lg border p-3 text-sm ${msg.role === "user" ? "border-primary/30 bg-primary/15" : "border-border bg-background"}`}>
135
+ <div className={`max-w-[78%] rounded-lg border p-4 text-sm leading-6 ${msg.role === "user" ? "border-primary/30 bg-primary/15" : "border-border bg-background/68"}`}>
129
136
  <div className="mb-1 text-xs uppercase text-muted-foreground">{msg.role || "message"}</div>
130
137
  <div className="whitespace-pre-wrap">{msg.content}</div>
131
138
  </div>
132
139
  </div>
133
- )) : <EmptyState title="Ready" detail="Ask a question once the backend and model are available." />}
140
+ )) : (
141
+ <div className="grid min-h-full place-items-center">
142
+ <EmptyState
143
+ title="What should we think through?"
144
+ detail="Ask about a document, a project, a memory, or a question you want Lattice to connect across your workspace."
145
+ />
146
+ </div>
147
+ )}
134
148
  </div>
135
- <div className="border-t border-border p-4">
149
+ <div className="border-t border-border bg-background/28 p-5">
136
150
  {imageData ? <Badge variant="success" className="mb-2">image attached</Badge> : null}
137
151
  <Textarea
138
152
  value={draft}
@@ -143,12 +157,12 @@ export function AskPage() {
143
157
  void send();
144
158
  }
145
159
  }}
146
- placeholder="Ask the brain..."
160
+ placeholder="Ask anything about your work..."
147
161
  />
148
162
  <div className="mt-2 flex flex-wrap items-center justify-between gap-2">
149
163
  <label className="inline-flex h-9 cursor-pointer items-center gap-2 rounded-md border border-border px-3 text-sm hover:bg-muted">
150
164
  <ImagePlus className="h-4 w-4" />
151
- Attach image
165
+ Image
152
166
  <input
153
167
  type="file"
154
168
  accept="image/*"
@@ -169,20 +183,38 @@ export function AskPage() {
169
183
  <aside className="space-y-4">
170
184
  <ContextPreview question={draft || [...messages].reverse().find((m: Msg) => m.role === "user")?.content || ""} trace={trace} />
171
185
  </aside>
186
+ </div>
172
187
  </div>
173
188
  );
174
189
  }
175
190
 
176
191
  function ContextPreview({ question, trace }: { question: string; trace: unknown }) {
192
+ const mode = useAppStore((state) => state.mode);
177
193
  const hybrid = useQuery({
178
194
  queryKey: ["askHybrid", question],
179
195
  queryFn: () => latticeApi.hybridSearch(question),
180
196
  enabled: question.trim().length > 2,
181
197
  });
182
198
  const graph = useQuery({ queryKey: ["graph"], queryFn: latticeApi.graph });
199
+ if (mode === "basic") {
200
+ return (
201
+ <>
202
+ <DataPanel title="Relevant memories" result={hybrid.data}>
203
+ {(data) => <EntityList items={(data as Record<string, unknown>).matches || data} titleKey="title" metaKey="type" limit={5} />}
204
+ </DataPanel>
205
+ <Card>
206
+ <CardHeader>
207
+ <CardTitle className="flex items-center gap-2"><Sparkles className="h-4 w-4" /> Sources</CardTitle>
208
+ <CardDescription>Lattice shows supporting memories when an answer uses them.</CardDescription>
209
+ </CardHeader>
210
+ <CardContent>{trace ? <StructuredView value={trace} /> : <EmptyState title="Ask to see sources" />}</CardContent>
211
+ </Card>
212
+ </>
213
+ );
214
+ }
183
215
  return (
184
216
  <>
185
- <DataPanel title="Retrieval preview" result={hybrid.data}>
217
+ <DataPanel title="Memory preview" result={hybrid.data}>
186
218
  {(data) => <EntityList items={(data as Record<string, unknown>).matches || data} titleKey="title" metaKey="type" limit={5} />}
187
219
  </DataPanel>
188
220
  <DataPanel title="Graph context" result={graph.data}>
@@ -190,10 +222,10 @@ function ContextPreview({ question, trace }: { question: string; trace: unknown
190
222
  </DataPanel>
191
223
  <Card>
192
224
  <CardHeader>
193
- <CardTitle>Why this context</CardTitle>
194
- <CardDescription>Trace emitted by `/chat` when the backend includes it.</CardDescription>
225
+ <CardTitle className="flex items-center gap-2"><Sparkles className="h-4 w-4" /> Why this context</CardTitle>
226
+ <CardDescription>Signals Lattice used to choose supporting memories.</CardDescription>
195
227
  </CardHeader>
196
- <CardContent>{trace ? <StructuredView value={trace} /> : <EmptyState title="No trace yet" />}</CardContent>
228
+ <CardContent>{trace ? <StructuredView value={trace} /> : <EmptyState title="Ask to see context" />}</CardContent>
197
229
  </Card>
198
230
  </>
199
231
  );
@@ -3,12 +3,13 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3
3
  import cytoscape, { Core, ElementDefinition } from "cytoscape";
4
4
  import { BrainCircuit, DatabaseBackup, Filter, Focus, Layers3, LocateFixed, Search, Sparkles } from "lucide-react";
5
5
  import { latticeApi } from "@/api/client";
6
- import { ActionButton, DataPanel, EmptyState, EntityList, LoadingPanel, OperationResult, StatGrid, StructuredView, Tabs } from "@/components/primitives";
6
+ import { ActionButton, DataPanel, EmptyState, EntityList, KeyValueList, LoadingPanel, OperationResult, StatGrid, StructuredView, Tabs } from "@/components/primitives";
7
7
  import { Badge } from "@/components/ui/badge";
8
8
  import { Button } from "@/components/ui/button";
9
9
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
10
10
  import { Input } from "@/components/ui/input";
11
11
  import { Textarea } from "@/components/ui/textarea";
12
+ import { useAppStore } from "@/store/appStore";
12
13
  import { asArray, fmtNumber, pct, shortId, titleize } from "@/lib/utils";
13
14
 
14
15
  type BrainTab = "overview" | "graph" | "search" | "memory" | "provenance" | "portability";
@@ -60,11 +61,11 @@ type ExplorerModel = ParsedGraph & {
60
61
  };
61
62
 
62
63
  const tabs: Array<{ id: BrainTab; label: string }> = [
63
- { id: "overview", label: "Overview" },
64
- { id: "graph", label: "Graph" },
64
+ { id: "overview", label: "Today" },
65
+ { id: "graph", label: "Map" },
65
66
  { id: "search", label: "Search" },
66
67
  { id: "memory", label: "Memory" },
67
- { id: "provenance", label: "Provenance" },
68
+ { id: "provenance", label: "Sources" },
68
69
  { id: "portability", label: "Portability" },
69
70
  ];
70
71
 
@@ -405,13 +406,14 @@ function CytoscapeGraph({
405
406
  <div
406
407
  ref={hostRef}
407
408
  data-testid="brain-cytoscape"
408
- className="h-[620px] min-h-[32rem] w-full overflow-hidden rounded-md border border-border bg-background brain-grid"
409
+ className="brain-grid h-[620px] min-h-[32rem] w-full overflow-hidden rounded-lg border border-border bg-background/80"
409
410
  />
410
411
  );
411
412
  }
412
413
 
413
414
  export function BrainPage({ initialTab }: { initialTab?: string }) {
414
- const [tab, setTab] = React.useState<BrainTab>((initialTab as BrainTab) || "graph");
415
+ const mode = useAppStore((state) => state.mode);
416
+ const [tab, setTab] = React.useState<BrainTab>((initialTab as BrainTab) || "overview");
415
417
  React.useEffect(() => {
416
418
  if (initialTab && tabs.some((item) => item.id === initialTab)) setTab(initialTab as BrainTab);
417
419
  }, [initialTab]);
@@ -423,19 +425,19 @@ export function BrainPage({ initialTab }: { initialTab?: string }) {
423
425
  const memory = useQuery({ queryKey: ["memoryManager"], queryFn: latticeApi.memoryManager });
424
426
 
425
427
  return (
426
- <div className="space-y-4">
427
- <header className="grid gap-4 xl:grid-cols-[1.4fr_0.6fr]">
428
+ <div className="space-y-5">
429
+ <header className="page-hero grid gap-4 xl:grid-cols-[1.4fr_0.6fr]">
428
430
  <div>
429
- <div className="flex items-center gap-2 text-sm text-primary"><BrainCircuit className="h-4 w-4" /> Graph-first Digital Brain</div>
430
- <h1 className="mt-2 text-3xl font-semibold tracking-normal">Brain</h1>
431
- <p className="mt-2 max-w-3xl text-sm text-muted-foreground">
432
- Explore the knowledge graph, memory, provenance, retrieval, and portable brain state. Empty states reflect live API availability.
431
+ <div className="page-kicker"><BrainCircuit className="h-4 w-4" /> Home</div>
432
+ <h1 className="page-title">Start from the shape of your work.</h1>
433
+ <p className="page-copy">
434
+ Lattice turns what you add into a living memory map, then keeps every answer tied back to its source.
433
435
  </p>
434
436
  </div>
435
- <div className="rounded-md border border-border bg-card p-4">
436
- <div className="text-xs uppercase text-muted-foreground">Provenance coverage</div>
437
+ <div className="rounded-lg border border-border bg-background/58 p-4">
438
+ <div className="text-xs uppercase text-muted-foreground">Source coverage</div>
437
439
  <div className="mt-2 text-3xl font-semibold">{pct((coverage.data?.data as Record<string, unknown>)?.coverage_ratio)}</div>
438
- <div className="mt-2 text-sm text-muted-foreground">Source: {coverage.data?.source || "loading"}</div>
440
+ <div className="mt-2 text-sm text-muted-foreground">{coverage.data?.ok ? "Sources are linked to graph records." : "Checking source coverage."}</div>
439
441
  </div>
440
442
  </header>
441
443
  <Tabs tabs={tabs} value={tab} onChange={(id) => setTab(id as BrainTab)} />
@@ -451,7 +453,7 @@ export function BrainPage({ initialTab }: { initialTab?: string }) {
451
453
  <DataPanel title="Memory tiers" result={memory.data}>
452
454
  {(data) => <MemoryStatus data={data as Record<string, unknown>} />}
453
455
  </DataPanel>
454
- <DataPanel title="Recent provenance" result={provenance.data}>
456
+ <DataPanel title="Recent sources" result={provenance.data}>
455
457
  {(data) => <EntityList items={(data as Record<string, unknown>).items || data} titleKey="source" metaKey="source_type" />}
456
458
  </DataPanel>
457
459
  </div>
@@ -459,7 +461,7 @@ export function BrainPage({ initialTab }: { initialTab?: string }) {
459
461
 
460
462
  {tab === "graph" ? (
461
463
  graph.isLoading ? <LoadingPanel title="Knowledge graph" /> : (
462
- <DataPanel title="Digital Brain explorer" description="Interactive Cytoscape.js explorer backed by /knowledge-graph/graph." result={graph.data}>
464
+ <DataPanel title="Brain map" description={mode === "basic" ? "Search, focus, and filter the ideas Lattice has learned from your workspace." : "Explore relationships, sources, and graph structure."} result={graph.data}>
463
465
  {(data) => <DigitalBrainExplorer data={data} />}
464
466
  </DataPanel>
465
467
  )
@@ -474,6 +476,7 @@ export function BrainPage({ initialTab }: { initialTab?: string }) {
474
476
  }
475
477
 
476
478
  function GraphStatus({ data }: { data: Record<string, unknown> }) {
479
+ const mode = useAppStore((state) => state.mode);
477
480
  const nodeTypes = Object.keys((data.nodes as Record<string, unknown>) || {});
478
481
  const edgeTypes = Object.keys((data.edges as Record<string, unknown>) || {});
479
482
  return (
@@ -484,7 +487,11 @@ function GraphStatus({ data }: { data: Record<string, unknown> }) {
484
487
  { label: "Node types", value: nodeTypes.length },
485
488
  { label: "Edge types", value: edgeTypes.length },
486
489
  ]} />
487
- <StructuredView value={{ node_types: nodeTypes, edge_types: edgeTypes }} />
490
+ {mode === "basic" ? (
491
+ <div className="flex flex-wrap gap-1">
492
+ {[...nodeTypes, ...edgeTypes].slice(0, 10).map((item) => <Badge key={item} variant="muted">{titleize(item)}</Badge>)}
493
+ </div>
494
+ ) : <StructuredView value={{ node_types: nodeTypes, edge_types: edgeTypes }} />}
488
495
  </div>
489
496
  );
490
497
  }
@@ -515,10 +522,11 @@ function MemoryStatus({ data }: { data: Record<string, unknown> }) {
515
522
  }
516
523
 
517
524
  function DigitalBrainExplorer({ data }: { data: unknown }) {
525
+ const mode = useAppStore((state) => state.mode);
518
526
  const parsed = React.useMemo(() => parseGraph(data), [data]);
519
527
  const [search, setSearch] = React.useState("");
520
528
  const [groupFilter, setGroupFilter] = React.useState("all");
521
- const [minImportance, setMinImportance] = React.useState(0);
529
+ const [minImportance, setMinImportance] = React.useState(mode === "basic" ? 0.1 : 0);
522
530
  const [labelMode, setLabelMode] = React.useState<LabelMode>("important");
523
531
  const [collapsedGroups, setCollapsedGroups] = React.useState<Set<string>>(new Set());
524
532
  const [selectedId, setSelectedId] = React.useState<string | null>(null);
@@ -544,11 +552,14 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
544
552
  return next;
545
553
  });
546
554
  };
555
+ React.useEffect(() => {
556
+ if (mode === "basic" && minImportance < 0.1) setMinImportance(0.1);
557
+ }, [mode, minImportance]);
547
558
  if (!parsed.nodes.length) {
548
559
  return (
549
560
  <EmptyState
550
561
  title="No graph records yet"
551
- detail="Capture a document, note, or local folder to create graph nodes with provenance."
562
+ detail="Capture a document, note, or local folder to create connected ideas with sources."
552
563
  />
553
564
  );
554
565
  }
@@ -557,7 +568,7 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
557
568
  <div className="grid gap-3 xl:grid-cols-[1fr_220px_180px_170px]">
558
569
  <div className="relative">
559
570
  <Search className="pointer-events-none absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
560
- <Input className="pl-9" value={search} onChange={(event) => setSearch(event.target.value)} placeholder="Search graph labels, types, provenance..." />
571
+ <Input className="pl-9" value={search} onChange={(event) => setSearch(event.target.value)} placeholder={mode === "basic" ? "Search ideas, files, people, and notes..." : "Search graph labels, types, provenance..."} />
561
572
  </div>
562
573
  <select className="h-9 rounded-md border border-border bg-background px-3 text-sm" value={groupFilter} onChange={(event) => setGroupFilter(event.target.value)}>
563
574
  <option value="all">All semantic groups</option>
@@ -574,9 +585,9 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
574
585
  <Card>
575
586
  <CardHeader className="flex-row items-start justify-between gap-3">
576
587
  <div>
577
- <CardTitle className="flex items-center gap-2"><Layers3 className="h-4 w-4" /> Semantic map</CardTitle>
588
+ <CardTitle className="flex items-center gap-2"><Layers3 className="h-4 w-4" /> Knowledge map</CardTitle>
578
589
  <CardDescription>
579
- Showing {fmtNumber(model.visibleNodes.length)} nodes and {fmtNumber(model.visibleEdges.length)} relationships from {fmtNumber(model.totalNodes)} graph nodes.
590
+ Showing {fmtNumber(model.visibleNodes.length)} ideas and {fmtNumber(model.visibleEdges.length)} connections from {fmtNumber(model.totalNodes)} saved items.
580
591
  </CardDescription>
581
592
  </div>
582
593
  <Badge variant={model.hiddenByFilters ? "warning" : "success"}>{model.hiddenByFilters ? `${fmtNumber(model.hiddenByFilters)} filtered` : "all in view"}</Badge>
@@ -598,7 +609,7 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
598
609
  />
599
610
  <Badge variant="muted">{Math.round(minImportance * 100)}%+</Badge>
600
611
  {selectedId ? <Button variant="outline" size="sm" onClick={() => setSelectedId(null)}>Clear focus</Button> : null}
601
- {search.trim() ? <Button variant="outline" size="sm" onClick={() => backendSearch.mutate()} disabled={backendSearch.isPending}>Search brain</Button> : null}
612
+ {search.trim() ? <Button variant="outline" size="sm" onClick={() => backendSearch.mutate()} disabled={backendSearch.isPending}>Search all memories</Button> : null}
602
613
  </div>
603
614
  <div className="flex flex-wrap gap-2">
604
615
  {model.groups.map((group) => (
@@ -620,7 +631,7 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
620
631
  <Card>
621
632
  <CardHeader>
622
633
  <CardTitle className="flex items-center gap-2"><Focus className="h-4 w-4" /> Focus</CardTitle>
623
- <CardDescription>Click a node to inspect its neighborhood.</CardDescription>
634
+ <CardDescription>Click any idea to see why it matters.</CardDescription>
624
635
  </CardHeader>
625
636
  <CardContent className="space-y-3">
626
637
  {selected ? (
@@ -634,11 +645,18 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
634
645
  </div>
635
646
  </div>
636
647
  {selected.summary ? <p className="text-sm text-muted-foreground">{selected.summary}</p> : null}
637
- <StructuredView value={{
638
- id: selected.id,
639
- degree: selected.degree,
640
- source: selected.source || "not reported",
641
- }} />
648
+ {mode === "basic" ? (
649
+ <KeyValueList data={{
650
+ connections: selected.degree,
651
+ source: selected.source || "not reported",
652
+ }} />
653
+ ) : (
654
+ <StructuredView value={{
655
+ id: selected.id,
656
+ degree: selected.degree,
657
+ source: selected.source || "not reported",
658
+ }} />
659
+ )}
642
660
  </>
643
661
  ) : selectedGroup ? (
644
662
  <div className="space-y-2">
@@ -651,8 +669,8 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
651
669
  </Card>
652
670
  <Card>
653
671
  <CardHeader>
654
- <CardTitle>Important nodes</CardTitle>
655
- <CardDescription>Highest-ranked visible graph records.</CardDescription>
672
+ <CardTitle>Important ideas</CardTitle>
673
+ <CardDescription>What Lattice thinks is most connected right now.</CardDescription>
656
674
  </CardHeader>
657
675
  <CardContent className="space-y-2">
658
676
  {model.visibleNodes.slice(0, 8).map((node) => (
@@ -687,8 +705,8 @@ function HybridSearch() {
687
705
  return (
688
706
  <Card>
689
707
  <CardHeader>
690
- <CardTitle className="flex items-center gap-2"><Search className="h-4 w-4" /> Hybrid search</CardTitle>
691
- <CardDescription>Calls the backend fused search endpoint and renders records with returned source scores.</CardDescription>
708
+ <CardTitle className="flex items-center gap-2"><Search className="h-4 w-4" /> Brain search</CardTitle>
709
+ <CardDescription>Find ideas across memories, documents, and connections.</CardDescription>
692
710
  </CardHeader>
693
711
  <CardContent className="space-y-3">
694
712
  <div className="flex flex-col gap-2 sm:flex-row">
@@ -717,7 +735,7 @@ function MemoryPanel() {
717
735
  <Card>
718
736
  <CardHeader>
719
737
  <CardTitle className="flex items-center gap-2"><Sparkles className="h-4 w-4" /> Recall</CardTitle>
720
- <CardDescription>Searches the real memory recall endpoint.</CardDescription>
738
+ <CardDescription>Bring back related memories from your workspace.</CardDescription>
721
739
  </CardHeader>
722
740
  <CardContent className="space-y-3">
723
741
  <Input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Recall memories about..." />
@@ -741,7 +759,7 @@ function ProvenancePanel() {
741
759
  <DataPanel title="Coverage" result={coverage.data}>
742
760
  {(data) => <StructuredView value={data} />}
743
761
  </DataPanel>
744
- <DataPanel title="Recent ingestion provenance" result={provenance.data}>
762
+ <DataPanel title="Recent sources" result={provenance.data}>
745
763
  {(data) => <EntityList items={(data as Record<string, unknown>).items || data} titleKey="source" metaKey="source_type" limit={14} />}
746
764
  </DataPanel>
747
765
  </div>
@@ -770,22 +788,22 @@ function PortabilityPanel() {
770
788
  <Card>
771
789
  <CardHeader>
772
790
  <CardTitle className="flex items-center gap-2"><DatabaseBackup className="h-4 w-4" /> Export, backup, import</CardTitle>
773
- <CardDescription>Every control calls a real portability endpoint. Import is dry-run by default from a pasted export artifact.</CardDescription>
791
+ <CardDescription>Export, back up, or preview an import before changing your brain.</CardDescription>
774
792
  </CardHeader>
775
793
  <CardContent className="space-y-3">
776
794
  <div className="flex flex-wrap gap-2">
777
- <ActionButton label="Export graph artifact" action={() => latticeApi.graphExport()} />
795
+ <ActionButton label="Export brain map" action={() => latticeApi.graphExport()} />
778
796
  <ActionButton label="Create backup" action={() => latticeApi.graphBackup()} />
779
797
  </div>
780
- <Textarea value={artifact} onChange={(e) => setArtifact(e.target.value)} placeholder="Paste an exported graph artifact for dry-run import" />
798
+ <Textarea value={artifact} onChange={(e) => setArtifact(e.target.value)} placeholder="Paste an exported brain map to preview import" />
781
799
  <Button
782
800
  variant="outline"
783
801
  disabled={!artifact.trim() || importMutation.isPending}
784
802
  onClick={() => importMutation.mutate()}
785
803
  >
786
- Dry-run import
804
+ Preview import
787
805
  </Button>
788
- {importMutation.data ? <OperationResult result={importMutation.data} successLabel="Dry run completed" /> : null}
806
+ {importMutation.data ? <OperationResult result={importMutation.data} successLabel="Import preview completed" /> : null}
789
807
  </CardContent>
790
808
  </Card>
791
809
  </div>
@@ -798,7 +816,7 @@ function PortabilityStatus({ data }: { data: Record<string, unknown> }) {
798
816
  return (
799
817
  <div className="space-y-3">
800
818
  <StatGrid stats={[
801
- { label: "Schema", value: data.graph_schema_version || data.schema_version || "reported" },
819
+ { label: "Graph version", value: data.graph_schema_version || data.schema_version || "reported" },
802
820
  { label: "Nodes", value: (stats.total_nodes as number) || Object.values((stats.nodes as Record<string, unknown>) || {}).reduce((sum: number, value) => sum + Number(value || 0), 0) },
803
821
  { label: "Edges", value: (stats.total_edges as number) || Object.values((stats.edges as Record<string, unknown>) || {}).reduce((sum: number, value) => sum + Number(value || 0), 0) },
804
822
  { label: "Storage", value: storage.engine || "reported" },
@@ -12,9 +12,9 @@ type CaptureTab = "files" | "local" | "browser" | "pipeline";
12
12
 
13
13
  const tabs: Array<{ id: CaptureTab; label: string }> = [
14
14
  { id: "files", label: "Files" },
15
- { id: "local", label: "Local folders" },
16
- { id: "browser", label: "Web capture" },
17
- { id: "pipeline", label: "Pipeline" },
15
+ { id: "local", label: "Folders" },
16
+ { id: "browser", label: "Web" },
17
+ { id: "pipeline", label: "Flow" },
18
18
  ];
19
19
 
20
20
  export function CapturePage({ initialTab }: { initialTab?: string }) {
@@ -23,11 +23,11 @@ export function CapturePage({ initialTab }: { initialTab?: string }) {
23
23
  if (initialTab === "pipeline" || initialTab === "local" || initialTab === "files") setTab(initialTab);
24
24
  }, [initialTab]);
25
25
  return (
26
- <div className="space-y-4">
27
- <header>
28
- <div className="flex items-center gap-2 text-sm text-primary"><Upload className="h-4 w-4" /> One ingestion door</div>
29
- <h1 className="mt-2 text-3xl font-semibold">Capture</h1>
30
- <p className="mt-2 max-w-3xl text-sm text-muted-foreground">Documents, folders, and URLs enter the brain through existing ingestion endpoints with provenance.</p>
26
+ <div className="space-y-5">
27
+ <header className="page-hero">
28
+ <div className="page-kicker"><Upload className="h-4 w-4" /> Add</div>
29
+ <h1 className="page-title">Feed the brain what matters.</h1>
30
+ <p className="page-copy">Drop in files, connect folders, or save a page. Lattice remembers the origin of every idea it learns.</p>
31
31
  </header>
32
32
  <Tabs tabs={tabs} value={tab} onChange={(id) => setTab(id as CaptureTab)} />
33
33
  {tab === "files" ? <FilesPanel /> : null}
@@ -49,14 +49,14 @@ function FilesPanel() {
49
49
  <div className="grid gap-4 xl:grid-cols-[0.75fr_1.25fr]">
50
50
  <Card>
51
51
  <CardHeader>
52
- <CardTitle className="flex items-center gap-2"><Upload className="h-4 w-4" /> Upload documents</CardTitle>
53
- <CardDescription>Multipart upload to `/upload/document`; accepted files are parsed and indexed by the backend.</CardDescription>
52
+ <CardTitle className="flex items-center gap-2"><Upload className="h-4 w-4" /> Add documents</CardTitle>
53
+ <CardDescription>Choose files and Lattice will prepare them for search and memory.</CardDescription>
54
54
  </CardHeader>
55
55
  <CardContent>
56
- <label className="flex min-h-44 cursor-pointer flex-col items-center justify-center gap-3 rounded-lg border border-dashed border-border bg-muted/30 p-5 text-center">
56
+ <label className="flex min-h-56 cursor-pointer flex-col items-center justify-center gap-3 rounded-lg border border-dashed border-border bg-muted/30 p-6 text-center transition hover:bg-muted/50">
57
57
  <Upload className="h-7 w-7 text-primary" />
58
- <span className="font-medium">Choose files</span>
59
- <span className="text-sm text-muted-foreground">PDF, DOCX, XLSX, PPTX, TXT, MD, CSV according to backend policy.</span>
58
+ <span className="text-lg font-semibold">Choose files</span>
59
+ <span className="max-w-sm text-sm leading-6 text-muted-foreground">PDF, Office files, notes, markdown, text, and spreadsheets are all welcome.</span>
60
60
  <input type="file" multiple className="sr-only" onChange={(e) => e.target.files && upload.mutate(e.target.files)} />
61
61
  </label>
62
62
  {upload.data ? (
@@ -86,12 +86,12 @@ function LocalPanel() {
86
86
  <div className="grid gap-4 xl:grid-cols-[0.9fr_1.1fr]">
87
87
  <Card>
88
88
  <CardHeader>
89
- <CardTitle className="flex items-center gap-2"><FolderPlus className="h-4 w-4" /> Connect folder</CardTitle>
90
- <CardDescription>The click is explicit consent; the backend still enforces its permission workflow.</CardDescription>
89
+ <CardTitle className="flex items-center gap-2"><FolderPlus className="h-4 w-4" /> Connect a folder</CardTitle>
90
+ <CardDescription>Point Lattice at a folder you want it to remember.</CardDescription>
91
91
  </CardHeader>
92
92
  <CardContent className="space-y-3">
93
- <Input value={path} onChange={(e) => setPath(e.target.value)} placeholder="/Users/me/Documents/project" />
94
- <Button disabled={!path.trim() || connect.isPending} onClick={() => connect.mutate()}>Connect and watch</Button>
93
+ <Input value={path} onChange={(e) => setPath(e.target.value)} placeholder="Folder path on this Mac" />
94
+ <Button disabled={!path.trim() || connect.isPending} onClick={() => connect.mutate()}>Connect Folder</Button>
95
95
  {connect.data ? <OperationResult result={connect.data} successLabel="Folder connection requested" /> : null}
96
96
  </CardContent>
97
97
  </Card>
@@ -110,7 +110,7 @@ function LocalPanel() {
110
110
  </div>
111
111
  )}
112
112
  </DataPanel>
113
- <DataPanel title="Local runtime probe" result={agent.data} className="xl:col-span-2">
113
+ <DataPanel title="Folder access" result={agent.data} className="xl:col-span-2">
114
114
  {(data) => <StructuredView value={data} />}
115
115
  </DataPanel>
116
116
  </div>
@@ -123,8 +123,8 @@ function BrowserPanel() {
123
123
  return (
124
124
  <Card>
125
125
  <CardHeader>
126
- <CardTitle className="flex items-center gap-2"><Globe2 className="h-4 w-4" /> URL capture</CardTitle>
127
- <CardDescription>Fetches a URL locally through `/api/browser/read-url` and ingests the content with provenance.</CardDescription>
126
+ <CardTitle className="flex items-center gap-2"><Globe2 className="h-4 w-4" /> Save a web page</CardTitle>
127
+ <CardDescription>Capture a page so Lattice can remember the useful parts.</CardDescription>
128
128
  </CardHeader>
129
129
  <CardContent className="space-y-3">
130
130
  <div className="flex flex-col gap-2 sm:flex-row">
@@ -142,16 +142,16 @@ function PipelinePanel() {
142
142
  const stats = useQuery({ queryKey: ["graphStats"], queryFn: latticeApi.graphStats });
143
143
  return (
144
144
  <div className="grid gap-4 xl:grid-cols-2">
145
- <DataPanel title="Index pipeline" result={index.data}>
145
+ <DataPanel title="Processing status" result={index.data}>
146
146
  {(data) => <StructuredView value={data} />}
147
147
  </DataPanel>
148
- <DataPanel title="Graph totals" result={stats.data}>
148
+ <DataPanel title="Brain growth" result={stats.data}>
149
149
  {(data) => <StructuredView value={data} />}
150
150
  </DataPanel>
151
151
  <Card className="xl:col-span-2">
152
152
  <CardHeader>
153
- <CardTitle className="flex items-center gap-2"><HardDrive className="h-4 w-4" /> Rebuild controls</CardTitle>
154
- <CardDescription>Rebuild calls the existing index endpoint. No background work is implied unless the API accepts it.</CardDescription>
153
+ <CardTitle className="flex items-center gap-2"><HardDrive className="h-4 w-4" /> Refresh memory</CardTitle>
154
+ <CardDescription>Refresh search when you want Lattice to re-check captured material.</CardDescription>
155
155
  </CardHeader>
156
156
  <CardContent>
157
157
  <ActionButton label="Rebuild retrieval index" action={() => latticeApi.rebuildIndex()} invalidate={["index"]} />