superlocalmemory 3.7.7 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/ATTRIBUTION.md +1 -3
  3. package/CHANGELOG.md +85 -0
  4. package/README.md +199 -29
  5. package/package.json +4 -2
  6. package/plugin/.claude-plugin/plugin.json +2 -2
  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/agents/slm-governance-advisor.md +80 -0
  30. package/plugin-src/agents/slm-loop-runner.md +71 -0
  31. package/plugin-src/agents/slm-memory-advisor.md +10 -5
  32. package/plugin-src/agents/slm-optimize-advisor.md +9 -3
  33. package/plugin-src/commands/slm-loop.md +31 -0
  34. package/plugin-src/hooks/hooks.json +79 -0
  35. package/plugin-src/manifest.json +7 -2
  36. package/plugin-src/requirements.txt +1 -1
  37. package/plugin-src/rules/AGENTS.md +57 -18
  38. package/plugin-src/rules/CLAUDE.md.fragment +8 -8
  39. package/plugin-src/scripts/slm-launch +46 -7
  40. package/plugin-src/settings.json +9 -0
  41. package/plugin-src/skills/slm-cache/SKILL.md +9 -1
  42. package/plugin-src/skills/slm-compress/SKILL.md +8 -1
  43. package/plugin-src/skills/slm-governance/SKILL.md +248 -0
  44. package/plugin-src/skills/slm-graph/SKILL.md +17 -3
  45. package/plugin-src/skills/slm-loop/SKILL.md +99 -0
  46. package/plugin-src/skills/slm-mesh/SKILL.md +282 -0
  47. package/plugin-src/skills/slm-profile/SKILL.md +148 -0
  48. package/plugin-src/skills/slm-recall/SKILL.md +46 -10
  49. package/plugin-src/skills/slm-remember/SKILL.md +48 -1
  50. package/plugin-src/skills/slm-scope/SKILL.md +176 -0
  51. package/plugin-src/skills/slm-session/SKILL.md +24 -1
  52. package/plugin-src/skills/slm-status/SKILL.md +18 -1
  53. package/pyproject.toml +1 -2
  54. package/scripts/postinstall/validation.js +2 -0
  55. package/scripts/postinstall-interactive.js +74 -2
  56. package/src/superlocalmemory/__init__.py +1 -1
  57. package/src/superlocalmemory/access/__init__.py +3 -0
  58. package/src/superlocalmemory/access/rbac.py +477 -0
  59. package/src/superlocalmemory/cli/commands.py +96 -12
  60. package/src/superlocalmemory/cli/compress_cmd.py +17 -7
  61. package/src/superlocalmemory/cli/loop_cmd.py +192 -0
  62. package/src/superlocalmemory/cli/main.py +39 -4
  63. package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
  64. package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
  65. package/src/superlocalmemory/cli/pending_store.py +49 -13
  66. package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
  67. package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
  68. package/src/superlocalmemory/cli/setup_wizard.py +22 -13
  69. package/src/superlocalmemory/compliance/audit.py +6 -0
  70. package/src/superlocalmemory/compliance/gdpr.py +128 -138
  71. package/src/superlocalmemory/compliance/retention.py +176 -45
  72. package/src/superlocalmemory/core/backend_orchestrator.py +5 -43
  73. package/src/superlocalmemory/core/community_summary.py +267 -0
  74. package/src/superlocalmemory/core/config.py +216 -3
  75. package/src/superlocalmemory/core/consolidation_engine.py +95 -22
  76. package/src/superlocalmemory/core/context_cache.py +61 -18
  77. package/src/superlocalmemory/core/embedding_worker.py +17 -2
  78. package/src/superlocalmemory/core/embeddings.py +12 -1
  79. package/src/superlocalmemory/core/engine.py +17 -1
  80. package/src/superlocalmemory/core/engine_ingestion.py +29 -0
  81. package/src/superlocalmemory/core/engine_wiring.py +13 -0
  82. package/src/superlocalmemory/core/entity_community.py +178 -0
  83. package/src/superlocalmemory/core/graph_analyzer.py +39 -2
  84. package/src/superlocalmemory/core/graph_pruner.py +13 -8
  85. package/src/superlocalmemory/core/key_expander.py +138 -0
  86. package/src/superlocalmemory/core/maintenance.py +23 -0
  87. package/src/superlocalmemory/core/modes.py +1 -1
  88. package/src/superlocalmemory/core/mutations.py +2 -2
  89. package/src/superlocalmemory/core/pii.py +105 -0
  90. package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
  91. package/src/superlocalmemory/core/recall_pipeline.py +2 -0
  92. package/src/superlocalmemory/core/recall_worker.py +20 -6
  93. package/src/superlocalmemory/core/scale_engine.py +60 -1
  94. package/src/superlocalmemory/core/security_primitives.py +40 -2
  95. package/src/superlocalmemory/core/store_pipeline.py +35 -11
  96. package/src/superlocalmemory/core/worker_pool.py +21 -6
  97. package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
  98. package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
  99. package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
  100. package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
  101. package/src/superlocalmemory/evolution/evolution_store.py +122 -45
  102. package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
  103. package/src/superlocalmemory/evolution/model_selection.py +160 -0
  104. package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
  105. package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
  106. package/src/superlocalmemory/evolution/triggers.py +22 -13
  107. package/src/superlocalmemory/graph/cozo_backend.py +43 -20
  108. package/src/superlocalmemory/hooks/adapter_base.py +5 -1
  109. package/src/superlocalmemory/hooks/auto_recall.py +13 -1
  110. package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
  111. package/src/superlocalmemory/hooks/codex_assets.py +64 -5
  112. package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
  113. package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
  114. package/src/superlocalmemory/hooks/portable_kit.py +114 -1
  115. package/src/superlocalmemory/infra/auth_middleware.py +28 -0
  116. package/src/superlocalmemory/infra/backup.py +12 -1
  117. package/src/superlocalmemory/infra/daemon_identity.py +40 -4
  118. package/src/superlocalmemory/infra/data_root.py +43 -4
  119. package/src/superlocalmemory/infra/event_bus.py +107 -24
  120. package/src/superlocalmemory/infra/rate_limiter.py +93 -0
  121. package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
  122. package/src/superlocalmemory/ingestion/credentials.py +1 -1
  123. package/src/superlocalmemory/learning/cross_project.py +28 -19
  124. package/src/superlocalmemory/learning/reward_proxy.py +42 -9
  125. package/src/superlocalmemory/loops/__init__.py +56 -0
  126. package/src/superlocalmemory/loops/budget.py +58 -0
  127. package/src/superlocalmemory/loops/engine.py +164 -0
  128. package/src/superlocalmemory/loops/ledger.py +243 -0
  129. package/src/superlocalmemory/loops/models.py +152 -0
  130. package/src/superlocalmemory/loops/rules.py +52 -0
  131. package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
  132. package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
  133. package/src/superlocalmemory/mcp/profiles.py +103 -0
  134. package/src/superlocalmemory/mcp/server.py +21 -49
  135. package/src/superlocalmemory/mcp/tools_active.py +4 -7
  136. package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
  137. package/src/superlocalmemory/mcp/tools_core.py +50 -5
  138. package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
  139. package/src/superlocalmemory/mcp/tools_loops.py +300 -0
  140. package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
  141. package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
  142. package/src/superlocalmemory/mesh/broker.py +237 -129
  143. package/src/superlocalmemory/mesh/remote_sync.py +50 -8
  144. package/src/superlocalmemory/optimize/NOTICE +1 -6
  145. package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
  146. package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
  147. package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
  148. package/src/superlocalmemory/optimize/compress/align.py +32 -26
  149. package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
  150. package/src/superlocalmemory/optimize/compress/router.py +105 -22
  151. package/src/superlocalmemory/optimize/config/defaults.py +1 -1
  152. package/src/superlocalmemory/optimize/config/schema.py +87 -4
  153. package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
  154. package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
  155. package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
  156. package/src/superlocalmemory/optimize/storage/db.py +38 -9
  157. package/src/superlocalmemory/optimize/storage/schema.py +10 -0
  158. package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
  159. package/src/superlocalmemory/retrieval/agentic.py +1 -1
  160. package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
  161. package/src/superlocalmemory/retrieval/engine.py +168 -26
  162. package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
  163. package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
  164. package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
  165. package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
  166. package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
  167. package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
  168. package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
  169. package/src/superlocalmemory/retrieval/time_window.py +181 -0
  170. package/src/superlocalmemory/server/api.py +21 -4
  171. package/src/superlocalmemory/server/profile_runtime.py +125 -8
  172. package/src/superlocalmemory/server/rbac_enforce.py +142 -0
  173. package/src/superlocalmemory/server/recall_health.py +24 -3
  174. package/src/superlocalmemory/server/recall_serializer.py +19 -1
  175. package/src/superlocalmemory/server/routes/abstraction.py +115 -0
  176. package/src/superlocalmemory/server/routes/agents.py +128 -38
  177. package/src/superlocalmemory/server/routes/backup.py +34 -10
  178. package/src/superlocalmemory/server/routes/behavioral.py +13 -12
  179. package/src/superlocalmemory/server/routes/brain.py +21 -5
  180. package/src/superlocalmemory/server/routes/chat.py +72 -16
  181. package/src/superlocalmemory/server/routes/compliance.py +171 -21
  182. package/src/superlocalmemory/server/routes/config_api.py +436 -0
  183. package/src/superlocalmemory/server/routes/data_io.py +30 -8
  184. package/src/superlocalmemory/server/routes/entity.py +9 -4
  185. package/src/superlocalmemory/server/routes/events.py +24 -8
  186. package/src/superlocalmemory/server/routes/evolution.py +135 -17
  187. package/src/superlocalmemory/server/routes/helpers.py +16 -1
  188. package/src/superlocalmemory/server/routes/ingest.py +7 -4
  189. package/src/superlocalmemory/server/routes/insights.py +3 -3
  190. package/src/superlocalmemory/server/routes/learning.py +14 -14
  191. package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
  192. package/src/superlocalmemory/server/routes/memories.py +221 -49
  193. package/src/superlocalmemory/server/routes/mesh.py +95 -15
  194. package/src/superlocalmemory/server/routes/optimize.py +33 -1
  195. package/src/superlocalmemory/server/routes/prewarm.py +2 -0
  196. package/src/superlocalmemory/server/routes/profiles.py +63 -17
  197. package/src/superlocalmemory/server/routes/ratelimit.py +124 -0
  198. package/src/superlocalmemory/server/routes/rbac.py +367 -0
  199. package/src/superlocalmemory/server/routes/stats.py +13 -6
  200. package/src/superlocalmemory/server/routes/tiers.py +11 -9
  201. package/src/superlocalmemory/server/routes/v3_api.py +194 -81
  202. package/src/superlocalmemory/server/routes/ws.py +5 -2
  203. package/src/superlocalmemory/server/security_middleware.py +12 -5
  204. package/src/superlocalmemory/server/ui.py +30 -5
  205. package/src/superlocalmemory/server/unified_daemon.py +431 -75
  206. package/src/superlocalmemory/server/write_identity.py +38 -8
  207. package/src/superlocalmemory/storage/database.py +265 -53
  208. package/src/superlocalmemory/storage/migration_runner.py +53 -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/models.py +4 -0
  217. package/src/superlocalmemory/storage/schema.py +87 -0
  218. package/src/superlocalmemory/storage/schema_v32.py +0 -9
  219. package/src/superlocalmemory/storage/schema_v343.py +24 -12
  220. package/src/superlocalmemory/trust/gate.py +49 -8
  221. package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
  222. package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
  223. package/src/superlocalmemory/ui/css/design-system.css +621 -0
  224. package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
  225. package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
  226. package/src/superlocalmemory/ui/favicon.svg +35 -4
  227. package/src/superlocalmemory/ui/index.html +306 -173
  228. package/src/superlocalmemory/ui/js/brain.js +5 -20
  229. package/src/superlocalmemory/ui/js/core.js +47 -31
  230. package/src/superlocalmemory/ui/js/dashboard.js +314 -63
  231. package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
  232. package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
  233. package/src/superlocalmemory/ui/js/math-health.js +1 -1
  234. package/src/superlocalmemory/ui/js/memories.js +15 -4
  235. package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
  236. package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
  237. package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
  238. package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
  239. package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
  240. package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
  241. package/src/superlocalmemory/ui/js/od-agents.js +544 -0
  242. package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
  243. package/src/superlocalmemory/ui/js/od-backup.js +780 -0
  244. package/src/superlocalmemory/ui/js/od-brain.js +779 -0
  245. package/src/superlocalmemory/ui/js/od-entities.js +579 -0
  246. package/src/superlocalmemory/ui/js/od-graph.js +593 -0
  247. package/src/superlocalmemory/ui/js/od-health.js +539 -0
  248. package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
  249. package/src/superlocalmemory/ui/js/od-memories.js +887 -0
  250. package/src/superlocalmemory/ui/js/od-mesh.js +539 -0
  251. package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
  252. package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
  253. package/src/superlocalmemory/ui/js/od-settings.js +1053 -0
  254. package/src/superlocalmemory/ui/js/od-shell.js +593 -0
  255. package/src/superlocalmemory/ui/js/od-skills.js +573 -0
  256. package/src/superlocalmemory/ui/js/od-team.js +258 -0
  257. package/src/superlocalmemory/ui/js/profiles.js +159 -46
  258. package/src/superlocalmemory/ui/js/settings.js +2 -2
  259. package/src/superlocalmemory/ui/js/timeline.js +34 -5
  260. package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
  261. package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
  262. package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
@@ -0,0 +1,593 @@
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
+ /* ================================================================
98
+ Brand links
99
+ ================================================================ */
100
+ var LINKS = {
101
+ repo: 'https://github.com/qualixar/superlocalmemory',
102
+ npm: 'https://www.npmjs.com/package/superlocalmemory',
103
+ pip: 'https://pypi.org/project/superlocalmemory/',
104
+ qualixar: 'https://qualixar.com',
105
+ author: 'https://varunpratap.com',
106
+ license: 'https://github.com/qualixar/superlocalmemory/blob/main/LICENSE',
107
+ stars: '2,431',
108
+ };
109
+
110
+ /* ================================================================
111
+ Theme: syncs data-theme (OD) + data-bs-theme (Bootstrap) + ng-dark
112
+ ================================================================ */
113
+ var THEME_KEY = 'slm-theme';
114
+
115
+ function applyTheme(t) {
116
+ document.documentElement.setAttribute('data-theme', t);
117
+ document.documentElement.setAttribute('data-bs-theme', t);
118
+ if (t === 'dark') {
119
+ document.body.classList.add('ng-dark');
120
+ } else {
121
+ document.body.classList.remove('ng-dark');
122
+ }
123
+ try { localStorage.setItem(THEME_KEY, t); } catch (e) {}
124
+
125
+ // Update all [data-theme-icon] placeholders (sun in dark, moon in light)
126
+ document.querySelectorAll('[data-theme-icon]').forEach(function (el) {
127
+ el.innerHTML = svg(t === 'dark' ? 'sun' : 'moon');
128
+ });
129
+
130
+ // Sync inline-styled graph/timeline backgrounds (Bootstrap inline styles override vars)
131
+ ['graph-container', 'memory-timeline-chart'].forEach(function (id) {
132
+ var el = document.getElementById(id);
133
+ if (!el) return;
134
+ el.style.setProperty('background', t === 'dark' ? '#0f1012' : '#ffffff', 'important');
135
+ el.style.setProperty('background-color', t === 'dark' ? '#0f1012' : '#ffffff', 'important');
136
+ el.style.setProperty('border-color', t === 'dark' ? 'rgba(255,255,255,0.06)' : '#e5e7eb', 'important');
137
+ });
138
+ }
139
+
140
+ // Apply theme immediately (before DOMContentLoaded) to prevent FOUC.
141
+ // Both data-theme (OD CSS) and data-bs-theme (Bootstrap) must be set here.
142
+ (function initThemeEarly() {
143
+ var t = 'dark';
144
+ try { t = localStorage.getItem(THEME_KEY) || 'dark'; } catch (e) {}
145
+ document.documentElement.setAttribute('data-theme', t);
146
+ document.documentElement.setAttribute('data-bs-theme', t);
147
+ }());
148
+
149
+ // Public API — override the core.js definition (which only sets data-bs-theme)
150
+ window.toggleDarkMode = function () {
151
+ var cur = document.documentElement.getAttribute('data-theme') || 'dark';
152
+ applyTheme(cur === 'dark' ? 'light' : 'dark');
153
+ };
154
+ window.slmToggleTheme = window.toggleDarkMode;
155
+
156
+ /* ================================================================
157
+ Build sidebar HTML
158
+ ================================================================ */
159
+ function buildSidebarHTML(activePane) {
160
+ var html =
161
+ '<div class="brand">' +
162
+ '<div class="logo"><img src="static/assets/slm-icon.svg" alt="SLM" width="26" height="26"></div>' +
163
+ '<div><div class="name">SuperLocalMemory</div><div class="sub">by Qualixar</div></div>' +
164
+ '</div>' +
165
+ '<nav class="nav">';
166
+
167
+ NAV.forEach(function (grp) {
168
+ html += '<div class="nav-group-label">' + grp.g + '</div>';
169
+ grp.items.forEach(function (it) {
170
+ var on = it.k === activePane ? ' active' : '';
171
+ var hero = it.hero ? ' hero' : '';
172
+ var tag = it.tag ? '<span class="tag">' + it.tag + '</span>' : '';
173
+ html +=
174
+ '<a class="nav-link' + on + hero + '" data-tab="' + it.k + '" ' +
175
+ 'role="button" tabindex="0" aria-controls="' + it.k + '">' +
176
+ svg(it.i) + '<span>' + it.t + '</span>' + tag +
177
+ '</a>';
178
+ });
179
+ });
180
+
181
+ html +=
182
+ '</nav>' +
183
+ '<div class="sidebar-foot" id="od-sidebar-foot">' +
184
+ '<span class="dot pulse"></span>' +
185
+ '<span id="od-version">Local daemon</span>' +
186
+ '</div>' +
187
+ '<div id="od-profile-container" style="padding:0 12px 12px;display:flex;gap:4px;align-items:center"></div>';
188
+
189
+ return html;
190
+ }
191
+
192
+ /* ================================================================
193
+ Tab activation — single-page SPA switching
194
+ ================================================================ */
195
+ function activateTab(paneId) {
196
+ // Hide all Bootstrap tab panes
197
+ document.querySelectorAll('.tab-pane').forEach(function (p) {
198
+ p.classList.remove('show', 'active');
199
+ });
200
+
201
+ // Show target pane
202
+ var pane = document.getElementById(paneId);
203
+ if (pane) pane.classList.add('show', 'active');
204
+
205
+ // Update sidebar active state
206
+ document.querySelectorAll('.nav-link[data-tab]').forEach(function (link) {
207
+ link.classList.toggle('active', link.getAttribute('data-tab') === paneId);
208
+ });
209
+
210
+ // Update topbar breadcrumb and heading
211
+ var item = NAV_MAP[paneId];
212
+ if (item) {
213
+ var crumbEl = document.getElementById('topbar-crumb');
214
+ var headEl = document.getElementById('topbar-heading');
215
+ if (crumbEl) crumbEl.textContent = item.crumb || '';
216
+ if (headEl) headEl.textContent = item.t;
217
+ }
218
+
219
+ // Dispatch Bootstrap shown.bs.tab event for backward compatibility.
220
+ // brain.js, dashboard.js, etc. listen on the hidden tab buttons.
221
+ var tabBtn = document.getElementById(paneId.replace('-pane', '-tab'));
222
+ if (tabBtn) {
223
+ try {
224
+ tabBtn.dispatchEvent(new Event('shown.bs.tab', { bubbles: true }));
225
+ } catch (e) {}
226
+ }
227
+
228
+ // Update URL hash without triggering a scroll
229
+ try { history.replaceState(null, '', '#' + paneId); } catch (e) {}
230
+
231
+ // Scroll both the window and the content area to top
232
+ window.scrollTo({ top: 0, behavior: 'instant' });
233
+ var contentEl = document.getElementById('main-content');
234
+ if (contentEl) contentEl.scrollTo({ top: 0, behavior: 'instant' });
235
+
236
+ // Fire lazy data loaders
237
+ triggerTabLoad(paneId);
238
+ // Deferred retry ONLY if the first render produced nothing (a pane that ran
239
+ // before it was visible/sized). Re-running unconditionally used to wipe and
240
+ // rebuild every pane 500ms after each switch — the visible "cards reload"
241
+ // flicker on every tab change. Guard on an empty, still-active pane so a
242
+ // successful render is never torn down and repainted.
243
+ setTimeout(function () {
244
+ var p = document.getElementById(paneId);
245
+ if (p && p.classList.contains('active') && p.children.length === 0) {
246
+ triggerTabLoad(paneId);
247
+ }
248
+ }, 500);
249
+
250
+ // Scroll the active sidebar item into view (mobile)
251
+ var activeLink = document.querySelector('.nav-link[data-tab="' + paneId + '"]');
252
+ if (activeLink) {
253
+ activeLink.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
254
+ }
255
+ }
256
+
257
+ /* ================================================================
258
+ Lazy data loaders — same mapping as ng-shell.js triggerTabLoad
259
+ ================================================================ */
260
+ function triggerTabLoad(tabId) {
261
+ var pane = document.getElementById(tabId);
262
+ // Open Design takeover: if the pane's OD render module is loaded, it OWNS
263
+ // the pane — clear the legacy markup and let the OD module render the
264
+ // approved design wired to live data. Fall back to the legacy loader(s)
265
+ // only when the OD module for that pane isn't present yet.
266
+ function od(fnName) {
267
+ if (pane && typeof window[fnName] === 'function') {
268
+ try { pane.innerHTML = ''; window[fnName](pane); return true; }
269
+ catch (e) { console.error('OD render failed for ' + tabId + ':', e); }
270
+ }
271
+ return false;
272
+ }
273
+ switch (tabId) {
274
+ case 'brain-pane':
275
+ if (od('odRenderBrain')) break;
276
+ if (typeof loadBrain === 'function') loadBrain();
277
+ break;
278
+ case 'graph-pane':
279
+ if (od('odRenderGraph')) break;
280
+ if (typeof loadGraph === 'function') loadGraph();
281
+ if (typeof initMemoryChat === 'function' && !document.getElementById('chat-panel')) {
282
+ initMemoryChat();
283
+ }
284
+ if (typeof loadClusters === 'function') loadClusters();
285
+ break;
286
+ case 'entities-pane':
287
+ if (od('odRenderEntities')) break;
288
+ if (typeof loadEntityExplorer === 'function') loadEntityExplorer();
289
+ break;
290
+ case 'memories-pane':
291
+ if (od('odRenderMemories')) break;
292
+ if (typeof loadMemories === 'function') loadMemories();
293
+ if (typeof loadTimeline === 'function') loadTimeline();
294
+ break;
295
+ case 'health-pane':
296
+ if (od('odRenderHealth')) break;
297
+ if (typeof loadHealthMonitor === 'function') loadHealthMonitor();
298
+ if (typeof initEventStream === 'function') initEventStream();
299
+ if (typeof loadEventStats === 'function') loadEventStats();
300
+ if (typeof loadAgents === 'function') loadAgents();
301
+ if (typeof loadIDEStatus === 'function') loadIDEStatus();
302
+ if (typeof loadMathHealth === 'function') loadMathHealth();
303
+ break;
304
+ case 'operations-pane':
305
+ if (od('odRenderOperations')) break;
306
+ if (typeof loadIngestionStatus === 'function') loadIngestionStatus();
307
+ if (typeof loadLifecycle === 'function') loadLifecycle();
308
+ if (typeof loadTrustDashboard === 'function') loadTrustDashboard();
309
+ if (typeof loadCompliance === 'function') loadCompliance();
310
+ break;
311
+ case 'skills-pane':
312
+ if (od('odRenderSkills')) break;
313
+ if (typeof loadSkillEvolution === 'function') loadSkillEvolution();
314
+ break;
315
+ case 'mesh-pane':
316
+ if (od('odRenderMesh')) break;
317
+ if (typeof loadMeshPeers === 'function') loadMeshPeers();
318
+ break;
319
+ case 'settings-pane':
320
+ if (od('odRenderSettings')) break;
321
+ if (typeof loadSettings === 'function') loadSettings();
322
+ if (typeof loadModeSettings === 'function') loadModeSettings();
323
+ if (typeof loadAutoSettings === 'function') loadAutoSettings();
324
+ if (typeof updateModeUI === 'function') updateModeUI();
325
+ break;
326
+ case 'optimize-pane':
327
+ if (od('odRenderOptimize')) break;
328
+ if (typeof initOptimizeTab === 'function') initOptimizeTab();
329
+ break;
330
+ case 'agents-pane':
331
+ if (od('odRenderAgents')) break;
332
+ break;
333
+ case 'mcp-pane':
334
+ od('odRenderMcp');
335
+ break;
336
+ case 'backup-pane':
337
+ od('odRenderBackup');
338
+ break;
339
+ }
340
+ }
341
+
342
+ /* ================================================================
343
+ Hash routing: handles both pane IDs and section anchors within panes
344
+ ================================================================ */
345
+ function handleHash() {
346
+ var hash = window.location.hash.replace('#', '');
347
+ if (!hash) return;
348
+ var el = document.getElementById(hash);
349
+ if (!el) return;
350
+
351
+ // Direct pane navigation
352
+ if (el.classList && el.classList.contains('tab-pane')) {
353
+ activateTab(hash);
354
+ return;
355
+ }
356
+
357
+ // Section within a pane (e.g. #health-section-events)
358
+ var parentPane = el.closest && el.closest('.tab-pane');
359
+ if (parentPane && parentPane.id) activateTab(parentPane.id);
360
+ try { el.scrollIntoView({ behavior: 'smooth', block: 'start' }); } catch (e) {}
361
+ }
362
+
363
+ /* ================================================================
364
+ Shell mount: called at DOMContentLoaded
365
+ ================================================================ */
366
+ window.slmShell = function (opts) {
367
+ opts = opts || {};
368
+ var active = opts.active || 'dashboard-pane';
369
+
370
+ // 1. Fill sidebar
371
+ var sb = document.getElementById('sidebar');
372
+ if (sb) sb.innerHTML = buildSidebarHTML(active);
373
+
374
+ // 2. Wire sidebar nav-link clicks and keyboard
375
+ document.querySelectorAll('.nav-link[data-tab]').forEach(function (link) {
376
+ link.addEventListener('click', function (e) {
377
+ e.preventDefault();
378
+ activateTab(this.getAttribute('data-tab'));
379
+ });
380
+ link.addEventListener('keydown', function (e) {
381
+ if (e.key === 'Enter' || e.key === ' ') {
382
+ e.preventDefault();
383
+ activateTab(this.getAttribute('data-tab'));
384
+ }
385
+ });
386
+ });
387
+
388
+ // 3. Mobile sidebar: hamburger button + scrim
389
+ var menuBtn = document.getElementById('menuBtn');
390
+ var scrim = document.getElementById('scrim');
391
+ var sidebarEl = document.getElementById('sidebar');
392
+ if (menuBtn && sidebarEl && scrim) {
393
+ menuBtn.addEventListener('click', function () {
394
+ sidebarEl.classList.toggle('open');
395
+ scrim.classList.toggle('on');
396
+ });
397
+ scrim.addEventListener('click', function () {
398
+ sidebarEl.classList.remove('open');
399
+ scrim.classList.remove('on');
400
+ });
401
+ }
402
+
403
+ // 4. Apply current theme (fills icons, syncs classes)
404
+ var t = 'dark';
405
+ try { t = localStorage.getItem(THEME_KEY) || 'dark'; } catch (e) {}
406
+ applyTheme(t);
407
+
408
+ // 5. Wire theme toggle buttons via addEventListener (NOT onclick — CSP safe)
409
+ document.querySelectorAll('[data-theme-icon]').forEach(function (btn) {
410
+ // Prevent double-binding if slmShell() is somehow called twice
411
+ if (btn.dataset.odThemeWired) return;
412
+ btn.dataset.odThemeWired = '1';
413
+ btn.addEventListener('click', function () { window.toggleDarkMode(); });
414
+ });
415
+
416
+ // 6. Fill data-ic SVG placeholders (icons in pane content, topbar, etc.)
417
+ document.querySelectorAll('[data-ic]').forEach(function (el) {
418
+ el.innerHTML = svg(el.getAttribute('data-ic'), el.getAttribute('data-ic-cls') || '');
419
+ });
420
+
421
+ // 7. Inject "Star us on GitHub" CTA into topbar (before the theme button)
422
+ var topbar = document.querySelector('.topbar');
423
+ if (topbar && !topbar.querySelector('.star-cta')) {
424
+ var themeBtn = topbar.querySelector('[data-theme-icon]');
425
+ var starLink = document.createElement('a');
426
+ starLink.className = 'star-cta';
427
+ starLink.href = LINKS.repo;
428
+ starLink.target = '_blank';
429
+ starLink.rel = 'noopener';
430
+ starLink.title = 'Star SuperLocalMemory on GitHub';
431
+ starLink.innerHTML =
432
+ svg('github', 'gh') +
433
+ "<span class='lbl'>Star us on GitHub</span>" +
434
+ "<span class='star-count'>" + svg('star') + LINKS.stars + '</span>';
435
+ if (themeBtn) topbar.insertBefore(starLink, themeBtn);
436
+ else topbar.appendChild(starLink);
437
+ }
438
+
439
+ // 8. Inject shared footer into .content (OD site-foot style)
440
+ var content = document.getElementById('main-content');
441
+ if (content && !content.querySelector('.site-foot')) {
442
+ content.insertAdjacentHTML('beforeend',
443
+ '<footer class="site-foot">' +
444
+ '<div class="foot-brand">' +
445
+ '<span class="foot-logo">' +
446
+ '<img src="static/assets/slm-icon.svg" alt="" width="22" height="22">' +
447
+ '</span>' +
448
+ '<div><b>SuperLocalMemory</b><span>by Qualixar · local-first AI memory</span></div>' +
449
+ '</div>' +
450
+ '<nav class="foot-links">' +
451
+ '<a href="' + LINKS.author + '" target="_blank" rel="noopener">Built by Varun Pratap Bhardwaj</a>' +
452
+ '<a href="' + LINKS.qualixar + '" target="_blank" rel="noopener">qualixar.com</a>' +
453
+ '<a href="' + LINKS.repo + '" target="_blank" rel="noopener">' + svg('github') + 'GitHub</a>' +
454
+ '<a href="' + LINKS.npm + '" target="_blank" rel="noopener">' + svg('pkg') + 'npm</a>' +
455
+ '<a href="' + LINKS.pip + '" target="_blank" rel="noopener">' + svg('pkg') + 'pip</a>' +
456
+ '<a href="' + LINKS.license + '" target="_blank" rel="noopener">License · AGPL-3.0</a>' +
457
+ '</nav>' +
458
+ '</footer>');
459
+ }
460
+
461
+ // 9. Move profile selector from ng-premount into sidebar footer
462
+ var profileContainer = document.getElementById('od-profile-container');
463
+ var profileSelect = document.getElementById('profile-select');
464
+ var addProfileBtn = document.getElementById('add-profile-btn');
465
+ if (profileContainer && profileSelect && profileSelect.parentNode !== profileContainer) {
466
+ 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;';
467
+ profileContainer.appendChild(profileSelect);
468
+ profileSelect.style.display = '';
469
+ if (addProfileBtn) {
470
+ addProfileBtn.className = '';
471
+ 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;';
472
+ profileContainer.appendChild(addProfileBtn);
473
+ addProfileBtn.style.display = '';
474
+ }
475
+ }
476
+
477
+ // 10. Fetch daemon version and update sidebar foot
478
+ fetch('/health', { credentials: 'same-origin' })
479
+ .then(function (r) { return r.ok ? r.json() : null; })
480
+ .then(function (data) {
481
+ var verEl = document.getElementById('od-version');
482
+ if (verEl && data && data.version) verEl.textContent = 'v' + data.version;
483
+ })
484
+ .catch(function () {
485
+ var dashVer = document.getElementById('dashboard-version');
486
+ var verEl = document.getElementById('od-version');
487
+ if (verEl && dashVer && dashVer.textContent && dashVer.textContent !== '...') {
488
+ verEl.textContent = 'v' + dashVer.textContent;
489
+ }
490
+ });
491
+
492
+ // 11. Privacy blur (screen recording mode)
493
+ window.togglePrivacyBlur = function () {
494
+ document.body.classList.toggle('ng-privacy-blur');
495
+ var icon = document.getElementById('ng-privacy-icon');
496
+ var isBlurred = document.body.classList.contains('ng-privacy-blur');
497
+ if (icon) icon.className = isBlurred ? 'bi bi-eye' : 'bi bi-eye-slash';
498
+ };
499
+ if (window.location.search.indexOf('blur=1') >= 0) {
500
+ document.body.classList.add('ng-privacy-blur');
501
+ }
502
+
503
+ // 12. Activate the initial pane (fires triggerTabLoad for dashboard etc.)
504
+ activateTab(active);
505
+ };
506
+
507
+ /* ================================================================
508
+ Chart helpers — ported from OD assets/shell.js
509
+ ================================================================ */
510
+
511
+ // Sparkline: array of numbers → inline SVG string
512
+ window.slmSpark = function (data, opts) {
513
+ opts = opts || {};
514
+ var w = opts.w || 200, h = opts.h || 60, pad = 3;
515
+ var min = Math.min.apply(null, data);
516
+ var max = Math.max.apply(null, data);
517
+ var rng = (max - min) || 1;
518
+ var stepX = (w - pad * 2) / (data.length - 1);
519
+ var pts = data.map(function (v, i) {
520
+ return [
521
+ pad + i * stepX,
522
+ h - pad - ((v - min) / rng) * (h - pad * 2),
523
+ ];
524
+ });
525
+ var line = pts.map(function (p, i) {
526
+ return (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1);
527
+ }).join(' ');
528
+ var area =
529
+ line +
530
+ ' L' + pts[pts.length - 1][0].toFixed(1) + ' ' + h +
531
+ ' L' + pts[0][0].toFixed(1) + ' ' + h + ' Z';
532
+ var id = 'g' + Math.random().toString(36).slice(2, 7);
533
+ var stroke = opts.color || 'var(--violet)';
534
+ return (
535
+ '<svg class="spark-lg" viewBox="0 0 ' + w + ' ' + h + '" preserveAspectRatio="none">' +
536
+ '<defs><linearGradient id="' + id + '" x1="0" y1="0" x2="0" y2="1">' +
537
+ '<stop offset="0" stop-color="' + stroke + '" stop-opacity="0.28"/>' +
538
+ '<stop offset="1" stop-color="' + stroke + '" stop-opacity="0"/>' +
539
+ '</linearGradient></defs>' +
540
+ '<path d="' + area + '" fill="url(#' + id + ')"/>' +
541
+ '<path d="' + line + '" fill="none" stroke="' + stroke + '" stroke-width="2" vector-effect="non-scaling-stroke"/>' +
542
+ '</svg>'
543
+ );
544
+ };
545
+
546
+ // GitHub-style contribution heatmap
547
+ window.slmHeatmap = function (el, weeks) {
548
+ weeks = weeks || 26;
549
+ var html = '';
550
+ for (var w = 0; w < weeks; w++) {
551
+ for (var d = 0; d < 7; d++) {
552
+ var recency = w / weeks;
553
+ var base = Math.random() * 0.5 + recency * 0.6;
554
+ var l = base < 0.35 ? 0 : base < 0.55 ? 1 : base < 0.72 ? 2 : base < 0.88 ? 3 : 4;
555
+ html += '<i data-l="' + l + '" title="reward signal"></i>';
556
+ }
557
+ }
558
+ el.innerHTML = html;
559
+ };
560
+
561
+ // Bar chart helper
562
+ window.slmBars = function (el, data) {
563
+ var maxVal = Math.max.apply(null, data) || 1;
564
+ el.innerHTML = data.map(function (v) {
565
+ return '<i style="height:' + Math.max(4, (v / maxVal) * 100) + '%" title="' + v + '"></i>';
566
+ }).join('');
567
+ };
568
+
569
+ /* ================================================================
570
+ DOMContentLoaded bootstrap
571
+ ================================================================ */
572
+ document.addEventListener('DOMContentLoaded', function () {
573
+ // Determine initial active pane from URL hash (if valid tab pane)
574
+ var active = 'dashboard-pane';
575
+ var hash = window.location.hash.replace('#', '');
576
+ if (hash) {
577
+ var el = document.getElementById(hash);
578
+ if (el && el.classList && el.classList.contains('tab-pane')) {
579
+ active = hash;
580
+ } else {
581
+ // Stale section anchor from a previous session — strip it so
582
+ // we always land on Dashboard, not a blank screen.
583
+ try { history.replaceState(null, '', window.location.pathname); } catch (e) {}
584
+ }
585
+ }
586
+
587
+ window.slmShell({ active: active });
588
+
589
+ // Wire subsequent hash changes (user-triggered navigation, not initial load)
590
+ window.addEventListener('hashchange', handleHash);
591
+ });
592
+
593
+ }());