ltcai 6.2.0 → 6.3.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 (51) hide show
  1. package/README.md +29 -17
  2. package/docs/CHANGELOG.md +66 -0
  3. package/frontend/src/components/onboarding/AnalysisScreen.tsx +127 -0
  4. package/frontend/src/components/onboarding/DownloadConsentPanel.tsx +29 -0
  5. package/frontend/src/components/onboarding/InstallScreen.tsx +208 -0
  6. package/frontend/src/components/onboarding/LanguageChooser.tsx +23 -0
  7. package/frontend/src/components/onboarding/LoginScreen.tsx +131 -0
  8. package/frontend/src/components/onboarding/ProductFlowScreens.tsx +13 -688
  9. package/frontend/src/components/onboarding/RecommendationScreen.tsx +59 -0
  10. package/frontend/src/components/onboarding/recommendationModel.ts +132 -0
  11. package/frontend/src/features/admin/AdminConsole.tsx +1 -1
  12. package/frontend/src/features/brain/BrainCarePanel.tsx +254 -0
  13. package/frontend/src/features/brain/BrainComposer.tsx +66 -0
  14. package/frontend/src/features/brain/BrainConversation.tsx +131 -0
  15. package/frontend/src/features/brain/BrainGraphLayer.tsx +132 -0
  16. package/frontend/src/features/brain/BrainHome.tsx +28 -795
  17. package/frontend/src/features/brain/BrainMemoryLayer.tsx +38 -0
  18. package/frontend/src/features/brain/BrainOverviewPanel.tsx +74 -0
  19. package/frontend/src/features/brain/BrainRelationshipLayer.tsx +43 -0
  20. package/frontend/src/features/brain/DepthEmergence.tsx +53 -0
  21. package/frontend/src/features/brain/types.ts +6 -6
  22. package/frontend/src/features/review/ReviewCard.tsx +14 -10
  23. package/frontend/src/features/review/ReviewInbox.tsx +24 -9
  24. package/frontend/src/i18n.ts +208 -0
  25. package/frontend/src/pages/Brain.tsx +63 -5
  26. package/frontend/src/pages/Capture.tsx +139 -43
  27. package/frontend/src/pages/Library.tsx +72 -6
  28. package/lattice_brain/__init__.py +1 -1
  29. package/lattice_brain/runtime/multi_agent.py +1 -1
  30. package/latticeai/__init__.py +1 -1
  31. package/latticeai/app_factory.py +48 -106
  32. package/latticeai/core/marketplace.py +1 -1
  33. package/latticeai/core/workspace_os.py +1 -1
  34. package/latticeai/runtime/access_runtime.py +97 -0
  35. package/latticeai/runtime/chat_wiring.py +119 -0
  36. package/latticeai/runtime/model_wiring.py +40 -0
  37. package/latticeai/runtime/platform_runtime_wiring.py +2 -3
  38. package/latticeai/runtime/review_wiring.py +37 -0
  39. package/latticeai/runtime/tail_wiring.py +21 -0
  40. package/package.json +2 -2
  41. package/scripts/check_i18n_literals.mjs +52 -0
  42. package/src-tauri/Cargo.lock +1 -1
  43. package/src-tauri/Cargo.toml +1 -1
  44. package/src-tauri/tauri.conf.json +1 -1
  45. package/static/app/asset-manifest.json +5 -5
  46. package/static/app/assets/index-Div5vMlq.css +2 -0
  47. package/static/app/assets/{index-D91Rz5--.js → index-t1jx1BR9.js} +2 -2
  48. package/static/app/assets/index-t1jx1BR9.js.map +1 -0
  49. package/static/app/index.html +2 -2
  50. package/static/app/assets/index-B2-1Gm0q.css +0 -2
  51. package/static/app/assets/index-D91Rz5--.js.map +0 -1
@@ -18,11 +18,24 @@ export const COPY: Record<Language, TextMap> = {
18
18
  "brain.depth.3": "주제",
19
19
  "brain.depth.4": "관계",
20
20
  "brain.depth.5": "전체 지식 그래프",
21
+ "brain.depthLabel.1": "살아있는 Brain",
22
+ "brain.depthLabel.2": "기억 계층",
23
+ "brain.depthLabel.3": "지식 계층",
24
+ "brain.depthLabel.4": "관계 계층",
25
+ "brain.depthLabel.5": "지식 그래프",
21
26
  "brain.view.memories": "기억 보기",
22
27
  "brain.view.topics": "주제 보기",
23
28
  "brain.view.relationships": "관계 보기",
24
29
  "brain.view.graph": "그래프로 보기",
25
30
  "brain.surface": "돌아가기",
31
+ "brain.aria.home": "Lattice Brain",
32
+ "brain.aria.exploration": "Brain 탐색",
33
+ "brain.aria.quickViews": "Brain 빠른 보기",
34
+ "brain.aria.conversation": "대화",
35
+ "brain.aria.ownership": "Brain 소유 보장",
36
+ "brain.aria.starterPrompts": "시작 프롬프트",
37
+ "brain.aria.overview": "Brain 한눈에 보기",
38
+ "brain.aria.graph": "지식 그래프",
26
39
  "brain.title": "Lattice Brain",
27
40
  "brain.local": "로컬 우선",
28
41
  "brain.portable": "이동 가능",
@@ -54,28 +67,108 @@ export const COPY: Record<Language, TextMap> = {
54
67
  "brain.overview.olderEmpty": "대화가 쌓이면 과거 기억이 보입니다.",
55
68
  "brain.overview.topics": "주요 주제",
56
69
  "brain.overview.topicsEmpty": "주제가 형성되는 중입니다.",
70
+ "brain.memory.empty": "기억이 조용합니다.",
71
+ "brain.knowledge.empty": "지식이 형성되는 중입니다.",
57
72
  "brain.graph.empty": "아직 맞는 지식이 없습니다.",
58
73
  "brain.graph.summaryFallback": "이 개념은 가장 깊은 지식 계층의 일부입니다.",
59
74
  "brain.graph.focused": "대화와 문서에서 함께 나온 내용이 선으로 이어집니다.",
60
75
  "brain.graph.emptyFocus": "대화, 문서, 프로젝트를 쌓으면 Brain 그래프가 자랍니다.",
76
+ "brain.graph.search": "검색",
77
+ "brain.graph.searchAria": "지식 그래프 검색",
61
78
  "care.title": "내 Brain 돌보기",
62
79
  "care.subtitle": "내 컴퓨터에 두고, 필요하면 옮길 수 있습니다.",
63
80
  "care.private": "개인 보관",
81
+ "care.ownershipModel": "소유 모델",
64
82
  "care.export": "내보내기",
65
83
  "care.export.detail": "다른 곳으로 가져가기",
66
84
  "care.backup": "백업",
67
85
  "care.backup.detail": "복사본 저장",
86
+ "care.backup.count": "백업 {count}개",
87
+ "care.backup.ready": "백업 준비됨",
68
88
  "care.archive": "보관 파일",
69
89
  "care.archive.detail": "암호화된 Brain",
70
90
  "care.path.placeholder": "확인하거나 미리 볼 보관 파일 경로",
71
91
  "care.path.label": "Brain 보관 파일 경로",
72
92
  "care.passphrase.placeholder": "보관 파일 비밀번호",
73
93
  "care.passphrase.label": "Brain 보관 파일 비밀번호",
94
+ "care.passphrase.confirmPlaceholder": "비밀번호 다시 입력",
95
+ "care.passphrase.confirmLabel": "Brain 보관 파일 비밀번호 확인",
96
+ "care.passphrase.tooShort": "비밀번호는 12자 이상이어야 합니다.",
97
+ "care.passphrase.tooSimple": "대문자, 소문자, 숫자, 기호 중 3종류 이상을 섞어 주세요.",
98
+ "care.passphrase.mismatch": "비밀번호 확인이 일치하지 않습니다.",
99
+ "care.passphrase.strong": "비밀번호 강도가 충분합니다.",
100
+ "care.path.ready": "보관 파일 경로 형식이 올바릅니다.",
101
+ "care.path.invalidChars": "보관 파일 경로에 사용할 수 없는 문자가 있습니다.",
102
+ "care.path.extension": ".latticebrain, .zip, .json 파일만 확인할 수 있습니다.",
74
103
  "care.inspect": "확인",
75
104
  "care.restorePreview": "복원 미리보기",
76
105
  "care.note": "복원 미리보기는 Brain을 바꾸지 않고 보관 파일만 확인합니다. 실제 복원은 설정에서 확정합니다.",
77
106
  "care.working": "진행 중",
107
+ "care.result.error": "Brain 관리 작업을 완료하지 못했습니다.",
108
+ "care.result.completed": "Brain 관리 작업 완료",
109
+ "care.result.path": "파일: {path}",
110
+ "care.result.contents": "기억 {memories}개 · 연결 {links}개 · 대화 {conversations}개",
111
+ "care.result.dryRun": "미리보기만 실행되어 현재 Brain은 바뀌지 않았습니다.",
112
+ "capture.tab.files": "파일",
113
+ "capture.tab.local": "폴더",
114
+ "capture.tab.browser": "웹",
115
+ "capture.tab.pipeline": "흐름",
116
+ "capture.kicker": "추가",
117
+ "capture.title": "중요한 자료를 Brain에 넣으세요.",
118
+ "capture.body": "파일을 넣고, 폴더를 연결하고, 웹페이지를 저장하세요. Lattice는 배운 생각의 출처를 함께 기억합니다.",
119
+ "capture.retry": "다시 시도",
120
+ "capture.files.title": "문서 추가",
121
+ "capture.files.description": "파일을 선택하면 Lattice가 검색과 기억에 들어갈 수 있게 준비합니다.",
122
+ "capture.files.drop": "파일을 놓거나 문서를 선택",
123
+ "capture.files.dropDetail": "각 파일은 대기열에 들어가고, 파싱된 뒤 Memory에 저장되며, 출처 메타데이터와 함께 Brain 그래프에 연결됩니다.",
124
+ "capture.files.uploaded": "업로드된 문서",
125
+ "capture.files.completed": "완료된 업로드는 Memory에 들어간 뒤 여기에 표시됩니다. 그래프 링크는 파싱 후 잠시 더 만들어질 수 있습니다.",
126
+ "capture.files.success": "Brain과 그래프 대기열에 들어감",
127
+ "capture.files.queued": "수집 대기열에서 대기 중",
128
+ "capture.files.uploading": "파싱 후 Memory로 전송 중",
129
+ "capture.files.failed": "Brain에 들어가기 전에 수집에 실패했습니다.",
130
+ "capture.files.captured": "출처 메타데이터와 함께 수집됨",
131
+ "capture.files.capturedWithNode": "출처 메타데이터와 함께 수집됨 · {node}",
132
+ "capture.local.title": "폴더 연결",
133
+ "capture.local.description": "Lattice가 기억해야 할 폴더를 지정하세요.",
134
+ "capture.local.placeholder": "이 Mac의 폴더 경로",
135
+ "capture.local.connect": "폴더 연결",
136
+ "capture.local.success": "폴더 연결 요청됨",
137
+ "capture.local.sources": "연결된 출처",
138
+ "capture.local.access": "폴더 접근",
139
+ "capture.browser.title": "웹페이지 저장",
140
+ "capture.browser.description": "유용한 내용을 기억할 수 있게 페이지를 수집합니다.",
141
+ "capture.browser.placeholder": "https://example.com/article",
142
+ "capture.browser.capture": "URL 수집",
143
+ "capture.browser.success": "URL 수집 요청됨",
144
+ "capture.pipeline.status": "처리 상태",
145
+ "capture.pipeline.growth": "Brain 성장",
146
+ "capture.pipeline.refresh": "기억 새로고침",
147
+ "capture.pipeline.description": "수집한 자료를 Lattice가 다시 확인해야 할 때 검색을 새로고침합니다.",
148
+ "capture.pipeline.rebuild": "검색 인덱스 다시 만들기",
149
+ "review.inbox.title": "검토함",
150
+ "review.inbox.description": "결정 대기 중인 자동화 제안입니다. 지금 실행은 승인 없이 미리보기만 수행합니다.",
151
+ "review.status.connected": "연결됨",
152
+ "review.status.unavailable": "사용 불가",
153
+ "review.inbox.loadError": "검토함을 불러올 수 없습니다.",
154
+ "review.inbox.unavailable": "지금은 검토 대기열을 사용할 수 없습니다.",
155
+ "review.inbox.empty": "검토할 항목 없음",
156
+ "review.inbox.emptySnoozed": "일시 중지된 항목은 해제되거나 다시 대기 상태가 될 때까지 여기에 표시됩니다.",
157
+ "review.inbox.emptyPending": "자동화가 검토 대기열을 사용하면 새 제안이 여기에 표시됩니다.",
158
+ "review.snoozed.detail": "해당 시점까지 대기열에서 빠져 있습니다. 일시 중지 해제 시 즉시 돌아옵니다.",
159
+ "review.unsnooze": "일시 중지 해제",
160
+ "review.runNow.detail": "지금 실행은 승인 없이 작업을 미리봅니다. 결과가 맞으면 승인하거나 기각하세요.",
161
+ "review.actions.aria": "검토 작업",
162
+ "review.runNow": "지금 실행",
163
+ "review.regenerated": "다시 생성됨",
164
+ "review.executed": "실행됨",
165
+ "review.approve": "승인",
166
+ "review.snoozeDay": "하루 일시 중지",
167
+ "review.dismiss": "기각",
168
+ "review.feedback.open": "승인 또는 기각 전까지 항목은 열린 상태로 유지됩니다.",
169
+ "review.action.failed": "{action} 실패",
78
170
  "admin.back": "Brain",
171
+ "admin.aria.console": "Lattice 관리자",
79
172
  "admin.kicker": "분리된 관리자 작업공간",
80
173
  "admin.title": "Admin Console",
81
174
  "admin.body": "사용자, 로그, 보안, Brain 상태는 일반 사용자 화면과 분리됩니다.",
@@ -173,7 +266,9 @@ export const COPY: Record<Language, TextMap> = {
173
266
  "flow.login.title": "내 AI 브레인의 주인을 정합니다.",
174
267
  "flow.login.body": "Lattice AI는 내 지식과 맥락을 이 컴퓨터에 보관하는 로컬 우선 AI 브레인입니다. 모델은 바꿀 수 있고, 외부 전송은 사용자가 선택할 때만 시작됩니다.",
175
268
  "flow.name": "이름",
269
+ "flow.name.placeholder": "나",
176
270
  "flow.email": "이메일",
271
+ "flow.email.placeholder": "you@local",
177
272
  "flow.password": "비밀번호",
178
273
  "flow.password.placeholder": "로컬 Brain 비밀번호",
179
274
  "flow.login.busy": "Brain 여는 중...",
@@ -189,6 +284,7 @@ export const COPY: Record<Language, TextMap> = {
189
284
  "flow.promise.model.v": "모델은 목소리이고, 자산은 Brain입니다.",
190
285
  "flow.promise.ownership.k": "사용자 소유",
191
286
  "flow.promise.ownership.v": "백업, 복원, 이동을 직접 결정합니다.",
287
+ "flow.promise.aria": "Lattice AI 제품 약속",
192
288
  "flow.analysis.title": "이 컴퓨터에서 가능한 경험을 확인합니다.",
193
289
  "flow.analysis.body": "스펙 점수가 아니라, 이 Mac에서 어떤 로컬 AI 브레인 경험이 편한지 알려드립니다. 확인은 로컬 상태를 읽는 것이고, 클라우드 모델은 선택 사항입니다.",
194
290
  "flow.analysis.finding": "가장 편한 설정을 찾는 중...",
@@ -196,6 +292,10 @@ export const COPY: Record<Language, TextMap> = {
196
292
  "flow.analysis.wait": "잠시만 기다리면 자동으로 정리됩니다.",
197
293
  "flow.analysis.error": "Lattice가 이 컴퓨터를 끝까지 확인하지 못했습니다. 그래도 안전한 기본값으로 계속할 수 있습니다.",
198
294
  "flow.analysis.continue": "추천 모델 보기",
295
+ "flow.analysis.bestFit": "{model}이 이 컴퓨터에 가장 잘 맞습니다.",
296
+ "flow.analysis.privateRecommended": "이 컴퓨터에는 개인 로컬 Brain을 추천합니다.",
297
+ "flow.analysis.os.windows": "Windows PC",
298
+ "flow.analysis.os.linux": "Linux 컴퓨터",
199
299
  "flow.recommend.title": "추천대로 시작하세요.",
200
300
  "flow.recommend.body": "모델은 Brain의 현재 목소리입니다. 가장 안전한 추천, 더 빠른 모델, 더 강한 모델만 먼저 보여드립니다.",
201
301
  "flow.recommend.primary": "추천대로 시작하기",
@@ -203,6 +303,10 @@ export const COPY: Record<Language, TextMap> = {
203
303
  "flow.recommend.back": "뒤로",
204
304
  "flow.recommend.skip": "모델 없이 Brain 열기",
205
305
  "flow.recommend.hint": "잘 모르겠다면 추천대로 시작하면 됩니다.",
306
+ "flow.recommend.rank.best": "추천",
307
+ "flow.recommend.rank.faster": "빠른 선택",
308
+ "flow.recommend.rank.advanced": "고급 선택",
309
+ "flow.recommend.rank.choice": "선택 {index}",
206
310
  "flow.install.title": "모델을 설치하고 시작합니다.",
207
311
  "flow.install.body": "이 모델이 Brain의 로컬 목소리가 됩니다. 인터넷은 다운로드할 때만 필요하고, 실행과 기억은 이 컴퓨터에서 유지됩니다.",
208
312
  "flow.install.wait": "Brain이 사용할 모델을 기다리고 있습니다.",
@@ -235,11 +339,24 @@ export const COPY: Record<Language, TextMap> = {
235
339
  "brain.depth.3": "Topics",
236
340
  "brain.depth.4": "Relationships",
237
341
  "brain.depth.5": "Full knowledge graph",
342
+ "brain.depthLabel.1": "Living Brain",
343
+ "brain.depthLabel.2": "Memory Layer",
344
+ "brain.depthLabel.3": "Knowledge Layer",
345
+ "brain.depthLabel.4": "Relationship Layer",
346
+ "brain.depthLabel.5": "Knowledge Graph",
238
347
  "brain.view.memories": "Memories",
239
348
  "brain.view.topics": "Topics",
240
349
  "brain.view.relationships": "Relationships",
241
350
  "brain.view.graph": "Graph",
242
351
  "brain.surface": "Back",
352
+ "brain.aria.home": "Lattice Brain",
353
+ "brain.aria.exploration": "Brain exploration",
354
+ "brain.aria.quickViews": "Brain quick views",
355
+ "brain.aria.conversation": "Conversation",
356
+ "brain.aria.ownership": "Brain ownership guarantees",
357
+ "brain.aria.starterPrompts": "Starter prompts",
358
+ "brain.aria.overview": "Brain overview",
359
+ "brain.aria.graph": "Knowledge Graph",
243
360
  "brain.title": "Lattice Brain",
244
361
  "brain.local": "Local-first",
245
362
  "brain.portable": "Portable",
@@ -271,28 +388,108 @@ export const COPY: Record<Language, TextMap> = {
271
388
  "brain.overview.olderEmpty": "Earlier memories appear as conversations accumulate.",
272
389
  "brain.overview.topics": "Main topics",
273
390
  "brain.overview.topicsEmpty": "Topics are still forming.",
391
+ "brain.memory.empty": "Memory is quiet",
392
+ "brain.knowledge.empty": "Knowledge is forming",
274
393
  "brain.graph.empty": "No matching knowledge yet",
275
394
  "brain.graph.summaryFallback": "This concept is part of the deepest knowledge layer.",
276
395
  "brain.graph.focused": "Ideas that appeared together in conversations and documents are connected by lines.",
277
396
  "brain.graph.emptyFocus": "Your Brain graph grows as you add conversations, documents, and projects.",
397
+ "brain.graph.search": "Search",
398
+ "brain.graph.searchAria": "Search knowledge graph",
278
399
  "care.title": "Care for my Brain",
279
400
  "care.subtitle": "Own it locally. Keep it portable.",
280
401
  "care.private": "Private",
402
+ "care.ownershipModel": "Ownership model",
281
403
  "care.export": "Export",
282
404
  "care.export.detail": "Take it with you",
283
405
  "care.backup": "Backup",
284
406
  "care.backup.detail": "Save a copy",
407
+ "care.backup.count": "{count} backups",
408
+ "care.backup.ready": "Backups ready",
285
409
  "care.archive": "Archive",
286
410
  "care.archive.detail": "Encrypted Brain",
287
411
  "care.path.placeholder": "Paste an archive path to inspect or preview",
288
412
  "care.path.label": "Brain archive path",
289
413
  "care.passphrase.placeholder": "Archive passphrase",
290
414
  "care.passphrase.label": "Brain archive passphrase",
415
+ "care.passphrase.confirmPlaceholder": "Repeat passphrase",
416
+ "care.passphrase.confirmLabel": "Confirm Brain archive passphrase",
417
+ "care.passphrase.tooShort": "Use at least 12 characters.",
418
+ "care.passphrase.tooSimple": "Mix at least three of uppercase, lowercase, numbers, and symbols.",
419
+ "care.passphrase.mismatch": "Passphrases do not match.",
420
+ "care.passphrase.strong": "Passphrase strength is sufficient.",
421
+ "care.path.ready": "Archive path format looks valid.",
422
+ "care.path.invalidChars": "Archive path contains unsupported characters.",
423
+ "care.path.extension": "Use a .latticebrain, .zip, or .json archive.",
291
424
  "care.inspect": "Inspect",
292
425
  "care.restorePreview": "Restore preview",
293
426
  "care.note": "Restore preview checks an archive without changing your Brain. Confirmed restore stays in Settings.",
294
427
  "care.working": "Working",
428
+ "care.result.error": "Brain care action could not complete.",
429
+ "care.result.completed": "Brain care action completed.",
430
+ "care.result.path": "File: {path}",
431
+ "care.result.contents": "{memories} memories · {links} links · {conversations} conversations",
432
+ "care.result.dryRun": "Preview only; your current Brain was not changed.",
433
+ "capture.tab.files": "Files",
434
+ "capture.tab.local": "Folders",
435
+ "capture.tab.browser": "Web",
436
+ "capture.tab.pipeline": "Flow",
437
+ "capture.kicker": "Add",
438
+ "capture.title": "Feed the brain what matters.",
439
+ "capture.body": "Drop in files, connect folders, or save a page. Lattice remembers the origin of every idea it learns.",
440
+ "capture.retry": "Retry",
441
+ "capture.files.title": "Add documents",
442
+ "capture.files.description": "Choose files and Lattice will prepare them for search and memory.",
443
+ "capture.files.drop": "Drop files or choose documents",
444
+ "capture.files.dropDetail": "Each file is queued, parsed, written to Memory, and linked into the Brain graph with source metadata.",
445
+ "capture.files.uploaded": "Uploaded documents",
446
+ "capture.files.completed": "Completed uploads appear here after they enter Memory. Graph links may continue building briefly after parsing finishes.",
447
+ "capture.files.success": "Entered Brain and graph queue",
448
+ "capture.files.queued": "Waiting in the ingest queue",
449
+ "capture.files.uploading": "Parsing and sending to Memory",
450
+ "capture.files.failed": "Ingest failed before it entered the Brain",
451
+ "capture.files.captured": "Captured with source metadata",
452
+ "capture.files.capturedWithNode": "Captured with source metadata · {node}",
453
+ "capture.local.title": "Connect a folder",
454
+ "capture.local.description": "Point Lattice at a folder you want it to remember.",
455
+ "capture.local.placeholder": "Folder path on this Mac",
456
+ "capture.local.connect": "Connect Folder",
457
+ "capture.local.success": "Folder connection requested",
458
+ "capture.local.sources": "Connected sources",
459
+ "capture.local.access": "Folder access",
460
+ "capture.browser.title": "Save a web page",
461
+ "capture.browser.description": "Capture a page so Lattice can remember the useful parts.",
462
+ "capture.browser.placeholder": "https://example.com/article",
463
+ "capture.browser.capture": "Capture URL",
464
+ "capture.browser.success": "URL capture requested",
465
+ "capture.pipeline.status": "Processing status",
466
+ "capture.pipeline.growth": "Brain growth",
467
+ "capture.pipeline.refresh": "Refresh memory",
468
+ "capture.pipeline.description": "Refresh search when you want Lattice to re-check captured material.",
469
+ "capture.pipeline.rebuild": "Rebuild retrieval index",
470
+ "review.inbox.title": "Review inbox",
471
+ "review.inbox.description": "Automation suggestions waiting for your decision. Run now previews without approving.",
472
+ "review.status.connected": "connected",
473
+ "review.status.unavailable": "unavailable",
474
+ "review.inbox.loadError": "Could not load review inbox",
475
+ "review.inbox.unavailable": "The review queue is not available right now.",
476
+ "review.inbox.empty": "Nothing to review",
477
+ "review.inbox.emptySnoozed": "Snoozed items will appear here until they are unsnoozed or become pending again.",
478
+ "review.inbox.emptyPending": "When automations opt into the review queue, new suggestions will appear here.",
479
+ "review.snoozed.detail": "This stays out of the pending queue until then. Unsnooze brings it back immediately.",
480
+ "review.unsnooze": "Unsnooze",
481
+ "review.runNow.detail": "Run now previews the action without approving it. Approve or dismiss when the result looks right.",
482
+ "review.actions.aria": "Review actions",
483
+ "review.runNow": "Run now",
484
+ "review.regenerated": "Regenerated",
485
+ "review.executed": "Executed",
486
+ "review.approve": "Approve",
487
+ "review.snoozeDay": "Snooze 1 day",
488
+ "review.dismiss": "Dismiss",
489
+ "review.feedback.open": "item stays open until you approve or dismiss.",
490
+ "review.action.failed": "{action} failed",
295
491
  "admin.back": "Brain",
492
+ "admin.aria.console": "Lattice Admin",
296
493
  "admin.kicker": "Separate admin workspace",
297
494
  "admin.title": "Admin Console",
298
495
  "admin.body": "Users, logs, security, and Brain health stay out of the normal user experience.",
@@ -390,7 +587,9 @@ export const COPY: Record<Language, TextMap> = {
390
587
  "flow.login.title": "Choose the owner of your AI Brain.",
391
588
  "flow.login.body": "Lattice AI is a local-first Digital Brain that keeps your knowledge on this computer. Models can change; external transfer starts only when you choose it.",
392
589
  "flow.name": "Name",
590
+ "flow.name.placeholder": "You",
393
591
  "flow.email": "Email",
592
+ "flow.email.placeholder": "you@local",
394
593
  "flow.password": "Password",
395
594
  "flow.password.placeholder": "Local Brain password",
396
595
  "flow.login.busy": "Opening Brain...",
@@ -406,6 +605,7 @@ export const COPY: Record<Language, TextMap> = {
406
605
  "flow.promise.model.v": "The model is the voice; the Brain is the asset.",
407
606
  "flow.promise.ownership.k": "User owned",
408
607
  "flow.promise.ownership.v": "You decide when to back up, restore, or move it.",
608
+ "flow.promise.aria": "Lattice AI product promise",
409
609
  "flow.analysis.title": "Checking what this computer can do.",
410
610
  "flow.analysis.body": "This is not a spec test. Lattice reads local capability and explains what AI Brain experience this Mac can support. Cloud models remain optional.",
411
611
  "flow.analysis.finding": "Finding the easiest setup...",
@@ -413,6 +613,10 @@ export const COPY: Record<Language, TextMap> = {
413
613
  "flow.analysis.wait": "This will be summarized automatically in a moment.",
414
614
  "flow.analysis.error": "Lattice could not finish checking this computer. You can still continue with a safe default.",
415
615
  "flow.analysis.continue": "View recommended models",
616
+ "flow.analysis.bestFit": "{model} looks like the best fit.",
617
+ "flow.analysis.privateRecommended": "A private local Brain is recommended for this computer.",
618
+ "flow.analysis.os.windows": "Windows PC",
619
+ "flow.analysis.os.linux": "Linux computer",
416
620
  "flow.recommend.title": "Start with the recommendation.",
417
621
  "flow.recommend.body": "The model is your Brain's current voice. You see the safest recommendation, a faster model, and a stronger model first.",
418
622
  "flow.recommend.primary": "Start with recommendation",
@@ -420,6 +624,10 @@ export const COPY: Record<Language, TextMap> = {
420
624
  "flow.recommend.back": "Back",
421
625
  "flow.recommend.skip": "Open Brain without a model",
422
626
  "flow.recommend.hint": "If unsure, start with the recommendation.",
627
+ "flow.recommend.rank.best": "Best Experience",
628
+ "flow.recommend.rank.faster": "Faster",
629
+ "flow.recommend.rank.advanced": "Advanced",
630
+ "flow.recommend.rank.choice": "Choice {index}",
423
631
  "flow.install.title": "Install the model and start.",
424
632
  "flow.install.body": "This model becomes your Brain's local voice. Internet is needed only for download; execution and memory stay on this computer.",
425
633
  "flow.install.wait": "Waiting for the model your Brain will use.",
@@ -457,7 +457,7 @@ export function BrainPage({ initialTab }: { initialTab?: string }) {
457
457
  {(data) => <MemoryStatus data={data as Record<string, unknown>} />}
458
458
  </DataPanel>
459
459
  <DataPanel title="Recent sources" result={provenance.data}>
460
- {(data) => <EntityList items={(data as Record<string, unknown>).items || data} titleKey="source" metaKey="source_type" />}
460
+ {(data) => <SourceProvenanceList items={(data as Record<string, unknown>).items || data} />}
461
461
  </DataPanel>
462
462
  </div>
463
463
  ) : null}
@@ -527,6 +527,7 @@ function RetrievalStatus({ data }: { data: Record<string, unknown> }) {
527
527
 
528
528
  function MemoryStatus({ data }: { data: Record<string, unknown> }) {
529
529
  const usage = isRecord(data.usage) ? data.usage : {};
530
+ const sources = asArray<Record<string, unknown>>(data.sources || data.tiers);
530
531
  return (
531
532
  <div className="space-y-3">
532
533
  <StatGrid stats={[
@@ -535,7 +536,7 @@ function MemoryStatus({ data }: { data: Record<string, unknown> }) {
535
536
  { label: "Bytes", value: usage.total_bytes ?? 0 },
536
537
  { label: "Health", value: data.health || "reported" },
537
538
  ]} />
538
- <EntityList items={data.sources || data.tiers} titleKey="label" metaKey="health" />
539
+ <SourceProvenanceList items={sources} limit={6} />
539
540
  </div>
540
541
  );
541
542
  }
@@ -667,15 +668,20 @@ function DigitalBrainExplorer({ data }: { data: unknown }) {
667
668
  {mode === "basic" ? (
668
669
  <KeyValueList data={{
669
670
  connections: selected.degree,
670
- source: selected.source || "not reported",
671
+ source: selected.source || "Source not recorded",
672
+ source_type: sourceType(selected.raw),
673
+ created_at: sourceCreatedAt(selected.raw) || "Created time not recorded",
671
674
  }} />
672
675
  ) : (
673
676
  <StructuredView value={{
674
677
  id: selected.id,
675
678
  degree: selected.degree,
676
- source: selected.source || "not reported",
679
+ source: selected.source || "Source not recorded",
680
+ source_type: sourceType(selected.raw),
681
+ created_at: sourceCreatedAt(selected.raw) || "Created time not recorded",
677
682
  }} />
678
683
  )}
684
+ {selected.source ? <Button variant="outline" size="sm" onClick={() => void navigator.clipboard?.writeText(selected.source)}>Copy source</Button> : null}
679
685
  </>
680
686
  ) : selectedGroup ? (
681
687
  <div className="space-y-2">
@@ -779,12 +785,64 @@ function ProvenancePanel() {
779
785
  {(data) => <StructuredView value={data} />}
780
786
  </DataPanel>
781
787
  <DataPanel title="Recent sources" result={provenance.data}>
782
- {(data) => <EntityList items={(data as Record<string, unknown>).items || data} titleKey="source" metaKey="source_type" limit={14} />}
788
+ {(data) => <SourceProvenanceList items={(data as Record<string, unknown>).items || data} limit={14} />}
783
789
  </DataPanel>
784
790
  </div>
785
791
  );
786
792
  }
787
793
 
794
+ function SourceProvenanceList({ items, limit = 8 }: { items: unknown; limit?: number }) {
795
+ const rows = asArray<Record<string, unknown>>(items).slice(0, limit);
796
+ if (!rows.length) return <EmptyState title="No sources yet" detail="New memories will show their chat, manual, document, or import origin here." />;
797
+ return (
798
+ <div className="grid gap-2">
799
+ {rows.map((item, index) => {
800
+ const title = String(item.title || item.label || item.source_title || item.filename || item.path || item.source || `Source ${index + 1}`);
801
+ const path = String(item.path || item.source_path || item.source || item.conversation_id || "");
802
+ const type = sourceType(item);
803
+ const created = sourceCreatedAt(item);
804
+ return (
805
+ <div key={String(item.id || item.source_id || title)} className="rounded-lg border border-border bg-background/55 p-3">
806
+ <div className="flex flex-wrap items-start justify-between gap-2">
807
+ <div>
808
+ <div className="font-medium">{title}</div>
809
+ <div className="mt-1 text-xs text-muted-foreground">
810
+ {type} · {created || "Created time not recorded"}
811
+ </div>
812
+ </div>
813
+ <Badge variant="muted">{path ? "inspectable" : "missing provenance"}</Badge>
814
+ </div>
815
+ {path ? (
816
+ <div className="mt-2 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
817
+ <span className="break-all">{path}</span>
818
+ <Button variant="outline" size="sm" onClick={() => void navigator.clipboard?.writeText(path)}>Copy source</Button>
819
+ </div>
820
+ ) : (
821
+ <p className="mt-2 text-xs text-muted-foreground">This older memory did not record a source path or conversation. It remains searchable, but provenance is incomplete.</p>
822
+ )}
823
+ </div>
824
+ );
825
+ })}
826
+ </div>
827
+ );
828
+ }
829
+
830
+ function sourceType(item: Record<string, unknown>) {
831
+ const metadata = isRecord(item.metadata) ? item.metadata : {};
832
+ const raw = String(item.source_type || item.type || item.kind || metadata.source_type || metadata.role || "").toLowerCase();
833
+ if (/chat|conversation|message/.test(raw)) return "chat";
834
+ if (/document|upload|file|pdf|markdown|text/.test(raw)) return "document";
835
+ if (/import|archive|restore/.test(raw)) return "import";
836
+ if (/manual|note/.test(raw)) return "manual";
837
+ return "source unknown";
838
+ }
839
+
840
+ function sourceCreatedAt(item: Record<string, unknown>) {
841
+ const metadata = isRecord(item.metadata) ? item.metadata : {};
842
+ const value = item.created_at || item.timestamp || item.updated_at || metadata.created_at || metadata.timestamp;
843
+ return value ? String(value) : "";
844
+ }
845
+
788
846
  function PortabilityPanel() {
789
847
  const qc = useQueryClient();
790
848
  const [artifact, setArtifact] = React.useState("");