superlocalmemory 3.7.8 → 3.8.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 (280) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/ATTRIBUTION.md +1 -3
  3. package/CHANGELOG.md +129 -0
  4. package/README.md +205 -123
  5. package/package.json +12 -3
  6. package/plugin/.claude-plugin/plugin.json +2 -3
  7. package/plugin/CLAUDE.md +8 -8
  8. package/plugin/agents/slm-governance-advisor.md +80 -0
  9. package/plugin/agents/slm-loop-runner.md +71 -0
  10. package/plugin/agents/slm-memory-advisor.md +10 -5
  11. package/plugin/agents/slm-optimize-advisor.md +9 -3
  12. package/plugin/commands/slm-loop.md +31 -0
  13. package/plugin/hooks/hooks.json +79 -0
  14. package/plugin/requirements.txt +1 -1
  15. package/plugin/scripts/slm-launch +46 -7
  16. package/plugin/settings.json +9 -0
  17. package/plugin/skills/slm-cache/SKILL.md +9 -1
  18. package/plugin/skills/slm-compress/SKILL.md +8 -1
  19. package/plugin/skills/slm-governance/SKILL.md +248 -0
  20. package/plugin/skills/slm-graph/SKILL.md +17 -3
  21. package/plugin/skills/slm-loop/SKILL.md +99 -0
  22. package/plugin/skills/slm-mesh/SKILL.md +282 -0
  23. package/plugin/skills/slm-profile/SKILL.md +148 -0
  24. package/plugin/skills/slm-recall/SKILL.md +46 -10
  25. package/plugin/skills/slm-remember/SKILL.md +48 -1
  26. package/plugin/skills/slm-scope/SKILL.md +176 -0
  27. package/plugin/skills/slm-session/SKILL.md +24 -1
  28. package/plugin/skills/slm-status/SKILL.md +18 -1
  29. package/plugin-src/rules/AGENTS.md +57 -18
  30. package/plugin-src/skills/slm-cache/SKILL.md +9 -1
  31. package/plugin-src/skills/slm-compress/SKILL.md +8 -1
  32. package/plugin-src/skills/slm-graph/SKILL.md +17 -3
  33. package/plugin-src/skills/slm-recall/SKILL.md +46 -10
  34. package/plugin-src/skills/slm-remember/SKILL.md +48 -1
  35. package/plugin-src/skills/slm-session/SKILL.md +24 -1
  36. package/plugin-src/skills/slm-status/SKILL.md +18 -1
  37. package/pyproject.toml +2 -1
  38. package/scripts/postinstall/validation.js +2 -0
  39. package/scripts/postinstall-interactive.js +74 -2
  40. package/src/superlocalmemory/__init__.py +1 -1
  41. package/src/superlocalmemory/access/__init__.py +3 -0
  42. package/src/superlocalmemory/access/rbac.py +477 -0
  43. package/src/superlocalmemory/cli/commands.py +228 -17
  44. package/src/superlocalmemory/cli/compress_cmd.py +17 -7
  45. package/src/superlocalmemory/cli/daemon.py +7 -0
  46. package/src/superlocalmemory/cli/loop_cmd.py +187 -0
  47. package/src/superlocalmemory/cli/main.py +49 -8
  48. package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
  49. package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
  50. package/src/superlocalmemory/cli/pending_store.py +49 -13
  51. package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
  52. package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
  53. package/src/superlocalmemory/cli/setup_wizard.py +22 -13
  54. package/src/superlocalmemory/cli/version_banner.py +17 -3
  55. package/src/superlocalmemory/compliance/audit.py +6 -0
  56. package/src/superlocalmemory/compliance/gdpr.py +128 -138
  57. package/src/superlocalmemory/compliance/retention.py +176 -45
  58. package/src/superlocalmemory/core/backend_orchestrator.py +23 -59
  59. package/src/superlocalmemory/core/community_summary.py +267 -0
  60. package/src/superlocalmemory/core/config.py +216 -3
  61. package/src/superlocalmemory/core/consolidation_engine.py +95 -22
  62. package/src/superlocalmemory/core/context_cache.py +61 -18
  63. package/src/superlocalmemory/core/embedding_worker.py +21 -7
  64. package/src/superlocalmemory/core/embeddings.py +131 -46
  65. package/src/superlocalmemory/core/engine.py +41 -22
  66. package/src/superlocalmemory/core/engine_ingestion.py +359 -43
  67. package/src/superlocalmemory/core/engine_wiring.py +13 -0
  68. package/src/superlocalmemory/core/entity_community.py +178 -0
  69. package/src/superlocalmemory/core/graph_analyzer.py +39 -2
  70. package/src/superlocalmemory/core/graph_pruner.py +13 -8
  71. package/src/superlocalmemory/core/ingestion_command.py +134 -25
  72. package/src/superlocalmemory/core/injection.py +12 -7
  73. package/src/superlocalmemory/core/key_expander.py +138 -0
  74. package/src/superlocalmemory/core/maintenance.py +23 -0
  75. package/src/superlocalmemory/core/maintenance_scheduler.py +17 -7
  76. package/src/superlocalmemory/core/modes.py +1 -1
  77. package/src/superlocalmemory/core/mutations.py +2 -2
  78. package/src/superlocalmemory/core/pii.py +105 -0
  79. package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
  80. package/src/superlocalmemory/core/recall_pipeline.py +7 -3
  81. package/src/superlocalmemory/core/recall_worker.py +20 -6
  82. package/src/superlocalmemory/core/scale_engine.py +60 -1
  83. package/src/superlocalmemory/core/security_primitives.py +40 -2
  84. package/src/superlocalmemory/core/store_pipeline.py +186 -29
  85. package/src/superlocalmemory/core/worker_pool.py +21 -6
  86. package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
  87. package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
  88. package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
  89. package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
  90. package/src/superlocalmemory/evolution/evolution_store.py +122 -45
  91. package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
  92. package/src/superlocalmemory/evolution/model_selection.py +160 -0
  93. package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
  94. package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
  95. package/src/superlocalmemory/evolution/triggers.py +22 -13
  96. package/src/superlocalmemory/graph/cozo_backend.py +43 -20
  97. package/src/superlocalmemory/hooks/adapter_base.py +5 -1
  98. package/src/superlocalmemory/hooks/auto_recall.py +13 -1
  99. package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
  100. package/src/superlocalmemory/hooks/codex_assets.py +64 -5
  101. package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
  102. package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
  103. package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
  104. package/src/superlocalmemory/hooks/portable_kit.py +148 -3
  105. package/src/superlocalmemory/infra/backup.py +12 -1
  106. package/src/superlocalmemory/infra/daemon_identity.py +40 -4
  107. package/src/superlocalmemory/infra/data_root.py +43 -4
  108. package/src/superlocalmemory/infra/event_bus.py +107 -24
  109. package/src/superlocalmemory/infra/rate_limiter.py +93 -0
  110. package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
  111. package/src/superlocalmemory/ingestion/credentials.py +1 -1
  112. package/src/superlocalmemory/learning/cross_project.py +28 -19
  113. package/src/superlocalmemory/learning/model_rollback.py +3 -0
  114. package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
  115. package/src/superlocalmemory/learning/reward.py +50 -0
  116. package/src/superlocalmemory/learning/reward_proxy.py +42 -9
  117. package/src/superlocalmemory/learning/source_quality.py +523 -1
  118. package/src/superlocalmemory/loops/__init__.py +56 -0
  119. package/src/superlocalmemory/loops/budget.py +58 -0
  120. package/src/superlocalmemory/loops/engine.py +164 -0
  121. package/src/superlocalmemory/loops/ledger.py +263 -0
  122. package/src/superlocalmemory/loops/models.py +152 -0
  123. package/src/superlocalmemory/loops/rules.py +52 -0
  124. package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
  125. package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
  126. package/src/superlocalmemory/mcp/profiles.py +103 -0
  127. package/src/superlocalmemory/mcp/server.py +32 -79
  128. package/src/superlocalmemory/mcp/tools_active.py +4 -7
  129. package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
  130. package/src/superlocalmemory/mcp/tools_core.py +12 -4
  131. package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
  132. package/src/superlocalmemory/mcp/tools_learning.py +2 -2
  133. package/src/superlocalmemory/mcp/tools_loops.py +300 -0
  134. package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
  135. package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
  136. package/src/superlocalmemory/mesh/broker.py +237 -129
  137. package/src/superlocalmemory/mesh/remote_sync.py +50 -8
  138. package/src/superlocalmemory/optimize/NOTICE +1 -6
  139. package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
  140. package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
  141. package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
  142. package/src/superlocalmemory/optimize/compress/align.py +32 -26
  143. package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
  144. package/src/superlocalmemory/optimize/compress/router.py +105 -22
  145. package/src/superlocalmemory/optimize/config/defaults.py +1 -1
  146. package/src/superlocalmemory/optimize/config/schema.py +87 -4
  147. package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
  148. package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
  149. package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
  150. package/src/superlocalmemory/optimize/storage/db.py +38 -9
  151. package/src/superlocalmemory/optimize/storage/schema.py +10 -0
  152. package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
  153. package/src/superlocalmemory/retrieval/agentic.py +1 -1
  154. package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
  155. package/src/superlocalmemory/retrieval/engine.py +221 -47
  156. package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
  157. package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
  158. package/src/superlocalmemory/retrieval/reranker.py +3 -4
  159. package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
  160. package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
  161. package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
  162. package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
  163. package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
  164. package/src/superlocalmemory/retrieval/time_window.py +181 -0
  165. package/src/superlocalmemory/server/api.py +4 -4
  166. package/src/superlocalmemory/server/config_file.py +90 -0
  167. package/src/superlocalmemory/server/origin.py +50 -0
  168. package/src/superlocalmemory/server/profile_runtime.py +125 -8
  169. package/src/superlocalmemory/server/rbac_enforce.py +142 -0
  170. package/src/superlocalmemory/server/recall_health.py +24 -3
  171. package/src/superlocalmemory/server/recall_serializer.py +19 -1
  172. package/src/superlocalmemory/server/routes/abstraction.py +115 -0
  173. package/src/superlocalmemory/server/routes/agents.py +128 -38
  174. package/src/superlocalmemory/server/routes/backup.py +317 -70
  175. package/src/superlocalmemory/server/routes/behavioral.py +349 -71
  176. package/src/superlocalmemory/server/routes/brain.py +69 -12
  177. package/src/superlocalmemory/server/routes/chat.py +10 -5
  178. package/src/superlocalmemory/server/routes/compliance.py +171 -21
  179. package/src/superlocalmemory/server/routes/config_api.py +438 -0
  180. package/src/superlocalmemory/server/routes/data_io.py +30 -8
  181. package/src/superlocalmemory/server/routes/entity.py +108 -26
  182. package/src/superlocalmemory/server/routes/events.py +24 -8
  183. package/src/superlocalmemory/server/routes/evolution.py +189 -68
  184. package/src/superlocalmemory/server/routes/helpers.py +16 -1
  185. package/src/superlocalmemory/server/routes/ingest.py +7 -4
  186. package/src/superlocalmemory/server/routes/insights.py +3 -3
  187. package/src/superlocalmemory/server/routes/learning.py +289 -118
  188. package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
  189. package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
  190. package/src/superlocalmemory/server/routes/memories.py +182 -57
  191. package/src/superlocalmemory/server/routes/mesh.py +200 -31
  192. package/src/superlocalmemory/server/routes/optimize.py +33 -1
  193. package/src/superlocalmemory/server/routes/prewarm.py +2 -0
  194. package/src/superlocalmemory/server/routes/profiles.py +63 -17
  195. package/src/superlocalmemory/server/routes/ratelimit.py +132 -0
  196. package/src/superlocalmemory/server/routes/rbac.py +367 -0
  197. package/src/superlocalmemory/server/routes/stats.py +103 -158
  198. package/src/superlocalmemory/server/routes/tiers.py +11 -9
  199. package/src/superlocalmemory/server/routes/token.py +3 -13
  200. package/src/superlocalmemory/server/routes/v3_api.py +247 -89
  201. package/src/superlocalmemory/server/routes/ws.py +5 -2
  202. package/src/superlocalmemory/server/security_middleware.py +12 -5
  203. package/src/superlocalmemory/server/ui.py +20 -5
  204. package/src/superlocalmemory/server/unified_daemon.py +827 -72
  205. package/src/superlocalmemory/server/write_identity.py +38 -8
  206. package/src/superlocalmemory/storage/database.py +265 -53
  207. package/src/superlocalmemory/storage/migration_runner.py +132 -1
  208. package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
  209. package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +108 -0
  210. package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +86 -0
  211. package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +194 -0
  212. package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +87 -0
  213. package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +90 -0
  214. package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +136 -0
  215. package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +163 -0
  216. package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +270 -0
  217. package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
  218. package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
  219. package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
  220. package/src/superlocalmemory/storage/models.py +4 -0
  221. package/src/superlocalmemory/storage/schema.py +136 -1
  222. package/src/superlocalmemory/storage/schema_v32.py +2 -0
  223. package/src/superlocalmemory/storage/schema_v343.py +24 -12
  224. package/src/superlocalmemory/storage/schema_v347.py +4 -0
  225. package/src/superlocalmemory/trust/gate.py +49 -8
  226. package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
  227. package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
  228. package/src/superlocalmemory/ui/css/design-system.css +621 -0
  229. package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
  230. package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
  231. package/src/superlocalmemory/ui/favicon.svg +35 -4
  232. package/src/superlocalmemory/ui/index.html +303 -173
  233. package/src/superlocalmemory/ui/js/brain.js +5 -20
  234. package/src/superlocalmemory/ui/js/core.js +100 -41
  235. package/src/superlocalmemory/ui/js/dashboard.js +403 -65
  236. package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
  237. package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
  238. package/src/superlocalmemory/ui/js/math-health.js +1 -1
  239. package/src/superlocalmemory/ui/js/memories.js +15 -4
  240. package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
  241. package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
  242. package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
  243. package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
  244. package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
  245. package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
  246. package/src/superlocalmemory/ui/js/od-agents.js +544 -0
  247. package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
  248. package/src/superlocalmemory/ui/js/od-backup.js +871 -0
  249. package/src/superlocalmemory/ui/js/od-brain.js +816 -0
  250. package/src/superlocalmemory/ui/js/od-entities.js +579 -0
  251. package/src/superlocalmemory/ui/js/od-graph.js +600 -0
  252. package/src/superlocalmemory/ui/js/od-health.js +539 -0
  253. package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
  254. package/src/superlocalmemory/ui/js/od-memories.js +929 -0
  255. package/src/superlocalmemory/ui/js/od-mesh.js +553 -0
  256. package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
  257. package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
  258. package/src/superlocalmemory/ui/js/od-settings.js +1107 -0
  259. package/src/superlocalmemory/ui/js/od-shell.js +809 -0
  260. package/src/superlocalmemory/ui/js/od-skills.js +600 -0
  261. package/src/superlocalmemory/ui/js/od-team.js +258 -0
  262. package/src/superlocalmemory/ui/js/profiles.js +159 -46
  263. package/src/superlocalmemory/ui/js/settings.js +17 -3
  264. package/src/superlocalmemory/ui/js/timeline.js +34 -5
  265. package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
  266. package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
  267. package/plugin-src/.mcp.json +0 -12
  268. package/plugin-src/agents/slm-memory-advisor.md +0 -44
  269. package/plugin-src/agents/slm-optimize-advisor.md +0 -38
  270. package/plugin-src/hooks/.gitkeep +0 -0
  271. package/plugin-src/hooks/hooks.json +0 -23
  272. package/plugin-src/manifest.json +0 -25
  273. package/plugin-src/requirements.txt +0 -1
  274. package/plugin-src/rules/CLAUDE.md.fragment +0 -44
  275. package/plugin-src/scripts/ensure-venv.bat +0 -122
  276. package/plugin-src/scripts/ensure-venv.sh +0 -105
  277. package/plugin-src/scripts/slm-launch +0 -23
  278. package/plugin-src/scripts/slm-launch.bat +0 -23
  279. package/plugin-src/settings.json +0 -16
  280. package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
@@ -0,0 +1,809 @@
1
+ /* SuperLocalMemory — OD Shell v1.0
2
+ * CSP-safe single-page port of assets/shell.js (Open Design 022c4af9).
3
+ *
4
+ * Hard constraints obeyed:
5
+ * - NO inline <script> blocks or on*= event handlers anywhere
6
+ * - All events wired via addEventListener or data-act-* delegation
7
+ * - Syncs data-theme (OD) + data-bs-theme (Bootstrap) + .ng-dark body class
8
+ * - Sidebar nav uses data-tab="<paneId>" for single-page switching
9
+ * - Replaces ng-shell.js entirely; all ng-shell functionality ported here
10
+ *
11
+ * Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar — AGPL-3.0
12
+ */
13
+ (function () {
14
+ 'use strict';
15
+
16
+ /* ================================================================
17
+ Icons (lucide-style 24×24 stroke path data)
18
+ Same set as OD assets/shell.js plus Bootstrap-icon compat shapes.
19
+ ================================================================ */
20
+ var I = {
21
+ dashboard: '<path d="M3 3h8v8H3zM13 3h8v5h-8zM13 12h8v9h-8zM3 15h8v6H3z"/>',
22
+ brain: '<path d="M12 5a3 3 0 0 0-6 0 3 3 0 0 0-3 3 3 3 0 0 0 1.5 2.6A3 3 0 0 0 6 17a3 3 0 0 0 6 0zM12 5a3 3 0 0 1 6 0 3 3 0 0 1 3 3 3 3 0 0 1-1.5 2.6A3 3 0 0 1 18 17a3 3 0 0 1-6 0zM12 5v14"/>',
23
+ graph: '<circle cx="5" cy="6" r="2.5"/><circle cx="19" cy="7" r="2.5"/><circle cx="6" cy="18" r="2.5"/><circle cx="17" cy="18" r="2.5"/><path d="M7 7l9 9M7.2 6.6L16.5 6.9M7.5 16.6l8.2.6M6 15.5l1-6.5"/>',
24
+ memories: '<ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v6c0 1.7 3.6 3 8 3s8-1.3 8-3V5M4 11v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6"/>',
25
+ entity: '<path d="M12 2l9 5v10l-9 5-9-5V7z"/><path d="M12 12l9-5M12 12v10M12 12L3 7"/>',
26
+ skill: '<path d="M12 3l1.9 4.6L18 9l-3.5 3 1 5-3.5-2.5L8.5 17l1-5L6 9l4.1-1.4z"/>',
27
+ health: '<path d="M3 12h4l2-6 4 12 2-6h6"/>',
28
+ operations: '<circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3M5 5l2 2M17 17l2 2M19 5l-2 2M7 17l-2 2"/>',
29
+ optimize: '<path d="M13 2L4.1 12.3a1 1 0 0 0 .8 1.7H11l-1 8 8.9-10.3a1 1 0 0 0-.8-1.7H12z"/>',
30
+ mesh: '<circle cx="12" cy="12" r="2.5"/><circle cx="5" cy="5" r="2"/><circle cx="19" cy="5" r="2"/><circle cx="5" cy="19" r="2"/><circle cx="19" cy="19" r="2"/><path d="M10 10L6.5 6.5M14 10l3.5-3.5M10 14l-3.5 3.5M14 14l3.5 3.5"/>',
31
+ settings: '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.6 1.6 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.6 1.6 0 0 0-1.8-.3 1.6 1.6 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.6 1.6 0 0 0-1-1.5 1.6 1.6 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.6 1.6 0 0 0 .3-1.8 1.6 1.6 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.6 1.6 0 0 0 1.5-1 1.6 1.6 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.6 1.6 0 0 0 1.8.3H9a1.6 1.6 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.6 1.6 0 0 0 1 1.5 1.6 1.6 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.6 1.6 0 0 0-.3 1.8V9a1.6 1.6 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.6 1.6 0 0 0-1.5 1z"/>',
32
+ search: '<circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/>',
33
+ sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2M5 5l1.5 1.5M17.5 17.5L19 19M19 5l-1.5 1.5M6.5 17.5L5 19"/>',
34
+ moon: '<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/>',
35
+ menu: '<path d="M4 6h16M4 12h16M4 18h16"/>',
36
+ lock: '<rect x="4" y="10" width="16" height="10" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/>',
37
+ shield: '<path d="M12 2l8 4v6c0 5-3.5 8.5-8 10-4.5-1.5-8-5-8-10V6z"/><path d="M9 12l2 2 4-4"/>',
38
+ plug: '<path d="M9 2v6M15 2v6M7 8h10v3a5 5 0 0 1-10 0zM12 16v6"/>',
39
+ github: '<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/>',
40
+ star: '<path d="M12 2l3 6.3 6.9 1-5 4.9 1.2 6.8L12 17.8 5.9 21l1.2-6.8-5-4.9 6.9-1z"/>',
41
+ pkg: '<path d="M12 2l9 5v10l-9 5-9-5V7z"/><path d="M12 12l9-5M12 12v10M12 12L3 7"/>',
42
+ clock: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>',
43
+ link: '<path d="M9 15l6-6M10 6l1-1a4 4 0 0 1 6 6l-1 1M14 18l-1 1a4 4 0 0 1-6-6l1-1"/>',
44
+ cloud: '<path d="M17.5 19a4.5 4.5 0 1 0-1.4-8.8A6 6 0 0 0 4.5 12 3.5 3.5 0 0 0 6 18.9z"/>',
45
+ filter: '<path d="M3 4h18l-7 8.5V20l-4 1v-8.5z"/>',
46
+ send: '<path d="M22 2L11 13M22 2l-7 20-4-9-9-4z"/>',
47
+ download: '<path d="M12 3v12M7 11l5 5 5-5M4 21h16"/>',
48
+ plus: '<path d="M12 5v14M5 12h14"/>',
49
+ refresh: '<path d="M21 12a9 9 0 1 1-3-6.7L21 8M21 3v5h-5"/>',
50
+ };
51
+
52
+ function svg(name, cls) {
53
+ return '<svg class="' + (cls || '') + '" viewBox="0 0 24 24" fill="none" ' +
54
+ 'stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">' +
55
+ (I[name] || '') + '</svg>';
56
+ }
57
+ window.slmIcon = svg;
58
+
59
+ /* ================================================================
60
+ Nav model: OD groups → real pane IDs (single-page, no page links)
61
+ ================================================================ */
62
+ var NAV = [
63
+ { g: 'Overview', items: [
64
+ { k: 'dashboard-pane', t: 'Dashboard', i: 'dashboard', crumb: 'Overview' },
65
+ { k: 'brain-pane', t: 'Brain', i: 'brain', crumb: 'Overview', tag: 'live', hero: true },
66
+ ]},
67
+ { g: 'Memory', items: [
68
+ { k: 'graph-pane', t: 'Knowledge Graph', i: 'graph', crumb: 'Memory', hero: true },
69
+ { k: 'memories-pane', t: 'Memories', i: 'memories', crumb: 'Memory' },
70
+ { k: 'entities-pane', t: 'Entity Explorer', i: 'entity', crumb: 'Memory' },
71
+ { k: 'agents-pane', t: 'Multi-Agent Memory', i: 'mesh', crumb: 'Memory' },
72
+ ]},
73
+ { g: 'Intelligence', items: [
74
+ { k: 'skills-pane', t: 'Skill Evolution', i: 'skill', crumb: 'Intelligence' },
75
+ ]},
76
+ { g: 'System', items: [
77
+ { k: 'health-pane', t: 'Health', i: 'health', crumb: 'System' },
78
+ { k: 'operations-pane', t: 'Governance', i: 'shield', crumb: 'System' },
79
+ { k: 'optimize-pane', t: 'Optimize', i: 'optimize', crumb: 'System' },
80
+ ]},
81
+ { g: 'Integrations', items: [
82
+ { k: 'mcp-pane', t: 'MCP & Tools', i: 'plug', crumb: 'Integrations' },
83
+ { k: 'mesh-pane', t: 'Mesh Peers', i: 'mesh', crumb: 'Integrations' },
84
+ ]},
85
+ { g: 'Config', items: [
86
+ { k: 'settings-pane', t: 'Settings', i: 'settings', crumb: 'Config' },
87
+ { k: 'backup-pane', t: 'Cloud Backup', i: 'cloud', crumb: 'Config' },
88
+ ]},
89
+ ];
90
+
91
+ // Flat lookup: paneId → nav item
92
+ var NAV_MAP = {};
93
+ NAV.forEach(function (grp) {
94
+ grp.items.forEach(function (it) { NAV_MAP[it.k] = it; });
95
+ });
96
+
97
+ // OD panes use a short in-memory stale-while-revalidate window. Navigation
98
+ // inside the window is a pure DOM switch; after it expires, the shell
99
+ // refreshes a hidden replacement pane and keeps the mounted pane visible
100
+ // until the replacement settles. Values can be overridden before this
101
+ // script loads to make the lifecycle deterministic in the Node UI harness.
102
+ var PANE_CACHE_TTL_MS = Number(window.SLM_PANE_CACHE_TTL_MS) || 15000;
103
+ var PANE_REFRESH_QUIET_MS = Number(window.SLM_PANE_REFRESH_QUIET_MS);
104
+ if (!Number.isFinite(PANE_REFRESH_QUIET_MS) || PANE_REFRESH_QUIET_MS < 0) {
105
+ PANE_REFRESH_QUIET_MS = 150;
106
+ }
107
+ var PANE_REFRESH_MAX_MS = 30000;
108
+ var paneLoadState = {};
109
+ var paneRefreshTail = Promise.resolve();
110
+
111
+ function getPaneLoadState(paneId) {
112
+ if (!paneLoadState[paneId]) {
113
+ paneLoadState[paneId] = {
114
+ mounted: false,
115
+ loadedAt: 0,
116
+ refreshing: false,
117
+ generation: 0,
118
+ };
119
+ }
120
+ return paneLoadState[paneId];
121
+ }
122
+
123
+ /* ================================================================
124
+ Brand links
125
+ ================================================================ */
126
+ var LINKS = {
127
+ repo: 'https://github.com/qualixar/superlocalmemory',
128
+ npm: 'https://www.npmjs.com/package/superlocalmemory',
129
+ pip: 'https://pypi.org/project/superlocalmemory/',
130
+ qualixar: 'https://qualixar.com',
131
+ author: 'https://varunpratap.com',
132
+ license: 'https://github.com/qualixar/superlocalmemory/blob/main/LICENSE',
133
+ };
134
+
135
+ /* ================================================================
136
+ Theme: syncs data-theme (OD) + data-bs-theme (Bootstrap) + ng-dark
137
+ ================================================================ */
138
+ var THEME_KEY = 'slm-theme';
139
+
140
+ function applyTheme(t) {
141
+ document.documentElement.setAttribute('data-theme', t);
142
+ document.documentElement.setAttribute('data-bs-theme', t);
143
+ if (t === 'dark') {
144
+ document.body.classList.add('ng-dark');
145
+ } else {
146
+ document.body.classList.remove('ng-dark');
147
+ }
148
+ try { localStorage.setItem(THEME_KEY, t); } catch (e) {}
149
+
150
+ // Update all [data-theme-icon] placeholders (sun in dark, moon in light)
151
+ document.querySelectorAll('[data-theme-icon]').forEach(function (el) {
152
+ el.innerHTML = svg(t === 'dark' ? 'sun' : 'moon');
153
+ });
154
+
155
+ // Sync inline-styled graph/timeline backgrounds (Bootstrap inline styles override vars)
156
+ ['graph-container', 'memory-timeline-chart'].forEach(function (id) {
157
+ var el = document.getElementById(id);
158
+ if (!el) return;
159
+ el.style.setProperty('background', t === 'dark' ? '#0f1012' : '#ffffff', 'important');
160
+ el.style.setProperty('background-color', t === 'dark' ? '#0f1012' : '#ffffff', 'important');
161
+ el.style.setProperty('border-color', t === 'dark' ? 'rgba(255,255,255,0.06)' : '#e5e7eb', 'important');
162
+ });
163
+ }
164
+
165
+ // Apply theme immediately (before DOMContentLoaded) to prevent FOUC.
166
+ // Both data-theme (OD CSS) and data-bs-theme (Bootstrap) must be set here.
167
+ (function initThemeEarly() {
168
+ var t = 'dark';
169
+ try { t = localStorage.getItem(THEME_KEY) || 'dark'; } catch (e) {}
170
+ document.documentElement.setAttribute('data-theme', t);
171
+ document.documentElement.setAttribute('data-bs-theme', t);
172
+ }());
173
+
174
+ // Public API — override the core.js definition (which only sets data-bs-theme)
175
+ window.toggleDarkMode = function () {
176
+ var cur = document.documentElement.getAttribute('data-theme') || 'dark';
177
+ applyTheme(cur === 'dark' ? 'light' : 'dark');
178
+ };
179
+ window.slmToggleTheme = window.toggleDarkMode;
180
+
181
+ /* ================================================================
182
+ Build sidebar HTML
183
+ ================================================================ */
184
+ function buildSidebarHTML(activePane) {
185
+ var html =
186
+ '<div class="brand">' +
187
+ '<div class="logo"><img src="static/assets/slm-icon.svg" alt="SLM" width="26" height="26"></div>' +
188
+ '<div><div class="name">SuperLocalMemory</div><div class="sub">by Qualixar</div></div>' +
189
+ '</div>' +
190
+ '<nav class="nav">';
191
+
192
+ NAV.forEach(function (grp) {
193
+ html += '<div class="nav-group-label">' + grp.g + '</div>';
194
+ grp.items.forEach(function (it) {
195
+ var on = it.k === activePane ? ' active' : '';
196
+ var hero = it.hero ? ' hero' : '';
197
+ var tag = it.tag ? '<span class="tag">' + it.tag + '</span>' : '';
198
+ html +=
199
+ '<a class="nav-link' + on + hero + '" data-tab="' + it.k + '" ' +
200
+ 'role="button" tabindex="0" aria-controls="' + it.k + '">' +
201
+ svg(it.i) + '<span>' + it.t + '</span>' + tag +
202
+ '</a>';
203
+ });
204
+ });
205
+
206
+ html +=
207
+ '</nav>' +
208
+ '<div class="sidebar-foot" id="od-sidebar-foot">' +
209
+ '<span class="dot pulse"></span>' +
210
+ '<span id="od-version">Local daemon</span>' +
211
+ '</div>' +
212
+ '<div id="od-profile-container" style="padding:0 12px 12px;display:flex;gap:4px;align-items:center"></div>';
213
+
214
+ return html;
215
+ }
216
+
217
+ /* ================================================================
218
+ Tab activation — single-page SPA switching
219
+ ================================================================ */
220
+ function activateTab(paneId) {
221
+ // Hide all Bootstrap tab panes
222
+ document.querySelectorAll('.tab-pane').forEach(function (p) {
223
+ p.classList.remove('show', 'active');
224
+ });
225
+
226
+ // Show target pane
227
+ var pane = document.getElementById(paneId);
228
+ if (pane) pane.classList.add('show', 'active');
229
+ // During a stale refresh the canonical pane is rendered off-screen and
230
+ // the previous pane remains as the visual snapshot. Keep that snapshot in
231
+ // sync with navigation so returning mid-refresh never exposes a blank pane.
232
+ document.querySelectorAll('[data-slm-stale-for]').forEach(function (snapshot) {
233
+ if (snapshot.dataset.slmStaleFor === paneId) {
234
+ snapshot.classList.add('show', 'active');
235
+ }
236
+ });
237
+
238
+ // Update sidebar active state
239
+ document.querySelectorAll('.nav-link[data-tab]').forEach(function (link) {
240
+ link.classList.toggle('active', link.getAttribute('data-tab') === paneId);
241
+ });
242
+
243
+ // Update topbar breadcrumb and heading
244
+ var item = NAV_MAP[paneId];
245
+ if (item) {
246
+ var crumbEl = document.getElementById('topbar-crumb');
247
+ var headEl = document.getElementById('topbar-heading');
248
+ if (crumbEl) crumbEl.textContent = item.crumb || '';
249
+ if (headEl) headEl.textContent = item.t;
250
+ }
251
+
252
+ // Mount the owning renderer before dispatching any legacy event. Returning
253
+ // to a mounted OD pane preserves its DOM/data instead of re-querying every
254
+ // API and showing skeletons again.
255
+ var handledByShell = triggerTabLoad(paneId);
256
+
257
+ // Dispatch Bootstrap shown.bs.tab only for panes not owned by a shell
258
+ // loader (currently Dashboard). OD-owned panes and direct legacy fallbacks
259
+ // have already loaded exactly once above.
260
+ var tabBtn = document.getElementById(paneId.replace('-pane', '-tab'));
261
+ if (tabBtn && !handledByShell) {
262
+ try {
263
+ tabBtn.dispatchEvent(new Event('shown.bs.tab', { bubbles: true }));
264
+ } catch (e) {}
265
+ }
266
+
267
+ // Update URL hash without triggering a scroll
268
+ try { history.replaceState(null, '', '#' + paneId); } catch (e) {}
269
+
270
+ // Scroll both the window and the content area to top
271
+ window.scrollTo({ top: 0, behavior: 'instant' });
272
+ var contentEl = document.getElementById('main-content');
273
+ if (contentEl) contentEl.scrollTo({ top: 0, behavior: 'instant' });
274
+
275
+ // Deferred retry ONLY if the first render produced nothing (a pane that ran
276
+ // before it was visible/sized). Re-running unconditionally used to wipe and
277
+ // rebuild every pane 500ms after each switch — the visible "cards reload"
278
+ // flicker on every tab change. Guard on an empty, still-active pane so a
279
+ // successful render is never torn down and repainted.
280
+ setTimeout(function () {
281
+ var p = document.getElementById(paneId);
282
+ if (p && p.classList.contains('active') && p.children.length === 0) {
283
+ triggerTabLoad(paneId);
284
+ }
285
+ }, 500);
286
+
287
+ // Scroll the active sidebar item into view (mobile)
288
+ var activeLink = document.querySelector('.nav-link[data-tab="' + paneId + '"]');
289
+ if (activeLink) {
290
+ activeLink.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
291
+ }
292
+ }
293
+
294
+ /* ================================================================
295
+ Lazy data loaders — same mapping as ng-shell.js triggerTabLoad
296
+ ================================================================ */
297
+ function queueOdPaneRefresh(tabId, fnName) {
298
+ var state = getPaneLoadState(tabId);
299
+ if (state.refreshing) return;
300
+ state.refreshing = true;
301
+ var generation = state.generation;
302
+
303
+ // Serialising the short handoff prevents nested global fetch wrappers when
304
+ // a user races across several stale panes. Each pane still remains usable
305
+ // from its mounted DOM while it waits its turn.
306
+ paneRefreshTail = paneRefreshTail
307
+ .catch(function () {})
308
+ .then(function () {
309
+ if (state.generation !== generation) return;
310
+ return refreshOdPane(tabId, fnName, state, generation);
311
+ })
312
+ .catch(function (err) {
313
+ console.error('OD background refresh failed for ' + tabId + ':', err);
314
+ })
315
+ .then(function () {
316
+ state.refreshing = false;
317
+ });
318
+ }
319
+
320
+ function refreshOdPane(tabId, fnName, state, generation) {
321
+ var oldPane = document.getElementById(tabId);
322
+ if (!oldPane || typeof window[fnName] !== 'function') return Promise.resolve();
323
+
324
+ var snapshotId = tabId + '--stale-' + String(generation);
325
+ var freshPane = oldPane.cloneNode(false);
326
+ var originalDisplay = freshPane.style.display;
327
+ oldPane.id = snapshotId;
328
+ oldPane.dataset.slmStaleFor = tabId;
329
+ freshPane.id = tabId;
330
+ freshPane.style.display = 'none';
331
+ freshPane.dataset.slmOdRenderer = fnName;
332
+ oldPane.parentNode.insertBefore(freshPane, oldPane);
333
+
334
+ return new Promise(function (resolve) {
335
+ var originalFetch = window.fetch;
336
+ var pending = 0;
337
+ var idleTimer = null;
338
+ var maxTimer = null;
339
+ var finished = false;
340
+ var observer = typeof MutationObserver === 'function'
341
+ ? new MutationObserver(scheduleIdle)
342
+ : null;
343
+
344
+ function restoreFetch() {
345
+ if (window.fetch === trackedFetch) window.fetch = originalFetch;
346
+ }
347
+
348
+ function restoreSnapshot() {
349
+ if (freshPane.parentNode) freshPane.parentNode.removeChild(freshPane);
350
+ oldPane.id = tabId;
351
+ delete oldPane.dataset.slmStaleFor;
352
+ // An explicit mutation/profile invalidation wins over an in-flight
353
+ // refresh. Preserve the visual snapshot, but leave it unmounted so the
354
+ // next visit cannot accidentally re-age invalid data as a cache hit.
355
+ if (state.generation !== generation) {
356
+ delete oldPane.dataset.slmOdRenderer;
357
+ }
358
+ }
359
+
360
+ function finish(commit) {
361
+ if (finished) return;
362
+ finished = true;
363
+ if (idleTimer) clearTimeout(idleTimer);
364
+ if (maxTimer) clearTimeout(maxTimer);
365
+ if (observer) observer.disconnect();
366
+ restoreFetch();
367
+
368
+ if (commit && state.generation === generation) {
369
+ if (oldPane.parentNode) oldPane.parentNode.removeChild(oldPane);
370
+ freshPane.style.display = originalDisplay;
371
+ freshPane.dataset.slmOdRenderer = fnName;
372
+ state.mounted = true;
373
+ state.loadedAt = Date.now();
374
+ } else {
375
+ restoreSnapshot();
376
+ }
377
+ resolve();
378
+ }
379
+
380
+ function scheduleIdle() {
381
+ if (finished || pending > 0) return;
382
+ if (idleTimer) clearTimeout(idleTimer);
383
+ idleTimer = setTimeout(function () { finish(true); }, PANE_REFRESH_QUIET_MS);
384
+ }
385
+
386
+ function trackPromise(promise) {
387
+ pending += 1;
388
+ if (idleTimer) {
389
+ clearTimeout(idleTimer);
390
+ idleTimer = null;
391
+ }
392
+ Promise.resolve(promise).then(settled, settled);
393
+ return promise;
394
+ }
395
+
396
+ function settled() {
397
+ pending = Math.max(0, pending - 1);
398
+ scheduleIdle();
399
+ }
400
+
401
+ function patchResponse(response) {
402
+ ['json', 'text', 'blob', 'arrayBuffer', 'formData'].forEach(function (name) {
403
+ if (!response || typeof response[name] !== 'function') return;
404
+ var bodyReader = response[name].bind(response);
405
+ try {
406
+ response[name] = function () {
407
+ return trackPromise(bodyReader());
408
+ };
409
+ } catch (e) {}
410
+ });
411
+ return response;
412
+ }
413
+
414
+ function trackedFetch() {
415
+ var result;
416
+ try {
417
+ result = originalFetch.apply(window, arguments);
418
+ } catch (err) {
419
+ scheduleIdle();
420
+ throw err;
421
+ }
422
+ var patched = Promise.resolve(result).then(patchResponse);
423
+ trackPromise(patched);
424
+ return patched;
425
+ }
426
+
427
+ if (observer) {
428
+ observer.observe(freshPane, { childList: true, subtree: true, characterData: true });
429
+ }
430
+ if (typeof originalFetch === 'function') window.fetch = trackedFetch;
431
+ maxTimer = setTimeout(function () { finish(false); }, PANE_REFRESH_MAX_MS);
432
+
433
+ try {
434
+ window[fnName](freshPane);
435
+ scheduleIdle();
436
+ } catch (err) {
437
+ finish(false);
438
+ throw err;
439
+ }
440
+ });
441
+ }
442
+
443
+ function triggerTabLoad(tabId) {
444
+ var pane = document.getElementById(tabId);
445
+ // Open Design takeover: if the pane's OD render module is loaded, it OWNS
446
+ // the pane — clear the legacy markup and let the OD module render the
447
+ // approved design wired to live data. Fall back to the legacy loader(s)
448
+ // only when the OD module for that pane isn't present yet.
449
+ function od(fnName) {
450
+ if (pane && typeof window[fnName] === 'function') {
451
+ if (pane.dataset.slmOdRenderer === fnName && pane.children.length > 0) {
452
+ var cached = getPaneLoadState(tabId);
453
+ if (!cached.mounted) {
454
+ cached.mounted = true;
455
+ cached.loadedAt = Date.now();
456
+ } else if (Date.now() - cached.loadedAt >= PANE_CACHE_TTL_MS) {
457
+ queueOdPaneRefresh(tabId, fnName);
458
+ }
459
+ return true;
460
+ }
461
+ try {
462
+ pane.innerHTML = '';
463
+ window[fnName](pane);
464
+ pane.dataset.slmOdRenderer = fnName;
465
+ var mounted = getPaneLoadState(tabId);
466
+ mounted.mounted = true;
467
+ mounted.loadedAt = Date.now();
468
+ return true;
469
+ }
470
+ catch (e) { console.error('OD render failed for ' + tabId + ':', e); }
471
+ }
472
+ return false;
473
+ }
474
+ switch (tabId) {
475
+ case 'brain-pane':
476
+ if (od('odRenderBrain')) return true;
477
+ if (typeof loadBrain === 'function') loadBrain();
478
+ return true;
479
+ case 'graph-pane':
480
+ if (od('odRenderGraph')) return true;
481
+ if (typeof loadGraph === 'function') loadGraph();
482
+ if (typeof initMemoryChat === 'function' && !document.getElementById('chat-panel')) {
483
+ initMemoryChat();
484
+ }
485
+ if (typeof loadClusters === 'function') loadClusters();
486
+ return true;
487
+ case 'entities-pane':
488
+ if (od('odRenderEntities')) return true;
489
+ if (typeof loadEntityExplorer === 'function') loadEntityExplorer();
490
+ return true;
491
+ case 'memories-pane':
492
+ if (od('odRenderMemories')) return true;
493
+ if (typeof loadMemories === 'function') loadMemories();
494
+ if (typeof loadTimeline === 'function') loadTimeline();
495
+ return true;
496
+ case 'health-pane':
497
+ if (od('odRenderHealth')) return true;
498
+ if (typeof loadHealthMonitor === 'function') loadHealthMonitor();
499
+ if (typeof initEventStream === 'function') initEventStream();
500
+ if (typeof loadEventStats === 'function') loadEventStats();
501
+ if (typeof loadAgents === 'function') loadAgents();
502
+ if (typeof loadIDEStatus === 'function') loadIDEStatus();
503
+ if (typeof loadMathHealth === 'function') loadMathHealth();
504
+ return true;
505
+ case 'operations-pane':
506
+ if (od('odRenderOperations')) return true;
507
+ if (typeof loadIngestionStatus === 'function') loadIngestionStatus();
508
+ if (typeof loadLifecycle === 'function') loadLifecycle();
509
+ if (typeof loadTrustDashboard === 'function') loadTrustDashboard();
510
+ if (typeof loadCompliance === 'function') loadCompliance();
511
+ return true;
512
+ case 'skills-pane':
513
+ if (od('odRenderSkills')) return true;
514
+ if (typeof loadSkillEvolution === 'function') loadSkillEvolution();
515
+ return true;
516
+ case 'mesh-pane':
517
+ if (od('odRenderMesh')) return true;
518
+ if (typeof loadMeshPeers === 'function') loadMeshPeers();
519
+ return true;
520
+ case 'settings-pane':
521
+ if (od('odRenderSettings')) return true;
522
+ if (typeof loadSettings === 'function') loadSettings();
523
+ if (typeof loadModeSettings === 'function') loadModeSettings();
524
+ if (typeof loadAutoSettings === 'function') loadAutoSettings();
525
+ if (typeof updateModeUI === 'function') updateModeUI();
526
+ return true;
527
+ case 'optimize-pane':
528
+ if (od('odRenderOptimize')) return true;
529
+ if (typeof initOptimizeTab === 'function') initOptimizeTab();
530
+ return true;
531
+ case 'agents-pane':
532
+ od('odRenderAgents');
533
+ return true;
534
+ case 'mcp-pane':
535
+ od('odRenderMcp');
536
+ return true;
537
+ case 'backup-pane':
538
+ od('odRenderBackup');
539
+ return true;
540
+ }
541
+ return false;
542
+ }
543
+
544
+ // Mutating actions and profile switches can invalidate cached panes without
545
+ // tearing down unrelated navigation state. The next visit remounts only the
546
+ // affected pane; no persistent browser storage is used.
547
+ window.slmInvalidatePanes = function (paneIds) {
548
+ var ids = Array.isArray(paneIds) ? paneIds : Object.keys(NAV_MAP);
549
+ ids.forEach(function (id) {
550
+ var pane = document.getElementById(id);
551
+ if (pane) delete pane.dataset.slmOdRenderer;
552
+ var state = getPaneLoadState(id);
553
+ state.generation += 1;
554
+ state.mounted = false;
555
+ state.loadedAt = 0;
556
+ });
557
+ };
558
+
559
+ /* ================================================================
560
+ Hash routing: handles both pane IDs and section anchors within panes
561
+ ================================================================ */
562
+ function handleHash() {
563
+ var hash = window.location.hash.replace('#', '');
564
+ if (!hash) return;
565
+ var el = document.getElementById(hash);
566
+ if (!el) return;
567
+
568
+ // Direct pane navigation
569
+ if (el.classList && el.classList.contains('tab-pane')) {
570
+ activateTab(hash);
571
+ return;
572
+ }
573
+
574
+ // Section within a pane (e.g. #health-section-events)
575
+ var parentPane = el.closest && el.closest('.tab-pane');
576
+ if (parentPane && parentPane.id) activateTab(parentPane.id);
577
+ try { el.scrollIntoView({ behavior: 'smooth', block: 'start' }); } catch (e) {}
578
+ }
579
+
580
+ /* ================================================================
581
+ Shell mount: called at DOMContentLoaded
582
+ ================================================================ */
583
+ window.slmShell = function (opts) {
584
+ opts = opts || {};
585
+ var active = opts.active || 'dashboard-pane';
586
+
587
+ // 1. Fill sidebar
588
+ var sb = document.getElementById('sidebar');
589
+ if (sb) sb.innerHTML = buildSidebarHTML(active);
590
+
591
+ // 2. Wire sidebar nav-link clicks and keyboard
592
+ document.querySelectorAll('.nav-link[data-tab]').forEach(function (link) {
593
+ link.addEventListener('click', function (e) {
594
+ e.preventDefault();
595
+ activateTab(this.getAttribute('data-tab'));
596
+ });
597
+ link.addEventListener('keydown', function (e) {
598
+ if (e.key === 'Enter' || e.key === ' ') {
599
+ e.preventDefault();
600
+ activateTab(this.getAttribute('data-tab'));
601
+ }
602
+ });
603
+ });
604
+
605
+ // 3. Mobile sidebar: hamburger button + scrim
606
+ var menuBtn = document.getElementById('menuBtn');
607
+ var scrim = document.getElementById('scrim');
608
+ var sidebarEl = document.getElementById('sidebar');
609
+ if (menuBtn && sidebarEl && scrim) {
610
+ menuBtn.addEventListener('click', function () {
611
+ sidebarEl.classList.toggle('open');
612
+ scrim.classList.toggle('on');
613
+ });
614
+ scrim.addEventListener('click', function () {
615
+ sidebarEl.classList.remove('open');
616
+ scrim.classList.remove('on');
617
+ });
618
+ }
619
+
620
+ // 4. Apply current theme (fills icons, syncs classes)
621
+ var t = 'dark';
622
+ try { t = localStorage.getItem(THEME_KEY) || 'dark'; } catch (e) {}
623
+ applyTheme(t);
624
+
625
+ // 5. Wire theme toggle buttons via addEventListener (NOT onclick — CSP safe)
626
+ document.querySelectorAll('[data-theme-icon]').forEach(function (btn) {
627
+ // Prevent double-binding if slmShell() is somehow called twice
628
+ if (btn.dataset.odThemeWired) return;
629
+ btn.dataset.odThemeWired = '1';
630
+ btn.addEventListener('click', function () { window.toggleDarkMode(); });
631
+ });
632
+
633
+ // 6. Fill data-ic SVG placeholders (icons in pane content, topbar, etc.)
634
+ document.querySelectorAll('[data-ic]').forEach(function (el) {
635
+ el.innerHTML = svg(el.getAttribute('data-ic'), el.getAttribute('data-ic-cls') || '');
636
+ });
637
+
638
+ // 7. Inject "Star us on GitHub" CTA into topbar (before the theme button)
639
+ var topbar = document.querySelector('.topbar');
640
+ if (topbar && !topbar.querySelector('.star-cta')) {
641
+ var themeBtn = topbar.querySelector('[data-theme-icon]');
642
+ var starLink = document.createElement('a');
643
+ starLink.className = 'star-cta';
644
+ starLink.href = LINKS.repo;
645
+ starLink.target = '_blank';
646
+ starLink.rel = 'noopener';
647
+ starLink.title = 'Star SuperLocalMemory on GitHub';
648
+ starLink.innerHTML =
649
+ svg('github', 'gh') +
650
+ "<span class='lbl'>Star us on GitHub</span>";
651
+ if (themeBtn) topbar.insertBefore(starLink, themeBtn);
652
+ else topbar.appendChild(starLink);
653
+ }
654
+
655
+ // 8. Inject shared footer into .content (OD site-foot style)
656
+ var content = document.getElementById('main-content');
657
+ if (content && !content.querySelector('.site-foot')) {
658
+ content.insertAdjacentHTML('beforeend',
659
+ '<footer class="site-foot">' +
660
+ '<div class="foot-brand">' +
661
+ '<span class="foot-logo">' +
662
+ '<img src="static/assets/slm-icon.svg" alt="" width="22" height="22">' +
663
+ '</span>' +
664
+ '<div><b>SuperLocalMemory</b><span>by Qualixar · local-first AI memory</span></div>' +
665
+ '</div>' +
666
+ '<nav class="foot-links">' +
667
+ '<a href="' + LINKS.author + '" target="_blank" rel="noopener">Built by Varun Pratap Bhardwaj</a>' +
668
+ '<a href="' + LINKS.qualixar + '" target="_blank" rel="noopener">qualixar.com</a>' +
669
+ '<a href="' + LINKS.repo + '" target="_blank" rel="noopener">' + svg('github') + 'GitHub</a>' +
670
+ '<a href="' + LINKS.npm + '" target="_blank" rel="noopener">' + svg('pkg') + 'npm</a>' +
671
+ '<a href="' + LINKS.pip + '" target="_blank" rel="noopener">' + svg('pkg') + 'pip</a>' +
672
+ '<a href="' + LINKS.license + '" target="_blank" rel="noopener">License · AGPL-3.0</a>' +
673
+ '</nav>' +
674
+ '</footer>');
675
+ }
676
+
677
+ // 9. Move profile selector from ng-premount into sidebar footer
678
+ var profileContainer = document.getElementById('od-profile-container');
679
+ var profileSelect = document.getElementById('profile-select');
680
+ var addProfileBtn = document.getElementById('add-profile-btn');
681
+ if (profileContainer && profileSelect && profileSelect.parentNode !== profileContainer) {
682
+ profileSelect.style.cssText = 'flex:1;font-size:0.8125rem;max-width:150px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.1);border-radius:6px;color:var(--on-sidebar);padding:3px 6px;';
683
+ profileContainer.appendChild(profileSelect);
684
+ profileSelect.style.display = '';
685
+ if (addProfileBtn) {
686
+ addProfileBtn.className = '';
687
+ addProfileBtn.style.cssText = 'padding:4px 8px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.1);border-radius:6px;color:var(--on-sidebar);cursor:pointer;';
688
+ profileContainer.appendChild(addProfileBtn);
689
+ addProfileBtn.style.display = '';
690
+ }
691
+ }
692
+
693
+ // 10. Fetch daemon version and update sidebar foot
694
+ fetch('/health', { credentials: 'same-origin' })
695
+ .then(function (r) { return r.ok ? r.json() : null; })
696
+ .then(function (data) {
697
+ var verEl = document.getElementById('od-version');
698
+ if (verEl && data && data.version) verEl.textContent = 'v' + data.version;
699
+ })
700
+ .catch(function () {
701
+ var dashVer = document.getElementById('dashboard-version');
702
+ var verEl = document.getElementById('od-version');
703
+ if (verEl && dashVer && dashVer.textContent && dashVer.textContent !== '...') {
704
+ verEl.textContent = 'v' + dashVer.textContent;
705
+ }
706
+ });
707
+
708
+ // 11. Privacy blur (screen recording mode)
709
+ window.togglePrivacyBlur = function () {
710
+ document.body.classList.toggle('ng-privacy-blur');
711
+ var icon = document.getElementById('ng-privacy-icon');
712
+ var isBlurred = document.body.classList.contains('ng-privacy-blur');
713
+ if (icon) icon.className = isBlurred ? 'bi bi-eye' : 'bi bi-eye-slash';
714
+ };
715
+ if (window.location.search.indexOf('blur=1') >= 0) {
716
+ document.body.classList.add('ng-privacy-blur');
717
+ }
718
+
719
+ // 12. Activate the initial pane (fires triggerTabLoad for dashboard etc.)
720
+ activateTab(active);
721
+ };
722
+
723
+ /* ================================================================
724
+ Chart helpers — ported from OD assets/shell.js
725
+ ================================================================ */
726
+
727
+ // Sparkline: array of numbers → inline SVG string
728
+ window.slmSpark = function (data, opts) {
729
+ opts = opts || {};
730
+ var w = opts.w || 200, h = opts.h || 60, pad = 3;
731
+ var min = Math.min.apply(null, data);
732
+ var max = Math.max.apply(null, data);
733
+ var rng = (max - min) || 1;
734
+ var stepX = (w - pad * 2) / (data.length - 1);
735
+ var pts = data.map(function (v, i) {
736
+ return [
737
+ pad + i * stepX,
738
+ h - pad - ((v - min) / rng) * (h - pad * 2),
739
+ ];
740
+ });
741
+ var line = pts.map(function (p, i) {
742
+ return (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1);
743
+ }).join(' ');
744
+ var area =
745
+ line +
746
+ ' L' + pts[pts.length - 1][0].toFixed(1) + ' ' + h +
747
+ ' L' + pts[0][0].toFixed(1) + ' ' + h + ' Z';
748
+ var id = 'g' + Math.random().toString(36).slice(2, 7);
749
+ var stroke = opts.color || 'var(--violet)';
750
+ return (
751
+ '<svg class="spark-lg" viewBox="0 0 ' + w + ' ' + h + '" preserveAspectRatio="none">' +
752
+ '<defs><linearGradient id="' + id + '" x1="0" y1="0" x2="0" y2="1">' +
753
+ '<stop offset="0" stop-color="' + stroke + '" stop-opacity="0.28"/>' +
754
+ '<stop offset="1" stop-color="' + stroke + '" stop-opacity="0"/>' +
755
+ '</linearGradient></defs>' +
756
+ '<path d="' + area + '" fill="url(#' + id + ')"/>' +
757
+ '<path d="' + line + '" fill="none" stroke="' + stroke + '" stroke-width="2" vector-effect="non-scaling-stroke"/>' +
758
+ '</svg>'
759
+ );
760
+ };
761
+
762
+ // GitHub-style contribution heatmap
763
+ window.slmHeatmap = function (el, weeks) {
764
+ weeks = weeks || 26;
765
+ var html = '';
766
+ for (var w = 0; w < weeks; w++) {
767
+ for (var d = 0; d < 7; d++) {
768
+ var recency = w / weeks;
769
+ var base = Math.random() * 0.5 + recency * 0.6;
770
+ var l = base < 0.35 ? 0 : base < 0.55 ? 1 : base < 0.72 ? 2 : base < 0.88 ? 3 : 4;
771
+ html += '<i data-l="' + l + '" title="reward signal"></i>';
772
+ }
773
+ }
774
+ el.innerHTML = html;
775
+ };
776
+
777
+ // Bar chart helper
778
+ window.slmBars = function (el, data) {
779
+ var maxVal = Math.max.apply(null, data) || 1;
780
+ el.innerHTML = data.map(function (v) {
781
+ return '<i style="height:' + Math.max(4, (v / maxVal) * 100) + '%" title="' + v + '"></i>';
782
+ }).join('');
783
+ };
784
+
785
+ /* ================================================================
786
+ DOMContentLoaded bootstrap
787
+ ================================================================ */
788
+ document.addEventListener('DOMContentLoaded', function () {
789
+ // Determine initial active pane from URL hash (if valid tab pane)
790
+ var active = 'dashboard-pane';
791
+ var hash = window.location.hash.replace('#', '');
792
+ if (hash) {
793
+ var el = document.getElementById(hash);
794
+ if (el && el.classList && el.classList.contains('tab-pane')) {
795
+ active = hash;
796
+ } else {
797
+ // Stale section anchor from a previous session — strip it so
798
+ // we always land on Dashboard, not a blank screen.
799
+ try { history.replaceState(null, '', window.location.pathname); } catch (e) {}
800
+ }
801
+ }
802
+
803
+ window.slmShell({ active: active });
804
+
805
+ // Wire subsequent hash changes (user-triggered navigation, not initial load)
806
+ window.addEventListener('hashchange', handleHash);
807
+ });
808
+
809
+ }());