ltcai 4.4.0 → 4.6.0

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 (67) hide show
  1. package/README.md +77 -33
  2. package/docs/CHANGELOG.md +128 -0
  3. package/docs/V4_5_0_GEMMA_RUNTIME_COMPATIBILITY_REPORT.md +49 -0
  4. package/docs/V4_5_0_GRAPH_UX_REPORT.md +34 -0
  5. package/docs/V4_5_0_MODEL_RUNTIME_UX_REPORT.md +40 -0
  6. package/docs/V4_5_0_ONBOARDING_REPORT.md +31 -0
  7. package/docs/V4_5_0_PRODUCT_EXPERIENCE_RECOVERY_REPORT.md +49 -0
  8. package/docs/V4_5_0_VALIDATION_REPORT.md +60 -0
  9. package/docs/V4_5_1_GRAPH_EXPERIENCE_REPORT.md +33 -0
  10. package/docs/V4_5_1_MODEL_EXPERIENCE_REPORT.md +37 -0
  11. package/docs/V4_5_1_NAVIGATION_REPORT.md +37 -0
  12. package/docs/V4_5_1_ONBOARDING_REPORT.md +29 -0
  13. package/docs/V4_5_1_PRODUCT_REIMAGINING_REPORT.md +61 -0
  14. package/docs/V4_5_1_RC_ARTIFACTS.md +44 -0
  15. package/docs/V4_5_1_UX_REPORT.md +45 -0
  16. package/docs/V4_5_1_VALIDATION_REPORT.md +54 -0
  17. package/docs/V4_5_1_VISUAL_DESIGN_REPORT.md +30 -0
  18. package/docs/V4_6_0_LIVING_BRAIN_EXPERIENCE_REPORT.md +58 -0
  19. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +18 -17
  20. package/docs/architecture.md +8 -4
  21. package/frontend/index.html +2 -2
  22. package/frontend/src/App.tsx +120 -98
  23. package/frontend/src/api/client.ts +84 -1
  24. package/frontend/src/components/BrainConversation.tsx +301 -0
  25. package/frontend/src/components/FirstRunGuide.tsx +99 -0
  26. package/frontend/src/components/LivingBrain.tsx +121 -0
  27. package/frontend/src/components/ProductFlow.tsx +596 -0
  28. package/frontend/src/components/primitives.tsx +131 -25
  29. package/frontend/src/components/ui/badge.tsx +2 -2
  30. package/frontend/src/components/ui/button.tsx +7 -7
  31. package/frontend/src/components/ui/card.tsx +5 -5
  32. package/frontend/src/components/ui/input.tsx +1 -1
  33. package/frontend/src/components/ui/textarea.tsx +1 -1
  34. package/frontend/src/pages/Act.tsx +58 -28
  35. package/frontend/src/pages/Ask.tsx +2 -197
  36. package/frontend/src/pages/Brain.tsx +108 -71
  37. package/frontend/src/pages/Capture.tsx +24 -24
  38. package/frontend/src/pages/Library.tsx +222 -32
  39. package/frontend/src/pages/System.tsx +56 -34
  40. package/frontend/src/routes.ts +16 -25
  41. package/frontend/src/store/appStore.ts +8 -1
  42. package/frontend/src/styles.css +1663 -36
  43. package/lattice_brain/__init__.py +1 -1
  44. package/lattice_brain/runtime/multi_agent.py +1 -1
  45. package/latticeai/__init__.py +1 -1
  46. package/latticeai/api/models.py +107 -18
  47. package/latticeai/core/marketplace.py +1 -1
  48. package/latticeai/core/model_compat.py +250 -0
  49. package/latticeai/core/workspace_os.py +1 -1
  50. package/latticeai/models/router.py +136 -32
  51. package/latticeai/services/model_catalog.py +2 -2
  52. package/latticeai/services/model_recommendation.py +8 -1
  53. package/latticeai/services/model_runtime.py +18 -3
  54. package/package.json +2 -2
  55. package/scripts/build_frontend_assets.mjs +12 -1
  56. package/src-tauri/Cargo.lock +1 -1
  57. package/src-tauri/Cargo.toml +1 -1
  58. package/src-tauri/tauri.conf.json +1 -1
  59. package/static/app/asset-manifest.json +5 -5
  60. package/static/app/assets/index-By-G-Kay.css +2 -0
  61. package/static/app/assets/index-CJx6WuQH.js +336 -0
  62. package/static/app/assets/index-CJx6WuQH.js.map +1 -0
  63. package/static/app/index.html +4 -4
  64. package/static/manifest.json +1 -1
  65. package/static/app/assets/index-CHHal8Zl.css +0 -2
  66. package/static/app/assets/index-pdzil9ac.js +0 -333
  67. package/static/app/assets/index-pdzil9ac.js.map +0 -1
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3
3
  import { Network, ShieldCheck, UserCircle, Users } from "lucide-react";
4
4
  import { latticeApi } from "@/api/client";
5
- import { ActionButton, DataPanel, EmptyState, EntityList, KeyValueList, OperationResult, StatGrid, StructuredView, Tabs } from "@/components/primitives";
5
+ import { ActionButton, DataPanel, EmptyState, EntityList, KeyValueList, ModeGate, OperationResult, StatGrid, StructuredView, Tabs } 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";
@@ -14,27 +14,28 @@ type SystemTab = "account" | "workspaces" | "snapshots" | "activity" | "network"
14
14
 
15
15
  const tabs: Array<{ id: SystemTab; label: string }> = [
16
16
  { id: "account", label: "Account" },
17
- { id: "workspaces", label: "Workspaces" },
17
+ { id: "workspaces", label: "Spaces" },
18
18
  { id: "snapshots", label: "Snapshots" },
19
- { id: "activity", label: "Activity" },
20
- { id: "network", label: "Network" },
21
- { id: "settings", label: "Settings" },
19
+ { id: "activity", label: "History" },
20
+ { id: "network", label: "Devices" },
21
+ { id: "settings", label: "Preferences" },
22
22
  { id: "admin", label: "Admin" },
23
23
  ];
24
24
 
25
25
  export function SystemPage({ initialTab }: { initialTab?: string }) {
26
+ const mode = useAppStore((state) => state.mode);
26
27
  const [tab, setTab] = React.useState<SystemTab>((initialTab as SystemTab) || "account");
27
28
  React.useEffect(() => {
28
29
  if (tabs.some((item) => item.id === initialTab)) setTab(initialTab as SystemTab);
29
30
  }, [initialTab]);
30
31
  return (
31
- <div className="space-y-4">
32
- <header>
33
- <div className="flex items-center gap-2 text-sm text-primary"><ShieldCheck className="h-4 w-4" /> Local-first control plane</div>
34
- <h1 className="mt-2 text-3xl font-semibold">System</h1>
35
- <p className="mt-2 max-w-3xl text-sm text-muted-foreground">Identity, workspaces, snapshots, activity, network exchange, runtime settings, and admin status.</p>
32
+ <div className="space-y-5">
33
+ <header className="page-hero">
34
+ <div className="page-kicker"><ShieldCheck className="h-4 w-4" /> Care</div>
35
+ <h1 className="page-title">Keep your brain safe and portable.</h1>
36
+ <p className="page-copy">Manage identity, spaces, backups, trusted devices, and safeguards from one calm place.</p>
36
37
  </header>
37
- <Tabs tabs={tabs} value={tab} onChange={(id) => setTab(id as SystemTab)} />
38
+ <Tabs tabs={mode === "basic" ? tabs.filter((item) => item.id !== "admin") : tabs} value={tab} onChange={(id) => setTab(id as SystemTab)} />
38
39
  {tab === "account" ? <AccountPanel /> : null}
39
40
  {tab === "workspaces" ? <WorkspacePanel /> : null}
40
41
  {tab === "snapshots" ? <SnapshotsPanel /> : null}
@@ -66,8 +67,8 @@ function AccountPanel() {
66
67
  </DataPanel>
67
68
  <Card>
68
69
  <CardHeader>
69
- <CardTitle className="flex items-center gap-2"><UserCircle className="h-4 w-4" /> Token-native account</CardTitle>
70
- <CardDescription>Login, registration, profile update, and password change all call existing auth endpoints.</CardDescription>
70
+ <CardTitle className="flex items-center gap-2"><UserCircle className="h-4 w-4" /> Account</CardTitle>
71
+ <CardDescription>Sign in, create a local account, and keep your profile current.</CardDescription>
71
72
  </CardHeader>
72
73
  <CardContent className="grid gap-3">
73
74
  <Input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="email" />
@@ -89,7 +90,7 @@ function AccountPanel() {
89
90
  ))}
90
91
  </CardContent>
91
92
  </Card>
92
- <DataPanel title="SSO config" result={sso.data} className="xl:col-span-2">
93
+ <DataPanel title="Sign-in options" result={sso.data} className="xl:col-span-2">
93
94
  {(data) => <StructuredView value={data} />}
94
95
  </DataPanel>
95
96
  </div>
@@ -110,7 +111,7 @@ function WorkspacePanel() {
110
111
  const workspaces = asArray<Record<string, unknown>>((registry.data?.data as Record<string, unknown>)?.workspaces);
111
112
  return (
112
113
  <div className="grid gap-4 xl:grid-cols-[1.1fr_0.9fr]">
113
- <DataPanel title="Workspace registry" result={registry.data}>
114
+ <DataPanel title="Your workspaces" result={registry.data}>
114
115
  {() => (
115
116
  <div className="grid gap-2">
116
117
  {workspaces.map((workspace) => {
@@ -137,7 +138,7 @@ function WorkspacePanel() {
137
138
  <Card>
138
139
  <CardHeader>
139
140
  <CardTitle className="flex items-center gap-2"><Users className="h-4 w-4" /> Organizations and invitations</CardTitle>
140
- <CardDescription>Local invite tokens and workspace creation are backend-owned.</CardDescription>
141
+ <CardDescription>Create or join a workspace before adding knowledge.</CardDescription>
141
142
  </CardHeader>
142
143
  <CardContent className="grid gap-3">
143
144
  <Input value={orgName} onChange={(e) => setOrgName(e.target.value)} placeholder="New organization name" />
@@ -187,7 +188,7 @@ function SnapshotsPanel() {
187
188
  <Card>
188
189
  <CardHeader>
189
190
  <CardTitle>Snapshot actions</CardTitle>
190
- <CardDescription>Create and compare through Workspace OS endpoints.</CardDescription>
191
+ <CardDescription>Create checkpoints and compare changes over time.</CardDescription>
191
192
  </CardHeader>
192
193
  <CardContent className="grid gap-3">
193
194
  <Input value={name} onChange={(e) => setName(e.target.value)} placeholder="snapshot name" />
@@ -240,14 +241,14 @@ function NetworkPanel() {
240
241
  </DataPanel>
241
242
  <Card>
242
243
  <CardHeader>
243
- <CardTitle className="flex items-center gap-2"><Network className="h-4 w-4" /> Pair peer</CardTitle>
244
- <CardDescription>Manual peer pairing for signed workspace bundle exchange.</CardDescription>
244
+ <CardTitle className="flex items-center gap-2"><Network className="h-4 w-4" /> Pair device</CardTitle>
245
+ <CardDescription>Pair a trusted device for workspace exchange.</CardDescription>
245
246
  </CardHeader>
246
247
  <CardContent className="grid gap-3">
247
- <Input value={name} onChange={(e) => setName(e.target.value)} placeholder="peer name" />
248
- <Input value={baseUrl} onChange={(e) => setBaseUrl(e.target.value)} placeholder="http://peer.local:8765" />
248
+ <Input value={name} onChange={(e) => setName(e.target.value)} placeholder="device name" />
249
+ <Input value={baseUrl} onChange={(e) => setBaseUrl(e.target.value)} placeholder="trusted device address" />
249
250
  <Input value={publicKey} onChange={(e) => setPublicKey(e.target.value)} placeholder="trusted public key" />
250
- <Button disabled={!name || !baseUrl || !publicKey || pair.isPending} onClick={() => pair.mutate()}>Pair</Button>
251
+ <Button disabled={!name || !baseUrl || !publicKey || pair.isPending} onClick={() => pair.mutate()}>Pair device</Button>
251
252
  {pair.data ? <OperationResult result={pair.data} successLabel="Peer pairing request completed" /> : null}
252
253
  </CardContent>
253
254
  </Card>
@@ -344,8 +345,15 @@ function SettingsPanel() {
344
345
  <DataPanel title="Server health" result={health.data}>
345
346
  {(data) => <HealthView data={data as Record<string, unknown>} />}
346
347
  </DataPanel>
347
- <DataPanel title="Host telemetry" result={sys.data}>
348
- {(data) => <StructuredView value={data} />}
348
+ <DataPanel title={mode === "basic" ? "Computer readiness" : "Host telemetry"} result={sys.data}>
349
+ {(data) => mode === "basic" ? (
350
+ <StatGrid stats={[
351
+ { label: "CPU", value: `${String((data as Record<string, unknown>).cpu_pct || "0")}%` },
352
+ { label: "Memory", value: `${String((data as Record<string, unknown>).ram_pct || "0")}%` },
353
+ { label: "GPU memory", value: `${String((data as Record<string, unknown>).gpu_mem_pct || "0")}%` },
354
+ { label: "Local status", value: "ready" },
355
+ ]} />
356
+ ) : <StructuredView value={data} />}
349
357
  </DataPanel>
350
358
  <DataPanel title="Brain storage" result={storage.data} className="xl:col-span-3">
351
359
  {(data) => <StorageView data={data as Record<string, unknown>} />}
@@ -356,7 +364,7 @@ function SettingsPanel() {
356
364
  <Card className="xl:col-span-3">
357
365
  <CardHeader>
358
366
  <CardTitle>.latticebrain portability</CardTitle>
359
- <CardDescription>Encrypted export, inspect, verify, dry-run restore, and confirmed restore use the Brain portability API.</CardDescription>
367
+ <CardDescription>Create an encrypted portable brain file, verify one, or preview a restore before applying it.</CardDescription>
360
368
  </CardHeader>
361
369
  <CardContent className="grid gap-3">
362
370
  <div className="grid gap-2 sm:grid-cols-[1fr_1fr]">
@@ -386,15 +394,15 @@ function SettingsPanel() {
386
394
  ))}
387
395
  </CardContent>
388
396
  </Card>
389
- <Card className="xl:col-span-3">
397
+ {mode !== "basic" ? <Card className="xl:col-span-3">
390
398
  <CardHeader>
391
- <CardTitle>Postgres scale mode</CardTitle>
392
- <CardDescription>Opt-in migration and Docker setup; SQLite remains the default.</CardDescription>
399
+ <CardTitle>Scale mode</CardTitle>
400
+ <CardDescription>Optional advanced storage. Local SQLite remains the default.</CardDescription>
393
401
  </CardHeader>
394
402
  <CardContent className="grid gap-3">
395
403
  <div className="grid gap-2 sm:grid-cols-[1fr_220px]">
396
- <Input value={dsn} onChange={(e) => setDsn(e.target.value)} placeholder="postgresql://user:pass@127.0.0.1:5432/lattice_brain" />
397
- <Input value={schema} onChange={(e) => setSchema(e.target.value)} placeholder="schema" />
404
+ <Input value={dsn} onChange={(e) => setDsn(e.target.value)} placeholder="Postgres connection string" />
405
+ <Input value={schema} onChange={(e) => setSchema(e.target.value)} placeholder="database schema" />
398
406
  </div>
399
407
  <div className="flex flex-wrap gap-2">
400
408
  <Button variant="outline" onClick={() => docker.mutate(false)} disabled={docker.isPending}>Docker plan</Button>
@@ -408,7 +416,7 @@ function SettingsPanel() {
408
416
  {docker.data ? <OperationResult result={docker.data} successLabel="Docker setup request completed" /> : null}
409
417
  {migration.data ? <OperationResult result={migration.data} successLabel="Migration plan completed" /> : null}
410
418
  </CardContent>
411
- </Card>
419
+ </Card> : null}
412
420
  <DataPanel title="Computer memory" result={comp.data} className="xl:col-span-3">
413
421
  {(data) => (
414
422
  <div className="space-y-3">
@@ -441,7 +449,16 @@ function PresenceView({ data }: { data: Record<string, unknown> }) {
441
449
  }
442
450
 
443
451
  function DeviceIdentityView({ data }: { data: Record<string, unknown> }) {
452
+ const mode = useAppStore((state) => state.mode);
444
453
  const publicKey = textValue(data.public_key, "");
454
+ if (mode === "basic") {
455
+ return (
456
+ <div className="space-y-3">
457
+ <StatusCard title="This Mac" status="trusted" detail="This device can participate in local workspace exchange when you pair another trusted device." />
458
+ <Badge variant="muted">{textValue(data.algorithm, "local identity")}</Badge>
459
+ </div>
460
+ );
461
+ }
445
462
  return (
446
463
  <div className="space-y-3">
447
464
  <div className="flex flex-wrap items-center gap-2">
@@ -458,15 +475,16 @@ function DeviceIdentityView({ data }: { data: Record<string, unknown> }) {
458
475
  }
459
476
 
460
477
  function HealthView({ data }: { data: Record<string, unknown> }) {
478
+ const mode = useAppStore((state) => state.mode);
461
479
  return (
462
480
  <div className="space-y-3">
463
481
  <StatGrid stats={[
464
482
  { label: "Status", value: data.status || data.ok || "reported" },
465
483
  { label: "Version", value: data.version || "not reported" },
466
484
  { label: "Mode", value: data.mode || data.environment || "local" },
467
- { label: "Port", value: data.port || data.backend_port || "configured" },
485
+ ...(mode === "basic" ? [] : [{ label: "Port", value: data.port || data.backend_port || "configured" }]),
468
486
  ]} />
469
- <StructuredView value={data} />
487
+ {mode === "basic" ? null : <StructuredView value={data} />}
470
488
  </div>
471
489
  );
472
490
  }
@@ -543,7 +561,7 @@ function HardeningView({ data }: { data: Record<string, unknown> }) {
543
561
  { label: "Backups", value: backup.count || backup.available || "reported" },
544
562
  ]} />
545
563
  <div className="grid gap-3 md:grid-cols-2">
546
- <StatusCard title="Startup" status={startup.network_exposed ? "network exposed" : "local-only"} detail={`Host ${textValue(startup.host, "127.0.0.1")} on port ${textValue(startup.port, "configured")}.`} />
564
+ <StatusCard title="Startup" status={startup.network_exposed ? "network exposed" : "local-only"} detail="Lattice starts locally by default and reports when network access is enabled." />
547
565
  <StatusCard title="Integrations" status={privacy.local_only_default === false ? "review required" : "opt-in"} detail="External integrations remain disabled until the user explicitly enables them." />
548
566
  <StatusCard title="Device identity" status={textValue(identity.algorithm || identity.fingerprint, "reported")} detail={textValue(identity.storage, "Stored locally and used for signed bundle exchange.")} />
549
567
  <StatusCard title="Permissions" status={permissions.destructive_restore_requires_confirmation === false ? "review required" : "guarded"} detail="Export, import, and destructive restore permissions are surfaced through admin status." />
@@ -569,6 +587,7 @@ function SecurityView({ data }: { data: Record<string, unknown> }) {
569
587
  }
570
588
 
571
589
  function AdminPanel() {
590
+ const mode = useAppStore((state) => state.mode);
572
591
  const summary = useQuery({ queryKey: ["adminSummary"], queryFn: latticeApi.adminSummary });
573
592
  const users = useQuery({ queryKey: ["adminUsers"], queryFn: latticeApi.adminUsers });
574
593
  const audit = useQuery({ queryKey: ["adminAudit"], queryFn: latticeApi.adminAudit });
@@ -577,6 +596,9 @@ function AdminPanel() {
577
596
  const hardening = useQuery({ queryKey: ["adminProductHardening"], queryFn: latticeApi.adminProductHardening });
578
597
  const security = useQuery({ queryKey: ["adminSecurity"], queryFn: latticeApi.adminSecurity });
579
598
  const vpc = useQuery({ queryKey: ["vpcStatus"], queryFn: latticeApi.vpcStatus });
599
+ if (mode !== "admin") {
600
+ return <ModeGate title="Admin controls" detail="Switch to Admin mode to review users, audit events, policies, security posture, and private networking diagnostics." target="admin" />;
601
+ }
580
602
  return (
581
603
  <div className="grid gap-4 xl:grid-cols-2">
582
604
  <DataPanel title="Admin summary" result={summary.data}>{(data) => <KeyValueList data={data as Record<string, unknown>} />}</DataPanel>
@@ -1,34 +1,31 @@
1
1
  import {
2
- Activity,
3
2
  Brain,
4
3
  Database,
5
4
  FolderInput,
6
5
  Library,
7
- MessageSquare,
8
- Network,
9
6
  Settings,
10
- Shield,
11
7
  Workflow,
12
- Zap,
13
8
  } from "lucide-react";
14
9
 
15
- export type PrimaryRoute = "brain" | "ask" | "capture" | "act" | "library" | "system";
10
+ export type PrimaryRoute = "brain" | "memory" | "capture" | "act" | "library" | "system";
16
11
 
17
12
  export const primaryRoutes = [
18
- { id: "brain", label: "Brain", icon: Brain, description: "Knowledge graph, retrieval, memory, provenance" },
19
- { id: "ask", label: "Ask", icon: MessageSquare, description: "Chat, conversations, context trace" },
20
- { id: "capture", label: "Capture", icon: FolderInput, description: "Documents, local folders, browser ingestion" },
21
- { id: "act", label: "Act", icon: Workflow, description: "Agents, workflows, approvals, hooks" },
22
- { id: "library", label: "Library", icon: Library, description: "Models, skills, MCP, templates" },
23
- { id: "system", label: "System", icon: Settings, description: "Account, workspaces, snapshots, network, admin" },
13
+ { id: "brain", label: "Brain", icon: Brain, description: "Talk with your living Brain" },
14
+ { id: "memory", label: "Memory", icon: Database, description: "Recall what your Brain remembers" },
15
+ { id: "capture", label: "Files", icon: FolderInput, description: "Bring in files, folders, and pages" },
16
+ { id: "act", label: "Automations", icon: Workflow, description: "Turn goals into supervised runs" },
17
+ { id: "library", label: "Models", icon: Library, description: "Choose the local model powering your Brain" },
18
+ { id: "system", label: "Settings", icon: Settings, description: "Keep your Brain safe and portable" },
24
19
  ] as const;
25
20
 
26
21
  export const routeAliases: Record<string, { primary: PrimaryRoute; tab?: string }> = {
27
- home: { primary: "brain", tab: "overview" },
22
+ home: { primary: "brain", tab: "conversation" },
23
+ onboarding: { primary: "system", tab: "account" },
28
24
  "knowledge-graph": { primary: "brain", tab: "graph" },
29
- "hybrid-search": { primary: "brain", tab: "search" },
30
- memory: { primary: "brain", tab: "memory" },
31
- chat: { primary: "ask", tab: "chat" },
25
+ "hybrid-search": { primary: "brain", tab: "knowledge" },
26
+ memory: { primary: "memory", tab: "memory" },
27
+ ask: { primary: "brain", tab: "conversation" },
28
+ chat: { primary: "brain", tab: "conversation" },
32
29
  files: { primary: "capture", tab: "files" },
33
30
  pipeline: { primary: "capture", tab: "pipeline" },
34
31
  "my-computer": { primary: "capture", tab: "local" },
@@ -58,17 +55,11 @@ export const routeAliases: Record<string, { primary: PrimaryRoute; tab?: string
58
55
 
59
56
  export const commandRoutes = [
60
57
  { key: "brain", label: "Brain", icon: Brain },
61
- { key: "knowledge-graph", label: "Knowledge Graph", icon: Network },
62
- { key: "hybrid-search", label: "Hybrid Search", icon: Zap },
63
58
  { key: "memory", label: "Memory", icon: Database },
64
- { key: "chat", label: "Ask", icon: MessageSquare },
65
- { key: "files", label: "Capture Files", icon: FolderInput },
66
- { key: "agents", label: "Agents", icon: Workflow },
67
- { key: "workflows", label: "Workflows", icon: Workflow },
59
+ { key: "files", label: "Files", icon: FolderInput },
60
+ { key: "workflows", label: "Automations", icon: Workflow },
68
61
  { key: "models", label: "Models", icon: Library },
69
- { key: "network", label: "Brain Network", icon: Network },
70
- { key: "activity", label: "Activity", icon: Activity },
71
- { key: "admin/security", label: "Security", icon: Shield },
62
+ { key: "settings", label: "Settings", icon: Settings },
72
63
  ];
73
64
 
74
65
  export function parseHash() {
@@ -30,10 +30,17 @@ function readMode(): WorkspaceMode {
30
30
  return "basic";
31
31
  }
32
32
 
33
+ function readWorkspaceId(): string | null {
34
+ try {
35
+ return localStorage.getItem("lattice.workspace") || null;
36
+ } catch {}
37
+ return null;
38
+ }
39
+
33
40
  export const useAppStore = create<AppState>((set) => ({
34
41
  theme: readTheme(),
35
42
  mode: readMode(),
36
- workspaceId: null,
43
+ workspaceId: readWorkspaceId(),
37
44
  apiBase: null,
38
45
  setTheme: (theme) => {
39
46
  document.documentElement.dataset.theme = theme;