@polderlabs/bizar 10.7.2 → 10.10.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 (1202) hide show
  1. package/.claude/agents/_shared/AGENT_BASELINE.md +48 -243
  2. package/.claude/agents/_shared/CLAUDE_TOOLS.md +4 -2
  3. package/.claude/agents/_shared/SKILLS.md +18 -95
  4. package/.claude/agents/brand-designer.md +7 -5
  5. package/.claude/agents/debug-specialist.md +68 -0
  6. package/.claude/agents/exec-assistant.md +2 -1
  7. package/.claude/agents/help-desk.md +3 -3
  8. package/.claude/agents/it-lead.md +5 -5
  9. package/.claude/agents/knowledge-manager.md +2 -2
  10. package/.claude/agents/office-coordinator.md +3 -4
  11. package/.claude/agents/office-greeter.md +5 -5
  12. package/.claude/agents/office-manager.md +127 -42
  13. package/.claude/agents/planner.md +120 -0
  14. package/.claude/agents/principal-engineer.md +6 -5
  15. package/.claude/agents/qa-reviewer.md +4 -4
  16. package/.claude/agents/research-analyst.md +13 -43
  17. package/.claude/agents/senior-engineer.md +5 -4
  18. package/.claude/agents/support-tech.md +21 -73
  19. package/.claude/agents/ui-designer.md +89 -0
  20. package/.claude/commands/bizar.md +9 -16
  21. package/.claude/commands/cron.md +7 -3
  22. package/.claude/commands/learn.md +4 -2
  23. package/.claude/commands/plan.md +14 -28
  24. package/.claude/commands/plow-through.md +19 -38
  25. package/.claude/commands/pr-review.md +2 -3
  26. package/.claude/commands/setup-provider.md +15 -88
  27. package/.claude/commands/sprint.md +3 -3
  28. package/.claude/commands/tailscale-serve.md +9 -95
  29. package/.claude/commands/team.md +26 -122
  30. package/.claude/commands/test.md +3 -3
  31. package/.claude/commands/use-default.md +53 -0
  32. package/.claude/commands/use-premium.md +74 -0
  33. package/.claude/commands/validate.md +13 -61
  34. package/.claude/hooks/README.md +24 -108
  35. package/.claude/hooks/advisor-context.mjs +42 -0
  36. package/.claude/hooks/agent-grounding.mjs +41 -0
  37. package/.claude/hooks/content-style-guard.mjs +73 -0
  38. package/.claude/hooks/control-inbox.mjs +48 -0
  39. package/.claude/hooks/git-workflow-guard.mjs +104 -0
  40. package/.claude/hooks/learning-extract.mjs +0 -0
  41. package/.claude/hooks/path-ownership-guard.mjs +89 -0
  42. package/.claude/hooks/precompact-priorities.sh +11 -0
  43. package/.claude/hooks/pretooluse-bash.mjs +4 -11
  44. package/.claude/hooks/sessionend-recall.mjs +0 -0
  45. package/.claude/hooks/sessionstart-prime.mjs +10 -4
  46. package/.claude/hooks/simplify-guard.mjs +51 -0
  47. package/.claude/hooks/telemetry.mjs +69 -0
  48. package/.claude/hooks/thinking-route.mjs +7 -9
  49. package/.claude/hooks/worker-suggest.mjs +21 -13
  50. package/.claude/hooks/worktree-bootstrap.mjs +64 -0
  51. package/.claude/model-router.json +57 -0
  52. package/.claude/settings.json +92 -140
  53. package/AGENTS.md +95 -0
  54. package/README.md +76 -472
  55. package/cli/agent-browser-update.mjs +87 -274
  56. package/cli/atomic.mjs +1 -1
  57. package/cli/audit.mjs +31 -121
  58. package/cli/banner.mjs +3 -14
  59. package/cli/bin.mjs +83 -331
  60. package/cli/bootstrap.mjs +9 -12
  61. package/cli/commands/claude-cmd.mjs +45 -309
  62. package/cli/commands/control.mjs +102 -0
  63. package/cli/commands/install.mjs +9 -56
  64. package/cli/commands/rca.mjs +63 -165
  65. package/cli/commands/sandbox.mjs +89 -183
  66. package/cli/commands/setup-provider.mjs +65 -423
  67. package/cli/commands/task.mjs +249 -0
  68. package/cli/commands/util.mjs +37 -197
  69. package/cli/commands/validate.mjs +45 -75
  70. package/cli/control-store.mjs +312 -0
  71. package/{bizar-dash/src/server → cli/core}/backup-store.mjs +7 -17
  72. package/cli/doctor.mjs +8 -13
  73. package/cli/export.mjs +3 -7
  74. package/cli/init.mjs +1 -135
  75. package/cli/install/banner.mjs +4 -14
  76. package/cli/install/paths.mjs +3 -8
  77. package/cli/install.mjs +2 -7
  78. package/cli/migrate.mjs +24 -24
  79. package/{bizar-dash/src/server → cli}/progress-parser.mjs +7 -5
  80. package/cli/provision.mjs +140 -165
  81. package/cli/task-ledger.mjs +842 -0
  82. package/cli/update.mjs +2 -2
  83. package/cli/utils.mjs +1 -38
  84. package/config/AGENTS.md +14 -591
  85. package/config/rules/general.md +1 -1
  86. package/config/rules/uncertainty.md +2 -2
  87. package/config/skills/9router/SKILL.md +3 -3
  88. package/config/skills/9router-tts/SKILL.md +1 -1
  89. package/config/skills/agent-browser/SKILL.md +53 -0
  90. package/config/skills/bizar/README.md +3 -3
  91. package/config/skills/bizar/SKILL.md +31 -431
  92. package/config/skills/clean-gone-branches/SKILL.md +8 -0
  93. package/config/skills/commit-staged/SKILL.md +15 -0
  94. package/config/skills/cpp-coding-standards/README.md +3 -3
  95. package/config/skills/cpp-testing/README.md +2 -2
  96. package/config/skills/create-pr/SKILL.md +16 -0
  97. package/config/skills/cubesandbox/SKILL.md +33 -128
  98. package/config/skills/embedded-esp-idf/README.md +3 -3
  99. package/config/skills/harness-engineering/SKILL.md +37 -126
  100. package/config/skills/read-the-damn-docs/SKILL.md +2 -2
  101. package/config/skills/resolve-pr-comments/SKILL.md +14 -0
  102. package/config/skills/review-pr/SKILL.md +10 -0
  103. package/config/skills/self-improvement/SKILL.md +19 -54
  104. package/config/skills/simplify/SKILL.md +15 -0
  105. package/config/skills/update-pr-summary/SKILL.md +8 -0
  106. package/config/trigger-patterns.json +11 -25
  107. package/package.json +28 -102
  108. package/packages/sdk/dist/agent-registry.d.ts +2 -2
  109. package/packages/sdk/dist/agent-registry.js +2 -2
  110. package/packages/sdk/dist/consensus/index.d.ts +4 -4
  111. package/packages/sdk/dist/consensus/index.js +5 -5
  112. package/packages/sdk/dist/federation/envelope.d.ts +1 -1
  113. package/packages/sdk/dist/federation/envelope.js +2 -2
  114. package/packages/sdk/dist/index.d.ts +1 -4
  115. package/packages/sdk/dist/index.js +1 -4
  116. package/packages/sdk/dist/mcp/server.d.ts +8 -13
  117. package/packages/sdk/dist/mcp/server.js +15 -77
  118. package/packages/sdk/dist/router/index.d.ts +2 -2
  119. package/packages/sdk/dist/router/index.js +2 -2
  120. package/packages/sdk/dist/version.d.ts +2 -2
  121. package/packages/sdk/dist/version.js +2 -2
  122. package/packages/sdk/package.json +1 -2
  123. package/scripts/git-hooks/pre-commit +2 -2
  124. package/scripts/git-hooks/pre-push +3 -3
  125. package/scripts/install-hooks.sh +2 -2
  126. package/.claude/agents/vp-engineering.md +0 -54
  127. package/.claude/commands/goal.md +0 -99
  128. package/.claude/commands/visual-plan.md +0 -24
  129. package/.claude/hooks/__tests__/pretooluse-editwrite.test.mjs +0 -146
  130. package/.claude/hooks/__tests__/sessionend-recall.test.mjs +0 -256
  131. package/.claude/hooks/__tests__/sessionstart-prime.test.mjs +0 -325
  132. package/.claude/hooks/__tests__/thinking-route.test.mjs +0 -319
  133. package/.claude/hooks/post-merge-audit.sh +0 -93
  134. package/.claude/skills/9router/SKILL.md +0 -80
  135. package/.claude/skills/9router-chat/SKILL.md +0 -73
  136. package/.claude/skills/9router-embeddings/SKILL.md +0 -69
  137. package/.claude/skills/9router-image/SKILL.md +0 -86
  138. package/.claude/skills/9router-stt/SKILL.md +0 -79
  139. package/.claude/skills/9router-tts/SKILL.md +0 -80
  140. package/.claude/skills/9router-web-fetch/SKILL.md +0 -99
  141. package/.claude/skills/9router-web-search/SKILL.md +0 -91
  142. package/.claude/skills/agent-browser/SKILL.md +0 -64
  143. package/.claude/skills/bizar/README.md +0 -9
  144. package/.claude/skills/bizar/SKILL.md +0 -447
  145. package/.claude/skills/cpp-coding-standards/README.md +0 -28
  146. package/.claude/skills/cpp-coding-standards/SKILL.md +0 -634
  147. package/.claude/skills/cpp-coding-standards/references/concurrency.md +0 -320
  148. package/.claude/skills/cpp-coding-standards/references/error-handling.md +0 -229
  149. package/.claude/skills/cpp-coding-standards/references/memory-safety.md +0 -216
  150. package/.claude/skills/cpp-coding-standards/references/modern-idioms.md +0 -282
  151. package/.claude/skills/cpp-coding-standards/references/review-checklist.md +0 -96
  152. package/.claude/skills/cpp-testing/README.md +0 -28
  153. package/.claude/skills/cpp-testing/SKILL.md +0 -304
  154. package/.claude/skills/cpp-testing/references/coverage.md +0 -370
  155. package/.claude/skills/cpp-testing/references/framework-compare.md +0 -175
  156. package/.claude/skills/cpp-testing/references/host-test-for-embedded.md +0 -499
  157. package/.claude/skills/cpp-testing/references/mocking.md +0 -364
  158. package/.claude/skills/cpp-testing/references/tdd-workflow.md +0 -308
  159. package/.claude/skills/cubesandbox/SKILL.md +0 -148
  160. package/.claude/skills/de-sloppify/cleanup.mjs +0 -253
  161. package/.claude/skills/de-sloppify/cleanup.test.mjs +0 -189
  162. package/.claude/skills/embedded-esp-idf/README.md +0 -41
  163. package/.claude/skills/embedded-esp-idf/SKILL.md +0 -439
  164. package/.claude/skills/embedded-esp-idf/references/freertos-patterns.md +0 -214
  165. package/.claude/skills/embedded-esp-idf/references/host-tests.md +0 -164
  166. package/.claude/skills/embedded-esp-idf/references/idf-py-commands.md +0 -157
  167. package/.claude/skills/embedded-esp-idf/references/kconfig.md +0 -159
  168. package/.claude/skills/embedded-esp-idf/references/logging-discipline.md +0 -118
  169. package/.claude/skills/embedded-esp-idf/references/memory-and-iram.md +0 -137
  170. package/.claude/skills/embedded-esp-idf/references/nvs.md +0 -121
  171. package/.claude/skills/embedded-esp-idf/references/packed-structs.md +0 -192
  172. package/.claude/skills/embedded-esp-idf/scripts/idf_env.sh +0 -47
  173. package/.claude/skills/embedded-esp-idf/scripts/size_check.sh +0 -77
  174. package/.claude/skills/glyph/SKILL.md +0 -163
  175. package/.claude/skills/harness-engineering/SKILL.md +0 -142
  176. package/.claude/skills/lightrag/SKILL.md +0 -81
  177. package/.claude/skills/memory-protocol/SKILL.md +0 -105
  178. package/.claude/skills/obsidian/SKILL.md +0 -306
  179. package/.claude/skills/read-the-damn-docs/SKILL.md +0 -113
  180. package/.claude/skills/self-improvement/SKILL.md +0 -64
  181. package/.claude/skills/skillopt/SKILL.md +0 -129
  182. package/.claude/skills/thinking-archetypes/SKILL.md +0 -90
  183. package/.claude/skills/thinking-bayesian/SKILL.md +0 -267
  184. package/.claude/skills/thinking-bounded-rationality/SKILL.md +0 -406
  185. package/.claude/skills/thinking-circle-of-competence/SKILL.md +0 -216
  186. package/.claude/skills/thinking-cynefin/SKILL.md +0 -70
  187. package/.claude/skills/thinking-debiasing/SKILL.md +0 -192
  188. package/.claude/skills/thinking-dual-process/SKILL.md +0 -282
  189. package/.claude/skills/thinking-effectuation/SKILL.md +0 -366
  190. package/.claude/skills/thinking-feedback-loops/SKILL.md +0 -464
  191. package/.claude/skills/thinking-fermi-estimation/SKILL.md +0 -263
  192. package/.claude/skills/thinking-first-principles/SKILL.md +0 -167
  193. package/.claude/skills/thinking-five-whys-plus/SKILL.md +0 -139
  194. package/.claude/skills/thinking-inversion/SKILL.md +0 -195
  195. package/.claude/skills/thinking-jobs-to-be-done/SKILL.md +0 -363
  196. package/.claude/skills/thinking-kepner-tregoe/SKILL.md +0 -154
  197. package/.claude/skills/thinking-leverage-points/SKILL.md +0 -390
  198. package/.claude/skills/thinking-lindy-effect/SKILL.md +0 -331
  199. package/.claude/skills/thinking-map-territory/SKILL.md +0 -111
  200. package/.claude/skills/thinking-margin-of-safety/SKILL.md +0 -330
  201. package/.claude/skills/thinking-model-combination/SKILL.md +0 -406
  202. package/.claude/skills/thinking-model-router/SKILL.md +0 -360
  203. package/.claude/skills/thinking-model-selection/SKILL.md +0 -341
  204. package/.claude/skills/thinking-occams-razor/SKILL.md +0 -129
  205. package/.claude/skills/thinking-ooda/SKILL.md +0 -127
  206. package/.claude/skills/thinking-opportunity-cost/SKILL.md +0 -360
  207. package/.claude/skills/thinking-pre-mortem/SKILL.md +0 -170
  208. package/.claude/skills/thinking-probabilistic/SKILL.md +0 -324
  209. package/.claude/skills/thinking-red-team/SKILL.md +0 -142
  210. package/.claude/skills/thinking-regret-minimization/SKILL.md +0 -335
  211. package/.claude/skills/thinking-reversibility/SKILL.md +0 -326
  212. package/.claude/skills/thinking-scientific-method/SKILL.md +0 -162
  213. package/.claude/skills/thinking-second-order/SKILL.md +0 -184
  214. package/.claude/skills/thinking-socratic/SKILL.md +0 -198
  215. package/.claude/skills/thinking-steel-manning/SKILL.md +0 -332
  216. package/.claude/skills/thinking-systems/SKILL.md +0 -238
  217. package/.claude/skills/thinking-theory-of-constraints/SKILL.md +0 -338
  218. package/.claude/skills/thinking-thought-experiment/SKILL.md +0 -354
  219. package/.claude/skills/thinking-triz/SKILL.md +0 -171
  220. package/.claude/skills/thinking-via-negativa/SKILL.md +0 -358
  221. package/bizar-dash/.bizar/graph/.graphify_analysis.json +0 -1540
  222. package/bizar-dash/.bizar/graph/.graphify_labels.json +0 -1
  223. package/bizar-dash/.bizar/graph/GRAPH_REPORT.md +0 -404
  224. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/0661b816358db84dfdb7f10443db8a61152fd20b3fee950caf8fbf32920a1790.json +0 -1
  225. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/16da06d774142a6e5d74829ad982531286a6193ef798de8f593db029e123ec32.json +0 -1
  226. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/27e5697c01254c69c64c5a33896669073b40afb5ba88375102ae77fa5404136d.json +0 -1
  227. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/532997898027254aa4c0932243dc71d2f55888181232b65974961934997dc85b.json +0 -1
  228. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/84e3451a64c7f99f0cd6a88b0748d7426780486f53ab4935b7476b28c08f8293.json +0 -1
  229. package/bizar-dash/.bizar/graph/cache/ast/v0.8.46/88e5ea5f3e78fa69353fe9e6a8d1a05391d54605e25600cb125531eb5a6432ff.json +0 -1
  230. package/bizar-dash/.bizar/graph/cache/stat-index.json +0 -1
  231. package/bizar-dash/.bizar/graph/graph.html +0 -307
  232. package/bizar-dash/.bizar/graph/graph.json +0 -40267
  233. package/bizar-dash/.bizar/graph/manifest.json +0 -762
  234. package/bizar-dash/.graphifyignore +0 -40
  235. package/bizar-dash/.obsidian/README.md +0 -14
  236. package/bizar-dash/.obsidian/daily/2026-06-25.md +0 -9
  237. package/bizar-dash/.obsidian/vault.json +0 -7
  238. package/bizar-dash/.od-skills/web-prototype-e1429c1737/SKILL.md +0 -103
  239. package/bizar-dash/.od-skills/web-prototype-e1429c1737/assets/template.html +0 -338
  240. package/bizar-dash/.od-skills/web-prototype-e1429c1737/example.html +0 -81
  241. package/bizar-dash/.od-skills/web-prototype-e1429c1737/open-design.json +0 -172
  242. package/bizar-dash/.od-skills/web-prototype-e1429c1737/references/checklist.md +0 -44
  243. package/bizar-dash/.od-skills/web-prototype-e1429c1737/references/layouts.md +0 -247
  244. package/bizar-dash/ARCHITECTURE.md +0 -197
  245. package/bizar-dash/BROWSER_VERIFICATION.md +0 -190
  246. package/bizar-dash/CHANGELOG.md +0 -807
  247. package/bizar-dash/CONTROL_SURFACES.md +0 -156
  248. package/bizar-dash/DESIGN.md +0 -912
  249. package/bizar-dash/GLYPH.md +0 -418
  250. package/bizar-dash/PLAN.md +0 -1135
  251. package/bizar-dash/SETTINGS_AUDIT.md +0 -225
  252. package/bizar-dash/critique.json +0 -26
  253. package/bizar-dash/dist/assets/ActivityFeed-Bw4Cai7r.js +0 -11
  254. package/bizar-dash/dist/assets/ActivityFeed-Bw4Cai7r.js.map +0 -1
  255. package/bizar-dash/dist/assets/ActivityView-BC0QQ_j9.js +0 -2
  256. package/bizar-dash/dist/assets/ActivityView-BC0QQ_j9.js.map +0 -1
  257. package/bizar-dash/dist/assets/AdminView-CLTNKFL4.js +0 -11
  258. package/bizar-dash/dist/assets/AdminView-CLTNKFL4.js.map +0 -1
  259. package/bizar-dash/dist/assets/AgentActivity-DBWDByjk.js +0 -67
  260. package/bizar-dash/dist/assets/AgentActivity-DBWDByjk.js.map +0 -1
  261. package/bizar-dash/dist/assets/AgentsView-DD6bCIbA.js +0 -15
  262. package/bizar-dash/dist/assets/AgentsView-DD6bCIbA.js.map +0 -1
  263. package/bizar-dash/dist/assets/Alert-pzFISNtS.js +0 -6
  264. package/bizar-dash/dist/assets/Alert-pzFISNtS.js.map +0 -1
  265. package/bizar-dash/dist/assets/ArtifactsView-BVU5lfo7.js +0 -21
  266. package/bizar-dash/dist/assets/ArtifactsView-BVU5lfo7.js.map +0 -1
  267. package/bizar-dash/dist/assets/AuthView-BZ-bPIOM.js +0 -16
  268. package/bizar-dash/dist/assets/AuthView-BZ-bPIOM.js.map +0 -1
  269. package/bizar-dash/dist/assets/Avatar-CRW81ewE.js +0 -6
  270. package/bizar-dash/dist/assets/Avatar-CRW81ewE.js.map +0 -1
  271. package/bizar-dash/dist/assets/BackgroundJobsView-w8OZtXyI.js +0 -1
  272. package/bizar-dash/dist/assets/BackgroundJobsView-w8OZtXyI.js.map +0 -1
  273. package/bizar-dash/dist/assets/BackupView-u_O3j0TG.js +0 -1
  274. package/bizar-dash/dist/assets/BackupView-u_O3j0TG.js.map +0 -1
  275. package/bizar-dash/dist/assets/Banner-Du-3l05O.js +0 -1
  276. package/bizar-dash/dist/assets/Banner-Du-3l05O.js.map +0 -1
  277. package/bizar-dash/dist/assets/Card-Clkvh43Q.js +0 -1
  278. package/bizar-dash/dist/assets/Card-Clkvh43Q.js.map +0 -1
  279. package/bizar-dash/dist/assets/ChatView-BTjb3mIh.js +0 -9
  280. package/bizar-dash/dist/assets/ChatView-BTjb3mIh.js.map +0 -1
  281. package/bizar-dash/dist/assets/Chip-B7ovcm01.js +0 -1
  282. package/bizar-dash/dist/assets/Chip-B7ovcm01.js.map +0 -1
  283. package/bizar-dash/dist/assets/ClaudeSessionsView-BoZS5ldi.js +0 -1
  284. package/bizar-dash/dist/assets/ClaudeSessionsView-BoZS5ldi.js.map +0 -1
  285. package/bizar-dash/dist/assets/ClipboardView-Cb6XgYZI.js +0 -1
  286. package/bizar-dash/dist/assets/ClipboardView-Cb6XgYZI.js.map +0 -1
  287. package/bizar-dash/dist/assets/ConfigView-DfTJk1uL.js +0 -1
  288. package/bizar-dash/dist/assets/ConfigView-DfTJk1uL.js.map +0 -1
  289. package/bizar-dash/dist/assets/DiagnosticsView-D5Wy9xlJ.js +0 -2
  290. package/bizar-dash/dist/assets/DiagnosticsView-D5Wy9xlJ.js.map +0 -1
  291. package/bizar-dash/dist/assets/DialogsView-HVyfrXpq.js +0 -6
  292. package/bizar-dash/dist/assets/DialogsView-HVyfrXpq.js.map +0 -1
  293. package/bizar-dash/dist/assets/DoctorView-Dm94WI2z.js +0 -6
  294. package/bizar-dash/dist/assets/DoctorView-Dm94WI2z.js.map +0 -1
  295. package/bizar-dash/dist/assets/EmptyState-ccT__JZG.js +0 -1
  296. package/bizar-dash/dist/assets/EmptyState-ccT__JZG.js.map +0 -1
  297. package/bizar-dash/dist/assets/EnvVarsView-BhcIuJ6V.js +0 -8
  298. package/bizar-dash/dist/assets/EnvVarsView-BhcIuJ6V.js.map +0 -1
  299. package/bizar-dash/dist/assets/ErrorState-BSqf65ka.js +0 -1
  300. package/bizar-dash/dist/assets/ErrorState-BSqf65ka.js.map +0 -1
  301. package/bizar-dash/dist/assets/EvalView-2cbWVBUA.js +0 -1
  302. package/bizar-dash/dist/assets/EvalView-2cbWVBUA.js.map +0 -1
  303. package/bizar-dash/dist/assets/GoalsView-C3QG5p7T.js +0 -6
  304. package/bizar-dash/dist/assets/GoalsView-C3QG5p7T.js.map +0 -1
  305. package/bizar-dash/dist/assets/Grid-Bojq8Sg7.js +0 -1
  306. package/bizar-dash/dist/assets/Grid-Bojq8Sg7.js.map +0 -1
  307. package/bizar-dash/dist/assets/HistoryView-Bum2TMMw.js +0 -1
  308. package/bizar-dash/dist/assets/HistoryView-Bum2TMMw.js.map +0 -1
  309. package/bizar-dash/dist/assets/Input-DV-vdaV8.js +0 -1
  310. package/bizar-dash/dist/assets/Input-DV-vdaV8.js.map +0 -1
  311. package/bizar-dash/dist/assets/LibrariesView-DH2E2uNb.js +0 -6
  312. package/bizar-dash/dist/assets/LibrariesView-DH2E2uNb.js.map +0 -1
  313. package/bizar-dash/dist/assets/LightRAGView-BFCcj7Tl.js +0 -6
  314. package/bizar-dash/dist/assets/LightRAGView-BFCcj7Tl.js.map +0 -1
  315. package/bizar-dash/dist/assets/ListHeader-CCD_nIHb.js +0 -1
  316. package/bizar-dash/dist/assets/ListHeader-CCD_nIHb.js.map +0 -1
  317. package/bizar-dash/dist/assets/MemoryView-Cr-obIQY.js +0 -8
  318. package/bizar-dash/dist/assets/MemoryView-Cr-obIQY.js.map +0 -1
  319. package/bizar-dash/dist/assets/MessageBubble-CrCJ0SYG.js +0 -29
  320. package/bizar-dash/dist/assets/MessageBubble-CrCJ0SYG.js.map +0 -1
  321. package/bizar-dash/dist/assets/MiscView-CDUQpYuV.js +0 -1
  322. package/bizar-dash/dist/assets/MiscView-CDUQpYuV.js.map +0 -1
  323. package/bizar-dash/dist/assets/ModsView-B6OR-a4B.js +0 -6
  324. package/bizar-dash/dist/assets/ModsView-B6OR-a4B.js.map +0 -1
  325. package/bizar-dash/dist/assets/NotificationsView-CIqtY6D0.js +0 -6
  326. package/bizar-dash/dist/assets/NotificationsView-CIqtY6D0.js.map +0 -1
  327. package/bizar-dash/dist/assets/ObsidianView-Nahah8QC.js +0 -1
  328. package/bizar-dash/dist/assets/ObsidianView-Nahah8QC.js.map +0 -1
  329. package/bizar-dash/dist/assets/OverviewView-B6L_wa4M.js +0 -1
  330. package/bizar-dash/dist/assets/OverviewView-B6L_wa4M.js.map +0 -1
  331. package/bizar-dash/dist/assets/ProgressBar-KC4VXsl_.js +0 -1
  332. package/bizar-dash/dist/assets/ProgressBar-KC4VXsl_.js.map +0 -1
  333. package/bizar-dash/dist/assets/ProjectsView-DgxWiqHb.js +0 -11
  334. package/bizar-dash/dist/assets/ProjectsView-DgxWiqHb.js.map +0 -1
  335. package/bizar-dash/dist/assets/ProvidersView-BOqN9omv.js +0 -11
  336. package/bizar-dash/dist/assets/ProvidersView-BOqN9omv.js.map +0 -1
  337. package/bizar-dash/dist/assets/SchedulesView-yMaG3Dtd.js +0 -1
  338. package/bizar-dash/dist/assets/SchedulesView-yMaG3Dtd.js.map +0 -1
  339. package/bizar-dash/dist/assets/Select-Bf_P0S6H.js +0 -6
  340. package/bizar-dash/dist/assets/Select-Bf_P0S6H.js.map +0 -1
  341. package/bizar-dash/dist/assets/SettingsRow-CJb-F94Y.js +0 -1
  342. package/bizar-dash/dist/assets/SettingsRow-CJb-F94Y.js.map +0 -1
  343. package/bizar-dash/dist/assets/SettingsView-BvOGcgMS.js +0 -18
  344. package/bizar-dash/dist/assets/SettingsView-BvOGcgMS.js.map +0 -1
  345. package/bizar-dash/dist/assets/Sheet-BwMVGN-8.js +0 -1
  346. package/bizar-dash/dist/assets/Sheet-BwMVGN-8.js.map +0 -1
  347. package/bizar-dash/dist/assets/Slider-AjjzqI-5.js +0 -1
  348. package/bizar-dash/dist/assets/Slider-AjjzqI-5.js.map +0 -1
  349. package/bizar-dash/dist/assets/Sparkline-Z4y0YUZS.js +0 -1
  350. package/bizar-dash/dist/assets/Sparkline-Z4y0YUZS.js.map +0 -1
  351. package/bizar-dash/dist/assets/Switch-B79a2t-G.js +0 -10
  352. package/bizar-dash/dist/assets/Switch-B79a2t-G.js.map +0 -1
  353. package/bizar-dash/dist/assets/TasksView-G-s_pwwr.js +0 -6
  354. package/bizar-dash/dist/assets/TasksView-G-s_pwwr.js.map +0 -1
  355. package/bizar-dash/dist/assets/Textarea-BfXGMXQ7.js +0 -1
  356. package/bizar-dash/dist/assets/Textarea-BfXGMXQ7.js.map +0 -1
  357. package/bizar-dash/dist/assets/UpdateView-BKx3u9X8.js +0 -1
  358. package/bizar-dash/dist/assets/UpdateView-BKx3u9X8.js.map +0 -1
  359. package/bizar-dash/dist/assets/UsageView-M01G6bZQ.js +0 -1
  360. package/bizar-dash/dist/assets/UsageView-M01G6bZQ.js.map +0 -1
  361. package/bizar-dash/dist/assets/ViewHeader-BsnaspKw.js +0 -6
  362. package/bizar-dash/dist/assets/ViewHeader-BsnaspKw.js.map +0 -1
  363. package/bizar-dash/dist/assets/VoiceView-rWE8vziD.js +0 -1
  364. package/bizar-dash/dist/assets/VoiceView-rWE8vziD.js.map +0 -1
  365. package/bizar-dash/dist/assets/brain-D7k5s8vH.js +0 -6
  366. package/bizar-dash/dist/assets/brain-D7k5s8vH.js.map +0 -1
  367. package/bizar-dash/dist/assets/calendar-CAZoDd-l.js +0 -6
  368. package/bizar-dash/dist/assets/calendar-CAZoDd-l.js.map +0 -1
  369. package/bizar-dash/dist/assets/circle-D99cZJBr.js +0 -6
  370. package/bizar-dash/dist/assets/circle-D99cZJBr.js.map +0 -1
  371. package/bizar-dash/dist/assets/circle-alert-QQTU4Quz.js +0 -6
  372. package/bizar-dash/dist/assets/circle-alert-QQTU4Quz.js.map +0 -1
  373. package/bizar-dash/dist/assets/circle-check-BMbe7Kkr.js +0 -6
  374. package/bizar-dash/dist/assets/circle-check-BMbe7Kkr.js.map +0 -1
  375. package/bizar-dash/dist/assets/clock-OtT5mvF3.js +0 -6
  376. package/bizar-dash/dist/assets/clock-OtT5mvF3.js.map +0 -1
  377. package/bizar-dash/dist/assets/cmdk-C3F6LD6N.js +0 -80
  378. package/bizar-dash/dist/assets/cmdk-C3F6LD6N.js.map +0 -1
  379. package/bizar-dash/dist/assets/dnd-kit-BAOLODOI.js +0 -5
  380. package/bizar-dash/dist/assets/dnd-kit-BAOLODOI.js.map +0 -1
  381. package/bizar-dash/dist/assets/download-B-mPX5P_.js +0 -6
  382. package/bizar-dash/dist/assets/download-B-mPX5P_.js.map +0 -1
  383. package/bizar-dash/dist/assets/external-link-DPDQM1YO.js +0 -6
  384. package/bizar-dash/dist/assets/external-link-DPDQM1YO.js.map +0 -1
  385. package/bizar-dash/dist/assets/file-text-BsCrv5aP.js +0 -6
  386. package/bizar-dash/dist/assets/file-text-BsCrv5aP.js.map +0 -1
  387. package/bizar-dash/dist/assets/filter-Do63x17E.js +0 -6
  388. package/bizar-dash/dist/assets/filter-Do63x17E.js.map +0 -1
  389. package/bizar-dash/dist/assets/git-pull-request-CpRAmERC.js +0 -11
  390. package/bizar-dash/dist/assets/git-pull-request-CpRAmERC.js.map +0 -1
  391. package/bizar-dash/dist/assets/index-B1OIS316.js +0 -1
  392. package/bizar-dash/dist/assets/index-B1OIS316.js.map +0 -1
  393. package/bizar-dash/dist/assets/index-CR0TeJmc.js +0 -1
  394. package/bizar-dash/dist/assets/index-CR0TeJmc.js.map +0 -1
  395. package/bizar-dash/dist/assets/main-BRmJZcHt.js +0 -277
  396. package/bizar-dash/dist/assets/main-BRmJZcHt.js.map +0 -1
  397. package/bizar-dash/dist/assets/main-D0TY0myE.css +0 -1
  398. package/bizar-dash/dist/assets/network-DJnHwHX3.js +0 -6
  399. package/bizar-dash/dist/assets/network-DJnHwHX3.js.map +0 -1
  400. package/bizar-dash/dist/assets/pause-HitDUnk_.js +0 -6
  401. package/bizar-dash/dist/assets/pause-HitDUnk_.js.map +0 -1
  402. package/bizar-dash/dist/assets/pencil-Dkks7eIC.js +0 -6
  403. package/bizar-dash/dist/assets/pencil-Dkks7eIC.js.map +0 -1
  404. package/bizar-dash/dist/assets/play-blqh0msT.js +0 -6
  405. package/bizar-dash/dist/assets/play-blqh0msT.js.map +0 -1
  406. package/bizar-dash/dist/assets/plug-DVvSSe40.js +0 -6
  407. package/bizar-dash/dist/assets/plug-DVvSSe40.js.map +0 -1
  408. package/bizar-dash/dist/assets/plus-Cps-8lcG.js +0 -6
  409. package/bizar-dash/dist/assets/plus-Cps-8lcG.js.map +0 -1
  410. package/bizar-dash/dist/assets/power-DEX4_Xoa.js +0 -6
  411. package/bizar-dash/dist/assets/power-DEX4_Xoa.js.map +0 -1
  412. package/bizar-dash/dist/assets/power-off-upZ4xjaG.js +0 -6
  413. package/bizar-dash/dist/assets/power-off-upZ4xjaG.js.map +0 -1
  414. package/bizar-dash/dist/assets/refresh-ccw-DN_zCB1h.js +0 -6
  415. package/bizar-dash/dist/assets/refresh-ccw-DN_zCB1h.js.map +0 -1
  416. package/bizar-dash/dist/assets/refresh-cw-KGm5eQgv.js +0 -6
  417. package/bizar-dash/dist/assets/refresh-cw-KGm5eQgv.js.map +0 -1
  418. package/bizar-dash/dist/assets/rotate-ccw-D5fkDRgk.js +0 -6
  419. package/bizar-dash/dist/assets/rotate-ccw-D5fkDRgk.js.map +0 -1
  420. package/bizar-dash/dist/assets/rotate-cw-CClSVUaX.js +0 -6
  421. package/bizar-dash/dist/assets/rotate-cw-CClSVUaX.js.map +0 -1
  422. package/bizar-dash/dist/assets/save-BY4ViPku.js +0 -6
  423. package/bizar-dash/dist/assets/save-BY4ViPku.js.map +0 -1
  424. package/bizar-dash/dist/assets/send-CaOL1yQD.js +0 -6
  425. package/bizar-dash/dist/assets/send-CaOL1yQD.js.map +0 -1
  426. package/bizar-dash/dist/assets/shield-alert-DA9K6uZO.js +0 -6
  427. package/bizar-dash/dist/assets/shield-alert-DA9K6uZO.js.map +0 -1
  428. package/bizar-dash/dist/assets/trash-2-B_Y6HrfN.js +0 -6
  429. package/bizar-dash/dist/assets/trash-2-B_Y6HrfN.js.map +0 -1
  430. package/bizar-dash/dist/assets/trending-up-qKTUQKIT.js +0 -6
  431. package/bizar-dash/dist/assets/trending-up-qKTUQKIT.js.map +0 -1
  432. package/bizar-dash/dist/assets/triangle-alert-BeY8LAv6.js +0 -6
  433. package/bizar-dash/dist/assets/triangle-alert-BeY8LAv6.js.map +0 -1
  434. package/bizar-dash/dist/assets/upload-BZCtmqIh.js +0 -6
  435. package/bizar-dash/dist/assets/upload-BZCtmqIh.js.map +0 -1
  436. package/bizar-dash/dist/assets/zap-Bz1Z8y1Q.js +0 -6
  437. package/bizar-dash/dist/assets/zap-Bz1Z8y1Q.js.map +0 -1
  438. package/bizar-dash/dist/index.html +0 -19
  439. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  440. package/bizar-dash/plans/debug-plan/comments.json +0 -1
  441. package/bizar-dash/plans/debug-plan/meta.json +0 -10
  442. package/bizar-dash/plans/debug-plan/plan.json +0 -174
  443. package/bizar-dash/scripts/smoke-bg-retry.mjs +0 -246
  444. package/bizar-dash/skills/agent-baseline/SKILL.md +0 -80
  445. package/bizar-dash/skills/agent-browser/SKILL.md +0 -181
  446. package/bizar-dash/skills/bizar/SKILL.md +0 -116
  447. package/bizar-dash/skills/chat/SKILL.md +0 -74
  448. package/bizar-dash/skills/eval/SKILL.md +0 -237
  449. package/bizar-dash/skills/lightrag/SKILL.md +0 -86
  450. package/bizar-dash/skills/minimax/SKILL.md +0 -80
  451. package/bizar-dash/skills/obsidian/SKILL.md +0 -68
  452. package/bizar-dash/skills/providers/SKILL.md +0 -75
  453. package/bizar-dash/skills/publishing/SKILL.md +0 -147
  454. package/bizar-dash/skills/sdk/SKILL.md +0 -138
  455. package/bizar-dash/skills/self-improvement/SKILL.md +0 -53
  456. package/bizar-dash/skills/skills-cli/SKILL.md +0 -94
  457. package/bizar-dash/skills/usage/SKILL.md +0 -62
  458. package/bizar-dash/src/cli/dashboard-ports.mjs +0 -488
  459. package/bizar-dash/src/cli.mjs +0 -537
  460. package/bizar-dash/src/server/activity-log.mjs +0 -174
  461. package/bizar-dash/src/server/agent-bus.mjs +0 -485
  462. package/bizar-dash/src/server/agents-store.mjs +0 -466
  463. package/bizar-dash/src/server/api.mjs +0 -246
  464. package/bizar-dash/src/server/artifact-mint.mjs +0 -69
  465. package/bizar-dash/src/server/artifact-template.html +0 -810
  466. package/bizar-dash/src/server/artifacts-store.mjs +0 -839
  467. package/bizar-dash/src/server/auth.mjs +0 -548
  468. package/bizar-dash/src/server/background-store.mjs +0 -446
  469. package/bizar-dash/src/server/bg-poller.mjs +0 -375
  470. package/bizar-dash/src/server/bg-poller.test.mjs +0 -28
  471. package/bizar-dash/src/server/bg-retry.mjs +0 -446
  472. package/bizar-dash/src/server/bg-spawner.mjs +0 -390
  473. package/bizar-dash/src/server/browser.mjs +0 -40
  474. package/bizar-dash/src/server/claude-artifacts.mjs +0 -281
  475. package/bizar-dash/src/server/claude-bg-spawner.mjs +0 -390
  476. package/bizar-dash/src/server/claude-info.mjs +0 -412
  477. package/bizar-dash/src/server/claude-runner.mjs +0 -289
  478. package/bizar-dash/src/server/claude-sdk.mjs +0 -191
  479. package/bizar-dash/src/server/diagnostics-store.mjs +0 -568
  480. package/bizar-dash/src/server/dialog-poller.mjs +0 -105
  481. package/bizar-dash/src/server/dialog-store.mjs +0 -259
  482. package/bizar-dash/src/server/digest-store.mjs +0 -558
  483. package/bizar-dash/src/server/eval-store.mjs +0 -281
  484. package/bizar-dash/src/server/eval.mjs +0 -500
  485. package/bizar-dash/src/server/glyphs/mdx-compiler.mjs +0 -596
  486. package/bizar-dash/src/server/lib/path-safe.mjs +0 -283
  487. package/bizar-dash/src/server/lib/rate-limit.mjs +0 -122
  488. package/bizar-dash/src/server/logger.mjs +0 -71
  489. package/bizar-dash/src/server/loop-runtime.mjs +0 -429
  490. package/bizar-dash/src/server/memory-consolidator.mjs +0 -225
  491. package/bizar-dash/src/server/memory-distillation.mjs +0 -296
  492. package/bizar-dash/src/server/memory-git.mjs +0 -473
  493. package/bizar-dash/src/server/memory-lightrag.mjs +0 -1483
  494. package/bizar-dash/src/server/memory-obsidian.mjs +0 -230
  495. package/bizar-dash/src/server/memory-schema.mjs +0 -145
  496. package/bizar-dash/src/server/memory-secrets.mjs +0 -84
  497. package/bizar-dash/src/server/memory-store.mjs +0 -1093
  498. package/bizar-dash/src/server/metrics.mjs +0 -268
  499. package/bizar-dash/src/server/minimax-usage-store.mjs +0 -372
  500. package/bizar-dash/src/server/minimax.mjs +0 -683
  501. package/bizar-dash/src/server/mod-security.mjs +0 -397
  502. package/bizar-dash/src/server/mods-loader.mjs +0 -1243
  503. package/bizar-dash/src/server/notifications-store.mjs +0 -243
  504. package/bizar-dash/src/server/obsidian-store.mjs +0 -318
  505. package/bizar-dash/src/server/ocr.mjs +0 -55
  506. package/bizar-dash/src/server/otel.mjs +0 -260
  507. package/bizar-dash/src/server/pair-store.mjs +0 -138
  508. package/bizar-dash/src/server/progress-parser.test.mjs +0 -129
  509. package/bizar-dash/src/server/projects-store.mjs +0 -364
  510. package/bizar-dash/src/server/providers-store.mjs +0 -1801
  511. package/bizar-dash/src/server/routes/_shared.mjs +0 -430
  512. package/bizar-dash/src/server/routes/activity.mjs +0 -215
  513. package/bizar-dash/src/server/routes/admin.mjs +0 -163
  514. package/bizar-dash/src/server/routes/agent-bus.mjs +0 -185
  515. package/bizar-dash/src/server/routes/agents-cc.mjs +0 -551
  516. package/bizar-dash/src/server/routes/agents-cc.test.mjs +0 -35
  517. package/bizar-dash/src/server/routes/agents.mjs +0 -149
  518. package/bizar-dash/src/server/routes/artifacts.mjs +0 -315
  519. package/bizar-dash/src/server/routes/audit.mjs +0 -79
  520. package/bizar-dash/src/server/routes/auth.mjs +0 -69
  521. package/bizar-dash/src/server/routes/background.mjs +0 -422
  522. package/bizar-dash/src/server/routes/backup.mjs +0 -112
  523. package/bizar-dash/src/server/routes/chat.mjs +0 -727
  524. package/bizar-dash/src/server/routes/claude-session-detail.mjs +0 -367
  525. package/bizar-dash/src/server/routes/claude-sessions.mjs +0 -259
  526. package/bizar-dash/src/server/routes/clipboard.mjs +0 -173
  527. package/bizar-dash/src/server/routes/config.mjs +0 -180
  528. package/bizar-dash/src/server/routes/decisions.mjs +0 -78
  529. package/bizar-dash/src/server/routes/diagnostics.mjs +0 -50
  530. package/bizar-dash/src/server/routes/dialogs.mjs +0 -134
  531. package/bizar-dash/src/server/routes/digests.mjs +0 -82
  532. package/bizar-dash/src/server/routes/distill.mjs +0 -129
  533. package/bizar-dash/src/server/routes/doctor.mjs +0 -71
  534. package/bizar-dash/src/server/routes/env-vars.mjs +0 -231
  535. package/bizar-dash/src/server/routes/eval.mjs +0 -187
  536. package/bizar-dash/src/server/routes/fs.mjs +0 -429
  537. package/bizar-dash/src/server/routes/goal-planner.mjs +0 -236
  538. package/bizar-dash/src/server/routes/goals.mjs +0 -428
  539. package/bizar-dash/src/server/routes/history.mjs +0 -78
  540. package/bizar-dash/src/server/routes/lightrag.mjs +0 -169
  541. package/bizar-dash/src/server/routes/loops.mjs +0 -138
  542. package/bizar-dash/src/server/routes/memory.mjs +0 -1524
  543. package/bizar-dash/src/server/routes/minimax.mjs +0 -133
  544. package/bizar-dash/src/server/routes/misc.mjs +0 -90
  545. package/bizar-dash/src/server/routes/model-router.mjs +0 -158
  546. package/bizar-dash/src/server/routes/model-router.test.mjs +0 -104
  547. package/bizar-dash/src/server/routes/mods.mjs +0 -359
  548. package/bizar-dash/src/server/routes/notifications.mjs +0 -54
  549. package/bizar-dash/src/server/routes/obsidian.mjs +0 -239
  550. package/bizar-dash/src/server/routes/ocr.mjs +0 -182
  551. package/bizar-dash/src/server/routes/overview.mjs +0 -219
  552. package/bizar-dash/src/server/routes/overview.test.mjs +0 -65
  553. package/bizar-dash/src/server/routes/pair.mjs +0 -67
  554. package/bizar-dash/src/server/routes/projects.mjs +0 -175
  555. package/bizar-dash/src/server/routes/providers.mjs +0 -361
  556. package/bizar-dash/src/server/routes/schedules.mjs +0 -159
  557. package/bizar-dash/src/server/routes/sessions.mjs +0 -80
  558. package/bizar-dash/src/server/routes/settings.mjs +0 -51
  559. package/bizar-dash/src/server/routes/skills.mjs +0 -70
  560. package/bizar-dash/src/server/routes/spawn.mjs +0 -59
  561. package/bizar-dash/src/server/routes/tailscale.mjs +0 -46
  562. package/bizar-dash/src/server/routes/tasks.mjs +0 -579
  563. package/bizar-dash/src/server/routes/themes.mjs +0 -51
  564. package/bizar-dash/src/server/routes/update.mjs +0 -367
  565. package/bizar-dash/src/server/routes/usage.mjs +0 -136
  566. package/bizar-dash/src/server/routes/users.mjs +0 -102
  567. package/bizar-dash/src/server/routes/voice.mjs +0 -167
  568. package/bizar-dash/src/server/routes/workspaces.mjs +0 -299
  569. package/bizar-dash/src/server/routes-v2/auth.mjs +0 -59
  570. package/bizar-dash/src/server/routes-v2/events.mjs +0 -121
  571. package/bizar-dash/src/server/routes-v2/health.mjs +0 -21
  572. package/bizar-dash/src/server/routes-v2/index.mjs +0 -90
  573. package/bizar-dash/src/server/routes-v2/sessions.mjs +0 -111
  574. package/bizar-dash/src/server/schedules-runner.mjs +0 -556
  575. package/bizar-dash/src/server/schedules-store.mjs +0 -268
  576. package/bizar-dash/src/server/search-store.mjs +0 -220
  577. package/bizar-dash/src/server/serve-info.mjs +0 -1110
  578. package/bizar-dash/src/server/server.mjs +0 -951
  579. package/bizar-dash/src/server/settings-store.mjs +0 -45
  580. package/bizar-dash/src/server/skills-store.mjs +0 -192
  581. package/bizar-dash/src/server/sprint.test.mjs +0 -163
  582. package/bizar-dash/src/server/state.mjs +0 -437
  583. package/bizar-dash/src/server/tailscale-store.mjs +0 -113
  584. package/bizar-dash/src/server/task-delegator.mjs +0 -969
  585. package/bizar-dash/src/server/task-splitter.mjs +0 -241
  586. package/bizar-dash/src/server/tasks-store.mjs +0 -556
  587. package/bizar-dash/src/server/tui.mjs +0 -923
  588. package/bizar-dash/src/server/update-store.mjs +0 -168
  589. package/bizar-dash/src/server/v2-auth-file.mjs +0 -99
  590. package/bizar-dash/src/server/v2-event-bus.mjs +0 -128
  591. package/bizar-dash/src/server/voice-store.mjs +0 -229
  592. package/bizar-dash/src/server/voice-transcribe.mjs +0 -72
  593. package/bizar-dash/src/server/watcher.mjs +0 -81
  594. package/bizar-dash/src/server/workers/transcription-worker.mjs +0 -213
  595. package/bizar-dash/src/server/workspaces.mjs +0 -626
  596. package/bizar-dash/src/server/yaml.mjs +0 -238
  597. package/bizar-dash/src/web/index.html +0 -17
  598. package/bizar-dash/src/web/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  599. package/bizar-dash/src/web/v8/App.tsx +0 -426
  600. package/bizar-dash/src/web/v8/__tests__/App.test.tsx +0 -102
  601. package/bizar-dash/src/web/v8/__tests__/AppShell.test.tsx +0 -79
  602. package/bizar-dash/src/web/v8/__tests__/BackgroundJobsView.test.tsx +0 -172
  603. package/bizar-dash/src/web/v8/__tests__/MemoryView.test.tsx +0 -91
  604. package/bizar-dash/src/web/v8/__tests__/NotificationsPopover.test.tsx +0 -91
  605. package/bizar-dash/src/web/v8/__tests__/PageSkeleton.test.tsx +0 -12
  606. package/bizar-dash/src/web/v8/__tests__/SchedulesView.test.tsx +0 -157
  607. package/bizar-dash/src/web/v8/__tests__/SettingsView.test.tsx +0 -84
  608. package/bizar-dash/src/web/v8/__tests__/Sidebar.test.tsx +0 -95
  609. package/bizar-dash/src/web/v8/__tests__/Topbar.test.tsx +0 -86
  610. package/bizar-dash/src/web/v8/__tests__/activity.test.tsx +0 -63
  611. package/bizar-dash/src/web/v8/__tests__/admin-view.test.tsx +0 -105
  612. package/bizar-dash/src/web/v8/__tests__/agent-card-metrics.test.tsx +0 -54
  613. package/bizar-dash/src/web/v8/__tests__/agent-detail-tasks.test.tsx +0 -68
  614. package/bizar-dash/src/web/v8/__tests__/agent-hierarchy.test.tsx +0 -79
  615. package/bizar-dash/src/web/v8/__tests__/agents-stuck-banner.test.tsx +0 -142
  616. package/bizar-dash/src/web/v8/__tests__/agents.test.tsx +0 -90
  617. package/bizar-dash/src/web/v8/__tests__/artifacts-view.test.tsx +0 -102
  618. package/bizar-dash/src/web/v8/__tests__/audit-fixes.test.tsx +0 -139
  619. package/bizar-dash/src/web/v8/__tests__/auth-view.test.tsx +0 -94
  620. package/bizar-dash/src/web/v8/__tests__/changelog.test.tsx +0 -79
  621. package/bizar-dash/src/web/v8/__tests__/chat-types.test.ts +0 -55
  622. package/bizar-dash/src/web/v8/__tests__/chat-view.test.tsx +0 -142
  623. package/bizar-dash/src/web/v8/__tests__/claude-sessions-view.test.tsx +0 -159
  624. package/bizar-dash/src/web/v8/__tests__/clipboard-view.test.tsx +0 -90
  625. package/bizar-dash/src/web/v8/__tests__/config-view.test.tsx +0 -111
  626. package/bizar-dash/src/web/v8/__tests__/control-plane.test.tsx +0 -104
  627. package/bizar-dash/src/web/v8/__tests__/controls.test.tsx +0 -135
  628. package/bizar-dash/src/web/v8/__tests__/cx.test.ts +0 -25
  629. package/bizar-dash/src/web/v8/__tests__/data.test.tsx +0 -293
  630. package/bizar-dash/src/web/v8/__tests__/dialogs-view.test.tsx +0 -81
  631. package/bizar-dash/src/web/v8/__tests__/env-vars-view.test.tsx +0 -123
  632. package/bizar-dash/src/web/v8/__tests__/feedback.test.tsx +0 -213
  633. package/bizar-dash/src/web/v8/__tests__/goals-cc-roundtrip.test.tsx +0 -94
  634. package/bizar-dash/src/web/v8/__tests__/goals.test.tsx +0 -105
  635. package/bizar-dash/src/web/v8/__tests__/history-view.test.tsx +0 -96
  636. package/bizar-dash/src/web/v8/__tests__/kanban.test.tsx +0 -176
  637. package/bizar-dash/src/web/v8/__tests__/libraries.test.tsx +0 -70
  638. package/bizar-dash/src/web/v8/__tests__/lightrag-view.test.tsx +0 -90
  639. package/bizar-dash/src/web/v8/__tests__/memory.test.tsx +0 -60
  640. package/bizar-dash/src/web/v8/__tests__/misc-view.test.tsx +0 -79
  641. package/bizar-dash/src/web/v8/__tests__/mods-view.test.tsx +0 -123
  642. package/bizar-dash/src/web/v8/__tests__/navigation.test.tsx +0 -137
  643. package/bizar-dash/src/web/v8/__tests__/new-views.test.tsx +0 -160
  644. package/bizar-dash/src/web/v8/__tests__/obsidian-view.test.tsx +0 -82
  645. package/bizar-dash/src/web/v8/__tests__/overview-trends.test.tsx +0 -87
  646. package/bizar-dash/src/web/v8/__tests__/projects-view.test.tsx +0 -134
  647. package/bizar-dash/src/web/v8/__tests__/providers-view.test.tsx +0 -100
  648. package/bizar-dash/src/web/v8/__tests__/settings.test.tsx +0 -104
  649. package/bizar-dash/src/web/v8/__tests__/theme.test.tsx +0 -104
  650. package/bizar-dash/src/web/v8/__tests__/update-view.test.tsx +0 -89
  651. package/bizar-dash/src/web/v8/__tests__/useFetch.test.ts +0 -92
  652. package/bizar-dash/src/web/v8/__tests__/useWebSocket.test.ts +0 -87
  653. package/bizar-dash/src/web/v8/__tests__/views.test.tsx +0 -188
  654. package/bizar-dash/src/web/v8/__tests__/voice-view.test.tsx +0 -80
  655. package/bizar-dash/src/web/v8/data/fetcher.ts +0 -59
  656. package/bizar-dash/src/web/v8/data/types.ts +0 -239
  657. package/bizar-dash/src/web/v8/data/useFetch.ts +0 -62
  658. package/bizar-dash/src/web/v8/data/useViewNavigate.ts +0 -54
  659. package/bizar-dash/src/web/v8/data/useWebSocket.ts +0 -206
  660. package/bizar-dash/src/web/v8/main.tsx +0 -28
  661. package/bizar-dash/src/web/v8/shell/AppShell.tsx +0 -126
  662. package/bizar-dash/src/web/v8/shell/PageSkeleton.tsx +0 -29
  663. package/bizar-dash/src/web/v8/shell/Sidebar.tsx +0 -515
  664. package/bizar-dash/src/web/v8/shell/StatusBar.tsx +0 -63
  665. package/bizar-dash/src/web/v8/shell/Topbar.tsx +0 -330
  666. package/bizar-dash/src/web/v8/shell/index.ts +0 -15
  667. package/bizar-dash/src/web/v8/ui/activity/ActivityFeed.tsx +0 -137
  668. package/bizar-dash/src/web/v8/ui/activity/ActivityLane.tsx +0 -315
  669. package/bizar-dash/src/web/v8/ui/activity/ActivityLanes.tsx +0 -51
  670. package/bizar-dash/src/web/v8/ui/agents/AgentActivity.tsx +0 -101
  671. package/bizar-dash/src/web/v8/ui/agents/AgentCard.tsx +0 -239
  672. package/bizar-dash/src/web/v8/ui/agents/AgentDetail.tsx +0 -439
  673. package/bizar-dash/src/web/v8/ui/agents/AgentLiveOutput.tsx +0 -252
  674. package/bizar-dash/src/web/v8/ui/agents/AgentStreamPanel.tsx +0 -371
  675. package/bizar-dash/src/web/v8/ui/chat/ChatDrawer.tsx +0 -208
  676. package/bizar-dash/src/web/v8/ui/chat/EventStream.tsx +0 -83
  677. package/bizar-dash/src/web/v8/ui/chat/MessageBubble.tsx +0 -174
  678. package/bizar-dash/src/web/v8/ui/controls/Button.tsx +0 -141
  679. package/bizar-dash/src/web/v8/ui/controls/ButtonGroup.tsx +0 -32
  680. package/bizar-dash/src/web/v8/ui/controls/Checkbox.tsx +0 -49
  681. package/bizar-dash/src/web/v8/ui/controls/Field.tsx +0 -78
  682. package/bizar-dash/src/web/v8/ui/controls/Form.tsx +0 -23
  683. package/bizar-dash/src/web/v8/ui/controls/IconButton.tsx +0 -112
  684. package/bizar-dash/src/web/v8/ui/controls/Input.tsx +0 -94
  685. package/bizar-dash/src/web/v8/ui/controls/RadioGroup.tsx +0 -74
  686. package/bizar-dash/src/web/v8/ui/controls/Select.tsx +0 -171
  687. package/bizar-dash/src/web/v8/ui/controls/Slider.tsx +0 -68
  688. package/bizar-dash/src/web/v8/ui/controls/Switch.tsx +0 -56
  689. package/bizar-dash/src/web/v8/ui/controls/Textarea.tsx +0 -64
  690. package/bizar-dash/src/web/v8/ui/controls/Toggle.tsx +0 -36
  691. package/bizar-dash/src/web/v8/ui/controls/ToggleGroup.tsx +0 -85
  692. package/bizar-dash/src/web/v8/ui/data/Accordion.tsx +0 -134
  693. package/bizar-dash/src/web/v8/ui/data/Avatar.tsx +0 -175
  694. package/bizar-dash/src/web/v8/ui/data/Badge.tsx +0 -93
  695. package/bizar-dash/src/web/v8/ui/data/BarList.tsx +0 -101
  696. package/bizar-dash/src/web/v8/ui/data/Card.tsx +0 -140
  697. package/bizar-dash/src/web/v8/ui/data/Chip.tsx +0 -96
  698. package/bizar-dash/src/web/v8/ui/data/Kbd.tsx +0 -42
  699. package/bizar-dash/src/web/v8/ui/data/ListHeader.tsx +0 -94
  700. package/bizar-dash/src/web/v8/ui/data/ProgressBar.tsx +0 -93
  701. package/bizar-dash/src/web/v8/ui/data/Sparkline.tsx +0 -98
  702. package/bizar-dash/src/web/v8/ui/data/StatTile.tsx +0 -150
  703. package/bizar-dash/src/web/v8/ui/data/Table.tsx +0 -170
  704. package/bizar-dash/src/web/v8/ui/data/Timeline.tsx +0 -130
  705. package/bizar-dash/src/web/v8/ui/data/ViewHeader.tsx +0 -122
  706. package/bizar-dash/src/web/v8/ui/feedback/Alert.tsx +0 -95
  707. package/bizar-dash/src/web/v8/ui/feedback/Banner.tsx +0 -39
  708. package/bizar-dash/src/web/v8/ui/feedback/ContextMenu.tsx +0 -143
  709. package/bizar-dash/src/web/v8/ui/feedback/Dialog.tsx +0 -149
  710. package/bizar-dash/src/web/v8/ui/feedback/Drawer.tsx +0 -8
  711. package/bizar-dash/src/web/v8/ui/feedback/DropdownMenu.tsx +0 -231
  712. package/bizar-dash/src/web/v8/ui/feedback/EmptyState.tsx +0 -48
  713. package/bizar-dash/src/web/v8/ui/feedback/ErrorState.tsx +0 -116
  714. package/bizar-dash/src/web/v8/ui/feedback/NotificationsPopover.tsx +0 -242
  715. package/bizar-dash/src/web/v8/ui/feedback/Popover.tsx +0 -56
  716. package/bizar-dash/src/web/v8/ui/feedback/Sheet.tsx +0 -103
  717. package/bizar-dash/src/web/v8/ui/feedback/Skeleton.tsx +0 -67
  718. package/bizar-dash/src/web/v8/ui/feedback/Spinner.tsx +0 -30
  719. package/bizar-dash/src/web/v8/ui/feedback/Toast.tsx +0 -66
  720. package/bizar-dash/src/web/v8/ui/feedback/Tooltip.tsx +0 -89
  721. package/bizar-dash/src/web/v8/ui/feedback/useFocusTrap.ts +0 -58
  722. package/bizar-dash/src/web/v8/ui/goals/GoalCard.tsx +0 -176
  723. package/bizar-dash/src/web/v8/ui/goals/GoalDetail.tsx +0 -278
  724. package/bizar-dash/src/web/v8/ui/goals/KeyResult.tsx +0 -115
  725. package/bizar-dash/src/web/v8/ui/index.ts +0 -369
  726. package/bizar-dash/src/web/v8/ui/kanban/KanbanBoard.tsx +0 -117
  727. package/bizar-dash/src/web/v8/ui/kanban/KanbanCard.tsx +0 -391
  728. package/bizar-dash/src/web/v8/ui/kanban/KanbanCardBadges.tsx +0 -111
  729. package/bizar-dash/src/web/v8/ui/kanban/KanbanColumn.tsx +0 -181
  730. package/bizar-dash/src/web/v8/ui/kanban/KanbanContextMenu.tsx +0 -126
  731. package/bizar-dash/src/web/v8/ui/kanban/KanbanDetailDialog.tsx +0 -638
  732. package/bizar-dash/src/web/v8/ui/kanban/KanbanEmptyColumn.tsx +0 -43
  733. package/bizar-dash/src/web/v8/ui/kanban/KanbanProgress.tsx +0 -130
  734. package/bizar-dash/src/web/v8/ui/kanban/KanbanQuickAdd.tsx +0 -141
  735. package/bizar-dash/src/web/v8/ui/kanban/KanbanToolbar.tsx +0 -291
  736. package/bizar-dash/src/web/v8/ui/kanban/useKanbanSelection.ts +0 -90
  737. package/bizar-dash/src/web/v8/ui/libraries/LibraryGrid.tsx +0 -30
  738. package/bizar-dash/src/web/v8/ui/libraries/LibraryItem.tsx +0 -161
  739. package/bizar-dash/src/web/v8/ui/memory/MemoryVault.tsx +0 -144
  740. package/bizar-dash/src/web/v8/ui/navigation/CommandPalette.tsx +0 -225
  741. package/bizar-dash/src/web/v8/ui/navigation/NavLink.tsx +0 -81
  742. package/bizar-dash/src/web/v8/ui/navigation/Pagination.tsx +0 -164
  743. package/bizar-dash/src/web/v8/ui/navigation/Tabs.tsx +0 -128
  744. package/bizar-dash/src/web/v8/ui/navigation/useCommandPaletteHotkey.test.ts +0 -79
  745. package/bizar-dash/src/web/v8/ui/primitives/Box.tsx +0 -144
  746. package/bizar-dash/src/web/v8/ui/primitives/Center.tsx +0 -40
  747. package/bizar-dash/src/web/v8/ui/primitives/Cluster.tsx +0 -67
  748. package/bizar-dash/src/web/v8/ui/primitives/Grid.tsx +0 -104
  749. package/bizar-dash/src/web/v8/ui/primitives/Inline.tsx +0 -77
  750. package/bizar-dash/src/web/v8/ui/primitives/Portal.tsx +0 -33
  751. package/bizar-dash/src/web/v8/ui/primitives/ScrollArea.tsx +0 -62
  752. package/bizar-dash/src/web/v8/ui/primitives/Separator.tsx +0 -42
  753. package/bizar-dash/src/web/v8/ui/primitives/Stack.tsx +0 -73
  754. package/bizar-dash/src/web/v8/ui/primitives/VisuallyHidden.tsx +0 -49
  755. package/bizar-dash/src/web/v8/ui/settings/SettingsNav.tsx +0 -91
  756. package/bizar-dash/src/web/v8/ui/settings/SettingsRow.tsx +0 -88
  757. package/bizar-dash/src/web/v8/ui/settings/SettingsSection.tsx +0 -109
  758. package/bizar-dash/src/web/v8/ui/styles/globals.css +0 -265
  759. package/bizar-dash/src/web/v8/ui/styles/reset.css +0 -57
  760. package/bizar-dash/src/web/v8/ui/styles/tokens.css +0 -238
  761. package/bizar-dash/src/web/v8/ui/tasks/TaskDetail.tsx +0 -313
  762. package/bizar-dash/src/web/v8/ui/theme/DensityProvider.test.tsx +0 -74
  763. package/bizar-dash/src/web/v8/ui/theme/DensityProvider.tsx +0 -66
  764. package/bizar-dash/src/web/v8/ui/theme/ThemeProvider.test.tsx +0 -86
  765. package/bizar-dash/src/web/v8/ui/theme/ThemeProvider.tsx +0 -94
  766. package/bizar-dash/src/web/v8/ui/theme/ThemeToggle.tsx +0 -93
  767. package/bizar-dash/src/web/v8/ui/theme/useDensity.ts +0 -15
  768. package/bizar-dash/src/web/v8/ui/theme/useTheme.ts +0 -16
  769. package/bizar-dash/src/web/v8/ui/utils/cx.ts +0 -19
  770. package/bizar-dash/src/web/v8/views/Activity/ActivityView.tsx +0 -457
  771. package/bizar-dash/src/web/v8/views/Admin/AdminView.tsx +0 -237
  772. package/bizar-dash/src/web/v8/views/Agents/AgentHierarchy.tsx +0 -212
  773. package/bizar-dash/src/web/v8/views/Agents/AgentsView.tsx +0 -511
  774. package/bizar-dash/src/web/v8/views/Artifacts/ArtifactsView.tsx +0 -452
  775. package/bizar-dash/src/web/v8/views/Artifacts/ClaudeArtifactView.tsx +0 -179
  776. package/bizar-dash/src/web/v8/views/Auth/AuthView.tsx +0 -179
  777. package/bizar-dash/src/web/v8/views/BackgroundJobs/BackgroundJobsView.tsx +0 -467
  778. package/bizar-dash/src/web/v8/views/Backup/BackupView.tsx +0 -204
  779. package/bizar-dash/src/web/v8/views/Chat/ChatView.tsx +0 -368
  780. package/bizar-dash/src/web/v8/views/ClaudeSessions/ClaudeSessionDetail.tsx +0 -144
  781. package/bizar-dash/src/web/v8/views/ClaudeSessions/ClaudeSessionsView.tsx +0 -331
  782. package/bizar-dash/src/web/v8/views/ClaudeSessions/NewSessionModal.tsx +0 -136
  783. package/bizar-dash/src/web/v8/views/Clipboard/ClipboardView.tsx +0 -210
  784. package/bizar-dash/src/web/v8/views/CommandPalette/AppCommandPalette.test.tsx +0 -142
  785. package/bizar-dash/src/web/v8/views/CommandPalette/AppCommandPalette.tsx +0 -272
  786. package/bizar-dash/src/web/v8/views/Config/ConfigView.tsx +0 -364
  787. package/bizar-dash/src/web/v8/views/Diagnostics/DiagnosticsView.tsx +0 -203
  788. package/bizar-dash/src/web/v8/views/Dialogs/DialogsView.tsx +0 -196
  789. package/bizar-dash/src/web/v8/views/Doctor/DoctorView.tsx +0 -181
  790. package/bizar-dash/src/web/v8/views/EnvVars/EnvVarsView.tsx +0 -365
  791. package/bizar-dash/src/web/v8/views/Eval/EvalView.tsx +0 -278
  792. package/bizar-dash/src/web/v8/views/Goals/GoalsView.tsx +0 -351
  793. package/bizar-dash/src/web/v8/views/History/HistoryView.tsx +0 -227
  794. package/bizar-dash/src/web/v8/views/Libraries/LibrariesView.tsx +0 -123
  795. package/bizar-dash/src/web/v8/views/LightRAG/LightRAGView.tsx +0 -204
  796. package/bizar-dash/src/web/v8/views/Memory/MemoryView.tsx +0 -258
  797. package/bizar-dash/src/web/v8/views/Misc/MiscView.tsx +0 -159
  798. package/bizar-dash/src/web/v8/views/Mods/ModsView.tsx +0 -348
  799. package/bizar-dash/src/web/v8/views/Notifications/NotificationsView.tsx +0 -247
  800. package/bizar-dash/src/web/v8/views/Obsidian/ObsidianView.tsx +0 -193
  801. package/bizar-dash/src/web/v8/views/Overview/OverviewView.tsx +0 -274
  802. package/bizar-dash/src/web/v8/views/Projects/ProjectsView.tsx +0 -273
  803. package/bizar-dash/src/web/v8/views/Providers/ProvidersView.tsx +0 -371
  804. package/bizar-dash/src/web/v8/views/Router.tsx +0 -202
  805. package/bizar-dash/src/web/v8/views/Schedules/SchedulesView.tsx +0 -462
  806. package/bizar-dash/src/web/v8/views/Settings/SettingsView.tsx +0 -1039
  807. package/bizar-dash/src/web/v8/views/Tasks/TasksView.tsx +0 -386
  808. package/bizar-dash/src/web/v8/views/Update/UpdateView.tsx +0 -270
  809. package/bizar-dash/src/web/v8/views/Usage/UsageView.tsx +0 -210
  810. package/bizar-dash/src/web/v8/views/Voice/VoiceView.tsx +0 -192
  811. package/bizar-dash/src/web/v8/vite-env.d.ts +0 -15
  812. package/bizar-dash/templates/mod/FORMAT.md +0 -76
  813. package/bizar-dash/templates/mod/hello-mod/README.md +0 -19
  814. package/bizar-dash/templates/mod/hello-mod/agents/greeter.md +0 -8
  815. package/bizar-dash/templates/mod/hello-mod/commands/hello.md +0 -6
  816. package/bizar-dash/templates/mod/hello-mod/mod.json +0 -20
  817. package/bizar-dash/templates/mod/hello-mod/routes/ping.mjs +0 -9
  818. package/bizar-dash/templates/mod/hello-mod/views/HelloView.tsx +0 -10
  819. package/bizar-dash/tests/_update-test-helpers.mjs +0 -13
  820. package/bizar-dash/tests/admin-labels.test.mjs +0 -42
  821. package/bizar-dash/tests/admin-run-grouping.test.mjs +0 -42
  822. package/bizar-dash/tests/agent-bus.test.mjs +0 -289
  823. package/bizar-dash/tests/agents-preview-truncate.test.mjs +0 -35
  824. package/bizar-dash/tests/agents-summary-sanitize.test.ts +0 -51
  825. package/bizar-dash/tests/auth-inputs-labels.test.mjs +0 -45
  826. package/bizar-dash/tests/background-jobs-action-labels.test.mjs +0 -41
  827. package/bizar-dash/tests/background-jobs-states.test.mjs +0 -80
  828. package/bizar-dash/tests/background-pause-resume.test.mjs +0 -46
  829. package/bizar-dash/tests/background-sdk-session.test.mjs +0 -94
  830. package/bizar-dash/tests/background-session-events.test.mjs +0 -74
  831. package/bizar-dash/tests/background-spawn-from-ui.test.mjs +0 -36
  832. package/bizar-dash/tests/background-steer-sdk.test.mjs +0 -71
  833. package/bizar-dash/tests/background-steer.test.mjs +0 -60
  834. package/bizar-dash/tests/backup-restore.test.mjs +0 -217
  835. package/bizar-dash/tests/backup-store.test.mjs +0 -300
  836. package/bizar-dash/tests/banner-tones.test.mjs +0 -50
  837. package/bizar-dash/tests/bundle-analysis.test.mjs +0 -74
  838. package/bizar-dash/tests/callout-tokens.test.mjs +0 -30
  839. package/bizar-dash/tests/chat-new-session.test.mjs +0 -63
  840. package/bizar-dash/tests/claude-session-detail-states.test.mjs +0 -67
  841. package/bizar-dash/tests/cli-bugfixes.test.mjs +0 -151
  842. package/bizar-dash/tests/cli-error-visibility.test.mjs +0 -153
  843. package/bizar-dash/tests/cli-refactor.test.mjs +0 -183
  844. package/bizar-dash/tests/cli-tailscale.test.mjs +0 -113
  845. package/bizar-dash/tests/clipboard.test.mjs +0 -147
  846. package/bizar-dash/tests/components/BackgroundJobsSkeleton.test.tsx +0 -38
  847. package/bizar-dash/tests/components/Button.test.tsx +0 -78
  848. package/bizar-dash/tests/components/Card.test.tsx +0 -59
  849. package/bizar-dash/tests/components/Dialog.test.tsx +0 -80
  850. package/bizar-dash/tests/components/Toast.test.tsx +0 -61
  851. package/bizar-dash/tests/dash-cli-load.test.mjs +0 -44
  852. package/bizar-dash/tests/dashboard-ports.test.mjs +0 -138
  853. package/bizar-dash/tests/density-tokens.test.mjs +0 -46
  854. package/bizar-dash/tests/deploy-templates.test.mjs +0 -100
  855. package/bizar-dash/tests/diagnostics-store.test.mjs +0 -205
  856. package/bizar-dash/tests/digest-generation.test.mjs +0 -191
  857. package/bizar-dash/tests/digest-store.test.mjs +0 -264
  858. package/bizar-dash/tests/docker-build.test.mjs +0 -96
  859. package/bizar-dash/tests/doctor-route-shape.test.mjs +0 -67
  860. package/bizar-dash/tests/empty-state-docs.test.mjs +0 -55
  861. package/bizar-dash/tests/env-vars-extended.test.mjs +0 -190
  862. package/bizar-dash/tests/env-vars-store.test.mjs +0 -216
  863. package/bizar-dash/tests/errorstate-retry.test.mjs +0 -42
  864. package/bizar-dash/tests/eval/fixtures-extra.test.mjs +0 -285
  865. package/bizar-dash/tests/eval/fixtures.test.mjs +0 -141
  866. package/bizar-dash/tests/eval/report.test.mjs +0 -284
  867. package/bizar-dash/tests/eval/runner.test.mjs +0 -471
  868. package/bizar-dash/tests/eval-csv.test.mjs +0 -67
  869. package/bizar-dash/tests/eval-scheduled.test.mjs +0 -84
  870. package/bizar-dash/tests/focus-visible.test.mjs +0 -30
  871. package/bizar-dash/tests/frontend-bugfixes.test.mjs +0 -151
  872. package/bizar-dash/tests/goals-decompose.test.mjs +0 -179
  873. package/bizar-dash/tests/kanban-empty-column.test.mjs +0 -56
  874. package/bizar-dash/tests/layout-spacing.test.mjs +0 -101
  875. package/bizar-dash/tests/library-dedupe.test.mjs +0 -62
  876. package/bizar-dash/tests/lightrag-defaults.node.test.mjs +0 -118
  877. package/bizar-dash/tests/lightrag-skeleton.test.mjs +0 -31
  878. package/bizar-dash/tests/lightrag-startup-hook.test.mjs +0 -238
  879. package/bizar-dash/tests/lightrag-states.test.mjs +0 -60
  880. package/bizar-dash/tests/logger.test.mjs +0 -207
  881. package/bizar-dash/tests/loop-runtime.test.mjs +0 -312
  882. package/bizar-dash/tests/loops-agent-bus-routes.test.mjs +0 -293
  883. package/bizar-dash/tests/memory-auto-reindex.test.mjs +0 -196
  884. package/bizar-dash/tests/memory-cli-readlistdelete.test.mjs +0 -405
  885. package/bizar-dash/tests/memory-cli-setup.test.mjs +0 -382
  886. package/bizar-dash/tests/memory-cli.test.mjs +0 -596
  887. package/bizar-dash/tests/memory-config.test.mjs +0 -422
  888. package/bizar-dash/tests/memory-conflicts.test.mjs +0 -229
  889. package/bizar-dash/tests/memory-default-vault.test.mjs +0 -185
  890. package/bizar-dash/tests/memory-git.test.mjs +0 -264
  891. package/bizar-dash/tests/memory-graph.test.mjs +0 -95
  892. package/bizar-dash/tests/memory-install-bootstrap.test.mjs +0 -129
  893. package/bizar-dash/tests/memory-lightrag-extended.test.mjs +0 -293
  894. package/bizar-dash/tests/memory-lightrag.test.mjs +0 -153
  895. package/bizar-dash/tests/memory-link.test.mjs +0 -134
  896. package/bizar-dash/tests/memory-namespace.test.mjs +0 -404
  897. package/bizar-dash/tests/memory-obsidian.test.mjs +0 -269
  898. package/bizar-dash/tests/memory-path-safety.test.mjs +0 -427
  899. package/bizar-dash/tests/memory-protocol-drift.test.mjs +0 -52
  900. package/bizar-dash/tests/memory-roundtrip.test.mjs +0 -219
  901. package/bizar-dash/tests/memory-schema.test.mjs +0 -198
  902. package/bizar-dash/tests/memory-secrets.test.mjs +0 -121
  903. package/bizar-dash/tests/memory-status-root.test.mjs +0 -144
  904. package/bizar-dash/tests/memory-store.test.mjs +0 -493
  905. package/bizar-dash/tests/memory-sync.test.mjs +0 -141
  906. package/bizar-dash/tests/memory-tab.test.mjs +0 -322
  907. package/bizar-dash/tests/memory-vault-config-endpoint.test.mjs +0 -173
  908. package/bizar-dash/tests/metrics.test.mjs +0 -183
  909. package/bizar-dash/tests/minimax-bar.test.tsx +0 -113
  910. package/bizar-dash/tests/minimax-chat-usage.test.mjs +0 -253
  911. package/bizar-dash/tests/minimax-models.test.mjs +0 -123
  912. package/bizar-dash/tests/minimax-usage-store.node.test.mjs +0 -293
  913. package/bizar-dash/tests/mod-instructions.node.test.mjs +0 -188
  914. package/bizar-dash/tests/mod-security.test.mjs +0 -273
  915. package/bizar-dash/tests/mod-upgrade.node.test.mjs +0 -357
  916. package/bizar-dash/tests/modal-focus-trap.test.mjs +0 -114
  917. package/bizar-dash/tests/no-browser-harness.node.test.mjs +0 -98
  918. package/bizar-dash/tests/notifications-dismiss-restore.test.mjs +0 -85
  919. package/bizar-dash/tests/obsidian-back-compat.test.mjs +0 -199
  920. package/bizar-dash/tests/ocr.test.mjs +0 -87
  921. package/bizar-dash/tests/orphan-tests-audit.test.mjs +0 -80
  922. package/bizar-dash/tests/otel-spans.test.mjs +0 -253
  923. package/bizar-dash/tests/otel.test.mjs +0 -188
  924. package/bizar-dash/tests/palette-keyboard-nav.test.mjs +0 -123
  925. package/bizar-dash/tests/palette-shortcut-hint.test.mjs +0 -58
  926. package/bizar-dash/tests/path-safe.test.mjs +0 -108
  927. package/bizar-dash/tests/projects-filter.test.mjs +0 -48
  928. package/bizar-dash/tests/providers-store-backup-keys.node.test.mjs +0 -809
  929. package/bizar-dash/tests/providers-store-search.node.test.mjs +0 -166
  930. package/bizar-dash/tests/rate-limit.test.mjs +0 -298
  931. package/bizar-dash/tests/routes-doctor.test.mjs +0 -159
  932. package/bizar-dash/tests/schedules-skeleton.test.mjs +0 -25
  933. package/bizar-dash/tests/schedules-states.test.mjs +0 -67
  934. package/bizar-dash/tests/schedules-templates.test.mjs +0 -144
  935. package/bizar-dash/tests/server-bugfixes.test.mjs +0 -318
  936. package/bizar-dash/tests/settings-labels.test.mjs +0 -80
  937. package/bizar-dash/tests/setup.ts +0 -84
  938. package/bizar-dash/tests/sidebar-collapse-animation.test.mjs +0 -37
  939. package/bizar-dash/tests/sidebar-distinct-icons.test.mjs +0 -28
  940. package/bizar-dash/tests/sidebar-responsive.test.tsx +0 -48
  941. package/bizar-dash/tests/sidebar-section-styles.test.mjs +0 -55
  942. package/bizar-dash/tests/sidebar-section-toggle.test.tsx +0 -63
  943. package/bizar-dash/tests/skeleton-height.test.mjs +0 -47
  944. package/bizar-dash/tests/skills-list.test.mjs +0 -232
  945. package/bizar-dash/tests/skills-search.test.mjs +0 -222
  946. package/bizar-dash/tests/smoke-v2.mjs +0 -212
  947. package/bizar-dash/tests/space-tokens.test.mjs +0 -36
  948. package/bizar-dash/tests/stattile-loading.test.mjs +0 -28
  949. package/bizar-dash/tests/strip-thinking.test.mjs +0 -175
  950. package/bizar-dash/tests/submit-feedback.test.mjs +0 -353
  951. package/bizar-dash/tests/task-splitter.test.mjs +0 -268
  952. package/bizar-dash/tests/tasks-create.test.mjs +0 -187
  953. package/bizar-dash/tests/tasks-patch-routes.test.mjs +0 -194
  954. package/bizar-dash/tests/theme-toggle-icon.test.mjs +0 -28
  955. package/bizar-dash/tests/tmux-wrap.test.mjs +0 -145
  956. package/bizar-dash/tests/topbar-design-system.test.mjs +0 -56
  957. package/bizar-dash/tests/topbar-popover-styles.test.mjs +0 -53
  958. package/bizar-dash/tests/ui/primitives/VisuallyHidden.test.tsx +0 -42
  959. package/bizar-dash/tests/update-channels.test.mjs +0 -67
  960. package/bizar-dash/tests/update-check.test.mjs +0 -132
  961. package/bizar-dash/tests/update-packages.test.mjs +0 -154
  962. package/bizar-dash/tests/update-run.test.mjs +0 -203
  963. package/bizar-dash/tests/users.test.mjs +0 -108
  964. package/bizar-dash/tests/v7-archive/BackgroundAgents.test.tsx +0 -160
  965. package/bizar-dash/tests/v7-archive/a11y/forms.test.tsx +0 -191
  966. package/bizar-dash/tests/v7-archive/a11y.test.tsx +0 -206
  967. package/bizar-dash/tests/v7-archive/autosave.test.tsx +0 -276
  968. package/bizar-dash/tests/v7-archive/backup-restore.test.tsx +0 -141
  969. package/bizar-dash/tests/v7-archive/chat-composer.test.tsx +0 -140
  970. package/bizar-dash/tests/v7-archive/communication-log.test.tsx +0 -67
  971. package/bizar-dash/tests/v7-archive/components/Modal.test.tsx +0 -104
  972. package/bizar-dash/tests/v7-archive/components/Spinner.test.tsx +0 -32
  973. package/bizar-dash/tests/v7-archive/components/StatusBadge.test.tsx +0 -35
  974. package/bizar-dash/tests/v7-archive/components/doctor-panel.test.tsx +0 -105
  975. package/bizar-dash/tests/v7-archive/components/screenshot-ocr.test.tsx +0 -75
  976. package/bizar-dash/tests/v7-archive/components/settings-search.test.tsx +0 -180
  977. package/bizar-dash/tests/v7-archive/components/workspace-selector.test.tsx +0 -73
  978. package/bizar-dash/tests/v7-archive/controls/Button.test.tsx +0 -50
  979. package/bizar-dash/tests/v7-archive/controls/Checkbox.test.tsx +0 -44
  980. package/bizar-dash/tests/v7-archive/controls/IconButton.test.tsx +0 -37
  981. package/bizar-dash/tests/v7-archive/controls/Kbd.test.tsx +0 -39
  982. package/bizar-dash/tests/v7-archive/controls/NumberInput.test.tsx +0 -56
  983. package/bizar-dash/tests/v7-archive/controls/RadioGroup.test.tsx +0 -58
  984. package/bizar-dash/tests/v7-archive/controls/SearchInput.test.tsx +0 -52
  985. package/bizar-dash/tests/v7-archive/controls/Select.test.tsx +0 -66
  986. package/bizar-dash/tests/v7-archive/controls/Slider.test.tsx +0 -41
  987. package/bizar-dash/tests/v7-archive/controls/TextInput.test.tsx +0 -44
  988. package/bizar-dash/tests/v7-archive/controls/Textarea.test.tsx +0 -56
  989. package/bizar-dash/tests/v7-archive/controls/Toggle.test.tsx +0 -39
  990. package/bizar-dash/tests/v7-archive/data/BarChart.test.tsx +0 -55
  991. package/bizar-dash/tests/v7-archive/data/DataTable.test.tsx +0 -112
  992. package/bizar-dash/tests/v7-archive/data/EmptyState.test.tsx +0 -57
  993. package/bizar-dash/tests/v7-archive/data/ErrorState.test.tsx +0 -47
  994. package/bizar-dash/tests/v7-archive/data/KeyValueList.test.tsx +0 -59
  995. package/bizar-dash/tests/v7-archive/data/LoadingState.test.tsx +0 -37
  996. package/bizar-dash/tests/v7-archive/data/Sparkline.test.tsx +0 -56
  997. package/bizar-dash/tests/v7-archive/data/StatTile.test.tsx +0 -69
  998. package/bizar-dash/tests/v7-archive/dep-graph.test.tsx +0 -40
  999. package/bizar-dash/tests/v7-archive/eval-web-ui.test.tsx +0 -220
  1000. package/bizar-dash/tests/v7-archive/event-log.test.tsx +0 -61
  1001. package/bizar-dash/tests/v7-archive/feedback/Badge.test.tsx +0 -41
  1002. package/bizar-dash/tests/v7-archive/feedback/Dialog.test.tsx +0 -99
  1003. package/bizar-dash/tests/v7-archive/feedback/ProgressBar.test.tsx +0 -66
  1004. package/bizar-dash/tests/v7-archive/feedback/StatusDot.test.tsx +0 -41
  1005. package/bizar-dash/tests/v7-archive/feedback/Toast.test.tsx +0 -124
  1006. package/bizar-dash/tests/v7-archive/feedback/Tooltip.test.tsx +0 -102
  1007. package/bizar-dash/tests/v7-archive/goal-input.test.tsx +0 -72
  1008. package/bizar-dash/tests/v7-archive/goap-planner.test.ts +0 -68
  1009. package/bizar-dash/tests/v7-archive/hooks/useModal.test.tsx +0 -84
  1010. package/bizar-dash/tests/v7-archive/hooks/useToast.test.tsx +0 -50
  1011. package/bizar-dash/tests/v7-archive/layout/AppShell.test.tsx +0 -50
  1012. package/bizar-dash/tests/v7-archive/layout/Breadcrumbs.test.tsx +0 -85
  1013. package/bizar-dash/tests/v7-archive/layout/Panel.test.tsx +0 -52
  1014. package/bizar-dash/tests/v7-archive/layout/PanelHeader.test.tsx +0 -32
  1015. package/bizar-dash/tests/v7-archive/layout/Sidebar.test.tsx +0 -95
  1016. package/bizar-dash/tests/v7-archive/layout/Tabs.test.tsx +0 -87
  1017. package/bizar-dash/tests/v7-archive/layout/Topbar.test.tsx +0 -41
  1018. package/bizar-dash/tests/v7-archive/layout/ViewHeader.test.tsx +0 -47
  1019. package/bizar-dash/tests/v7-archive/lib/i18n.test.ts +0 -46
  1020. package/bizar-dash/tests/v7-archive/lib/search-fuzzy.test.ts +0 -149
  1021. package/bizar-dash/tests/v7-archive/lib/utils.test.ts +0 -194
  1022. package/bizar-dash/tests/v7-archive/memory-graph-view.test.tsx +0 -69
  1023. package/bizar-dash/tests/v7-archive/mobile-chat.test.tsx +0 -82
  1024. package/bizar-dash/tests/v7-archive/mobile-layout.test.tsx +0 -332
  1025. package/bizar-dash/tests/v7-archive/mobile-tasks.test.tsx +0 -162
  1026. package/bizar-dash/tests/v7-archive/navigation/NavGroup.test.tsx +0 -55
  1027. package/bizar-dash/tests/v7-archive/navigation/NavLink.test.tsx +0 -61
  1028. package/bizar-dash/tests/v7-archive/quality-gates.test.tsx +0 -59
  1029. package/bizar-dash/tests/v7-archive/settings-layout.test.tsx +0 -129
  1030. package/bizar-dash/tests/v7-archive/settings-mode-wiring.test.tsx +0 -151
  1031. package/bizar-dash/tests/v7-archive/ui/primitives/Box.test.tsx +0 -49
  1032. package/bizar-dash/tests/v7-archive/ui/primitives/Grid.test.tsx +0 -37
  1033. package/bizar-dash/tests/v7-archive/ui/primitives/Inline.test.tsx +0 -33
  1034. package/bizar-dash/tests/v7-archive/ui/primitives/Separator.test.tsx +0 -34
  1035. package/bizar-dash/tests/v7-archive/ui/primitives/Stack.test.tsx +0 -45
  1036. package/bizar-dash/tests/v7-archive/views/Agents.test.tsx +0 -359
  1037. package/bizar-dash/tests/v7-archive/views/Memory.test.tsx +0 -274
  1038. package/bizar-dash/tests/v7-archive/views/Tasks.test.tsx +0 -250
  1039. package/bizar-dash/tests/v7-archive/views/sidebar.test.tsx +0 -116
  1040. package/bizar-dash/tests/v7-archive/views/topbar.test.tsx +0 -87
  1041. package/bizar-dash/tests/v7-archive/voice-recorder.test.tsx +0 -95
  1042. package/bizar-dash/tests/voice-store.test.mjs +0 -148
  1043. package/bizar-dash/tests/voice-transcribe-worker.test.mjs +0 -343
  1044. package/bizar-dash/tests/voice-transcribe.test.mjs +0 -87
  1045. package/bizar-dash/tests/workspaces.test.mjs +0 -527
  1046. package/bizar-dash/tests/yaml.test.mjs +0 -128
  1047. package/bizar-dash/tsconfig.json +0 -23
  1048. package/bizar-dash/vite.config.ts +0 -82
  1049. package/bizar-dash/vitest.config.ts +0 -20
  1050. package/cli/__tests__/cost-gate.test.mjs +0 -301
  1051. package/cli/__tests__/esm-no-require.test.mjs +0 -97
  1052. package/cli/__tests__/feature-list-bridge.test.mjs +0 -371
  1053. package/cli/agent-browser-up.sh +0 -152
  1054. package/cli/agent-browser-update.test.mjs +0 -86
  1055. package/cli/artifact-cli.mjs +0 -605
  1056. package/cli/artifact-render.mjs +0 -621
  1057. package/cli/artifact-server.mjs +0 -847
  1058. package/cli/artifact-templates.mjs +0 -523
  1059. package/cli/artifact.mjs +0 -63
  1060. package/cli/artifact.test.mjs +0 -2331
  1061. package/cli/bg.mjs +0 -471
  1062. package/cli/cli-commands-validation.test.mjs +0 -106
  1063. package/cli/commands/artifact.mjs +0 -20
  1064. package/cli/commands/clip.mjs +0 -163
  1065. package/cli/commands/dash.mjs +0 -193
  1066. package/cli/commands/deploy/cloudflare.mjs +0 -250
  1067. package/cli/commands/deploy/docker.mjs +0 -219
  1068. package/cli/commands/deploy/fly.mjs +0 -161
  1069. package/cli/commands/deploy/vercel.mjs +0 -225
  1070. package/cli/commands/deploy.mjs +0 -240
  1071. package/cli/commands/eval.mjs +0 -396
  1072. package/cli/commands/lightrag.mjs +0 -170
  1073. package/cli/commands/marketplace.mjs +0 -64
  1074. package/cli/commands/memory.mjs +0 -25
  1075. package/cli/commands/minimax.mjs +0 -288
  1076. package/cli/commands/mod.mjs +0 -185
  1077. package/cli/commands/ocr.mjs +0 -182
  1078. package/cli/commands/plugin.mjs +0 -358
  1079. package/cli/commands/rca.test.mjs +0 -99
  1080. package/cli/commands/service.mjs +0 -79
  1081. package/cli/commands/setup-provider.test.mjs +0 -311
  1082. package/cli/commands/tailscale.mjs +0 -256
  1083. package/cli/commands/usage.mjs +0 -113
  1084. package/cli/commands/voice.mjs +0 -211
  1085. package/cli/commands/workspace.mjs +0 -247
  1086. package/cli/copy.mjs +0 -540
  1087. package/cli/dev-link.mjs +0 -191
  1088. package/cli/dev-link.test.mjs +0 -310
  1089. package/cli/digest.mjs +0 -149
  1090. package/cli/install/banner.test.mjs +0 -39
  1091. package/cli/install/index.test.mjs +0 -61
  1092. package/cli/install/paths.test.mjs +0 -77
  1093. package/cli/install/plugin.mjs +0 -161
  1094. package/cli/install/postinstall.mjs +0 -137
  1095. package/cli/install/postinstall.test.mjs +0 -98
  1096. package/cli/install.test.mjs +0 -272
  1097. package/cli/memory-constants.mjs +0 -76
  1098. package/cli/memory.mjs +0 -1850
  1099. package/cli/migrate.test.mjs +0 -85
  1100. package/cli/plow-through.test.mjs +0 -56
  1101. package/cli/plugin-runtime-deps.mjs +0 -250
  1102. package/cli/plugin-runtime-deps.test.mjs +0 -378
  1103. package/cli/post-install-smoke.mjs +0 -256
  1104. package/cli/post-install-smoke.test.mjs +0 -72
  1105. package/cli/prompts.mjs +0 -168
  1106. package/cli/providers-detect.mjs +0 -233
  1107. package/cli/provision.test.mjs +0 -324
  1108. package/cli/service-controller.mjs +0 -735
  1109. package/cli/service-controller.test.mjs +0 -191
  1110. package/cli/service-env.mjs +0 -130
  1111. package/cli/service.mjs +0 -492
  1112. package/cli/tests/minimax-cli.test.mjs +0 -79
  1113. package/cli/worker-dispatcher.test.mjs +0 -204
  1114. package/config/skills/glyph/SKILL.md +0 -163
  1115. package/config/skills/lightrag/SKILL.md +0 -81
  1116. package/config/skills/memory-protocol/SKILL.md +0 -105
  1117. package/config/skills/obsidian/SKILL.md +0 -306
  1118. package/packages/sdk/${HOME}/.bizar_home/memory/projects/sdk/sessions/2026-07-16-e16c2f59.md +0 -14
  1119. package/packages/sdk/.bizar/learning/instincts.jsonl +0 -5
  1120. package/packages/sdk/LICENSE +0 -21
  1121. package/packages/sdk/dist/memory/index.d.ts +0 -43
  1122. package/packages/sdk/dist/memory/index.js +0 -159
  1123. package/packages/sdk/dist/router/memory-distillation.d.ts +0 -58
  1124. package/packages/sdk/dist/router/memory-distillation.js +0 -195
  1125. package/packages/sdk/vitest.config.ts +0 -9
  1126. package/plugins/bizar/LICENSE +0 -21
  1127. package/plugins/bizar/dist/index.d.ts +0 -25
  1128. package/plugins/bizar/dist/index.js +0 -32
  1129. package/plugins/bizar/package.json +0 -18
  1130. package/scripts/audit.mjs +0 -388
  1131. package/scripts/audit.test.mjs +0 -149
  1132. package/scripts/bh-full-e2e.mjs +0 -300
  1133. package/scripts/check-agents.mjs +0 -76
  1134. package/scripts/check-arch.sh +0 -80
  1135. package/scripts/check-deps.mjs +0 -468
  1136. package/scripts/clean-state-check.sh +0 -81
  1137. package/scripts/eval-gate.mjs +0 -134
  1138. package/scripts/eval-gate.test.mjs +0 -138
  1139. package/scripts/feature-state-machine.mjs +0 -178
  1140. package/scripts/feature-state-machine.test.mjs +0 -135
  1141. package/scripts/git-hooks/__tests__/commit-msg.test.mjs +0 -61
  1142. package/scripts/install-curl.sh +0 -17
  1143. package/scripts/install-service.mjs +0 -190
  1144. package/scripts/loop-log.txt +0 -29
  1145. package/scripts/loop-tasks.md +0 -79
  1146. package/scripts/mirror-claude-md.sh +0 -78
  1147. package/scripts/overnight-loop.mjs +0 -60
  1148. package/scripts/run-dev.mjs +0 -84
  1149. package/scripts/session-trace.sh +0 -52
  1150. package/scripts/spec-dedup.test.mjs +0 -124
  1151. package/scripts/sprint.mjs +0 -213
  1152. package/scripts/sync-skills-mirror.mjs +0 -113
  1153. package/scripts/test-in-container.sh +0 -157
  1154. package/scripts/verify-feature.sh +0 -102
  1155. package/scripts/verify-thinking-skills.mjs +0 -135
  1156. package/scripts/worktree-setup.sh +0 -79
  1157. package/templates/clean-state-checklist.md +0 -55
  1158. package/templates/deploy/cloudflare/README.md +0 -32
  1159. package/templates/deploy/cloudflare/functions-index.template.js +0 -15
  1160. package/templates/deploy/cloudflare/wrangler.toml.template +0 -9
  1161. package/templates/deploy/docker/.env.template +0 -16
  1162. package/templates/deploy/docker/README.md +0 -58
  1163. package/templates/deploy/docker/docker-compose.template.yml +0 -23
  1164. package/templates/deploy/fly/README.md +0 -35
  1165. package/templates/deploy/fly/fly.toml.template +0 -28
  1166. package/templates/deploy/vercel/README.md +0 -29
  1167. package/templates/deploy/vercel/api-index.template.js +0 -18
  1168. package/templates/deploy/vercel/vercel.json.template +0 -16
  1169. package/templates/eval-fixtures/README.md +0 -58
  1170. package/templates/eval-fixtures/citation.json +0 -16
  1171. package/templates/eval-fixtures/code-review.json +0 -16
  1172. package/templates/eval-fixtures/code-search-basic.json +0 -28
  1173. package/templates/eval-fixtures/concise-output.json +0 -16
  1174. package/templates/eval-fixtures/context-window.json +0 -16
  1175. package/templates/eval-fixtures/error-recovery.json +0 -16
  1176. package/templates/eval-fixtures/json-output.json +0 -20
  1177. package/templates/eval-fixtures/latency-bounds.json +0 -16
  1178. package/templates/eval-fixtures/multi-language.json +0 -16
  1179. package/templates/eval-fixtures/regression-suite.json +0 -79
  1180. package/templates/eval-fixtures/response-format.json +0 -30
  1181. package/templates/eval-fixtures/safe-paths.json +0 -16
  1182. package/templates/eval-fixtures/tool-call-correctness.json +0 -24
  1183. package/templates/eval-fixtures/tool-call-multi-step.json +0 -18
  1184. package/templates/eval-fixtures/unicode-handling.json +0 -16
  1185. package/templates/evaluator-rubric.md +0 -50
  1186. package/templates/plan/htmx.min.js +0 -1
  1187. package/templates/plan/library/bug-investigation.mdx +0 -79
  1188. package/templates/plan/library/decision-record.mdx +0 -71
  1189. package/templates/plan/library/feature-design.mdx +0 -92
  1190. package/templates/plan/meta.json.template +0 -8
  1191. package/templates/plan/plan.mdx.template +0 -46
  1192. package/templates/plugin-template/README.md +0 -121
  1193. package/templates/plugin-template/index.js +0 -66
  1194. package/templates/plugin-template/plugin.json +0 -42
  1195. package/templates/plugin-template/tests/plugin.test.js +0 -83
  1196. package/templates/schedules/daily-backup.json +0 -12
  1197. package/templates/schedules/daily-cleanup.json +0 -12
  1198. package/templates/schedules/hourly-health-check.json +0 -12
  1199. package/templates/schedules/webhook-on-push.json +0 -13
  1200. package/templates/schedules/weekly-digest.json +0 -13
  1201. package/templates/sprint-contract.md +0 -47
  1202. /package/{.claude → config}/skills/de-sloppify/SKILL.md +0 -0
@@ -1,2331 +0,0 @@
1
- /**
2
- * artifact.mjs tests — uses Node's built-in node:test (Node 20+)
3
- * Tests: slug validation, new, list, delete, export, server routes
4
- *
5
- * Note: These tests import from artifact.mjs directly, so they test internal
6
- * functions via their named exports. The server runs in the test process
7
- * but is shut down after each test.
8
- */
9
-
10
- import { test, describe, beforeEach, afterEach } from 'node:test';
11
- import assert from 'node:assert';
12
- import {
13
- existsSync,
14
- mkdirSync,
15
- writeFileSync,
16
- readFileSync,
17
- rmSync,
18
- readdirSync,
19
- } from 'node:fs';
20
- import { join, resolve, dirname } from 'node:path';
21
- import { fileURLToPath } from 'node:url';
22
-
23
- // Resolve artifact.mjs from this test file's location
24
- const __dirname = dirname(fileURLToPath(import.meta.url));
25
- const PROJECT_ROOT = resolve(__dirname, '..');
26
- const TEMPLATES_DIR = join(PROJECT_ROOT, 'templates', 'artifact');
27
- const PLANS_DIR = join(PROJECT_ROOT, 'artifacts');
28
-
29
- // ── Named imports from artifact.mjs ──────────────────────────────────────────────
30
- const { runPlan, startServer, regenerateHtml } = await import('./artifact.mjs');
31
-
32
- // Suppress MaxListenersWarning (each server adds SIGINT+SIGTERM listeners)
33
- process.setMaxListeners(64);
34
-
35
- // ── Test helpers ─────────────────────────────────────────────────────────────
36
-
37
- /** Slug validation regex (mirrors artifact.mjs) */
38
- // Must match artifact.mjs SLUG_REGEX: ^[a-z0-9][a-z0-9-]{0,63}$
39
- const SLUG_REGEX = /^[a-z0-9][a-z0-9-]{0,63}$/;
40
-
41
- /** Create a temp artifact directory for testing */
42
- function createTempPlan(slug, overrides = {}) {
43
- const planDir = join(PLANS_DIR, slug);
44
- mkdirSync(planDir, { recursive: true });
45
-
46
- const now = new Date().toISOString();
47
- const meta = {
48
- title: overrides.title || slug.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()),
49
- slug,
50
- status: overrides.status || 'draft',
51
- author: process.env.USER || 'test-user',
52
- created: now,
53
- lastEdited: now,
54
- ...overrides.meta,
55
- };
56
-
57
- writeFileSync(join(planDir, 'meta.json'), JSON.stringify(meta, null, 2), 'utf-8');
58
- const mdxContent = overrides.mdx || `# ${meta.title}\n\nContent here.\n`;
59
- writeFileSync(join(planDir, 'artifact.mdx'), mdxContent, 'utf-8');
60
- writeFileSync(
61
- join(planDir, 'comments.json'),
62
- overrides.comments || '[]',
63
- 'utf-8'
64
- );
65
-
66
- // Generate artifact.html
67
- const planMdx = readFileSync(join(planDir, 'artifact.mdx'), 'utf-8');
68
- const commentsJson = readFileSync(join(planDir, 'comments.json'), 'utf-8');
69
- const metaJson = readFileSync(join(planDir, 'meta.json'), 'utf-8');
70
- const planJson = JSON.stringify(planMdx);
71
- const htmlContent = `<!doctype html>
72
- <html>
73
- <head><title>${meta.title} — Bizar Artifact</title></head>
74
- <body>
75
- <header><h1>${meta.title}</h1></header>
76
- <main><pre>${planMdx.replace(/</g, '&lt;')}</pre></main>
77
- <script>
78
- const INITIAL_STATE = { artifact: ${planJson}, comments: ${commentsJson}, meta: ${metaJson} };
79
- </script>
80
- </body>
81
- </html>`;
82
- writeFileSync(join(planDir, 'artifact.html'), htmlContent, 'utf-8');
83
-
84
- return planDir;
85
- }
86
-
87
- /** Clean up a temp artifact */
88
- function cleanupPlan(slug) {
89
- const planDir = join(PLANS_DIR, slug);
90
- if (existsSync(planDir)) rmSync(planDir, { recursive: true });
91
- }
92
-
93
- // ── Slug validation tests ─────────────────────────────────────────────────────
94
-
95
- describe('Slug validation', () => {
96
- test('accepts valid slugs', () => {
97
- // Per spec regex: ^[a-z0-9][a-z0-9-]{0,63}$
98
- const valid = ['a', 'ab', 'a1', 'my-feature', 'feature-123', 'abc', 'a1b2c3', 'feature-'];
99
- for (const slug of valid) {
100
- assert.equal(SLUG_REGEX.test(slug), true, `slug "${slug}" should be valid`);
101
- }
102
- });
103
-
104
- test('rejects invalid slugs', () => {
105
- const invalid = [
106
- '', // empty
107
- '-feature', // starts with hyphen
108
- 'my feature', // spaces
109
- 'UPPER', // uppercase
110
- 'my_feature', // underscores
111
- ];
112
- for (const slug of invalid) {
113
- assert.equal(SLUG_REGEX.test(slug), false, `slug "${slug}" should be invalid`);
114
- }
115
- });
116
-
117
- test('slug max length 64 chars (spec regex)', () => {
118
- // Spec regex: ^[a-z0-9][a-z0-9-]{0,63}$ = 1 + up to 63 = max 64 chars
119
- const long = 'a' + 'b'.repeat(62); // 63 chars (1+62=63) - valid
120
- assert.equal(SLUG_REGEX.test(long), true, '63-char should be valid');
121
- const max = 'a' + 'b'.repeat(62) + 'c'; // 64 chars (1+62+1, but middle is only 62?)
122
- // Actually: first=a, middle=b*62, last=c. Total=64. Valid.
123
- const max64 = 'a' + '-'.repeat(62) + 'a'; // 64 chars with hyphens
124
- assert.equal(SLUG_REGEX.test(max64), true, '64-char with hyphens should be valid');
125
- const tooLong = 'a' + '-'.repeat(63) + 'a'; // 65 chars
126
- assert.equal(SLUG_REGEX.test(tooLong), false, '65-char should be invalid');
127
- });
128
- });
129
-
130
- // ── createPlan / new flow tests ───────────────────────────────────────────────
131
- // Note: runPlan(['new', slug]) starts a blocking server so can't be tested directly.
132
- // The file creation is tested in 'Artifact file creation' suite below.
133
- // Slug validation is tested independently above.
134
-
135
- // Test createPlan by checking the files it creates
136
- describe('Artifact file creation', () => {
137
- const TEST_SLUG = 'test-files-' + Date.now();
138
-
139
- afterEach(() => {
140
- cleanupPlan(TEST_SLUG);
141
- });
142
-
143
- test('runPlan new creates artifact.mdx, meta.json, comments.json', async () => {
144
- // We'll use a subprocess to run just the file creation part
145
- // For unit testing, we directly verify the file operations work
146
- mkdirSync(join(PLANS_DIR, TEST_SLUG), { recursive: true });
147
-
148
- const now = new Date().toISOString();
149
- const title = TEST_SLUG.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
150
- const meta = { title, slug: TEST_SLUG, status: 'draft', author: 'tester', created: now, lastEdited: now };
151
-
152
- writeFileSync(join(PLANS_DIR, TEST_SLUG, 'meta.json'), JSON.stringify(meta, null, 2), 'utf-8');
153
- writeFileSync(join(PLANS_DIR, TEST_SLUG, 'artifact.mdx'), `# ${title}\n\nTest content.\n`, 'utf-8');
154
- writeFileSync(join(PLANS_DIR, TEST_SLUG, 'comments.json'), '[]', 'utf-8');
155
-
156
- // Verify files
157
- assert.equal(existsSync(join(PLANS_DIR, TEST_SLUG, 'artifact.mdx')), true);
158
- assert.equal(existsSync(join(PLANS_DIR, TEST_SLUG, 'meta.json')), true);
159
- assert.equal(existsSync(join(PLANS_DIR, TEST_SLUG, 'comments.json')), true);
160
-
161
- const metaRead = JSON.parse(readFileSync(join(PLANS_DIR, TEST_SLUG, 'meta.json'), 'utf-8'));
162
- assert.equal(metaRead.slug, TEST_SLUG);
163
- assert.equal(metaRead.title, title);
164
- });
165
- });
166
-
167
- // ── list flow tests ───────────────────────────────────────────────────────────
168
-
169
- describe('list flow', () => {
170
- const SLUG1 = 'test-list-1-' + Date.now();
171
- const SLUG2 = 'test-list-2-' + Date.now();
172
-
173
- beforeEach(() => {
174
- createTempPlan(SLUG1, {
175
- meta: { lastEdited: '2026-01-01T00:00:00.000Z', title: 'List Test One' },
176
- });
177
- createTempPlan(SLUG2, {
178
- meta: { lastEdited: '2026-06-01T00:00:00.000Z', title: 'List Test Two' },
179
- });
180
- });
181
-
182
- afterEach(() => {
183
- cleanupPlan(SLUG1);
184
- cleanupPlan(SLUG2);
185
- });
186
-
187
- test('reads artifacts directory and meta.json correctly', async () => {
188
- const planDir1 = join(PLANS_DIR, SLUG1);
189
- const planDir2 = join(PLANS_DIR, SLUG2);
190
-
191
- assert.equal(existsSync(join(planDir1, 'meta.json')), true);
192
- assert.equal(existsSync(join(planDir2, 'meta.json')), true);
193
-
194
- const meta1 = JSON.parse(readFileSync(join(planDir1, 'meta.json'), 'utf-8'));
195
- const meta2 = JSON.parse(readFileSync(join(planDir2, 'meta.json'), 'utf-8'));
196
-
197
- assert.equal(meta1.title, 'List Test One');
198
- assert.equal(meta2.title, 'List Test Two');
199
- // Sort by lastEdited: meta2 (June) should come before meta1 (January)
200
- const sorted = [meta1, meta2].sort((a, b) => new Date(b.lastEdited) - new Date(a.lastEdited));
201
- assert.equal(sorted[0].title, 'List Test Two');
202
- assert.equal(sorted[1].title, 'List Test One');
203
- });
204
- });
205
-
206
- // ── delete flow tests ────────────────────────────────────────────────────────
207
-
208
- describe('delete flow', () => {
209
- const TEST_SLUG = 'test-delete-artifact-' + Date.now();
210
-
211
- beforeEach(() => {
212
- createTempPlan(TEST_SLUG, { title: 'Delete Test Artifact' });
213
- });
214
-
215
- afterEach(() => {
216
- cleanupPlan(TEST_SLUG);
217
- });
218
-
219
- test('removes artifact directory', async () => {
220
- const planDir = join(PLANS_DIR, TEST_SLUG);
221
- assert.equal(existsSync(planDir), true);
222
-
223
- rmSync(planDir, { recursive: true });
224
- assert.equal(existsSync(planDir), false);
225
- });
226
-
227
- test('directory does not exist after delete', async () => {
228
- const planDir = join(PLANS_DIR, TEST_SLUG);
229
- rmSync(planDir, { recursive: true });
230
- assert.equal(existsSync(planDir), false);
231
- });
232
- });
233
-
234
- // ── export flow tests ───────────────────────────────────────────────────────
235
-
236
- describe('export flow', () => {
237
- const TEST_SLUG = 'test-export-artifact-' + Date.now();
238
- const EXPECTED_CONTENT = '# Export Test Artifact\n\nSome content here.\n';
239
-
240
- beforeEach(() => {
241
- createTempPlan(TEST_SLUG, { mdx: EXPECTED_CONTENT });
242
- });
243
-
244
- afterEach(() => {
245
- cleanupPlan(TEST_SLUG);
246
- });
247
-
248
- test('reads artifact.mdx content', async () => {
249
- const planDir = join(PLANS_DIR, TEST_SLUG);
250
- const content = readFileSync(join(planDir, 'artifact.mdx'), 'utf-8');
251
- assert.equal(content, EXPECTED_CONTENT);
252
- });
253
- });
254
-
255
- // ── help flow tests ──────────────────────────────────────────────────────────
256
-
257
- describe('help flow', () => {
258
- test('unknown subcommand returns false', async () => {
259
- const result = await runPlan(['unknown-cmd'], {});
260
- assert.equal(result, false);
261
- });
262
- });
263
-
264
- // ── Server tests ─────────────────────────────────────────────────────────────
265
- // These start a real HTTP server on a random port and make actual requests.
266
-
267
- describe('Local HTTP server', () => {
268
- const TEST_SLUG = 'test-server-artifact-' + Date.now();
269
- let serverInfo;
270
- let baseUrl;
271
-
272
- beforeEach(async () => {
273
- // Create a artifact for the server to serve
274
- createTempPlan(TEST_SLUG, {
275
- mdx: '# Test Server Artifact\n\nServer test content.\n',
276
- comments: JSON.stringify([
277
- {
278
- id: '1',
279
- sectionId: 'test',
280
- text: 'Test comment',
281
- author: 'tester',
282
- created: new Date().toISOString(),
283
- },
284
- ]),
285
- });
286
-
287
- // Start server on random port (0 = OS-assigned)
288
- serverInfo = await startServer(TEST_SLUG, join(PLANS_DIR, TEST_SLUG), 0);
289
- baseUrl = `http://127.0.0.1:${serverInfo.port}`;
290
- });
291
-
292
- afterEach(async () => {
293
- if (serverInfo) await serverInfo.close();
294
- cleanupPlan(TEST_SLUG);
295
- });
296
-
297
- test('GET /<slug>/ returns HTML', async () => {
298
- const res = await fetch(`${baseUrl}/${TEST_SLUG}/`);
299
- assert.equal(res.status, 200);
300
- assert.equal(res.headers.get('content-type').includes('text/html'), true);
301
- const html = await res.text();
302
- assert.equal(html.includes('Test Server Artifact'), true);
303
- });
304
-
305
- test('GET /api/artifact returns MDX', async () => {
306
- const res = await fetch(`${baseUrl}/api/artifact`);
307
- assert.equal(res.status, 200);
308
- assert.equal(res.headers.get('content-type').includes('text/plain'), true);
309
- const text = await res.text();
310
- assert.equal(text.includes('Test Server Artifact'), true);
311
- });
312
-
313
- test('GET /api/comments returns JSON', async () => {
314
- const res = await fetch(`${baseUrl}/api/comments`);
315
- assert.equal(res.status, 200);
316
- const contentType = res.headers.get('content-type');
317
- assert.equal(contentType.includes('application/json'), true);
318
- const comments = await res.json();
319
- assert.equal(Array.isArray(comments), true);
320
- assert.equal(comments.length > 0, true);
321
- });
322
-
323
- test('PUT /api/artifact saves MDX', async () => {
324
- const newContent = '# Updated Artifact\n\nUpdated content.\n';
325
- const res = await fetch(`${baseUrl}/api/artifact`, {
326
- method: 'PUT',
327
- body: newContent,
328
- headers: { 'Content-Type': 'text/plain' },
329
- });
330
- assert.equal(res.status, 200);
331
-
332
- // Verify file was updated
333
- const saved = readFileSync(join(PLANS_DIR, TEST_SLUG, 'artifact.mdx'), 'utf-8');
334
- assert.equal(saved, newContent);
335
- });
336
-
337
- test('POST /api/comments adds a comment', async () => {
338
- const initial = JSON.parse(
339
- readFileSync(join(PLANS_DIR, TEST_SLUG, 'comments.json'), 'utf-8')
340
- );
341
- const initialCount = initial.length;
342
-
343
- const res = await fetch(`${baseUrl}/api/comments`, {
344
- method: 'POST',
345
- headers: { 'Content-Type': 'application/json' },
346
- body: JSON.stringify({
347
- sectionId: 'intro',
348
- text: 'New comment',
349
- author: 'tester',
350
- }),
351
- });
352
- assert.equal(res.status, 200);
353
-
354
- const updated = JSON.parse(
355
- readFileSync(join(PLANS_DIR, TEST_SLUG, 'comments.json'), 'utf-8')
356
- );
357
- assert.equal(updated.length, initialCount + 1);
358
- });
359
-
360
- test('PUT /api/comments saves comments array', async () => {
361
- const newComments = [
362
- {
363
- id: '99',
364
- sectionId: 'intro',
365
- text: 'Replaced',
366
- author: 'test',
367
- created: new Date().toISOString(),
368
- },
369
- ];
370
- const res = await fetch(`${baseUrl}/api/comments`, {
371
- method: 'PUT',
372
- headers: { 'Content-Type': 'application/json' },
373
- body: JSON.stringify(newComments),
374
- });
375
- assert.equal(res.status, 200);
376
-
377
- const saved = JSON.parse(
378
- readFileSync(join(PLANS_DIR, TEST_SLUG, 'comments.json'), 'utf-8')
379
- );
380
- assert.deepEqual(saved, newComments);
381
- });
382
-
383
- test('returns 404 for unknown routes', async () => {
384
- const res = await fetch(`${baseUrl}/api/nonexistent`);
385
- assert.equal(res.status, 404);
386
- });
387
-
388
- test('CORS headers are set', async () => {
389
- const res = await fetch(`${baseUrl}/api/artifact`, { method: 'OPTIONS' });
390
- assert.equal(res.headers.get('access-control-allow-origin'), '*');
391
- assert.equal(
392
- res.headers.get('access-control-allow-methods').includes('GET'),
393
- true
394
- );
395
- assert.equal(
396
- res.headers.get('access-control-allow-methods').includes('PUT'),
397
- true
398
- );
399
- assert.equal(
400
- res.headers.get('access-control-allow-methods').includes('POST'),
401
- true
402
- );
403
- });
404
-
405
- test('server falls back to next port if busy', async () => {
406
- // Start two servers — second should get a different port
407
- const server1 = await startServer(TEST_SLUG, join(PLANS_DIR, TEST_SLUG), 4321);
408
- const server2 = await startServer(TEST_SLUG, join(PLANS_DIR, TEST_SLUG), 4321);
409
-
410
- assert.notEqual(server1.port, server2.port);
411
- assert.ok(server1.port >= 4321 && server1.port <= 4330);
412
- assert.ok(server2.port >= 4321 && server2.port <= 4330);
413
-
414
- await server1.close();
415
- await server2.close();
416
- });
417
- });
418
-
419
- // ── Template library tests ─────────────────────────────────────────────────────
420
- // Tests for artifact-templates.mjs — getTemplate, getTemplateNames, printTemplates, etc.
421
-
422
- import {
423
- getTemplate,
424
- getTemplateNames,
425
- listTemplates,
426
- printTemplates,
427
- substitute,
428
- buildVars,
429
- } from './artifact-templates.mjs';
430
-
431
- describe('Template library — getTemplate', () => {
432
- test('getTemplate("feature-design") returns content', () => {
433
- const tpl = getTemplate('feature-design');
434
- assert.ok(tpl, 'should return a template object');
435
- assert.equal(tpl.name, 'feature-design');
436
- assert.ok(tpl.description, 'should have a description');
437
- assert.ok(tpl.content, 'should have non-null content');
438
- assert.ok(tpl.content.includes('Feature:'), 'content should contain title placeholder');
439
- assert.ok(tpl.content.includes('{{title}}'), 'content should have {{title}} variable');
440
- assert.ok(['built-in', 'library'].includes(tpl.source), 'source should be valid');
441
- });
442
-
443
- test('getTemplate("bug-investigation") returns content', () => {
444
- const tpl = getTemplate('bug-investigation');
445
- assert.ok(tpl, 'should return a template object');
446
- assert.ok(tpl.content, 'should have non-null content');
447
- assert.ok(tpl.content.includes('Bug:'), 'content should reference bug theme');
448
- });
449
-
450
- test('getTemplate("decision-record") returns content', () => {
451
- const tpl = getTemplate('decision-record');
452
- assert.ok(tpl, 'should return a template object');
453
- assert.ok(tpl.content, 'should have non-null content');
454
- assert.ok(tpl.content.includes('Decision:'), 'content should reference ADR theme');
455
- });
456
-
457
- test('getTemplate("blank") returns null content', () => {
458
- const tpl = getTemplate('blank');
459
- assert.ok(tpl, 'should return a template object for blank');
460
- assert.equal(tpl.name, 'blank');
461
- assert.strictEqual(tpl.content, null, 'blank template should have null content');
462
- assert.equal(tpl.source, 'built-in');
463
- });
464
-
465
- test('getTemplate("nonexistent") returns null', () => {
466
- assert.strictEqual(getTemplate('nonexistent'), null);
467
- });
468
-
469
- test('getTemplate("") returns null', () => {
470
- assert.strictEqual(getTemplate(''), null);
471
- });
472
-
473
- test('getTemplate(null) returns null', () => {
474
- assert.strictEqual(getTemplate(null), null);
475
- });
476
-
477
- test('getTemplate is case-insensitive', () => {
478
- const tpl = getTemplate('FEATURE-DESIGN');
479
- assert.ok(tpl, 'uppercase name should still match');
480
- assert.equal(tpl.name, 'feature-design');
481
- });
482
- });
483
-
484
- describe('Template library — getTemplateNames', () => {
485
- test('includes all 4 built-in names', () => {
486
- const names = getTemplateNames();
487
- assert.ok(Array.isArray(names));
488
- assert.ok(names.includes('blank'), 'should include blank');
489
- assert.ok(names.includes('feature-design'), 'should include feature-design');
490
- assert.ok(names.includes('bug-investigation'), 'should include bug-investigation');
491
- assert.ok(names.includes('decision-record'), 'should include decision-record');
492
- assert.equal(names.length, 4, 'should have exactly 4 built-in names');
493
- });
494
-
495
- test('names are sorted alphabetically', () => {
496
- const names = getTemplateNames();
497
- const sorted = [...names].sort();
498
- assert.deepEqual(names, sorted, 'names should already be sorted');
499
- });
500
- });
501
-
502
- describe('Template library — listTemplates', () => {
503
- test('returns array with name, description, source for each', () => {
504
- const all = listTemplates();
505
- assert.ok(Array.isArray(all));
506
- assert.ok(all.length >= 4, 'should have at least 4 templates');
507
- for (const t of all) {
508
- assert.ok(t.name, `template should have a name (got ${JSON.stringify(t)})`);
509
- assert.ok(t.description, `template "${t.name}" should have a description`);
510
- assert.ok(['built-in', 'library'].includes(t.source), `template "${t.name}" should have valid source`);
511
- }
512
- });
513
- });
514
-
515
- describe('Template library — printTemplates', () => {
516
- test('writes output to console (capture stdout)', () => {
517
- const logs = [];
518
- const origLog = console.log;
519
- console.log = (...args) => logs.push(args.join(' '));
520
-
521
- printTemplates();
522
-
523
- console.log = origLog;
524
-
525
- assert.ok(logs.length > 0, 'should have logged something');
526
- const output = logs.join('\n');
527
- assert.ok(output.includes('feature-design'), 'output should mention feature-design');
528
- assert.ok(output.includes('bug-investigation'), 'output should mention bug-investigation');
529
- assert.ok(output.includes('decision-record'), 'output should mention decision-record');
530
- assert.ok(output.includes('blank'), 'output should mention blank');
531
- assert.ok(output.includes('--template'), 'output should mention --template flag');
532
- });
533
- });
534
-
535
- describe('Template library — substitute', () => {
536
- test('replaces {{title}} and {{slug}} in content', () => {
537
- const content = '# {{title}}\n\nSlug: {{slug}}';
538
- const result = substitute(content, { title: 'My Feature', slug: 'my-feature' });
539
- assert.equal(result, '# My Feature\n\nSlug: my-feature');
540
- });
541
-
542
- test('leaves unknown variables as-is', () => {
543
- const content = 'Hello {{name}}';
544
- const result = substitute(content, {});
545
- assert.equal(result, 'Hello {{name}}');
546
- });
547
-
548
- test('replaces all occurrences of the same variable', () => {
549
- const content = '{{x}}-{{x}}-{{x}}';
550
- const result = substitute(content, { x: 'foo' });
551
- assert.equal(result, 'foo-foo-foo');
552
- });
553
-
554
- test('handles empty content', () => {
555
- assert.equal(substitute('', { a: 'b' }), '');
556
- });
557
-
558
- test('handles empty variables', () => {
559
- assert.equal(substitute('hello', {}), 'hello');
560
- });
561
- });
562
-
563
- describe('Template library — buildVars', () => {
564
- test('builds standard variable set', () => {
565
- const vars = buildVars({ slug: 'my-feature', title: 'My Feature' });
566
- assert.equal(vars.title, 'My Feature');
567
- assert.equal(vars.slug, 'my-feature');
568
- assert.ok(vars.author, 'author should be set');
569
- assert.ok(vars.created, 'created should be set');
570
- assert.ok(vars.lastEdited, 'lastEdited should be set');
571
- assert.equal(vars.lastEdited, vars.created, 'created and lastEdited should match for a new artifact');
572
- });
573
-
574
- test('auto-generates title from slug when not provided', () => {
575
- const vars = buildVars({ slug: 'my-feature' });
576
- assert.equal(vars.title, 'my-feature');
577
- });
578
-
579
- test('dates are valid ISO strings', () => {
580
- const vars = buildVars({ slug: 'test' });
581
- const created = new Date(vars.created);
582
- assert.ok(created instanceof Date && !isNaN(created), 'created should be a valid date');
583
- });
584
- });
585
-
586
- describe('Template library — template content via runPlan', () => {
587
- const TEST_SLUG = 'test-tpl-new-' + Date.now();
588
-
589
- afterEach(() => {
590
- // Clean up artifact directory if created
591
- const planDir = join(PLANS_DIR, TEST_SLUG);
592
- if (existsSync(planDir)) rmSync(planDir, { recursive: true, force: true });
593
- });
594
-
595
- test('runPlan new with invalid template returns false', async () => {
596
- const result = await runPlan(
597
- ['new', TEST_SLUG, '--template', 'nonexistent-template-name-xyz'],
598
- {}
599
- );
600
- assert.equal(result, false, 'should return false for invalid template');
601
-
602
- // Verify no artifact directory was left behind
603
- const planDir = join(PLANS_DIR, TEST_SLUG);
604
- assert.equal(existsSync(planDir), false, 'should clean up on error');
605
- });
606
- });
607
-
608
- // ── Comment regression: createPlan → POST /api/comments ──────────────────────
609
- // This regression test ensures that a freshly created artifact (with comments.json
610
- // as an empty array []) can receive the first comment via POST without crashing.
611
- // Previously, createPlan wrote {schemaVersion: 2, threads: []} which caused
612
- // JSON.parse + .push() to fail on the first comment.
613
-
614
- describe('Comment regression: createPlan → POST /api/comments', () => {
615
- const TEST_SLUG = 'test-comment-regression-' + Date.now();
616
- let serverInfo;
617
- let baseUrl;
618
-
619
- afterEach(async () => {
620
- if (serverInfo) await serverInfo.close();
621
- cleanupPlan(TEST_SLUG);
622
- });
623
-
624
- test('first comment on freshly created artifact works (array shape)', async () => {
625
- // Create a artifact directory — replicating what createPlan() does after the fix
626
- const planDir = join(PLANS_DIR, TEST_SLUG);
627
- mkdirSync(planDir, { recursive: true });
628
-
629
- const now = new Date().toISOString();
630
- const meta = {
631
- title: 'Comment Regression Test',
632
- slug: TEST_SLUG,
633
- status: 'draft',
634
- author: 'tester',
635
- created: now,
636
- lastEdited: now,
637
- };
638
- writeFileSync(join(planDir, 'meta.json'), JSON.stringify(meta, null, 2), 'utf-8');
639
- writeFileSync(join(planDir, 'artifact.mdx'), '# Comment Regression Test\n\nTest.\n', 'utf-8');
640
- // Write as [] — same shape as the fixed createPlan() now produces
641
- writeFileSync(join(planDir, 'comments.json'), '[]', 'utf-8');
642
-
643
- // Start server (port 0 = OS-assigned)
644
- serverInfo = await startServer(TEST_SLUG, planDir, 0);
645
- baseUrl = `http://127.0.0.1:${serverInfo.port}`;
646
-
647
- // POST a comment — this is the path that crashed before the fix
648
- const res = await fetch(`${baseUrl}/api/comments`, {
649
- method: 'POST',
650
- headers: { 'Content-Type': 'application/json' },
651
- body: JSON.stringify({
652
- sectionId: 'regression-test',
653
- text: 'First comment on a fresh artifact',
654
- author: 'test-gate',
655
- }),
656
- });
657
- assert.equal(res.status, 200, 'POST should succeed without crashing');
658
-
659
- // Verify the comment was saved by re-reading the file
660
- const updated = JSON.parse(readFileSync(join(planDir, 'comments.json'), 'utf-8'));
661
- assert.equal(Array.isArray(updated), true, 'comments should be an array');
662
- assert.equal(updated.length, 1, 'should have exactly 1 comment');
663
- assert.equal(updated[0].sectionId, 'regression-test');
664
- assert.equal(updated[0].text, 'First comment on a fresh artifact');
665
- assert.equal(updated[0].author, 'test-gate');
666
-
667
- // Verify the file is still valid JSON
668
- assert.doesNotThrow(() => JSON.parse(readFileSync(join(planDir, 'comments.json'), 'utf-8')));
669
-
670
- // Verify GET returns the comments array correctly
671
- const getRes = await fetch(`${baseUrl}/api/comments`);
672
- assert.equal(getRes.status, 200);
673
- const getData = await getRes.json();
674
- assert.equal(Array.isArray(getData), true);
675
- assert.equal(getData.length, 1);
676
-
677
- // Verify PUT replaces the array cleanly
678
- const replacement = [
679
- {
680
- id: 'replaced',
681
- sectionId: 'intro',
682
- text: 'Replaced comment',
683
- author: 'test',
684
- created: new Date().toISOString(),
685
- },
686
- ];
687
- const putRes = await fetch(`${baseUrl}/api/comments`, {
688
- method: 'PUT',
689
- headers: { 'Content-Type': 'application/json' },
690
- body: JSON.stringify(replacement),
691
- });
692
- assert.equal(putRes.status, 200);
693
-
694
- const afterPut = JSON.parse(readFileSync(join(planDir, 'comments.json'), 'utf-8'));
695
- assert.equal(Array.isArray(afterPut), true);
696
- assert.equal(afterPut.length, 1);
697
- assert.equal(afterPut[0].id, 'replaced');
698
- });
699
- });
700
-
701
- // ── htmx-friendly RESTful routes ───────────────────────────────────────────────
702
- // New slug-scoped routes designed for htmx (and the new HTML template).
703
- // GET /api/<slug>/artifact → MDX text/plain
704
- // PUT /api/<slug>/artifact → save MDX (form data or raw body)
705
- // GET /api/<slug>/comments → JSON (default) or HTML (?format=html)
706
- // POST /api/<slug>/comments → add comment, returns <li> HTML
707
- // PUT /api/<slug>/comments → replace comments array (JSON)
708
- // GET /api/<slug>/count → <span class="count">N</span>
709
- // GET /htmx.min.js → self-hosted htmx library
710
- // The cross-slug protection: any path with a different slug in the URL
711
- // returns 403 to prevent the server from being tricked into serving
712
- // data for a different artifact.
713
-
714
- describe('htmx-friendly RESTful routes', () => {
715
- const TEST_SLUG = 'test-htmx-routes-' + Date.now();
716
- let serverInfo;
717
- let baseUrl;
718
-
719
- beforeEach(async () => {
720
- createTempPlan(TEST_SLUG, {
721
- mdx: '# Htmx Routes Test\n\n## Overview\n\nThe first section.\n\n## Goals\n\n- First\n- Second\n',
722
- comments: JSON.stringify([
723
- {
724
- id: 'existing-1',
725
- sectionId: 'overview',
726
- text: 'Existing comment on overview',
727
- author: 'tester',
728
- timestamp: '2026-01-01T00:00:00.000Z',
729
- },
730
- {
731
- id: 'existing-2',
732
- sectionId: 'goals',
733
- text: 'Existing comment on goals',
734
- author: 'tester',
735
- timestamp: '2026-01-02T00:00:00.000Z',
736
- },
737
- ]),
738
- });
739
-
740
- serverInfo = await startServer(TEST_SLUG, join(PLANS_DIR, TEST_SLUG), 0);
741
- baseUrl = `http://127.0.0.1:${serverInfo.port}`;
742
- });
743
-
744
- afterEach(async () => {
745
- if (serverInfo) await serverInfo.close();
746
- cleanupPlan(TEST_SLUG);
747
- });
748
-
749
- // ── Artifact routes ────────────────────────────────────────────────────
750
- test('GET /api/<slug>/artifact returns MDX as text/plain', async () => {
751
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/artifact`);
752
- assert.equal(res.status, 200);
753
- assert.equal(res.headers.get('content-type').includes('text/plain'), true);
754
- const text = await res.text();
755
- assert.equal(text.includes('Htmx Routes Test'), true);
756
- });
757
-
758
- test('PUT /api/<slug>/artifact with form data saves MDX', async () => {
759
- const newContent = '# Updated Htmx Artifact\n\nNew content.\n';
760
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/artifact`, {
761
- method: 'PUT',
762
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
763
- body: 'content=' + encodeURIComponent(newContent),
764
- });
765
- assert.equal(res.status, 200);
766
- const saved = readFileSync(join(PLANS_DIR, TEST_SLUG, 'artifact.mdx'), 'utf-8');
767
- assert.equal(saved, newContent);
768
- });
769
-
770
- test('PUT /api/<slug>/artifact with raw text body saves MDX', async () => {
771
- const newContent = '# Raw Body Artifact\n\nRaw body content.\n';
772
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/artifact`, {
773
- method: 'PUT',
774
- headers: { 'Content-Type': 'text/plain' },
775
- body: newContent,
776
- });
777
- assert.equal(res.status, 200);
778
- const saved = readFileSync(join(PLANS_DIR, TEST_SLUG, 'artifact.mdx'), 'utf-8');
779
- assert.equal(saved, newContent);
780
- });
781
-
782
- test('PUT /api/<slug>/artifact updates lastEdited in meta.json', async () => {
783
- const metaPath = join(PLANS_DIR, TEST_SLUG, 'meta.json');
784
- const before = JSON.parse(readFileSync(metaPath, 'utf-8'));
785
- // Wait a tick so the timestamp actually advances
786
- await new Promise((r) => setTimeout(r, 10));
787
- await fetch(`${baseUrl}/api/${TEST_SLUG}/artifact`, {
788
- method: 'PUT',
789
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
790
- body: 'content=' + encodeURIComponent('# updated\n'),
791
- });
792
- const after = JSON.parse(readFileSync(metaPath, 'utf-8'));
793
- assert.ok(after.lastEdited >= before.lastEdited, 'lastEdited should advance or stay the same');
794
- });
795
-
796
- // ── Comments routes ────────────────────────────────────────────────
797
- test('GET /api/<slug>/comments returns JSON array by default', async () => {
798
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`);
799
- assert.equal(res.status, 200);
800
- assert.equal(res.headers.get('content-type').includes('application/json'), true);
801
- const data = await res.json();
802
- assert.ok(Array.isArray(data), 'should return array');
803
- assert.equal(data.length, 2);
804
- });
805
-
806
- test('GET /api/<slug>/comments?format=html returns HTML fragments', async () => {
807
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments?format=html`);
808
- assert.equal(res.status, 200);
809
- assert.equal(res.headers.get('content-type').includes('text/html'), true);
810
- const html = await res.text();
811
- assert.ok(html.includes('<li'), 'should contain <li> elements');
812
- assert.ok(html.includes('class="comment"'), 'should have class="comment"');
813
- assert.ok(html.includes('existing-1'), 'should include the existing comment ids');
814
- assert.ok(html.includes('existing-2'), true);
815
- // Should be XSS-safe — escape the comment text
816
- assert.ok(!html.includes('<script>'), 'no raw script tags');
817
- });
818
-
819
- test('GET /api/<slug>/comments?format=html&sectionId=... filters by section', async () => {
820
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments?format=html&sectionId=overview`);
821
- assert.equal(res.status, 200);
822
- const html = await res.text();
823
- assert.ok(html.includes('existing-1'), 'overview comment should be present');
824
- assert.ok(!html.includes('existing-2'), 'goals comment should be filtered out');
825
- });
826
-
827
- test('GET /api/<slug>/comments?format=html with no comments returns the empty marker', async () => {
828
- // Make a fresh artifact with no comments
829
- cleanupPlan(TEST_SLUG);
830
- createTempPlan(TEST_SLUG + '-empty', { mdx: '# Empty\n', comments: '[]' });
831
- const info = await startServer(TEST_SLUG + '-empty', join(PLANS_DIR, TEST_SLUG + '-empty'), 0);
832
- try {
833
- const res = await fetch(`http://127.0.0.1:${info.port}/api/${TEST_SLUG}-empty/comments?format=html`);
834
- assert.equal(res.status, 200);
835
- const html = await res.text();
836
- assert.ok(html.includes('No comments yet'), 'should show empty message');
837
- } finally {
838
- await info.close();
839
- cleanupPlan(TEST_SLUG + '-empty');
840
- }
841
- });
842
-
843
- test('POST /api/<slug>/comments with form data adds comment and returns <li> HTML', async () => {
844
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
845
- method: 'POST',
846
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
847
- body: 'sectionId=overview&text=' + encodeURIComponent('New form comment') + '&author=alice',
848
- });
849
- assert.equal(res.status, 200);
850
- assert.equal(res.headers.get('content-type').includes('text/html'), true);
851
- const html = await res.text();
852
- assert.ok(html.startsWith('<li'), 'response should be a <li> element');
853
- assert.ok(html.includes('New form comment'), 'should include the new text');
854
- assert.ok(html.includes('alice'), 'should include the author');
855
- assert.ok(html.includes('class="comment"'), 'should have class="comment"');
856
-
857
- // Verify the file was updated
858
- const updated = JSON.parse(
859
- readFileSync(join(PLANS_DIR, TEST_SLUG, 'comments.json'), 'utf-8')
860
- );
861
- assert.equal(updated.length, 3);
862
- assert.equal(updated[2].text, 'New form comment');
863
- assert.equal(updated[2].sectionId, 'overview');
864
- assert.equal(updated[2].author, 'alice');
865
- });
866
-
867
- test('POST /api/<slug>/comments with JSON body also works', async () => {
868
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
869
- method: 'POST',
870
- headers: { 'Content-Type': 'application/json' },
871
- body: JSON.stringify({ sectionId: 'goals', text: 'JSON comment', author: 'bob' }),
872
- });
873
- assert.equal(res.status, 200);
874
- const html = await res.text();
875
- assert.ok(html.includes('JSON comment'), true);
876
-
877
- const updated = JSON.parse(
878
- readFileSync(join(PLANS_DIR, TEST_SLUG, 'comments.json'), 'utf-8')
879
- );
880
- assert.equal(updated[updated.length - 1].author, 'bob');
881
- });
882
-
883
- // In v1, POST without sectionId returned 400. In v2, the endpoint has
884
- // been merged: requests without sectionId are treated as v2 canvas
885
- // comments (no elementId) and accepted with 200. The v1 contract was
886
- // "sectionId is required", but the new behavior is "sectionId is
887
- // optional — used only for v1 back-compat".
888
- test('POST /api/<slug>/comments without sectionId (v2: canvas-pinned) returns 200', async () => {
889
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
890
- method: 'POST',
891
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
892
- body: 'text=no-section',
893
- });
894
- assert.equal(res.status, 200);
895
- });
896
-
897
- test('POST /api/<slug>/comments escapes HTML in text (XSS safety)', async () => {
898
- const xss = '<script>alert(1)</script>';
899
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
900
- method: 'POST',
901
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
902
- body: 'sectionId=overview&text=' + encodeURIComponent(xss) + '&author=evil',
903
- });
904
- assert.equal(res.status, 200);
905
- const html = await res.text();
906
- assert.ok(!html.includes('<script>alert(1)</script>'), 'raw <script> should be escaped');
907
- assert.ok(html.includes('&lt;script&gt;'), 'should contain the escaped form');
908
- });
909
-
910
- test('PUT /api/<slug>/comments replaces the whole array', async () => {
911
- const replacement = [
912
- { id: 'new-1', sectionId: 'goals', text: 'Replacement', author: 'replacer', timestamp: new Date().toISOString() },
913
- ];
914
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
915
- method: 'PUT',
916
- headers: { 'Content-Type': 'application/json' },
917
- body: JSON.stringify(replacement),
918
- });
919
- assert.equal(res.status, 200);
920
-
921
- const saved = JSON.parse(
922
- readFileSync(join(PLANS_DIR, TEST_SLUG, 'comments.json'), 'utf-8')
923
- );
924
- assert.deepEqual(saved, replacement);
925
- });
926
-
927
- test('PUT /api/<slug>/comments with non-array body returns 400', async () => {
928
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
929
- method: 'PUT',
930
- headers: { 'Content-Type': 'application/json' },
931
- body: JSON.stringify({ not: 'an array' }),
932
- });
933
- assert.equal(res.status, 400);
934
- });
935
-
936
- // ── Count route ────────────────────────────────────────────────────
937
- test('GET /api/<slug>/count?sectionId=... returns <span> with the right count', async () => {
938
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/count?sectionId=overview`);
939
- assert.equal(res.status, 200);
940
- assert.equal(res.headers.get('content-type').includes('text/html'), true);
941
- const html = await res.text();
942
- assert.equal(html, '<span class="count">1</span>');
943
- });
944
-
945
- test('GET /api/<slug>/count without sectionId returns total count', async () => {
946
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/count`);
947
- assert.equal(res.status, 200);
948
- const html = await res.text();
949
- assert.equal(html, '<span class="count">2</span>');
950
- });
951
-
952
- // ── Self-hosted htmx ───────────────────────────────────────────────
953
- test('GET /htmx.min.js serves the self-hosted htmx library', async () => {
954
- const res = await fetch(`${baseUrl}/htmx.min.js`);
955
- assert.equal(res.status, 200);
956
- const ct = res.headers.get('content-type');
957
- assert.ok(ct.includes('application/javascript'), `expected application/javascript, got ${ct}`);
958
- const body = await res.text();
959
- assert.ok(body.includes('htmx'), 'body should mention htmx');
960
- // The actual htmx library defines a function called htmx
961
- assert.ok(body.includes('var htmx=') || body.includes('const htmx='), 'should define a top-level htmx');
962
- });
963
-
964
- // ── Cross-slug protection ──────────────────────────────────────────
965
- test('cross-slug access returns 403', async () => {
966
- const res = await fetch(`${baseUrl}/api/wrong-slug/comments`);
967
- assert.equal(res.status, 403);
968
- });
969
-
970
- test('cross-slug PUT also returns 403', async () => {
971
- const res = await fetch(`${baseUrl}/api/wrong-slug/artifact`, {
972
- method: 'PUT',
973
- body: 'evil=true',
974
- });
975
- assert.equal(res.status, 403);
976
- });
977
-
978
- // ── 404 for unknown resources under a valid slug ───────────────────
979
- test('GET /api/<slug>/unknown returns 404 (not 403)', async () => {
980
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/unknown-resource`);
981
- assert.equal(res.status, 404);
982
- });
983
- });
984
-
985
- // ── HTML template smoke tests ─────────────────────────────────────────────────
986
- // These tests regenerate artifact.html and verify the resulting HTML
987
- // uses htmx attributes (and does NOT use fetch() in JS for the
988
- // save/comment flows). The script tag for htmx should reference
989
- // the local /htmx.min.js path.
990
-
991
- describe('HTML template uses htmx', () => {
992
- const TEST_SLUG = 'test-tpl-htmx-' + Date.now();
993
-
994
- afterEach(() => {
995
- cleanupPlan(TEST_SLUG);
996
- });
997
-
998
- // Test the v1 template (artifact.html.template) directly. The v1 template is
999
- // htmx-based; the v2 canvas template (artifact.canvas.template) is a separate
1000
- // file with its own tests below.
1001
- test('v1 artifact.html.template uses htmx (read directly)', () => {
1002
- const tplPath = join(TEMPLATES_DIR, 'artifact.html.template');
1003
- const tpl = readFileSync(tplPath, 'utf-8');
1004
-
1005
- // Should load htmx from the local path
1006
- assert.ok(tpl.includes('src="/htmx.min.js"'), 'v1 should load htmx from local path');
1007
-
1008
- // Should use htmx attributes for the comment list (auto-load)
1009
- assert.ok(/hx-get="\/api\/[^"]+\/comments\?format=html"/.test(tpl),
1010
- 'v1 should have hx-get on the comment list');
1011
- assert.ok(/hx-trigger="load[^"]*"/.test(tpl),
1012
- 'v1 should have hx-trigger="load" on the comment list');
1013
-
1014
- // Should use htmx for the comment form
1015
- assert.ok(/hx-post="\/api\/[^"]+\/comments"/.test(tpl), 'v1 should have hx-post for comments');
1016
- assert.ok(/hx-target="#comment-list"/.test(tpl), 'v1 comment form should target the list');
1017
- assert.ok(/hx-swap="beforeend"/.test(tpl), 'v1 comment form should append (beforeend)');
1018
-
1019
- // The autosave textarea is created by enterEditMode() via setAttribute.
1020
- assert.ok(tpl.includes("setAttribute('hx-put', '/api/") ||
1021
- tpl.includes('setAttribute("hx-put", "/api/'),
1022
- 'v1 JS should set hx-put on the autosave textarea');
1023
- assert.ok(tpl.includes("setAttribute('hx-trigger'") ||
1024
- tpl.includes('setAttribute("hx-trigger"'),
1025
- 'v1 JS should set hx-trigger on the autosave textarea');
1026
-
1027
- // The v1 template should not call fetch() directly (htmx does the work).
1028
- const noCommentFetch = tpl
1029
- .split('\n')
1030
- .filter((line) => !line.trim().startsWith('//') && !line.trim().startsWith('*'))
1031
- .filter((line) => !/<!--/.test(line) && !/^[\s]*\*/.test(line))
1032
- .join('\n');
1033
- assert.ok(!/fetch\s*\(/.test(noCommentFetch), 'v1 non-comment code should not call fetch() directly');
1034
- });
1035
-
1036
- test('regenerated artifact.html uses the v2 canvas template by default', async () => {
1037
- const planDir = join(PLANS_DIR, TEST_SLUG);
1038
- mkdirSync(planDir, { recursive: true });
1039
- writeFileSync(
1040
- join(planDir, 'meta.json'),
1041
- JSON.stringify({
1042
- title: 'V2 Default Test',
1043
- slug: TEST_SLUG,
1044
- status: 'draft',
1045
- author: 'tester',
1046
- created: '2026-06-01T00:00:00.000Z',
1047
- lastEdited: '2026-06-01T00:00:00.000Z',
1048
- })
1049
- );
1050
- writeFileSync(join(planDir, 'artifact.mdx'), '# V2 Default Test\n\n## Section\n\nHello.\n');
1051
- writeFileSync(join(planDir, 'comments.json'), '[]');
1052
-
1053
- await regenerateHtml(TEST_SLUG);
1054
-
1055
- const html = readFileSync(join(planDir, 'artifact.html'), 'utf-8');
1056
-
1057
- // The default regenerated artifact.html should be the v2 canvas view
1058
- assert.ok(html.includes('id="canvas"'), 'default artifact.html should be the v2 canvas view');
1059
- assert.ok(html.includes('id="connections-layer"'),
1060
- 'default artifact.html should have the SVG connections layer');
1061
-
1062
- // Should have all placeholders substituted
1063
- assert.ok(!html.includes('{{slug}}'), 'slug placeholder should be substituted');
1064
- assert.ok(!html.includes('{{title}}'), 'title placeholder should be substituted');
1065
- assert.ok(!html.includes('{{planJson}}'), 'planJson placeholder should be substituted');
1066
- assert.ok(!html.includes('{{commentsJson}}'), 'commentsJson placeholder should be substituted');
1067
- assert.ok(!html.includes('{{metaJson}}'), 'metaJson placeholder should be substituted');
1068
- assert.ok(!html.includes('{{canvasJson}}'), 'canvasJson placeholder should be substituted');
1069
- assert.ok(!html.includes('{{status}}'), 'status placeholder should be substituted');
1070
- assert.ok(!html.includes('{{created}}'), 'created placeholder should be substituted');
1071
- assert.ok(!html.includes('{{lastEdited}}'), 'lastEdited placeholder should be substituted');
1072
- assert.ok(!html.includes('{{author}}'), 'author placeholder should be substituted');
1073
- });
1074
-
1075
- test('template is smaller than the pre-htmx version (regression check)', () => {
1076
- const tplPath = join(TEMPLATES_DIR, 'artifact.html.template');
1077
- const lines = readFileSync(tplPath, 'utf-8').split('\n').length;
1078
- assert.ok(lines < 1039, `template should be smaller than the original 1039 lines, got ${lines}`);
1079
- });
1080
- });
1081
-
1082
- // ── Canvas endpoints (v2) ────────────────────────────────────────────────────
1083
- // New slug-scoped routes for the v2 canvas state.
1084
- // GET /api/<slug>/canvas → full JSON canvas state
1085
- // PUT /api/<slug>/canvas → save canvas state
1086
- // GET /api/<slug>/elements → just the elements array
1087
- // POST /api/<slug>/elements → add element, returns it
1088
- // PUT /api/<slug>/elements/<id> → update element
1089
- // DELETE /api/<slug>/elements/<id> → remove element + cascade
1090
- // GET /api/<slug>/connections → just the connections array
1091
- // POST /api/<slug>/connections → add connection
1092
- // DELETE /api/<slug>/connections/<id> → remove connection
1093
- // GET /api/<slug>/comments?elementId= → canvas comments
1094
- // POST /api/<slug>/comments → add canvas comment
1095
- // PUT /api/<slug>/comments/<id> → update comment (add reply)
1096
- // DELETE /api/<slug>/comments/<id> → remove comment
1097
- // GET /api/<slug>/markdown-export → derived markdown from canvas
1098
-
1099
- import {
1100
- loadOrMigrateCanvas,
1101
- canvasToMarkdown,
1102
- emptyCanvas,
1103
- CANVAS_SCHEMA_VERSION,
1104
- makeElementId,
1105
- makeConnectionId,
1106
- makeCommentId,
1107
- makeReplyId,
1108
- } from './artifact.mjs';
1109
-
1110
- describe('Canvas helpers (pure functions)', () => {
1111
- test('emptyCanvas returns a v2 schema with empty arrays', () => {
1112
- const c = emptyCanvas('Test');
1113
- assert.equal(c.schemaVersion, CANVAS_SCHEMA_VERSION);
1114
- assert.equal(c.title, 'Test');
1115
- assert.deepEqual(c.elements, []);
1116
- assert.deepEqual(c.connections, []);
1117
- assert.deepEqual(c.comments, []);
1118
- assert.deepEqual(c.viewport, { x: 0, y: 0, zoom: 1 });
1119
- });
1120
-
1121
- test('emptyCanvas with no title uses "Untitled artifact"', () => {
1122
- const c = emptyCanvas();
1123
- assert.equal(c.title, 'Untitled artifact');
1124
- });
1125
-
1126
- test('id generators produce namespaced ids', () => {
1127
- assert.ok(makeElementId().startsWith('el_'), 'element id should start with el_');
1128
- assert.ok(makeConnectionId().startsWith('conn_'), 'connection id should start with conn_');
1129
- assert.ok(makeCommentId().startsWith('c_'), 'comment id should start with c_');
1130
- assert.ok(makeReplyId().startsWith('r_'), 'reply id should start with r_');
1131
- // They should be unique across calls.
1132
- const a = makeElementId();
1133
- const b = makeElementId();
1134
- assert.notEqual(a, b);
1135
- });
1136
-
1137
- test('canvasToMarkdown produces a sensible header', () => {
1138
- const c = emptyCanvas('My Title');
1139
- const md = canvasToMarkdown(c);
1140
- assert.ok(md.startsWith('# My Title'), `expected "# My Title" header, got: ${md.slice(0, 30)}`);
1141
- });
1142
-
1143
- test('canvasToMarkdown serializes text elements', () => {
1144
- const c = emptyCanvas('T');
1145
- c.elements.push({
1146
- id: 'el_1', type: 'text', x: 0, y: 0, width: 100, height: 100,
1147
- title: 'Intro', content: 'Hello world',
1148
- });
1149
- const md = canvasToMarkdown(c);
1150
- assert.ok(md.includes('## Intro'), 'should include element title as section');
1151
- assert.ok(md.includes('Hello world'), 'should include element content');
1152
- });
1153
-
1154
- test('canvasToMarkdown serializes code with language fence', () => {
1155
- const c = emptyCanvas('T');
1156
- c.elements.push({
1157
- id: 'el_1', type: 'code', x: 0, y: 0, width: 100, height: 100,
1158
- language: 'javascript', content: 'const x = 1;',
1159
- });
1160
- const md = canvasToMarkdown(c);
1161
- assert.ok(md.includes('```javascript'), 'should include the language fence');
1162
- assert.ok(md.includes('const x = 1;'), 'should include the code');
1163
- });
1164
-
1165
- test('canvasToMarkdown serializes diagram as mermaid block', () => {
1166
- const c = emptyCanvas('T');
1167
- c.elements.push({
1168
- id: 'el_1', type: 'diagram', x: 0, y: 0, width: 100, height: 100,
1169
- content: 'graph LR\nA --> B',
1170
- });
1171
- const md = canvasToMarkdown(c);
1172
- assert.ok(md.includes('```mermaid'), 'should use mermaid fence');
1173
- assert.ok(md.includes('A --> B'), 'should include diagram source');
1174
- });
1175
-
1176
- test('canvasToMarkdown serializes ui-mockup button', () => {
1177
- const c = emptyCanvas('T');
1178
- c.elements.push({
1179
- id: 'el_1', type: 'ui-mockup', x: 0, y: 0, width: 100, height: 100,
1180
- component: 'button', label: 'Submit',
1181
- });
1182
- const md = canvasToMarkdown(c);
1183
- assert.ok(md.includes('[Submit]'), 'should include button label in markdown');
1184
- });
1185
-
1186
- test('canvasToMarkdown serializes comments as a notes section', () => {
1187
- const c = emptyCanvas('T');
1188
- c.comments.push({
1189
- id: 'c_1', x: 0, y: 0, elementId: null,
1190
- author: 'Alice', text: 'looks good', created: '2026-06-18T00:00:00Z', thread: [],
1191
- });
1192
- const md = canvasToMarkdown(c);
1193
- assert.ok(md.includes('## Notes'), 'should have a Notes section');
1194
- assert.ok(md.includes('**Alice**'), 'should include author');
1195
- assert.ok(md.includes('looks good'), 'should include comment text');
1196
- });
1197
-
1198
- test('canvasToMarkdown handles empty canvas', () => {
1199
- const md = canvasToMarkdown(emptyCanvas('Empty'));
1200
- assert.ok(md.includes('# Empty'));
1201
- // No element sections, but should still have the header.
1202
- });
1203
- });
1204
-
1205
- describe('loadOrMigrateCanvas', () => {
1206
- const TEST_SLUG = 'test-canvas-migrate-' + Date.now();
1207
-
1208
- afterEach(() => {
1209
- cleanupPlan(TEST_SLUG);
1210
- });
1211
-
1212
- test('returns existing artifact.json if present', () => {
1213
- const planDir = join(PLANS_DIR, TEST_SLUG);
1214
- mkdirSync(planDir, { recursive: true });
1215
- const canvas = {
1216
- schemaVersion: 2, title: 'Existing', elements: [], connections: [], comments: [],
1217
- viewport: { x: 0, y: 0, zoom: 1 },
1218
- };
1219
- writeFileSync(join(planDir, 'artifact.json'), JSON.stringify(canvas));
1220
-
1221
- const result = loadOrMigrateCanvas(planDir, 'Existing');
1222
- assert.equal(result.title, 'Existing');
1223
- });
1224
-
1225
- test('migrates artifact.mdx to canvas on first read', () => {
1226
- const planDir = join(PLANS_DIR, TEST_SLUG);
1227
- mkdirSync(planDir, { recursive: true });
1228
- const mdx = '# Hello\n\n## World\n\nFoo bar\n';
1229
- writeFileSync(join(planDir, 'artifact.mdx'), mdx);
1230
-
1231
- const result = loadOrMigrateCanvas(planDir, 'Migrated');
1232
- assert.equal(result.schemaVersion, 2);
1233
- assert.equal(result.elements.length, 1);
1234
- assert.equal(result.elements[0].type, 'text');
1235
- assert.ok(result.elements[0].content.includes('Foo bar'));
1236
-
1237
- // After migration, artifact.json should be on disk.
1238
- assert.equal(existsSync(join(planDir, 'artifact.json')), true);
1239
- });
1240
-
1241
- test('returns empty canvas if no artifact.json and no artifact.mdx', () => {
1242
- const planDir = join(PLANS_DIR, TEST_SLUG);
1243
- mkdirSync(planDir, { recursive: true });
1244
-
1245
- const result = loadOrMigrateCanvas(planDir, 'Brand New');
1246
- assert.equal(result.title, 'Brand New');
1247
- assert.equal(result.elements.length, 0);
1248
-
1249
- // Should have created artifact.json.
1250
- assert.equal(existsSync(join(planDir, 'artifact.json')), true);
1251
- });
1252
-
1253
- test('backfills missing arrays on a partial canvas', () => {
1254
- const planDir = join(PLANS_DIR, TEST_SLUG);
1255
- mkdirSync(planDir, { recursive: true });
1256
- // Write a partial canvas (missing comments)
1257
- writeFileSync(join(planDir, 'artifact.json'), JSON.stringify({
1258
- schemaVersion: 2, title: 'Partial',
1259
- elements: [], connections: [],
1260
- }));
1261
-
1262
- const result = loadOrMigrateCanvas(planDir, 'Partial');
1263
- assert.deepEqual(result.comments, []);
1264
- assert.ok(result.viewport);
1265
- });
1266
- });
1267
-
1268
- describe('Canvas HTTP endpoints', () => {
1269
- const TEST_SLUG = 'test-canvas-api-' + Date.now();
1270
- let serverInfo;
1271
- let baseUrl;
1272
-
1273
- beforeEach(async () => {
1274
- createTempPlan(TEST_SLUG, {
1275
- mdx: '# Canvas Test\n\n## Section 1\n\nHello.\n',
1276
- comments: '[]',
1277
- });
1278
- serverInfo = await startServer(TEST_SLUG, join(PLANS_DIR, TEST_SLUG), 0);
1279
- baseUrl = `http://127.0.0.1:${serverInfo.port}`;
1280
- });
1281
-
1282
- afterEach(async () => {
1283
- if (serverInfo) await serverInfo.close();
1284
- cleanupPlan(TEST_SLUG);
1285
- });
1286
-
1287
- // ── Canvas state ─────────────────────────────────────────────
1288
- test('GET /api/<slug>/canvas returns full v2 canvas state', async () => {
1289
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/canvas`);
1290
- assert.equal(res.status, 200);
1291
- const data = await res.json();
1292
- assert.equal(data.schemaVersion, 2);
1293
- // Title comes from meta.json (auto-migration). The test slug derives
1294
- // the title as "Test Canvas Api <timestamp>".
1295
- assert.ok(data.title.startsWith('Test Canvas Api'),
1296
- `expected title to start with 'Test Canvas Api', got: ${data.title}`);
1297
- assert.ok(Array.isArray(data.elements));
1298
- assert.ok(Array.isArray(data.connections));
1299
- assert.ok(Array.isArray(data.comments));
1300
- assert.ok(data.viewport);
1301
- // Auto-migrated from mdx: should have one text element
1302
- assert.equal(data.elements.length, 1);
1303
- assert.equal(data.elements[0].type, 'text');
1304
- });
1305
-
1306
- test('GET /api/<slug>/canvas auto-migrates from mdx on first call', async () => {
1307
- const planDir = join(PLANS_DIR, TEST_SLUG);
1308
- // First, clear the auto-generated artifact.json so we test the migration path
1309
- const jsonPath = join(planDir, 'artifact.json');
1310
- if (existsSync(jsonPath)) rmSync(jsonPath);
1311
- assert.equal(existsSync(jsonPath), false);
1312
-
1313
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/canvas`);
1314
- assert.equal(res.status, 200);
1315
- const data = await res.json();
1316
- // The migration creates a text element from the mdx
1317
- assert.equal(data.elements.length, 1);
1318
- assert.equal(data.elements[0].type, 'text');
1319
- // After this call, artifact.json should exist
1320
- assert.equal(existsSync(jsonPath), true);
1321
- });
1322
-
1323
- test('PUT /api/<slug>/canvas saves the full canvas state', async () => {
1324
- const newCanvas = {
1325
- schemaVersion: 2,
1326
- title: 'Updated',
1327
- elements: [
1328
- { id: 'el_a', type: 'text', x: 0, y: 0, width: 200, height: 100, content: 'A' },
1329
- { id: 'el_b', type: 'text', x: 300, y: 0, width: 200, height: 100, content: 'B' },
1330
- ],
1331
- connections: [],
1332
- comments: [],
1333
- viewport: { x: 10, y: 20, zoom: 1.5 },
1334
- };
1335
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/canvas`, {
1336
- method: 'PUT',
1337
- headers: { 'Content-Type': 'application/json' },
1338
- body: JSON.stringify(newCanvas),
1339
- });
1340
- assert.equal(res.status, 200);
1341
-
1342
- // Verify the file was written
1343
- const saved = JSON.parse(readFileSync(join(PLANS_DIR, TEST_SLUG, 'artifact.json'), 'utf-8'));
1344
- assert.equal(saved.title, 'Updated');
1345
- assert.equal(saved.elements.length, 2);
1346
- assert.equal(saved.viewport.zoom, 1.5);
1347
- });
1348
-
1349
- test('PUT /api/<slug>/canvas normalizes partial canvas', async () => {
1350
- // No elements, connections, or comments arrays — should be added.
1351
- const partial = { schemaVersion: 2, title: 'Sparse' };
1352
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/canvas`, {
1353
- method: 'PUT',
1354
- headers: { 'Content-Type': 'application/json' },
1355
- body: JSON.stringify(partial),
1356
- });
1357
- assert.equal(res.status, 200);
1358
- const saved = JSON.parse(readFileSync(join(PLANS_DIR, TEST_SLUG, 'artifact.json'), 'utf-8'));
1359
- assert.deepEqual(saved.elements, []);
1360
- assert.deepEqual(saved.connections, []);
1361
- assert.deepEqual(saved.comments, []);
1362
- assert.ok(saved.viewport);
1363
- });
1364
-
1365
- test('PUT /api/<slug>/canvas with invalid JSON returns 400', async () => {
1366
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/canvas`, {
1367
- method: 'PUT',
1368
- headers: { 'Content-Type': 'application/json' },
1369
- body: 'not json{',
1370
- });
1371
- assert.equal(res.status, 400);
1372
- });
1373
-
1374
- // ── Elements ─────────────────────────────────────────────────
1375
- test('GET /api/<slug>/elements returns the elements array', async () => {
1376
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`);
1377
- assert.equal(res.status, 200);
1378
- const arr = await res.json();
1379
- assert.ok(Array.isArray(arr));
1380
- });
1381
-
1382
- test('POST /api/<slug>/elements adds an element and returns it', async () => {
1383
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1384
- method: 'POST',
1385
- headers: { 'Content-Type': 'application/json' },
1386
- body: JSON.stringify({ type: 'text', x: 100, y: 200, width: 300, height: 150, title: 'Hi', content: 'Body' }),
1387
- });
1388
- assert.equal(res.status, 200);
1389
- const el = await res.json();
1390
- assert.ok(el.id.startsWith('el_'));
1391
- assert.equal(el.type, 'text');
1392
- assert.equal(el.x, 100);
1393
- assert.equal(el.title, 'Hi');
1394
- assert.equal(el.content, 'Body');
1395
- });
1396
-
1397
- test('PUT /api/<slug>/elements/<id> updates an element', async () => {
1398
- // First add an element
1399
- const addRes = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1400
- method: 'POST',
1401
- headers: { 'Content-Type': 'application/json' },
1402
- body: JSON.stringify({ type: 'text', x: 0, y: 0, width: 100, height: 100, content: 'old' }),
1403
- });
1404
- const el = await addRes.json();
1405
-
1406
- // Now update it
1407
- const putRes = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements/${el.id}`, {
1408
- method: 'PUT',
1409
- headers: { 'Content-Type': 'application/json' },
1410
- body: JSON.stringify({ x: 999, content: 'new' }),
1411
- });
1412
- assert.equal(putRes.status, 200);
1413
- const updated = await putRes.json();
1414
- assert.equal(updated.x, 999);
1415
- assert.equal(updated.content, 'new');
1416
- // Untouched fields preserved
1417
- assert.equal(updated.y, 0);
1418
- assert.equal(updated.width, 100);
1419
- });
1420
-
1421
- test('PUT /api/<slug>/elements/<id> for unknown id returns 404', async () => {
1422
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements/el_does_not_exist`, {
1423
- method: 'PUT',
1424
- headers: { 'Content-Type': 'application/json' },
1425
- body: JSON.stringify({ x: 1 }),
1426
- });
1427
- assert.equal(res.status, 404);
1428
- });
1429
-
1430
- test('DELETE /api/<slug>/elements/<id> removes the element', async () => {
1431
- const addRes = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1432
- method: 'POST',
1433
- headers: { 'Content-Type': 'application/json' },
1434
- body: JSON.stringify({ type: 'text', x: 0, y: 0, width: 100, height: 100 }),
1435
- });
1436
- const el = await addRes.json();
1437
- const delRes = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements/${el.id}`, { method: 'DELETE' });
1438
- assert.equal(delRes.status, 200);
1439
-
1440
- // Verify it's gone
1441
- const getRes = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`);
1442
- const arr = await getRes.json();
1443
- assert.equal(arr.find((e) => e.id === el.id), undefined);
1444
- });
1445
-
1446
- test('DELETE /api/<slug>/elements/<id> cascades to connections and comments', async () => {
1447
- // Add two elements
1448
- const e1 = (await (await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1449
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1450
- body: JSON.stringify({ type: 'text', x: 0, y: 0, width: 100, height: 100 }),
1451
- })).json());
1452
- const e2 = (await (await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1453
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1454
- body: JSON.stringify({ type: 'text', x: 200, y: 0, width: 100, height: 100 }),
1455
- })).json());
1456
-
1457
- // Connect them
1458
- await fetch(`${baseUrl}/api/${TEST_SLUG}/connections`, {
1459
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1460
- body: JSON.stringify({ from: e1.id, to: e2.id, type: 'arrow' }),
1461
- });
1462
-
1463
- // Add a comment pinned to e1
1464
- await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
1465
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1466
- body: JSON.stringify({ x: 50, y: 50, elementId: e1.id, text: 'note on e1' }),
1467
- });
1468
-
1469
- // Delete e1
1470
- const delRes = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements/${e1.id}`, { method: 'DELETE' });
1471
- assert.equal(delRes.status, 200);
1472
-
1473
- // The connection should be gone
1474
- const connRes = await fetch(`${baseUrl}/api/${TEST_SLUG}/connections`);
1475
- const conns = await connRes.json();
1476
- assert.equal(conns.length, 0, 'connections referencing deleted element should be removed');
1477
-
1478
- // The comment should still exist but be detached
1479
- // Use elementId= (empty) to explicitly request v2 endpoint.
1480
- const cRes = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments?elementId=`);
1481
- const cs = await cRes.json();
1482
- assert.equal(cs.length, 1, 'comment should remain (now unpinned)');
1483
- assert.equal(cs[0].elementId, null, 'comment elementId should be nulled out');
1484
- });
1485
-
1486
- // ── Connections ─────────────────────────────────────────────
1487
- test('GET /api/<slug>/connections returns the connections array', async () => {
1488
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/connections`);
1489
- assert.equal(res.status, 200);
1490
- const arr = await res.json();
1491
- assert.ok(Array.isArray(arr));
1492
- });
1493
-
1494
- test('POST /api/<slug>/connections creates a connection', async () => {
1495
- const e1 = (await (await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1496
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1497
- body: JSON.stringify({ type: 'text', x: 0, y: 0, width: 100, height: 100 }),
1498
- })).json());
1499
- const e2 = (await (await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1500
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1501
- body: JSON.stringify({ type: 'text', x: 200, y: 0, width: 100, height: 100 }),
1502
- })).json());
1503
-
1504
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/connections`, {
1505
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1506
- body: JSON.stringify({ from: e1.id, to: e2.id, type: 'arrow', label: 'leads to' }),
1507
- });
1508
- assert.equal(res.status, 200);
1509
- const conn = await res.json();
1510
- assert.ok(conn.id.startsWith('conn_'));
1511
- assert.equal(conn.from, e1.id);
1512
- assert.equal(conn.to, e2.id);
1513
- assert.equal(conn.type, 'arrow');
1514
- assert.equal(conn.label, 'leads to');
1515
- });
1516
-
1517
- test('POST /api/<slug>/connections with missing endpoint returns 400', async () => {
1518
- const e1 = (await (await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1519
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1520
- body: JSON.stringify({ type: 'text', x: 0, y: 0, width: 100, height: 100 }),
1521
- })).json());
1522
-
1523
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/connections`, {
1524
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1525
- body: JSON.stringify({ from: e1.id, to: 'el_nonexistent' }),
1526
- });
1527
- assert.equal(res.status, 400);
1528
- });
1529
-
1530
- test('DELETE /api/<slug>/connections/<id> removes a connection', async () => {
1531
- const e1 = (await (await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1532
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1533
- body: JSON.stringify({ type: 'text', x: 0, y: 0, width: 100, height: 100 }),
1534
- })).json());
1535
- const e2 = (await (await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1536
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1537
- body: JSON.stringify({ type: 'text', x: 200, y: 0, width: 100, height: 100 }),
1538
- })).json());
1539
-
1540
- const addRes = await fetch(`${baseUrl}/api/${TEST_SLUG}/connections`, {
1541
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1542
- body: JSON.stringify({ from: e1.id, to: e2.id }),
1543
- });
1544
- const conn = await addRes.json();
1545
-
1546
- const delRes = await fetch(`${baseUrl}/api/${TEST_SLUG}/connections/${conn.id}`, { method: 'DELETE' });
1547
- assert.equal(delRes.status, 200);
1548
-
1549
- const getRes = await fetch(`${baseUrl}/api/${TEST_SLUG}/connections`);
1550
- const arr = await getRes.json();
1551
- assert.equal(arr.length, 0);
1552
- });
1553
-
1554
- test('DELETE /api/<slug>/connections/<id> for unknown id returns 404', async () => {
1555
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/connections/conn_does_not_exist`, { method: 'DELETE' });
1556
- assert.equal(res.status, 404);
1557
- });
1558
-
1559
- // ── Comments (v2 canvas shape) ───────────────────────────────
1560
- test('GET /api/<slug>/comments returns canvas comments (empty array by default)', async () => {
1561
- // Pass elementId= to explicitly request the v2 endpoint. Without it,
1562
- // the request falls through to the v1 handler for back-compat.
1563
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments?elementId=`);
1564
- assert.equal(res.status, 200);
1565
- const arr = await res.json();
1566
- assert.ok(Array.isArray(arr));
1567
- });
1568
-
1569
- test('POST /api/<slug>/comments creates a canvas comment', async () => {
1570
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
1571
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1572
- body: JSON.stringify({ x: 100, y: 200, text: 'hi', author: 'Alice' }),
1573
- });
1574
- assert.equal(res.status, 200);
1575
- const c = await res.json();
1576
- assert.ok(c.id.startsWith('c_'));
1577
- assert.equal(c.x, 100);
1578
- assert.equal(c.y, 200);
1579
- assert.equal(c.text, 'hi');
1580
- assert.equal(c.author, 'Alice');
1581
- assert.equal(c.elementId, null);
1582
- assert.ok(Array.isArray(c.thread));
1583
- });
1584
-
1585
- test('POST /api/<slug>/comments attaches to an element', async () => {
1586
- const e = (await (await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1587
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1588
- body: JSON.stringify({ type: 'text', x: 0, y: 0, width: 100, height: 100 }),
1589
- })).json());
1590
-
1591
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
1592
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1593
- body: JSON.stringify({ x: 50, y: 50, elementId: e.id, text: 'note' }),
1594
- });
1595
- const c = await res.json();
1596
- assert.equal(c.elementId, e.id);
1597
- });
1598
-
1599
- test('GET /api/<slug>/comments?elementId=... filters by element', async () => {
1600
- const e1 = (await (await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1601
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1602
- body: JSON.stringify({ type: 'text', x: 0, y: 0, width: 100, height: 100 }),
1603
- })).json());
1604
- const e2 = (await (await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1605
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1606
- body: JSON.stringify({ type: 'text', x: 200, y: 0, width: 100, height: 100 }),
1607
- })).json());
1608
-
1609
- await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
1610
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1611
- body: JSON.stringify({ x: 0, y: 0, elementId: e1.id, text: 'on e1' }),
1612
- });
1613
- await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
1614
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1615
- body: JSON.stringify({ x: 0, y: 0, elementId: e2.id, text: 'on e2' }),
1616
- });
1617
-
1618
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments?elementId=${e1.id}`);
1619
- const arr = await res.json();
1620
- assert.equal(arr.length, 1);
1621
- assert.equal(arr[0].text, 'on e1');
1622
- });
1623
-
1624
- test('GET /api/<slug>/comments?elementId=nil returns only canvas-pinned comments', async () => {
1625
- await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
1626
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1627
- body: JSON.stringify({ x: 50, y: 50, text: 'pinned to canvas' }),
1628
- });
1629
- const e = (await (await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1630
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1631
- body: JSON.stringify({ type: 'text', x: 0, y: 0, width: 100, height: 100 }),
1632
- })).json());
1633
- await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
1634
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1635
- body: JSON.stringify({ x: 0, y: 0, elementId: e.id, text: 'on element' }),
1636
- });
1637
-
1638
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments?elementId=nil`);
1639
- const arr = await res.json();
1640
- assert.equal(arr.length, 1);
1641
- assert.equal(arr[0].text, 'pinned to canvas');
1642
- });
1643
-
1644
- test('PUT /api/<slug>/comments/<id> adds a reply', async () => {
1645
- const addRes = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
1646
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1647
- body: JSON.stringify({ x: 0, y: 0, text: 'original' }),
1648
- });
1649
- const c = await addRes.json();
1650
-
1651
- const replyRes = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments/${c.id}`, {
1652
- method: 'PUT', headers: { 'Content-Type': 'application/json' },
1653
- body: JSON.stringify({ reply: 'a reply', replyAuthor: 'ai' }),
1654
- });
1655
- assert.equal(replyRes.status, 200);
1656
- const updated = await replyRes.json();
1657
- assert.equal(updated.thread.length, 1);
1658
- assert.equal(updated.thread[0].text, 'a reply');
1659
- assert.equal(updated.thread[0].author, 'ai');
1660
- });
1661
-
1662
- test('DELETE /api/<slug>/comments/<id> removes a comment', async () => {
1663
- const addRes = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
1664
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1665
- body: JSON.stringify({ x: 0, y: 0, text: 'doomed' }),
1666
- });
1667
- const c = await addRes.json();
1668
-
1669
- const delRes = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments/${c.id}`, { method: 'DELETE' });
1670
- assert.equal(delRes.status, 200);
1671
-
1672
- const listRes = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`);
1673
- const arr = await listRes.json();
1674
- assert.equal(arr.length, 0);
1675
- });
1676
-
1677
- test('PUT /api/<slug>/comments/<id> for unknown id returns 404', async () => {
1678
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments/c_does_not_exist`, {
1679
- method: 'PUT', headers: { 'Content-Type': 'application/json' },
1680
- body: JSON.stringify({ text: 'x' }),
1681
- });
1682
- assert.equal(res.status, 404);
1683
- });
1684
-
1685
- // ── Markdown export ──────────────────────────────────────────
1686
- test('GET /api/<slug>/markdown-export returns derived markdown', async () => {
1687
- // Set up a known canvas
1688
- const canvas = {
1689
- schemaVersion: 2,
1690
- title: 'Export Test',
1691
- elements: [
1692
- { id: 'el_x', type: 'text', x: 0, y: 0, width: 100, height: 100, title: 'Header', content: 'Body text' },
1693
- { id: 'el_y', type: 'code', x: 0, y: 100, width: 100, height: 100, language: 'js', content: 'var a;' },
1694
- ],
1695
- connections: [],
1696
- comments: [],
1697
- viewport: { x: 0, y: 0, zoom: 1 },
1698
- };
1699
- await fetch(`${baseUrl}/api/${TEST_SLUG}/canvas`, {
1700
- method: 'PUT', headers: { 'Content-Type': 'application/json' },
1701
- body: JSON.stringify(canvas),
1702
- });
1703
-
1704
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/markdown-export`);
1705
- assert.equal(res.status, 200);
1706
- assert.equal(res.headers.get('content-type').includes('text/markdown'), true);
1707
- const md = await res.text();
1708
- assert.ok(md.includes('# Export Test'), 'should include title');
1709
- assert.ok(md.includes('## Header'), 'should include element title as section');
1710
- assert.ok(md.includes('Body text'), 'should include text content');
1711
- assert.ok(md.includes('```js'), 'should include code with language fence');
1712
- assert.ok(md.includes('var a;'), 'should include code content');
1713
- });
1714
-
1715
- // ── Cross-slug ───────────────────────────────────────────────
1716
- test('cross-slug canvas access returns 403', async () => {
1717
- const res = await fetch(`${baseUrl}/api/wrong-slug/canvas`);
1718
- assert.equal(res.status, 403);
1719
- });
1720
-
1721
- test('cross-slug element add returns 403', async () => {
1722
- const res = await fetch(`${baseUrl}/api/wrong-slug/elements`, {
1723
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1724
- body: JSON.stringify({ type: 'text' }),
1725
- });
1726
- assert.equal(res.status, 403);
1727
- });
1728
- });
1729
-
1730
- // ── htmx content-negotiation tests ───────────────────────────────────────────
1731
- // Verify that the canvas endpoints return HTML when the request comes from
1732
- // htmx (Accept: text/html or HX-Request: true), and JSON otherwise (for the
1733
- // AI tool, tests, and CLI scripts).
1734
-
1735
- import {
1736
- renderElementHTML,
1737
- renderConnectionHTML,
1738
- renderCommentPinHTML,
1739
- renderCommentThreadHTML,
1740
- renderReplyHTML,
1741
- } from './artifact.mjs';
1742
-
1743
- describe('htmx content negotiation on canvas endpoints', () => {
1744
- const TEST_SLUG = 'test-htmx-negotiate-' + Date.now();
1745
- let serverInfo;
1746
- let baseUrl;
1747
-
1748
- beforeEach(async () => {
1749
- createTempPlan(TEST_SLUG, { mdx: '# Neg Test\n', comments: '[]' });
1750
- serverInfo = await startServer(TEST_SLUG, join(PLANS_DIR, TEST_SLUG), 0);
1751
- baseUrl = `http://127.0.0.1:${serverInfo.port}`;
1752
- });
1753
-
1754
- afterEach(async () => {
1755
- if (serverInfo) await serverInfo.close();
1756
- cleanupPlan(TEST_SLUG);
1757
- });
1758
-
1759
- // ── POST /api/<slug>/elements ────────────────────────────────────
1760
- test('POST /elements with Accept: text/html returns element HTML', async () => {
1761
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1762
- method: 'POST',
1763
- headers: {
1764
- 'Content-Type': 'application/json',
1765
- 'Accept': 'text/html',
1766
- },
1767
- body: JSON.stringify({ type: 'text', x: 10, y: 20, width: 200, height: 100, title: 'Hi' }),
1768
- });
1769
- assert.equal(res.status, 200);
1770
- assert.ok(res.headers.get('content-type').includes('text/html'),
1771
- 'should return text/html');
1772
- const html = await res.text();
1773
- assert.ok(html.startsWith('<div class="element"'),
1774
- `response should start with <div class="element">, got: ${html.slice(0, 60)}`);
1775
- assert.ok(html.includes('data-element-id="el_'),
1776
- 'should include data-element-id attribute');
1777
- assert.ok(html.includes('data-element-type="text"'),
1778
- 'should include data-element-type="text"');
1779
- assert.ok(html.includes('style="left:10px;top:20px;width:200px;height:100px"'),
1780
- 'should include inline position styles');
1781
- assert.ok(html.includes('Hi'), 'should include title text');
1782
- });
1783
-
1784
- test('POST /elements with HX-Request: true returns element HTML', async () => {
1785
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1786
- method: 'POST',
1787
- headers: {
1788
- 'Content-Type': 'application/json',
1789
- 'HX-Request': 'true',
1790
- },
1791
- body: JSON.stringify({ type: 'text', title: 'Hi' }),
1792
- });
1793
- assert.equal(res.status, 200);
1794
- assert.ok(res.headers.get('content-type').includes('text/html'));
1795
- const html = await res.text();
1796
- assert.ok(html.includes('class="element"'),
1797
- 'should return element HTML on HX-Request header');
1798
- });
1799
-
1800
- test('POST /elements with Accept: application/json returns JSON', async () => {
1801
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1802
- method: 'POST',
1803
- headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
1804
- body: JSON.stringify({ type: 'text', title: 'Hi' }),
1805
- });
1806
- assert.equal(res.status, 200);
1807
- assert.ok(res.headers.get('content-type').includes('application/json'));
1808
- const el = await res.json();
1809
- assert.equal(el.type, 'text');
1810
- assert.equal(el.title, 'Hi');
1811
- });
1812
-
1813
- test('POST /elements with no Accept header returns JSON (backwards compat)', async () => {
1814
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1815
- method: 'POST',
1816
- headers: { 'Content-Type': 'application/json' },
1817
- body: JSON.stringify({ type: 'text', title: 'Hi' }),
1818
- });
1819
- assert.equal(res.status, 200);
1820
- assert.ok(res.headers.get('content-type').includes('application/json'),
1821
- 'default (no Accept) should return JSON for backwards compat');
1822
- });
1823
-
1824
- // ── PUT /api/<slug>/elements/<id> ────────────────────────────────
1825
- test('PUT /elements/<id> with Accept: text/html returns updated element HTML', async () => {
1826
- // First add via JSON
1827
- const add = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1828
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1829
- body: JSON.stringify({ type: 'text', title: 'Old' }),
1830
- });
1831
- const el = await add.json();
1832
-
1833
- const put = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements/${el.id}`, {
1834
- method: 'PUT',
1835
- headers: { 'Content-Type': 'application/json', 'Accept': 'text/html' },
1836
- body: JSON.stringify({ title: 'New' }),
1837
- });
1838
- assert.equal(put.status, 200);
1839
- assert.ok(put.headers.get('content-type').includes('text/html'));
1840
- const html = await put.text();
1841
- assert.ok(html.includes('New'), 'should reflect updated title');
1842
- assert.ok(html.includes('data-element-id="' + el.id + '"'),
1843
- 'should keep the same element id');
1844
- });
1845
-
1846
- test('PUT /elements/<id> with Accept: application/json returns JSON', async () => {
1847
- const add = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1848
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1849
- body: JSON.stringify({ type: 'text', title: 'Old' }),
1850
- });
1851
- const el = await add.json();
1852
-
1853
- const put = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements/${el.id}`, {
1854
- method: 'PUT', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
1855
- body: JSON.stringify({ title: 'New' }),
1856
- });
1857
- const updated = await put.json();
1858
- assert.equal(updated.title, 'New');
1859
- });
1860
-
1861
- // ── DELETE /api/<slug>/elements/<id> ─────────────────────────────
1862
- test('DELETE /elements/<id> with Accept: text/html returns empty 200', async () => {
1863
- const add = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1864
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1865
- body: JSON.stringify({ type: 'text' }),
1866
- });
1867
- const el = await add.json();
1868
-
1869
- const del = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements/${el.id}`, {
1870
- method: 'DELETE', headers: { 'Accept': 'text/html' },
1871
- });
1872
- assert.equal(del.status, 200);
1873
- assert.ok(del.headers.get('content-type').includes('text/html'));
1874
- const html = await del.text();
1875
- assert.equal(html, '', 'htmx DELETE should return empty body so swap="delete" can remove the node');
1876
-
1877
- // Verify it's actually gone
1878
- const get = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`);
1879
- const arr = await get.json();
1880
- assert.equal(arr.find((e) => e.id === el.id), undefined);
1881
- });
1882
-
1883
- test('DELETE /elements/<id> with Accept: application/json returns JSON ok', async () => {
1884
- const add = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1885
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1886
- body: JSON.stringify({ type: 'text' }),
1887
- });
1888
- const el = await add.json();
1889
-
1890
- const del = await fetch(`${baseUrl}/api/${TEST_SLUG}/elements/${el.id}`, {
1891
- method: 'DELETE', headers: { 'Accept': 'application/json' },
1892
- });
1893
- const result = await del.json();
1894
- assert.equal(result.ok, true);
1895
- assert.equal(result.removed, el.id);
1896
- });
1897
-
1898
- // ── POST /api/<slug>/connections ─────────────────────────────────
1899
- test('POST /connections with Accept: text/html returns SVG fragment', async () => {
1900
- const e1 = (await (await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1901
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1902
- body: JSON.stringify({ type: 'text' }),
1903
- })).json());
1904
- const e2 = (await (await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1905
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1906
- body: JSON.stringify({ type: 'text', x: 100 }),
1907
- })).json());
1908
-
1909
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/connections`, {
1910
- method: 'POST',
1911
- headers: { 'Content-Type': 'application/json', 'Accept': 'text/html' },
1912
- body: JSON.stringify({ from: e1.id, to: e2.id, type: 'arrow' }),
1913
- });
1914
- assert.equal(res.status, 200);
1915
- const html = await res.text();
1916
- assert.ok(html.startsWith('<g class="connection"'),
1917
- `should be an SVG <g> fragment, got: ${html.slice(0, 60)}`);
1918
- assert.ok(html.includes('data-connection-id="conn_'));
1919
- assert.ok(html.includes('data-from="' + e1.id + '"'));
1920
- assert.ok(html.includes('data-to="' + e2.id + '"'));
1921
- assert.ok(html.includes('data-type="arrow"'));
1922
- });
1923
-
1924
- test('POST /connections with Accept: application/json returns JSON', async () => {
1925
- const e1 = (await (await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1926
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1927
- body: JSON.stringify({ type: 'text' }),
1928
- })).json());
1929
- const e2 = (await (await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1930
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1931
- body: JSON.stringify({ type: 'text', x: 100 }),
1932
- })).json());
1933
-
1934
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/connections`, {
1935
- method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
1936
- body: JSON.stringify({ from: e1.id, to: e2.id }),
1937
- });
1938
- const conn = await res.json();
1939
- assert.equal(conn.from, e1.id);
1940
- assert.equal(conn.to, e2.id);
1941
- });
1942
-
1943
- test('DELETE /connections/<id> with Accept: text/html returns empty 200', async () => {
1944
- const e1 = (await (await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1945
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1946
- body: JSON.stringify({ type: 'text' }),
1947
- })).json());
1948
- const e2 = (await (await fetch(`${baseUrl}/api/${TEST_SLUG}/elements`, {
1949
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1950
- body: JSON.stringify({ type: 'text', x: 100 }),
1951
- })).json());
1952
- const add = await fetch(`${baseUrl}/api/${TEST_SLUG}/connections`, {
1953
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1954
- body: JSON.stringify({ from: e1.id, to: e2.id }),
1955
- });
1956
- const conn = await add.json();
1957
-
1958
- const del = await fetch(`${baseUrl}/api/${TEST_SLUG}/connections/${conn.id}`, {
1959
- method: 'DELETE', headers: { 'Accept': 'text/html' },
1960
- });
1961
- assert.equal(del.status, 200);
1962
- assert.equal(await del.text(), '');
1963
- });
1964
-
1965
- // ── POST /api/<slug>/comments ────────────────────────────────────
1966
- test('POST /comments with Accept: text/html returns pin HTML', async () => {
1967
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
1968
- method: 'POST',
1969
- headers: { 'Content-Type': 'application/json', 'Accept': 'text/html' },
1970
- body: JSON.stringify({ x: 100, y: 200, text: 'note', author: 'Alice' }),
1971
- });
1972
- assert.equal(res.status, 200);
1973
- assert.ok(res.headers.get('content-type').includes('text/html'));
1974
- const html = await res.text();
1975
- assert.ok(html.startsWith('<div class="comment-pin"'),
1976
- `should be a comment-pin <div>, got: ${html.slice(0, 60)}`);
1977
- assert.ok(html.includes('data-comment-id="c_'));
1978
- assert.ok(html.includes('style="left:100px;top:200px"'),
1979
- 'should include inline position styles');
1980
- assert.ok(html.includes('title="note"'),
1981
- 'should include the text as a tooltip');
1982
- });
1983
-
1984
- test('POST /comments with Accept: application/json returns JSON', async () => {
1985
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
1986
- method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
1987
- body: JSON.stringify({ x: 0, y: 0, text: 'note', author: 'Bob' }),
1988
- });
1989
- const c = await res.json();
1990
- assert.equal(c.text, 'note');
1991
- assert.equal(c.author, 'Bob');
1992
- });
1993
-
1994
- // ── PUT /api/<slug>/comments/<id> (add reply) ────────────────────
1995
- test('PUT /comments/<id> with Accept: text/html returns full thread HTML', async () => {
1996
- const add = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
1997
- method: 'POST', headers: { 'Content-Type': 'application/json' },
1998
- body: JSON.stringify({ x: 0, y: 0, text: 'original', author: 'Alice' }),
1999
- });
2000
- const c = await add.json();
2001
-
2002
- const reply = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments/${c.id}`, {
2003
- method: 'PUT',
2004
- headers: { 'Content-Type': 'application/json', 'Accept': 'text/html' },
2005
- body: JSON.stringify({ reply: 'a reply', replyAuthor: 'ai' }),
2006
- });
2007
- assert.equal(reply.status, 200);
2008
- assert.ok(reply.headers.get('content-type').includes('text/html'));
2009
- const html = await reply.text();
2010
- assert.ok(html.startsWith('<li class="comment"'),
2011
- `should be a <li class="comment">, got: ${html.slice(0, 60)}`);
2012
- assert.ok(html.includes('original'), 'should include original comment');
2013
- assert.ok(html.includes('a reply'), 'should include the new reply');
2014
- assert.ok(html.includes('class="reply"'), 'should mark reply with class="reply"');
2015
- });
2016
-
2017
- test('PUT /comments/<id> with Accept: application/json returns updated JSON', async () => {
2018
- const add = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
2019
- method: 'POST', headers: { 'Content-Type': 'application/json' },
2020
- body: JSON.stringify({ x: 0, y: 0, text: 'original' }),
2021
- });
2022
- const c = await add.json();
2023
-
2024
- const reply = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments/${c.id}`, {
2025
- method: 'PUT', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
2026
- body: JSON.stringify({ reply: 'a reply', replyAuthor: 'ai' }),
2027
- });
2028
- const updated = await reply.json();
2029
- assert.equal(updated.thread.length, 1);
2030
- assert.equal(updated.thread[0].text, 'a reply');
2031
- });
2032
-
2033
- test('DELETE /comments/<id> with Accept: text/html returns empty 200', async () => {
2034
- const add = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments`, {
2035
- method: 'POST', headers: { 'Content-Type': 'application/json' },
2036
- body: JSON.stringify({ x: 0, y: 0, text: 'doomed' }),
2037
- });
2038
- const c = await add.json();
2039
-
2040
- const del = await fetch(`${baseUrl}/api/${TEST_SLUG}/comments/${c.id}`, {
2041
- method: 'DELETE', headers: { 'Accept': 'text/html' },
2042
- });
2043
- assert.equal(del.status, 200);
2044
- assert.equal(await del.text(), '');
2045
- });
2046
-
2047
- // ── PUT /api/<slug>/canvas (full autosave) ───────────────────────
2048
- test('PUT /canvas with Accept: text/html returns save-status badge HTML', async () => {
2049
- const newCanvas = {
2050
- schemaVersion: 2, title: 'Updated',
2051
- elements: [{ id: 'el_x', type: 'text', x: 0, y: 0, width: 100, height: 100, content: 'x' }],
2052
- connections: [], comments: [], viewport: { x: 0, y: 0, zoom: 1 },
2053
- };
2054
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/canvas`, {
2055
- method: 'PUT',
2056
- headers: { 'Content-Type': 'application/json', 'Accept': 'text/html' },
2057
- body: JSON.stringify(newCanvas),
2058
- });
2059
- assert.equal(res.status, 200);
2060
- assert.ok(res.headers.get('content-type').includes('text/html'));
2061
- const html = await res.text();
2062
- assert.ok(html.includes('class="save-status'),
2063
- 'should return a save-status badge (got: ' + html.slice(0, 80) + ')');
2064
- assert.ok(html.includes('Saved'), 'should say "Saved"');
2065
- });
2066
-
2067
- test('PUT /canvas accepts htmx form-encoded body with JSON-stringified nested values', async () => {
2068
- // htmx 2.x form-encodes nested objects as JSON strings. The canvas
2069
- // PUT handler must decode them so the saved JSON is correct.
2070
- const params = new URLSearchParams();
2071
- params.set('title', 'Form Encoded');
2072
- params.set('elements', JSON.stringify([{ id: 'el_1', type: 'text', x: 5, y: 5, width: 50, height: 50, content: 'hi' }]));
2073
- params.set('connections', '[]');
2074
- params.set('comments', '[]');
2075
- params.set('viewport', JSON.stringify({ x: 1, y: 2, zoom: 1.5 }));
2076
-
2077
- const res = await fetch(`${baseUrl}/api/${TEST_SLUG}/canvas`, {
2078
- method: 'PUT',
2079
- headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },
2080
- body: params.toString(),
2081
- });
2082
- assert.equal(res.status, 200);
2083
-
2084
- // Verify the saved state has proper arrays/objects, not JSON strings
2085
- const saved = JSON.parse(readFileSync(join(PLANS_DIR, TEST_SLUG, 'artifact.json'), 'utf-8'));
2086
- assert.equal(saved.title, 'Form Encoded');
2087
- assert.ok(Array.isArray(saved.elements));
2088
- assert.equal(saved.elements.length, 1);
2089
- assert.equal(saved.elements[0].content, 'hi');
2090
- assert.equal(saved.elements[0].x, 5);
2091
- assert.ok(Array.isArray(saved.connections));
2092
- assert.ok(Array.isArray(saved.comments));
2093
- assert.ok(saved.viewport);
2094
- assert.equal(saved.viewport.zoom, 1.5);
2095
- });
2096
-
2097
- // ── Unit tests for the renderers themselves ─────────────────────
2098
- test('renderElementHTML produces valid element HTML', () => {
2099
- const e = {
2100
- id: 'el_test', type: 'text', x: 50, y: 60, width: 200, height: 100,
2101
- title: 'Hello', content: 'World',
2102
- };
2103
- const html = renderElementHTML(e);
2104
- assert.ok(html.includes('class="element"'));
2105
- assert.ok(html.includes('data-element-id="el_test"'));
2106
- assert.ok(html.includes('data-element-type="text"'));
2107
- assert.ok(html.includes('style="left:50px;top:60px;width:200px;height:100px"'));
2108
- assert.ok(html.includes('Hello'));
2109
- assert.ok(html.includes('World'));
2110
- assert.ok(html.includes('class="resize-handle"'));
2111
- // XSS safety
2112
- assert.ok(!html.includes('<script>'));
2113
- });
2114
-
2115
- test('renderElementHTML escapes HTML in user content (XSS safety)', () => {
2116
- const e = {
2117
- id: 'el_xss', type: 'text', x: 0, y: 0, width: 100, height: 100,
2118
- title: '<script>alert(1)</script>',
2119
- content: '<img src=x onerror=alert(2)>',
2120
- };
2121
- const html = renderElementHTML(e);
2122
- assert.ok(!html.includes('<script>alert(1)</script>'),
2123
- 'raw <script> in title should be escaped');
2124
- assert.ok(html.includes('&lt;script&gt;alert(1)&lt;/script&gt;'));
2125
- assert.ok(!html.includes('<img src=x'),
2126
- 'raw <img> in content should be escaped');
2127
- });
2128
-
2129
- test('renderElementHTML handles code/diagram/ui-mockup element types', () => {
2130
- const code = renderElementHTML({
2131
- id: 'el_c', type: 'code', x: 0, y: 0, width: 100, height: 100,
2132
- language: 'javascript', content: 'const x = 1;',
2133
- });
2134
- assert.ok(code.includes('class="language-javascript"'));
2135
- assert.ok(code.includes('const x = 1;'));
2136
-
2137
- const diagram = renderElementHTML({
2138
- id: 'el_d', type: 'diagram', x: 0, y: 0, width: 100, height: 100,
2139
- content: 'graph LR\nA --> B',
2140
- });
2141
- assert.ok(diagram.includes('class="mermaid"'));
2142
- assert.ok(diagram.includes('graph LR'));
2143
-
2144
- const button = renderElementHTML({
2145
- id: 'el_b', type: 'ui-mockup', x: 0, y: 0, width: 100, height: 100,
2146
- component: 'button', label: 'Submit',
2147
- });
2148
- assert.ok(button.includes('class="ui-mockup-button"'));
2149
- assert.ok(button.includes('Submit'));
2150
- });
2151
-
2152
- test('renderConnectionHTML produces a valid SVG fragment', () => {
2153
- const conn = {
2154
- id: 'conn_test', from: 'el_a', to: 'el_b', type: 'arrow', label: 'leads to',
2155
- };
2156
- const html = renderConnectionHTML(conn);
2157
- assert.ok(html.startsWith('<g class="connection"'));
2158
- assert.ok(html.includes('data-connection-id="conn_test"'));
2159
- assert.ok(html.includes('data-from="el_a"'));
2160
- assert.ok(html.includes('data-to="el_b"'));
2161
- assert.ok(html.includes('data-type="arrow"'));
2162
- assert.ok(html.includes('data-label="leads to"'));
2163
- });
2164
-
2165
- test('renderCommentPinHTML produces a valid pin HTML', () => {
2166
- const pin = renderCommentPinHTML({
2167
- id: 'c_test', x: 100, y: 200, text: 'note',
2168
- }, 0);
2169
- assert.ok(pin.startsWith('<div class="comment-pin"'));
2170
- assert.ok(pin.includes('data-comment-id="c_test"'));
2171
- assert.ok(pin.includes('style="left:100px;top:200px"'));
2172
- assert.ok(pin.includes('title="note"'));
2173
- assert.ok(pin.includes('>1<'), 'first pin should show number 1');
2174
- });
2175
-
2176
- test('renderCommentPinHTML shows correct chronological number', () => {
2177
- const pin2 = renderCommentPinHTML({
2178
- id: 'c_2', x: 0, y: 0, text: 'second',
2179
- }, 1);
2180
- assert.ok(pin2.includes('>2<'), 'second pin should show number 2');
2181
- });
2182
-
2183
- test('renderReplyHTML produces a valid <li class="reply"> fragment', () => {
2184
- const r = { id: 'r_1', author: 'ai', text: 'a reply', created: '2026-06-18T00:00:00.000Z' };
2185
- const html = renderReplyHTML(r);
2186
- assert.ok(html.startsWith('<li class="reply"'));
2187
- assert.ok(html.includes('id="reply-r_1"'));
2188
- assert.ok(html.includes('ai'));
2189
- assert.ok(html.includes('a reply'));
2190
- });
2191
-
2192
- test('renderCommentThreadHTML includes original + all replies', () => {
2193
- const c = {
2194
- id: 'c_thread', author: 'Alice', text: 'original',
2195
- created: '2026-06-18T00:00:00.000Z',
2196
- thread: [
2197
- { id: 'r_1', author: 'ai', text: 'reply 1', created: '2026-06-18T00:01:00.000Z' },
2198
- { id: 'r_2', author: 'Alice', text: 'reply 2', created: '2026-06-18T00:02:00.000Z' },
2199
- ],
2200
- };
2201
- const html = renderCommentThreadHTML(c);
2202
- assert.ok(html.startsWith('<li class="comment"'));
2203
- assert.ok(html.includes('id="comment-c_thread"'));
2204
- assert.ok(html.includes('original'));
2205
- assert.ok(html.includes('reply 1'));
2206
- assert.ok(html.includes('reply 2'));
2207
- // Should have 2 reply <li>s
2208
- const replyCount = (html.match(/<li class="reply"/g) || []).length;
2209
- assert.equal(replyCount, 2);
2210
- });
2211
- });
2212
-
2213
- // ── Canvas template structure tests ────────────────────────────────────────
2214
- // Verify the new artifact.canvas.template has the right shape:
2215
- // - pan/zoom controls
2216
- // - SVG layer for connections
2217
- // - comment pins
2218
- // - toolbar with element-type buttons
2219
-
2220
- describe('artifact.canvas.template structure', () => {
2221
- const tplPath = join(TEMPLATES_DIR, 'artifact.canvas.template');
2222
-
2223
- test('template file exists', () => {
2224
- assert.ok(existsSync(tplPath), 'artifact.canvas.template should exist');
2225
- });
2226
-
2227
- test('template declares {{canvasJson}} placeholder', () => {
2228
- const tpl = readFileSync(tplPath, 'utf-8');
2229
- assert.ok(tpl.includes('{{canvasJson}}'), 'should reference {{canvasJson}}');
2230
- });
2231
-
2232
- test('template has a pan/zoom-capable canvas div', () => {
2233
- const tpl = readFileSync(tplPath, 'utf-8');
2234
- assert.ok(/id=["']canvas["']/.test(tpl), 'should have a #canvas element');
2235
- // Should have zoom controls
2236
- assert.ok(/id=["']zoom-in["']/.test(tpl), 'should have a zoom-in button');
2237
- assert.ok(/id=["']zoom-out["']/.test(tpl), 'should have a zoom-out button');
2238
- assert.ok(/id=["']zoom-reset["']/.test(tpl), 'should have a zoom-reset button');
2239
- });
2240
-
2241
- test('template has an SVG layer for connections', () => {
2242
- const tpl = readFileSync(tplPath, 'utf-8');
2243
- assert.ok(/id=["']connections-layer["']/.test(tpl),
2244
- 'should have a #connections-layer SVG element');
2245
- // Should have arrowhead markers
2246
- assert.ok(/<marker[^>]+id=["']arrowhead["']/.test(tpl) || /id=["']arrowhead["']/.test(tpl),
2247
- 'should define arrowhead markers');
2248
- });
2249
-
2250
- test('template has comment pin styles and a comment panel', () => {
2251
- const tpl = readFileSync(tplPath, 'utf-8');
2252
- assert.ok(/\.comment-pin/.test(tpl), 'should have CSS for .comment-pin');
2253
- assert.ok(/id=["']comment-panel["']/.test(tpl), 'should have a #comment-panel');
2254
- assert.ok(/id=["']comment-list["']/.test(tpl), 'should have a #comment-list');
2255
- });
2256
-
2257
- test('template has toolbar buttons for all element types', () => {
2258
- const tpl = readFileSync(tplPath, 'utf-8');
2259
- // Toolbar with data-tool="text", "image", "code", "diagram", "ui-mockup", "connect", "comment"
2260
- const tools = ['text', 'image', 'code', 'diagram', 'ui-mockup', 'connect', 'comment'];
2261
- for (const tool of tools) {
2262
- const re = new RegExp('data-tool=["\']' + tool + '["\']');
2263
- assert.ok(re.test(tpl), `toolbar should have a button for "${tool}"`);
2264
- }
2265
- });
2266
-
2267
- test('template has an add-element modal', () => {
2268
- const tpl = readFileSync(tplPath, 'utf-8');
2269
- assert.ok(/id=["']modal-backdrop["']/.test(tpl), 'should have a #modal-backdrop');
2270
- assert.ok(/id=["']element-form["']/.test(tpl), 'should have an #element-form');
2271
- // UI mockup component selector
2272
- assert.ok(/id=["']el-component["']/.test(tpl), 'should have a component selector for ui-mockup');
2273
- });
2274
-
2275
- test('template wires htmx attributes to the v2 canvas endpoints', () => {
2276
- const tpl = readFileSync(tplPath, 'utf-8');
2277
- // The v2 canvas template is declarative: it uses htmx attributes
2278
- // (hx-post, hx-put, hx-delete, hx-target, hx-swap) to talk to the
2279
- // canvas endpoints. No raw fetch() calls remain for CRUD — the
2280
- // only fetch() is the markdown-export download flow.
2281
- //
2282
- // Form-driven flows (element add/edit, comment reply) get static
2283
- // hx-* attributes; programmatic flows (add connection, drag-save)
2284
- // use htmx.ajax() and so don't have static hx-post/hx-put.
2285
- assert.ok(/hx-post=["']\/api\/.+\/elements/.test(tpl),
2286
- 'element-form should hx-post to /api/.../elements');
2287
- assert.ok(/hx-put=["']\/api\/.+\/comments/.test(tpl),
2288
- 'comment-form should hx-put to /api/.../comments');
2289
- assert.ok(/hx-(post|put|delete)=["']\/api\/.+\/(elements|comments)/.test(tpl),
2290
- 'should reference /api/.../elements or /api/.../comments');
2291
- // The element-form should target the elements layer
2292
- assert.ok(/id=["']element-form["']/.test(tpl), 'should have an #element-form');
2293
- assert.ok(/hx-target=["']#elements-layer["']/.test(tpl),
2294
- 'element form should target #elements-layer');
2295
- // Programmatic htmx.ajax() calls for connection creation should be
2296
- // present (the template drives them via JS, not static markup).
2297
- // Match either a literal "/api/.../connections" or a JS-concatenated
2298
- // "/api/' + SLUG + '/connections" style.
2299
- assert.ok(/htmx\.ajax\s*\(\s*['"]POST['"]/.test(tpl),
2300
- 'should have a programmatic htmx.ajax POST call');
2301
- assert.ok(/\/connections['"]?\s*,/.test(tpl) ||
2302
- /\/connections['"]?\s*\)/.test(tpl) ||
2303
- /\+ ['"]\/connections['"]?/.test(tpl),
2304
- 'should POST to /api/.../connections (literal or JS-concatenated)');
2305
- });
2306
-
2307
- test('template has no raw fetch() for canvas CRUD (only the export download)', () => {
2308
- // The only remaining fetch() call should be the markdown export
2309
- // (a Blob download flow that doesn't fit htmx's swap model).
2310
- const tpl = readFileSync(tplPath, 'utf-8');
2311
- // Strip comments and string literals before counting fetch() calls
2312
- const stripped = tpl
2313
- .split('\n')
2314
- .filter((line) => !line.trim().startsWith('//') && !line.trim().startsWith('*'))
2315
- .join('\n');
2316
- const matches = stripped.match(/fetch\s*\(/g) || [];
2317
- assert.equal(matches.length, 1,
2318
- `expected exactly 1 fetch() call (the markdown export), found ${matches.length}`);
2319
- assert.ok(/fetch\s*\(.*markdown-export/.test(stripped),
2320
- 'the remaining fetch() should be the markdown-export download');
2321
- });
2322
-
2323
- test('template exports the markdown via the export button', () => {
2324
- const tpl = readFileSync(tplPath, 'utf-8');
2325
- assert.ok(tpl.includes('markdown-export'), 'should reference the markdown-export endpoint');
2326
- });
2327
- });
2328
-
2329
- // ── End of template tests ──────────────────────────────────────────────────────
2330
-
2331
- console.log(' artifact.mjs tests loaded — run with: node --test cli/artifact.test.mjs');