@sureshdsk/devflow-mcp 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (504) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +4 -4
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
  6. package/.next/standalone/.next/server/app/_global-error/page.js +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +2 -2
  9. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  16. package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
  17. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.rsc +14 -13
  21. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -13
  22. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  23. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +5 -4
  24. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  25. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  26. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  27. package/.next/standalone/.next/server/app/api/projects/[id]/route.js +2 -2
  28. package/.next/standalone/.next/server/app/api/projects/[id]/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/projects/route.js +2 -2
  30. package/.next/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/specs/[name]/artifacts/[artifactType]/approve/route.js +2 -2
  32. package/.next/standalone/.next/server/app/api/specs/[name]/artifacts/[artifactType]/approve/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/specs/[name]/artifacts/[artifactType]/route.js +2 -2
  34. package/.next/standalone/.next/server/app/api/specs/[name]/artifacts/[artifactType]/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/specs/[name]/promote/route.js +2 -2
  36. package/.next/standalone/.next/server/app/api/specs/[name]/promote/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/specs/[name]/route.js +2 -2
  38. package/.next/standalone/.next/server/app/api/specs/[name]/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/specs/route.js +2 -2
  40. package/.next/standalone/.next/server/app/api/specs/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/tasks/[id]/route.js +2 -2
  42. package/.next/standalone/.next/server/app/api/tasks/[id]/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/api/tasks/route.js +2 -2
  44. package/.next/standalone/.next/server/app/api/tasks/route.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/index.html +1 -1
  46. package/.next/standalone/.next/server/app/index.rsc +15 -14
  47. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  48. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -14
  49. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  50. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +5 -4
  51. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  52. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  53. package/.next/standalone/.next/server/app/page.js +2 -2
  54. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/app/specs/[name]/page/build-manifest.json +2 -2
  57. package/.next/standalone/.next/server/app/specs/[name]/page.js +2 -2
  58. package/.next/standalone/.next/server/app/specs/[name]/page.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/specs/[name]/page_client-reference-manifest.js +1 -1
  60. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0be0eb8b._.js +3 -0
  61. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__a9d478ee._.js → [root-of-the-server]__160c54ca._.js} +2 -2
  62. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17ac51e3._.js +3 -0
  63. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bb083e9._.js +3 -0
  64. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1dc1e944._.js +3 -0
  65. package/.next/standalone/.next/server/chunks/[root-of-the-server]__22ecfa2c._.js +3 -0
  66. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__13a93eb6._.js → [root-of-the-server]__64710c0d._.js} +2 -2
  67. package/.next/standalone/.next/server/chunks/[root-of-the-server]__a2b7e5f2._.js +3 -0
  68. package/.next/standalone/.next/server/chunks/[root-of-the-server]__cf302bda._.js +3 -0
  69. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d85482cd._.js +3 -0
  70. package/.next/standalone/.next/server/chunks/[root-of-the-server]__dfa21757._.js +3 -0
  71. package/.next/standalone/.next/server/chunks/_3f452afe._.js +2 -2
  72. package/.next/standalone/.next/server/chunks/src_db_index_ts_a71f50f0._.js +3 -0
  73. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__023e2c69._.js +3 -0
  74. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0a1a1435._.js +3 -0
  75. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0c49a387._.js +3 -0
  76. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1be1fc5f._.js +3 -0
  77. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__3a2f2efb._.js +3 -0
  78. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__4693399c._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__937c9212._.js → [root-of-the-server]__72a14301._.js} +2 -2
  80. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__8064ca06._.js +3 -0
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__8b786d48._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__8c052461._.js +3 -0
  83. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__8d306066._.js +3 -0
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__9532fd59._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__a119dd75._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__7fb6ef14._.js → [root-of-the-server]__a57b312b._.js} +2 -2
  87. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__a80bd0ea._.js +3 -0
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__abe095b3._.js +3 -0
  89. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__ad3bd783._.js +3 -0
  90. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1e05fe0e._.js → [root-of-the-server]__ae9f05d6._.js} +2 -2
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__c8781469._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__c8a25070._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__e8c1e595._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/ssr/_0ff3de19._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/ssr/node_modules_01a5cd4b._.js +3 -0
  96. package/.next/standalone/.next/server/chunks/ssr/node_modules_05c6819f._.js +31 -0
  97. package/.next/standalone/.next/server/chunks/ssr/node_modules_06c2c30a._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/ssr/node_modules_09efe605._.js +3 -0
  99. package/.next/standalone/.next/server/chunks/ssr/{node_modules_mermaid_dist_chunks_mermaid_core_ef601841._.js → node_modules_0a439e3b._.js} +2 -2
  100. package/.next/standalone/.next/server/chunks/ssr/{node_modules_3507b41a._.js → node_modules_0e73ad6c._.js} +3 -3
  101. package/.next/standalone/.next/server/chunks/ssr/node_modules_0e7ff06d._.js +3 -0
  102. package/.next/standalone/.next/server/chunks/ssr/node_modules_0f73e25c._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/ssr/node_modules_14cf9cea._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/ssr/node_modules_19801f01._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/ssr/node_modules_2235b8cf._.js +17 -0
  106. package/.next/standalone/.next/server/chunks/ssr/node_modules_23096c6d._.js +3 -0
  107. package/.next/standalone/.next/server/chunks/ssr/node_modules_23db4cf7._.js +17 -0
  108. package/.next/standalone/.next/server/chunks/ssr/node_modules_24381e95._.js +3 -0
  109. package/.next/standalone/.next/server/chunks/ssr/node_modules_24ca0eb6._.js +17 -0
  110. package/.next/standalone/.next/server/chunks/ssr/node_modules_27525793._.js +17 -0
  111. package/.next/standalone/.next/server/chunks/ssr/{node_modules_mermaid_dist_chunks_mermaid_core_4cfa6360._.js → node_modules_27c20382._.js} +2 -2
  112. package/.next/standalone/.next/server/chunks/ssr/node_modules_2c901b92._.js +3 -0
  113. package/.next/standalone/.next/server/chunks/ssr/node_modules_33b818b7._.js +45 -0
  114. package/.next/standalone/.next/server/chunks/ssr/node_modules_345d7791._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/node_modules_36185c24._.js +45 -0
  116. package/.next/standalone/.next/server/chunks/ssr/node_modules_3ac7ce09._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/ssr/node_modules_41b77cc7._.js +3 -0
  118. package/.next/standalone/.next/server/chunks/ssr/{node_modules_ed317ff6._.js → node_modules_42acd8db._.js} +2 -2
  119. package/.next/standalone/.next/server/chunks/ssr/node_modules_434aa5ec._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/ssr/node_modules_54a9e72d._.js +17 -0
  121. package/.next/standalone/.next/server/chunks/ssr/node_modules_54f1273a._.js +26 -0
  122. package/.next/standalone/.next/server/chunks/ssr/node_modules_58d64491._.js +3 -0
  123. package/.next/standalone/.next/server/chunks/ssr/node_modules_5e012e3d._.js +3 -0
  124. package/.next/standalone/.next/server/chunks/ssr/node_modules_5ea9e4cf._.js +3 -0
  125. package/.next/standalone/.next/server/chunks/ssr/node_modules_5ecf8270._.js +26 -0
  126. package/.next/standalone/.next/server/chunks/ssr/node_modules_611c99b5._.js +1 -1
  127. package/.next/standalone/.next/server/chunks/ssr/{node_modules_d3-scale_src_e02ef77f._.js → node_modules_6629853d._.js} +2 -2
  128. package/.next/standalone/.next/server/chunks/ssr/node_modules_6fe2661c._.js +3 -0
  129. package/.next/standalone/.next/server/chunks/ssr/node_modules_7fe65a84._.js +3 -0
  130. package/.next/standalone/.next/server/chunks/ssr/node_modules_860f971a._.js +3 -0
  131. package/.next/standalone/.next/server/chunks/ssr/node_modules_882f3743._.js +3 -0
  132. package/.next/standalone/.next/server/chunks/ssr/node_modules_8e047938._.js +3 -0
  133. package/.next/standalone/.next/server/chunks/ssr/node_modules_9268cdc4._.js +3 -0
  134. package/.next/standalone/.next/server/chunks/ssr/node_modules_942541c9._.js +3 -0
  135. package/.next/standalone/.next/server/chunks/ssr/node_modules_9d52c0d6._.js +3 -0
  136. package/.next/standalone/.next/server/chunks/ssr/node_modules_@tanstack_query-core_build_modern_21ece5ea._.js +3 -0
  137. package/.next/standalone/.next/server/chunks/ssr/node_modules_ae6841d2._.js +17 -0
  138. package/.next/standalone/.next/server/chunks/ssr/node_modules_ba280bb0._.js +31 -0
  139. package/.next/standalone/.next/server/chunks/ssr/node_modules_bffdb8ec._.js +3 -0
  140. package/.next/standalone/.next/server/chunks/ssr/node_modules_c00f1821._.js +17 -0
  141. package/.next/standalone/.next/server/chunks/ssr/node_modules_c142ba86._.js +3 -0
  142. package/.next/standalone/.next/server/chunks/ssr/node_modules_d1a47cde._.js +17 -0
  143. package/.next/standalone/.next/server/chunks/ssr/node_modules_d9b454fe._.js +3 -0
  144. package/.next/standalone/.next/server/chunks/ssr/node_modules_dbe36b1d._.js +3 -0
  145. package/.next/standalone/.next/server/chunks/ssr/node_modules_ddbe26b7._.js +3 -0
  146. package/.next/standalone/.next/server/chunks/ssr/node_modules_e76b8bf2._.js +3 -0
  147. package/.next/standalone/.next/server/chunks/ssr/node_modules_e945b74d._.js +3 -0
  148. package/.next/standalone/.next/server/chunks/ssr/node_modules_fbd8a2ee._.js +17 -0
  149. package/.next/standalone/.next/server/chunks/ssr/node_modules_katex_dist_katex_mjs_31d456ba._.js +3 -0
  150. package/.next/standalone/.next/server/chunks/ssr/node_modules_katex_dist_katex_mjs_e1edc676._.js +3 -0
  151. package/.next/standalone/.next/server/chunks/ssr/node_modules_lodash-es_33e24dce._.js +3 -0
  152. package/.next/standalone/.next/server/chunks/ssr/node_modules_mermaid_dist_chunks_mermaid_core_chunk-QXUST7PY_mjs_159fba97._.js +1 -1
  153. package/.next/standalone/.next/server/chunks/ssr/node_modules_mermaid_dist_chunks_mermaid_core_chunk-S3R3BYOJ_mjs_dbeeb277._.js +1 -1
  154. package/.next/standalone/.next/server/chunks/ssr/node_modules_mermaid_dist_mermaid_core_mjs_a11916de._.js +9 -0
  155. package/.next/standalone/.next/server/chunks/ssr/src_66a70595._.js +3 -0
  156. package/.next/standalone/.next/server/chunks/ssr/src_components_kanban-board_tsx_7b875e8e._.js +1 -1
  157. package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
  158. package/.next/standalone/.next/server/pages/404.html +1 -1
  159. package/.next/standalone/.next/server/pages/500.html +2 -2
  160. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  161. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  162. package/.next/standalone/AGENTS.md +13 -7
  163. package/.next/standalone/CONTRIBUTING.md +42 -26
  164. package/.next/standalone/README.md +166 -25
  165. package/.next/standalone/bin/devflow.js +12 -5
  166. package/.next/standalone/bun.lock +78 -24
  167. package/.next/standalone/devflow/project-config.json +4 -0
  168. package/.next/standalone/devflow/schemas/api-first/schema.yaml +39 -0
  169. package/.next/standalone/devflow/schemas/api-first/templates/api-design.md +41 -0
  170. package/.next/standalone/devflow/schemas/api-first/templates/db-design.md +28 -0
  171. package/.next/standalone/devflow/schemas/api-first/templates/final-architecture.md +31 -0
  172. package/.next/standalone/devflow/schemas/api-first/templates/proposal.md +22 -0
  173. package/.next/standalone/devflow/schemas/api-first/templates/tasks.md +16 -0
  174. package/.next/standalone/devflow/specs/autodetect-skill-and-slash-command-installation/.approvals.json +1 -1
  175. package/.next/standalone/devflow/specs/autodetect-skill-and-slash-command-installation/.meta.json +1 -1
  176. package/.next/standalone/devflow/specs/autodetect-skill-and-slash-command-installation/design.md +11 -0
  177. package/.next/standalone/devflow/specs/autodetect-skill-and-slash-command-installation/proposal.md +3 -0
  178. package/.next/standalone/devflow/specs/autodetect-skill-and-slash-command-installation/tasks.md +34 -2
  179. package/.next/standalone/devflow/specs/precommit/.approvals.json +26 -0
  180. package/.next/standalone/devflow/specs/precommit/.meta.json +7 -0
  181. package/.next/standalone/devflow/specs/precommit/design.md +105 -0
  182. package/.next/standalone/devflow/specs/precommit/proposal.md +43 -0
  183. package/.next/standalone/devflow/specs/precommit/specs.md +89 -0
  184. package/.next/standalone/devflow/specs/precommit/tasks.md +283 -0
  185. package/.next/standalone/docs/custom-schemas.md +117 -0
  186. package/.next/standalone/docs/local-dev-guide.md +73 -0
  187. package/.next/standalone/drizzle.config.ts +7 -7
  188. package/.next/standalone/eslint.config.mjs +2 -2
  189. package/.next/standalone/instrumentation.ts +3 -5
  190. package/.next/standalone/next.config.ts +11 -2
  191. package/.next/standalone/node_modules/{sharp → libsql}/node_modules/detect-libc/package.json +4 -8
  192. package/.next/standalone/package-lock.json +5257 -2085
  193. package/.next/standalone/package.json +16 -7
  194. package/.next/standalone/postcss.config.mjs +1 -1
  195. package/.next/standalone/scripts/init-db.js +3 -5
  196. package/.next/standalone/scripts/init-db.test.js +6 -6
  197. package/.next/standalone/scripts/init-schema.js +22 -22
  198. package/.next/standalone/scripts/init-schema.test.js +45 -45
  199. package/.next/standalone/scripts/install-environments.js +27 -8
  200. package/.next/standalone/scripts/install-environments.test.js +19 -10
  201. package/.next/standalone/scripts/install-skills.js +104 -31
  202. package/.next/standalone/scripts/install-skills.test.js +6 -6
  203. package/.next/standalone/server.js +1 -1
  204. package/.next/standalone/src/app/api/agents/route.ts +2 -2
  205. package/.next/standalone/src/app/api/projects/[id]/route.ts +10 -19
  206. package/.next/standalone/src/app/api/projects/route.ts +4 -10
  207. package/.next/standalone/src/app/api/specs/[name]/artifacts/[artifactType]/approve/route.ts +8 -8
  208. package/.next/standalone/src/app/api/specs/[name]/artifacts/[artifactType]/route.ts +14 -14
  209. package/.next/standalone/src/app/api/specs/[name]/promote/route.ts +17 -23
  210. package/.next/standalone/src/app/api/specs/[name]/route.ts +34 -36
  211. package/.next/standalone/src/app/api/specs/route.ts +10 -10
  212. package/.next/standalone/src/app/api/tasks/[id]/route.ts +11 -33
  213. package/.next/standalone/src/app/api/tasks/route.ts +3 -6
  214. package/.next/standalone/src/app/globals.css +3 -3
  215. package/.next/standalone/src/app/layout.tsx +9 -5
  216. package/.next/standalone/src/app/page.tsx +1 -1
  217. package/.next/standalone/src/app/specs/[name]/page.tsx +26 -68
  218. package/.next/standalone/src/components/kanban-board.tsx +78 -157
  219. package/.next/standalone/src/components/kanban-column.tsx +10 -12
  220. package/.next/standalone/src/components/markdown-preview.tsx +36 -19
  221. package/.next/standalone/src/components/providers.tsx +20 -0
  222. package/.next/standalone/src/components/specs/artifact-dag-status.tsx +12 -14
  223. package/.next/standalone/src/components/specs/artifact-editor.tsx +55 -26
  224. package/.next/standalone/src/components/specs/spec-detail.tsx +48 -40
  225. package/.next/standalone/src/components/specs/spec-kanban-column.tsx +49 -68
  226. package/.next/standalone/src/components/specs/spec-modal.tsx +119 -83
  227. package/.next/standalone/src/components/task-card.tsx +34 -38
  228. package/.next/standalone/src/components/task-dialog.tsx +16 -31
  229. package/.next/standalone/src/components/ui/badge.tsx +12 -18
  230. package/.next/standalone/src/components/ui/button.tsx +23 -28
  231. package/.next/standalone/src/components/ui/card.tsx +42 -62
  232. package/.next/standalone/src/components/ui/dialog.tsx +17 -35
  233. package/.next/standalone/src/db/index.ts +10 -10
  234. package/.next/standalone/src/db/schema.ts +37 -37
  235. package/.next/standalone/src/hooks/use-queries.ts +81 -0
  236. package/.next/standalone/src/hooks/use-websocket.ts +74 -0
  237. package/.next/standalone/src/lib/schema.test.ts +37 -37
  238. package/.next/standalone/src/lib/schema.ts +77 -31
  239. package/.next/standalone/src/lib/specs-dir.ts +8 -8
  240. package/.next/standalone/src/lib/specs.ts +98 -92
  241. package/.next/standalone/src/lib/utils.ts +2 -2
  242. package/.next/standalone/src/mcp/server.ts +556 -349
  243. package/.next/standalone/src/mcp/websocket.ts +26 -23
  244. package/.next/standalone/src/schemas/backend-api/templates/proposal.md +6 -4
  245. package/.next/standalone/src/schemas/backend-api/templates/tasks.md +6 -0
  246. package/.next/standalone/src/schemas/data-engineering/templates/proposal.md +6 -4
  247. package/.next/standalone/src/schemas/data-engineering/templates/tasks.md +6 -0
  248. package/.next/standalone/src/schemas/devops-platform/templates/proposal.md +6 -4
  249. package/.next/standalone/src/schemas/devops-platform/templates/tasks.md +6 -0
  250. package/.next/standalone/src/schemas/frontend-product/templates/proposal.md +6 -4
  251. package/.next/standalone/src/schemas/frontend-product/templates/tasks.md +6 -0
  252. package/.next/standalone/src/schemas/spec-driven/templates/proposal.md +6 -4
  253. package/.next/standalone/src/schemas/spec-driven/templates/tasks.md +26 -0
  254. package/.next/standalone/src/websocket/server.ts +20 -18
  255. package/.next/standalone/tsconfig.json +3 -12
  256. package/.next/standalone/tsconfig.tsbuildinfo +1 -1
  257. package/.next/static/chunks/4f23f64d46848f57.js +1 -0
  258. package/.next/static/chunks/53081dcdcad4f31e.js +1 -0
  259. package/.next/static/chunks/{6a3d582315f1c214.js → 701cfcbb41e0ab47.js} +1 -1
  260. package/.next/static/chunks/ac4b6b4d5b7e486e.css +1 -0
  261. package/.next/static/chunks/d0309ea872db5d9f.js +1 -0
  262. package/.next/static/chunks/d4cf8893a018e965.js +1 -0
  263. package/.next/static/chunks/e6c0233269a4bd69.js +13 -0
  264. package/.next/static/chunks/{7e65cfa9841f66a5.js → e7d83ce855afe416.js} +2 -2
  265. package/.next/static/chunks/ee7035560fc9e5e9.js +1 -0
  266. package/.next/static/chunks/{turbopack-f4d3806ab575051d.js → turbopack-8b3b66a22a5e0fb4.js} +2 -2
  267. package/README.md +166 -25
  268. package/bin/devflow.js +12 -5
  269. package/drizzle.config.ts +7 -7
  270. package/next.config.ts +11 -2
  271. package/package.json +16 -7
  272. package/scripts/init-db.js +3 -5
  273. package/scripts/init-db.test.js +6 -6
  274. package/scripts/init-schema.js +22 -22
  275. package/scripts/init-schema.test.js +45 -45
  276. package/scripts/install-environments.js +27 -8
  277. package/scripts/install-environments.test.js +19 -10
  278. package/scripts/install-skills.js +104 -31
  279. package/scripts/install-skills.test.js +6 -6
  280. package/src/app/api/agents/route.ts +2 -2
  281. package/src/app/api/projects/[id]/route.ts +10 -19
  282. package/src/app/api/projects/route.ts +4 -10
  283. package/src/app/api/specs/[name]/artifacts/[artifactType]/approve/route.ts +8 -8
  284. package/src/app/api/specs/[name]/artifacts/[artifactType]/route.ts +14 -14
  285. package/src/app/api/specs/[name]/promote/route.ts +17 -23
  286. package/src/app/api/specs/[name]/route.ts +34 -36
  287. package/src/app/api/specs/route.ts +10 -10
  288. package/src/app/api/tasks/[id]/route.ts +11 -33
  289. package/src/app/api/tasks/route.ts +3 -6
  290. package/src/app/globals.css +3 -3
  291. package/src/app/layout.tsx +9 -5
  292. package/src/app/page.tsx +1 -1
  293. package/src/app/specs/[name]/page.tsx +26 -68
  294. package/src/components/kanban-board.tsx +78 -157
  295. package/src/components/kanban-column.tsx +10 -12
  296. package/src/components/markdown-preview.tsx +36 -19
  297. package/src/components/providers.tsx +20 -0
  298. package/src/components/specs/artifact-dag-status.tsx +12 -14
  299. package/src/components/specs/artifact-editor.tsx +55 -26
  300. package/src/components/specs/spec-detail.tsx +48 -40
  301. package/src/components/specs/spec-kanban-column.tsx +49 -68
  302. package/src/components/specs/spec-modal.tsx +119 -83
  303. package/src/components/task-card.tsx +34 -38
  304. package/src/components/task-dialog.tsx +16 -31
  305. package/src/components/ui/badge.tsx +12 -18
  306. package/src/components/ui/button.tsx +23 -28
  307. package/src/components/ui/card.tsx +42 -62
  308. package/src/components/ui/dialog.tsx +17 -35
  309. package/src/db/index.ts +10 -10
  310. package/src/db/schema.ts +37 -37
  311. package/src/hooks/use-queries.ts +81 -0
  312. package/src/hooks/use-websocket.ts +74 -0
  313. package/src/lib/schema.test.ts +37 -37
  314. package/src/lib/schema.ts +77 -31
  315. package/src/lib/specs-dir.ts +8 -8
  316. package/src/lib/specs.ts +98 -92
  317. package/src/lib/utils.ts +2 -2
  318. package/src/mcp/server.ts +556 -349
  319. package/src/mcp/websocket.ts +26 -23
  320. package/src/schemas/backend-api/templates/proposal.md +6 -4
  321. package/src/schemas/backend-api/templates/tasks.md +6 -0
  322. package/src/schemas/data-engineering/templates/proposal.md +6 -4
  323. package/src/schemas/data-engineering/templates/tasks.md +6 -0
  324. package/src/schemas/devops-platform/templates/proposal.md +6 -4
  325. package/src/schemas/devops-platform/templates/tasks.md +6 -0
  326. package/src/schemas/frontend-product/templates/proposal.md +6 -4
  327. package/src/schemas/frontend-product/templates/tasks.md +6 -0
  328. package/src/schemas/spec-driven/templates/proposal.md +6 -4
  329. package/src/schemas/spec-driven/templates/tasks.md +26 -0
  330. package/src/types/bun-sqlite.d.ts +1 -1
  331. package/src/websocket/server.ts +20 -18
  332. package/tsconfig.json +3 -12
  333. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1be4e4b6._.js +0 -3
  334. package/.next/standalone/.next/server/chunks/[root-of-the-server]__492a4a24._.js +0 -3
  335. package/.next/standalone/.next/server/chunks/[root-of-the-server]__67c59ae1._.js +0 -3
  336. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6cc7aecd._.js +0 -3
  337. package/.next/standalone/.next/server/chunks/[root-of-the-server]__83a77b07._.js +0 -3
  338. package/.next/standalone/.next/server/chunks/[root-of-the-server]__88d91cb5._.js +0 -3
  339. package/.next/standalone/.next/server/chunks/[root-of-the-server]__a9e095e8._.js +0 -3
  340. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b367d327._.js +0 -3
  341. package/.next/standalone/.next/server/chunks/[root-of-the-server]__eb413bbc._.js +0 -3
  342. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0b8c072a._.js +0 -3
  343. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12b2191f._.js +0 -3
  344. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15316462._.js +0 -3
  345. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19c9c409._.js +0 -3
  346. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19e5e22f._.js +0 -3
  347. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__25b5ccb3._.js +0 -3
  348. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__2f78c3da._.js +0 -3
  349. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__3160b3f4._.js +0 -9
  350. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__319559be._.js +0 -3
  351. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__34f7cbcb._.js +0 -31
  352. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__3abab94b._.js +0 -3
  353. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__3b3d8930._.js +0 -3
  354. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__4250799b._.js +0 -3
  355. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__4c29cbc3._.js +0 -26
  356. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__4e0bb6eb._.js +0 -9
  357. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__5678d2e3._.js +0 -3
  358. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__673445e0._.js +0 -31
  359. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__934de06b._.js +0 -3
  360. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__9b6c6f09._.js +0 -3
  361. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__a1b83a98._.js +0 -3
  362. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__b00a15c5._.js +0 -3
  363. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__b8567be3._.js +0 -26
  364. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__ce64536e._.js +0 -45
  365. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__e96d33d2._.js +0 -45
  366. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__fd7ec054._.js +0 -3
  367. package/.next/standalone/.next/server/chunks/ssr/node_modules_16a3f9e7._.js +0 -17
  368. package/.next/standalone/.next/server/chunks/ssr/node_modules_45bce0e1._.js +0 -3
  369. package/.next/standalone/.next/server/chunks/ssr/node_modules_5e5a372d._.js +0 -17
  370. package/.next/standalone/.next/server/chunks/ssr/node_modules_6ece6f1e._.js +0 -3
  371. package/.next/standalone/.next/server/chunks/ssr/node_modules_d173e749._.js +0 -3
  372. package/.next/standalone/.next/server/chunks/ssr/node_modules_d3-shape_src_arc_fb1ac087.js +0 -3
  373. package/.next/standalone/.next/server/chunks/ssr/node_modules_mermaid_dist_chunks_mermaid_core_1f73a830._.js +0 -3
  374. package/.next/standalone/.next/server/chunks/ssr/node_modules_mermaid_dist_chunks_mermaid_core_663ac803._.js +0 -3
  375. package/.next/standalone/.next/server/chunks/ssr/node_modules_mermaid_dist_chunks_mermaid_core_chunk-FMBD7UC4_mjs_4f485529._.js +0 -17
  376. package/.next/standalone/.next/server/chunks/ssr/node_modules_mermaid_dist_chunks_mermaid_core_chunk-TZMSLE5B_mjs_8436a62a._.js +0 -3
  377. package/.next/standalone/.next/server/chunks/ssr/node_modules_mermaid_dist_chunks_mermaid_core_f78d2dc4._.js +0 -3
  378. package/.next/standalone/.next/server/chunks/ssr/src_app_layout_tsx_cc8184fa._.js +0 -3
  379. package/.next/standalone/devflow/specs/add-default-schema-template-selection/.approvals.json +0 -26
  380. package/.next/standalone/devflow/specs/add-default-schema-template-selection/.meta.json +0 -8
  381. package/.next/standalone/devflow/specs/add-default-schema-template-selection/design.md +0 -126
  382. package/.next/standalone/devflow/specs/add-default-schema-template-selection/proposal.md +0 -65
  383. package/.next/standalone/devflow/specs/add-default-schema-template-selection/specs.md +0 -107
  384. package/.next/standalone/devflow/specs/add-default-schema-template-selection/tasks.md +0 -235
  385. package/.next/standalone/devflow/specs/test-no-project/.approvals.json +0 -17
  386. package/.next/standalone/devflow/specs/test-no-project/.meta.json +0 -6
  387. package/.next/standalone/devflow/specs/ui-cleanup/.approvals.json +0 -29
  388. package/.next/standalone/devflow/specs/ui-cleanup/.meta.json +0 -6
  389. package/.next/standalone/devflow/specs/ui-cleanup/design.md +0 -49
  390. package/.next/standalone/devflow/specs/ui-cleanup/proposal.md +0 -34
  391. package/.next/standalone/devflow/specs/ui-cleanup/specs.md +0 -55
  392. package/.next/standalone/devflow/specs/ui-cleanup/tasks.md +0 -28
  393. package/.next/standalone/node_modules/@img/colour/color.cjs +0 -1594
  394. package/.next/standalone/node_modules/@img/colour/index.cjs +0 -1
  395. package/.next/standalone/node_modules/@img/colour/package.json +0 -45
  396. package/.next/standalone/node_modules/@img/sharp-darwin-arm64/lib/sharp-darwin-arm64.node +0 -0
  397. package/.next/standalone/node_modules/@img/sharp-darwin-arm64/package.json +0 -40
  398. package/.next/standalone/node_modules/@img/sharp-libvips-darwin-arm64/README.md +0 -46
  399. package/.next/standalone/node_modules/@img/sharp-libvips-darwin-arm64/lib/glib-2.0/include/glibconfig.h +0 -220
  400. package/.next/standalone/node_modules/@img/sharp-libvips-darwin-arm64/lib/index.js +0 -1
  401. package/.next/standalone/node_modules/@img/sharp-libvips-darwin-arm64/lib/libvips-cpp.8.17.3.dylib +0 -0
  402. package/.next/standalone/node_modules/@img/sharp-libvips-darwin-arm64/package.json +0 -36
  403. package/.next/standalone/node_modules/@img/sharp-libvips-darwin-arm64/versions.json +0 -30
  404. package/.next/standalone/node_modules/buffer-from/index.js +0 -72
  405. package/.next/standalone/node_modules/buffer-from/package.json +0 -19
  406. package/.next/standalone/node_modules/sharp/lib/channel.js +0 -177
  407. package/.next/standalone/node_modules/sharp/lib/colour.js +0 -195
  408. package/.next/standalone/node_modules/sharp/lib/composite.js +0 -212
  409. package/.next/standalone/node_modules/sharp/lib/constructor.js +0 -499
  410. package/.next/standalone/node_modules/sharp/lib/index.js +0 -16
  411. package/.next/standalone/node_modules/sharp/lib/input.js +0 -809
  412. package/.next/standalone/node_modules/sharp/lib/is.js +0 -143
  413. package/.next/standalone/node_modules/sharp/lib/libvips.js +0 -207
  414. package/.next/standalone/node_modules/sharp/lib/operation.js +0 -1016
  415. package/.next/standalone/node_modules/sharp/lib/output.js +0 -1666
  416. package/.next/standalone/node_modules/sharp/lib/resize.js +0 -595
  417. package/.next/standalone/node_modules/sharp/lib/sharp.js +0 -121
  418. package/.next/standalone/node_modules/sharp/lib/utility.js +0 -291
  419. package/.next/standalone/node_modules/sharp/node_modules/detect-libc/lib/detect-libc.js +0 -313
  420. package/.next/standalone/node_modules/sharp/node_modules/detect-libc/lib/elf.js +0 -39
  421. package/.next/standalone/node_modules/sharp/node_modules/detect-libc/lib/filesystem.js +0 -51
  422. package/.next/standalone/node_modules/sharp/node_modules/detect-libc/lib/process.js +0 -24
  423. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/comparator.js +0 -143
  424. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/range.js +0 -557
  425. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/semver.js +0 -333
  426. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/cmp.js +0 -54
  427. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/coerce.js +0 -62
  428. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/compare.js +0 -7
  429. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/eq.js +0 -5
  430. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/gt.js +0 -5
  431. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/gte.js +0 -5
  432. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/lt.js +0 -5
  433. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/lte.js +0 -5
  434. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/neq.js +0 -5
  435. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/parse.js +0 -18
  436. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/satisfies.js +0 -12
  437. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/constants.js +0 -37
  438. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/debug.js +0 -11
  439. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/identifiers.js +0 -29
  440. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/lrucache.js +0 -42
  441. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/parse-options.js +0 -17
  442. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/re.js +0 -223
  443. package/.next/standalone/node_modules/sharp/node_modules/semver/package.json +0 -78
  444. package/.next/standalone/node_modules/sharp/package.json +0 -202
  445. package/.next/standalone/node_modules/source-map/lib/array-set.js +0 -121
  446. package/.next/standalone/node_modules/source-map/lib/base64-vlq.js +0 -140
  447. package/.next/standalone/node_modules/source-map/lib/base64.js +0 -67
  448. package/.next/standalone/node_modules/source-map/lib/binary-search.js +0 -111
  449. package/.next/standalone/node_modules/source-map/lib/mapping-list.js +0 -79
  450. package/.next/standalone/node_modules/source-map/lib/quick-sort.js +0 -114
  451. package/.next/standalone/node_modules/source-map/lib/source-map-consumer.js +0 -1145
  452. package/.next/standalone/node_modules/source-map/lib/source-map-generator.js +0 -425
  453. package/.next/standalone/node_modules/source-map/lib/source-node.js +0 -413
  454. package/.next/standalone/node_modules/source-map/lib/util.js +0 -488
  455. package/.next/standalone/node_modules/source-map/package.json +0 -73
  456. package/.next/standalone/node_modules/source-map/source-map.js +0 -8
  457. package/.next/standalone/node_modules/source-map-support/LICENSE.md +0 -21
  458. package/.next/standalone/node_modules/source-map-support/README.md +0 -284
  459. package/.next/standalone/node_modules/source-map-support/browser-source-map-support.js +0 -114
  460. package/.next/standalone/node_modules/source-map-support/package.json +0 -31
  461. package/.next/standalone/node_modules/source-map-support/register-hook-require.js +0 -1
  462. package/.next/standalone/node_modules/source-map-support/register.js +0 -1
  463. package/.next/standalone/node_modules/source-map-support/source-map-support.js +0 -625
  464. package/.next/standalone/node_modules/typescript/lib/_tsc.js +0 -133818
  465. package/.next/standalone/node_modules/typescript/lib/_tsserver.js +0 -659
  466. package/.next/standalone/node_modules/typescript/lib/_typingsInstaller.js +0 -222
  467. package/.next/standalone/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +0 -2122
  468. package/.next/standalone/node_modules/typescript/lib/de/diagnosticMessages.generated.json +0 -2122
  469. package/.next/standalone/node_modules/typescript/lib/es/diagnosticMessages.generated.json +0 -2122
  470. package/.next/standalone/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +0 -2122
  471. package/.next/standalone/node_modules/typescript/lib/it/diagnosticMessages.generated.json +0 -2122
  472. package/.next/standalone/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +0 -2122
  473. package/.next/standalone/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +0 -2122
  474. package/.next/standalone/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +0 -2122
  475. package/.next/standalone/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +0 -2122
  476. package/.next/standalone/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +0 -2122
  477. package/.next/standalone/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +0 -2122
  478. package/.next/standalone/node_modules/typescript/lib/tsc.js +0 -8
  479. package/.next/standalone/node_modules/typescript/lib/tsserver.js +0 -8
  480. package/.next/standalone/node_modules/typescript/lib/tsserverlibrary.js +0 -21
  481. package/.next/standalone/node_modules/typescript/lib/typesMap.json +0 -497
  482. package/.next/standalone/node_modules/typescript/lib/typescript.js +0 -200276
  483. package/.next/standalone/node_modules/typescript/lib/typingsInstaller.js +0 -8
  484. package/.next/standalone/node_modules/typescript/lib/watchGuard.js +0 -53
  485. package/.next/standalone/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +0 -2122
  486. package/.next/standalone/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +0 -2122
  487. package/.next/standalone/node_modules/typescript/package.json +0 -120
  488. package/.next/standalone/src/components/ui/collapsible.tsx +0 -12
  489. package/.next/standalone/src/components/ui/sheet.tsx +0 -130
  490. package/.next/standalone/src/components/ui/tabs.tsx +0 -58
  491. package/.next/static/chunks/0177331b8adf7346.js +0 -7
  492. package/.next/static/chunks/06af74e2f3e207c5.js +0 -7
  493. package/.next/static/chunks/1748d5cd2443723b.css +0 -1
  494. package/.next/static/chunks/7ae917cb100be2b9.js +0 -7
  495. package/.next/static/chunks/7c4b901b394c3a8c.js +0 -7
  496. package/src/components/ui/collapsible.tsx +0 -12
  497. package/src/components/ui/sheet.tsx +0 -130
  498. package/src/components/ui/tabs.tsx +0 -58
  499. /package/.next/standalone/node_modules/{detect-libc → libsql/node_modules/detect-libc}/lib/detect-libc.js +0 -0
  500. /package/.next/standalone/node_modules/{detect-libc → libsql/node_modules/detect-libc}/lib/filesystem.js +0 -0
  501. /package/.next/standalone/node_modules/{detect-libc → libsql/node_modules/detect-libc}/lib/process.js +0 -0
  502. /package/.next/static/{5YFrdIQd1UU2IxLxekrU9 → Ql6qo0GYLL23WRu6ec4N5}/_buildManifest.js +0 -0
  503. /package/.next/static/{5YFrdIQd1UU2IxLxekrU9 → Ql6qo0GYLL23WRu6ec4N5}/_clientMiddlewareManifest.json +0 -0
  504. /package/.next/static/{5YFrdIQd1UU2IxLxekrU9 → Ql6qo0GYLL23WRu6ec4N5}/_ssgManifest.js +0 -0
@@ -1,16 +1,17 @@
1
- "use client";
1
+ 'use client';
2
2
 
3
- import { useState, useEffect, useCallback, use } from "react";
4
- import { SpecDetail } from "@/components/specs/spec-detail";
5
- import Link from "next/link";
3
+ import { use } from 'react';
4
+ import { SpecDetail } from '@/components/specs/spec-detail';
5
+ import Link from 'next/link';
6
+ import { useSpec, useInvalidate } from '@/hooks/use-queries';
7
+ import { useWebSocket } from '@/hooks/use-websocket';
6
8
 
7
9
  interface SpecData {
8
10
  name: string;
9
11
  title: string;
10
- projectId?: string;
11
12
  statuses: Array<{
12
13
  id: string;
13
- state: "blocked" | "ready" | "in_review" | "in_progress" | "done";
14
+ state: 'blocked' | 'ready' | 'in_review' | 'in_progress' | 'done';
14
15
  description: string;
15
16
  requires: string[];
16
17
  fileExists: boolean;
@@ -30,63 +31,16 @@ interface SpecData {
30
31
 
31
32
  export default function SpecPage({ params }: { params: Promise<{ name: string }> }) {
32
33
  const { name } = use(params);
33
- const [spec, setSpec] = useState<SpecData | null>(null);
34
- const [error, setError] = useState<string | null>(null);
34
+ const { data, isLoading, error } = useSpec(name);
35
+ const { invalidateSpec, invalidateSpecs } = useInvalidate();
36
+ useWebSocket();
35
37
 
36
- const loadSpec = useCallback(async function () {
37
- try {
38
- const response = await fetch(`/api/specs/${name}`);
39
- if (!response.ok) {
40
- const data = await response.json();
41
- setError(data.error || "Spec not found");
42
- return;
43
- }
44
- const data = await response.json();
45
- setSpec(data);
46
- } catch {
47
- setError("Failed to load spec");
48
- }
49
- }, [name]);
38
+ const spec = data as SpecData | undefined;
50
39
 
51
- // eslint-disable-next-line react-hooks/set-state-in-effect
52
- useEffect(() => { loadSpec(); }, [loadSpec]);
53
-
54
- // Real-time updates via WebSocket
55
- useEffect(() => {
56
- let ws: WebSocket | null = null;
57
- let reconnectTimeout: ReturnType<typeof setTimeout>;
58
- let isUnmounted = false;
59
-
60
- function connect() {
61
- if (isUnmounted) return;
62
- try {
63
- ws = new WebSocket("ws://localhost:3001");
64
- ws.onmessage = (event) => {
65
- const data = JSON.parse(event.data);
66
- if (data.specName === name && (
67
- data.type === "artifact_approved" ||
68
- data.type === "artifact_updated" ||
69
- data.type === "spec_promoted"
70
- ) || (data.type === "task_updated" && data.task?.specName === name)) {
71
- loadSpec();
72
- }
73
- };
74
- ws.onclose = () => {
75
- if (!isUnmounted) reconnectTimeout = setTimeout(connect, 3000);
76
- };
77
- ws.onerror = () => {};
78
- } catch {
79
- if (!isUnmounted) reconnectTimeout = setTimeout(connect, 3000);
80
- }
81
- }
82
-
83
- connect();
84
- return () => {
85
- isUnmounted = true;
86
- clearTimeout(reconnectTimeout);
87
- ws?.close();
88
- };
89
- }, [name, loadSpec]);
40
+ function handleRefresh() {
41
+ invalidateSpec(name);
42
+ invalidateSpecs();
43
+ }
90
44
 
91
45
  return (
92
46
  <div className="min-h-screen bg-[--color-bg]">
@@ -96,8 +50,12 @@ export default function SpecPage({ params }: { params: Promise<{ name: string }>
96
50
  DevFlow
97
51
  </Link>
98
52
  <nav className="flex gap-4">
99
- <Link href="/" className="font-bold uppercase text-sm hover:underline">Board</Link>
100
- <Link href="/specs" className="font-bold uppercase text-sm hover:underline">Specs</Link>
53
+ <Link href="/" className="font-bold uppercase text-sm hover:underline">
54
+ Board
55
+ </Link>
56
+ <Link href="/specs" className="font-bold uppercase text-sm hover:underline">
57
+ Specs
58
+ </Link>
101
59
  </nav>
102
60
  </div>
103
61
  </header>
@@ -105,11 +63,13 @@ export default function SpecPage({ params }: { params: Promise<{ name: string }>
105
63
  <main className="container mx-auto px-6 py-8">
106
64
  {error ? (
107
65
  <div className="p-6 bg-red-100 border-4 border-red-500">
108
- <p className="font-bold text-red-800">{error}</p>
66
+ <p className="font-bold text-red-800">Failed to load spec</p>
109
67
  <Link href="/specs" className="text-sm underline mt-2 block">
110
68
  Back to Specs
111
69
  </Link>
112
70
  </div>
71
+ ) : isLoading ? (
72
+ <p className="font-bold">Loading...</p>
113
73
  ) : spec ? (
114
74
  <SpecDetail
115
75
  specName={spec.name}
@@ -117,11 +77,9 @@ export default function SpecPage({ params }: { params: Promise<{ name: string }>
117
77
  statuses={spec.statuses}
118
78
  artifacts={spec.artifacts}
119
79
  tasks={spec.tasks ?? []}
120
- onRefresh={loadSpec}
80
+ onRefresh={handleRefresh}
121
81
  />
122
- ) : (
123
- <p className="font-bold">Loading...</p>
124
- )}
82
+ ) : null}
125
83
  </main>
126
84
  </div>
127
85
  );
@@ -1,6 +1,6 @@
1
- "use client";
1
+ 'use client';
2
2
 
3
- import { useState, useEffect, useCallback, useRef } from "react";
3
+ import { useState, useEffect, useCallback, useRef } from 'react';
4
4
  import {
5
5
  DndContext,
6
6
  DragEndEvent,
@@ -9,167 +9,89 @@ import {
9
9
  PointerSensor,
10
10
  useSensor,
11
11
  useSensors,
12
- } from "@dnd-kit/core";
13
- import { Task, Project } from "@/db/schema";
14
- import { KanbanColumn } from "./kanban-column";
15
- import { TaskCard } from "./task-card";
16
- import { ChevronDown, Trash2 } from "lucide-react";
17
- import { SpecKanbanColumn } from "./specs/spec-kanban-column";
18
- import { SpecModal } from "./specs/spec-modal";
19
-
20
- type TaskStatus = "backlog" | "todo" | "in_progress" | "interrupted" | "done";
12
+ } from '@dnd-kit/core';
13
+ import { Task, Project } from '@/db/schema';
14
+ import { KanbanColumn } from './kanban-column';
15
+ import { TaskCard } from './task-card';
16
+ import { ChevronDown, Trash2 } from 'lucide-react';
17
+ import { SpecKanbanColumn } from './specs/spec-kanban-column';
18
+ import { SpecModal } from './specs/spec-modal';
19
+ import { useProjects, useTasks, useInvalidate } from '@/hooks/use-queries';
20
+ import { useWebSocket } from '@/hooks/use-websocket';
21
+
22
+ type TaskStatus = 'backlog' | 'todo' | 'in_progress' | 'interrupted' | 'done';
21
23
 
22
24
  const COLUMNS: { id: TaskStatus; title: string; color: string }[] = [
23
- { id: "backlog", title: "Backlog", color: "bg-gray-200" },
24
- { id: "todo", title: "To Do", color: "bg-[--color-primary]" },
25
- { id: "in_progress", title: "In Progress", color: "bg-[--color-secondary]" },
26
- { id: "done", title: "Done", color: "bg-[--color-success]" },
25
+ { id: 'backlog', title: 'Backlog', color: 'bg-gray-200' },
26
+ { id: 'todo', title: 'To Do', color: 'bg-[--color-primary]' },
27
+ { id: 'in_progress', title: 'In Progress', color: 'bg-[--color-secondary]' },
28
+ { id: 'done', title: 'Done', color: 'bg-[--color-success]' },
27
29
  ];
28
30
 
29
31
  const COLUMN_IDS: Set<string> = new Set(COLUMNS.map((c) => c.id));
30
32
 
31
- const STORAGE_KEY = "devflow-selected-project";
33
+ const STORAGE_KEY = 'devflow-selected-project';
32
34
 
33
35
  export function KanbanBoard() {
34
- const [tasks, setTasks] = useState<Task[]>([]);
35
- const [projects, setProjects] = useState<Project[]>([]);
36
+ const { data: projects = [], isLoading: projectsLoading } = useProjects();
37
+ const { data: tasks = [], isLoading: tasksLoading } = useTasks();
38
+ const { invalidateTasks, invalidateProjects } = useInvalidate();
39
+ useWebSocket();
40
+
36
41
  const [selectedProjectId, setSelectedProjectId] = useState<string | null>(null);
37
42
  const [selectedSpecName, setSelectedSpecName] = useState<string | null>(null);
38
43
  const [specPanelOpen, setSpecPanelOpen] = useState(false);
39
44
  const [activeTask, setActiveTask] = useState<Task | null>(null);
40
- const [isLoading, setIsLoading] = useState(true);
41
45
  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
42
46
  const [isInitialized, setIsInitialized] = useState(false);
43
47
 
44
- const selectedProjectIdRef = useRef<string | null>(null);
45
- selectedProjectIdRef.current = selectedProjectId;
46
-
47
48
  const sensors = useSensors(
48
49
  useSensor(PointerSensor, {
49
50
  activationConstraint: {
50
51
  distance: 8,
51
52
  },
52
- })
53
+ }),
53
54
  );
54
55
 
55
56
  useEffect(() => {
56
57
  const saved = localStorage.getItem(STORAGE_KEY);
57
58
  if (saved) {
58
- setSelectedProjectId(saved === "all" ? null : saved);
59
+ setSelectedProjectId(saved === 'all' ? null : saved);
59
60
  }
60
61
  setIsInitialized(true);
61
62
  }, []);
62
63
 
63
- const selectProject = useCallback((projectId: string | null) => {
64
- setSelectedProjectId(projectId);
65
- setSelectedSpecName(null);
66
- localStorage.setItem(STORAGE_KEY, projectId || "all");
67
- setIsDropdownOpen(false);
68
- }, []);
69
-
64
+ // Auto-select project if needed
70
65
  useEffect(() => {
71
- fetchProjects();
72
- fetchTasks();
73
-
74
- const pollInterval = setInterval(() => {
75
- fetchTasks();
76
- fetchProjects();
77
- }, 5000);
78
-
79
- return () => clearInterval(pollInterval);
80
- }, []);
81
-
82
- useEffect(() => {
83
- let ws: WebSocket | null = null;
84
- let reconnectTimeout: NodeJS.Timeout;
85
- let isUnmounted = false;
86
-
87
- function connect() {
88
- if (isUnmounted) return;
89
-
90
- try {
91
- ws = new WebSocket("ws://localhost:3001");
92
-
93
- ws.onopen = () => {
94
- console.log("WebSocket connected");
95
- };
96
-
97
- ws.onmessage = (event) => {
98
- const data = JSON.parse(event.data);
99
- if (data.type === "agent_count") return;
100
- if (data.type?.includes("project")) {
101
- fetchProjects();
102
- }
103
- fetchTasks();
104
- };
105
-
106
- ws.onerror = () => {};
107
-
108
- ws.onclose = () => {
109
- if (!isUnmounted) {
110
- reconnectTimeout = setTimeout(connect, 3000);
111
- }
112
- };
113
- } catch {
114
- if (!isUnmounted) {
115
- reconnectTimeout = setTimeout(connect, 3000);
116
- }
66
+ if (!isInitialized || projectsLoading) return;
67
+ if (selectedProjectId) {
68
+ const exists = projects.some((p: Project) => p.id === selectedProjectId);
69
+ if (!exists && projects.length > 0) {
70
+ selectProject(projects[0].id);
117
71
  }
72
+ } else if (!localStorage.getItem(STORAGE_KEY) && projects.length > 0) {
73
+ selectProject(projects[0].id);
118
74
  }
75
+ // eslint-disable-next-line react-hooks/exhaustive-deps
76
+ }, [isInitialized, projectsLoading, projects]);
119
77
 
120
- connect();
121
-
122
- return () => {
123
- isUnmounted = true;
124
- clearTimeout(reconnectTimeout);
125
- ws?.close();
126
- };
127
- // eslint-disable-next-line react-hooks/exhaustive-deps
78
+ const selectProject = useCallback((projectId: string | null) => {
79
+ setSelectedProjectId(projectId);
80
+ setSelectedSpecName(null);
81
+ localStorage.setItem(STORAGE_KEY, projectId || 'all');
82
+ setIsDropdownOpen(false);
128
83
  }, []);
129
84
 
130
- async function fetchProjects() {
131
- try {
132
- const response = await fetch("/api/projects");
133
- const data = await response.json();
134
- setProjects(data);
135
-
136
- if (isInitialized && selectedProjectId) {
137
- const exists = data.some((p: Project) => p.id === selectedProjectId);
138
- if (!exists && data.length > 0) {
139
- selectProject(data[0].id);
140
- }
141
- } else if (isInitialized && !selectedProjectId && !localStorage.getItem(STORAGE_KEY)) {
142
- if (data.length > 0) {
143
- selectProject(data[0].id);
144
- }
145
- }
146
- } catch (error) {
147
- console.error("Failed to fetch projects:", error);
148
- }
149
- }
150
-
151
- async function fetchTasks() {
152
- try {
153
- const response = await fetch("/api/tasks");
154
- const data = await response.json();
155
- setTasks(data);
156
- } catch (error) {
157
- console.error("Failed to fetch tasks:", error);
158
- } finally {
159
- setIsLoading(false);
160
- }
161
- }
162
-
163
85
  async function updateTaskStatus(taskId: string, newStatus: TaskStatus) {
164
86
  try {
165
87
  await fetch(`/api/tasks/${taskId}`, {
166
- method: "PATCH",
167
- headers: { "Content-Type": "application/json" },
88
+ method: 'PATCH',
89
+ headers: { 'Content-Type': 'application/json' },
168
90
  body: JSON.stringify({ status: newStatus }),
169
91
  });
170
- await fetchTasks();
92
+ invalidateTasks();
171
93
  } catch (error) {
172
- console.error("Failed to update task:", error);
94
+ console.error('Failed to update task:', error);
173
95
  }
174
96
  }
175
97
 
@@ -205,8 +127,8 @@ export function KanbanBoard() {
205
127
  function getTasksByStatus(status: TaskStatus) {
206
128
  return tasks
207
129
  .filter((task) => {
208
- if (task.status === "interrupted") {
209
- return status === "in_progress";
130
+ if (task.status === 'interrupted') {
131
+ return status === 'in_progress';
210
132
  }
211
133
  return task.status === status;
212
134
  })
@@ -216,29 +138,32 @@ export function KanbanBoard() {
216
138
 
217
139
  async function deleteProject(projectId: string, projectName: string) {
218
140
  const taskCount = tasks.filter((t) => t.projectId === projectId).length;
219
- const confirmMessage = taskCount > 0
220
- ? `Delete "${projectName}" and its ${taskCount} task${taskCount === 1 ? "" : "s"}? This cannot be undone.`
221
- : `Delete "${projectName}"? This cannot be undone.`;
141
+ const confirmMessage =
142
+ taskCount > 0
143
+ ? `Delete "${projectName}" and its ${taskCount} task${taskCount === 1 ? '' : 's'}? This cannot be undone.`
144
+ : `Delete "${projectName}"? This cannot be undone.`;
222
145
 
223
146
  if (!confirm(confirmMessage)) return;
224
147
 
225
148
  try {
226
149
  const response = await fetch(`/api/projects/${projectId}`, {
227
- method: "DELETE",
150
+ method: 'DELETE',
228
151
  });
229
152
 
230
153
  if (response.ok) {
231
154
  if (selectedProjectId === projectId) {
232
155
  selectProject(null);
233
156
  }
234
- await fetchProjects();
235
- await fetchTasks();
157
+ invalidateProjects();
158
+ invalidateTasks();
236
159
  }
237
160
  } catch (error) {
238
- console.error("Failed to delete project:", error);
161
+ console.error('Failed to delete project:', error);
239
162
  }
240
163
  }
241
164
 
165
+ const isLoading = tasksLoading && !tasks.length;
166
+
242
167
  if (isLoading) {
243
168
  return (
244
169
  <div className="flex items-center justify-center h-screen">
@@ -264,7 +189,7 @@ export function KanbanBoard() {
264
189
  <div className="flex items-center gap-6">
265
190
  <div className="flex items-center gap-4">
266
191
  <h1 className="text-3xl font-black uppercase tracking-tight">DevFlow</h1>
267
- </div>
192
+ </div>
268
193
 
269
194
  {/* Project Chooser Dropdown */}
270
195
  <div className="relative">
@@ -273,24 +198,23 @@ export function KanbanBoard() {
273
198
  className="flex items-center gap-2 px-4 py-2 bg-[--color-primary] border-4 border-black font-bold text-sm uppercase hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-none transition-all shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]"
274
199
  >
275
200
  <span className="max-w-[200px] truncate">
276
- {selectedProject ? selectedProject.name : "All Projects"}
201
+ {selectedProject ? selectedProject.name : 'All Projects'}
277
202
  </span>
278
203
  <span className="text-xs opacity-70">({totalTasks})</span>
279
- <ChevronDown className={`h-4 w-4 transition-transform ${isDropdownOpen ? "rotate-180" : ""}`} />
204
+ <ChevronDown
205
+ className={`h-4 w-4 transition-transform ${isDropdownOpen ? 'rotate-180' : ''}`}
206
+ />
280
207
  </button>
281
208
 
282
209
  {isDropdownOpen && (
283
210
  <>
284
- <div
285
- className="fixed inset-0 z-10"
286
- onClick={() => setIsDropdownOpen(false)}
287
- />
211
+ <div className="fixed inset-0 z-10" onClick={() => setIsDropdownOpen(false)} />
288
212
 
289
213
  <div className="absolute top-full left-0 mt-2 w-64 bg-white border-4 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] z-20 max-h-80 overflow-y-auto">
290
214
  <button
291
215
  onClick={() => selectProject(null)}
292
216
  className={`w-full px-4 py-3 text-left font-bold text-sm uppercase border-b-2 border-black hover:bg-gray-100 flex items-center justify-between ${
293
- !selectedProjectId ? "bg-black text-white hover:bg-gray-800" : ""
217
+ !selectedProjectId ? 'bg-black text-white hover:bg-gray-800' : ''
294
218
  }`}
295
219
  >
296
220
  <span>All Projects</span>
@@ -298,12 +222,16 @@ export function KanbanBoard() {
298
222
  </button>
299
223
 
300
224
  {projects.map((project) => {
301
- const projectTaskCount = tasks.filter((t) => t.projectId === project.id).length;
225
+ const projectTaskCount = tasks.filter(
226
+ (t) => t.projectId === project.id,
227
+ ).length;
302
228
  return (
303
229
  <div
304
230
  key={project.id}
305
231
  className={`flex items-center border-b border-gray-200 last:border-b-0 ${
306
- selectedProjectId === project.id ? "bg-[--color-primary]" : "hover:bg-gray-100"
232
+ selectedProjectId === project.id
233
+ ? 'bg-[--color-primary]'
234
+ : 'hover:bg-gray-100'
307
235
  }`}
308
236
  >
309
237
  <button
@@ -328,9 +256,7 @@ export function KanbanBoard() {
328
256
  })}
329
257
 
330
258
  {projects.length === 0 && (
331
- <div className="px-4 py-3 text-sm text-gray-500">
332
- No projects yet
333
- </div>
259
+ <div className="px-4 py-3 text-sm text-gray-500">No projects yet</div>
334
260
  )}
335
261
  </div>
336
262
  </>
@@ -353,8 +279,10 @@ export function KanbanBoard() {
353
279
  )}
354
280
  </div>
355
281
  <div className="flex items-center gap-4 text-sm font-bold">
356
- <span>{projectTasks.filter((t) => t.status === "in_progress").length} in progress</span>
357
- <span>{projectTasks.filter((t) => t.status === "done").length} done</span>
282
+ <span>
283
+ {projectTasks.filter((t) => t.status === 'in_progress').length} in progress
284
+ </span>
285
+ <span>{projectTasks.filter((t) => t.status === 'done').length} done</span>
358
286
  </div>
359
287
  </div>
360
288
  </div>
@@ -376,11 +304,7 @@ export function KanbanBoard() {
376
304
 
377
305
  {/* Kanban columns */}
378
306
  <main className="flex-1 min-h-0 overflow-hidden">
379
- <DndContext
380
- sensors={sensors}
381
- onDragStart={handleDragStart}
382
- onDragEnd={handleDragEnd}
383
- >
307
+ <DndContext sensors={sensors} onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
384
308
  <div className="h-full px-6 py-6">
385
309
  <div className="grid grid-cols-4 gap-6 h-full min-h-0">
386
310
  {COLUMNS.map((column) => (
@@ -391,7 +315,7 @@ export function KanbanBoard() {
391
315
  color={column.color}
392
316
  tasks={getTasksByStatus(column.id)}
393
317
  features={[]}
394
- onRefresh={fetchTasks}
318
+ onRefresh={() => invalidateTasks()}
395
319
  />
396
320
  ))}
397
321
  </div>
@@ -404,7 +328,7 @@ export function KanbanBoard() {
404
328
  task={activeTask}
405
329
  feature={undefined}
406
330
  features={[]}
407
- onRefresh={fetchTasks}
331
+ onRefresh={() => invalidateTasks()}
408
332
  />
409
333
  </div>
410
334
  ) : null}
@@ -414,10 +338,7 @@ export function KanbanBoard() {
414
338
 
415
339
  {/* Spec modal */}
416
340
  {specPanelOpen && selectedSpecName && (
417
- <SpecModal
418
- specName={selectedSpecName}
419
- onClose={() => setSpecPanelOpen(false)}
420
- />
341
+ <SpecModal specName={selectedSpecName} onClose={() => setSpecPanelOpen(false)} />
421
342
  )}
422
343
  </div>
423
344
  </div>
@@ -1,10 +1,10 @@
1
- "use client";
1
+ 'use client';
2
2
 
3
- import { useDroppable } from "@dnd-kit/core";
4
- import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
5
- import { Task } from "@/db/schema";
6
- import { TaskCard } from "./task-card";
7
- import { cn } from "@/lib/utils";
3
+ import { useDroppable } from '@dnd-kit/core';
4
+ import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
5
+ import { Task } from '@/db/schema';
6
+ import { TaskCard } from './task-card';
7
+ import { cn } from '@/lib/utils';
8
8
 
9
9
  interface KanbanColumnProps {
10
10
  id: string;
@@ -20,20 +20,18 @@ export function KanbanColumn({ id, title, color, tasks, onRefresh }: KanbanColum
20
20
 
21
21
  return (
22
22
  <div className="flex flex-col h-full min-h-0">
23
- <div className={cn("px-4 py-3 border-3 border-black border-b-0", color)}>
23
+ <div className={cn('px-4 py-3 border-3 border-black border-b-0', color)}>
24
24
  <h2 className="font-black uppercase tracking-wide text-black flex items-center justify-between">
25
25
  <span>{title}</span>
26
- <span className="text-sm font-bold bg-black text-white px-2 py-1">
27
- {tasks.length}
28
- </span>
26
+ <span className="text-sm font-bold bg-black text-white px-2 py-1">{tasks.length}</span>
29
27
  </h2>
30
28
  </div>
31
29
 
32
30
  <div
33
31
  ref={setNodeRef}
34
32
  className={cn(
35
- "flex-1 overflow-y-auto p-4 space-y-3 bg-white border-3 border-black transition-colors",
36
- isOver && "bg-gray-100"
33
+ 'flex-1 overflow-y-auto p-4 space-y-3 bg-white border-3 border-black transition-colors',
34
+ isOver && 'bg-gray-100',
37
35
  )}
38
36
  >
39
37
  <SortableContext items={tasks.map((t) => t.id)} strategy={verticalListSortingStrategy}>
@@ -1,29 +1,40 @@
1
- "use client";
1
+ 'use client';
2
2
 
3
- import { useEffect, useRef, useMemo } from "react";
4
- import ReactMarkdown from "react-markdown";
5
- import remarkGfm from "remark-gfm";
6
- import rehypeRaw from "rehype-raw";
7
- import mermaid from "mermaid";
8
- import { cn } from "@/lib/utils";
9
-
10
- mermaid.initialize({ startOnLoad: false, theme: "neutral" });
3
+ import { useEffect, useRef, useMemo, useState } from 'react';
4
+ import ReactMarkdown from 'react-markdown';
5
+ import remarkGfm from 'remark-gfm';
6
+ import rehypeRaw from 'rehype-raw';
7
+ import { cn } from '@/lib/utils';
11
8
 
12
9
  let mermaidCounter = 0;
13
10
 
14
11
  function MermaidBlock({ chart }: { chart: string }) {
15
12
  const ref = useRef<HTMLDivElement>(null);
16
13
  const id = useMemo(() => `mermaid-${++mermaidCounter}`, []);
14
+ const [error, setError] = useState<string | null>(null);
17
15
 
18
16
  useEffect(() => {
19
17
  if (!ref.current) return;
20
- mermaid.render(id, chart).then(({ svg }) => {
21
- if (ref.current) ref.current.innerHTML = svg;
22
- }).catch((err) => {
23
- if (ref.current) ref.current.textContent = `Mermaid error: ${err.message}`;
24
- });
18
+ let cancelled = false;
19
+ import('mermaid')
20
+ .then(({ default: mermaid }) => {
21
+ if (cancelled) return;
22
+ mermaid.initialize({ startOnLoad: false, theme: 'neutral' });
23
+ return mermaid.render(id, chart);
24
+ })
25
+ .then((result) => {
26
+ if (cancelled || !result) return;
27
+ if (ref.current) ref.current.innerHTML = result.svg;
28
+ })
29
+ .catch((err) => {
30
+ if (!cancelled && ref.current) setError(err.message);
31
+ });
32
+ return () => {
33
+ cancelled = true;
34
+ };
25
35
  }, [id, chart]);
26
36
 
37
+ if (error) return <div className="my-4 text-red-600">Mermaid error: {error}</div>;
27
38
  return <div ref={ref} className="my-4 overflow-x-auto" />;
28
39
  }
29
40
 
@@ -34,7 +45,7 @@ interface MarkdownPreviewProps {
34
45
 
35
46
  export function MarkdownPreview({ content, className }: MarkdownPreviewProps) {
36
47
  return (
37
- <div className={cn("markdown-preview", className)}>
48
+ <div className={cn('markdown-preview', className)}>
38
49
  <ReactMarkdown
39
50
  remarkPlugins={[remarkGfm]}
40
51
  rehypePlugins={[rehypeRaw]}
@@ -63,17 +74,23 @@ export function MarkdownPreview({ content, className }: MarkdownPreviewProps) {
63
74
  </div>
64
75
  ),
65
76
  thead: ({ children }) => <thead className="bg-black text-white">{children}</thead>,
66
- th: ({ children }) => <th className="px-3 py-2 text-left font-bold uppercase">{children}</th>,
77
+ th: ({ children }) => (
78
+ <th className="px-3 py-2 text-left font-bold uppercase">{children}</th>
79
+ ),
67
80
  td: ({ children }) => <td className="px-3 py-2 border-t-2 border-black">{children}</td>,
68
81
  // Code blocks + Mermaid
69
82
  code: ({ className: cls, children, ...props }) => {
70
- const lang = /language-(\w+)/.exec(cls || "")?.[1];
71
- if (lang === "mermaid") {
83
+ const lang = /language-(\w+)/.exec(cls || '')?.[1];
84
+ if (lang === 'mermaid') {
72
85
  return <MermaidBlock chart={String(children).trim()} />;
73
86
  }
74
87
  // Inline code (no language class)
75
88
  if (!cls) {
76
- return <code className="inline-code" {...props}>{children}</code>;
89
+ return (
90
+ <code className="inline-code" {...props}>
91
+ {children}
92
+ </code>
93
+ );
77
94
  }
78
95
  return (
79
96
  <pre className="code-block" data-lang={lang}>