@proflandrigan/shards 1.1.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 (397) hide show
  1. package/README.md +475 -0
  2. package/package.json +37 -0
  3. package/src/agents/academic.md +276 -0
  4. package/src/agents/ai-engineer.md +377 -0
  5. package/src/agents/analytics-engineer.md +364 -0
  6. package/src/agents/applied-ml-scientist.md +410 -0
  7. package/src/agents/backend-engineer.md +255 -0
  8. package/src/agents/bi-engineer.md +333 -0
  9. package/src/agents/data-analyst.md +343 -0
  10. package/src/agents/data-engineer.md +260 -0
  11. package/src/agents/data-modeller.md +386 -0
  12. package/src/agents/data-scientist.md +366 -0
  13. package/src/agents/deep-learning-engineer.md +389 -0
  14. package/src/agents/ml-engineer.md +424 -0
  15. package/src/agents/mlops-engineer.md +339 -0
  16. package/src/agents/researcher.md +187 -0
  17. package/src/agents/specific_instructions/academic/critical_review.md +263 -0
  18. package/src/agents/specific_instructions/academic/report.md +113 -0
  19. package/src/agents/specific_instructions/ai_engineer/advise.md +162 -0
  20. package/src/agents/specific_instructions/ai_engineer/bi_engineer_handoff.md +86 -0
  21. package/src/agents/specific_instructions/ai_engineer/experiment.md +471 -0
  22. package/src/agents/specific_instructions/ai_engineer/experiment_ui_mode.md +44 -0
  23. package/src/agents/specific_instructions/ai_engineer/phases/index.md +45 -0
  24. package/src/agents/specific_instructions/ai_engineer/phases/phase-1.md +55 -0
  25. package/src/agents/specific_instructions/ai_engineer/phases/phase-2.md +86 -0
  26. package/src/agents/specific_instructions/ai_engineer/phases/phase-3.md +96 -0
  27. package/src/agents/specific_instructions/ai_engineer/phases/phase-4.md +138 -0
  28. package/src/agents/specific_instructions/ai_engineer/phases/phase-5.md +157 -0
  29. package/src/agents/specific_instructions/ai_engineer/phases/phase-6.md +196 -0
  30. package/src/agents/specific_instructions/ai_engineer/phases/phase-7.md +313 -0
  31. package/src/agents/specific_instructions/ai_engineer/phases.md +1011 -0
  32. package/src/agents/specific_instructions/ai_engineer/prompt_lab.md +161 -0
  33. package/src/agents/specific_instructions/ai_engineer/prompt_lab_ui_mode.md +28 -0
  34. package/src/agents/specific_instructions/ai_engineer/research.md +393 -0
  35. package/src/agents/specific_instructions/ai_engineer/research_ui_mode.md +66 -0
  36. package/src/agents/specific_instructions/ai_engineer/review.md +159 -0
  37. package/src/agents/specific_instructions/ai_engineer/validation_checklist.md +182 -0
  38. package/src/agents/specific_instructions/analytics_engineer/advise.md +155 -0
  39. package/src/agents/specific_instructions/analytics_engineer/bi_engineer_handoff.md +91 -0
  40. package/src/agents/specific_instructions/analytics_engineer/data_analyst_handoff.md +84 -0
  41. package/src/agents/specific_instructions/analytics_engineer/deep_phases.md +818 -0
  42. package/src/agents/specific_instructions/analytics_engineer/phases_deep/index.md +24 -0
  43. package/src/agents/specific_instructions/analytics_engineer/phases_deep/phase-1.md +77 -0
  44. package/src/agents/specific_instructions/analytics_engineer/phases_deep/phase-2.md +106 -0
  45. package/src/agents/specific_instructions/analytics_engineer/phases_deep/phase-3.md +93 -0
  46. package/src/agents/specific_instructions/analytics_engineer/phases_deep/phase-4.md +79 -0
  47. package/src/agents/specific_instructions/analytics_engineer/phases_deep/phase-5.md +61 -0
  48. package/src/agents/specific_instructions/analytics_engineer/phases_deep/phase-6.md +45 -0
  49. package/src/agents/specific_instructions/analytics_engineer/phases_deep/phase-7.md +235 -0
  50. package/src/agents/specific_instructions/analytics_engineer/phases_deep/phase-8.md +221 -0
  51. package/src/agents/specific_instructions/analytics_engineer/phases_quick/index.md +19 -0
  52. package/src/agents/specific_instructions/analytics_engineer/phases_quick/phase-1.md +47 -0
  53. package/src/agents/specific_instructions/analytics_engineer/phases_quick/phase-2.md +78 -0
  54. package/src/agents/specific_instructions/analytics_engineer/quick_phases.md +112 -0
  55. package/src/agents/specific_instructions/analytics_engineer/review.md +167 -0
  56. package/src/agents/specific_instructions/analytics_engineer/service_mode.md +369 -0
  57. package/src/agents/specific_instructions/analytics_engineer/ui_mode.md +45 -0
  58. package/src/agents/specific_instructions/analytics_engineer/update.md +162 -0
  59. package/src/agents/specific_instructions/analytics_engineer/validation_checklist.md +121 -0
  60. package/src/agents/specific_instructions/applied_ml_scientist/advise.md +143 -0
  61. package/src/agents/specific_instructions/applied_ml_scientist/phases/index.md +21 -0
  62. package/src/agents/specific_instructions/applied_ml_scientist/phases/phase-1.md +51 -0
  63. package/src/agents/specific_instructions/applied_ml_scientist/phases/phase-2.md +66 -0
  64. package/src/agents/specific_instructions/applied_ml_scientist/phases/phase-3.md +113 -0
  65. package/src/agents/specific_instructions/applied_ml_scientist/phases/phase-4.md +104 -0
  66. package/src/agents/specific_instructions/applied_ml_scientist/phases/phase-5.md +156 -0
  67. package/src/agents/specific_instructions/applied_ml_scientist/phases.md +428 -0
  68. package/src/agents/specific_instructions/applied_ml_scientist/research.md +379 -0
  69. package/src/agents/specific_instructions/applied_ml_scientist/review.md +142 -0
  70. package/src/agents/specific_instructions/applied_ml_scientist/validation_checklist.md +136 -0
  71. package/src/agents/specific_instructions/backend_engineer/clean.md +149 -0
  72. package/src/agents/specific_instructions/backend_engineer/review.md +91 -0
  73. package/src/agents/specific_instructions/backend_engineer/review_checklist.md +54 -0
  74. package/src/agents/specific_instructions/backend_engineer/service_mode.md +67 -0
  75. package/src/agents/specific_instructions/bi_engineer/advise.md +137 -0
  76. package/src/agents/specific_instructions/bi_engineer/data_analyst_handoff.md +77 -0
  77. package/src/agents/specific_instructions/bi_engineer/incoming_handoff.md +45 -0
  78. package/src/agents/specific_instructions/bi_engineer/phases/index.md +20 -0
  79. package/src/agents/specific_instructions/bi_engineer/phases/phase-1.md +164 -0
  80. package/src/agents/specific_instructions/bi_engineer/phases/phase-2.md +92 -0
  81. package/src/agents/specific_instructions/bi_engineer/phases/phase-3.md +121 -0
  82. package/src/agents/specific_instructions/bi_engineer/phases/phase-4.md +106 -0
  83. package/src/agents/specific_instructions/bi_engineer/phases.md +451 -0
  84. package/src/agents/specific_instructions/bi_engineer/review.md +166 -0
  85. package/src/agents/specific_instructions/bi_engineer/update.md +147 -0
  86. package/src/agents/specific_instructions/bi_engineer/validation_checklist.md +124 -0
  87. package/src/agents/specific_instructions/data_analyst/advise.md +138 -0
  88. package/src/agents/specific_instructions/data_analyst/explain.md +221 -0
  89. package/src/agents/specific_instructions/data_analyst/incoming_handoff.md +40 -0
  90. package/src/agents/specific_instructions/data_analyst/phases/index.md +20 -0
  91. package/src/agents/specific_instructions/data_analyst/phases/phase-1.md +159 -0
  92. package/src/agents/specific_instructions/data_analyst/phases/phase-2.md +112 -0
  93. package/src/agents/specific_instructions/data_analyst/phases/phase-3.md +265 -0
  94. package/src/agents/specific_instructions/data_analyst/phases/phase-4.md +100 -0
  95. package/src/agents/specific_instructions/data_analyst/phases.md +501 -0
  96. package/src/agents/specific_instructions/data_analyst/review.md +138 -0
  97. package/src/agents/specific_instructions/data_analyst/ui_mode.md +26 -0
  98. package/src/agents/specific_instructions/data_analyst/update.md +144 -0
  99. package/src/agents/specific_instructions/data_analyst/validation_checklist.md +95 -0
  100. package/src/agents/specific_instructions/data_engineer/advise.md +137 -0
  101. package/src/agents/specific_instructions/data_engineer/phases.md +466 -0
  102. package/src/agents/specific_instructions/data_engineer/phases_deep/index.md +23 -0
  103. package/src/agents/specific_instructions/data_engineer/phases_deep/phase-1.md +49 -0
  104. package/src/agents/specific_instructions/data_engineer/phases_deep/phase-2.md +93 -0
  105. package/src/agents/specific_instructions/data_engineer/phases_deep/phase-3.md +55 -0
  106. package/src/agents/specific_instructions/data_engineer/phases_deep/phase-4.md +48 -0
  107. package/src/agents/specific_instructions/data_engineer/phases_deep/phase-5.md +40 -0
  108. package/src/agents/specific_instructions/data_engineer/phases_deep/phase-6.md +102 -0
  109. package/src/agents/specific_instructions/data_engineer/phases_deep/phase-7.md +87 -0
  110. package/src/agents/specific_instructions/data_engineer/phases_quick/index.md +19 -0
  111. package/src/agents/specific_instructions/data_engineer/phases_quick/phase-1.md +45 -0
  112. package/src/agents/specific_instructions/data_engineer/phases_quick/phase-2.md +54 -0
  113. package/src/agents/specific_instructions/data_engineer/review.md +135 -0
  114. package/src/agents/specific_instructions/data_engineer/validation_checklist.md +136 -0
  115. package/src/agents/specific_instructions/data_modeller/advise.md +137 -0
  116. package/src/agents/specific_instructions/data_modeller/phases.md +581 -0
  117. package/src/agents/specific_instructions/data_modeller/phases_deep/index.md +23 -0
  118. package/src/agents/specific_instructions/data_modeller/phases_deep/phase-1.md +52 -0
  119. package/src/agents/specific_instructions/data_modeller/phases_deep/phase-2.md +113 -0
  120. package/src/agents/specific_instructions/data_modeller/phases_deep/phase-3.md +47 -0
  121. package/src/agents/specific_instructions/data_modeller/phases_deep/phase-4.md +51 -0
  122. package/src/agents/specific_instructions/data_modeller/phases_deep/phase-5.md +45 -0
  123. package/src/agents/specific_instructions/data_modeller/phases_deep/phase-6.md +105 -0
  124. package/src/agents/specific_instructions/data_modeller/phases_deep/phase-7.md +136 -0
  125. package/src/agents/specific_instructions/data_modeller/phases_quick/index.md +19 -0
  126. package/src/agents/specific_instructions/data_modeller/phases_quick/phase-1.md +47 -0
  127. package/src/agents/specific_instructions/data_modeller/phases_quick/phase-2.md +65 -0
  128. package/src/agents/specific_instructions/data_modeller/review.md +141 -0
  129. package/src/agents/specific_instructions/data_modeller/service_mode.md +218 -0
  130. package/src/agents/specific_instructions/data_modeller/validation_checklist.md +125 -0
  131. package/src/agents/specific_instructions/data_scientist/advise.md +158 -0
  132. package/src/agents/specific_instructions/data_scientist/bi_engineer_handoff.md +63 -0
  133. package/src/agents/specific_instructions/data_scientist/experiment.md +482 -0
  134. package/src/agents/specific_instructions/data_scientist/experiment_ui_mode.md +44 -0
  135. package/src/agents/specific_instructions/data_scientist/explain.md +247 -0
  136. package/src/agents/specific_instructions/data_scientist/greenfield_data.md +35 -0
  137. package/src/agents/specific_instructions/data_scientist/ml_engineer_handoff.md +52 -0
  138. package/src/agents/specific_instructions/data_scientist/notebook_walkthrough.md +76 -0
  139. package/src/agents/specific_instructions/data_scientist/phases/index.md +24 -0
  140. package/src/agents/specific_instructions/data_scientist/phases/phase-1.md +45 -0
  141. package/src/agents/specific_instructions/data_scientist/phases/phase-2.md +67 -0
  142. package/src/agents/specific_instructions/data_scientist/phases/phase-3.md +89 -0
  143. package/src/agents/specific_instructions/data_scientist/phases/phase-4.md +143 -0
  144. package/src/agents/specific_instructions/data_scientist/phases/phase-5.md +71 -0
  145. package/src/agents/specific_instructions/data_scientist/phases/phase-6.md +239 -0
  146. package/src/agents/specific_instructions/data_scientist/phases/phase-7.md +207 -0
  147. package/src/agents/specific_instructions/data_scientist/phases.md +651 -0
  148. package/src/agents/specific_instructions/data_scientist/research.md +345 -0
  149. package/src/agents/specific_instructions/data_scientist/research_ui_mode.md +52 -0
  150. package/src/agents/specific_instructions/data_scientist/review.md +136 -0
  151. package/src/agents/specific_instructions/data_scientist/service_mode.md +247 -0
  152. package/src/agents/specific_instructions/data_scientist/validation_checklist.md +183 -0
  153. package/src/agents/specific_instructions/deep_learning_engineer/advise.md +145 -0
  154. package/src/agents/specific_instructions/deep_learning_engineer/phases/index.md +21 -0
  155. package/src/agents/specific_instructions/deep_learning_engineer/phases/phase-1.md +74 -0
  156. package/src/agents/specific_instructions/deep_learning_engineer/phases/phase-2.md +98 -0
  157. package/src/agents/specific_instructions/deep_learning_engineer/phases/phase-3.md +76 -0
  158. package/src/agents/specific_instructions/deep_learning_engineer/phases/phase-4.md +128 -0
  159. package/src/agents/specific_instructions/deep_learning_engineer/phases/phase-5.md +292 -0
  160. package/src/agents/specific_instructions/deep_learning_engineer/phases.md +567 -0
  161. package/src/agents/specific_instructions/deep_learning_engineer/research.md +389 -0
  162. package/src/agents/specific_instructions/deep_learning_engineer/review.md +155 -0
  163. package/src/agents/specific_instructions/deep_learning_engineer/validation_checklist.md +147 -0
  164. package/src/agents/specific_instructions/ml_engineer/advise.md +174 -0
  165. package/src/agents/specific_instructions/ml_engineer/bi_engineer_handoff.md +71 -0
  166. package/src/agents/specific_instructions/ml_engineer/experiment.md +474 -0
  167. package/src/agents/specific_instructions/ml_engineer/experiment_ui_mode.md +44 -0
  168. package/src/agents/specific_instructions/ml_engineer/notebook_walkthrough.md +75 -0
  169. package/src/agents/specific_instructions/ml_engineer/phases/index.md +25 -0
  170. package/src/agents/specific_instructions/ml_engineer/phases/phase-1.md +49 -0
  171. package/src/agents/specific_instructions/ml_engineer/phases/phase-2.md +75 -0
  172. package/src/agents/specific_instructions/ml_engineer/phases/phase-3.md +124 -0
  173. package/src/agents/specific_instructions/ml_engineer/phases/phase-4.md +279 -0
  174. package/src/agents/specific_instructions/ml_engineer/phases/phase-5.md +160 -0
  175. package/src/agents/specific_instructions/ml_engineer/phases/phase-6-5.md +170 -0
  176. package/src/agents/specific_instructions/ml_engineer/phases/phase-6.md +295 -0
  177. package/src/agents/specific_instructions/ml_engineer/phases/phase-7.md +337 -0
  178. package/src/agents/specific_instructions/ml_engineer/phases.md +1068 -0
  179. package/src/agents/specific_instructions/ml_engineer/research.md +437 -0
  180. package/src/agents/specific_instructions/ml_engineer/research_ui_mode.md +71 -0
  181. package/src/agents/specific_instructions/ml_engineer/review.md +187 -0
  182. package/src/agents/specific_instructions/ml_engineer/service_mode.md +273 -0
  183. package/src/agents/specific_instructions/ml_engineer/validation_checklist.md +185 -0
  184. package/src/agents/specific_instructions/mlops_engineer/advise.md +139 -0
  185. package/src/agents/specific_instructions/mlops_engineer/phases/index.md +23 -0
  186. package/src/agents/specific_instructions/mlops_engineer/phases/phase-1.md +52 -0
  187. package/src/agents/specific_instructions/mlops_engineer/phases/phase-2.md +86 -0
  188. package/src/agents/specific_instructions/mlops_engineer/phases/phase-3.md +105 -0
  189. package/src/agents/specific_instructions/mlops_engineer/phases/phase-4.md +128 -0
  190. package/src/agents/specific_instructions/mlops_engineer/phases/phase-5.md +106 -0
  191. package/src/agents/specific_instructions/mlops_engineer/phases/phase-6.md +128 -0
  192. package/src/agents/specific_instructions/mlops_engineer/phases/phase-7.md +144 -0
  193. package/src/agents/specific_instructions/mlops_engineer/phases.md +671 -0
  194. package/src/agents/specific_instructions/mlops_engineer/review.md +164 -0
  195. package/src/agents/specific_instructions/mlops_engineer/service_mode.md +81 -0
  196. package/src/agents/specific_instructions/mlops_engineer/validation_checklist.md +151 -0
  197. package/src/agents/specific_instructions/researcher/critical_review.md +292 -0
  198. package/src/agents/specific_instructions/researcher/review_checklist.md +67 -0
  199. package/src/agents/specific_instructions/researcher/service_mode.md +224 -0
  200. package/src/agents/specific_instructions/shared/auto_verify_mode.md +141 -0
  201. package/src/agents/specific_instructions/shared/autonomous_research.md +1289 -0
  202. package/src/agents/specific_instructions/shared/behavioral_rules.md +36 -0
  203. package/src/agents/specific_instructions/shared/diverge_protocol.md +387 -0
  204. package/src/agents/specific_instructions/shared/engineering_guidelines.md +136 -0
  205. package/src/agents/specific_instructions/shared/experiment_versioning.md +184 -0
  206. package/src/agents/specific_instructions/shared/goal_mode.md +187 -0
  207. package/src/agents/specific_instructions/shared/incremental_testing.md +139 -0
  208. package/src/agents/specific_instructions/shared/intent_discovery.md +223 -0
  209. package/src/agents/specific_instructions/shared/join_path_protocol.md +168 -0
  210. package/src/agents/specific_instructions/shared/knowledge_checkpoint.md +83 -0
  211. package/src/agents/specific_instructions/shared/knowledge_harvest.md +220 -0
  212. package/src/agents/specific_instructions/shared/knowledge_retrieval.md +100 -0
  213. package/src/agents/specific_instructions/shared/notebook_walkthrough_protocol.md +367 -0
  214. package/src/agents/specific_instructions/shared/reviewer_verdict_protocol.md +74 -0
  215. package/src/agents/specific_instructions/shared/swarm_protocol.md +97 -0
  216. package/src/agents/specific_instructions/shared/validation_protocol.md +139 -0
  217. package/src/agents/specific_instructions/syn/arbiter.md +140 -0
  218. package/src/agents/specific_instructions/syn/brainstorm.md +550 -0
  219. package/src/agents/specific_instructions/syn/code_review.md +232 -0
  220. package/src/agents/specific_instructions/syn/diff.md +239 -0
  221. package/src/agents/specific_instructions/syn/final_review.md +65 -0
  222. package/src/agents/specific_instructions/syn/fixer.md +240 -0
  223. package/src/agents/specific_instructions/syn/free_form.md +130 -0
  224. package/src/agents/specific_instructions/syn/knowledge.md +468 -0
  225. package/src/agents/specific_instructions/syn/notebook_walkthrough.md +78 -0
  226. package/src/agents/specific_instructions/syn/panel_review.md +634 -0
  227. package/src/agents/specific_instructions/syn/pm.md +453 -0
  228. package/src/agents/specific_instructions/syn/pr_review.md +255 -0
  229. package/src/agents/specific_instructions/syn/slides.md +417 -0
  230. package/src/agents/syn.md +729 -0
  231. package/src/commands/academic.md +41 -0
  232. package/src/commands/ai-engineer.md +45 -0
  233. package/src/commands/analytics-engineer.md +48 -0
  234. package/src/commands/applied-ml-scientist.md +45 -0
  235. package/src/commands/backend-engineer.md +35 -0
  236. package/src/commands/bi-engineer.md +40 -0
  237. package/src/commands/brainstorm.md +24 -0
  238. package/src/commands/data-analyst.md +38 -0
  239. package/src/commands/data-engineer.md +37 -0
  240. package/src/commands/data-modeller.md +38 -0
  241. package/src/commands/data-scientist.md +38 -0
  242. package/src/commands/deep-learning-engineer.md +47 -0
  243. package/src/commands/end.md +49 -0
  244. package/src/commands/knowledge.md +24 -0
  245. package/src/commands/ml-engineer.md +42 -0
  246. package/src/commands/mlops-engineer.md +47 -0
  247. package/src/commands/notebook-walkthrough.md +58 -0
  248. package/src/commands/researcher.md +40 -0
  249. package/src/commands/resume.md +57 -0
  250. package/src/commands/review-pr.md +26 -0
  251. package/src/commands/shards-guide.md +41 -0
  252. package/src/commands/shards-ui.md +32 -0
  253. package/src/commands/shards.md +41 -0
  254. package/src/docs/01-getting-started/concepts.md +109 -0
  255. package/src/docs/01-getting-started/first-session.md +79 -0
  256. package/src/docs/01-getting-started/install.md +61 -0
  257. package/src/docs/02-agents/academic.md +71 -0
  258. package/src/docs/02-agents/ai-engineer.md +78 -0
  259. package/src/docs/02-agents/analytics-engineer.md +58 -0
  260. package/src/docs/02-agents/applied-ml-scientist.md +59 -0
  261. package/src/docs/02-agents/backend-engineer.md +58 -0
  262. package/src/docs/02-agents/bi-engineer.md +65 -0
  263. package/src/docs/02-agents/data-analyst.md +67 -0
  264. package/src/docs/02-agents/data-engineer.md +57 -0
  265. package/src/docs/02-agents/data-modeller.md +51 -0
  266. package/src/docs/02-agents/data-scientist.md +78 -0
  267. package/src/docs/02-agents/deep-learning-engineer.md +64 -0
  268. package/src/docs/02-agents/ml-engineer.md +80 -0
  269. package/src/docs/02-agents/mlops-engineer.md +59 -0
  270. package/src/docs/02-agents/overview.md +62 -0
  271. package/src/docs/02-agents/researcher.md +73 -0
  272. package/src/docs/02-agents/syn.md +88 -0
  273. package/src/docs/03-protocols/auto-verify.md +82 -0
  274. package/src/docs/03-protocols/autonomous-research.md +59 -0
  275. package/src/docs/03-protocols/behavioral-rules.md +35 -0
  276. package/src/docs/03-protocols/diverge.md +50 -0
  277. package/src/docs/03-protocols/engineering-guidelines.md +56 -0
  278. package/src/docs/03-protocols/experiment-versioning.md +38 -0
  279. package/src/docs/03-protocols/gate-pattern.md +65 -0
  280. package/src/docs/03-protocols/incremental-testing.md +68 -0
  281. package/src/docs/03-protocols/join-path.md +46 -0
  282. package/src/docs/03-protocols/knowledge-ledger.md +70 -0
  283. package/src/docs/03-protocols/reviewer-verdicts.md +39 -0
  284. package/src/docs/03-protocols/swarm.md +40 -0
  285. package/src/docs/03-protocols/validation.md +174 -0
  286. package/src/docs/04-ui/activity-bar.md +70 -0
  287. package/src/docs/04-ui/chat-pane.md +80 -0
  288. package/src/docs/04-ui/code-intel.md +62 -0
  289. package/src/docs/04-ui/file-editing.md +61 -0
  290. package/src/docs/04-ui/git.md +54 -0
  291. package/src/docs/04-ui/keybindings.md +79 -0
  292. package/src/docs/04-ui/knowledge-map.md +76 -0
  293. package/src/docs/04-ui/overview.md +93 -0
  294. package/src/docs/04-ui/panels.md +49 -0
  295. package/src/docs/04-ui/pinboard-selection.md +66 -0
  296. package/src/docs/04-ui/quick-open-palette.md +56 -0
  297. package/src/docs/04-ui/sessions.md +81 -0
  298. package/src/docs/04-ui/settings-permissions.md +56 -0
  299. package/src/docs/05-commands/reference.md +59 -0
  300. package/src/docs/06-outputs/directory-map.md +116 -0
  301. package/src/docs/07-workflows/ai-eval-first.md +57 -0
  302. package/src/docs/07-workflows/deep-study-to-production.md +76 -0
  303. package/src/docs/07-workflows/diverge-exploration.md +77 -0
  304. package/src/docs/07-workflows/quick-analysis.md +45 -0
  305. package/src/docs/08-integrations/claude-code-auto-mode.md +191 -0
  306. package/src/docs/08-integrations/google-slides.md +175 -0
  307. package/src/docs/README.md +30 -0
  308. package/src/docs/manifest.json +108 -0
  309. package/src/templates/analysis-template.md +20 -0
  310. package/src/templates/branch-report.md +46 -0
  311. package/src/templates/diff-report.md +88 -0
  312. package/src/templates/knowledge-index.md +7 -0
  313. package/src/templates/model-card-schema.json +186 -0
  314. package/src/templates/model-card-schema.md +88 -0
  315. package/src/templates/model-card.md +124 -0
  316. package/src/templates/project-plan.md +47 -0
  317. package/src/templates/project-specs.md +81 -0
  318. package/src/templates/report-template.md +43 -0
  319. package/src/templates/study-template.md +25 -0
  320. package/src/ui/cc-readonly.js +181 -0
  321. package/src/ui/chat-session.js +466 -0
  322. package/src/ui/css/base.css +136 -0
  323. package/src/ui/css/brainstorm.css +525 -0
  324. package/src/ui/css/chat.css +1405 -0
  325. package/src/ui/css/editor.css +546 -0
  326. package/src/ui/css/eval-dashboard.css +157 -0
  327. package/src/ui/css/experiment.css +237 -0
  328. package/src/ui/css/guide.css +186 -0
  329. package/src/ui/css/knowledge-map.css +383 -0
  330. package/src/ui/css/layout.css +431 -0
  331. package/src/ui/css/model-card.css +161 -0
  332. package/src/ui/css/notebook-walkthrough.css +271 -0
  333. package/src/ui/css/pr-review.css +403 -0
  334. package/src/ui/css/prompt-lab.css +325 -0
  335. package/src/ui/css/sessions.css +258 -0
  336. package/src/ui/css/sidebar.css +661 -0
  337. package/src/ui/css/terminal.css +113 -0
  338. package/src/ui/css/theme-light.css +542 -0
  339. package/src/ui/index.html +389 -0
  340. package/src/ui/js/agents.js +32 -0
  341. package/src/ui/js/bookmarks.js +230 -0
  342. package/src/ui/js/chat.js +1776 -0
  343. package/src/ui/js/code-intel.js +328 -0
  344. package/src/ui/js/command-palette.js +142 -0
  345. package/src/ui/js/events.js +591 -0
  346. package/src/ui/js/explorer.js +317 -0
  347. package/src/ui/js/file-view.js +477 -0
  348. package/src/ui/js/git.js +536 -0
  349. package/src/ui/js/guide.js +198 -0
  350. package/src/ui/js/hud.js +75 -0
  351. package/src/ui/js/init.js +351 -0
  352. package/src/ui/js/knowledge-map.js +906 -0
  353. package/src/ui/js/markdown.js +114 -0
  354. package/src/ui/js/monaco.js +164 -0
  355. package/src/ui/js/notebook-walkthrough.js +272 -0
  356. package/src/ui/js/notebook.js +448 -0
  357. package/src/ui/js/panels.js +2681 -0
  358. package/src/ui/js/pinboard.js +186 -0
  359. package/src/ui/js/quick-open.js +164 -0
  360. package/src/ui/js/selection-context.js +131 -0
  361. package/src/ui/js/sessions.js +256 -0
  362. package/src/ui/js/settings.js +476 -0
  363. package/src/ui/js/split-view.js +82 -0
  364. package/src/ui/js/state.js +343 -0
  365. package/src/ui/js/table.js +161 -0
  366. package/src/ui/js/tabs.js +284 -0
  367. package/src/ui/js/tabular.js +125 -0
  368. package/src/ui/js/terminal.js +354 -0
  369. package/src/ui/js/timeline.js +137 -0
  370. package/src/ui/js/utils.js +293 -0
  371. package/src/ui/notebook-kernel.py +790 -0
  372. package/src/ui/open-browser.js +55 -0
  373. package/src/ui/permission-pattern.js +42 -0
  374. package/src/ui/relay.js +513 -0
  375. package/src/ui/server.js +3072 -0
  376. package/src/ui/session-index.js +225 -0
  377. package/src/ui/shards_icon.png +0 -0
  378. package/src/ui/spawn-server.js +41 -0
  379. package/src/ui/symbol-index.js +813 -0
  380. package/src/ui/ui-push.js +177 -0
  381. package/tools/gate-hook/VALIDATION_SPEC.md +273 -0
  382. package/tools/gate-hook/__tests__/auto-verify.test.js +343 -0
  383. package/tools/gate-hook/auto-allowlist.js +179 -0
  384. package/tools/gate-hook/auto-state.js +68 -0
  385. package/tools/gate-hook/classify.js +21 -0
  386. package/tools/gate-hook/log.js +57 -0
  387. package/tools/gate-hook/parser.js +205 -0
  388. package/tools/gate-hook/sql-guard.js +230 -0
  389. package/tools/gate-hook/state.js +170 -0
  390. package/tools/gate-hook/sweep.js +139 -0
  391. package/tools/gate-hook/transcript.js +45 -0
  392. package/tools/gate-hook/validation.js +321 -0
  393. package/tools/gate-hook.js +475 -0
  394. package/tools/install.js +914 -0
  395. package/tools/shards-gates.js +311 -0
  396. package/tools/shards-sessions.js +261 -0
  397. package/tools/shards-ui.js +377 -0
@@ -0,0 +1,2681 @@
1
+ // ═══════════════════════════════════════════════════════════════
2
+ // Agent-pushed panel tabs (data-viewer, dag, diagram, chart, etc.)
3
+ // ═══════════════════════════════════════════════════════════════
4
+
5
+ // ─── Theme re-render helper (called from init.js / settings.js) ──────────────
6
+
7
+ function rerenderActiveDiagramPanel() {
8
+ var activeKey = splitMode ? currentFileInPane : activeTabId;
9
+ if (!activeKey) return;
10
+ var p = openPanels[activeKey];
11
+ if (!p || (p.panel !== 'diagram' && p.panel !== 'dag')) return;
12
+ if (p._diagramState) p._diagramState.lastContent = null; // force Mermaid re-init with new theme
13
+ renderPanelPane(activeKey);
14
+ }
15
+
16
+ // ─── Data normalization ────────────────────────────────────────────────────────
17
+ // Accepts any data shape the server or agent may send; returns { columns, data }
18
+
19
+ function normalizePanelData(raw) {
20
+ if (!raw) return { columns: [], data: [] };
21
+ // Already normalized by server (CSV/JSON file parse or prior normalization)
22
+ if (raw.columns && Array.isArray(raw.columns) && raw.data && Array.isArray(raw.data)) {
23
+ return raw;
24
+ }
25
+ // Inline JSON array of objects
26
+ if (Array.isArray(raw)) {
27
+ if (raw.length === 0) return { columns: [], data: [] };
28
+ if (typeof raw[0] === 'object' && raw[0] !== null) return flattenToTable(raw);
29
+ // Array of primitives — single column
30
+ return { columns: ['value'], data: raw.map(function(v) { return { value: v }; }) };
31
+ }
32
+ // Single object — wrap as one row
33
+ if (typeof raw === 'object') return flattenToTable([raw]);
34
+ return { columns: [], data: [] };
35
+ }
36
+
37
+ // ─── Panel tab lifecycle ──────────────────────────────────────────────────────
38
+
39
+ function openPanelTab(panelId, panelInfo) {
40
+ if (openPanels[panelId]) {
41
+ // Update existing panel
42
+ var p = openPanels[panelId];
43
+ if (panelInfo.title) p.title = panelInfo.title;
44
+ if (panelInfo.agent) p.agent = panelInfo.agent;
45
+ p.rawData = panelInfo.data !== undefined ? panelInfo.data : p.rawData;
46
+ renderWsTabs();
47
+ // Re-render if this panel is currently active
48
+ var activeKey = splitMode ? currentFileInPane : activeTabId;
49
+ if (activeKey === panelId) renderPanelPane(panelId);
50
+ return;
51
+ }
52
+
53
+ openPanels[panelId] = {
54
+ panelId: panelId,
55
+ panel: panelInfo.panel,
56
+ title: panelInfo.title || panelInfo.panel,
57
+ agent: panelInfo.agent || null,
58
+ rawData: panelInfo.data !== undefined ? panelInfo.data : null,
59
+ tabulatorInstance: null,
60
+ };
61
+ panelTabOrder.push(panelId);
62
+
63
+ // Switch to the new panel
64
+ if (splitMode) {
65
+ currentFileInPane = panelId;
66
+ renderWsTabs();
67
+ renderPanelPane(panelId);
68
+ } else {
69
+ activeTabId = panelId;
70
+ renderWsTabs();
71
+ showActiveContent();
72
+ }
73
+ }
74
+
75
+ function closePanelTab(panelId) {
76
+ var p = openPanels[panelId];
77
+ if (!p) return;
78
+
79
+ // Destroy tabulator instance if it exists
80
+ if (p.tabulatorInstance) {
81
+ if (activeTabulatorInstance === p.tabulatorInstance) {
82
+ activeTabulatorInstance = null;
83
+ activeTabularColumns = null;
84
+ }
85
+ try { p.tabulatorInstance.destroy(); } catch(e) {}
86
+ p.tabulatorInstance = null;
87
+ }
88
+
89
+ delete openPanels[panelId];
90
+ var ptIdx = panelTabOrder.indexOf(panelId);
91
+ if (ptIdx !== -1) panelTabOrder.splice(ptIdx, 1);
92
+
93
+ // Switch away if this panel was active
94
+ var activeKey = splitMode ? currentFileInPane : activeTabId;
95
+ if (activeKey === panelId) {
96
+ if (splitMode) {
97
+ currentFileInPane = fileTabOrder.length > 0 ? fileTabOrder[0] :
98
+ (panelTabOrder.length > 0 ? panelTabOrder[0] : null);
99
+ } else {
100
+ activeTabId = panelTabOrder.length > 0 ? panelTabOrder[0] :
101
+ (fileTabOrder.length > 0 ? fileTabOrder[0] : 'chat');
102
+ }
103
+ }
104
+
105
+ renderWsTabs();
106
+ showActiveContent();
107
+ }
108
+
109
+ // ─── Live update (Story 2.3) ──────────────────────────────────────────────────
110
+ // Called on ui-panel-update SSE; re-renders data preserving sort state
111
+
112
+ function updatePanelData(panelId, newData) {
113
+ var p = openPanels[panelId];
114
+ if (!p) return;
115
+ var oldRawData = p.rawData;
116
+ p.rawData = newData;
117
+
118
+ // Only do a live update if this panel is currently visible
119
+ var activeKey = splitMode ? currentFileInPane : activeTabId;
120
+ if (activeKey !== panelId) return;
121
+
122
+ if (p.panel === 'diagram' || p.panel === 'dag') {
123
+ var oldContent = typeof oldRawData === 'string' ? oldRawData : JSON.stringify(oldRawData, null, 2);
124
+ var newContent = typeof newData === 'string' ? newData : JSON.stringify(newData, null, 2);
125
+ if (oldContent === newContent) return;
126
+ if (p._diagramState) {
127
+ p._diagramState.lastContent = null; // force re-fit on changed content
128
+ }
129
+ renderPanelPane(panelId);
130
+ return;
131
+ }
132
+
133
+ if (p.panel === 'experiment-dashboard') {
134
+ cleanupExperimentDashboard(p);
135
+ renderExperimentDashboard(document.getElementById('file-rendered-view'), p);
136
+ return;
137
+ }
138
+
139
+ if (p.panel === 'prompt-lab') {
140
+ cleanupPromptLab(p);
141
+ renderPromptLab(document.getElementById('file-rendered-view'), p);
142
+ return;
143
+ }
144
+
145
+ if (p.panel === 'eval-dashboard') {
146
+ cleanupEvalDashboard(p);
147
+ renderEvalDashboard(document.getElementById('file-rendered-view'), p);
148
+ return;
149
+ }
150
+
151
+ if (p.panel === 'model-card') {
152
+ cleanupModelCard(p);
153
+ renderModelCard(document.getElementById('file-rendered-view'), p);
154
+ return;
155
+ }
156
+
157
+ if (p.panel === 'knowledge-map') {
158
+ cleanupKnowledgeMap(p);
159
+ renderKnowledgeMapPanel(document.getElementById('file-rendered-view'), p);
160
+ return;
161
+ }
162
+
163
+ if (p.panel === 'guide') {
164
+ cleanupGuide(p);
165
+ renderGuidePanel(document.getElementById('file-rendered-view'), p);
166
+ return;
167
+ }
168
+
169
+ if (p.panel === 'pr-review') {
170
+ renderPRReviewPanel(document.getElementById('file-rendered-view'), p);
171
+ return;
172
+ }
173
+
174
+ if (p.panel === 'notebook-walkthrough') {
175
+ if (typeof cleanupNotebookWalkthrough === 'function') cleanupNotebookWalkthrough(p);
176
+ if (typeof renderNotebookWalkthrough === 'function') {
177
+ renderNotebookWalkthrough(document.getElementById('file-rendered-view'), p);
178
+ }
179
+ return;
180
+ }
181
+
182
+ if (p.panel === 'brainstorm') {
183
+ cleanupBrainstorm(p);
184
+ renderBrainstorm(document.getElementById('file-rendered-view'), p);
185
+ return;
186
+ }
187
+
188
+ if (p.panel === 'data-viewer' && p.tabulatorInstance) {
189
+ var sorters = [];
190
+ try { sorters = p.tabulatorInstance.getSorters() || []; } catch(e) {}
191
+ var tableData = normalizePanelData(newData);
192
+ p.tabulatorInstance.replaceData(tableData.data).then(function() {
193
+ if (sorters.length > 0) {
194
+ try { p.tabulatorInstance.setSort(sorters); } catch(e) {}
195
+ }
196
+ document.getElementById('table-row-count').textContent = tableData.data.length + ' rows';
197
+ }).catch(function() {});
198
+ }
199
+ }
200
+
201
+ // ─── Panel rendering ──────────────────────────────────────────────────────────
202
+
203
+ function renderPanelPane(panelId) {
204
+ var p = openPanels[panelId];
205
+ if (!p) { renderEmptyFilePane(); return; }
206
+
207
+ // Set header, hide file-specific controls
208
+ document.getElementById('file-path-display').textContent = p.title;
209
+ document.getElementById('edit-btn').style.display = 'none';
210
+ document.getElementById('save-btn').style.display = 'none';
211
+
212
+ var renderedView = document.getElementById('file-rendered-view');
213
+ var editorEl = document.getElementById('file-editor');
214
+ var tableView = document.getElementById('table-view');
215
+
216
+ renderedView.classList.remove('visible');
217
+ renderedView.classList.remove('diagram-active');
218
+ renderedView.innerHTML = '';
219
+ editorEl.style.display = 'none';
220
+ disposeMonacoInstance();
221
+
222
+ if (p.panel === 'data-viewer') {
223
+ tableView.classList.add('visible');
224
+ document.getElementById('table-download-csv').style.display = 'inline-block';
225
+
226
+ // Always destroy stale instance — container DOM may have been overwritten
227
+ // by a file tab's initTabulator since we last rendered this panel
228
+ if (p.tabulatorInstance) {
229
+ if (activeTabulatorInstance === p.tabulatorInstance) {
230
+ activeTabulatorInstance = null;
231
+ activeTabularColumns = null;
232
+ }
233
+ try { p.tabulatorInstance.destroy(); } catch(e) {}
234
+ p.tabulatorInstance = null;
235
+ }
236
+
237
+ var tableData = normalizePanelData(p.rawData);
238
+ initPanelTabulator(p, tableData);
239
+ } else if (p.panel === 'experiment-dashboard') {
240
+ tableView.classList.remove('visible');
241
+ renderedView.classList.add('visible');
242
+ renderExperimentDashboard(renderedView, p);
243
+ } else if (p.panel === 'prompt-lab') {
244
+ tableView.classList.remove('visible');
245
+ renderedView.classList.add('visible');
246
+ renderPromptLab(renderedView, p);
247
+ } else if (p.panel === 'eval-dashboard') {
248
+ tableView.classList.remove('visible');
249
+ renderedView.classList.add('visible');
250
+ renderEvalDashboard(renderedView, p);
251
+ } else if (p.panel === 'model-card') {
252
+ tableView.classList.remove('visible');
253
+ renderedView.classList.add('visible');
254
+ renderModelCard(renderedView, p);
255
+ } else if (p.panel === 'chart') {
256
+ tableView.classList.remove('visible');
257
+ renderedView.classList.add('visible');
258
+ renderChartPanel(renderedView, p);
259
+ } else if (p.panel === 'diagram' || p.panel === 'dag') {
260
+ tableView.classList.remove('visible');
261
+ renderedView.classList.add('visible');
262
+ renderDiagramPanel(renderedView, p);
263
+ } else if (p.panel === 'knowledge-map') {
264
+ tableView.classList.remove('visible');
265
+ renderedView.classList.add('visible');
266
+ renderKnowledgeMapPanel(renderedView, p);
267
+ } else if (p.panel === 'guide') {
268
+ tableView.classList.remove('visible');
269
+ renderedView.classList.add('visible');
270
+ renderGuidePanel(renderedView, p);
271
+ } else if (p.panel === 'pr-review') {
272
+ tableView.classList.remove('visible');
273
+ renderedView.classList.add('visible');
274
+ renderPRReviewPanel(renderedView, p);
275
+ } else if (p.panel === 'notebook-walkthrough') {
276
+ tableView.classList.remove('visible');
277
+ renderedView.classList.add('visible');
278
+ if (typeof renderNotebookWalkthrough === 'function') {
279
+ renderNotebookWalkthrough(renderedView, p);
280
+ } else {
281
+ renderedView.innerHTML = '<div class="no-file-msg">notebook-walkthrough renderer not loaded.</div>';
282
+ }
283
+ } else if (p.panel === 'brainstorm') {
284
+ tableView.classList.remove('visible');
285
+ renderedView.classList.add('visible');
286
+ renderBrainstorm(renderedView, p);
287
+ } else {
288
+ tableView.classList.remove('visible');
289
+ renderedView.classList.add('visible');
290
+ renderedView.innerHTML =
291
+ '<div class="no-file-msg">Panel type <strong>' + esc(p.panel) + '</strong> is not yet supported in this version.</div>';
292
+ }
293
+ }
294
+
295
+ // ─── Chart Panel (Plotly.js) ──────────────────────────────────────────────────
296
+
297
+ function renderChartPanel(container, panel) {
298
+ var data = panel.rawData;
299
+ if (!data) {
300
+ container.innerHTML = '<div class="no-file-msg">No chart data provided.</div>';
301
+ return;
302
+ }
303
+
304
+ // Ensure container has a div for Plotly
305
+ var chartId = 'chart-' + panel.panelId;
306
+ container.innerHTML = '<div id="' + chartId + '" style="width:100%;height:100%"></div>';
307
+
308
+ if (typeof Plotly === 'undefined') {
309
+ container.innerHTML = '<div class="no-file-msg">Plotly.js not loaded. Cannot render chart.</div>';
310
+ return;
311
+ }
312
+
313
+ // Support both full Plotly spec { data, layout } and just the data array
314
+ var plotlyData = Array.isArray(data) ? data : (data.data || []);
315
+ var plotlyLayout = data.layout || {
316
+ title: panel.title,
317
+ paper_bgcolor: 'transparent',
318
+ plot_bgcolor: 'transparent',
319
+ font: { color: document.documentElement.getAttribute('data-theme') !== 'light' ? '#b0b0c8' : '#333' },
320
+ margin: { t: 40, r: 20, b: 40, l: 60 }
321
+ };
322
+
323
+ try {
324
+ Plotly.newPlot(chartId, plotlyData, plotlyLayout, { responsive: true, displayModeBar: false });
325
+ } catch(e) {
326
+ container.innerHTML = '<div class="no-file-msg">Failed to render Plotly chart: ' + esc(e.message) + '</div>';
327
+ }
328
+ }
329
+
330
+ // ─── Diagram Panel (Mermaid.js) — pan / zoom ──────────────────────────────────
331
+
332
+ function renderDiagramPanel(container, panel) {
333
+ var data = panel.rawData;
334
+ if (!data) {
335
+ container.innerHTML = '<div class="no-file-msg">No diagram data provided.</div>';
336
+ return;
337
+ }
338
+
339
+ var content = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
340
+
341
+ if (typeof mermaid === 'undefined') {
342
+ container.innerHTML = '<div class="no-file-msg">Mermaid.js not loaded. Cannot render diagram.</div>';
343
+ return;
344
+ }
345
+
346
+ // Decide whether to restore viewport or fit-to-view
347
+ var isRestore = panel._diagramState && panel._diagramState.lastContent === content;
348
+ if (!panel._diagramState) {
349
+ panel._diagramState = { panX: 0, panY: 0, scale: 1, lastContent: content };
350
+ }
351
+ panel._diagramState.lastContent = content;
352
+
353
+ var diagramId = 'mermaid-' + panel.panelId;
354
+
355
+ // Build DOM: viewport > canvas > mermaid + controls
356
+ container.innerHTML = '';
357
+ container.classList.add('diagram-active');
358
+
359
+ var viewport = document.createElement('div');
360
+ viewport.className = 'diagram-viewport';
361
+
362
+ var canvas = document.createElement('div');
363
+ canvas.className = 'diagram-canvas';
364
+
365
+ var mermaidDiv = document.createElement('div');
366
+ mermaidDiv.className = 'mermaid';
367
+ mermaidDiv.id = diagramId;
368
+ mermaidDiv.textContent = content;
369
+
370
+ canvas.appendChild(mermaidDiv);
371
+ viewport.appendChild(canvas);
372
+
373
+ var controls = document.createElement('div');
374
+ controls.className = 'diagram-controls';
375
+ controls.innerHTML =
376
+ '<button class="diagram-ctrl-btn" title="Zoom in" data-action="zoom-in">+</button>' +
377
+ '<span class="diagram-zoom-label">100%</span>' +
378
+ '<button class="diagram-ctrl-btn" title="Zoom out" data-action="zoom-out">&minus;</button>' +
379
+ '<button class="diagram-ctrl-btn" title="Fit to view" data-action="fit">&#9638;</button>';
380
+ viewport.appendChild(controls);
381
+ container.appendChild(viewport);
382
+
383
+ mermaid.initialize({
384
+ startOnLoad: false,
385
+ theme: document.documentElement.getAttribute('data-theme') !== 'light' ? 'dark' : 'default',
386
+ securityLevel: 'loose',
387
+ });
388
+
389
+ try {
390
+ var result = mermaid.run({ nodes: [document.getElementById(diagramId)] });
391
+ // mermaid.run returns a promise in v11+
392
+ if (result && typeof result.then === 'function') {
393
+ result.then(function() {
394
+ if (isRestore) {
395
+ applyDiagramTransform(canvas, panel._diagramState, controls);
396
+ } else {
397
+ fitDiagramToView(viewport, canvas, panel._diagramState, controls);
398
+ }
399
+ });
400
+ } else {
401
+ // Fallback: run synchronously completed
402
+ if (isRestore) {
403
+ applyDiagramTransform(canvas, panel._diagramState, controls);
404
+ } else {
405
+ fitDiagramToView(viewport, canvas, panel._diagramState, controls);
406
+ }
407
+ }
408
+ } catch(e) {
409
+ container.innerHTML = '<div class="no-file-msg">Failed to render Mermaid diagram: ' + esc(e.message) + '</div>';
410
+ return;
411
+ }
412
+
413
+ initDiagramPanZoom(viewport, canvas, panel._diagramState, controls);
414
+ }
415
+
416
+ function applyDiagramTransform(canvas, state, controls) {
417
+ canvas.style.transform = 'translate(' + state.panX + 'px, ' + state.panY + 'px) scale(' + state.scale + ')';
418
+ var label = controls.querySelector('.diagram-zoom-label');
419
+ if (label) label.textContent = Math.round(state.scale * 100) + '%';
420
+ }
421
+
422
+ function fitDiagramToView(viewport, canvas, state, controls) {
423
+ canvas.style.transform = 'none';
424
+ var svg = canvas.querySelector('svg');
425
+ if (!svg) return;
426
+
427
+ var svgRect = svg.getBoundingClientRect();
428
+ var vpRect = viewport.getBoundingClientRect();
429
+ var pad = 40;
430
+
431
+ if (svgRect.width === 0 || svgRect.height === 0) return;
432
+
433
+ var scaleX = (vpRect.width - pad * 2) / svgRect.width;
434
+ var scaleY = (vpRect.height - pad * 2) / svgRect.height;
435
+ var scale = Math.min(scaleX, scaleY, 1.5);
436
+ scale = Math.max(scale, 0.1);
437
+
438
+ var scaledW = svgRect.width * scale;
439
+ var scaledH = svgRect.height * scale;
440
+ state.panX = (vpRect.width - scaledW) / 2;
441
+ state.panY = (vpRect.height - scaledH) / 2;
442
+ state.scale = scale;
443
+
444
+ applyDiagramTransform(canvas, state, controls);
445
+ }
446
+
447
+ function initDiagramPanZoom(viewport, canvas, state, controls) {
448
+ var MIN_SCALE = 0.1;
449
+ var MAX_SCALE = 5;
450
+ var isPanning = false;
451
+ var startX, startY, startPanX, startPanY;
452
+
453
+ viewport.addEventListener('pointerdown', function(e) {
454
+ if (e.button !== 0) return;
455
+ if (e.target.closest('.diagram-controls')) return;
456
+ isPanning = true;
457
+ startX = e.clientX;
458
+ startY = e.clientY;
459
+ startPanX = state.panX;
460
+ startPanY = state.panY;
461
+ viewport.style.cursor = 'grabbing';
462
+ viewport.setPointerCapture(e.pointerId);
463
+ e.preventDefault();
464
+ });
465
+
466
+ viewport.addEventListener('pointermove', function(e) {
467
+ if (!isPanning) return;
468
+ state.panX = startPanX + (e.clientX - startX);
469
+ state.panY = startPanY + (e.clientY - startY);
470
+ applyDiagramTransform(canvas, state, controls);
471
+ });
472
+
473
+ viewport.addEventListener('pointerup', function(e) {
474
+ if (!isPanning) return;
475
+ isPanning = false;
476
+ viewport.style.cursor = '';
477
+ });
478
+
479
+ viewport.addEventListener('pointercancel', function() {
480
+ isPanning = false;
481
+ viewport.style.cursor = '';
482
+ });
483
+
484
+ viewport.addEventListener('wheel', function(e) {
485
+ e.preventDefault();
486
+ var rect = viewport.getBoundingClientRect();
487
+ var mouseX = e.clientX - rect.left;
488
+ var mouseY = e.clientY - rect.top;
489
+
490
+ var zoomFactor = e.deltaY < 0 ? 1.1 : 0.9;
491
+ var newScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, state.scale * zoomFactor));
492
+
493
+ var scaleChange = newScale / state.scale;
494
+ state.panX = mouseX - scaleChange * (mouseX - state.panX);
495
+ state.panY = mouseY - scaleChange * (mouseY - state.panY);
496
+ state.scale = newScale;
497
+
498
+ applyDiagramTransform(canvas, state, controls);
499
+ }, { passive: false });
500
+
501
+ controls.addEventListener('click', function(e) {
502
+ var btn = e.target.closest('[data-action]');
503
+ if (!btn) return;
504
+ var action = btn.dataset.action;
505
+ var rect = viewport.getBoundingClientRect();
506
+ var cx = rect.width / 2;
507
+ var cy = rect.height / 2;
508
+
509
+ if (action === 'zoom-in') {
510
+ var ns = Math.min(MAX_SCALE, state.scale * 1.25);
511
+ var sc = ns / state.scale;
512
+ state.panX = cx - sc * (cx - state.panX);
513
+ state.panY = cy - sc * (cy - state.panY);
514
+ state.scale = ns;
515
+ applyDiagramTransform(canvas, state, controls);
516
+ } else if (action === 'zoom-out') {
517
+ var ns2 = Math.max(MIN_SCALE, state.scale * 0.8);
518
+ var sc2 = ns2 / state.scale;
519
+ state.panX = cx - sc2 * (cx - state.panX);
520
+ state.panY = cy - sc2 * (cy - state.panY);
521
+ state.scale = ns2;
522
+ applyDiagramTransform(canvas, state, controls);
523
+ } else if (action === 'fit') {
524
+ fitDiagramToView(viewport, canvas, state, controls);
525
+ }
526
+ });
527
+ }
528
+
529
+ // ─── Panel Tabulator init ─────────────────────────────────────────────────────
530
+
531
+ function initPanelTabulator(panel, tableData) {
532
+ // If the global active instance belongs to a file (not this panel), leave it —
533
+ // it will be cleaned up by the next renderFilePane call. We just overwrite the
534
+ // global pointer after creating the panel instance.
535
+ activeTabularColumns = tableData.columns;
536
+
537
+ var columns = tableData.columns.map(function(col) {
538
+ return {
539
+ title: col,
540
+ field: col,
541
+ headerFilter: 'input',
542
+ sorter: 'string',
543
+ resizable: true,
544
+ editor: false,
545
+ };
546
+ });
547
+
548
+ var container = document.getElementById('table-container');
549
+ container.innerHTML = '';
550
+
551
+ // Hide edit buttons — panels are always read-only
552
+ document.getElementById('table-edit-btns').className = '';
553
+
554
+ if (typeof Tabulator === 'undefined') {
555
+ container.innerHTML = '<div class="no-file-msg">Tabulator not loaded. Cannot render data viewer.</div>';
556
+ return;
557
+ }
558
+
559
+ var instance = new Tabulator(container, {
560
+ data: tableData.data,
561
+ columns: columns.length > 0 ? columns : [{ title: '(no columns)', field: '_empty' }],
562
+ layout: 'fitDataFill',
563
+ height: '100%',
564
+ virtualDom: true,
565
+ selectableRows: false,
566
+ placeholder: 'No data',
567
+ });
568
+
569
+ instance.on('dataLoaded', function(data) {
570
+ document.getElementById('table-row-count').textContent = data.length + ' rows';
571
+ });
572
+ instance.on('dataFiltered', function(filters, rows) {
573
+ document.getElementById('table-row-count').textContent = rows.length + ' rows';
574
+ });
575
+
576
+ // Wire global search to this instance
577
+ var searchInput = document.getElementById('table-search');
578
+ searchInput.value = '';
579
+ searchInput.oninput = function() {
580
+ var term = searchInput.value.toLowerCase();
581
+ if (!term) {
582
+ instance.clearFilter();
583
+ } else {
584
+ instance.setFilter(function(rowData) {
585
+ return Object.values(rowData).some(function(v) {
586
+ return v != null && String(v).toLowerCase().includes(term);
587
+ });
588
+ });
589
+ }
590
+ };
591
+
592
+ panel.tabulatorInstance = instance;
593
+ activeTabulatorInstance = instance;
594
+ }
595
+
596
+ // ─── Experiment Dashboard ────────────────────────────────────────────────────
597
+
598
+ function cleanupExperimentDashboard(panel) {
599
+ if (panel._expTabulator) {
600
+ try { panel._expTabulator.destroy(); } catch(e) {}
601
+ panel._expTabulator = null;
602
+ }
603
+ var barEl = document.getElementById('exp-bar-' + panel.panelId);
604
+ var lineEl = document.getElementById('exp-line-' + panel.panelId);
605
+ if (typeof Plotly !== 'undefined') {
606
+ if (barEl) try { Plotly.purge(barEl); } catch(e) {}
607
+ if (lineEl) try { Plotly.purge(lineEl); } catch(e) {}
608
+ }
609
+ }
610
+
611
+ function renderExperimentDashboard(container, panel) {
612
+ var d = panel.rawData;
613
+ if (!d) {
614
+ container.innerHTML = '<div class="no-file-msg">No experiment data provided.</div>';
615
+ return;
616
+ }
617
+
618
+ // Detect AR (autonomous-research) mode vs. classic experiment mode.
619
+ // AR uses `iterationBudget` in constraints and marks `mode: "autonomous-research"`.
620
+ // Classic experiment mode uses `plannedCount`. The dashboard renders both, with
621
+ // AR-specific enrichments (auto-decision colors, cost accounting, convergence).
622
+ var isAR = d.mode === 'autonomous-research';
623
+ var constraints = d.constraints || {};
624
+ var cost = d.costAccounting || {};
625
+ var convergence = d.convergence || {};
626
+ var exps = d.experiments || [];
627
+
628
+ // Preserve comparison state across re-renders
629
+ panel._expState = panel._expState || { selectedRows: [] };
630
+
631
+ var html = '<div class="experiment-dashboard' + (isAR ? ' ar-mode' : '') + '">';
632
+
633
+ // ── Progress bar ──
634
+ var total = isAR
635
+ ? (constraints.iterationBudget || d.plannedCount || 0)
636
+ : (d.plannedCount || 0);
637
+ var done = exps.length;
638
+ var status = d.status || 'setup';
639
+ var current = d.currentExperiment || null;
640
+ var label = isAR ? 'iterations' : 'experiments';
641
+
642
+ html += '<div class="exp-progress">';
643
+ html += '<div class="exp-progress-label">';
644
+ html += '<span><span class="exp-status-badge ' + esc(status) + '">' + esc(status) + '</span></span>';
645
+ if (isAR && d.preset) {
646
+ html += '<span class="exp-preset-badge">' + esc(d.preset) + '</span>';
647
+ }
648
+ html += '<span>' + done + ' / ' + total + ' ' + label + '</span>';
649
+ if (isAR && convergence.detected && convergence.reason) {
650
+ html += '<span class="exp-converge-badge" title="Convergence">' + esc(convergence.reason) + '</span>';
651
+ }
652
+ html += '</div>';
653
+ html += '<div class="exp-progress-bar">';
654
+ if (isAR) {
655
+ // AR: color-code each completed iteration by auto-decision
656
+ for (var i = 1; i <= total; i++) {
657
+ var cls = 'pending';
658
+ if (i <= done) {
659
+ var ent = exps[i - 1];
660
+ if (ent && ent.autoDecision === 'green') cls = 'completed ar-green';
661
+ else if (ent && ent.autoDecision === 'red') cls = 'completed ar-red';
662
+ else if (ent && ent.autoDecision === 'yellow') cls = 'completed ar-yellow';
663
+ else cls = 'completed';
664
+ } else if (status === 'running' && current === i) {
665
+ cls = 'active';
666
+ }
667
+ html += '<div class="exp-progress-segment ' + cls + '"></div>';
668
+ }
669
+ } else {
670
+ for (var i = 1; i <= total; i++) {
671
+ var cls = 'pending';
672
+ if (i <= done) cls = 'completed';
673
+ else if (status === 'running' && current === i) cls = 'active';
674
+ html += '<div class="exp-progress-segment ' + cls + '"></div>';
675
+ }
676
+ }
677
+ html += '</div></div>';
678
+
679
+ // ── AR cost/convergence strip ──
680
+ if (isAR) {
681
+ var ceiling = constraints.costCeiling || {};
682
+ var dollarsSpent = cost.dollarsSpent || 0;
683
+ var tokensIn = cost.tokensIn || 0;
684
+ var tokensOut = cost.tokensOut || 0;
685
+ var reviewerTasks = cost.reviewerTasksSpawned || 0;
686
+ var pctDollars = (ceiling.dollars && ceiling.dollars > 0)
687
+ ? (dollarsSpent / ceiling.dollars * 100) : null;
688
+ var pctTokens = (ceiling.tokens && ceiling.tokens > 0)
689
+ ? ((tokensIn + tokensOut) / ceiling.tokens * 100) : null;
690
+ var warnCls = '';
691
+ if (pctDollars >= 80 || pctTokens >= 80) warnCls = ' ar-cost-warn';
692
+ else if (pctDollars >= 50 || pctTokens >= 50) warnCls = ' ar-cost-caution';
693
+
694
+ html += '<div class="ar-cost-strip' + warnCls + '">';
695
+ html += '<span class="ar-cost-item"><strong>$' + dollarsSpent.toFixed(4) + '</strong>';
696
+ if (ceiling.dollars) html += ' / $' + ceiling.dollars;
697
+ html += '</span>';
698
+ html += '<span class="ar-cost-item"><strong>' + (tokensIn + tokensOut) + '</strong> tokens';
699
+ if (ceiling.tokens) html += ' / ' + ceiling.tokens;
700
+ html += '</span>';
701
+ html += '<span class="ar-cost-item"><strong>' + reviewerTasks + '</strong> reviewer tasks</span>';
702
+ if (d.lastGreenCommit) {
703
+ html += '<span class="ar-cost-item" title="lastGreenCommit">green @ '
704
+ + esc(String(d.lastGreenCommit).slice(0, 7)) + '</span>';
705
+ }
706
+ html += '</div>';
707
+ }
708
+
709
+ // ── Charts ──
710
+ var barId = 'exp-bar-' + panel.panelId;
711
+ var lineId = 'exp-line-' + panel.panelId;
712
+ html += '<div class="exp-charts">';
713
+ html += '<div id="' + barId + '"></div>';
714
+ html += '<div id="' + lineId + '"></div>';
715
+ html += '</div>';
716
+
717
+ // ── Comparison controls ──
718
+ html += '<div class="exp-compare-bar">';
719
+ html += '<button id="exp-compare-btn-' + panel.panelId + '" class="exp-compare-action-btn" data-panel-id="' + esc(panel.panelId) + '" disabled>Compare Selected</button>';
720
+ html += '<span id="exp-compare-count-' + panel.panelId + '" class="exp-compare-hint">Select 2 rows to compare</span>';
721
+ html += '</div>';
722
+ html += '<div id="exp-compare-view-' + panel.panelId + '"></div>';
723
+
724
+ // ── Results table ──
725
+ html += '<div class="exp-results-table" id="exp-table-' + panel.panelId + '"></div>';
726
+
727
+ html += '</div>';
728
+ container.innerHTML = html;
729
+
730
+ container.querySelectorAll('.exp-compare-action-btn').forEach(function(btn) {
731
+ btn.addEventListener('click', function() { expCompare(btn.dataset.panelId); });
732
+ });
733
+
734
+ // ── Render Plotly charts ──
735
+ if (typeof Plotly !== 'undefined' && exps.length > 0) {
736
+ var isDark = document.documentElement.getAttribute('data-theme') !== 'light';
737
+ var fontColor = isDark ? '#b0b0c8' : '#333';
738
+ var gridColor = isDark ? '#1e1e32' : '#e0e0e0';
739
+
740
+ // Bar chart — deltas
741
+ var barNames = exps.map(function(e) { return e.name || ('Exp ' + e.index); });
742
+ var barDeltas = exps.map(function(e) { return e.metrics && e.metrics.outcome ? e.metrics.outcome.delta : 0; });
743
+ var barColors = barDeltas.map(function(v) { return v >= 0 ? '#4a9' : '#c84a4a'; });
744
+
745
+ var barShapes = [];
746
+ if (d.successThreshold != null && d.baseline && d.baseline.value != null) {
747
+ barShapes.push({
748
+ type: 'line', yref: 'y', y0: d.successThreshold - d.baseline.value,
749
+ y1: d.successThreshold - d.baseline.value, x0: -0.5, x1: barNames.length - 0.5,
750
+ line: { color: '#c8a84a', width: 1, dash: 'dash' }
751
+ });
752
+ }
753
+
754
+ Plotly.newPlot(barId, [{
755
+ x: barNames, y: barDeltas, type: 'bar',
756
+ marker: { color: barColors },
757
+ hovertemplate: '%{x}: %{y:+.4f}<extra></extra>'
758
+ }], {
759
+ title: { text: 'Outcome Metric Delta', font: { size: 13, color: fontColor } },
760
+ paper_bgcolor: 'transparent', plot_bgcolor: 'transparent',
761
+ font: { color: fontColor }, margin: { t: 36, r: 16, b: 40, l: 50 },
762
+ xaxis: { gridcolor: gridColor }, yaxis: { gridcolor: gridColor, zeroline: true, zerolinecolor: '#6a6a88' },
763
+ shapes: barShapes
764
+ }, { responsive: true, displayModeBar: false });
765
+
766
+ // Line chart — cumulative outcome value
767
+ var lineX = ['Baseline'].concat(barNames);
768
+ var lineY = [d.baseline ? d.baseline.value : 0];
769
+ for (var j = 0; j < exps.length; j++) {
770
+ lineY.push(exps[j].metrics && exps[j].metrics.outcome ? exps[j].metrics.outcome.after : lineY[lineY.length - 1]);
771
+ }
772
+
773
+ var lineShapes = [];
774
+ if (d.successThreshold != null) {
775
+ lineShapes.push({
776
+ type: 'line', yref: 'y', y0: d.successThreshold, y1: d.successThreshold,
777
+ x0: -0.5, x1: lineX.length - 0.5,
778
+ line: { color: '#c8a84a', width: 1, dash: 'dash' }
779
+ });
780
+ }
781
+
782
+ Plotly.newPlot(lineId, [{
783
+ x: lineX, y: lineY, type: 'scatter', mode: 'lines+markers',
784
+ line: { color: '#6a8ac8', width: 2 }, marker: { size: 6 },
785
+ hovertemplate: '%{x}: %{y:.4f}<extra></extra>'
786
+ }], {
787
+ title: { text: 'Outcome Metric Over Time', font: { size: 13, color: fontColor } },
788
+ paper_bgcolor: 'transparent', plot_bgcolor: 'transparent',
789
+ font: { color: fontColor }, margin: { t: 36, r: 16, b: 40, l: 50 },
790
+ xaxis: { gridcolor: gridColor }, yaxis: { gridcolor: gridColor },
791
+ shapes: lineShapes
792
+ }, { responsive: true, displayModeBar: false });
793
+ }
794
+
795
+ // ── Results Tabulator table ──
796
+ if (typeof Tabulator !== 'undefined' && exps.length > 0) {
797
+ var tableRows = exps.map(function(e) {
798
+ var om = e.metrics && e.metrics.outcome ? e.metrics.outcome : {};
799
+ return {
800
+ index: e.index,
801
+ name: e.name || '',
802
+ before: om.before != null ? om.before : '',
803
+ after: om.after != null ? om.after : '',
804
+ delta: om.delta != null ? om.delta : '',
805
+ dsVerdict: e.dsVerdict || '',
806
+ outcome: e.outcome || '',
807
+ recommendation: e.recommendation || '',
808
+ // AR-specific fields (undefined on classic experiment runs — columns hidden below)
809
+ autoDecision: e.autoDecision || '',
810
+ reverted: e.reverted === true ? 'yes' : (e.reverted === false ? 'no' : ''),
811
+ evalType: e.evalType || '',
812
+ hypothesisSource: e.hypothesisSource || '',
813
+ reviewerVerdict: e.reviewerVerdict || ''
814
+ };
815
+ });
816
+
817
+ var columns = [
818
+ { title: '#', field: 'index', width: 40, hozAlign: 'center' },
819
+ { title: 'Name', field: 'name', minWidth: 120 },
820
+ { title: 'Before', field: 'before', hozAlign: 'right', width: 80 },
821
+ { title: 'After', field: 'after', hozAlign: 'right', width: 80 },
822
+ { title: 'Delta', field: 'delta', hozAlign: 'right', width: 80,
823
+ formatter: function(cell) {
824
+ var v = cell.getValue();
825
+ if (v === '' || v == null) return '';
826
+ var cls = v > 0 ? 'exp-delta-positive' : (v < 0 ? 'exp-delta-negative' : '');
827
+ return '<span class="' + cls + '">' + (v > 0 ? '+' : '') + v + '</span>';
828
+ }
829
+ }
830
+ ];
831
+
832
+ if (isAR) {
833
+ columns.push(
834
+ { title: 'Auto', field: 'autoDecision', width: 80,
835
+ formatter: function(cell) {
836
+ var v = cell.getValue();
837
+ if (!v) return '';
838
+ return '<span class="ar-auto-badge ar-' + esc(v) + '">' + esc(v) + '</span>';
839
+ }
840
+ },
841
+ { title: 'Kept?', field: 'reverted', width: 70,
842
+ formatter: function(cell) {
843
+ var v = cell.getValue();
844
+ if (v === 'yes') return '<span class="ar-reverted">reverted</span>';
845
+ if (v === 'no') return '<span class="ar-kept">kept</span>';
846
+ return '';
847
+ }
848
+ },
849
+ { title: 'Eval', field: 'evalType', width: 70 },
850
+ { title: 'Source', field: 'hypothesisSource', width: 110 },
851
+ { title: 'Reviewer', field: 'reviewerVerdict', width: 110,
852
+ formatter: function(cell) {
853
+ var v = cell.getValue();
854
+ if (!v) return '';
855
+ return '<span class="ar-reviewer-verdict ar-rv-' + esc(v.toLowerCase()) + '">' + esc(v) + '</span>';
856
+ }
857
+ }
858
+ );
859
+ } else {
860
+ columns.push(
861
+ { title: 'DS Verdict', field: 'dsVerdict', minWidth: 150,
862
+ formatter: function(cell) {
863
+ var v = cell.getValue();
864
+ return v.length > 80 ? v.substring(0, 80) + '...' : v;
865
+ }
866
+ },
867
+ { title: 'Outcome', field: 'outcome', width: 100,
868
+ formatter: function(cell) {
869
+ var v = cell.getValue();
870
+ var cls = v.toLowerCase();
871
+ return '<span class="exp-outcome-badge ' + cls + '">' + esc(v) + '</span>';
872
+ }
873
+ },
874
+ { title: 'Recommendation', field: 'recommendation', width: 120,
875
+ formatter: function(cell) {
876
+ var v = cell.getValue();
877
+ var cls = v.toLowerCase();
878
+ return '<span class="exp-rec-badge ' + cls + '">' + esc(v) + '</span>';
879
+ }
880
+ }
881
+ );
882
+ }
883
+
884
+ var tblContainer = document.getElementById('exp-table-' + panel.panelId);
885
+ var tbl = new Tabulator(tblContainer, {
886
+ data: tableRows,
887
+ layout: 'fitDataFill',
888
+ height: '100%',
889
+ selectableRows: 'highlight',
890
+ columns: columns
891
+ });
892
+
893
+ // Track selection for comparison
894
+ tbl.on('rowSelected', function() { expUpdateCompareUI(panel.panelId, tbl); });
895
+ tbl.on('rowDeselected', function() { expUpdateCompareUI(panel.panelId, tbl); });
896
+ panel._expTabulator = tbl;
897
+ }
898
+ }
899
+
900
+ // ── Comparison helpers ──
901
+
902
+ function expUpdateCompareUI(panelId, tbl) {
903
+ var selected = tbl.getSelectedData();
904
+ var btn = document.getElementById('exp-compare-btn-' + panelId);
905
+ var count = document.getElementById('exp-compare-count-' + panelId);
906
+ if (btn) btn.disabled = selected.length !== 2;
907
+ if (count) count.textContent = selected.length === 2 ? '2 selected' : 'Select 2 rows to compare';
908
+ }
909
+
910
+ function expCompare(panelId) {
911
+ var p = openPanels[panelId];
912
+ if (!p || !p._expTabulator) return;
913
+ var selected = p._expTabulator.getSelectedData();
914
+ if (selected.length !== 2) return;
915
+
916
+ var d = p.rawData;
917
+ var exps = d.experiments || [];
918
+ var a = exps.find(function(e) { return e.index === selected[0].index; });
919
+ var b = exps.find(function(e) { return e.index === selected[1].index; });
920
+ if (!a || !b) return;
921
+
922
+ var view = document.getElementById('exp-compare-view-' + panelId);
923
+ if (!view) return;
924
+
925
+ view.innerHTML = '<div class="exp-compare-split">' +
926
+ expCompareColumn(a) + expCompareColumn(b) +
927
+ '</div>';
928
+ }
929
+
930
+ function expCompareColumn(exp) {
931
+ var html = '<div>';
932
+ html += '<h4>Experiment ' + exp.index + ': ' + esc(exp.name || '') + '</h4>';
933
+ html += '<table><tr><th>Metric</th><th>Before</th><th>After</th><th>Delta</th></tr>';
934
+
935
+ var om = exp.metrics && exp.metrics.outcome ? exp.metrics.outcome : {};
936
+ html += '<tr><td><strong>Outcome</strong></td>';
937
+ html += '<td>' + (om.before != null ? om.before : '-') + '</td>';
938
+ html += '<td>' + (om.after != null ? om.after : '-') + '</td>';
939
+ html += '<td class="' + (om.delta > 0 ? 'exp-delta-positive' : om.delta < 0 ? 'exp-delta-negative' : '') + '">';
940
+ html += (om.delta != null ? (om.delta > 0 ? '+' : '') + om.delta : '-') + '</td></tr>';
941
+
942
+ var secondary = (exp.metrics && exp.metrics.secondary) || [];
943
+ for (var i = 0; i < secondary.length; i++) {
944
+ var s = secondary[i];
945
+ html += '<tr><td>' + esc(s.name) + '</td>';
946
+ html += '<td>' + (s.before != null ? s.before : '-') + '</td>';
947
+ html += '<td>' + (s.after != null ? s.after : '-') + '</td>';
948
+ var dCls = s.delta > 0 ? 'exp-delta-positive' : s.delta < 0 ? 'exp-delta-negative' : '';
949
+ html += '<td class="' + dCls + '">' + (s.delta != null ? (s.delta > 0 ? '+' : '') + s.delta : '-') + '</td></tr>';
950
+ }
951
+
952
+ html += '</table>';
953
+ html += '<div style="margin-top:8px;font-size:11px"><strong>Recommendation:</strong> ' + esc(exp.recommendation || '-') + '</div>';
954
+ html += '</div>';
955
+ return html;
956
+ }
957
+
958
+ // ═══════════════════════════════════════════════════════════════
959
+ // Eval Dashboard panel
960
+ // ═══════════════════════════════════════════════════════════════
961
+
962
+ function cleanupEvalDashboard(panel) {
963
+ if (panel._evalTabulator) {
964
+ try { panel._evalTabulator.destroy(); } catch(e) {}
965
+ panel._evalTabulator = null;
966
+ }
967
+ if (panel._evalPromptTabulator) {
968
+ try { panel._evalPromptTabulator.destroy(); } catch(e) {}
969
+ panel._evalPromptTabulator = null;
970
+ }
971
+ if (panel._evalInfraTabulator) {
972
+ try { panel._evalInfraTabulator.destroy(); } catch(e) {}
973
+ panel._evalInfraTabulator = null;
974
+ }
975
+ var chartEl = document.getElementById('eval-chart-' + panel.panelId);
976
+ if (typeof Plotly !== 'undefined' && chartEl) {
977
+ try { Plotly.purge(chartEl); } catch(e) {}
978
+ }
979
+ }
980
+
981
+ function renderEvalDashboard(container, panel) {
982
+ var d = panel.rawData;
983
+ if (!d) {
984
+ container.innerHTML = '<div class="no-file-msg">No evaluation data provided.</div>';
985
+ return;
986
+ }
987
+
988
+ var summary = d.summary || {};
989
+ var dims = d.dimensions || [];
990
+ var cost = d.cost || {};
991
+ var variant = d.variant || 'ai-engineer';
992
+ var status = d.status || 'running';
993
+
994
+ var html = '<div class="eval-dashboard">';
995
+
996
+ // ── Summary header ──
997
+ var verdict = (summary.overallVerdict || 'PARTIAL').toLowerCase();
998
+ html += '<div class="eval-summary-header">';
999
+ html += '<span class="eval-verdict-badge ' + verdict + '">' + esc(summary.overallVerdict || 'PENDING') + '</span>';
1000
+ html += '<span class="eval-summary-stat"><strong>' + (summary.passed || 0) + '</strong> passed</span>';
1001
+ html += '<span class="eval-summary-stat"><strong>' + (summary.failed || 0) + '</strong> failed</span>';
1002
+ html += '<span class="eval-status-badge ' + esc(status) + '">' + esc(status) + '</span>';
1003
+ html += '</div>';
1004
+
1005
+ // ── Dimensions table ──
1006
+ html += '<p class="eval-section-label">Evaluation Dimensions</p>';
1007
+ html += '<div class="eval-dimensions-table" id="eval-dims-' + panel.panelId + '"></div>';
1008
+
1009
+ // ── Variant-specific sections ──
1010
+ if (variant === 'ai-engineer') {
1011
+ // Prompt comparison
1012
+ var prompts = d.prompts || [];
1013
+ if (prompts.length > 0) {
1014
+ html += '<p class="eval-section-label">Prompt Comparison</p>';
1015
+ html += '<div class="eval-prompt-compare" id="eval-prompts-' + panel.panelId + '"></div>';
1016
+ }
1017
+
1018
+ // Safety cards
1019
+ var safety = d.safety || {};
1020
+ var safetyKeys = Object.keys(safety);
1021
+ if (safetyKeys.length > 0) {
1022
+ html += '<p class="eval-section-label">Safety</p>';
1023
+ html += '<div class="eval-safety-cards">';
1024
+ for (var si = 0; si < safetyKeys.length; si++) {
1025
+ var sk = safetyKeys[si];
1026
+ var sv = safety[sk];
1027
+ var rate = sv.passRate != null ? sv.passRate : 0;
1028
+ var rateStr = (rate * 100).toFixed(1) + '%';
1029
+ var rateCls = rate >= 0.95 ? 'high' : rate >= 0.8 ? 'medium' : 'low';
1030
+ html += '<div class="eval-safety-card">';
1031
+ html += '<div class="eval-safety-card-label">' + esc(sk.replace(/([A-Z])/g, ' $1').trim()) + '</div>';
1032
+ html += '<div class="eval-safety-card-value ' + rateCls + '">' + rateStr + '</div>';
1033
+ html += '<div class="eval-safety-card-count">' + (sv.total || 0) + ' tests</div>';
1034
+ html += '</div>';
1035
+ }
1036
+ html += '</div>';
1037
+ }
1038
+ } else if (variant === 'ml-engineer') {
1039
+ // Baseline vs Candidate chart
1040
+ var baseline = d.baseline;
1041
+ var bestCandidate = d.bestCandidate;
1042
+ if (baseline && bestCandidate) {
1043
+ html += '<p class="eval-section-label">Baseline vs Best Candidate</p>';
1044
+ html += '<div class="eval-charts"><div id="eval-chart-' + panel.panelId + '"></div></div>';
1045
+ }
1046
+
1047
+ // Infrastructure readiness
1048
+ var infra = d.infrastructure || [];
1049
+ if (infra.length > 0) {
1050
+ html += '<p class="eval-section-label">Infrastructure Readiness</p>';
1051
+ html += '<div class="eval-infra-table" id="eval-infra-' + panel.panelId + '"></div>';
1052
+ }
1053
+ }
1054
+
1055
+ // ── Cost cards ──
1056
+ if (cost.perRequest != null || cost.per1kTokens != null || cost.monthlyProjected != null) {
1057
+ html += '<p class="eval-section-label">Cost</p>';
1058
+ html += '<div class="eval-cost-cards">';
1059
+ if (cost.perRequest != null) {
1060
+ html += '<div class="eval-cost-card"><div class="eval-cost-card-label">Per Request</div>';
1061
+ html += '<div class="eval-cost-card-value">$' + cost.perRequest.toFixed(4) + '</div></div>';
1062
+ }
1063
+ if (cost.per1kTokens != null) {
1064
+ html += '<div class="eval-cost-card"><div class="eval-cost-card-label">Per 1k Tokens</div>';
1065
+ html += '<div class="eval-cost-card-value">$' + cost.per1kTokens.toFixed(4) + '</div></div>';
1066
+ }
1067
+ if (cost.monthlyProjected != null) {
1068
+ html += '<div class="eval-cost-card"><div class="eval-cost-card-label">Monthly Projection</div>';
1069
+ html += '<div class="eval-cost-card-value">$' + cost.monthlyProjected.toLocaleString() + '</div></div>';
1070
+ }
1071
+ if (cost.budget != null) {
1072
+ html += '<div class="eval-cost-card"><div class="eval-cost-card-label">Budget</div>';
1073
+ html += '<div class="eval-cost-card-value">$' + cost.budget.toLocaleString() + '</div></div>';
1074
+ }
1075
+ html += '</div>';
1076
+ }
1077
+
1078
+ html += '</div>';
1079
+ container.innerHTML = html;
1080
+
1081
+ // ── Render dimensions Tabulator ──
1082
+ if (typeof Tabulator !== 'undefined' && dims.length > 0) {
1083
+ var dimsEl = document.getElementById('eval-dims-' + panel.panelId);
1084
+ if (dimsEl) {
1085
+ panel._evalTabulator = new Tabulator(dimsEl, {
1086
+ data: dims,
1087
+ layout: 'fitDataFill',
1088
+ height: '100%',
1089
+ columns: [
1090
+ { title: 'Dimension', field: 'dimension', minWidth: 120 },
1091
+ { title: 'Metric', field: 'metric', minWidth: 100 },
1092
+ { title: 'Target', field: 'target', hozAlign: 'right', width: 90 },
1093
+ { title: 'Actual', field: 'actual', hozAlign: 'right', width: 90 },
1094
+ { title: 'Unit', field: 'unit', width: 80 },
1095
+ { title: 'Verdict', field: 'verdict', width: 80, hozAlign: 'center',
1096
+ formatter: function(cell) {
1097
+ var v = (cell.getValue() || '').toLowerCase();
1098
+ return '<span class="eval-cell-' + (v === 'pass' ? 'pass' : 'fail') + '">' + esc(v.toUpperCase()) + '</span>';
1099
+ }
1100
+ }
1101
+ ]
1102
+ });
1103
+ }
1104
+ }
1105
+
1106
+ // ── AI Engineer: Prompt comparison Tabulator ──
1107
+ if (variant === 'ai-engineer' && typeof Tabulator !== 'undefined') {
1108
+ var prompts = d.prompts || [];
1109
+ var promptsEl = document.getElementById('eval-prompts-' + panel.panelId);
1110
+ if (promptsEl && prompts.length > 0) {
1111
+ // Build columns: fixed cols + one col per unique dimension
1112
+ var dimNames = [];
1113
+ for (var pi = 0; pi < prompts.length; pi++) {
1114
+ var pDims = prompts[pi].dimensions || [];
1115
+ for (var di = 0; di < pDims.length; di++) {
1116
+ if (dimNames.indexOf(pDims[di].dimension) === -1) dimNames.push(pDims[di].dimension);
1117
+ }
1118
+ }
1119
+
1120
+ var promptRows = prompts.map(function(p) {
1121
+ var row = {
1122
+ name: p.name || '',
1123
+ version: p.version || '',
1124
+ model: p.model || '',
1125
+ costPerCall: p.costPerCall != null ? '$' + p.costPerCall.toFixed(4) : '',
1126
+ costPer1kTokens: p.costPer1kTokens != null ? '$' + p.costPer1kTokens.toFixed(4) : '',
1127
+ latencyP95: p.latencyP95ms != null ? p.latencyP95ms + 'ms' : ''
1128
+ };
1129
+ var pDims = p.dimensions || [];
1130
+ for (var di = 0; di < dimNames.length; di++) {
1131
+ var match = pDims.find(function(dd) { return dd.dimension === dimNames[di]; });
1132
+ if (match) {
1133
+ row['dim_' + di] = (match.actual != null ? match.actual : '') + ' ' + (match.verdict || '');
1134
+ row['dim_' + di + '_verdict'] = match.verdict || '';
1135
+ } else {
1136
+ row['dim_' + di] = '';
1137
+ row['dim_' + di + '_verdict'] = '';
1138
+ }
1139
+ }
1140
+ return row;
1141
+ });
1142
+
1143
+ var promptCols = [
1144
+ { title: 'Prompt', field: 'name', minWidth: 120 },
1145
+ { title: 'Version', field: 'version', width: 70 },
1146
+ { title: 'Model', field: 'model', minWidth: 100 },
1147
+ { title: 'Cost/Call', field: 'costPerCall', hozAlign: 'right', width: 90 },
1148
+ { title: 'Cost/1k Tokens', field: 'costPer1kTokens', hozAlign: 'right', width: 110 },
1149
+ { title: 'Latency P95', field: 'latencyP95', hozAlign: 'right', width: 90 }
1150
+ ];
1151
+ for (var di = 0; di < dimNames.length; di++) {
1152
+ (function(idx, name) {
1153
+ promptCols.push({
1154
+ title: name, field: 'dim_' + idx, hozAlign: 'center', minWidth: 90,
1155
+ formatter: function(cell) {
1156
+ var row = cell.getRow().getData();
1157
+ var v = (row['dim_' + idx + '_verdict'] || '').toLowerCase();
1158
+ var cls = v === 'pass' ? 'eval-cell-pass' : v === 'fail' ? 'eval-cell-fail' : '';
1159
+ return '<span class="' + cls + '">' + esc(cell.getValue()) + '</span>';
1160
+ }
1161
+ });
1162
+ })(di, dimNames[di]);
1163
+ }
1164
+
1165
+ panel._evalPromptTabulator = new Tabulator(promptsEl, {
1166
+ data: promptRows,
1167
+ layout: 'fitDataFill',
1168
+ height: '100%',
1169
+ columns: promptCols
1170
+ });
1171
+ }
1172
+ }
1173
+
1174
+ // ── ML Engineer: Baseline vs Candidate Plotly chart ──
1175
+ if (variant === 'ml-engineer' && typeof Plotly !== 'undefined') {
1176
+ var baseline = d.baseline;
1177
+ var bestCandidate = d.bestCandidate;
1178
+ var chartEl = document.getElementById('eval-chart-' + panel.panelId);
1179
+ if (baseline && bestCandidate && chartEl) {
1180
+ var isDark = document.documentElement.getAttribute('data-theme') !== 'light';
1181
+ var fontColor = isDark ? '#b0b0c8' : '#333';
1182
+ var gridColor = isDark ? '#1e1e32' : '#e0e0e0';
1183
+
1184
+ var bMetrics = baseline.metrics || {};
1185
+ var cMetrics = bestCandidate.metrics || {};
1186
+ var metricKeys = Object.keys(cMetrics);
1187
+
1188
+ Plotly.newPlot(chartEl, [
1189
+ {
1190
+ x: metricKeys, y: metricKeys.map(function(k) { return bMetrics[k] || 0; }),
1191
+ type: 'bar', name: baseline.model || 'Baseline',
1192
+ marker: { color: '#6a6a88' }
1193
+ },
1194
+ {
1195
+ x: metricKeys, y: metricKeys.map(function(k) { return cMetrics[k] || 0; }),
1196
+ type: 'bar', name: bestCandidate.model || 'Best Candidate',
1197
+ marker: { color: '#4a9' }
1198
+ }
1199
+ ], {
1200
+ barmode: 'group',
1201
+ title: { text: baseline.model + ' vs ' + bestCandidate.model, font: { size: 13, color: fontColor } },
1202
+ paper_bgcolor: 'transparent', plot_bgcolor: 'transparent',
1203
+ font: { color: fontColor }, margin: { t: 36, r: 16, b: 40, l: 50 },
1204
+ xaxis: { gridcolor: gridColor }, yaxis: { gridcolor: gridColor },
1205
+ legend: { orientation: 'h', y: -0.15 }
1206
+ }, { responsive: true, displayModeBar: false });
1207
+ }
1208
+ }
1209
+
1210
+ // ── ML Engineer: Infrastructure Tabulator ──
1211
+ if (variant === 'ml-engineer' && typeof Tabulator !== 'undefined') {
1212
+ var infra = d.infrastructure || [];
1213
+ var infraEl = document.getElementById('eval-infra-' + panel.panelId);
1214
+ if (infraEl && infra.length > 0) {
1215
+ panel._evalInfraTabulator = new Tabulator(infraEl, {
1216
+ data: infra,
1217
+ layout: 'fitDataFill',
1218
+ height: '100%',
1219
+ columns: [
1220
+ { title: 'Dimension', field: 'dimension', minWidth: 120 },
1221
+ { title: 'Actual', field: 'actual', hozAlign: 'right', width: 100 },
1222
+ { title: 'Budget', field: 'budget', hozAlign: 'right', width: 100 },
1223
+ { title: 'Verdict', field: 'verdict', width: 80, hozAlign: 'center',
1224
+ formatter: function(cell) {
1225
+ var v = (cell.getValue() || '').toLowerCase();
1226
+ return '<span class="eval-cell-' + (v === 'pass' ? 'pass' : 'fail') + '">' + esc(v.toUpperCase()) + '</span>';
1227
+ }
1228
+ }
1229
+ ]
1230
+ });
1231
+ }
1232
+ }
1233
+ }
1234
+
1235
+ // ═══════════════════════════════════════════════════════════════
1236
+ // Model Card panel
1237
+ // ═══════════════════════════════════════════════════════════════
1238
+
1239
+ function cleanupModelCard(panel) {
1240
+ if (panel._mcMetricsTabulator) {
1241
+ try { panel._mcMetricsTabulator.destroy(); } catch(e) {}
1242
+ panel._mcMetricsTabulator = null;
1243
+ }
1244
+ if (panel._mcQuantTabulator) {
1245
+ try { panel._mcQuantTabulator.destroy(); } catch(e) {}
1246
+ panel._mcQuantTabulator = null;
1247
+ }
1248
+ }
1249
+
1250
+ function renderModelCard(container, panel) {
1251
+ var d = panel.rawData;
1252
+ if (!d) {
1253
+ container.innerHTML = '<div class="no-file-msg">No model card data provided.</div>';
1254
+ return;
1255
+ }
1256
+
1257
+ var md = d.modelDetails || {};
1258
+ var iu = d.intendedUse || {};
1259
+ var fac = d.factors || {};
1260
+ var met = d.metrics || {};
1261
+ var evData = d.evaluationData || {};
1262
+ var trData = d.trainingData || {};
1263
+ var qa = d.quantitativeAnalyses || {};
1264
+ var eth = d.ethicalConsiderations || {};
1265
+ var cav = d.caveatsAndRecommendations || {};
1266
+ var evalSum = d.evalSummary || {};
1267
+
1268
+ var html = '<div class="model-card-panel">';
1269
+
1270
+ // ── Header ──
1271
+ html += '<div class="model-card-header">';
1272
+ html += '<h2 class="model-card-title">' + esc(md.name || d.projectName || 'Untitled Model') + '</h2>';
1273
+ html += '<div class="model-card-meta">';
1274
+ if (md.version) html += '<span class="model-card-version-badge">' + esc(md.version) + '</span>';
1275
+ if (md.type) html += '<span class="model-card-meta-item"><strong>Type:</strong> ' + esc(md.type) + '</span>';
1276
+ if (md.date) html += '<span class="model-card-meta-item"><strong>Date:</strong> ' + esc(md.date) + '</span>';
1277
+ if (md.owner) html += '<span class="model-card-meta-item"><strong>Owner:</strong> ' + esc(md.owner) + '</span>';
1278
+ if (md.framework) html += '<span class="model-card-meta-item"><strong>Framework:</strong> ' + esc(md.framework) + '</span>';
1279
+ html += '</div></div>';
1280
+
1281
+ // ── Sections ──
1282
+
1283
+ // 1. Model Details
1284
+ html += mcSection('Model Details', false, function() {
1285
+ var body = '';
1286
+ if (md.license) body += '<p><strong>License:</strong> ' + esc(md.license) + '</p>';
1287
+ var refs = md.references || [];
1288
+ if (refs.length > 0) {
1289
+ body += '<p><strong>References:</strong></p><ul>';
1290
+ for (var i = 0; i < refs.length; i++) body += '<li>' + esc(refs[i]) + '</li>';
1291
+ body += '</ul>';
1292
+ }
1293
+ return body || '<p>No additional details.</p>';
1294
+ });
1295
+
1296
+ // 2. Intended Use
1297
+ html += mcSection('Intended Use', false, function() {
1298
+ var body = '';
1299
+ if (iu.primaryUse) body += '<p><strong>Primary use:</strong> ' + esc(iu.primaryUse) + '</p>';
1300
+ if (iu.primaryUsers) body += '<p><strong>Primary users:</strong> ' + esc(iu.primaryUsers) + '</p>';
1301
+ var oos = iu.outOfScopeUses || [];
1302
+ if (oos.length > 0) {
1303
+ body += '<p><strong>Out-of-scope uses:</strong></p><ul>';
1304
+ for (var i = 0; i < oos.length; i++) body += '<li>' + esc(oos[i]) + '</li>';
1305
+ body += '</ul>';
1306
+ }
1307
+ return body || '<p>Not specified.</p>';
1308
+ });
1309
+
1310
+ // 3. Factors
1311
+ html += mcSection('Factors', false, function() {
1312
+ var body = '';
1313
+ var rf = fac.relevantFactors || [];
1314
+ var ef = fac.evaluationFactors || [];
1315
+ if (rf.length > 0) {
1316
+ body += '<p><strong>Relevant factors:</strong></p><ul>';
1317
+ for (var i = 0; i < rf.length; i++) body += '<li>' + esc(rf[i]) + '</li>';
1318
+ body += '</ul>';
1319
+ }
1320
+ if (ef.length > 0) {
1321
+ body += '<p><strong>Evaluation factors:</strong></p><ul>';
1322
+ for (var i = 0; i < ef.length; i++) body += '<li>' + esc(ef[i]) + '</li>';
1323
+ body += '</ul>';
1324
+ }
1325
+ return body || '<p>Not specified.</p>';
1326
+ });
1327
+
1328
+ // 4. Metrics (rendered as inline table + optional Tabulator)
1329
+ html += mcSection('Metrics', false, function() {
1330
+ var body = '';
1331
+ var pm = met.performanceMeasures || [];
1332
+ if (pm.length > 0) {
1333
+ body += '<table class="model-card-table"><tr><th>Measure</th><th>Value</th><th>Description</th><th>Rationale</th></tr>';
1334
+ for (var i = 0; i < pm.length; i++) {
1335
+ body += '<tr><td>' + esc(pm[i].name || '') + '</td><td>' + esc(String(pm[i].value || '')) + '</td>';
1336
+ body += '<td>' + esc(pm[i].description || '') + '</td><td>' + esc(pm[i].rationale || '') + '</td></tr>';
1337
+ }
1338
+ body += '</table>';
1339
+ }
1340
+ var dt = met.decisionThresholds || [];
1341
+ if (dt.length > 0) {
1342
+ body += '<p><strong>Decision Thresholds:</strong></p>';
1343
+ body += '<table class="model-card-table"><tr><th>Threshold</th><th>Value</th><th>Rationale</th></tr>';
1344
+ for (var i = 0; i < dt.length; i++) {
1345
+ body += '<tr><td>' + esc(dt[i].name || '') + '</td><td>' + esc(String(dt[i].threshold || '')) + '</td>';
1346
+ body += '<td>' + esc(dt[i].rationale || '') + '</td></tr>';
1347
+ }
1348
+ body += '</table>';
1349
+ }
1350
+ return body || '<p>No metrics defined.</p>';
1351
+ });
1352
+
1353
+ // 5. Evaluation Data
1354
+ html += mcSection('Evaluation Data', false, function() {
1355
+ return mcDataSection(evData);
1356
+ });
1357
+
1358
+ // 6. Training Data
1359
+ html += mcSection('Training Data', false, function() {
1360
+ return mcDataSection(trData);
1361
+ });
1362
+
1363
+ // 7. Quantitative Analyses
1364
+ html += mcSection('Quantitative Analyses', false, function() {
1365
+ var body = '';
1366
+ var ur = qa.unitaryResults || [];
1367
+ if (ur.length > 0) {
1368
+ body += '<p><strong>Unitary Results:</strong></p>';
1369
+ body += '<table class="model-card-table"><tr><th>Metric</th><th>Value</th><th>Subset</th></tr>';
1370
+ for (var i = 0; i < ur.length; i++) {
1371
+ body += '<tr><td>' + esc(ur[i].metric || '') + '</td><td>' + esc(String(ur[i].value || '')) + '</td>';
1372
+ body += '<td>' + esc(ur[i].subset || '') + '</td></tr>';
1373
+ }
1374
+ body += '</table>';
1375
+ }
1376
+ var ir = qa.intersectionalResults || [];
1377
+ if (ir.length > 0) {
1378
+ body += '<p><strong>Intersectional Results:</strong></p>';
1379
+ body += '<table class="model-card-table"><tr><th>Metric</th><th>Value</th><th>Factors</th></tr>';
1380
+ for (var i = 0; i < ir.length; i++) {
1381
+ body += '<tr><td>' + esc(ir[i].metric || '') + '</td><td>' + esc(String(ir[i].value || '')) + '</td>';
1382
+ body += '<td>' + esc((ir[i].factors || []).join(', ')) + '</td></tr>';
1383
+ }
1384
+ body += '</table>';
1385
+ }
1386
+ return body || '<p>No quantitative analyses available.</p>';
1387
+ });
1388
+
1389
+ // 8. Ethical Considerations (highlighted)
1390
+ html += mcSection('Ethical Considerations', true, function() {
1391
+ var body = '';
1392
+ var risks = eth.risks || [];
1393
+ if (risks.length > 0) {
1394
+ body += '<p><strong>Risks:</strong></p><ul>';
1395
+ for (var i = 0; i < risks.length; i++) body += '<li>' + esc(risks[i]) + '</li>';
1396
+ body += '</ul>';
1397
+ }
1398
+ var mits = eth.mitigations || [];
1399
+ if (mits.length > 0) {
1400
+ body += '<p><strong>Mitigations:</strong></p><ul>';
1401
+ for (var i = 0; i < mits.length; i++) body += '<li>' + esc(mits[i]) + '</li>';
1402
+ body += '</ul>';
1403
+ }
1404
+ if (eth.academicReview) {
1405
+ body += '<p><strong>Academic Review:</strong></p><p>' + esc(eth.academicReview) + '</p>';
1406
+ }
1407
+ return body || '<p>No ethical considerations documented.</p>';
1408
+ });
1409
+
1410
+ // 9. Caveats and Recommendations
1411
+ html += mcSection('Caveats and Recommendations', false, function() {
1412
+ var body = '';
1413
+ var cavs = cav.caveats || [];
1414
+ if (cavs.length > 0) {
1415
+ body += '<p><strong>Caveats:</strong></p><ul>';
1416
+ for (var i = 0; i < cavs.length; i++) body += '<li>' + esc(cavs[i]) + '</li>';
1417
+ body += '</ul>';
1418
+ }
1419
+ var recs = cav.recommendations || [];
1420
+ if (recs.length > 0) {
1421
+ body += '<p><strong>Recommendations:</strong></p><ul>';
1422
+ for (var i = 0; i < recs.length; i++) body += '<li>' + esc(recs[i]) + '</li>';
1423
+ body += '</ul>';
1424
+ }
1425
+ return body || '<p>No caveats or recommendations.</p>';
1426
+ });
1427
+
1428
+ // ── Eval Summary footer ──
1429
+ var evalDims = evalSum.dimensions || [];
1430
+ if (evalSum.overallVerdict || evalDims.length > 0) {
1431
+ html += '<div class="model-card-eval-footer">';
1432
+ html += '<p class="eval-section-label">Evaluation Summary</p>';
1433
+ if (evalSum.overallVerdict) {
1434
+ var v = evalSum.overallVerdict.toLowerCase();
1435
+ html += '<span class="eval-verdict-badge ' + v + '">' + esc(evalSum.overallVerdict) + '</span>';
1436
+ }
1437
+ if (evalDims.length > 0) {
1438
+ html += '<div class="model-card-eval-dims">';
1439
+ for (var i = 0; i < evalDims.length; i++) {
1440
+ var ev = evalDims[i];
1441
+ var cls = (ev.verdict || '').toLowerCase() === 'pass' ? 'pass' : 'fail';
1442
+ html += '<span class="model-card-eval-dim ' + cls + '">' + esc(ev.dimension) + ': ' + esc(ev.verdict || '') + '</span>';
1443
+ }
1444
+ html += '</div>';
1445
+ }
1446
+ html += '</div>';
1447
+ }
1448
+
1449
+ // ── Export button ──
1450
+ html += '<button class="model-card-export-btn" data-panel-id="' + esc(panel.panelId) + '">Export as Markdown</button>';
1451
+
1452
+ html += '</div>';
1453
+ container.innerHTML = html;
1454
+
1455
+ container.querySelectorAll('.model-card-export-btn').forEach(function(btn) {
1456
+ btn.addEventListener('click', function() { mcExportMarkdown(btn.dataset.panelId); });
1457
+ });
1458
+ }
1459
+
1460
+ // Model card section builder
1461
+ function mcSection(title, isEthical, contentFn) {
1462
+ var cls = 'model-card-section' + (isEthical ? ' ethical' : '');
1463
+ return '<details class="' + cls + '" open>' +
1464
+ '<summary>' + esc(title) + '</summary>' +
1465
+ '<div class="model-card-section-body">' + contentFn() + '</div>' +
1466
+ '</details>';
1467
+ }
1468
+
1469
+ // Dataset info helper
1470
+ function mcDataSection(data) {
1471
+ var body = '';
1472
+ var ds = data.datasets;
1473
+ if (ds) {
1474
+ var arr = Array.isArray(ds) ? ds : [ds];
1475
+ body += '<p><strong>Datasets:</strong> ' + arr.map(function(d) { return esc(d); }).join(', ') + '</p>';
1476
+ }
1477
+ if (data.size) body += '<p><strong>Size:</strong> ' + esc(String(data.size)) + '</p>';
1478
+ if (data.preprocessing) body += '<p><strong>Preprocessing:</strong> ' + esc(data.preprocessing) + '</p>';
1479
+ if (data.motivation) body += '<p><strong>Motivation:</strong> ' + esc(data.motivation) + '</p>';
1480
+ return body || '<p>Not specified.</p>';
1481
+ }
1482
+
1483
+ // Export model card as markdown
1484
+ function mcExportMarkdown(panelId) {
1485
+ var p = openPanels[panelId];
1486
+ if (!p || !p.rawData) return;
1487
+ var md = modelCardToMarkdown(p.rawData);
1488
+ var blob = new Blob([md], { type: 'text/markdown' });
1489
+ var url = URL.createObjectURL(blob);
1490
+ var a = document.createElement('a');
1491
+ a.href = url;
1492
+ a.download = (p.rawData.projectName || 'model-card') + '-model-card.md';
1493
+ document.body.appendChild(a);
1494
+ a.click();
1495
+ document.body.removeChild(a);
1496
+ URL.revokeObjectURL(url);
1497
+ }
1498
+
1499
+ function modelCardToMarkdown(d) {
1500
+ var md = d.modelDetails || {};
1501
+ var iu = d.intendedUse || {};
1502
+ var fac = d.factors || {};
1503
+ var met = d.metrics || {};
1504
+ var evData = d.evaluationData || {};
1505
+ var trData = d.trainingData || {};
1506
+ var qa = d.quantitativeAnalyses || {};
1507
+ var eth = d.ethicalConsiderations || {};
1508
+ var cav = d.caveatsAndRecommendations || {};
1509
+ var evalSum = d.evalSummary || {};
1510
+ var cost = evalSum.cost || {};
1511
+
1512
+ var lines = [];
1513
+ lines.push('# Model Card: ' + (md.name || d.projectName || 'Untitled'));
1514
+ lines.push('');
1515
+ lines.push('- **Version:** ' + (md.version || 'N/A'));
1516
+ lines.push('- **Type:** ' + (md.type || 'N/A'));
1517
+ lines.push('- **Owner:** ' + (md.owner || 'N/A'));
1518
+ lines.push('- **Date:** ' + (md.date || 'N/A'));
1519
+ lines.push('- **Framework:** ' + (md.framework || 'N/A'));
1520
+ lines.push('');
1521
+ lines.push('---');
1522
+ lines.push('');
1523
+
1524
+ // Model Details
1525
+ lines.push('## Model Details');
1526
+ lines.push('');
1527
+ if (md.license) lines.push('**License:** ' + md.license);
1528
+ var refs = md.references || [];
1529
+ if (refs.length > 0) {
1530
+ lines.push('');
1531
+ lines.push('**References:**');
1532
+ for (var i = 0; i < refs.length; i++) lines.push('- ' + refs[i]);
1533
+ }
1534
+ lines.push('');
1535
+ lines.push('---');
1536
+ lines.push('');
1537
+
1538
+ // Intended Use
1539
+ lines.push('## Intended Use');
1540
+ lines.push('');
1541
+ if (iu.primaryUse) lines.push('- **Primary use:** ' + iu.primaryUse);
1542
+ if (iu.primaryUsers) lines.push('- **Primary users:** ' + iu.primaryUsers);
1543
+ var oos = iu.outOfScopeUses || [];
1544
+ if (oos.length > 0) {
1545
+ lines.push('- **Out-of-scope uses:**');
1546
+ for (var i = 0; i < oos.length; i++) lines.push(' - ' + oos[i]);
1547
+ }
1548
+ lines.push('');
1549
+ lines.push('---');
1550
+ lines.push('');
1551
+
1552
+ // Factors
1553
+ lines.push('## Factors');
1554
+ lines.push('');
1555
+ var rf = fac.relevantFactors || [];
1556
+ if (rf.length > 0) {
1557
+ lines.push('- **Relevant factors:** ' + rf.join(', '));
1558
+ }
1559
+ var ef = fac.evaluationFactors || [];
1560
+ if (ef.length > 0) {
1561
+ lines.push('- **Evaluation factors:** ' + ef.join(', '));
1562
+ }
1563
+ lines.push('');
1564
+ lines.push('---');
1565
+ lines.push('');
1566
+
1567
+ // Metrics
1568
+ lines.push('## Metrics');
1569
+ lines.push('');
1570
+ var pm = met.performanceMeasures || [];
1571
+ if (pm.length > 0) {
1572
+ lines.push('| Measure | Value | Description | Rationale |');
1573
+ lines.push('|---------|-------|-------------|-----------|');
1574
+ for (var i = 0; i < pm.length; i++) {
1575
+ lines.push('| ' + (pm[i].name || '') + ' | ' + (pm[i].value || '') + ' | ' + (pm[i].description || '') + ' | ' + (pm[i].rationale || '') + ' |');
1576
+ }
1577
+ }
1578
+ var dt = met.decisionThresholds || [];
1579
+ if (dt.length > 0) {
1580
+ lines.push('');
1581
+ lines.push('### Decision Thresholds');
1582
+ lines.push('');
1583
+ lines.push('| Threshold | Value | Rationale |');
1584
+ lines.push('|-----------|-------|-----------|');
1585
+ for (var i = 0; i < dt.length; i++) {
1586
+ lines.push('| ' + (dt[i].name || '') + ' | ' + (dt[i].threshold || '') + ' | ' + (dt[i].rationale || '') + ' |');
1587
+ }
1588
+ }
1589
+ lines.push('');
1590
+ lines.push('---');
1591
+ lines.push('');
1592
+
1593
+ // Evaluation Data
1594
+ lines.push('## Evaluation Data');
1595
+ lines.push('');
1596
+ lines.push(mdDataSection(evData));
1597
+ lines.push('---');
1598
+ lines.push('');
1599
+
1600
+ // Training Data
1601
+ lines.push('## Training Data');
1602
+ lines.push('');
1603
+ lines.push(mdDataSection(trData));
1604
+ lines.push('---');
1605
+ lines.push('');
1606
+
1607
+ // Quantitative Analyses
1608
+ lines.push('## Quantitative Analyses');
1609
+ lines.push('');
1610
+ var ur = qa.unitaryResults || [];
1611
+ if (ur.length > 0) {
1612
+ lines.push('### Unitary Results');
1613
+ lines.push('');
1614
+ lines.push('| Metric | Value | Subset |');
1615
+ lines.push('|--------|-------|--------|');
1616
+ for (var i = 0; i < ur.length; i++) {
1617
+ lines.push('| ' + (ur[i].metric || '') + ' | ' + (ur[i].value || '') + ' | ' + (ur[i].subset || '') + ' |');
1618
+ }
1619
+ lines.push('');
1620
+ }
1621
+ var ir = qa.intersectionalResults || [];
1622
+ if (ir.length > 0) {
1623
+ lines.push('### Intersectional Results');
1624
+ lines.push('');
1625
+ lines.push('| Metric | Value | Factors |');
1626
+ lines.push('|--------|-------|---------|');
1627
+ for (var i = 0; i < ir.length; i++) {
1628
+ lines.push('| ' + (ir[i].metric || '') + ' | ' + (ir[i].value || '') + ' | ' + (ir[i].factors || []).join(', ') + ' |');
1629
+ }
1630
+ lines.push('');
1631
+ }
1632
+ lines.push('---');
1633
+ lines.push('');
1634
+
1635
+ // Ethical Considerations
1636
+ lines.push('## Ethical Considerations');
1637
+ lines.push('');
1638
+ var risks = eth.risks || [];
1639
+ if (risks.length > 0) {
1640
+ lines.push('**Risks:**');
1641
+ for (var i = 0; i < risks.length; i++) lines.push('- ' + risks[i]);
1642
+ lines.push('');
1643
+ }
1644
+ var mits = eth.mitigations || [];
1645
+ if (mits.length > 0) {
1646
+ lines.push('**Mitigations:**');
1647
+ for (var i = 0; i < mits.length; i++) lines.push('- ' + mits[i]);
1648
+ lines.push('');
1649
+ }
1650
+ if (eth.academicReview) {
1651
+ lines.push('**Academic Review:**');
1652
+ lines.push('');
1653
+ lines.push(eth.academicReview);
1654
+ lines.push('');
1655
+ }
1656
+ lines.push('---');
1657
+ lines.push('');
1658
+
1659
+ // Caveats and Recommendations
1660
+ lines.push('## Caveats and Recommendations');
1661
+ lines.push('');
1662
+ var cavs = cav.caveats || [];
1663
+ if (cavs.length > 0) {
1664
+ lines.push('**Caveats:**');
1665
+ for (var i = 0; i < cavs.length; i++) lines.push('- ' + cavs[i]);
1666
+ lines.push('');
1667
+ }
1668
+ var recs = cav.recommendations || [];
1669
+ if (recs.length > 0) {
1670
+ lines.push('**Recommendations:**');
1671
+ for (var i = 0; i < recs.length; i++) lines.push('- ' + recs[i]);
1672
+ lines.push('');
1673
+ }
1674
+ lines.push('---');
1675
+ lines.push('');
1676
+
1677
+ // Eval Summary
1678
+ var evalDims = evalSum.dimensions || [];
1679
+ if (evalSum.overallVerdict || evalDims.length > 0) {
1680
+ lines.push('## Evaluation Summary');
1681
+ lines.push('');
1682
+ if (evalSum.overallVerdict) lines.push('- **Overall verdict:** ' + evalSum.overallVerdict);
1683
+ lines.push('');
1684
+ if (evalDims.length > 0) {
1685
+ lines.push('| Dimension | Metric | Target | Actual | Verdict |');
1686
+ lines.push('|-----------|--------|--------|--------|---------|');
1687
+ for (var i = 0; i < evalDims.length; i++) {
1688
+ var ev = evalDims[i];
1689
+ lines.push('| ' + (ev.dimension || '') + ' | ' + (ev.metric || '') + ' | ' + (ev.target || '') + ' | ' + (ev.actual || '') + ' | ' + (ev.verdict || '') + ' |');
1690
+ }
1691
+ lines.push('');
1692
+ }
1693
+ if (cost.perRequest != null || cost.per1kTokens != null || cost.monthlyProjected != null) {
1694
+ lines.push('### Cost Profile');
1695
+ lines.push('');
1696
+ if (cost.perRequest != null) lines.push('- **Per request:** $' + cost.perRequest.toFixed(4));
1697
+ if (cost.per1kTokens != null) lines.push('- **Per 1k tokens:** $' + cost.per1kTokens.toFixed(4));
1698
+ if (cost.monthlyProjected != null) lines.push('- **Monthly projection:** $' + cost.monthlyProjected);
1699
+ if (cost.budget != null) lines.push('- **Budget:** $' + cost.budget);
1700
+ lines.push('');
1701
+ }
1702
+ }
1703
+
1704
+ return lines.join('\n');
1705
+ }
1706
+
1707
+ function mdDataSection(data) {
1708
+ var lines = [];
1709
+ var ds = data.datasets;
1710
+ if (ds) {
1711
+ var arr = Array.isArray(ds) ? ds : [ds];
1712
+ lines.push('- **Datasets:** ' + arr.join(', '));
1713
+ }
1714
+ if (data.size) lines.push('- **Size:** ' + data.size);
1715
+ if (data.preprocessing) lines.push('- **Preprocessing:** ' + data.preprocessing);
1716
+ if (data.motivation) lines.push('- **Motivation:** ' + data.motivation);
1717
+ lines.push('');
1718
+ return lines.join('\n');
1719
+ }
1720
+
1721
+ // ═══════════════════════════════════════════════════════════════
1722
+ // Prompt Laboratory panel
1723
+ // ═══════════════════════════════════════════════════════════════
1724
+
1725
+ function cleanupPromptLab(panel) {
1726
+ if (panel._plEditor) {
1727
+ try { panel._plEditor.dispose(); } catch(e) {}
1728
+ panel._plEditor = null;
1729
+ }
1730
+ if (panel._plDiffEditor) {
1731
+ try {
1732
+ var model = panel._plDiffEditor.getModel();
1733
+ if (model) {
1734
+ if (model.original) model.original.dispose();
1735
+ if (model.modified) model.modified.dispose();
1736
+ }
1737
+ panel._plDiffEditor.dispose();
1738
+ } catch(e) {}
1739
+ panel._plDiffEditor = null;
1740
+ }
1741
+ if (panel._plOriginalModel) {
1742
+ try { panel._plOriginalModel.dispose(); } catch(e) {}
1743
+ panel._plOriginalModel = null;
1744
+ }
1745
+ if (panel._plModifiedModel) {
1746
+ try { panel._plModifiedModel.dispose(); } catch(e) {}
1747
+ panel._plModifiedModel = null;
1748
+ }
1749
+ }
1750
+
1751
+ function renderPromptLab(container, panel) {
1752
+ var d = panel.rawData;
1753
+ if (!d) {
1754
+ container.innerHTML = '<div class="pl-empty">No prompt lab data. The AI Engineer will push this panel when a project has prompts.</div>';
1755
+ return;
1756
+ }
1757
+
1758
+ // Preserve editor state across re-renders
1759
+ panel._plState = panel._plState || { diffMode: false, resultsCollapsed: false };
1760
+ var state = panel._plState;
1761
+
1762
+ var prompts = d.prompts || [];
1763
+ var active = d.activePrompt || (prompts.length > 0 ? prompts[0].name : '');
1764
+ var status = d.status || 'idle';
1765
+ var testRuns = d.testRuns || [];
1766
+ var syncHistory = d.syncHistory || [];
1767
+ var pid = panel.panelId;
1768
+
1769
+ var html = '<div class="prompt-lab">';
1770
+
1771
+ // ── Toolbar ──
1772
+ html += '<div class="pl-toolbar">';
1773
+ html += '<select id="pl-select-' + esc(pid) + '" class="pl-select-prompt" data-panel-id="' + esc(pid) + '">';
1774
+ for (var i = 0; i < prompts.length; i++) {
1775
+ var sel = prompts[i].name === active ? ' selected' : '';
1776
+ html += '<option value="' + esc(prompts[i].name) + '"' + sel + '>' + esc(prompts[i].filename) + ' (v' + esc(prompts[i].currentVersion) + ')</option>';
1777
+ }
1778
+ if (prompts.length === 0) html += '<option value="">No prompts found</option>';
1779
+ html += '</select>';
1780
+
1781
+ html += '<span class="pl-status-badge ' + esc(status) + '">' + esc(status) + '</span>';
1782
+ html += '<span class="pl-toolbar-spacer"></span>';
1783
+
1784
+ html += '<button class="pl-btn pl-action-diff' + (state.diffMode ? ' active' : '') + '" data-panel-id="' + esc(pid) + '" title="Toggle diff view">Diff</button>';
1785
+ html += '<button class="pl-btn primary pl-action-run" data-panel-id="' + esc(pid) + '"' + (status !== 'idle' ? ' disabled' : '') + '>Run Test</button>';
1786
+ html += '<button class="pl-btn sync pl-action-sync" data-panel-id="' + esc(pid) + '"' + (status !== 'idle' ? ' disabled' : '') + '>Sync to Project</button>';
1787
+ html += '</div>';
1788
+
1789
+ // ── Body: editor + sidebar ──
1790
+ html += '<div class="pl-body">';
1791
+
1792
+ // Editor column
1793
+ html += '<div class="pl-editor-col">';
1794
+ html += '<div class="pl-editor-container" id="pl-editor-' + esc(pid) + '"></div>';
1795
+ html += '</div>';
1796
+
1797
+ // Version sidebar
1798
+ html += '<div class="pl-sidebar" id="pl-sidebar-' + esc(pid) + '">';
1799
+ html += '</div>';
1800
+
1801
+ html += '</div>'; // end pl-body
1802
+
1803
+ // ── Results area ──
1804
+ html += '<div class="pl-results" id="pl-results-' + esc(pid) + '" style="' + (state.resultsCollapsed ? 'display:none' : '') + '">';
1805
+ html += '</div>';
1806
+
1807
+ html += '</div>'; // end prompt-lab
1808
+ container.innerHTML = html;
1809
+
1810
+ container.querySelectorAll('.pl-select-prompt').forEach(function(el) {
1811
+ el.addEventListener('change', function() { plSelectPrompt(el.dataset.panelId); });
1812
+ });
1813
+ container.querySelectorAll('.pl-action-diff').forEach(function(btn) {
1814
+ btn.addEventListener('click', function() { plToggleDiff(btn.dataset.panelId); });
1815
+ });
1816
+ container.querySelectorAll('.pl-action-run').forEach(function(btn) {
1817
+ btn.addEventListener('click', function() { plRunTest(btn.dataset.panelId); });
1818
+ });
1819
+ container.querySelectorAll('.pl-action-sync').forEach(function(btn) {
1820
+ btn.addEventListener('click', function() { plSyncToProject(btn.dataset.panelId); });
1821
+ });
1822
+
1823
+ // Render sidebar
1824
+ plRenderVersionSidebar(
1825
+ document.getElementById('pl-sidebar-' + pid),
1826
+ prompts, active, syncHistory, pid
1827
+ );
1828
+
1829
+ // Render results
1830
+ plRenderResults(document.getElementById('pl-results-' + pid), testRuns);
1831
+
1832
+ // Initialize Monaco editor
1833
+ var editorContainer = document.getElementById('pl-editor-' + pid);
1834
+ if (!editorContainer) return;
1835
+
1836
+ // Determine content to show
1837
+ var content = d.editedContent || d.originalContent || '';
1838
+ var originalContent = d.originalContent || '';
1839
+ panel._plOriginalContent = originalContent;
1840
+ panel._plEditedContent = content;
1841
+
1842
+ if (state.diffMode) {
1843
+ plInitDiffEditor(panel, editorContainer, originalContent, content);
1844
+ } else {
1845
+ plInitEditor(panel, editorContainer, content);
1846
+ }
1847
+ }
1848
+
1849
+ function plInitEditor(panel, container, content) {
1850
+ loadMonaco().then(function() {
1851
+ if (panel._plEditor || panel._plDiffEditor) return; // already initialized
1852
+ var editor = createMonacoEditor(container, {
1853
+ value: content,
1854
+ language: 'markdown',
1855
+ wordWrap: 'on',
1856
+ readOnly: false,
1857
+ });
1858
+ panel._plEditor = editor;
1859
+
1860
+ // Track edits
1861
+ editor.onDidChangeModelContent(function() {
1862
+ panel._plEditedContent = editor.getValue();
1863
+ });
1864
+
1865
+ // Keyboard shortcuts
1866
+ editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, function() {
1867
+ plRunTest(panel.panelId);
1868
+ });
1869
+ editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, function() {
1870
+ // Save draft — prevent default, content is auto-tracked
1871
+ });
1872
+ });
1873
+ }
1874
+
1875
+ function plInitDiffEditor(panel, container, original, modified) {
1876
+ loadMonaco().then(function() {
1877
+ if (panel._plEditor || panel._plDiffEditor) return;
1878
+ var originalModel = monaco.editor.createModel(original, 'markdown');
1879
+ var modifiedModel = monaco.editor.createModel(modified, 'markdown');
1880
+ panel._plOriginalModel = originalModel;
1881
+ panel._plModifiedModel = modifiedModel;
1882
+
1883
+ var diffEditor = monaco.editor.createDiffEditor(container, {
1884
+ theme: currentMonacoTheme(),
1885
+ automaticLayout: true,
1886
+ readOnly: false,
1887
+ renderSideBySide: true,
1888
+ minimap: { enabled: false },
1889
+ scrollBeyondLastLine: false,
1890
+ fontSize: 13,
1891
+ fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace",
1892
+ lineHeight: 20,
1893
+ scrollbar: { verticalScrollbarSize: 5, horizontalScrollbarSize: 5 },
1894
+ overviewRulerLanes: 0,
1895
+ padding: { top: 8, bottom: 8 },
1896
+ });
1897
+ diffEditor.setModel({ original: originalModel, modified: modifiedModel });
1898
+ panel._plDiffEditor = diffEditor;
1899
+
1900
+ // Track edits on modified side
1901
+ modifiedModel.onDidChangeContent(function() {
1902
+ panel._plEditedContent = modifiedModel.getValue();
1903
+ });
1904
+ });
1905
+ }
1906
+
1907
+ function plSelectPrompt(panelId) {
1908
+ var p = openPanels[panelId];
1909
+ if (!p || !p.rawData) return;
1910
+ var sel = document.getElementById('pl-select-' + panelId);
1911
+ if (!sel) return;
1912
+ var promptName = sel.value;
1913
+ plLoadPrompt(panelId, promptName);
1914
+ }
1915
+
1916
+ function plLoadPrompt(panelId, promptName) {
1917
+ var p = openPanels[panelId];
1918
+ if (!p || !p.rawData) return;
1919
+ var d = p.rawData;
1920
+
1921
+ // Find the prompt in data
1922
+ var prompt = null;
1923
+ for (var i = 0; i < (d.prompts || []).length; i++) {
1924
+ if (d.prompts[i].name === promptName) { prompt = d.prompts[i]; break; }
1925
+ }
1926
+ if (!prompt) return;
1927
+
1928
+ // Build the file path
1929
+ var promptsDir = d.promptsDir || 'prompts';
1930
+ var filePath = (d.projectDir ? d.projectDir + '/' : '') + promptsDir + '/' + prompt.filename;
1931
+
1932
+ authFetch('/browse/file/text?path=' + encodeURIComponent(filePath))
1933
+ .then(function(res) { return res.ok ? res.text() : Promise.reject('File not found'); })
1934
+ .then(function(content) {
1935
+ p._plOriginalContent = content;
1936
+ p._plEditedContent = content;
1937
+ d.activePrompt = promptName;
1938
+ d.originalContent = content;
1939
+ d.editedContent = content;
1940
+
1941
+ // Update editor
1942
+ if (p._plEditor) {
1943
+ p._plEditor.setValue(content);
1944
+ } else if (p._plDiffEditor) {
1945
+ // Re-render in diff mode with new content
1946
+ cleanupPromptLab(p);
1947
+ var container = document.getElementById('pl-editor-' + panelId);
1948
+ if (container) {
1949
+ container.innerHTML = '';
1950
+ plInitDiffEditor(p, container, content, content);
1951
+ }
1952
+ }
1953
+
1954
+ // Re-render sidebar to update active state
1955
+ var sidebar = document.getElementById('pl-sidebar-' + panelId);
1956
+ if (sidebar) {
1957
+ plRenderVersionSidebar(sidebar, d.prompts || [], promptName, d.syncHistory || [], panelId);
1958
+ }
1959
+ })
1960
+ .catch(function(err) {
1961
+ console.warn('plLoadPrompt failed:', err);
1962
+ });
1963
+ }
1964
+
1965
+ function plToggleDiff(panelId) {
1966
+ var p = openPanels[panelId];
1967
+ if (!p) return;
1968
+ p._plState = p._plState || { diffMode: false, resultsCollapsed: false };
1969
+ p._plState.diffMode = !p._plState.diffMode;
1970
+
1971
+ // Capture current content before cleanup
1972
+ var editedContent = p._plEditedContent || '';
1973
+ var originalContent = p._plOriginalContent || '';
1974
+ cleanupPromptLab(p);
1975
+
1976
+ var container = document.getElementById('pl-editor-' + panelId);
1977
+ if (!container) return;
1978
+ container.innerHTML = '';
1979
+
1980
+ if (p._plState.diffMode) {
1981
+ plInitDiffEditor(p, container, originalContent, editedContent);
1982
+ } else {
1983
+ plInitEditor(p, container, editedContent);
1984
+ }
1985
+
1986
+ // Update diff button active state
1987
+ var toolbar = container.closest('.prompt-lab');
1988
+ if (toolbar) {
1989
+ var btns = toolbar.querySelectorAll('.pl-btn');
1990
+ for (var i = 0; i < btns.length; i++) {
1991
+ if (btns[i].textContent === 'Diff') {
1992
+ btns[i].classList.toggle('active', p._plState.diffMode);
1993
+ break;
1994
+ }
1995
+ }
1996
+ }
1997
+ }
1998
+
1999
+ function plRunTest(panelId) {
2000
+ var p = openPanels[panelId];
2001
+ if (!p || !p.rawData) return;
2002
+ var d = p.rawData;
2003
+ if (d.status !== 'idle') return;
2004
+
2005
+ var promptName = d.activePrompt || '';
2006
+ var content = p._plEditedContent || '';
2007
+ if (!promptName || !content) return;
2008
+
2009
+ var message = '[PROMPT-LAB] Run evaluation for prompt "' + promptName + '" with content:\n---\n' + content + '\n---';
2010
+
2011
+ // Find active session
2012
+ var sessionId = activeSessionId;
2013
+
2014
+ authFetch('/chat/send', {
2015
+ method: 'POST',
2016
+ headers: { 'Content-Type': 'application/json' },
2017
+ body: JSON.stringify({ message: message, sessionId: sessionId })
2018
+ }).then(function(res) {
2019
+ if (!res.ok) console.warn('plRunTest: chat/send failed');
2020
+ }).catch(function(err) {
2021
+ console.warn('plRunTest error:', err);
2022
+ });
2023
+
2024
+ // Optimistic status update
2025
+ var badge = document.querySelector('#pl-select-' + panelId);
2026
+ if (badge) {
2027
+ var statusBadge = badge.parentNode.querySelector('.pl-status-badge');
2028
+ if (statusBadge) {
2029
+ statusBadge.className = 'pl-status-badge testing';
2030
+ statusBadge.textContent = 'testing';
2031
+ }
2032
+ }
2033
+ }
2034
+
2035
+ function plSyncToProject(panelId) {
2036
+ var p = openPanels[panelId];
2037
+ if (!p || !p.rawData) return;
2038
+ var d = p.rawData;
2039
+ if (d.status !== 'idle') return;
2040
+
2041
+ var promptName = d.activePrompt || '';
2042
+ var content = p._plEditedContent || '';
2043
+ if (!promptName || !content) return;
2044
+
2045
+ var message = '[PROMPT-LAB] Sync prompt "' + promptName + '" to project. Edited content:\n---\n' + content + '\n---\nWrite to prompts/ with incremented version, update project-specs.md, and commit.';
2046
+
2047
+ var sessionId = activeSessionId;
2048
+
2049
+ authFetch('/chat/send', {
2050
+ method: 'POST',
2051
+ headers: { 'Content-Type': 'application/json' },
2052
+ body: JSON.stringify({ message: message, sessionId: sessionId })
2053
+ }).then(function(res) {
2054
+ if (!res.ok) console.warn('plSyncToProject: chat/send failed');
2055
+ }).catch(function(err) {
2056
+ console.warn('plSyncToProject error:', err);
2057
+ });
2058
+
2059
+ // Optimistic status update
2060
+ var badge = document.querySelector('#pl-select-' + panelId);
2061
+ if (badge) {
2062
+ var statusBadge = badge.parentNode.querySelector('.pl-status-badge');
2063
+ if (statusBadge) {
2064
+ statusBadge.className = 'pl-status-badge syncing';
2065
+ statusBadge.textContent = 'syncing';
2066
+ }
2067
+ }
2068
+ }
2069
+
2070
+ function plRenderResults(container, testRuns) {
2071
+ if (!container) return;
2072
+ if (!testRuns || testRuns.length === 0) {
2073
+ container.innerHTML = '<div class="pl-results-header"><span class="pl-results-title">Test Results</span></div>' +
2074
+ '<div style="font-size:11px;color:#6a6a88">No test runs yet. Edit a prompt and click "Run Test".</div>';
2075
+ return;
2076
+ }
2077
+
2078
+ // Show latest run
2079
+ var run = testRuns[testRuns.length - 1];
2080
+ var html = '<div class="pl-results-header">';
2081
+ html += '<span class="pl-results-title">Test Results — ' + esc(run.promptName || '') + '</span>';
2082
+ html += '<span style="font-size:10px;color:#6a6a88">' + esc(run.timestamp || '') + '</span>';
2083
+ html += '</div>';
2084
+
2085
+ // Status
2086
+ if (run.status === 'running') {
2087
+ html += '<div style="font-size:11px;color:#c8a84a">Evaluation running...</div>';
2088
+ container.innerHTML = html;
2089
+ return;
2090
+ }
2091
+ if (run.status === 'error') {
2092
+ html += '<div style="font-size:11px;color:#c84a4a">Error: ' + esc(run.error || 'Unknown error') + '</div>';
2093
+ container.innerHTML = html;
2094
+ return;
2095
+ }
2096
+
2097
+ // Metrics cards
2098
+ var metrics = run.metrics || {};
2099
+ var metricKeys = Object.keys(metrics);
2100
+ if (metricKeys.length > 0) {
2101
+ html += '<div class="pl-metrics">';
2102
+ for (var i = 0; i < metricKeys.length; i++) {
2103
+ var key = metricKeys[i];
2104
+ var val = metrics[key];
2105
+ var valStr = typeof val === 'object' ? JSON.stringify(val) : String(val);
2106
+ html += '<div class="pl-metric-card">';
2107
+ html += '<div class="pl-metric-name">' + esc(key) + '</div>';
2108
+ html += '<div class="pl-metric-value">' + esc(valStr) + '</div>';
2109
+ html += '</div>';
2110
+ }
2111
+ html += '</div>';
2112
+ }
2113
+
2114
+ // Sample outputs
2115
+ var samples = run.sampleOutputs || [];
2116
+ if (samples.length > 0) {
2117
+ html += '<div class="pl-samples">';
2118
+ for (var j = 0; j < samples.length && j < 5; j++) {
2119
+ var s = samples[j];
2120
+ html += '<div class="pl-sample-card">';
2121
+ if (s.score != null) html += '<span class="pl-sample-score">' + s.score + '</span>';
2122
+ html += '<div class="pl-sample-label">Input</div>';
2123
+ html += '<div class="pl-sample-text">' + esc(s.input || '') + '</div>';
2124
+ html += '<div class="pl-sample-label" style="margin-top:4px">Output</div>';
2125
+ html += '<div class="pl-sample-text">' + esc(s.output || '') + '</div>';
2126
+ html += '</div>';
2127
+ }
2128
+ html += '</div>';
2129
+ }
2130
+
2131
+ container.innerHTML = html;
2132
+ }
2133
+
2134
+ function plRenderVersionSidebar(container, prompts, activePrompt, syncHistory, panelId) {
2135
+ if (!container) return;
2136
+ var html = '<div class="pl-sidebar-title">Prompts</div>';
2137
+
2138
+ for (var i = 0; i < prompts.length; i++) {
2139
+ var pr = prompts[i];
2140
+ var isActive = pr.name === activePrompt;
2141
+ html += '<div class="pl-prompt-item pl-load-prompt' + (isActive ? ' active' : '') + '" data-panel-id="' + esc(panelId) + '" data-prompt-name="' + esc(pr.name) + '">';
2142
+ html += '<div class="pl-prompt-name">' + esc(pr.filename) + '</div>';
2143
+ html += '<div class="pl-prompt-meta">v' + esc(pr.currentVersion) + ' &middot; ' + esc(pr.model || '') + '</div>';
2144
+ html += '</div>';
2145
+
2146
+ // Version list (expanded for active prompt)
2147
+ if (isActive && pr.versions && pr.versions.length > 0) {
2148
+ html += '<div class="pl-version-list">';
2149
+ for (var j = 0; j < pr.versions.length; j++) {
2150
+ var v = pr.versions[j];
2151
+ var isCurrent = v.version === pr.currentVersion;
2152
+ var scoreStr = '';
2153
+ if (v.evaluationScore) {
2154
+ var scoreKeys = Object.keys(v.evaluationScore);
2155
+ if (scoreKeys.length > 0) scoreStr = scoreKeys[0] + ': ' + v.evaluationScore[scoreKeys[0]];
2156
+ }
2157
+ html += '<div class="pl-version-item' + (isCurrent ? ' current' : '') + '">';
2158
+ html += '<span>v' + esc(v.version) + (isCurrent ? ' (current)' : '') + '</span>';
2159
+ html += '<span>' + esc(scoreStr) + '</span>';
2160
+ html += '</div>';
2161
+ }
2162
+ html += '</div>';
2163
+ }
2164
+ }
2165
+
2166
+ // Sync history
2167
+ if (syncHistory.length > 0) {
2168
+ html += '<div class="pl-sync-history">';
2169
+ html += '<div class="pl-sidebar-title">Sync History</div>';
2170
+ for (var k = syncHistory.length - 1; k >= 0 && k >= syncHistory.length - 5; k--) {
2171
+ var sh = syncHistory[k];
2172
+ html += '<div class="pl-sync-entry">';
2173
+ html += esc(sh.promptName) + ' <span class="version">v' + esc(sh.fromVersion) + ' → v' + esc(sh.toVersion) + '</span>';
2174
+ html += '</div>';
2175
+ }
2176
+ html += '</div>';
2177
+ }
2178
+
2179
+ if (prompts.length === 0) {
2180
+ html += '<div style="padding:12px;font-size:11px;color:#6a6a88">No prompts in project.</div>';
2181
+ }
2182
+
2183
+ container.innerHTML = html;
2184
+
2185
+ container.querySelectorAll('.pl-load-prompt').forEach(function(el) {
2186
+ el.addEventListener('click', function() { plLoadPrompt(el.dataset.panelId, el.dataset.promptName); });
2187
+ });
2188
+ }
2189
+
2190
+ // ─── PR Review Panel ──────────────────────────────────────────────────────────
2191
+
2192
+ function renderPRReviewPanel(container, panel) {
2193
+ var data = panel.rawData;
2194
+
2195
+ // Ensure addressed map exists on panel object (persists across re-renders)
2196
+ if (!panel._addressedMap) panel._addressedMap = {};
2197
+
2198
+ if (!data) {
2199
+ container.innerHTML = '<div class="pr-review"><div class="pr-empty"><h3>Loading PR comments...</h3><p>Fetching from GitHub.</p></div></div>';
2200
+ return;
2201
+ }
2202
+
2203
+ if (data.ghMissing) {
2204
+ container.innerHTML =
2205
+ '<div class="pr-review"><div class="pr-gh-missing">' +
2206
+ '<h3>GitHub CLI not installed</h3>' +
2207
+ '<p>Install it with <code>brew install gh</code> and authenticate with <code>gh auth login</code>.</p>' +
2208
+ '</div></div>';
2209
+ return;
2210
+ }
2211
+
2212
+ if (data.error) {
2213
+ container.innerHTML =
2214
+ '<div class="pr-review"><div class="pr-empty"><h3>Could not load PR</h3><p>' + esc(data.error) + '</p></div></div>';
2215
+ return;
2216
+ }
2217
+
2218
+ var threads = data.threads || [];
2219
+ var generalComments = data.generalComments || [];
2220
+ var reviews = data.reviews || [];
2221
+
2222
+ // Count addressed
2223
+ var addressedCount = Object.values(panel._addressedMap).filter(Boolean).length;
2224
+ var totalThreads = threads.length;
2225
+ var pct = totalThreads > 0 ? Math.round((addressedCount / totalThreads) * 100) : 0;
2226
+
2227
+ // Determine review decision display
2228
+ var prInfo = panel._prInfo || {};
2229
+ var reviewDecision = prInfo.reviewDecision || '';
2230
+ var decisionClass = reviewDecision === 'APPROVED' ? 'approved' :
2231
+ reviewDecision === 'CHANGES_REQUESTED' ? 'changes' :
2232
+ reviewDecision === 'REVIEW_REQUIRED' ? 'review-required' : 'pending';
2233
+ var decisionLabel = reviewDecision === 'APPROVED' ? 'Approved' :
2234
+ reviewDecision === 'CHANGES_REQUESTED' ? 'Changes Requested' :
2235
+ reviewDecision === 'REVIEW_REQUIRED' ? 'Review Required' : 'Pending';
2236
+
2237
+ var html = '<div class="pr-review">';
2238
+
2239
+ // Header
2240
+ html += '<div class="pr-header">';
2241
+ html += '<div class="pr-header-title">';
2242
+ if (prInfo.url) {
2243
+ html += '<a href="' + esc(prInfo.url) + '" target="_blank" rel="noopener">PR #' + esc(String(data.pr || '')) + '</a>';
2244
+ if (prInfo.title) html += ' &mdash; ' + esc(prInfo.title);
2245
+ } else {
2246
+ html += 'PR #' + esc(String(data.pr || ''));
2247
+ }
2248
+ html += '</div>';
2249
+ html += '<div class="pr-header-meta">';
2250
+ if (reviewDecision) {
2251
+ html += '<span class="pr-review-badge ' + decisionClass + '">' + esc(decisionLabel) + '</span>';
2252
+ }
2253
+ html += '<button class="pr-btn pr-refresh-btn" data-panel-id="' + esc(panel.panelId) + '">Refresh</button>';
2254
+ html += '</div>';
2255
+ html += '</div>';
2256
+
2257
+ // Progress bar
2258
+ if (totalThreads > 0) {
2259
+ html += '<div class="pr-progress-bar">';
2260
+ html += '<span class="pr-progress-label">' + addressedCount + ' / ' + totalThreads + ' addressed</span>';
2261
+ html += '<div class="pr-progress-track"><div class="pr-progress-fill" style="width:' + pct + '%"></div></div>';
2262
+ html += '</div>';
2263
+ }
2264
+
2265
+ html += '<div class="pr-content" id="pr-content-' + esc(panel.panelId) + '">';
2266
+
2267
+ // Review summaries (non-COMMENTED reviews with a body)
2268
+ var summaryReviews = reviews.filter(function(r) { return r.body && r.body.trim(); });
2269
+ if (summaryReviews.length > 0) {
2270
+ html += '<div class="pr-section-header">Reviews</div>';
2271
+ for (var ri = 0; ri < summaryReviews.length; ri++) {
2272
+ var rv = summaryReviews[ri];
2273
+ var rvClass = rv.state === 'APPROVED' ? 'approved' : rv.state === 'CHANGES_REQUESTED' ? 'changes' : 'pending';
2274
+ html += '<div class="pr-review-card">';
2275
+ html += '<div class="pr-review-card-header">';
2276
+ html += '<span class="pr-review-badge ' + rvClass + '">' + esc(rv.state.replace(/_/g, ' ')) + '</span>';
2277
+ html += '<span class="pr-review-card-author">' + esc(rv.author) + '</span>';
2278
+ html += '<span class="pr-review-card-date">' + esc(formatPRDate(rv.submittedAt)) + '</span>';
2279
+ html += '</div>';
2280
+ if (rv.body) html += '<div class="pr-review-card-body">' + renderPRMarkdown(rv.body) + '</div>';
2281
+ html += '</div>';
2282
+ }
2283
+ }
2284
+
2285
+ // Inline review threads
2286
+ if (threads.length > 0) {
2287
+ html += '<div class="pr-section-header">Review Threads (' + threads.length + ')</div>';
2288
+ for (var ti = 0; ti < threads.length; ti++) {
2289
+ var thread = threads[ti];
2290
+ var isAddressed = !!panel._addressedMap[thread.threadId];
2291
+ var isCollapsed = panel._collapsedMap && panel._collapsedMap[thread.threadId];
2292
+ var threadClass = 'pr-thread-card' + (isAddressed ? ' addressed' : '') + (isCollapsed ? ' collapsed' : '');
2293
+ html += '<div class="' + threadClass + '" id="pr-thread-' + esc(thread.threadId) + '" data-thread-id="' + esc(thread.threadId) + '">';
2294
+
2295
+ // Thread header (file + line, clickable to open file)
2296
+ html += '<div class="pr-thread-header pr-toggle-thread" data-panel-id="' + esc(panel.panelId) + '" data-thread-id="' + esc(thread.threadId) + '">';
2297
+ html += '<span class="pr-thread-file pr-open-file" title="Open file at line ' + thread.line + '" data-file="' + esc(thread.file) + '" data-line="' + esc(String(thread.line || '')) + '">' + esc(thread.file) + '</span>';
2298
+ if (thread.line) html += '<span class="pr-thread-line">:' + thread.line + '</span>';
2299
+ html += '<span class="pr-thread-count">' + thread.comments.length + ' comment' + (thread.comments.length !== 1 ? 's' : '') + '</span>';
2300
+ html += '<div class="pr-addressed-toggle pr-stop-propagation">';
2301
+ html += '<input type="checkbox" class="pr-addressed-checkbox" id="pr-addr-' + esc(thread.threadId) + '" ' + (isAddressed ? 'checked' : '') +
2302
+ ' data-panel-id="' + esc(panel.panelId) + '" data-thread-id="' + esc(thread.threadId) + '">';
2303
+ html += '<label class="pr-addressed-label" for="pr-addr-' + esc(thread.threadId) + '">Done</label>';
2304
+ html += '</div>';
2305
+ html += '<span class="pr-thread-collapse-icon">&#9660;</span>';
2306
+ html += '</div>';
2307
+
2308
+ // Thread body
2309
+ html += '<div class="pr-thread-body">';
2310
+
2311
+ // Diff hunk
2312
+ if (thread.diffHunk) {
2313
+ html += '<div class="pr-diff-hunk">';
2314
+ var hunkLines = thread.diffHunk.split('\n');
2315
+ for (var hl = 0; hl < hunkLines.length; hl++) {
2316
+ var ln = hunkLines[hl];
2317
+ var lnClass = ln.startsWith('+') ? 'add' : ln.startsWith('-') ? 'del' : ln.startsWith('@') ? 'meta' : 'ctx';
2318
+ html += '<span class="pr-diff-hunk-line ' + lnClass + '">' + esc(ln) + '</span>';
2319
+ }
2320
+ html += '</div>';
2321
+ }
2322
+
2323
+ // Comments
2324
+ html += '<div class="pr-comment-list">';
2325
+ for (var ci = 0; ci < thread.comments.length; ci++) {
2326
+ var comment = thread.comments[ci];
2327
+ html += '<div class="pr-comment' + (comment.isReply ? ' reply' : '') + '">';
2328
+ html += '<div class="pr-comment-header">';
2329
+ html += '<span class="pr-comment-author">' + esc(comment.author) + '</span>';
2330
+ html += '<span class="pr-comment-date">' + esc(formatPRDate(comment.createdAt)) + '</span>';
2331
+ html += '</div>';
2332
+ html += '<div class="pr-comment-body">' + renderPRMarkdown(comment.body) + '</div>';
2333
+ html += '</div>';
2334
+ }
2335
+ html += '</div>';
2336
+
2337
+ html += '</div>'; // thread body
2338
+ html += '</div>'; // thread card
2339
+ }
2340
+ } else {
2341
+ html += '<div class="pr-empty"><h3>No review threads</h3><p>No inline code review comments found on this PR.</p></div>';
2342
+ }
2343
+
2344
+ // General comments
2345
+ if (generalComments.length > 0) {
2346
+ html += '<div class="pr-section-header">PR Comments (' + generalComments.length + ')</div>';
2347
+ for (var gi = 0; gi < generalComments.length; gi++) {
2348
+ var gc = generalComments[gi];
2349
+ html += '<div class="pr-general-comment">';
2350
+ html += '<div class="pr-comment-header">';
2351
+ html += '<span class="pr-comment-author">' + esc(gc.author) + '</span>';
2352
+ html += '<span class="pr-comment-date">' + esc(formatPRDate(gc.createdAt)) + '</span>';
2353
+ html += '</div>';
2354
+ html += '<div class="pr-comment-body">' + renderPRMarkdown(gc.body) + '</div>';
2355
+ html += '</div>';
2356
+ }
2357
+ }
2358
+
2359
+ html += '</div>'; // pr-content
2360
+ html += '</div>'; // pr-review
2361
+
2362
+ container.innerHTML = html;
2363
+
2364
+ container.querySelectorAll('.pr-refresh-btn').forEach(function(btn) {
2365
+ btn.addEventListener('click', function() { refreshPRReviewPanel(btn.dataset.panelId); });
2366
+ });
2367
+ container.querySelectorAll('.pr-toggle-thread').forEach(function(el) {
2368
+ el.addEventListener('click', function() { prToggleThread(el.dataset.panelId, el.dataset.threadId); });
2369
+ });
2370
+ container.querySelectorAll('.pr-open-file').forEach(function(el) {
2371
+ el.addEventListener('click', function(ev) {
2372
+ ev.stopPropagation();
2373
+ var lineStr = el.dataset.line;
2374
+ var line = lineStr ? parseInt(lineStr, 10) : 0;
2375
+ prOpenFileLine(el.dataset.file, line);
2376
+ });
2377
+ });
2378
+ container.querySelectorAll('.pr-stop-propagation').forEach(function(el) {
2379
+ el.addEventListener('click', function(ev) { ev.stopPropagation(); });
2380
+ });
2381
+ container.querySelectorAll('.pr-addressed-checkbox').forEach(function(el) {
2382
+ el.addEventListener('change', function() { prToggleAddressed(el.dataset.panelId, el.dataset.threadId, el.checked); });
2383
+ });
2384
+ }
2385
+
2386
+ function prToggleThread(panelId, threadId) {
2387
+ var p = openPanels[panelId];
2388
+ if (!p) return;
2389
+ if (!p._collapsedMap) p._collapsedMap = {};
2390
+ p._collapsedMap[threadId] = !p._collapsedMap[threadId];
2391
+ var card = document.getElementById('pr-thread-' + threadId);
2392
+ if (card) card.classList.toggle('collapsed', !!p._collapsedMap[threadId]);
2393
+ }
2394
+
2395
+ function prToggleAddressed(panelId, threadId, checked) {
2396
+ var p = openPanels[panelId];
2397
+ if (!p) return;
2398
+ if (!p._addressedMap) p._addressedMap = {};
2399
+ p._addressedMap[threadId] = checked;
2400
+ // Update addressed class on the card
2401
+ var card = document.getElementById('pr-thread-' + threadId);
2402
+ if (card) card.classList.toggle('addressed', checked);
2403
+ // Recompute progress bar
2404
+ var threads = (p.rawData && p.rawData.threads) || [];
2405
+ var total = threads.length;
2406
+ var addressed = Object.values(p._addressedMap).filter(Boolean).length;
2407
+ var pct = total > 0 ? Math.round((addressed / total) * 100) : 0;
2408
+ var fill = document.querySelector('#pr-content-' + CSS.escape(panelId) + ' ~ .pr-progress-bar .pr-progress-fill') ||
2409
+ document.querySelector('.pr-progress-fill');
2410
+ if (fill) fill.style.width = pct + '%';
2411
+ var label = document.querySelector('.pr-progress-label');
2412
+ if (label) label.textContent = addressed + ' / ' + total + ' addressed';
2413
+ }
2414
+
2415
+ function prOpenFileLine(filePath, line) {
2416
+ if (typeof openFileFromExplorer !== 'function') return;
2417
+ openFileFromExplorer(filePath).then(function() {
2418
+ setTimeout(function() {
2419
+ var inst = typeof activeMonacoInstance !== 'undefined' ? activeMonacoInstance : null;
2420
+ if (inst && line) {
2421
+ inst.revealLineInCenter(line);
2422
+ inst.setPosition({ lineNumber: line, column: 1 });
2423
+ inst.focus();
2424
+ }
2425
+ }, 200);
2426
+ }).catch(function() {});
2427
+ }
2428
+
2429
+ function refreshPRReviewPanel(panelId) {
2430
+ var p = openPanels[panelId];
2431
+ if (!p || !p.rawData || !p.rawData.pr) return;
2432
+ var prNum = p.rawData.pr;
2433
+ authFetch('/git/pr-comments?pr=' + prNum).then(function(res) { return res.json(); }).then(function(data) {
2434
+ p.rawData = Object.assign({}, p.rawData, data);
2435
+ var container = document.getElementById('file-rendered-view');
2436
+ if (container) renderPRReviewPanel(container, p);
2437
+ }).catch(function() {});
2438
+ }
2439
+
2440
+ function formatPRDate(iso) {
2441
+ if (!iso) return '';
2442
+ try {
2443
+ var d = new Date(iso);
2444
+ return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) + ' ' +
2445
+ d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
2446
+ } catch { return iso; }
2447
+ }
2448
+
2449
+ function renderPRMarkdown(text) {
2450
+ if (!text) return '';
2451
+ if (typeof marked !== 'undefined') {
2452
+ try { return marked.parse(text); } catch {}
2453
+ }
2454
+ // Minimal fallback: escape and preserve newlines
2455
+ return '<p>' + esc(text).replace(/\n\n+/g, '</p><p>').replace(/\n/g, '<br>') + '</p>';
2456
+ }
2457
+
2458
+ // ═══════════════════════════════════════════════════════════════
2459
+ // Brainstorm panel — live multi-specialist fan-out from Syn
2460
+ // ═══════════════════════════════════════════════════════════════
2461
+ //
2462
+ // Data shape (written by syn/brainstorm.md to
2463
+ // brainstorm/brainstorm_<project>.state.json):
2464
+ //
2465
+ // {
2466
+ // "mode": "brainstorm",
2467
+ // "project": "<slug>",
2468
+ // "created": "<ISO>",
2469
+ // "phase": "intake|gathering|synthesis|facilitation",
2470
+ // "problem": "<initial idea>",
2471
+ // "context": { "environment": ..., "data": ..., "compute": ..., "openness": ... },
2472
+ // "specialists": [
2473
+ // { "name": "data-scientist", "status": "queued|thinking|responded|skipped",
2474
+ // "started_at": "...", "responded_at": "...",
2475
+ // "headline": "...", "ideas_count": 3 }
2476
+ // ],
2477
+ // "synthesis": { "quick_wins": [...], "bold_bets": [...], "wildcards": [...],
2478
+ // "themes": [...], "recommended_start": "...",
2479
+ // "recommendation_rationale": "..." } | null,
2480
+ // "facilitation_log": [ { "ts": "...", "actor": "...", "text": "..." } ],
2481
+ // "outcome": { "decision": "single|multi-workstream", ... } | null
2482
+ // }
2483
+
2484
+ function cleanupBrainstorm(panel) {
2485
+ // No persistent sub-components yet (no Tabulator, no Plotly); placeholder for
2486
+ // symmetry with other panel cleanup helpers.
2487
+ return;
2488
+ }
2489
+
2490
+ function renderBrainstorm(container, panel) {
2491
+ var d = panel.rawData;
2492
+ if (!d) {
2493
+ container.innerHTML = '<div class="no-file-msg">No brainstorm state available yet.</div>';
2494
+ return;
2495
+ }
2496
+
2497
+ // Defensive defaults — agent may write state mid-phase before all fields exist.
2498
+ var phase = d.phase || 'intake';
2499
+ var problem = d.problem || '(problem not captured yet)';
2500
+ var specialists = Array.isArray(d.specialists) ? d.specialists : [];
2501
+ var synthesis = d.synthesis || null;
2502
+ var log = Array.isArray(d.facilitation_log) ? d.facilitation_log : [];
2503
+ var outcome = d.outcome || null;
2504
+ var ctx = d.context || {};
2505
+
2506
+ var html = '<div class="brainstorm-panel">';
2507
+
2508
+ // ── Header ──
2509
+ html += '<div class="brain-header">';
2510
+ html += '<div class="brain-title">' + esc(d.project || 'brainstorm') + '</div>';
2511
+ html += '<div class="brain-phase-pill brain-phase-' + esc(phase) + '">' + esc(phase) + '</div>';
2512
+ html += '</div>';
2513
+
2514
+ html += '<div class="brain-problem"><span class="brain-problem-label">Problem</span>'
2515
+ + '<div class="brain-problem-body">' + esc(problem) + '</div></div>';
2516
+
2517
+ // ── Context strip (compact) ──
2518
+ var ctxItems = [];
2519
+ if (ctx.environment) ctxItems.push(['env', ctx.environment]);
2520
+ if (ctx.data) ctxItems.push(['data', ctx.data]);
2521
+ if (ctx.compute) ctxItems.push(['compute', ctx.compute]);
2522
+ if (ctx.openness) ctxItems.push(['openness', ctx.openness]);
2523
+ if (ctxItems.length > 0) {
2524
+ html += '<div class="brain-context-strip">';
2525
+ for (var c = 0; c < ctxItems.length; c++) {
2526
+ html += '<div class="brain-context-item">'
2527
+ + '<span class="brain-context-key">' + esc(ctxItems[c][0]) + '</span>'
2528
+ + '<span class="brain-context-val">' + esc(String(ctxItems[c][1])) + '</span>'
2529
+ + '</div>';
2530
+ }
2531
+ html += '</div>';
2532
+ }
2533
+
2534
+ // ── Specialist progress bar ──
2535
+ var total = specialists.length;
2536
+ var done = specialists.filter(function(s) { return s.status === 'responded'; }).length;
2537
+ var pending = specialists.filter(function(s) { return s.status === 'thinking'; }).length;
2538
+ if (total > 0) {
2539
+ html += '<div class="brain-progress">';
2540
+ html += '<div class="brain-progress-label">'
2541
+ + '<span><strong>' + done + '</strong> / ' + total + ' shards checked in</span>'
2542
+ + (pending > 0 ? '<span class="brain-progress-pending">' + pending + ' thinking…</span>' : '')
2543
+ + '</div>';
2544
+ html += '<div class="brain-progress-bar">';
2545
+ for (var i = 0; i < total; i++) {
2546
+ var s = specialists[i];
2547
+ var cls = 'brain-prog-seg ' + (s.status || 'queued');
2548
+ html += '<div class="' + cls + '" title="' + esc(s.name || '') + ' — ' + esc(s.status || '') + '"></div>';
2549
+ }
2550
+ html += '</div></div>';
2551
+ }
2552
+
2553
+ // ── Specialist grid ──
2554
+ html += '<div class="brain-section-label">Specialist input</div>';
2555
+ if (specialists.length === 0) {
2556
+ html += '<div class="brain-empty">Waiting for Syn to spawn the chorus…</div>';
2557
+ } else {
2558
+ html += '<div class="brain-specialist-grid">';
2559
+ for (var si = 0; si < specialists.length; si++) {
2560
+ var sp = specialists[si];
2561
+ var status = sp.status || 'queued';
2562
+ html += '<div class="brain-card brain-card-' + esc(status) + '">';
2563
+ html += '<div class="brain-card-head">';
2564
+ html += '<span class="brain-card-name">' + esc(sp.name || '') + '</span>';
2565
+ html += '<span class="brain-card-status">';
2566
+ if (status === 'thinking') html += '<span class="brain-spinner"></span>';
2567
+ html += esc(status);
2568
+ html += '</span>';
2569
+ html += '</div>';
2570
+ if (sp.headline) {
2571
+ html += '<div class="brain-card-headline">' + esc(sp.headline) + '</div>';
2572
+ } else if (status === 'thinking') {
2573
+ html += '<div class="brain-card-headline brain-card-pending">thinking…</div>';
2574
+ } else if (status === 'queued') {
2575
+ html += '<div class="brain-card-headline brain-card-pending">queued</div>';
2576
+ }
2577
+ if (typeof sp.ideas_count === 'number' && sp.ideas_count > 0) {
2578
+ html += '<div class="brain-card-meta">' + sp.ideas_count + ' idea'
2579
+ + (sp.ideas_count === 1 ? '' : 's') + '</div>';
2580
+ }
2581
+ html += '</div>';
2582
+ }
2583
+ html += '</div>';
2584
+ }
2585
+
2586
+ // ── Synthesis (appears when phase >= synthesis) ──
2587
+ if (synthesis) {
2588
+ html += '<div class="brain-section-label">Synthesis</div>';
2589
+ html += '<div class="brain-synthesis">';
2590
+ html += brainSynthBucket('Quick wins', 'quick-wins', synthesis.quick_wins);
2591
+ html += brainSynthBucket('Bold bets', 'bold-bets', synthesis.bold_bets);
2592
+ html += brainSynthBucket('Wildcards', 'wildcards', synthesis.wildcards);
2593
+ if (Array.isArray(synthesis.themes) && synthesis.themes.length > 0) {
2594
+ html += '<div class="brain-themes"><span class="brain-themes-label">Themes:</span> ';
2595
+ html += synthesis.themes.map(function(t) {
2596
+ return '<span class="brain-theme-chip">' + esc(t) + '</span>';
2597
+ }).join('');
2598
+ html += '</div>';
2599
+ }
2600
+ if (synthesis.recommended_start) {
2601
+ html += '<div class="brain-recommend">';
2602
+ html += '<div class="brain-recommend-label">Recommended starting point</div>';
2603
+ html += '<div class="brain-recommend-title">' + esc(synthesis.recommended_start) + '</div>';
2604
+ if (synthesis.recommendation_rationale) {
2605
+ html += '<div class="brain-recommend-rationale">'
2606
+ + esc(synthesis.recommendation_rationale) + '</div>';
2607
+ }
2608
+ html += '</div>';
2609
+ }
2610
+ html += '</div>';
2611
+ }
2612
+
2613
+ // ── Facilitation log (Phase 3) ──
2614
+ if (log.length > 0) {
2615
+ html += '<div class="brain-section-label">Facilitation log</div>';
2616
+ html += '<div class="brain-log">';
2617
+ for (var li = log.length - 1; li >= 0; li--) {
2618
+ var entry = log[li];
2619
+ html += '<div class="brain-log-entry">';
2620
+ html += '<span class="brain-log-actor brain-actor-' + esc(entry.actor || 'syn') + '">'
2621
+ + esc(entry.actor || '') + '</span>';
2622
+ html += '<span class="brain-log-text">' + esc(entry.text || '') + '</span>';
2623
+ if (entry.ts) {
2624
+ html += '<span class="brain-log-ts">' + esc(brainFormatTs(entry.ts)) + '</span>';
2625
+ }
2626
+ html += '</div>';
2627
+ }
2628
+ html += '</div>';
2629
+ }
2630
+
2631
+ // ── Outcome banner ──
2632
+ if (outcome) {
2633
+ html += '<div class="brain-outcome brain-outcome-' + esc(outcome.decision || 'unknown') + '">';
2634
+ html += '<span class="brain-outcome-label">Outcome</span>';
2635
+ if (outcome.decision === 'single') {
2636
+ html += '<span class="brain-outcome-detail">Escalated to <strong>'
2637
+ + esc(outcome.direction || '?') + '</strong>';
2638
+ if (outcome.target_dir) html += ' → <code>' + esc(outcome.target_dir) + '</code>';
2639
+ html += '</span>';
2640
+ } else if (outcome.decision === 'multi-workstream') {
2641
+ var n = outcome.workstream_count
2642
+ || (Array.isArray(outcome.workstreams) ? outcome.workstreams.length : 0);
2643
+ html += '<span class="brain-outcome-detail">Multi-workstream — <strong>'
2644
+ + n + '</strong> workstream' + (n === 1 ? '' : 's') + '</span>';
2645
+ } else {
2646
+ html += '<span class="brain-outcome-detail">' + esc(JSON.stringify(outcome)) + '</span>';
2647
+ }
2648
+ html += '</div>';
2649
+ }
2650
+
2651
+ html += '</div>';
2652
+ container.innerHTML = html;
2653
+ }
2654
+
2655
+ function brainSynthBucket(label, cls, items) {
2656
+ if (!Array.isArray(items) || items.length === 0) return '';
2657
+ var html = '<div class="brain-bucket brain-bucket-' + cls + '">';
2658
+ html += '<div class="brain-bucket-label">' + esc(label) + '</div>';
2659
+ html += '<ul class="brain-bucket-list">';
2660
+ for (var i = 0; i < items.length; i++) {
2661
+ var item = items[i];
2662
+ if (typeof item === 'string') {
2663
+ html += '<li><strong>' + esc(item) + '</strong></li>';
2664
+ } else {
2665
+ html += '<li>';
2666
+ html += '<strong>' + esc(item.title || '') + '</strong>';
2667
+ if (item.rationale) html += ' — ' + esc(item.rationale);
2668
+ html += '</li>';
2669
+ }
2670
+ }
2671
+ html += '</ul></div>';
2672
+ return html;
2673
+ }
2674
+
2675
+ function brainFormatTs(iso) {
2676
+ if (!iso) return '';
2677
+ try {
2678
+ var d = new Date(iso);
2679
+ return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
2680
+ } catch (e) { return iso; }
2681
+ }