@jungjaehoon/mama-os 0.1.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 (356) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +643 -0
  3. package/dist/agent/agent-loop.d.ts +98 -0
  4. package/dist/agent/agent-loop.d.ts.map +1 -0
  5. package/dist/agent/agent-loop.js +417 -0
  6. package/dist/agent/agent-loop.js.map +1 -0
  7. package/dist/agent/auto-recall.d.ts +48 -0
  8. package/dist/agent/auto-recall.d.ts.map +1 -0
  9. package/dist/agent/auto-recall.js +178 -0
  10. package/dist/agent/auto-recall.js.map +1 -0
  11. package/dist/agent/claude-cli-wrapper.d.ts +130 -0
  12. package/dist/agent/claude-cli-wrapper.d.ts.map +1 -0
  13. package/dist/agent/claude-cli-wrapper.js +227 -0
  14. package/dist/agent/claude-cli-wrapper.js.map +1 -0
  15. package/dist/agent/claude-client.d.ts +50 -0
  16. package/dist/agent/claude-client.d.ts.map +1 -0
  17. package/dist/agent/claude-client.js +214 -0
  18. package/dist/agent/claude-client.js.map +1 -0
  19. package/dist/agent/gateway-tool-executor.d.ts +75 -0
  20. package/dist/agent/gateway-tool-executor.d.ts.map +1 -0
  21. package/dist/agent/gateway-tool-executor.js +348 -0
  22. package/dist/agent/gateway-tool-executor.js.map +1 -0
  23. package/dist/agent/index.d.ts +13 -0
  24. package/dist/agent/index.d.ts.map +1 -0
  25. package/dist/agent/index.js +18 -0
  26. package/dist/agent/index.js.map +1 -0
  27. package/dist/agent/mcp-executor.d.ts +75 -0
  28. package/dist/agent/mcp-executor.d.ts.map +1 -0
  29. package/dist/agent/mcp-executor.js +307 -0
  30. package/dist/agent/mcp-executor.js.map +1 -0
  31. package/dist/agent/session-pool.d.ts +148 -0
  32. package/dist/agent/session-pool.d.ts.map +1 -0
  33. package/dist/agent/session-pool.js +272 -0
  34. package/dist/agent/session-pool.js.map +1 -0
  35. package/dist/agent/streaming-callback-manager.d.ts +85 -0
  36. package/dist/agent/streaming-callback-manager.d.ts.map +1 -0
  37. package/dist/agent/streaming-callback-manager.js +103 -0
  38. package/dist/agent/streaming-callback-manager.js.map +1 -0
  39. package/dist/agent/types.d.ts +437 -0
  40. package/dist/agent/types.d.ts.map +1 -0
  41. package/dist/agent/types.js +29 -0
  42. package/dist/agent/types.js.map +1 -0
  43. package/dist/api/cron-handler.d.ts +44 -0
  44. package/dist/api/cron-handler.d.ts.map +1 -0
  45. package/dist/api/cron-handler.js +195 -0
  46. package/dist/api/cron-handler.js.map +1 -0
  47. package/dist/api/error-handler.d.ts +22 -0
  48. package/dist/api/error-handler.d.ts.map +1 -0
  49. package/dist/api/error-handler.js +104 -0
  50. package/dist/api/error-handler.js.map +1 -0
  51. package/dist/api/heartbeat-handler.d.ts +49 -0
  52. package/dist/api/heartbeat-handler.d.ts.map +1 -0
  53. package/dist/api/heartbeat-handler.js +91 -0
  54. package/dist/api/heartbeat-handler.js.map +1 -0
  55. package/dist/api/index.d.ts +61 -0
  56. package/dist/api/index.d.ts.map +1 -0
  57. package/dist/api/index.js +145 -0
  58. package/dist/api/index.js.map +1 -0
  59. package/dist/api/types.d.ts +156 -0
  60. package/dist/api/types.d.ts.map +1 -0
  61. package/dist/api/types.js +62 -0
  62. package/dist/api/types.js.map +1 -0
  63. package/dist/auth/index.d.ts +7 -0
  64. package/dist/auth/index.d.ts.map +1 -0
  65. package/dist/auth/index.js +11 -0
  66. package/dist/auth/index.js.map +1 -0
  67. package/dist/auth/oauth-manager.d.ts +59 -0
  68. package/dist/auth/oauth-manager.d.ts.map +1 -0
  69. package/dist/auth/oauth-manager.js +237 -0
  70. package/dist/auth/oauth-manager.js.map +1 -0
  71. package/dist/auth/types.d.ts +92 -0
  72. package/dist/auth/types.d.ts.map +1 -0
  73. package/dist/auth/types.js +23 -0
  74. package/dist/auth/types.js.map +1 -0
  75. package/dist/cli/commands/init.d.ts +19 -0
  76. package/dist/cli/commands/init.d.ts.map +1 -0
  77. package/dist/cli/commands/init.js +155 -0
  78. package/dist/cli/commands/init.js.map +1 -0
  79. package/dist/cli/commands/run.d.ts +19 -0
  80. package/dist/cli/commands/run.d.ts.map +1 -0
  81. package/dist/cli/commands/run.js +89 -0
  82. package/dist/cli/commands/run.js.map +1 -0
  83. package/dist/cli/commands/setup.d.ts +19 -0
  84. package/dist/cli/commands/setup.d.ts.map +1 -0
  85. package/dist/cli/commands/setup.js +134 -0
  86. package/dist/cli/commands/setup.js.map +1 -0
  87. package/dist/cli/commands/start.d.ts +24 -0
  88. package/dist/cli/commands/start.d.ts.map +1 -0
  89. package/dist/cli/commands/start.js +1073 -0
  90. package/dist/cli/commands/start.js.map +1 -0
  91. package/dist/cli/commands/status.d.ts +10 -0
  92. package/dist/cli/commands/status.d.ts.map +1 -0
  93. package/dist/cli/commands/status.js +85 -0
  94. package/dist/cli/commands/status.js.map +1 -0
  95. package/dist/cli/commands/stop.d.ts +10 -0
  96. package/dist/cli/commands/stop.d.ts.map +1 -0
  97. package/dist/cli/commands/stop.js +65 -0
  98. package/dist/cli/commands/stop.js.map +1 -0
  99. package/dist/cli/config/config-manager.d.ts +51 -0
  100. package/dist/cli/config/config-manager.d.ts.map +1 -0
  101. package/dist/cli/config/config-manager.js +216 -0
  102. package/dist/cli/config/config-manager.js.map +1 -0
  103. package/dist/cli/config/types.d.ts +172 -0
  104. package/dist/cli/config/types.d.ts.map +1 -0
  105. package/dist/cli/config/types.js +48 -0
  106. package/dist/cli/config/types.js.map +1 -0
  107. package/dist/cli/index.d.ts +8 -0
  108. package/dist/cli/index.d.ts.map +1 -0
  109. package/dist/cli/index.js +92 -0
  110. package/dist/cli/index.js.map +1 -0
  111. package/dist/cli/utils/pid-manager.d.ts +66 -0
  112. package/dist/cli/utils/pid-manager.d.ts.map +1 -0
  113. package/dist/cli/utils/pid-manager.js +167 -0
  114. package/dist/cli/utils/pid-manager.js.map +1 -0
  115. package/dist/concurrency/index.d.ts +13 -0
  116. package/dist/concurrency/index.d.ts.map +1 -0
  117. package/dist/concurrency/index.js +22 -0
  118. package/dist/concurrency/index.js.map +1 -0
  119. package/dist/concurrency/lane-manager.d.ts +113 -0
  120. package/dist/concurrency/lane-manager.d.ts.map +1 -0
  121. package/dist/concurrency/lane-manager.js +245 -0
  122. package/dist/concurrency/lane-manager.js.map +1 -0
  123. package/dist/concurrency/session-key.d.ts +41 -0
  124. package/dist/concurrency/session-key.d.ts.map +1 -0
  125. package/dist/concurrency/session-key.js +61 -0
  126. package/dist/concurrency/session-key.js.map +1 -0
  127. package/dist/concurrency/types.d.ts +69 -0
  128. package/dist/concurrency/types.d.ts.map +1 -0
  129. package/dist/concurrency/types.js +16 -0
  130. package/dist/concurrency/types.js.map +1 -0
  131. package/dist/gateways/channel-history.d.ts +102 -0
  132. package/dist/gateways/channel-history.d.ts.map +1 -0
  133. package/dist/gateways/channel-history.js +181 -0
  134. package/dist/gateways/channel-history.js.map +1 -0
  135. package/dist/gateways/context-injector.d.ts +74 -0
  136. package/dist/gateways/context-injector.d.ts.map +1 -0
  137. package/dist/gateways/context-injector.js +121 -0
  138. package/dist/gateways/context-injector.js.map +1 -0
  139. package/dist/gateways/discord.d.ts +122 -0
  140. package/dist/gateways/discord.d.ts.map +1 -0
  141. package/dist/gateways/discord.js +602 -0
  142. package/dist/gateways/discord.js.map +1 -0
  143. package/dist/gateways/index.d.ts +30 -0
  144. package/dist/gateways/index.d.ts.map +1 -0
  145. package/dist/gateways/index.js +49 -0
  146. package/dist/gateways/index.js.map +1 -0
  147. package/dist/gateways/message-router.d.ts +116 -0
  148. package/dist/gateways/message-router.d.ts.map +1 -0
  149. package/dist/gateways/message-router.js +315 -0
  150. package/dist/gateways/message-router.js.map +1 -0
  151. package/dist/gateways/message-splitter.d.ts +54 -0
  152. package/dist/gateways/message-splitter.d.ts.map +1 -0
  153. package/dist/gateways/message-splitter.js +146 -0
  154. package/dist/gateways/message-splitter.js.map +1 -0
  155. package/dist/gateways/plugin-loader.d.ts +76 -0
  156. package/dist/gateways/plugin-loader.d.ts.map +1 -0
  157. package/dist/gateways/plugin-loader.js +221 -0
  158. package/dist/gateways/plugin-loader.js.map +1 -0
  159. package/dist/gateways/session-store.d.ts +77 -0
  160. package/dist/gateways/session-store.d.ts.map +1 -0
  161. package/dist/gateways/session-store.js +233 -0
  162. package/dist/gateways/session-store.js.map +1 -0
  163. package/dist/gateways/slack.d.ts +90 -0
  164. package/dist/gateways/slack.d.ts.map +1 -0
  165. package/dist/gateways/slack.js +281 -0
  166. package/dist/gateways/slack.js.map +1 -0
  167. package/dist/gateways/telegram.d.ts +79 -0
  168. package/dist/gateways/telegram.d.ts.map +1 -0
  169. package/dist/gateways/telegram.js +207 -0
  170. package/dist/gateways/telegram.js.map +1 -0
  171. package/dist/gateways/types.d.ts +340 -0
  172. package/dist/gateways/types.d.ts.map +1 -0
  173. package/dist/gateways/types.js +6 -0
  174. package/dist/gateways/types.js.map +1 -0
  175. package/dist/index.d.ts +7 -0
  176. package/dist/index.d.ts.map +1 -0
  177. package/dist/index.js +26 -0
  178. package/dist/index.js.map +1 -0
  179. package/dist/memory/memory-logger.d.ts +47 -0
  180. package/dist/memory/memory-logger.d.ts.map +1 -0
  181. package/dist/memory/memory-logger.js +126 -0
  182. package/dist/memory/memory-logger.js.map +1 -0
  183. package/dist/onboarding/all-tools.d.ts +18 -0
  184. package/dist/onboarding/all-tools.d.ts.map +1 -0
  185. package/dist/onboarding/all-tools.js +149 -0
  186. package/dist/onboarding/all-tools.js.map +1 -0
  187. package/dist/onboarding/autonomous-discovery-tools.d.ts +13 -0
  188. package/dist/onboarding/autonomous-discovery-tools.d.ts.map +1 -0
  189. package/dist/onboarding/autonomous-discovery-tools.js +268 -0
  190. package/dist/onboarding/autonomous-discovery-tools.js.map +1 -0
  191. package/dist/onboarding/bootstrap-template.d.ts +5 -0
  192. package/dist/onboarding/bootstrap-template.d.ts.map +1 -0
  193. package/dist/onboarding/bootstrap-template.js +142 -0
  194. package/dist/onboarding/bootstrap-template.js.map +1 -0
  195. package/dist/onboarding/complete-autonomous-prompt.d.ts +13 -0
  196. package/dist/onboarding/complete-autonomous-prompt.d.ts.map +1 -0
  197. package/dist/onboarding/complete-autonomous-prompt.js +1220 -0
  198. package/dist/onboarding/complete-autonomous-prompt.js.map +1 -0
  199. package/dist/onboarding/onboarding-state.d.ts +70 -0
  200. package/dist/onboarding/onboarding-state.d.ts.map +1 -0
  201. package/dist/onboarding/onboarding-state.js +184 -0
  202. package/dist/onboarding/onboarding-state.js.map +1 -0
  203. package/dist/onboarding/personality-quiz.d.ts +35 -0
  204. package/dist/onboarding/personality-quiz.d.ts.map +1 -0
  205. package/dist/onboarding/personality-quiz.js +219 -0
  206. package/dist/onboarding/personality-quiz.js.map +1 -0
  207. package/dist/onboarding/phase-5-summary.d.ts +22 -0
  208. package/dist/onboarding/phase-5-summary.d.ts.map +1 -0
  209. package/dist/onboarding/phase-5-summary.js +151 -0
  210. package/dist/onboarding/phase-5-summary.js.map +1 -0
  211. package/dist/onboarding/phase-6-security.d.ts +33 -0
  212. package/dist/onboarding/phase-6-security.d.ts.map +1 -0
  213. package/dist/onboarding/phase-6-security.js +473 -0
  214. package/dist/onboarding/phase-6-security.js.map +1 -0
  215. package/dist/onboarding/phase-7-integrations.d.ts +66 -0
  216. package/dist/onboarding/phase-7-integrations.d.ts.map +1 -0
  217. package/dist/onboarding/phase-7-integrations.js +619 -0
  218. package/dist/onboarding/phase-7-integrations.js.map +1 -0
  219. package/dist/onboarding/phase-8-demo.d.ts +43 -0
  220. package/dist/onboarding/phase-8-demo.d.ts.map +1 -0
  221. package/dist/onboarding/phase-8-demo.js +346 -0
  222. package/dist/onboarding/phase-8-demo.js.map +1 -0
  223. package/dist/onboarding/phase-9-finalization.d.ts +22 -0
  224. package/dist/onboarding/phase-9-finalization.d.ts.map +1 -0
  225. package/dist/onboarding/phase-9-finalization.js +375 -0
  226. package/dist/onboarding/phase-9-finalization.js.map +1 -0
  227. package/dist/onboarding/ritual-prompt.d.ts +2 -0
  228. package/dist/onboarding/ritual-prompt.d.ts.map +1 -0
  229. package/dist/onboarding/ritual-prompt.js +285 -0
  230. package/dist/onboarding/ritual-prompt.js.map +1 -0
  231. package/dist/onboarding/ritual-tools.d.ts +13 -0
  232. package/dist/onboarding/ritual-tools.d.ts.map +1 -0
  233. package/dist/onboarding/ritual-tools.js +93 -0
  234. package/dist/onboarding/ritual-tools.js.map +1 -0
  235. package/dist/runners/cli-runner.d.ts +59 -0
  236. package/dist/runners/cli-runner.d.ts.map +1 -0
  237. package/dist/runners/cli-runner.js +190 -0
  238. package/dist/runners/cli-runner.js.map +1 -0
  239. package/dist/runners/index.d.ts +11 -0
  240. package/dist/runners/index.d.ts.map +1 -0
  241. package/dist/runners/index.js +15 -0
  242. package/dist/runners/index.js.map +1 -0
  243. package/dist/runners/types.d.ts +81 -0
  244. package/dist/runners/types.d.ts.map +1 -0
  245. package/dist/runners/types.js +31 -0
  246. package/dist/runners/types.js.map +1 -0
  247. package/dist/scheduler/cron-scheduler.d.ts +115 -0
  248. package/dist/scheduler/cron-scheduler.d.ts.map +1 -0
  249. package/dist/scheduler/cron-scheduler.js +320 -0
  250. package/dist/scheduler/cron-scheduler.js.map +1 -0
  251. package/dist/scheduler/heartbeat.d.ts +53 -0
  252. package/dist/scheduler/heartbeat.d.ts.map +1 -0
  253. package/dist/scheduler/heartbeat.js +160 -0
  254. package/dist/scheduler/heartbeat.js.map +1 -0
  255. package/dist/scheduler/index.d.ts +22 -0
  256. package/dist/scheduler/index.d.ts.map +1 -0
  257. package/dist/scheduler/index.js +31 -0
  258. package/dist/scheduler/index.js.map +1 -0
  259. package/dist/scheduler/job-lock.d.ts +85 -0
  260. package/dist/scheduler/job-lock.d.ts.map +1 -0
  261. package/dist/scheduler/job-lock.js +137 -0
  262. package/dist/scheduler/job-lock.js.map +1 -0
  263. package/dist/scheduler/recovery.d.ts +78 -0
  264. package/dist/scheduler/recovery.d.ts.map +1 -0
  265. package/dist/scheduler/recovery.js +124 -0
  266. package/dist/scheduler/recovery.js.map +1 -0
  267. package/dist/scheduler/schedule-store.d.ts +112 -0
  268. package/dist/scheduler/schedule-store.d.ts.map +1 -0
  269. package/dist/scheduler/schedule-store.js +259 -0
  270. package/dist/scheduler/schedule-store.js.map +1 -0
  271. package/dist/scheduler/token-keep-alive.d.ts +49 -0
  272. package/dist/scheduler/token-keep-alive.d.ts.map +1 -0
  273. package/dist/scheduler/token-keep-alive.js +102 -0
  274. package/dist/scheduler/token-keep-alive.js.map +1 -0
  275. package/dist/scheduler/types.d.ts +96 -0
  276. package/dist/scheduler/types.d.ts.map +1 -0
  277. package/dist/scheduler/types.js +21 -0
  278. package/dist/scheduler/types.js.map +1 -0
  279. package/dist/setup/setup-prompt.d.ts +2 -0
  280. package/dist/setup/setup-prompt.d.ts.map +1 -0
  281. package/dist/setup/setup-prompt.js +138 -0
  282. package/dist/setup/setup-prompt.js.map +1 -0
  283. package/dist/setup/setup-server.d.ts +8 -0
  284. package/dist/setup/setup-server.d.ts.map +1 -0
  285. package/dist/setup/setup-server.js +71 -0
  286. package/dist/setup/setup-server.js.map +1 -0
  287. package/dist/setup/setup-tools.d.ts +13 -0
  288. package/dist/setup/setup-tools.d.ts.map +1 -0
  289. package/dist/setup/setup-tools.js +103 -0
  290. package/dist/setup/setup-tools.js.map +1 -0
  291. package/dist/setup/setup-websocket.d.ts +6 -0
  292. package/dist/setup/setup-websocket.d.ts.map +1 -0
  293. package/dist/setup/setup-websocket.js +312 -0
  294. package/dist/setup/setup-websocket.js.map +1 -0
  295. package/dist/skills/index.d.ts +10 -0
  296. package/dist/skills/index.d.ts.map +1 -0
  297. package/dist/skills/index.js +26 -0
  298. package/dist/skills/index.js.map +1 -0
  299. package/dist/skills/skill-executor.d.ts +48 -0
  300. package/dist/skills/skill-executor.d.ts.map +1 -0
  301. package/dist/skills/skill-executor.js +483 -0
  302. package/dist/skills/skill-executor.js.map +1 -0
  303. package/dist/skills/skill-loader.d.ts +40 -0
  304. package/dist/skills/skill-loader.d.ts.map +1 -0
  305. package/dist/skills/skill-loader.js +225 -0
  306. package/dist/skills/skill-loader.js.map +1 -0
  307. package/dist/skills/skill-matcher.d.ts +33 -0
  308. package/dist/skills/skill-matcher.d.ts.map +1 -0
  309. package/dist/skills/skill-matcher.js +190 -0
  310. package/dist/skills/skill-matcher.js.map +1 -0
  311. package/dist/skills/types.d.ts +123 -0
  312. package/dist/skills/types.d.ts.map +1 -0
  313. package/dist/skills/types.js +12 -0
  314. package/dist/skills/types.js.map +1 -0
  315. package/dist/tools/browser-tool.d.ts +149 -0
  316. package/dist/tools/browser-tool.d.ts.map +1 -0
  317. package/dist/tools/browser-tool.js +257 -0
  318. package/dist/tools/browser-tool.js.map +1 -0
  319. package/package.json +84 -0
  320. package/public/favicon.ico +0 -0
  321. package/public/setup.html +1026 -0
  322. package/public/viewer/icons/icon-192.png +0 -0
  323. package/public/viewer/icons/icon-512.png +0 -0
  324. package/public/viewer/js/modules/chat.js +1587 -0
  325. package/public/viewer/js/modules/dashboard.js +275 -0
  326. package/public/viewer/js/modules/graph.js +997 -0
  327. package/public/viewer/js/modules/memory.js +353 -0
  328. package/public/viewer/js/modules/settings.js +255 -0
  329. package/public/viewer/js/utils/api.js +169 -0
  330. package/public/viewer/js/utils/dom.js +92 -0
  331. package/public/viewer/js/utils/format.js +192 -0
  332. package/public/viewer/manifest.json +26 -0
  333. package/public/viewer/sw.js +131 -0
  334. package/public/viewer/viewer.css +500 -0
  335. package/public/viewer/viewer.html +1535 -0
  336. package/scripts/postinstall.js +118 -0
  337. package/templates/skills/document-analyze.md +63 -0
  338. package/templates/skills/heartbeat-report.md +75 -0
  339. package/templates/skills/image-translate.md +67 -0
  340. package/templates/workspace/skill-forge/DESIGN.md +115 -0
  341. package/templates/workspace/skill-forge/agents/architect.ts +295 -0
  342. package/templates/workspace/skill-forge/agents/developer.ts +364 -0
  343. package/templates/workspace/skill-forge/agents/qa.ts +313 -0
  344. package/templates/workspace/skill-forge/claude-api.ts +353 -0
  345. package/templates/workspace/skill-forge/discord-ui.ts +580 -0
  346. package/templates/workspace/skill-forge/error-handler.ts +354 -0
  347. package/templates/workspace/skill-forge/mama-integration.ts +357 -0
  348. package/templates/workspace/skill-forge/orchestrator.ts +495 -0
  349. package/templates/workspace/skill-forge/output/generated-skills/skills/hello-world/README.md +24 -0
  350. package/templates/workspace/skill-forge/output/generated-skills/skills/hello-world/index.ts +79 -0
  351. package/templates/workspace/skill-forge/output/generated-skills/skills/hello-world/types.ts +17 -0
  352. package/templates/workspace/skill-forge/package.json +21 -0
  353. package/templates/workspace/skill-forge/state/session.json +132 -0
  354. package/templates/workspace/skill-forge/test-e2e.ts +139 -0
  355. package/templates/workspace/skill-forge/tsconfig.json +20 -0
  356. package/templates/workspace/skill-forge/types.ts +159 -0
@@ -0,0 +1,997 @@
1
+ /* global marked */
2
+ /**
3
+ * Graph Module - Decision Graph Visualization
4
+ * @module modules/graph
5
+ * @version 1.0.0
6
+ *
7
+ * Handles Graph visualization using vis.js:
8
+ * - Network initialization and rendering
9
+ * - Node/edge styling and clustering
10
+ * - Search and filter functionality
11
+ * - Detail panel for node information
12
+ * - BFS traversal for connected nodes
13
+ */
14
+
15
+ /* eslint-env browser */
16
+ /* global vis */
17
+
18
+ import { escapeHtml, debounce, showToast } from '../utils/dom.js';
19
+ import { API } from '../utils/api.js';
20
+
21
+ /**
22
+ * Graph Module Class
23
+ */
24
+ export class GraphModule {
25
+ constructor() {
26
+ // Network state
27
+ this.network = null;
28
+ this.graphData = { nodes: [], edges: [], meta: {} };
29
+ this.currentNodeId = null;
30
+ this.adjacencyList = new Map();
31
+
32
+ // Search state
33
+ this.searchMatches = [];
34
+ this.currentSearchIndex = 0;
35
+ this.debouncedSearch = debounce(() => this.search(), 300);
36
+
37
+ // Color management
38
+ this.topicColors = {};
39
+ this.colorPalette = [
40
+ '#6366f1',
41
+ '#8b5cf6',
42
+ '#a855f7',
43
+ '#d946ef',
44
+ '#ec4899',
45
+ '#f43f5e',
46
+ '#ef4444',
47
+ '#f97316',
48
+ '#f59e0b',
49
+ '#eab308',
50
+ '#84cc16',
51
+ '#22c55e',
52
+ '#10b981',
53
+ '#14b8a6',
54
+ '#06b6d4',
55
+ '#0ea5e9',
56
+ ];
57
+ this.colorIndex = 0;
58
+
59
+ // Edge styles
60
+ this.edgeStyles = {
61
+ supersedes: { color: '#848484', dashes: false },
62
+ builds_on: { color: '#457b9d', dashes: [5, 5] },
63
+ debates: { color: '#e63946', dashes: [5, 5] },
64
+ synthesizes: { color: '#9b59b6', width: 3, dashes: false },
65
+ };
66
+ }
67
+
68
+ // =============================================
69
+ // Data Loading
70
+ // =============================================
71
+
72
+ /**
73
+ * Fetch graph data from API
74
+ */
75
+ async fetchData() {
76
+ try {
77
+ this.graphData = await API.getGraph();
78
+ console.log('[MAMA] Graph data loaded:', this.graphData.meta);
79
+ return this.graphData;
80
+ } catch (error) {
81
+ console.error('[MAMA] Failed to fetch graph:', error);
82
+ throw error;
83
+ }
84
+ }
85
+
86
+ // =============================================
87
+ // Graph Initialization
88
+ // =============================================
89
+
90
+ /**
91
+ * Initialize vis-network
92
+ */
93
+ init(data) {
94
+ const container = document.getElementById('graph-canvas');
95
+ if (!container) {
96
+ console.error('[MAMA] graph-canvas element not found');
97
+ return;
98
+ }
99
+
100
+ // Ensure container has dimensions for vis-network
101
+ if (container.offsetHeight === 0) {
102
+ container.style.minHeight = '400px';
103
+ }
104
+
105
+ console.log(
106
+ '[MAMA] Graph canvas dimensions:',
107
+ container.offsetWidth,
108
+ 'x',
109
+ container.offsetHeight
110
+ );
111
+
112
+ this.graphData = data;
113
+
114
+ // Build adjacency list for BFS
115
+ this.buildAdjacencyList(data.edges);
116
+
117
+ // Calculate connection counts for sizing
118
+ const connectionCounts = this.calculateConnectionCounts(data.nodes, data.edges);
119
+
120
+ // Map nodes to vis-network format
121
+ const nodes = data.nodes.map((n) => ({
122
+ id: n.id,
123
+ label: n.topic || n.id.substring(0, 20),
124
+ title: this.createNodeTooltip(n),
125
+ color: {
126
+ background: this.getTopicColor(n.topic),
127
+ border: this.getOutcomeBorderColor(n.outcome),
128
+ highlight: { background: this.getTopicColor(n.topic), border: '#fff' },
129
+ },
130
+ size: this.getNodeSize(connectionCounts[n.id] || 0),
131
+ font: { color: '#fff', size: 12 },
132
+ borderWidth: 3,
133
+ data: n,
134
+ }));
135
+
136
+ // Map edges to vis-network format
137
+ const edges = data.edges.map((e) => {
138
+ const style = this.getEdgeStyle(e.relationship);
139
+ return {
140
+ from: e.from,
141
+ to: e.to,
142
+ arrows: { to: { enabled: true, scaleFactor: 0.5 } },
143
+ color: style.color,
144
+ dashes: style.dashes,
145
+ width: style.width || 2,
146
+ title: e.relationship,
147
+ };
148
+ });
149
+
150
+ const networkData = {
151
+ nodes: new vis.DataSet(nodes),
152
+ edges: new vis.DataSet(edges),
153
+ };
154
+
155
+ const options = {
156
+ nodes: {
157
+ shape: 'dot',
158
+ scaling: { min: 10, max: 30 },
159
+ },
160
+ edges: {
161
+ smooth: { type: 'continuous', roundness: 0.5 },
162
+ width: 2,
163
+ },
164
+ physics: {
165
+ enabled: true,
166
+ barnesHut: {
167
+ gravitationalConstant: -8000,
168
+ centralGravity: 0.3,
169
+ springLength: 150,
170
+ springConstant: 0.04,
171
+ damping: 0.09,
172
+ avoidOverlap: 0.5,
173
+ },
174
+ stabilization: {
175
+ enabled: true,
176
+ iterations: 200,
177
+ updateInterval: 50,
178
+ },
179
+ },
180
+ interaction: {
181
+ hover: true,
182
+ tooltipDelay: 200,
183
+ zoomView: true,
184
+ dragView: true,
185
+ },
186
+ };
187
+
188
+ this.network = new vis.Network(container, networkData, options);
189
+
190
+ // Event handlers
191
+ this.network.on('click', (params) => {
192
+ try {
193
+ if (params.nodes.length > 0) {
194
+ const nodeId = params.nodes[0];
195
+ const node = data.nodes.find((n) => n.id === nodeId);
196
+ if (node) {
197
+ console.log('[MAMA] Node clicked:', nodeId, node);
198
+ this.showDetail(node);
199
+ this.highlightConnectedNodes(nodeId);
200
+ }
201
+ } else {
202
+ this.closeDetail();
203
+ this.resetNodeHighlight();
204
+ }
205
+ } catch (error) {
206
+ console.error('[MAMA] Error handling click:', error);
207
+ console.error('[MAMA] Error stack:', error.stack);
208
+ }
209
+ });
210
+
211
+ this.network.on('stabilized', () => {
212
+ const loadingEl = document.getElementById('graph-loading');
213
+ if (loadingEl) {
214
+ loadingEl.style.display = 'none';
215
+ }
216
+ console.log('[MAMA] Graph stabilized');
217
+ });
218
+
219
+ // Backup: hide loading after 3 seconds even if stabilization doesn't complete
220
+ setTimeout(() => {
221
+ const loadingEl = document.getElementById('graph-loading');
222
+ if (loadingEl && loadingEl.style.display !== 'none') {
223
+ loadingEl.style.display = 'none';
224
+ console.log('[MAMA] Graph loading hidden by timeout');
225
+ }
226
+ }, 3000);
227
+
228
+ // Populate topic filter
229
+ const topics = [...new Set(data.nodes.map((n) => n.topic))].sort();
230
+ this.populateTopicFilter(topics);
231
+
232
+ console.log('[MAMA] Graph initialized with', nodes.length, 'nodes and', edges.length, 'edges');
233
+ }
234
+
235
+ // =============================================
236
+ // Styling Utilities
237
+ // =============================================
238
+
239
+ /**
240
+ * Get color for topic
241
+ */
242
+ getTopicColor(topic) {
243
+ if (!this.topicColors[topic]) {
244
+ this.topicColors[topic] = this.colorPalette[this.colorIndex % this.colorPalette.length];
245
+ this.colorIndex++;
246
+ }
247
+ return this.topicColors[topic];
248
+ }
249
+
250
+ /**
251
+ * Get border color based on outcome
252
+ */
253
+ getOutcomeBorderColor(outcome) {
254
+ switch (outcome?.toLowerCase()) {
255
+ case 'success':
256
+ return '#22c55e';
257
+ case 'failed':
258
+ return '#ef4444';
259
+ case 'partial':
260
+ return '#f59e0b';
261
+ default:
262
+ return '#4a4a6a';
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Get edge style by relationship type
268
+ */
269
+ getEdgeStyle(relationship) {
270
+ return this.edgeStyles[relationship] || { color: '#4a4a6a', dashes: false };
271
+ }
272
+
273
+ /**
274
+ * Get node size based on connection count
275
+ */
276
+ getNodeSize(connectionCount) {
277
+ if (connectionCount <= 2) {
278
+ return 12;
279
+ }
280
+ if (connectionCount <= 5) {
281
+ return 18;
282
+ }
283
+ if (connectionCount <= 10) {
284
+ return 24;
285
+ }
286
+ return 30;
287
+ }
288
+
289
+ /**
290
+ * Create node tooltip
291
+ */
292
+ createNodeTooltip(node) {
293
+ return `
294
+ <strong>${escapeHtml(node.topic || 'Unknown')}</strong><br>
295
+ Decision: ${escapeHtml((node.decision || '').substring(0, 100))}...<br>
296
+ Outcome: ${node.outcome || 'PENDING'}<br>
297
+ Confidence: ${Math.round((node.confidence || 0) * 100)}%
298
+ `;
299
+ }
300
+
301
+ // =============================================
302
+ // Data Processing
303
+ // =============================================
304
+
305
+ /**
306
+ * Build adjacency list for BFS
307
+ */
308
+ buildAdjacencyList(edges) {
309
+ this.adjacencyList = new Map();
310
+
311
+ edges.forEach((edge) => {
312
+ if (!this.adjacencyList.has(edge.from)) {
313
+ this.adjacencyList.set(edge.from, []);
314
+ }
315
+ if (!this.adjacencyList.has(edge.to)) {
316
+ this.adjacencyList.set(edge.to, []);
317
+ }
318
+ this.adjacencyList.get(edge.from).push(edge.to);
319
+ this.adjacencyList.get(edge.to).push(edge.from);
320
+ });
321
+
322
+ console.log('[MAMA] Adjacency list built with', this.adjacencyList.size, 'nodes');
323
+ }
324
+
325
+ /**
326
+ * Calculate connection count for each node
327
+ */
328
+ calculateConnectionCounts(nodes, edges) {
329
+ const counts = {};
330
+ nodes.forEach((n) => (counts[n.id] = 0));
331
+
332
+ edges.forEach((edge) => {
333
+ if (counts[edge.from] !== undefined) {
334
+ counts[edge.from]++;
335
+ }
336
+ if (counts[edge.to] !== undefined) {
337
+ counts[edge.to]++;
338
+ }
339
+ });
340
+
341
+ return counts;
342
+ }
343
+
344
+ // =============================================
345
+ // Graph Traversal & Highlighting
346
+ // =============================================
347
+
348
+ /**
349
+ * Get connected node IDs using BFS
350
+ */
351
+ getConnectedNodeIds(nodeId, maxDepth = 3) {
352
+ const visited = new Set();
353
+ const queue = [{ id: nodeId, depth: 0 }];
354
+ visited.add(nodeId);
355
+
356
+ while (queue.length > 0) {
357
+ const { id, depth } = queue.shift();
358
+
359
+ if (depth >= maxDepth) {
360
+ continue;
361
+ }
362
+
363
+ const neighbors = this.adjacencyList.get(id) || [];
364
+ neighbors.forEach((neighborId) => {
365
+ if (!visited.has(neighborId)) {
366
+ visited.add(neighborId);
367
+ queue.push({ id: neighborId, depth: depth + 1 });
368
+ }
369
+ });
370
+ }
371
+
372
+ return Array.from(visited);
373
+ }
374
+
375
+ /**
376
+ * Highlight connected nodes
377
+ */
378
+ highlightConnectedNodes(nodeId) {
379
+ if (!this.network) {
380
+ return;
381
+ }
382
+
383
+ const connectedIds = this.getConnectedNodeIds(nodeId, 3);
384
+ const allNodes = this.network.body.data.nodes.get();
385
+
386
+ allNodes.forEach((node) => {
387
+ const isConnected = connectedIds.includes(node.id);
388
+ const opacity = isConnected ? 1.0 : 0.2;
389
+
390
+ this.network.body.data.nodes.update({
391
+ id: node.id,
392
+ opacity: opacity,
393
+ font: { ...node.font, color: isConnected ? '#fff' : '#666' },
394
+ });
395
+ });
396
+
397
+ const allEdges = this.network.body.data.edges.get();
398
+ allEdges.forEach((edge) => {
399
+ const isConnected = connectedIds.includes(edge.from) && connectedIds.includes(edge.to);
400
+ this.network.body.data.edges.update({
401
+ id: edge.id,
402
+ opacity: isConnected ? 1.0 : 0.1,
403
+ });
404
+ });
405
+ }
406
+
407
+ /**
408
+ * Reset node highlight
409
+ */
410
+ resetNodeHighlight() {
411
+ if (!this.network) {
412
+ return;
413
+ }
414
+
415
+ const allNodes = this.network.body.data.nodes.get();
416
+ allNodes.forEach((node) => {
417
+ this.network.body.data.nodes.update({
418
+ id: node.id,
419
+ opacity: 1.0,
420
+ font: { ...node.font, color: '#fff' },
421
+ });
422
+ });
423
+
424
+ const allEdges = this.network.body.data.edges.get();
425
+ allEdges.forEach((edge) => {
426
+ this.network.body.data.edges.update({
427
+ id: edge.id,
428
+ opacity: 1.0,
429
+ });
430
+ });
431
+ }
432
+
433
+ // =============================================
434
+ // Detail Panel
435
+ // =============================================
436
+
437
+ /**
438
+ * Show node detail panel
439
+ */
440
+ async showDetail(node) {
441
+ try {
442
+ console.log('[MAMA] showDetail called with node:', node);
443
+ this.currentNodeId = node.id;
444
+ const panel = document.getElementById('decision-detail-modal');
445
+
446
+ if (!panel) {
447
+ console.error('[MAMA] decision-detail-modal element not found');
448
+ return;
449
+ }
450
+
451
+ // Update existing DOM elements with markdown rendering
452
+ document.getElementById('detail-topic').textContent = node.topic || 'Unknown Topic';
453
+ const decisionEl = document.getElementById('detail-decision');
454
+ const reasoningEl = document.getElementById('detail-reasoning');
455
+
456
+ // Use marked for markdown if available
457
+ if (typeof marked !== 'undefined') {
458
+ decisionEl.innerHTML = marked.parse(node.decision || '-');
459
+ reasoningEl.innerHTML = marked.parse(node.reasoning || '-');
460
+ } else {
461
+ decisionEl.textContent = node.decision || '-';
462
+ reasoningEl.textContent = node.reasoning || '-';
463
+ }
464
+
465
+ const outcomeSelect = document.getElementById('detail-outcome-select');
466
+ if (outcomeSelect) {
467
+ outcomeSelect.value = (node.outcome || 'PENDING').toUpperCase();
468
+ }
469
+
470
+ // Clear outcome status
471
+ const outcomeStatus = document.getElementById('outcome-status');
472
+ if (outcomeStatus) {
473
+ outcomeStatus.textContent = '';
474
+ outcomeStatus.className = '';
475
+ }
476
+
477
+ document.getElementById('detail-confidence').textContent = node.confidence
478
+ ? `${(node.confidence * 100).toFixed(0)}%`
479
+ : '-';
480
+
481
+ const createdEl = document.getElementById('detail-created');
482
+ if (createdEl) {
483
+ createdEl.textContent = node.created_at ? new Date(node.created_at).toLocaleString() : '-';
484
+ }
485
+
486
+ // Reset reasoning toggle
487
+ const reasoningArrow = document.getElementById('reasoning-arrow');
488
+ if (reasoningArrow) {
489
+ reasoningArrow.textContent = '▶';
490
+ }
491
+ document.getElementById('detail-reasoning').classList.add('hidden');
492
+
493
+ // Show loading state for similar decisions
494
+ const similarEl = document.getElementById('detail-similar');
495
+ if (similarEl) {
496
+ similarEl.innerHTML = '<span class="loading-similar">Searching...</span>';
497
+ }
498
+
499
+ // Show panel
500
+ panel.classList.add('visible');
501
+
502
+ // Fetch similar decisions
503
+ console.log('[MAMA] Fetching similar decisions...');
504
+ this.fetchSimilarDecisions(node.id);
505
+ console.log('[MAMA] showDetail completed successfully');
506
+ } catch (error) {
507
+ console.error('[MAMA] Error in showDetail:', error);
508
+ console.error('[MAMA] Error stack:', error.stack);
509
+ console.error('[MAMA] Node data:', node);
510
+ }
511
+ }
512
+
513
+ /**
514
+ * Get outcome icon name
515
+ */
516
+ getOutcomeIcon(outcome) {
517
+ const outcomeMap = {
518
+ pending: 'clock',
519
+ success: 'check-circle',
520
+ failed: 'x-circle',
521
+ partial: 'alert-circle',
522
+ };
523
+ return outcomeMap[(outcome || 'pending').toLowerCase()] || 'clock';
524
+ }
525
+
526
+ /**
527
+ * Toggle reasoning full text
528
+ */
529
+ toggleReasoning() {
530
+ const arrow = document.getElementById('reasoning-arrow');
531
+ const content = document.getElementById('detail-reasoning');
532
+ if (arrow && content) {
533
+ const isHidden = content.classList.contains('hidden');
534
+ content.classList.toggle('hidden');
535
+ arrow.textContent = isHidden ? '▼' : '▶';
536
+ }
537
+ }
538
+
539
+ /**
540
+ * Close detail panel
541
+ */
542
+ closeDetail() {
543
+ const panel = document.getElementById('decision-detail-modal');
544
+ if (panel) {
545
+ panel.classList.remove('visible');
546
+ }
547
+ this.currentNodeId = null;
548
+ this.resetNodeHighlight();
549
+ }
550
+
551
+ /**
552
+ * Fetch similar decisions
553
+ */
554
+ async fetchSimilarDecisions(nodeId) {
555
+ console.log('[MAMA] fetchSimilarDecisions called for node:', nodeId);
556
+ const container = document.getElementById('detail-similar');
557
+
558
+ if (!container) {
559
+ console.warn('[MAMA] detail-similar element not found');
560
+ return;
561
+ }
562
+
563
+ try {
564
+ console.log('[MAMA] Calling API.getSimilarDecisions...');
565
+ const data = await API.getSimilarDecisions(nodeId);
566
+ console.log('[MAMA] Similar decisions received:', data);
567
+
568
+ if (data.error) {
569
+ container.innerHTML = `<span style="color:#666">${data.message || 'Search failed'}</span>`;
570
+ return;
571
+ }
572
+
573
+ const similar = data.similar || [];
574
+
575
+ if (similar.length === 0) {
576
+ console.log('[MAMA] No similar decisions found');
577
+ container.innerHTML = '<span style="color:#666">No similar decisions found</span>';
578
+ return;
579
+ }
580
+
581
+ console.log('[MAMA] Building similar decisions HTML for', similar.length, 'items');
582
+ const html = similar
583
+ .map(
584
+ (s) => `
585
+ <button class="w-full text-left p-2 mb-2 bg-gray-100 dark:bg-gray-800 hover:bg-indigo-100 dark:hover:bg-indigo-900/30 border border-gray-200 dark:border-gray-700 rounded-lg transition-colors" onclick="window.graphModule.navigateToNode('${s.id}')">
586
+ <div class="text-xs font-semibold text-indigo-600 dark:text-indigo-400">${escapeHtml(s.topic)}</div>
587
+ <div class="text-xs text-gray-600 dark:text-gray-400 mt-1 line-clamp-2">${escapeHtml((s.decision || '').substring(0, 80))}...</div>
588
+ <div class="text-xs text-gray-500 mt-1">${Math.round((s.similarity || 0) * 100)}% match</div>
589
+ </button>
590
+ `
591
+ )
592
+ .join('');
593
+
594
+ console.log('[MAMA] Setting similar decisions HTML...');
595
+ container.innerHTML = html;
596
+ console.log('[MAMA] fetchSimilarDecisions completed');
597
+ } catch (error) {
598
+ console.error('[MAMA] Failed to fetch similar decisions:', error);
599
+ console.error('[MAMA] Error stack:', error.stack);
600
+ container.innerHTML = '<span style="color:#f66">Failed to load</span>';
601
+ }
602
+ }
603
+
604
+ /**
605
+ * Save outcome for current node
606
+ */
607
+ async saveOutcome() {
608
+ const select = document.getElementById('detail-outcome-select');
609
+ const newOutcome = select.value;
610
+
611
+ if (!this.currentNodeId || !newOutcome) {
612
+ return;
613
+ }
614
+
615
+ try {
616
+ await API.updateOutcome(this.currentNodeId, newOutcome);
617
+
618
+ // Update local data
619
+ const node = this.graphData.nodes.find((n) => n.id === this.currentNodeId);
620
+ if (node) {
621
+ node.outcome = newOutcome.toUpperCase();
622
+
623
+ // Update visualization
624
+ this.network.body.data.nodes.update({
625
+ id: this.currentNodeId,
626
+ color: {
627
+ border: this.getOutcomeBorderColor(newOutcome),
628
+ },
629
+ });
630
+
631
+ // Refresh detail panel
632
+ this.showDetail(node);
633
+ }
634
+
635
+ console.log('[MAMA] Outcome updated:', this.currentNodeId, newOutcome);
636
+ } catch (error) {
637
+ console.error('[MAMA] Failed to update outcome:', error);
638
+ alert('Failed to update outcome: ' + error.message);
639
+ }
640
+ }
641
+
642
+ // =============================================
643
+ // Navigation
644
+ // =============================================
645
+
646
+ /**
647
+ * Navigate to specific node
648
+ */
649
+ async navigateToNode(nodeId) {
650
+ if (!this.network) {
651
+ return;
652
+ }
653
+
654
+ // Try exact match first
655
+ let node = this.graphData.nodes.find((n) => n.id === nodeId);
656
+
657
+ // If not found, try partial match (for short IDs from checkpoints)
658
+ if (!node) {
659
+ console.log('[MAMA] Exact match not found, trying partial match for:', nodeId);
660
+ node = this.graphData.nodes.find((n) => n.id.startsWith(nodeId));
661
+
662
+ if (node) {
663
+ console.log('[MAMA] Found node via partial match:', node.id);
664
+ nodeId = node.id; // Update nodeId to the full ID
665
+ }
666
+ }
667
+
668
+ // If node not in current graph, reload without filters
669
+ if (!node) {
670
+ console.log('[MAMA] Node not in current graph, reloading all nodes...');
671
+
672
+ // Reset topic filter
673
+ const topicFilter = document.getElementById('topic-filter');
674
+ if (topicFilter) {
675
+ topicFilter.value = '';
676
+ }
677
+
678
+ // Reload graph: fetch fresh data and reinitialize
679
+ await this.fetchData();
680
+ this.init(this.graphData);
681
+
682
+ // Try exact match again
683
+ node = this.graphData.nodes.find((n) => n.id === nodeId);
684
+
685
+ // If still not found, try partial match
686
+ if (!node) {
687
+ node = this.graphData.nodes.find((n) => n.id.startsWith(nodeId));
688
+ if (node) {
689
+ console.log('[MAMA] Found node via partial match after reload:', node.id);
690
+ nodeId = node.id;
691
+ }
692
+ }
693
+
694
+ if (!node) {
695
+ console.warn('[MAMA] Node not found even after reload:', nodeId);
696
+ showToast('⚠️ Decision not found in graph');
697
+ return;
698
+ }
699
+ }
700
+
701
+ // Focus on node
702
+ this.network.focus(nodeId, {
703
+ scale: 1.5,
704
+ animation: { duration: 500, easingFunction: 'easeInOutQuad' },
705
+ });
706
+
707
+ // Select node (triggers click event)
708
+ this.network.selectNodes([nodeId]);
709
+
710
+ // Show detail
711
+ this.showDetail(node);
712
+ this.highlightConnectedNodes(nodeId);
713
+ }
714
+
715
+ /**
716
+ * Get connected edge types
717
+ */
718
+ getConnectedEdges(nodeId) {
719
+ const edges = this.graphData.edges.filter((e) => e.from === nodeId || e.to === nodeId);
720
+
721
+ const outgoing = edges.filter((e) => e.from === nodeId);
722
+ const incoming = edges.filter((e) => e.to === nodeId);
723
+
724
+ return { outgoing, incoming, all: edges };
725
+ }
726
+
727
+ // =============================================
728
+ // Filtering
729
+ // =============================================
730
+
731
+ /**
732
+ * Populate topic filter dropdown
733
+ */
734
+ populateTopicFilter(topics) {
735
+ const select = document.getElementById('topic-filter');
736
+ if (!select) {
737
+ return;
738
+ }
739
+
740
+ select.innerHTML = '<option value="">All Topics</option>';
741
+ topics.forEach((topic) => {
742
+ const option = document.createElement('option');
743
+ option.value = topic;
744
+ option.textContent = topic;
745
+ select.appendChild(option);
746
+ });
747
+ }
748
+
749
+ /**
750
+ * Filter by topic
751
+ */
752
+ filterByTopic(topic) {
753
+ if (!this.network) {
754
+ return;
755
+ }
756
+
757
+ const allNodes = this.network.body.data.nodes.get();
758
+
759
+ if (!topic) {
760
+ // Show all
761
+ allNodes.forEach((node) => {
762
+ this.network.body.data.nodes.update({
763
+ id: node.id,
764
+ hidden: false,
765
+ });
766
+ });
767
+ } else {
768
+ // Filter
769
+ allNodes.forEach((node) => {
770
+ const nodeData = this.graphData.nodes.find((n) => n.id === node.id);
771
+ this.network.body.data.nodes.update({
772
+ id: node.id,
773
+ hidden: nodeData?.topic !== topic,
774
+ });
775
+ });
776
+ }
777
+
778
+ console.log('[MAMA] Filtered by topic:', topic || 'all');
779
+ }
780
+
781
+ /**
782
+ * Filter by outcome
783
+ */
784
+ filterByOutcome(outcome) {
785
+ if (!this.network) {
786
+ return;
787
+ }
788
+
789
+ const allNodes = this.network.body.data.nodes.get();
790
+
791
+ if (!outcome) {
792
+ // Show all
793
+ allNodes.forEach((node) => {
794
+ this.network.body.data.nodes.update({
795
+ id: node.id,
796
+ hidden: false,
797
+ });
798
+ });
799
+ } else {
800
+ // Filter by outcome
801
+ allNodes.forEach((node) => {
802
+ const nodeData = this.graphData.nodes.find((n) => n.id === node.id);
803
+ const nodeOutcome = (nodeData?.outcome || 'pending').toLowerCase();
804
+ this.network.body.data.nodes.update({
805
+ id: node.id,
806
+ hidden: nodeOutcome !== outcome.toLowerCase(),
807
+ });
808
+ });
809
+ }
810
+
811
+ console.log('[MAMA] Filtered by outcome:', outcome || 'all');
812
+ }
813
+
814
+ /**
815
+ * Clear all filters
816
+ */
817
+ clearFilters() {
818
+ if (!this.network) {
819
+ return;
820
+ }
821
+
822
+ // Show all nodes
823
+ const allNodes = this.network.body.data.nodes.get();
824
+ allNodes.forEach((node) => {
825
+ this.network.body.data.nodes.update({
826
+ id: node.id,
827
+ hidden: false,
828
+ opacity: 1.0,
829
+ font: { ...node.font, color: '#fff' },
830
+ });
831
+ });
832
+
833
+ // Show all edges
834
+ const allEdges = this.network.body.data.edges.get();
835
+ allEdges.forEach((edge) => {
836
+ this.network.body.data.edges.update({
837
+ id: edge.id,
838
+ opacity: 1.0,
839
+ });
840
+ });
841
+
842
+ // Clear search state
843
+ this.searchMatches = [];
844
+ this.currentSearchIndex = 0;
845
+
846
+ const countEl = document.getElementById('search-count');
847
+ if (countEl) {
848
+ countEl.style.display = 'none';
849
+ }
850
+
851
+ console.log('[MAMA] All filters cleared');
852
+ }
853
+
854
+ // =============================================
855
+ // Search
856
+ // =============================================
857
+
858
+ /**
859
+ * Perform search
860
+ */
861
+ search() {
862
+ const query = document.getElementById('search-input').value.trim().toLowerCase();
863
+
864
+ if (!query) {
865
+ this.clearSearch();
866
+ return;
867
+ }
868
+
869
+ // Search in topic, decision, and reasoning
870
+ this.searchMatches = this.graphData.nodes.filter(
871
+ (node) =>
872
+ (node.topic || '').toLowerCase().includes(query) ||
873
+ (node.decision || '').toLowerCase().includes(query) ||
874
+ (node.reasoning || '').toLowerCase().includes(query)
875
+ );
876
+
877
+ this.currentSearchIndex = 0;
878
+ this.updateSearchResults();
879
+
880
+ if (this.searchMatches.length > 0) {
881
+ this.highlightSearchResults();
882
+ this.navigateToNode(this.searchMatches[0].id);
883
+ }
884
+
885
+ console.log('[MAMA] Search:', query, '- Found', this.searchMatches.length, 'matches');
886
+ }
887
+
888
+ /**
889
+ * Handle search input
890
+ */
891
+ handleSearchInput(event) {
892
+ if (event.key === 'Enter' && this.searchMatches.length > 0) {
893
+ this.nextSearchResult();
894
+ } else {
895
+ this.debouncedSearch();
896
+ }
897
+ }
898
+
899
+ /**
900
+ * Navigate to next search result
901
+ */
902
+ nextSearchResult() {
903
+ if (this.searchMatches.length === 0) {
904
+ return;
905
+ }
906
+
907
+ this.currentSearchIndex = (this.currentSearchIndex + 1) % this.searchMatches.length;
908
+ this.navigateToNode(this.searchMatches[this.currentSearchIndex].id);
909
+ this.updateSearchResults();
910
+ }
911
+
912
+ /**
913
+ * Navigate to previous search result
914
+ */
915
+ prevSearchResult() {
916
+ if (this.searchMatches.length === 0) {
917
+ return;
918
+ }
919
+
920
+ this.currentSearchIndex =
921
+ (this.currentSearchIndex - 1 + this.searchMatches.length) % this.searchMatches.length;
922
+ this.navigateToNode(this.searchMatches[this.currentSearchIndex].id);
923
+ this.updateSearchResults();
924
+ }
925
+
926
+ /**
927
+ * Update search count display
928
+ */
929
+ updateSearchResults() {
930
+ const countEl = document.getElementById('search-count');
931
+ if (!countEl) {
932
+ return;
933
+ }
934
+
935
+ if (this.searchMatches.length > 0) {
936
+ countEl.textContent = `${this.currentSearchIndex + 1} / ${this.searchMatches.length}`;
937
+ countEl.style.display = 'inline';
938
+ } else {
939
+ countEl.textContent = 'No results';
940
+ countEl.style.display = 'inline';
941
+ }
942
+ }
943
+
944
+ /**
945
+ * Highlight search results
946
+ */
947
+ highlightSearchResults() {
948
+ if (!this.network) {
949
+ return;
950
+ }
951
+
952
+ const matchIds = this.searchMatches.map((n) => n.id);
953
+ const allNodes = this.network.body.data.nodes.get();
954
+
955
+ allNodes.forEach((node) => {
956
+ const isMatch = matchIds.includes(node.id);
957
+ this.network.body.data.nodes.update({
958
+ id: node.id,
959
+ opacity: isMatch ? 1.0 : 0.2,
960
+ font: { ...node.font, color: isMatch ? '#fff' : '#666' },
961
+ });
962
+ });
963
+ }
964
+
965
+ /**
966
+ * Clear search
967
+ */
968
+ clearSearch() {
969
+ this.searchMatches = [];
970
+ this.currentSearchIndex = 0;
971
+ this.resetNodeHighlight();
972
+
973
+ const countEl = document.getElementById('search-count');
974
+ if (countEl) {
975
+ countEl.style.display = 'none';
976
+ }
977
+ }
978
+
979
+ /**
980
+ * Open search panel
981
+ */
982
+ openSearch() {
983
+ const searchContainer = document.getElementById('search-container');
984
+ const searchInput = document.getElementById('search-input');
985
+
986
+ searchContainer.style.display = 'flex';
987
+ searchInput.focus();
988
+ }
989
+
990
+ /**
991
+ * Close search panel
992
+ */
993
+ closeSearch() {
994
+ document.getElementById('search-container').style.display = 'none';
995
+ this.clearSearch();
996
+ }
997
+ }