ltcai 4.0.0 → 4.0.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 (108) hide show
  1. package/README.md +37 -33
  2. package/docs/CHANGELOG.md +64 -0
  3. package/docs/REALTIME_COLLABORATION.md +3 -3
  4. package/docs/V3_FRONTEND.md +9 -8
  5. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +86 -43
  6. package/docs/kg-schema.md +6 -2
  7. package/docs/spec-vs-impl.md +10 -10
  8. package/kg_schema.py +2 -603
  9. package/knowledge_graph.py +37 -4958
  10. package/latticeai/__init__.py +1 -1
  11. package/latticeai/api/admin.py +15 -16
  12. package/latticeai/api/agents.py +13 -6
  13. package/latticeai/api/auth.py +19 -11
  14. package/latticeai/api/invitations.py +100 -0
  15. package/latticeai/api/knowledge_graph.py +4 -11
  16. package/latticeai/api/plugins.py +3 -6
  17. package/latticeai/api/realtime.py +4 -7
  18. package/latticeai/api/static_routes.py +9 -12
  19. package/latticeai/api/ui_redirects.py +26 -0
  20. package/latticeai/api/workflow_designer.py +39 -6
  21. package/latticeai/api/workspace.py +24 -10
  22. package/latticeai/app_factory.py +88 -17
  23. package/latticeai/brain/_kg_common.py +1123 -0
  24. package/latticeai/brain/discovery.py +1455 -0
  25. package/latticeai/brain/documents.py +218 -0
  26. package/latticeai/brain/ingest.py +644 -0
  27. package/latticeai/brain/projection.py +561 -0
  28. package/latticeai/brain/provenance.py +401 -0
  29. package/latticeai/brain/retrieval.py +1316 -0
  30. package/latticeai/brain/schema.py +640 -0
  31. package/latticeai/brain/store.py +216 -0
  32. package/latticeai/brain/write_master.py +225 -0
  33. package/latticeai/core/invitations.py +131 -0
  34. package/latticeai/core/marketplace.py +1 -1
  35. package/latticeai/core/multi_agent.py +1 -1
  36. package/latticeai/core/policy.py +54 -0
  37. package/latticeai/core/realtime.py +65 -44
  38. package/latticeai/core/sessions.py +31 -5
  39. package/latticeai/core/users.py +147 -0
  40. package/latticeai/core/workspace_os.py +420 -20
  41. package/latticeai/services/agent_runtime.py +242 -4
  42. package/latticeai/services/run_executor.py +328 -0
  43. package/latticeai/services/workspace_service.py +27 -19
  44. package/package.json +2 -14
  45. package/scripts/lint_v3.mjs +23 -0
  46. package/static/v3/asset-manifest.json +21 -14
  47. package/static/v3/js/{app.356e6452.js → app.c5c80c46.js} +1 -1
  48. package/static/v3/js/core/{api.7a308b89.js → api.ba0fbf14.js} +58 -1
  49. package/static/v3/js/core/api.js +57 -0
  50. package/static/v3/js/core/i18n.880e1fec.js +575 -0
  51. package/static/v3/js/core/i18n.js +575 -0
  52. package/static/v3/js/core/routes.37522821.js +101 -0
  53. package/static/v3/js/core/routes.js +71 -63
  54. package/static/v3/js/core/{shell.a1657f20.js → shell.e3f6bbfa.js} +67 -38
  55. package/static/v3/js/core/shell.js +65 -36
  56. package/static/v3/js/core/{store.204a08b2.js → store.7b2aa044.js} +10 -0
  57. package/static/v3/js/core/store.js +10 -0
  58. package/static/v3/js/views/account.eff40715.js +143 -0
  59. package/static/v3/js/views/account.js +143 -0
  60. package/static/v3/js/views/activity.0d271ef9.js +67 -0
  61. package/static/v3/js/views/activity.js +67 -0
  62. package/static/v3/js/views/{admin-users.03bac88c.js → admin-users.f7ac7b43.js} +4 -6
  63. package/static/v3/js/views/admin-users.js +4 -6
  64. package/static/v3/js/views/{agents.014d0b74.js → agents.17c5288d.js} +35 -12
  65. package/static/v3/js/views/agents.js +35 -12
  66. package/static/v3/js/views/{chat.e6dd7dd0.js → chat.e250e2cc.js} +23 -0
  67. package/static/v3/js/views/chat.js +23 -0
  68. package/static/v3/js/views/{knowledge-graph.5e40cbeb.js → knowledge-graph.4d09c537.js} +27 -7
  69. package/static/v3/js/views/knowledge-graph.js +27 -7
  70. package/static/v3/js/views/network.52a4f181.js +97 -0
  71. package/static/v3/js/views/network.js +97 -0
  72. package/static/v3/js/views/{planning.9ac3e313.js → planning.4876fd77.js} +26 -5
  73. package/static/v3/js/views/planning.js +26 -5
  74. package/static/v3/js/views/runs.b63b2afa.js +144 -0
  75. package/static/v3/js/views/runs.js +144 -0
  76. package/static/v3/js/views/{settings.8631fa5e.js → settings.b7140634.js} +7 -8
  77. package/static/v3/js/views/settings.js +7 -8
  78. package/static/v3/js/views/snapshots.6f5db095.js +135 -0
  79. package/static/v3/js/views/snapshots.js +135 -0
  80. package/static/v3/js/views/{workflows.26c57290.js → workflows.7752225a.js} +87 -2
  81. package/static/v3/js/views/workflows.js +87 -2
  82. package/static/v3/js/views/workspace-admin.c466029b.js +156 -0
  83. package/static/v3/js/views/workspace-admin.js +156 -0
  84. package/static/account.html +0 -113
  85. package/static/activity.html +0 -73
  86. package/static/admin.html +0 -486
  87. package/static/agents.html +0 -139
  88. package/static/chat.html +0 -841
  89. package/static/css/reference/account.css +0 -439
  90. package/static/css/reference/admin.css +0 -610
  91. package/static/css/reference/base.css +0 -1661
  92. package/static/css/reference/chat.css +0 -4623
  93. package/static/css/reference/graph.css +0 -1016
  94. package/static/css/responsive.css +0 -861
  95. package/static/graph.html +0 -122
  96. package/static/platform.css +0 -104
  97. package/static/plugins.html +0 -136
  98. package/static/scripts/account.js +0 -238
  99. package/static/scripts/admin.js +0 -1614
  100. package/static/scripts/chat.js +0 -5081
  101. package/static/scripts/graph.js +0 -1804
  102. package/static/scripts/platform.js +0 -64
  103. package/static/scripts/ux.js +0 -167
  104. package/static/scripts/workspace.js +0 -948
  105. package/static/v3/js/core/routes.7222343d.js +0 -93
  106. package/static/workflows.html +0 -146
  107. package/static/workspace.css +0 -1121
  108. package/static/workspace.html +0 -357
@@ -0,0 +1,101 @@
1
+ /* ============================================================================
2
+ * Lattice AI v3 — Information architecture (single source of truth)
3
+ *
4
+ * One declarative table drives the nav rail, command palette, router,
5
+ * breadcrumbs, and lazy view loading. Labels are translated at render time.
6
+ * ========================================================================== */
7
+
8
+ import { t } from "./i18n.880e1fec.js";
9
+
10
+ export const MODE_RANK = { basic: 0, advanced: 1, admin: 2 };
11
+
12
+ export const GROUPS = [
13
+ { id: "brain", labelKey: "group.brain" },
14
+ { id: "ask", labelKey: "group.ask" },
15
+ { id: "capture", labelKey: "group.capture" },
16
+ { id: "act", labelKey: "group.act" },
17
+ { id: "library", labelKey: "group.library" },
18
+ { id: "system", labelKey: "group.system" },
19
+ { id: "admin", labelKey: "group.admin", adminOnly: true },
20
+ ];
21
+
22
+ function r(key, labelKey, icon, group, minMode, view, titleKey, descKey, extra = {}) {
23
+ return { key, labelKey, icon, group, minMode, view, titleKey, descKey, ...extra };
24
+ }
25
+
26
+ export const ROUTES = [
27
+ r("home", "route.home.label", "layout-dashboard", "system", "basic", "home", "route.home.title", "route.home.desc"),
28
+ r("account", "route.account.label", "user-circle", "system", "basic", "account", "route.account.title", "route.account.desc"),
29
+ r("chat", "route.chat.label", "message-2", "ask", "basic", "chat", "route.chat.title", "route.chat.desc"),
30
+ r("files", "route.files.label", "folders", "capture", "basic", "files", "route.files.title", "route.files.desc"),
31
+
32
+ r("hybrid-search", "route.hybridSearch.label", "arrows-join", "brain", "basic", "hybrid-search", "route.hybridSearch.title", "route.hybridSearch.desc"),
33
+ r("knowledge-graph", "route.knowledgeGraph.label", "chart-dots-3", "brain", "basic", "knowledge-graph", "route.knowledgeGraph.title", "route.knowledgeGraph.desc"),
34
+ r("memory", "route.memory.label", "brain", "brain", "basic", "memory", "route.memory.title", "route.memory.desc"),
35
+
36
+ r("models", "route.models.label", "cpu", "library", "basic", "models", "route.models.title", "route.models.desc"),
37
+ r("agents", "route.agents.label", "robot", "act", "advanced", "agents", "route.agents.title", "route.agents.desc"),
38
+ r("runs", "route.runs.label", "progress-check", "act", "advanced", "runs", "route.runs.title", "route.runs.desc"),
39
+ r("workflows", "route.workflows.label", "sitemap", "act", "advanced", "workflows", "route.workflows.title", "route.workflows.desc"),
40
+
41
+ r("skills", "route.skills.label", "puzzle", "library", "advanced", "skills", "route.skills.title", "route.skills.desc"),
42
+ r("hooks", "route.hooks.label", "webhook", "act", "advanced", "hooks", "route.hooks.title", "route.hooks.desc"),
43
+ r("mcp", "route.mcp.label", "plug-connected", "library", "advanced", "mcp", "route.mcp.title", "route.mcp.desc"),
44
+
45
+ r("workspace-admin", "route.workspaceAdmin.label", "building-community", "system", "basic", "workspace-admin", "route.workspaceAdmin.title", "route.workspaceAdmin.desc"),
46
+ r("snapshots", "route.snapshots.label", "history", "system", "basic", "snapshots", "route.snapshots.title", "route.snapshots.desc"),
47
+ r("activity", "route.activity.label", "activity", "system", "basic", "activity", "route.activity.title", "route.activity.desc"),
48
+ r("network", "route.network.label", "network", "system", "advanced", "network", "route.network.title", "route.network.desc"),
49
+ r("settings", "route.settings.label", "settings", "system", "basic", "settings", "route.settings.title", "route.settings.desc"),
50
+
51
+ r("pipeline", "route.pipeline.label", "git-branch", "capture", "advanced", "pipeline", "route.pipeline.title", "route.pipeline.desc", { hidden: true }),
52
+ r("planning", "route.planning.label", "target-arrow", "act", "advanced", "planning", "route.planning.title", "route.planning.desc", { hidden: true }),
53
+ r("my-computer", "route.myComputer.label", "device-desktop-analytics", "system", "advanced", "my-computer", "route.myComputer.title", "route.myComputer.desc", { hidden: true }),
54
+ r("marketplace", "route.marketplace.label", "building-store", "library", "advanced", "marketplace", "route.marketplace.title", "route.marketplace.desc", { hidden: true }),
55
+ r("tools", "route.tools.label", "tools", "act", "advanced", "tools", "route.tools.title", "route.tools.desc", { hidden: true }),
56
+
57
+ r("admin/users", "route.adminUsers.label", "users", "admin", "admin", "admin-users", "route.adminUsers.title", "route.adminUsers.desc", { admin: true }),
58
+ r("admin/permissions", "route.adminPermissions.label", "key", "admin", "admin", "admin-permissions", "route.adminPermissions.title", "route.adminPermissions.desc", { admin: true }),
59
+ r("admin/audit", "route.adminAudit.label", "report-search", "admin", "admin", "admin-audit", "route.adminAudit.title", "route.adminAudit.desc", { admin: true }),
60
+ r("admin/security", "route.adminSecurity.label", "shield-check", "admin", "admin", "admin-security", "route.adminSecurity.title", "route.adminSecurity.desc", { admin: true }),
61
+ r("admin/policies", "route.adminPolicies.label", "file-certificate", "admin", "admin", "admin-policies", "route.adminPolicies.title", "route.adminPolicies.desc", { admin: true }),
62
+ r("admin/private-vpc", "route.adminVpc.label", "cloud-lock", "admin", "admin", "admin-private-vpc", "route.adminVpc.title", "route.adminVpc.desc", { admin: true }),
63
+ ];
64
+
65
+ export const ROUTE_BY_KEY = Object.fromEntries(ROUTES.map((route) => [route.key, route]));
66
+
67
+ export function groupLabel(group) {
68
+ return t(group.labelKey || group.id);
69
+ }
70
+
71
+ export function localizeRoute(route) {
72
+ if (!route) return route;
73
+ return {
74
+ ...route,
75
+ label: t(route.labelKey),
76
+ title: t(route.titleKey || route.labelKey),
77
+ desc: t(route.descKey || ""),
78
+ };
79
+ }
80
+
81
+ export function visibleRoutes(mode) {
82
+ const rank = MODE_RANK[mode] ?? 0;
83
+ return ROUTES.filter((route) => {
84
+ if (route.hidden) return false;
85
+ if (route.admin) return mode === "admin";
86
+ return (MODE_RANK[route.minMode] ?? 0) <= rank;
87
+ }).map(localizeRoute);
88
+ }
89
+
90
+ const cache = new Map();
91
+ function assetUrl(key, fallback) {
92
+ const manifest = window.__LT_ASSET_MANIFEST__;
93
+ return (manifest && manifest.assets && manifest.assets[key]) || fallback;
94
+ }
95
+
96
+ export async function loadView(view) {
97
+ if (cache.has(view)) return cache.get(view);
98
+ const mod = await import(assetUrl(`static/v3/js/views/${view}.js`, `../views/${view}.js`));
99
+ cache.set(view, mod);
100
+ return mod;
101
+ }
@@ -1,84 +1,92 @@
1
1
  /* ============================================================================
2
2
  * Lattice AI v3 — Information architecture (single source of truth)
3
3
  *
4
- * One declarative table drives the nav rail, the command palette, the router,
5
- * breadcrumbs, and lazy view loading. Mode gating (Basic < Advanced < Admin)
6
- * and the Admin section live here so the whole shell stays consistent.
4
+ * One declarative table drives the nav rail, command palette, router,
5
+ * breadcrumbs, and lazy view loading. Labels are translated at render time.
7
6
  * ========================================================================== */
8
7
 
8
+ import { t } from "./i18n.js";
9
+
9
10
  export const MODE_RANK = { basic: 0, advanced: 1, admin: 2 };
10
11
 
11
- /** Nav groups in display order. */
12
12
  export const GROUPS = [
13
- { id: "brain", label: "Brain" },
14
- { id: "ask", label: "Ask" },
15
- { id: "capture", label: "Capture" },
16
- { id: "act", label: "Act" },
17
- { id: "library", label: "Library" },
18
- { id: "system", label: "System" },
19
- { id: "admin", label: "Administration", adminOnly: true },
13
+ { id: "brain", labelKey: "group.brain" },
14
+ { id: "ask", labelKey: "group.ask" },
15
+ { id: "capture", labelKey: "group.capture" },
16
+ { id: "act", labelKey: "group.act" },
17
+ { id: "library", labelKey: "group.library" },
18
+ { id: "system", labelKey: "group.system" },
19
+ { id: "admin", labelKey: "group.admin", adminOnly: true },
20
20
  ];
21
21
 
22
- /**
23
- * Route table. `minMode` = lowest mode in which the item appears in the rail
24
- * (deep-links still resolve). `view` = module basename under js/views/.
25
- */
22
+ function r(key, labelKey, icon, group, minMode, view, titleKey, descKey, extra = {}) {
23
+ return { key, labelKey, icon, group, minMode, view, titleKey, descKey, ...extra };
24
+ }
25
+
26
26
  export const ROUTES = [
27
- // Workspace
28
- { key: "home", label: "Overview", icon: "layout-dashboard", group: "system", minMode: "basic", view: "home", title: "Overview", desc: "Your digital brain at a glance." },
29
- { key: "chat", label: "Chat", icon: "message-2", group: "ask", minMode: "basic", view: "chat", title: "Chat", desc: "Grounded conversation over your brain — memories, knowledge, and notes assembled with provenance." },
30
-
31
- // Data
32
- { key: "files", label: "Files", icon: "folders", group: "capture", minMode: "basic", view: "files", title: "Files", desc: "Connected sources and indexed documents." },
33
-
34
- // Retrieval (the product identity)
35
- { key: "hybrid-search", label: "Search", icon: "arrows-join", group: "brain", minMode: "basic", view: "hybrid-search", title: "Hybrid Search", desc: "Graph structure fused with vector similarity." },
36
- { key: "knowledge-graph", label: "Knowledge Graph", icon: "chart-dots-3", group: "brain", minMode: "basic", view: "knowledge-graph", title: "Knowledge Graph", desc: "Your digital brain — every source converges here. Explore, ingest, and export." },
37
- { key: "memory", label: "Memory", icon: "brain", group: "brain", minMode: "basic", view: "memory", title: "Memory", desc: "Long-term workspace, project, agent, and conversation memory." },
38
-
39
- // Compute
40
- { key: "models", label: "Models", icon: "cpu", group: "library", minMode: "basic", view: "models", title: "Models", desc: "Local MLX models and embeddings." },
41
- { key: "agents", label: "Agents", icon: "robot", group: "act", minMode: "advanced", view: "agents", title: "Agents", desc: "Multi-agent roles, runs, and handoffs." },
42
- { key: "workflows", label: "Workflows", icon: "sitemap", group: "act", minMode: "advanced", view: "workflows", title: "Workflow Agents", desc: "Trigger → agent chain → tools → memory → result." },
43
-
44
- // Platform (the agent ecosystem)
45
- { key: "skills", label: "Skills", icon: "puzzle", group: "library", minMode: "advanced", view: "skills", title: "Skills", desc: "Install, enable, and manage skills." },
46
- { key: "hooks", label: "Hooks", icon: "webhook", group: "act", minMode: "advanced", view: "hooks", title: "Hooks", desc: "Lifecycle hooks across runs, tools, and workflows." },
47
- { key: "mcp", label: "MCP", icon: "plug-connected", group: "library", minMode: "advanced", view: "mcp", title: "MCP Manager", desc: "Connected MCP servers, available tools, and health." },
48
-
49
- // System
50
- { key: "settings", label: "Settings", icon: "settings", group: "system", minMode: "basic", view: "settings", title: "Settings", desc: "Appearance, workspace, and integrations." },
51
-
52
- // Deep-linkable legacy/experimental surfaces. They remain renderable for
53
- // compatibility, but are not promoted in the production navigation.
54
- { key: "pipeline", label: "Pipeline", icon: "git-branch", group: "capture", minMode: "advanced", view: "pipeline", title: "Pipeline", desc: "Ingest, embed, and graph-build flows.", hidden: true },
55
- { key: "planning", label: "Planning", icon: "target-arrow", group: "act", minMode: "advanced", view: "planning", title: "Autonomous Planning", desc: "Goal → plan → execute → review → replan.", hidden: true },
56
- { key: "my-computer", label: "My Computer", icon: "device-desktop-analytics", group: "system", minMode: "advanced", view: "my-computer", title: "My Computer", desc: "Local hardware, memory, and runtime.", hidden: true },
57
- { key: "marketplace", label: "Marketplace", icon: "building-store", group: "library", minMode: "advanced", view: "marketplace", title: "Marketplace", desc: "Agent templates, agents, plugins, and skills.", hidden: true },
58
- { key: "tools", label: "Tools", icon: "tools", group: "act", minMode: "advanced", view: "tools", title: "Tool Registry", desc: "Local, workspace, and MCP tools with governance.", hidden: true },
59
-
60
- // Admin
61
- { key: "admin/users", label: "Users", icon: "users", group: "admin", minMode: "admin", view: "admin-users", title: "Users", desc: "Workspace members and access.", admin: true },
62
- { key: "admin/permissions", label: "Permissions", icon: "key", group: "admin", minMode: "admin", view: "admin-permissions", title: "Permissions", desc: "Roles and capability mapping.", admin: true },
63
- { key: "admin/audit", label: "Audit Logs", icon: "report-search", group: "admin", minMode: "admin", view: "admin-audit", title: "Audit Logs", desc: "Activity and access trail.", admin: true },
64
- { key: "admin/security", label: "Security", icon: "shield-check", group: "admin", minMode: "admin", view: "admin-security", title: "Security", desc: "Sensitive-data signals and DLP.", admin: true },
65
- { key: "admin/policies", label: "Policies", icon: "file-certificate", group: "admin", minMode: "admin", view: "admin-policies", title: "Policies", desc: "Governance and enforcement.", admin: true },
66
- { key: "admin/private-vpc", label: "Private VPC", icon: "cloud-lock", group: "admin", minMode: "admin", view: "admin-private-vpc", title: "Private VPC", desc: "Network isolation and peering.", admin: true },
27
+ r("home", "route.home.label", "layout-dashboard", "system", "basic", "home", "route.home.title", "route.home.desc"),
28
+ r("account", "route.account.label", "user-circle", "system", "basic", "account", "route.account.title", "route.account.desc"),
29
+ r("chat", "route.chat.label", "message-2", "ask", "basic", "chat", "route.chat.title", "route.chat.desc"),
30
+ r("files", "route.files.label", "folders", "capture", "basic", "files", "route.files.title", "route.files.desc"),
31
+
32
+ r("hybrid-search", "route.hybridSearch.label", "arrows-join", "brain", "basic", "hybrid-search", "route.hybridSearch.title", "route.hybridSearch.desc"),
33
+ r("knowledge-graph", "route.knowledgeGraph.label", "chart-dots-3", "brain", "basic", "knowledge-graph", "route.knowledgeGraph.title", "route.knowledgeGraph.desc"),
34
+ r("memory", "route.memory.label", "brain", "brain", "basic", "memory", "route.memory.title", "route.memory.desc"),
35
+
36
+ r("models", "route.models.label", "cpu", "library", "basic", "models", "route.models.title", "route.models.desc"),
37
+ r("agents", "route.agents.label", "robot", "act", "advanced", "agents", "route.agents.title", "route.agents.desc"),
38
+ r("runs", "route.runs.label", "progress-check", "act", "advanced", "runs", "route.runs.title", "route.runs.desc"),
39
+ r("workflows", "route.workflows.label", "sitemap", "act", "advanced", "workflows", "route.workflows.title", "route.workflows.desc"),
40
+
41
+ r("skills", "route.skills.label", "puzzle", "library", "advanced", "skills", "route.skills.title", "route.skills.desc"),
42
+ r("hooks", "route.hooks.label", "webhook", "act", "advanced", "hooks", "route.hooks.title", "route.hooks.desc"),
43
+ r("mcp", "route.mcp.label", "plug-connected", "library", "advanced", "mcp", "route.mcp.title", "route.mcp.desc"),
44
+
45
+ r("workspace-admin", "route.workspaceAdmin.label", "building-community", "system", "basic", "workspace-admin", "route.workspaceAdmin.title", "route.workspaceAdmin.desc"),
46
+ r("snapshots", "route.snapshots.label", "history", "system", "basic", "snapshots", "route.snapshots.title", "route.snapshots.desc"),
47
+ r("activity", "route.activity.label", "activity", "system", "basic", "activity", "route.activity.title", "route.activity.desc"),
48
+ r("network", "route.network.label", "network", "system", "advanced", "network", "route.network.title", "route.network.desc"),
49
+ r("settings", "route.settings.label", "settings", "system", "basic", "settings", "route.settings.title", "route.settings.desc"),
50
+
51
+ r("pipeline", "route.pipeline.label", "git-branch", "capture", "advanced", "pipeline", "route.pipeline.title", "route.pipeline.desc", { hidden: true }),
52
+ r("planning", "route.planning.label", "target-arrow", "act", "advanced", "planning", "route.planning.title", "route.planning.desc", { hidden: true }),
53
+ r("my-computer", "route.myComputer.label", "device-desktop-analytics", "system", "advanced", "my-computer", "route.myComputer.title", "route.myComputer.desc", { hidden: true }),
54
+ r("marketplace", "route.marketplace.label", "building-store", "library", "advanced", "marketplace", "route.marketplace.title", "route.marketplace.desc", { hidden: true }),
55
+ r("tools", "route.tools.label", "tools", "act", "advanced", "tools", "route.tools.title", "route.tools.desc", { hidden: true }),
56
+
57
+ r("admin/users", "route.adminUsers.label", "users", "admin", "admin", "admin-users", "route.adminUsers.title", "route.adminUsers.desc", { admin: true }),
58
+ r("admin/permissions", "route.adminPermissions.label", "key", "admin", "admin", "admin-permissions", "route.adminPermissions.title", "route.adminPermissions.desc", { admin: true }),
59
+ r("admin/audit", "route.adminAudit.label", "report-search", "admin", "admin", "admin-audit", "route.adminAudit.title", "route.adminAudit.desc", { admin: true }),
60
+ r("admin/security", "route.adminSecurity.label", "shield-check", "admin", "admin", "admin-security", "route.adminSecurity.title", "route.adminSecurity.desc", { admin: true }),
61
+ r("admin/policies", "route.adminPolicies.label", "file-certificate", "admin", "admin", "admin-policies", "route.adminPolicies.title", "route.adminPolicies.desc", { admin: true }),
62
+ r("admin/private-vpc", "route.adminVpc.label", "cloud-lock", "admin", "admin", "admin-private-vpc", "route.adminVpc.title", "route.adminVpc.desc", { admin: true }),
67
63
  ];
68
64
 
69
- export const ROUTE_BY_KEY = Object.fromEntries(ROUTES.map((r) => [r.key, r]));
65
+ export const ROUTE_BY_KEY = Object.fromEntries(ROUTES.map((route) => [route.key, route]));
66
+
67
+ export function groupLabel(group) {
68
+ return t(group.labelKey || group.id);
69
+ }
70
+
71
+ export function localizeRoute(route) {
72
+ if (!route) return route;
73
+ return {
74
+ ...route,
75
+ label: t(route.labelKey),
76
+ title: t(route.titleKey || route.labelKey),
77
+ desc: t(route.descKey || ""),
78
+ };
79
+ }
70
80
 
71
- /** Routes visible in the rail for a given mode. */
72
81
  export function visibleRoutes(mode) {
73
82
  const rank = MODE_RANK[mode] ?? 0;
74
- return ROUTES.filter((r) => {
75
- if (r.hidden) return false;
76
- if (r.admin) return mode === "admin";
77
- return (MODE_RANK[r.minMode] ?? 0) <= rank;
78
- });
83
+ return ROUTES.filter((route) => {
84
+ if (route.hidden) return false;
85
+ if (route.admin) return mode === "admin";
86
+ return (MODE_RANK[route.minMode] ?? 0) <= rank;
87
+ }).map(localizeRoute);
79
88
  }
80
89
 
81
- /** Lazy-load a view module by basename. Cached. */
82
90
  const cache = new Map();
83
91
  function assetUrl(key, fallback) {
84
92
  const manifest = window.__LT_ASSET_MANIFEST__;
@@ -6,16 +6,17 @@
6
6
  * ========================================================================== */
7
7
 
8
8
  import { h, icon, $, $$ } from "./dom.a2773eb0.js";
9
- import { store } from "./store.204a08b2.js";
10
- import { api } from "./api.7a308b89.js";
9
+ import { store } from "./store.7b2aa044.js";
10
+ import { api } from "./api.ba0fbf14.js";
11
11
  import * as c from "./components.f25b3b93.js";
12
12
  import { createRouter } from "./router.584570f2.js";
13
- import { GROUPS, ROUTES, ROUTE_BY_KEY, MODE_RANK, visibleRoutes, loadView } from "./routes.7222343d.js";
13
+ import { GROUPS, ROUTE_BY_KEY, MODE_RANK, visibleRoutes, loadView, groupLabel, localizeRoute } from "./routes.37522821.js";
14
+ import { setI18nLanguage, t } from "./i18n.880e1fec.js";
14
15
 
15
16
  const MODES = [
16
- { key: "basic", label: "Basic", icon: "circle" },
17
- { key: "advanced", label: "Advanced", icon: "circles" },
18
- { key: "admin", label: "Admin", icon: "shield-half" },
17
+ { key: "basic", labelKey: "shell.mode.basic", icon: "circle" },
18
+ { key: "advanced", labelKey: "shell.mode.advanced", icon: "circles" },
19
+ { key: "admin", labelKey: "shell.mode.admin", icon: "shield-half" },
19
20
  ];
20
21
 
21
22
  const ctxBase = { h, icon, api, store, c };
@@ -27,7 +28,7 @@ let currentRoute = null;
27
28
  export function boot(rootEl) {
28
29
  rootEl.classList.add("lt3-app");
29
30
  rootEl.append(
30
- h("a.lt3-skip", { href: "#lt3-view" }, "Skip to content"),
31
+ h("a.lt3-skip", { href: "#lt3-view" }, t("shell.skip")),
31
32
  h("div.lt3-rail__scrim", { on: { click: closeDrawer } }),
32
33
  buildRail(),
33
34
  buildMain(),
@@ -46,19 +47,19 @@ export function boot(rootEl) {
46
47
 
47
48
  /* ── Rail ────────────────────────────────────────────────────────────────── */
48
49
  function buildRail() {
49
- return h("aside.lt3-rail", { id: "lt3-rail", "aria-label": "Primary" },
50
+ return h("aside.lt3-rail", { id: "lt3-rail", "aria-label": t("shell.primary") },
50
51
  h("div.lt3-rail__brand",
51
52
  h("div.lt3-rail__logo", { html: latticeMark() }),
52
- h("div.lt3-rail__word", h("b", "Lattice AI"), h("small", "Private runtime")),
53
- h("button.lt3-iconbtn.lt3-iconbtn--sm.lt3-rail__close", { "aria-label": "Close menu", on: { click: closeDrawer } }, icon("x")),
53
+ h("div.lt3-rail__word", h("b", "Lattice AI"), h("small", t("shell.privateRuntime"))),
54
+ h("button.lt3-iconbtn.lt3-iconbtn--sm.lt3-rail__close", { "aria-label": t("shell.closeMenu"), on: { click: closeDrawer } }, icon("x")),
54
55
  ),
55
56
  h("div.lt3-rail__scope", { id: "lt3-scope" }),
56
57
  h("nav.lt3-rail__nav", { id: "lt3-nav", "aria-label": "Sections" }),
57
58
  h("div.lt3-rail__foot",
58
59
  h("div.lt3-rail__status", { id: "lt3-rail-status" }),
59
60
  h("div.lt3-rail__foot-row",
60
- h("button.lt3-rail__user", { id: "lt3-user", "aria-label": "Account", on: { click: () => router.navigate("settings") } }),
61
- h("button.lt3-iconbtn", { id: "lt3-theme", "aria-label": "Toggle theme", title: "Toggle theme", on: { click: () => store.toggleTheme() } }, icon("moon")),
61
+ h("button.lt3-rail__user", { id: "lt3-user", "aria-label": t("shell.account"), on: { click: () => router.navigate("account") } }),
62
+ h("button.lt3-iconbtn", { id: "lt3-theme", "aria-label": t("shell.toggleTheme"), title: t("shell.toggleTheme"), on: { click: () => store.toggleTheme() } }, icon("moon")),
62
63
  ),
63
64
  ),
64
65
  );
@@ -73,7 +74,7 @@ function renderNav() {
73
74
  const items = routes.filter((r) => r.group === group.id);
74
75
  if (!items.length) continue;
75
76
  const groupEl = h("div.lt3-navgroup",
76
- h("div.lt3-navgroup__label", group.label),
77
+ h("div.lt3-navgroup__label", groupLabel(group)),
77
78
  items.map((r) => navItem(r)),
78
79
  );
79
80
  nav.append(groupEl);
@@ -109,7 +110,7 @@ function renderScope() {
109
110
  els.scope.replaceChildren(
110
111
  h("button.lt3-scope", { "aria-haspopup": "listbox", on: { click: openScopeMenu } },
111
112
  h("div.lt3-scope__icon", icon(ws.type === "organization" ? "building-community" : "user")),
112
- h("div.lt3-scope__meta", h("b", ws.name), h("small", `${ws.type} · ${ws.your_role || "member"}`)),
113
+ h("div.lt3-scope__meta", h("b", ws.name), h("small", `${ws.type} · ${ws.your_role || t("shell.member")}`)),
113
114
  icon("selector"),
114
115
  ),
115
116
  );
@@ -120,7 +121,7 @@ function renderUser() {
120
121
  const initials = (u.nickname || u.email || "U").slice(0, 2);
121
122
  els.user.replaceChildren(
122
123
  h("span.lt3-avatar", initials),
123
- h("div.lt3-rail__user-meta", h("b", u.nickname || u.email || "You"), h("small", u.role || "local")),
124
+ h("div.lt3-rail__user-meta", h("b", u.nickname || u.email || t("shell.you")), h("small", u.role || t("shell.local"))),
124
125
  );
125
126
  }
126
127
 
@@ -134,17 +135,17 @@ function updateThemeIcon() {
134
135
  function buildMain() {
135
136
  return h("div.lt3-main",
136
137
  h("header.lt3-topbar",
137
- h("button.lt3-iconbtn.lt3-topbar__menu", { "aria-label": "Open menu", on: { click: openDrawer } }, icon("menu-2")),
138
+ h("button.lt3-iconbtn.lt3-topbar__menu", { "aria-label": t("shell.openMenu"), on: { click: openDrawer } }, icon("menu-2")),
138
139
  h("div.lt3-topbar__crumbs", { id: "lt3-crumbs" }),
139
140
  h("div.lt3-spacer"),
140
- h("button.lt3-cmd-trigger", { "aria-label": "Search and commands", on: { click: openPalette } },
141
- icon("search"), h("span", "Search & commands"), h("span.lt3-kbd", "⌘K")),
141
+ h("button.lt3-cmd-trigger", { "aria-label": t("shell.searchCommands"), on: { click: openPalette } },
142
+ icon("search"), h("span", { id: "lt3-cmd-text" }, t("shell.searchCommands")), h("span.lt3-kbd", "⌘K")),
142
143
  h("div", { id: "lt3-idxchip" }),
143
- h("div.lt3-mode", { id: "lt3-mode", role: "tablist", "aria-label": "Workspace mode" },
144
+ h("div.lt3-mode", { id: "lt3-mode", role: "tablist", "aria-label": t("shell.workspaceMode") },
144
145
  MODES.map((m) => h("button", {
145
146
  type: "button", role: "tab", dataset: { mode: m.key },
146
147
  on: { click: () => store.setMode(m.key) },
147
- }, icon(m.icon), h("span", m.label))),
148
+ }, icon(m.icon), h("span", t(m.labelKey)))),
148
149
  ),
149
150
  ),
150
151
  h("main.lt3-view", { id: "lt3-view", tabindex: "-1" },
@@ -155,13 +156,18 @@ function buildMain() {
155
156
 
156
157
  function renderMode() {
157
158
  $$("#lt3-mode button", els.root).forEach((b) => b.dataset.active = String(b.dataset.mode === store.get().mode));
159
+ $$("#lt3-mode button", els.root).forEach((b) => {
160
+ const mode = MODES.find((m) => m.key === b.dataset.mode);
161
+ const span = $("span", b);
162
+ if (mode && span) span.textContent = t(mode.labelKey);
163
+ });
158
164
  }
159
165
 
160
166
  function renderCrumbs() {
161
167
  const r = currentRoute;
162
168
  if (!r) return;
163
169
  const parts = [h("span.lt3-crumb", store.activeWorkspace().name)];
164
- if (r.group === "admin") parts.push(icon("chevron-right"), h("span.lt3-crumb", "Admin"));
170
+ if (r.group === "admin") parts.push(icon("chevron-right"), h("span.lt3-crumb", t("shell.adminCrumb")));
165
171
  parts.push(icon("chevron-right"), h("span.lt3-crumb.lt3-crumb--current", r.title || r.label));
166
172
  els.crumbs.replaceChildren(...parts);
167
173
  }
@@ -171,6 +177,23 @@ function renderIndexChip() {
171
177
  renderRailStatus();
172
178
  }
173
179
 
180
+ function renderChromeText() {
181
+ const skip = $(".lt3-skip", els.root);
182
+ if (skip) skip.textContent = t("shell.skip");
183
+ if (els.cmdText) els.cmdText.textContent = t("shell.searchCommands");
184
+ const rail = $("#lt3-rail", els.root);
185
+ if (rail) rail.setAttribute("aria-label", t("shell.primary"));
186
+ const mode = $("#lt3-mode", els.root);
187
+ if (mode) mode.setAttribute("aria-label", t("shell.workspaceMode"));
188
+ const user = $("#lt3-user", els.root);
189
+ if (user) user.setAttribute("aria-label", t("shell.account"));
190
+ const theme = $("#lt3-theme", els.root);
191
+ if (theme) {
192
+ theme.setAttribute("aria-label", t("shell.toggleTheme"));
193
+ theme.setAttribute("title", t("shell.toggleTheme"));
194
+ }
195
+ }
196
+
174
197
  function renderRailStatus() {
175
198
  if (!els.railStatus) return;
176
199
  const status = store.get().indexStatus;
@@ -181,21 +204,21 @@ function renderRailStatus() {
181
204
  els.railStatus.replaceChildren(
182
205
  h("div.lt3-rail__status-top",
183
206
  h("span.lt3-rail__status-dot", { dataset: { state: unavailable ? "pending" : ready === keys.length ? "ready" : "partial" } }),
184
- h("span", unavailable ? "Local index pending" : `${ready}/${keys.length} retrieval signals ready`),
207
+ h("span", unavailable ? t("shell.indexPending") : t("shell.indexReady", { ready, total: keys.length })),
185
208
  ),
186
- h("div.lt3-rail__status-sub", unavailable ? "Start backend to sync live state" : "Graph · vector · hybrid"),
209
+ h("div.lt3-rail__status-sub", unavailable ? t("shell.startBackend") : t("shell.graphVectorHybrid")),
187
210
  );
188
211
  }
189
212
 
190
213
  /* ── View rendering ─────────────────────────────────────────────────────── */
191
214
  async function renderRoute({ key, params }) {
192
- let route = ROUTE_BY_KEY[key] || ROUTE_BY_KEY.home;
215
+ let route = localizeRoute(ROUTE_BY_KEY[key] || ROUTE_BY_KEY.home);
193
216
  // Deep-linking into an admin area surfaces Admin mode so the rail matches.
194
217
  if (route.admin && store.get().mode !== "admin") store.setMode("admin");
195
218
  currentRoute = route;
196
219
  store.setRoute({ key: route.key, params });
197
220
 
198
- document.title = `${route.title || route.label} · Lattice AI`;
221
+ document.title = t("shell.documentTitle", { title: route.title || route.label });
199
222
  markActive();
200
223
  renderCrumbs();
201
224
 
@@ -213,7 +236,7 @@ async function renderRoute({ key, params }) {
213
236
  outlet.replaceChildren(node);
214
237
  } catch (err) {
215
238
  console.error("[shell] view render failed:", route.view, err);
216
- outlet.replaceChildren(c.errorState(`View "${route.label}" failed to load.`, () => renderRoute({ key: route.key, params })));
239
+ outlet.replaceChildren(c.errorState(t("shell.viewFailed", { label: route.label }), () => renderRoute({ key: route.key, params })));
217
240
  }
218
241
  }
219
242
 
@@ -230,6 +253,10 @@ function onStateChange(_state, change) {
230
253
  case "user": renderUser(); break;
231
254
  case "theme": updateThemeIcon(); break;
232
255
  case "index": renderIndexChip(); break;
256
+ case "language":
257
+ setI18nLanguage(store.get().lang);
258
+ renderNav(); renderScope(); renderUser(); renderMode(); renderCrumbs(); renderIndexChip(); renderChromeText(); renderCurrent();
259
+ break;
233
260
  }
234
261
  }
235
262
 
@@ -249,8 +276,8 @@ function openScopeMenu(ev) {
249
276
  w.workspace_id === store.get().workspaceId ? icon("check", "") : null,
250
277
  )),
251
278
  h("div.lt3-menu__sep"),
252
- h("button.lt3-menu__item", { on: { click: () => { c.toast("Organization creation opens in Settings", "info"); closeMenus(); router.navigate("settings"); } } },
253
- icon("plus"), "New organization"),
279
+ h("button.lt3-menu__item", { on: { click: () => { c.toast(t("shell.orgCreationOpens"), "info"); closeMenus(); router.navigate("workspace-admin"); } } },
280
+ icon("plus"), t("shell.newOrganization")),
254
281
  );
255
282
  document.body.append(menu);
256
283
  setTimeout(() => document.addEventListener("click", closeMenusOnce, { once: true }), 0);
@@ -267,16 +294,16 @@ function paletteItems() {
267
294
  const mode = store.get().mode;
268
295
  const currentRoutes = visibleRoutes(mode);
269
296
  const nav = currentRoutes.map((r) => ({
270
- group: "Go to", label: r.title || r.label, icon: r.icon, hint: r.label === r.title ? r.group : r.label,
297
+ group: t("shell.goTo"), label: r.title || r.label, icon: r.icon, hint: r.label === r.title ? groupLabel(GROUPS.find((g) => g.id === r.group) || { labelKey: r.group }) : r.label,
271
298
  run: () => router.navigate(r.key),
272
299
  }));
273
300
  const actions = [
274
- { group: "Actions", label: "Toggle light / dark theme", icon: "contrast", run: () => store.toggleTheme() },
275
- { group: "Actions", label: "Mode: Basic", icon: "circle", run: () => store.setMode("basic") },
276
- { group: "Actions", label: "Mode: Advanced", icon: "circles", run: () => store.setMode("advanced") },
277
- { group: "Actions", label: "Mode: Admin", icon: "shield-half", run: () => store.setMode("admin") },
278
- { group: "Actions", label: "New chat", icon: "message-plus", run: () => router.navigate("chat", { new: "1" }) },
279
- { group: "Actions", label: "Run hybrid search", icon: "arrows-join", run: () => router.navigate("hybrid-search") },
301
+ { group: t("shell.actions"), label: t("shell.toggleLightDark"), icon: "contrast", run: () => store.toggleTheme() },
302
+ { group: t("shell.actions"), label: `${t("common.status")}: ${t("shell.mode.basic")}`, icon: "circle", run: () => store.setMode("basic") },
303
+ { group: t("shell.actions"), label: `${t("common.status")}: ${t("shell.mode.advanced")}`, icon: "circles", run: () => store.setMode("advanced") },
304
+ { group: t("shell.actions"), label: `${t("common.status")}: ${t("shell.mode.admin")}`, icon: "shield-half", run: () => store.setMode("admin") },
305
+ { group: t("shell.actions"), label: t("shell.newChat"), icon: "message-plus", run: () => router.navigate("chat", { new: "1" }) },
306
+ { group: t("shell.actions"), label: t("shell.runHybridSearch"), icon: "arrows-join", run: () => router.navigate("hybrid-search") },
280
307
  ];
281
308
  return [...nav, ...actions];
282
309
  }
@@ -287,8 +314,8 @@ function openPalette() {
287
314
  let active = 0, filtered = all;
288
315
 
289
316
  const listEl = h("div.lt3-palette__list");
290
- const input = h("input", { type: "text", placeholder: "Search views, run a command…", "aria-label": "Command palette", autocomplete: "off" });
291
- const palette = h("div.lt3-palette", { id: "lt3-palette", role: "dialog", "aria-modal": "true", "aria-label": "Command palette" },
317
+ const input = h("input", { type: "text", placeholder: t("shell.palettePlaceholder"), "aria-label": t("shell.commandPalette"), autocomplete: "off" });
318
+ const palette = h("div.lt3-palette", { id: "lt3-palette", role: "dialog", "aria-modal": "true", "aria-label": t("shell.commandPalette") },
292
319
  h("div.lt3-palette__input", icon("search"), input, h("span.lt3-kbd", "Esc")),
293
320
  listEl,
294
321
  );
@@ -298,7 +325,7 @@ function openPalette() {
298
325
 
299
326
  function renderList() {
300
327
  listEl.replaceChildren();
301
- if (!filtered.length) { listEl.append(h("div.lt3-palette__empty", "No matches")); return; }
328
+ if (!filtered.length) { listEl.append(h("div.lt3-palette__empty", t("shell.noMatches"))); return; }
302
329
  let lastGroup = null;
303
330
  filtered.forEach((item, i) => {
304
331
  if (item.group !== lastGroup) { listEl.append(h("div.lt3-palette__group-label", item.group)); lastGroup = item.group; }
@@ -366,6 +393,7 @@ function cacheEls(root) {
366
393
  theme: $("#lt3-theme", root),
367
394
  crumbs: $("#lt3-crumbs", root),
368
395
  idxchip: $("#lt3-idxchip", root),
396
+ cmdText: $("#lt3-cmd-text", root),
369
397
  railStatus: $("#lt3-rail-status", root),
370
398
  outlet: $("#lt3-outlet", root),
371
399
  view: $("#lt3-view", root),
@@ -377,6 +405,7 @@ function cacheEls(root) {
377
405
  updateThemeIcon();
378
406
  renderIndexChip();
379
407
  renderRailStatus();
408
+ renderChromeText();
380
409
  }
381
410
 
382
411
  function latticeMark() {