@leanspec/ui 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/.next/standalone/packages/ui/.next/BUILD_ID +1 -1
  2. package/.next/standalone/packages/ui/.next/app-path-routes-manifest.json +4 -0
  3. package/.next/standalone/packages/ui/.next/build-manifest.json +4 -4
  4. package/.next/standalone/packages/ui/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/packages/ui/.next/routes-manifest.json +24 -0
  6. package/.next/standalone/packages/ui/.next/server/app/_global-error/page/build-manifest.json +2 -2
  7. package/.next/standalone/packages/ui/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/packages/ui/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/packages/ui/.next/server/app/_global-error.html +2 -2
  10. package/.next/standalone/packages/ui/.next/server/app/_global-error.rsc +1 -1
  11. package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  12. package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  13. package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/standalone/packages/ui/.next/server/app/_not-found/page/build-manifest.json +2 -2
  16. package/.next/standalone/packages/ui/.next/server/app/_not-found/page.js +1 -1
  17. package/.next/standalone/packages/ui/.next/server/app/_not-found/page.js.nft.json +1 -1
  18. package/.next/standalone/packages/ui/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  19. package/.next/standalone/packages/ui/.next/server/app/_not-found.html +2 -2
  20. package/.next/standalone/packages/ui/.next/server/app/_not-found.rsc +19 -19
  21. package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_full.segment.rsc +19 -19
  22. package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
  23. package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +3 -3
  24. package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  25. package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_tree.segment.rsc +6 -6
  26. package/.next/standalone/packages/ui/.next/server/app/api/context/route/app-paths-manifest.json +3 -0
  27. package/.next/standalone/packages/ui/.next/server/app/api/context/route/build-manifest.json +11 -0
  28. package/.next/standalone/packages/ui/.next/server/app/api/context/route/server-reference-manifest.json +4 -0
  29. package/.next/standalone/packages/ui/.next/server/app/api/context/route.js +8 -0
  30. package/.next/standalone/packages/ui/.next/server/app/api/context/route.js.map +5 -0
  31. package/.next/standalone/packages/ui/.next/server/app/api/context/route.js.nft.json +1 -0
  32. package/.next/standalone/packages/ui/.next/server/app/api/context/route_client-reference-manifest.js +2 -0
  33. package/.next/standalone/packages/ui/.next/server/app/api/dependencies/route/app-paths-manifest.json +3 -0
  34. package/.next/standalone/packages/ui/.next/server/app/api/dependencies/route/build-manifest.json +11 -0
  35. package/.next/standalone/packages/ui/.next/server/app/api/dependencies/route/server-reference-manifest.json +4 -0
  36. package/.next/standalone/packages/ui/.next/server/app/api/dependencies/route.js +8 -0
  37. package/.next/standalone/packages/ui/.next/server/app/api/dependencies/route.js.map +5 -0
  38. package/.next/standalone/packages/ui/.next/server/app/api/dependencies/route.js.nft.json +1 -0
  39. package/.next/standalone/packages/ui/.next/server/app/api/dependencies/route_client-reference-manifest.js +2 -0
  40. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/[id]/route.js.nft.json +1 -1
  41. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/discover/route.js.nft.json +1 -1
  42. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/list-directory/route.js.nft.json +1 -1
  43. package/.next/standalone/packages/ui/.next/server/app/api/local-projects/route.js.nft.json +1 -1
  44. package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/route.js.nft.json +1 -1
  45. package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/specs/[spec]/route.js.nft.json +1 -1
  46. package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/specs/[spec]/status/route.js.nft.json +1 -1
  47. package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/specs/route.js.nft.json +1 -1
  48. package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/stats/route.js.nft.json +1 -1
  49. package/.next/standalone/packages/ui/.next/server/app/api/projects/route.js.nft.json +1 -1
  50. package/.next/standalone/packages/ui/.next/server/app/api/revalidate/route.js.nft.json +1 -1
  51. package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/dependency-graph/route.js.nft.json +1 -1
  52. package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/route.js.nft.json +1 -1
  53. package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/status/route.js.nft.json +1 -1
  54. package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/subspecs/[file]/route.js.nft.json +1 -1
  55. package/.next/standalone/packages/ui/.next/server/app/api/stats/route.js.nft.json +1 -1
  56. package/.next/standalone/packages/ui/.next/server/app/context/page/app-paths-manifest.json +3 -0
  57. package/.next/standalone/packages/ui/.next/server/app/context/page/build-manifest.json +18 -0
  58. package/.next/standalone/packages/ui/.next/server/app/context/page/next-font-manifest.json +6 -0
  59. package/.next/standalone/packages/ui/.next/server/app/context/page/react-loadable-manifest.json +1 -0
  60. package/.next/standalone/packages/ui/.next/server/app/context/page/server-reference-manifest.json +4 -0
  61. package/.next/standalone/packages/ui/.next/server/app/context/page.js +19 -0
  62. package/.next/standalone/packages/ui/.next/server/app/context/page.js.map +5 -0
  63. package/.next/standalone/packages/ui/.next/server/app/context/page.js.nft.json +1 -0
  64. package/.next/standalone/packages/ui/.next/server/app/context/page_client-reference-manifest.js +2 -0
  65. package/.next/standalone/packages/ui/.next/server/app/dependencies/page/app-paths-manifest.json +3 -0
  66. package/.next/standalone/packages/ui/.next/server/app/dependencies/page/build-manifest.json +18 -0
  67. package/.next/standalone/packages/ui/.next/server/app/dependencies/page/next-font-manifest.json +6 -0
  68. package/.next/standalone/packages/ui/.next/server/app/dependencies/page/react-loadable-manifest.json +1 -0
  69. package/.next/standalone/packages/ui/.next/server/app/dependencies/page/server-reference-manifest.json +4 -0
  70. package/.next/standalone/packages/ui/.next/server/app/dependencies/page.js +19 -0
  71. package/.next/standalone/packages/ui/.next/server/app/dependencies/page.js.map +5 -0
  72. package/.next/standalone/packages/ui/.next/server/app/dependencies/page.js.nft.json +1 -0
  73. package/.next/standalone/packages/ui/.next/server/app/dependencies/page_client-reference-manifest.js +2 -0
  74. package/.next/standalone/packages/ui/.next/server/app/page/build-manifest.json +2 -2
  75. package/.next/standalone/packages/ui/.next/server/app/page.js +1 -1
  76. package/.next/standalone/packages/ui/.next/server/app/page.js.nft.json +1 -1
  77. package/.next/standalone/packages/ui/.next/server/app/page_client-reference-manifest.js +1 -1
  78. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/page/build-manifest.json +2 -2
  79. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/page.js +1 -1
  80. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/page.js.nft.json +1 -1
  81. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/page_client-reference-manifest.js +1 -1
  82. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/[specId]/page/build-manifest.json +2 -2
  83. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/[specId]/page.js +1 -1
  84. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/[specId]/page.js.nft.json +1 -1
  85. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/[specId]/page_client-reference-manifest.js +1 -1
  86. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/page/build-manifest.json +2 -2
  87. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/page.js +1 -1
  88. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/page.js.nft.json +1 -1
  89. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/page_client-reference-manifest.js +1 -1
  90. package/.next/standalone/packages/ui/.next/server/app/projects/page/build-manifest.json +2 -2
  91. package/.next/standalone/packages/ui/.next/server/app/projects/page.js +1 -1
  92. package/.next/standalone/packages/ui/.next/server/app/projects/page.js.nft.json +1 -1
  93. package/.next/standalone/packages/ui/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  94. package/.next/standalone/packages/ui/.next/server/app/projects.html +2 -2
  95. package/.next/standalone/packages/ui/.next/server/app/projects.rsc +24 -23
  96. package/.next/standalone/packages/ui/.next/server/app/projects.segments/_full.segment.rsc +24 -23
  97. package/.next/standalone/packages/ui/.next/server/app/projects.segments/_index.segment.rsc +9 -9
  98. package/.next/standalone/packages/ui/.next/server/app/projects.segments/_tree.segment.rsc +3 -3
  99. package/.next/standalone/packages/ui/.next/server/app/projects.segments/projects/__PAGE__.segment.rsc +2 -2
  100. package/.next/standalone/packages/ui/.next/server/app/projects.segments/projects.segment.rsc +1 -1
  101. package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page/build-manifest.json +2 -2
  102. package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page.js +1 -1
  103. package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page.js.nft.json +1 -1
  104. package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page_client-reference-manifest.js +1 -1
  105. package/.next/standalone/packages/ui/.next/server/app/specs/page/build-manifest.json +2 -2
  106. package/.next/standalone/packages/ui/.next/server/app/specs/page.js +1 -1
  107. package/.next/standalone/packages/ui/.next/server/app/specs/page.js.nft.json +1 -1
  108. package/.next/standalone/packages/ui/.next/server/app/specs/page_client-reference-manifest.js +1 -1
  109. package/.next/standalone/packages/ui/.next/server/app/stats/page/build-manifest.json +2 -2
  110. package/.next/standalone/packages/ui/.next/server/app/stats/page.js +1 -1
  111. package/.next/standalone/packages/ui/.next/server/app/stats/page.js.nft.json +1 -1
  112. package/.next/standalone/packages/ui/.next/server/app/stats/page_client-reference-manifest.js +1 -1
  113. package/.next/standalone/packages/ui/.next/server/app-paths-manifest.json +4 -0
  114. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__3559376c._.js +2 -2
  115. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__65667b70._.js +1 -1
  116. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__803d07f0._.js +1 -1
  117. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__84cdc14a._.js +1 -1
  118. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__8a9ab1a3._.js +3 -0
  119. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__b0969111._.js +3 -0
  120. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__bdc3963a._.js +1 -1
  121. package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__f5c6d6b8._.js +1 -1
  122. package/.next/standalone/packages/ui/.next/server/chunks/packages_ui__next-internal_server_app_api_context_route_actions_dead6daa.js +3 -0
  123. package/.next/standalone/packages/ui/.next/server/chunks/packages_ui__next-internal_server_app_api_dependencies_route_actions_cf6b14c3.js +3 -0
  124. package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__12b4eb41._.js +3 -0
  125. package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__3c77d95b._.js +3 -0
  126. package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__44b603f9._.js +3 -0
  127. package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__69a0d63a._.js +3 -0
  128. package/.next/standalone/packages/ui/.next/server/chunks/ssr/{[root-of-the-server]__daee3355._.js → [root-of-the-server]__8608a6fa._.js} +2 -2
  129. package/.next/standalone/packages/ui/.next/server/chunks/ssr/{[root-of-the-server]__fd80e4dd._.js → [root-of-the-server]__a965a67b._.js} +2 -2
  130. package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__daedd80e._.js +3 -0
  131. package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__dc176c61._.js +3 -0
  132. package/.next/standalone/packages/ui/.next/server/chunks/ssr/{[root-of-the-server]__5382b397._.js → [root-of-the-server]__ead1539c._.js} +2 -2
  133. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_000dd317._.js +1 -1
  134. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_03aa8d19._.js +7 -0
  135. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_28fe1532._.js +5 -0
  136. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_8cec504f._.js +3 -0
  137. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_959ad3d8._.js +4 -0
  138. package/.next/standalone/packages/ui/.next/server/chunks/ssr/{_22274047._.js → _adb9d7cb._.js} +2 -2
  139. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_da18b655._.js +3 -0
  140. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_ea899b87._.js +3 -0
  141. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_f38e75b7._.js +4 -0
  142. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_fe120f8a._.js +3 -0
  143. package/.next/standalone/packages/ui/.next/server/chunks/ssr/node_modules__pnpm_015f83ca._.js +3 -0
  144. package/.next/standalone/packages/ui/.next/server/chunks/ssr/node_modules__pnpm_1120b57c._.js +3 -0
  145. package/.next/standalone/packages/ui/.next/server/chunks/ssr/node_modules__pnpm_151891de._.js +3 -0
  146. package/.next/standalone/packages/ui/.next/server/chunks/ssr/node_modules__pnpm_1c916fe3._.js +3 -0
  147. package/.next/standalone/packages/ui/.next/server/chunks/ssr/node_modules__pnpm_80605a06._.js +3 -0
  148. package/.next/standalone/packages/ui/.next/server/chunks/ssr/node_modules__pnpm_bb80de48._.js +3 -0
  149. package/.next/standalone/packages/ui/.next/server/chunks/ssr/node_modules__pnpm_e8075f9b._.js +3 -0
  150. package/.next/standalone/packages/ui/.next/server/chunks/ssr/node_modules__pnpm_f919ef4a._.js +3 -0
  151. package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui__next-internal_server_app_context_page_actions_1a062d48.js +3 -0
  152. package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui__next-internal_server_app_dependencies_page_actions_57387d47.js +3 -0
  153. package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_app_context_context-client_tsx_4ba99a62._.js +12 -0
  154. package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_app_dependencies_dependencies-client_tsx_0e82443a._.js +4 -0
  155. package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_app_specs_specs-client_tsx_0bb8f8f8._.js +1 -1
  156. package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_components_spec-detail-wrapper_tsx_fd35401c._.js +3 -0
  157. package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_components_specs-nav-sidebar_tsx_8237ed13._.js +1 -1
  158. package/.next/standalone/packages/ui/.next/server/middleware-build-manifest.js +2 -2
  159. package/.next/standalone/packages/ui/.next/server/pages/404.html +2 -2
  160. package/.next/standalone/packages/ui/.next/server/pages/500.html +2 -2
  161. package/.next/standalone/packages/ui/.next/server/server-reference-manifest.js +1 -1
  162. package/.next/standalone/packages/ui/.next/server/server-reference-manifest.json +1 -1
  163. package/.next/standalone/packages/ui/.next/static/chunks/094cf9f4e3553261.js +1 -0
  164. package/.next/standalone/packages/ui/.next/static/chunks/1c015eb9eaaf9f9c.js +1 -0
  165. package/.next/standalone/packages/ui/.next/static/chunks/1d36660b2877d213.js +3 -0
  166. package/.next/standalone/packages/ui/.next/static/chunks/46275d9d67603bf5.js +1 -0
  167. package/.next/standalone/packages/ui/.next/static/chunks/4d29ca0fa6843070.js +2 -0
  168. package/.next/standalone/packages/ui/.next/static/chunks/59854b15bf046467.js +1 -0
  169. package/.next/standalone/packages/ui/.next/static/chunks/{cca4441cde342ae3.js → 5fd8101e32b076b1.js} +1 -1
  170. package/.next/standalone/packages/ui/.next/static/chunks/6d938a49daa10208.js +2 -0
  171. package/.next/standalone/packages/ui/.next/static/chunks/979625373d5474b6.js +1 -0
  172. package/.next/standalone/packages/ui/.next/static/chunks/a33f10af6abef4df.js +10 -0
  173. package/.next/{static/chunks/794f3931f1ca12d2.js → standalone/packages/ui/.next/static/chunks/a3d7e1be47de010b.js} +1 -1
  174. package/.next/standalone/packages/ui/.next/static/chunks/a5e25b9fa6b88eee.js +1 -0
  175. package/.next/standalone/packages/ui/.next/static/chunks/b53250480fde6816.js +1 -0
  176. package/.next/standalone/packages/ui/.next/static/chunks/b7f19087afe1d2c9.css +1 -0
  177. package/.next/standalone/packages/ui/.next/static/chunks/c658e22a605e0ed1.js +1 -0
  178. package/.next/standalone/packages/ui/.next/static/chunks/c92660d8d0c4763d.js +1 -0
  179. package/.next/standalone/packages/ui/.next/static/chunks/ddd87cd0d26bc2f5.js +1 -0
  180. package/.next/standalone/packages/ui/.next/static/chunks/eeec245955b3b600.js +5 -0
  181. package/.next/standalone/packages/ui/.next/static/chunks/ff11efb770d5a0bc.js +1 -0
  182. package/.next/{static/chunks/turbopack-5fa55215af0efb15.js → standalone/packages/ui/.next/static/chunks/turbopack-261c5dcdd873f310.js} +1 -1
  183. package/.next/standalone/packages/ui/package.json +5 -1
  184. package/.next/standalone/packages/ui/src/app/api/context/route.ts +22 -0
  185. package/.next/standalone/packages/ui/src/app/api/dependencies/route.ts +81 -0
  186. package/.next/standalone/packages/ui/src/app/context/context-client.tsx +393 -0
  187. package/.next/standalone/packages/ui/src/app/context/page.tsx +17 -0
  188. package/.next/standalone/packages/ui/src/app/dependencies/constants.ts +24 -0
  189. package/.next/standalone/packages/ui/src/app/dependencies/dependencies-client.tsx +625 -0
  190. package/.next/standalone/packages/ui/src/app/dependencies/index.ts +19 -0
  191. package/.next/standalone/packages/ui/src/app/dependencies/page.tsx +38 -0
  192. package/.next/standalone/packages/ui/src/app/dependencies/spec-node.tsx +80 -0
  193. package/.next/standalone/packages/ui/src/app/dependencies/spec-sidebar.tsx +198 -0
  194. package/.next/standalone/packages/ui/src/app/dependencies/types.ts +37 -0
  195. package/.next/standalone/packages/ui/src/app/dependencies/utils.ts +194 -0
  196. package/.next/standalone/packages/ui/src/app/globals.css +16 -16
  197. package/.next/standalone/packages/ui/src/app/layout.tsx +4 -7
  198. package/.next/standalone/packages/ui/src/components/context-file-detail.tsx +308 -0
  199. package/.next/standalone/packages/ui/src/components/context-file-viewer.tsx +385 -0
  200. package/.next/standalone/packages/ui/src/components/main-sidebar.tsx +19 -1
  201. package/.next/standalone/packages/ui/src/components/spec-detail-client.tsx +181 -134
  202. package/.next/standalone/packages/ui/src/components/spec-detail-wrapper.tsx +20 -0
  203. package/.next/standalone/packages/ui/src/components/specs-nav-sidebar.tsx +3 -2
  204. package/.next/standalone/packages/ui/src/components/ui/accordion.tsx +58 -0
  205. package/.next/standalone/packages/ui/src/lib/db/service-queries.ts +172 -3
  206. package/.next/standalone/packages/ui/src/lib/specs/types.ts +44 -0
  207. package/.next/standalone/packages/ui/tsconfig.tsbuildinfo +1 -1
  208. package/.next/static/chunks/094cf9f4e3553261.js +1 -0
  209. package/.next/static/chunks/1c015eb9eaaf9f9c.js +1 -0
  210. package/.next/static/chunks/1d36660b2877d213.js +3 -0
  211. package/.next/static/chunks/46275d9d67603bf5.js +1 -0
  212. package/.next/static/chunks/4d29ca0fa6843070.js +2 -0
  213. package/.next/static/chunks/59854b15bf046467.js +1 -0
  214. package/.next/static/chunks/{cca4441cde342ae3.js → 5fd8101e32b076b1.js} +1 -1
  215. package/.next/static/chunks/6d938a49daa10208.js +2 -0
  216. package/.next/static/chunks/979625373d5474b6.js +1 -0
  217. package/.next/static/chunks/a33f10af6abef4df.js +10 -0
  218. package/.next/{standalone/packages/ui/.next/static/chunks/794f3931f1ca12d2.js → static/chunks/a3d7e1be47de010b.js} +1 -1
  219. package/.next/static/chunks/a5e25b9fa6b88eee.js +1 -0
  220. package/.next/static/chunks/b53250480fde6816.js +1 -0
  221. package/.next/static/chunks/b7f19087afe1d2c9.css +1 -0
  222. package/.next/static/chunks/c658e22a605e0ed1.js +1 -0
  223. package/.next/static/chunks/c92660d8d0c4763d.js +1 -0
  224. package/.next/static/chunks/ddd87cd0d26bc2f5.js +1 -0
  225. package/.next/static/chunks/eeec245955b3b600.js +5 -0
  226. package/.next/static/chunks/ff11efb770d5a0bc.js +1 -0
  227. package/.next/{standalone/packages/ui/.next/static/chunks/turbopack-5fa55215af0efb15.js → static/chunks/turbopack-261c5dcdd873f310.js} +1 -1
  228. package/package.json +6 -2
  229. package/.next/standalone/node_modules/.pnpm/source-map@0.8.0-beta.0/node_modules/source-map/package.json +0 -95
  230. package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__1d0c2012._.js +0 -3
  231. package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__73f60f12._.js +0 -7
  232. package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__a7ae8552._.js +0 -7
  233. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_0f9ffe32._.js +0 -3
  234. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_14118969._.js +0 -3
  235. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_4129cc0f._.js +0 -3
  236. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_497c8b73._.js +0 -3
  237. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_ac867463._.js +0 -3
  238. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_c2f54661._.js +0 -5
  239. package/.next/standalone/packages/ui/.next/static/chunks/16ff9833ae1bb3ae.js +0 -1
  240. package/.next/standalone/packages/ui/.next/static/chunks/2204c0f16b23ec4c.js +0 -3
  241. package/.next/standalone/packages/ui/.next/static/chunks/294dea6dbec43ca6.js +0 -1
  242. package/.next/standalone/packages/ui/.next/static/chunks/7590e65bcaa41e8b.js +0 -1
  243. package/.next/standalone/packages/ui/.next/static/chunks/b6976cf6c48996e5.js +0 -1
  244. package/.next/standalone/packages/ui/.next/static/chunks/b8353eb8c6fb895e.js +0 -1
  245. package/.next/standalone/packages/ui/.next/static/chunks/b845813463167db0.js +0 -5
  246. package/.next/standalone/packages/ui/.next/static/chunks/bd9893e28f8f6a9a.css +0 -1
  247. package/.next/standalone/packages/ui/.next/static/chunks/d784d84d5b880e48.js +0 -1
  248. package/.next/static/chunks/16ff9833ae1bb3ae.js +0 -1
  249. package/.next/static/chunks/2204c0f16b23ec4c.js +0 -3
  250. package/.next/static/chunks/294dea6dbec43ca6.js +0 -1
  251. package/.next/static/chunks/7590e65bcaa41e8b.js +0 -1
  252. package/.next/static/chunks/b6976cf6c48996e5.js +0 -1
  253. package/.next/static/chunks/b8353eb8c6fb895e.js +0 -1
  254. package/.next/static/chunks/b845813463167db0.js +0 -5
  255. package/.next/static/chunks/bd9893e28f8f6a9a.css +0 -1
  256. package/.next/static/chunks/d784d84d5b880e48.js +0 -1
  257. /package/.next/standalone/packages/ui/.next/static/{6nq7WNafPv5Bf_7mWgRQ- → 8PwFWS-09QjFSbDbfp15t}/_buildManifest.js +0 -0
  258. /package/.next/standalone/packages/ui/.next/static/{6nq7WNafPv5Bf_7mWgRQ- → 8PwFWS-09QjFSbDbfp15t}/_clientMiddlewareManifest.json +0 -0
  259. /package/.next/standalone/packages/ui/.next/static/{6nq7WNafPv5Bf_7mWgRQ- → 8PwFWS-09QjFSbDbfp15t}/_ssgManifest.js +0 -0
  260. /package/.next/static/{6nq7WNafPv5Bf_7mWgRQ- → 8PwFWS-09QjFSbDbfp15t}/_buildManifest.js +0 -0
  261. /package/.next/static/{6nq7WNafPv5Bf_7mWgRQ- → 8PwFWS-09QjFSbDbfp15t}/_clientMiddlewareManifest.json +0 -0
  262. /package/.next/static/{6nq7WNafPv5Bf_7mWgRQ- → 8PwFWS-09QjFSbDbfp15t}/_ssgManifest.js +0 -0
@@ -0,0 +1,625 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+ import ReactFlow, {
6
+ Background,
7
+ Controls,
8
+ Edge,
9
+ MiniMap,
10
+ MarkerType,
11
+ Node,
12
+ Position,
13
+ ReactFlowInstance,
14
+ } from 'reactflow';
15
+ import 'reactflow/dist/style.css';
16
+ import { cn } from '@/lib/utils';
17
+ import type { ProjectDependencyGraph } from '@/app/api/dependencies/route';
18
+
19
+ import { nodeTypes } from './spec-node';
20
+ import { SpecSidebar } from './spec-sidebar';
21
+ import { getConnectionDepths, layoutGraph } from './utils';
22
+ import { DEPENDS_ON_COLOR, toneBgColors } from './constants';
23
+ import type { SpecNodeData, GraphTone, FocusedNodeDetails, ConnectionStats } from './types';
24
+
25
+ interface ProjectDependencyGraphClientProps {
26
+ data: ProjectDependencyGraph;
27
+ }
28
+
29
+ export function ProjectDependencyGraphClient({ data }: ProjectDependencyGraphClientProps) {
30
+ const router = useRouter();
31
+ const [instance, setInstance] = React.useState<ReactFlowInstance | null>(null);
32
+ const [showStandalone, setShowStandalone] = React.useState(false);
33
+ const [statusFilter, setStatusFilter] = React.useState<string[]>([]);
34
+ const [focusedNodeId, setFocusedNodeId] = React.useState<string | null>(null);
35
+ const [isCompact, setIsCompact] = React.useState(data.nodes.length > 30);
36
+ const [selectorOpen, setSelectorOpen] = React.useState(false);
37
+ const [selectorQuery, setSelectorQuery] = React.useState('');
38
+ const selectorRef = React.useRef<HTMLDivElement>(null);
39
+
40
+ // Only use dependsOn edges (DAG only - no related edges)
41
+ const dependsOnEdges = React.useMemo(
42
+ () => data.edges.filter((e) => e.type === 'dependsOn'),
43
+ [data.edges]
44
+ );
45
+
46
+ // Get connection depths for focused node (all transitive deps)
47
+ const connectionDepths = React.useMemo(() => {
48
+ if (!focusedNodeId) return null;
49
+ return getConnectionDepths(focusedNodeId, dependsOnEdges, Infinity);
50
+ }, [focusedNodeId, dependsOnEdges]);
51
+
52
+ // Get detailed info for focused node (for sidebar)
53
+ // Edge direction: source depends_on target (A→B means A depends on B)
54
+ // Upstream = specs THIS spec depends on (where focused is source, get target)
55
+ // Downstream = specs that depend on THIS spec (where focused is target, get source)
56
+ const focusedNodeDetails = React.useMemo((): FocusedNodeDetails | null => {
57
+ if (!focusedNodeId) return null;
58
+ const node = data.nodes.find((n) => n.id === focusedNodeId);
59
+ if (!node) return null;
60
+
61
+ const nodeMap = new Map(data.nodes.map((n) => [n.id, n]));
62
+
63
+ // Build directional adjacency maps
64
+ const upstreamMap = new Map<string, Set<string>>();
65
+ const downstreamMap = new Map<string, Set<string>>();
66
+ dependsOnEdges.forEach((e) => {
67
+ if (!upstreamMap.has(e.source)) upstreamMap.set(e.source, new Set());
68
+ upstreamMap.get(e.source)!.add(e.target);
69
+ if (!downstreamMap.has(e.target)) downstreamMap.set(e.target, new Set());
70
+ downstreamMap.get(e.target)!.add(e.source);
71
+ });
72
+
73
+ // BFS to get all upstream specs grouped by depth
74
+ const getTransitiveDeps = (
75
+ startId: string,
76
+ adjacencyMap: Map<string, Set<string>>
77
+ ): { depth: number; specs: typeof data.nodes }[] => {
78
+ const visited = new Set<string>([startId]);
79
+ const result: { depth: number; specs: typeof data.nodes }[] = [];
80
+ let currentLevel = new Set([startId]);
81
+ let depth = 1;
82
+
83
+ while (currentLevel.size > 0) {
84
+ const nextLevel = new Set<string>();
85
+ const specsAtDepth: typeof data.nodes = [];
86
+
87
+ currentLevel.forEach((nodeId) => {
88
+ const neighbors = adjacencyMap.get(nodeId);
89
+ if (neighbors) {
90
+ neighbors.forEach((neighborId) => {
91
+ if (!visited.has(neighborId)) {
92
+ visited.add(neighborId);
93
+ nextLevel.add(neighborId);
94
+ const spec = nodeMap.get(neighborId);
95
+ if (spec) specsAtDepth.push(spec);
96
+ }
97
+ });
98
+ }
99
+ });
100
+
101
+ if (specsAtDepth.length > 0) {
102
+ result.push({ depth, specs: specsAtDepth });
103
+ }
104
+ currentLevel = nextLevel;
105
+ depth++;
106
+ }
107
+
108
+ return result;
109
+ };
110
+
111
+ const upstream = getTransitiveDeps(focusedNodeId, upstreamMap);
112
+ const downstream = getTransitiveDeps(focusedNodeId, downstreamMap);
113
+
114
+ return { node, upstream, downstream };
115
+ }, [focusedNodeId, data.nodes, dependsOnEdges]);
116
+
117
+ // Build the graph
118
+ const graph = React.useMemo(() => {
119
+ // Primary nodes: those matching the status filter
120
+ const primaryNodes = data.nodes.filter(
121
+ (node) => statusFilter.length === 0 || statusFilter.includes(node.status)
122
+ );
123
+ const primaryNodeIds = new Set(primaryNodes.map((n) => n.id));
124
+
125
+ // Find all nodes in the critical path (connected to primary nodes via dependencies)
126
+ // This includes nodes with filtered-out statuses if they're part of dependency chains
127
+ const criticalPathIds = new Set<string>(primaryNodeIds);
128
+
129
+ // BFS to find all connected nodes through dependencies
130
+ const queue = [...primaryNodeIds];
131
+ while (queue.length > 0) {
132
+ const nodeId = queue.shift()!;
133
+ dependsOnEdges.forEach((e) => {
134
+ // Check both directions - upstream and downstream dependencies
135
+ if (e.source === nodeId && !criticalPathIds.has(e.target)) {
136
+ criticalPathIds.add(e.target);
137
+ queue.push(e.target);
138
+ }
139
+ if (e.target === nodeId && !criticalPathIds.has(e.source)) {
140
+ criticalPathIds.add(e.source);
141
+ queue.push(e.source);
142
+ }
143
+ });
144
+ }
145
+
146
+ // Get all nodes in critical path
147
+ const criticalPathNodes = data.nodes.filter((n) => criticalPathIds.has(n.id));
148
+
149
+ // Filter edges to only those between critical path nodes
150
+ const filteredEdges = dependsOnEdges.filter(
151
+ (e) => criticalPathIds.has(e.source) && criticalPathIds.has(e.target)
152
+ );
153
+
154
+ // Filter to nodes with dependencies unless showStandalone
155
+ let visibleNodes = criticalPathNodes;
156
+ if (!showStandalone) {
157
+ const nodesWithDeps = new Set<string>();
158
+ filteredEdges.forEach((e) => {
159
+ nodesWithDeps.add(e.source);
160
+ nodesWithDeps.add(e.target);
161
+ });
162
+ visibleNodes = criticalPathNodes.filter((n) => nodesWithDeps.has(n.id));
163
+ }
164
+
165
+ const visibleNodeIds = new Set(visibleNodes.map((n) => n.id));
166
+
167
+ // Track which nodes are "secondary" (shown due to critical path, not primary filter)
168
+ const secondaryNodeIds = new Set(
169
+ [...visibleNodeIds].filter((id) => !primaryNodeIds.has(id))
170
+ );
171
+
172
+ const nodes: Node<SpecNodeData>[] = visibleNodes.map((node) => {
173
+ const isFocused = focusedNodeId === node.id;
174
+ const isSecondary = secondaryNodeIds.has(node.id);
175
+
176
+ let connectionDepth: number | undefined;
177
+ let isDimmed = false;
178
+
179
+ if (focusedNodeId) {
180
+ connectionDepth = connectionDepths?.get(node.id);
181
+ isDimmed = connectionDepth === undefined;
182
+ }
183
+
184
+ return {
185
+ id: node.id,
186
+ type: 'specNode',
187
+ data: {
188
+ label: node.name,
189
+ shortLabel: node.name.length > 14 ? node.name.slice(0, 12) + '…' : node.name,
190
+ badge: node.status === 'in-progress' ? 'WIP' : node.status.slice(0, 3).toUpperCase(),
191
+ number: node.number,
192
+ tone: node.status as GraphTone,
193
+ href: `/specs/${node.number}`,
194
+ interactive: true,
195
+ isFocused,
196
+ connectionDepth,
197
+ isDimmed,
198
+ isCompact,
199
+ isSecondary,
200
+ },
201
+ position: { x: 0, y: 0 },
202
+ draggable: true,
203
+ selectable: true,
204
+ sourcePosition: Position.Right,
205
+ targetPosition: Position.Left,
206
+ };
207
+ });
208
+
209
+ const edges: Edge[] = filteredEdges
210
+ .filter((edge) => visibleNodeIds.has(edge.source) && visibleNodeIds.has(edge.target))
211
+ .map((edge) => {
212
+ let isHighlighted = true;
213
+ let opacity = 0.7;
214
+
215
+ if (focusedNodeId) {
216
+ const sourceDepth = connectionDepths?.get(edge.source);
217
+ const targetDepth = connectionDepths?.get(edge.target);
218
+ isHighlighted =
219
+ sourceDepth !== undefined &&
220
+ targetDepth !== undefined &&
221
+ (sourceDepth === 0 || targetDepth === 0);
222
+ opacity = isHighlighted
223
+ ? 1
224
+ : sourceDepth !== undefined && targetDepth !== undefined
225
+ ? 0.4
226
+ : 0.1;
227
+ }
228
+
229
+ return {
230
+ id: `${edge.source}-${edge.target}-dependsOn`,
231
+ source: edge.source,
232
+ target: edge.target,
233
+ type: 'smoothstep',
234
+ animated: isHighlighted && focusedNodeId !== null,
235
+ markerEnd: {
236
+ type: MarkerType.ArrowClosed,
237
+ color: DEPENDS_ON_COLOR,
238
+ width: 18,
239
+ height: 18,
240
+ },
241
+ style: {
242
+ stroke: DEPENDS_ON_COLOR,
243
+ strokeWidth: isHighlighted ? 2.5 : 1.5,
244
+ opacity,
245
+ },
246
+ };
247
+ });
248
+
249
+ return layoutGraph(nodes, edges, isCompact, showStandalone);
250
+ }, [
251
+ data,
252
+ dependsOnEdges,
253
+ statusFilter,
254
+ focusedNodeId,
255
+ connectionDepths,
256
+ isCompact,
257
+ showStandalone,
258
+ ]);
259
+
260
+ // Connection stats
261
+ const connectionStats = React.useMemo((): ConnectionStats => {
262
+ const nodesWithDeps = new Set<string>();
263
+ dependsOnEdges.forEach((e) => {
264
+ nodesWithDeps.add(e.source);
265
+ nodesWithDeps.add(e.target);
266
+ });
267
+
268
+ return {
269
+ connected: nodesWithDeps.size,
270
+ standalone: data.nodes.length - nodesWithDeps.size,
271
+ };
272
+ }, [dependsOnEdges, data.nodes.length]);
273
+
274
+ const statusCounts = React.useMemo(() => {
275
+ const counts: Record<string, number> = {};
276
+ data.nodes.forEach((node) => {
277
+ counts[node.status] = (counts[node.status] || 0) + 1;
278
+ });
279
+ return counts;
280
+ }, [data.nodes]);
281
+
282
+ const handleInit = React.useCallback((flowInstance: ReactFlowInstance) => {
283
+ setInstance(flowInstance);
284
+ requestAnimationFrame(() => {
285
+ flowInstance.fitView({ padding: 0.15, duration: 300 });
286
+ });
287
+ }, []);
288
+
289
+ React.useEffect(() => {
290
+ if (!instance) return;
291
+ const timer = setTimeout(() => {
292
+ instance.fitView({ padding: 0.15, duration: 300 });
293
+ }, 50);
294
+ return () => clearTimeout(timer);
295
+ }, [instance, graph.nodes.length, statusFilter, showStandalone]);
296
+
297
+ // Close selector dropdown when clicking outside
298
+ React.useEffect(() => {
299
+ const handleClickOutside = (e: MouseEvent) => {
300
+ if (selectorRef.current && !selectorRef.current.contains(e.target as globalThis.Node)) {
301
+ setSelectorOpen(false);
302
+ }
303
+ };
304
+ document.addEventListener('mousedown', handleClickOutside);
305
+ return () => document.removeEventListener('mousedown', handleClickOutside);
306
+ }, []);
307
+
308
+ // Filter specs for selector dropdown
309
+ const filteredSpecs = React.useMemo(() => {
310
+ if (!selectorQuery.trim()) return data.nodes.slice(0, 15);
311
+ const q = selectorQuery.toLowerCase();
312
+ return data.nodes
313
+ .filter(
314
+ (n) =>
315
+ n.name.toLowerCase().includes(q) ||
316
+ n.number.toString().includes(q) ||
317
+ n.tags.some((t) => t.toLowerCase().includes(q))
318
+ )
319
+ .slice(0, 15);
320
+ }, [data.nodes, selectorQuery]);
321
+
322
+ // Get focused spec name for display
323
+ const focusedSpec = React.useMemo(
324
+ () => (focusedNodeId ? data.nodes.find((n) => n.id === focusedNodeId) : null),
325
+ [focusedNodeId, data.nodes]
326
+ );
327
+
328
+ const handleNodeClick = React.useCallback(
329
+ (event: React.MouseEvent, node: Node<SpecNodeData>) => {
330
+ if (!node?.data) return;
331
+ if (event.detail === 2 && node.data.href) {
332
+ router.push(node.data.href);
333
+ return;
334
+ }
335
+ setFocusedNodeId((prev) => (prev === node.id ? null : node.id));
336
+ },
337
+ [router]
338
+ );
339
+
340
+ const handlePaneClick = React.useCallback(() => {
341
+ setFocusedNodeId(null);
342
+ }, []);
343
+
344
+ const toggleStatus = (status: string) => {
345
+ setStatusFilter((prev) =>
346
+ prev.includes(status) ? prev.filter((s) => s !== status) : [...prev, status]
347
+ );
348
+ setFocusedNodeId(null);
349
+ };
350
+
351
+ const clearFilters = () => {
352
+ setStatusFilter([]);
353
+ setFocusedNodeId(null);
354
+ setSelectorQuery('');
355
+ };
356
+
357
+ const handleSelectSpec = (specId: string) => {
358
+ setFocusedNodeId(specId);
359
+ setSelectorOpen(false);
360
+ setSelectorQuery('');
361
+ // Center on the selected node
362
+ if (instance) {
363
+ const node = graph.nodes.find((n) => n.id === specId);
364
+ if (node) {
365
+ instance.setCenter(node.position.x + 80, node.position.y + 30, {
366
+ duration: 400,
367
+ zoom: 1,
368
+ });
369
+ }
370
+ }
371
+ };
372
+
373
+ const hasFilters = statusFilter.length > 0 || focusedNodeId;
374
+
375
+ return (
376
+ <div className="flex h-full flex-col gap-2">
377
+ {/* Header */}
378
+ <div className="flex flex-wrap items-center justify-between gap-2">
379
+ <div className="flex items-center gap-4">
380
+ <div>
381
+ <p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
382
+ Dependency Graph
383
+ </p>
384
+ <p className="text-sm text-foreground">
385
+ {connectionStats.connected > 0 ? (
386
+ <>
387
+ <span className="text-emerald-400">{connectionStats.connected} specs with dependencies</span>
388
+ {connectionStats.standalone > 0 && (
389
+ <>
390
+ {' • '}
391
+ <span className="text-muted-foreground">{connectionStats.standalone} standalone</span>
392
+ </>
393
+ )}
394
+ </>
395
+ ) : (
396
+ <span className="text-muted-foreground">No dependencies defined</span>
397
+ )}
398
+ </p>
399
+ </div>
400
+ </div>
401
+
402
+ {/* Spec Selector */}
403
+ <div className="relative" ref={selectorRef}>
404
+ <button
405
+ onClick={() => setSelectorOpen(!selectorOpen)}
406
+ className={cn(
407
+ 'h-7 w-52 rounded-md border bg-background px-2.5 text-xs text-left flex items-center gap-2 transition-colors',
408
+ focusedNodeId
409
+ ? 'border-primary/60 bg-primary/10'
410
+ : 'border-border hover:border-primary/40'
411
+ )}
412
+ >
413
+ {focusedSpec ? (
414
+ <>
415
+ <span className="text-muted-foreground">#{focusedSpec.number.toString().padStart(3, '0')}</span>
416
+ <span className="truncate flex-1 text-foreground">{focusedSpec.name}</span>
417
+ </>
418
+ ) : (
419
+ <span className="text-muted-foreground">Select spec to highlight...</span>
420
+ )}
421
+ <svg className="w-3 h-3 text-muted-foreground shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
422
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
423
+ </svg>
424
+ </button>
425
+
426
+ {selectorOpen && (
427
+ <div className="absolute right-0 top-8 z-50 w-64 rounded-md border border-border bg-background shadow-lg overflow-hidden">
428
+ <div className="p-2 border-b border-border">
429
+ <input
430
+ type="text"
431
+ placeholder="Type to filter..."
432
+ value={selectorQuery}
433
+ onChange={(e) => setSelectorQuery(e.target.value)}
434
+ className="w-full h-7 rounded border border-border bg-muted/30 px-2 text-xs placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary"
435
+ autoFocus
436
+ />
437
+ </div>
438
+ <div className="max-h-64 overflow-auto">
439
+ {focusedNodeId && (
440
+ <button
441
+ onClick={() => {
442
+ setFocusedNodeId(null);
443
+ setSelectorOpen(false);
444
+ setSelectorQuery('');
445
+ }}
446
+ className="w-full px-3 py-2 text-xs text-left hover:bg-muted/50 border-b border-border text-muted-foreground flex items-center gap-2"
447
+ >
448
+ <span className="text-red-400">×</span> Clear selection
449
+ </button>
450
+ )}
451
+ {filteredSpecs.length > 0 ? (
452
+ filteredSpecs.map((spec) => (
453
+ <button
454
+ key={spec.id}
455
+ onClick={() => handleSelectSpec(spec.id)}
456
+ className={cn(
457
+ 'w-full px-3 py-2 text-xs text-left hover:bg-muted/50 flex items-center gap-2',
458
+ focusedNodeId === spec.id && 'bg-primary/20'
459
+ )}
460
+ >
461
+ <span className="text-muted-foreground font-mono">#{spec.number.toString().padStart(3, '0')}</span>
462
+ <span className="truncate flex-1">{spec.name}</span>
463
+ <span
464
+ className={cn(
465
+ 'text-[9px] px-1 py-0.5 rounded uppercase font-medium',
466
+ spec.status === 'planned' && 'bg-blue-500/20 text-blue-400',
467
+ spec.status === 'in-progress' && 'bg-orange-500/20 text-orange-400',
468
+ spec.status === 'complete' && 'bg-green-500/20 text-green-400',
469
+ spec.status === 'archived' && 'bg-gray-500/20 text-gray-400'
470
+ )}
471
+ >
472
+ {spec.status === 'in-progress' ? 'WIP' : spec.status.slice(0, 3)}
473
+ </span>
474
+ </button>
475
+ ))
476
+ ) : (
477
+ <div className="px-3 py-4 text-xs text-muted-foreground text-center">
478
+ No specs found
479
+ </div>
480
+ )}
481
+ </div>
482
+ </div>
483
+ )}
484
+ </div>
485
+ </div>
486
+
487
+ {/* Filters */}
488
+ <div className="flex flex-wrap items-center gap-1.5 text-xs">
489
+ {(['planned', 'in-progress', 'complete', 'archived'] as const).map((status) => {
490
+ const isActive = statusFilter.length === 0 || statusFilter.includes(status);
491
+ return (
492
+ <button
493
+ key={status}
494
+ onClick={() => toggleStatus(status)}
495
+ className={cn(
496
+ 'rounded border px-2 py-1 font-medium transition-colors',
497
+ isActive && status === 'planned' && 'border-blue-500/60 bg-blue-500/20 text-blue-300',
498
+ isActive && status === 'in-progress' && 'border-orange-500/60 bg-orange-500/20 text-orange-300',
499
+ isActive && status === 'complete' && 'border-green-500/60 bg-green-500/20 text-green-300',
500
+ isActive && status === 'archived' && 'border-gray-500/60 bg-gray-500/20 text-gray-300',
501
+ !isActive && 'border-border bg-background text-muted-foreground/40'
502
+ )}
503
+ >
504
+ {status === 'in-progress' ? 'WIP' : status.charAt(0).toUpperCase() + status.slice(1)}
505
+ <span className="ml-1 opacity-60">({statusCounts[status] || 0})</span>
506
+ </button>
507
+ );
508
+ })}
509
+
510
+ <span className="h-3 w-px bg-border" />
511
+
512
+ <button
513
+ onClick={() => setShowStandalone(!showStandalone)}
514
+ className={cn(
515
+ 'rounded border px-2 py-1 font-medium transition-colors',
516
+ showStandalone
517
+ ? 'border-violet-500/60 bg-violet-500/20 text-violet-300'
518
+ : 'border-border bg-background hover:bg-accent text-muted-foreground'
519
+ )}
520
+ >
521
+ Show Standalone
522
+ <span className="ml-1 opacity-60">({connectionStats.standalone})</span>
523
+ </button>
524
+
525
+ <span className="h-3 w-px bg-border" />
526
+
527
+ <button
528
+ onClick={() => setIsCompact(!isCompact)}
529
+ className={cn(
530
+ 'rounded border px-2 py-1 font-medium transition-colors',
531
+ isCompact
532
+ ? 'border-primary/60 bg-primary/20 text-primary'
533
+ : 'border-border bg-background hover:bg-accent text-muted-foreground'
534
+ )}
535
+ >
536
+ Compact
537
+ </button>
538
+
539
+ {hasFilters && (
540
+ <>
541
+ <span className="h-3 w-px bg-border" />
542
+ <button
543
+ onClick={clearFilters}
544
+ className="rounded border border-red-500/40 bg-red-500/10 px-2 py-1 font-medium text-red-400 hover:bg-red-500/20"
545
+ >
546
+ Clear
547
+ </button>
548
+ </>
549
+ )}
550
+ </div>
551
+
552
+ {/* Main content */}
553
+ <div className="flex flex-1 gap-3 min-h-0">
554
+ {/* Graph */}
555
+ <div className="flex-1 overflow-hidden rounded-lg border border-border bg-[#080c14]">
556
+ {graph.nodes.length > 0 ? (
557
+ <ReactFlow
558
+ nodes={graph.nodes}
559
+ edges={graph.edges}
560
+ nodeTypes={nodeTypes}
561
+ onInit={handleInit}
562
+ className="h-full w-full"
563
+ fitView
564
+ proOptions={{ hideAttribution: true }}
565
+ nodesDraggable
566
+ nodesConnectable={false}
567
+ elementsSelectable
568
+ panOnScroll
569
+ panOnDrag
570
+ zoomOnScroll
571
+ zoomOnPinch
572
+ minZoom={0.05}
573
+ maxZoom={2}
574
+ onNodeClick={handleNodeClick}
575
+ onPaneClick={handlePaneClick}
576
+ >
577
+ <Background gap={20} size={1} color="rgba(100, 116, 139, 0.06)" />
578
+ <Controls showInteractive={false} className="!bg-background/90 !border-border !rounded-md" />
579
+ <MiniMap
580
+ nodeColor={(node) => {
581
+ const d = node.data as SpecNodeData;
582
+ return toneBgColors[d.tone] || '#1f2937';
583
+ }}
584
+ maskColor="rgba(0, 0, 0, 0.85)"
585
+ className="!bg-background/95 !border-border !rounded-md"
586
+ style={{ width: 120, height: 80 }}
587
+ pannable
588
+ zoomable
589
+ />
590
+ </ReactFlow>
591
+ ) : (
592
+ <div className="flex h-full items-center justify-center text-muted-foreground">
593
+ <div className="text-center">
594
+ <p className="text-sm font-medium">No dependencies to display</p>
595
+ <p className="text-xs mt-1">
596
+ {showStandalone
597
+ ? 'No specs match the current filters'
598
+ : 'Enable "Show Standalone" to see specs without dependencies'}
599
+ </p>
600
+ </div>
601
+ </div>
602
+ )}
603
+ </div>
604
+
605
+ {/* Right Sidebar */}
606
+ <SpecSidebar
607
+ focusedDetails={focusedNodeDetails}
608
+ onSelectSpec={setFocusedNodeId}
609
+ onOpenSpec={(num) => router.push(`/specs/${num}`)}
610
+ />
611
+ </div>
612
+
613
+ {/* Legend */}
614
+ <div className="flex flex-wrap items-center gap-4 text-[10px] text-muted-foreground">
615
+ <span className="inline-flex items-center gap-1.5">
616
+ <span className="inline-block h-0.5 w-6 bg-amber-400 rounded" />
617
+ Depends On (arrow shows direction)
618
+ </span>
619
+ <span className="text-muted-foreground/50 ml-auto">
620
+ Click: select • Double-click: open • Drag to rearrange
621
+ </span>
622
+ </div>
623
+ </div>
624
+ );
625
+ }
@@ -0,0 +1,19 @@
1
+ // Re-export main component
2
+ export { ProjectDependencyGraphClient } from './dependencies-client';
3
+
4
+ // Re-export types for use elsewhere
5
+ export type { SpecNodeData, GraphTone, FocusedNodeDetails, ConnectionStats, SpecNode } from './types';
6
+
7
+ // Re-export utilities for potential reuse
8
+ export { getConnectionDepths, layoutGraph } from './utils';
9
+
10
+ // Re-export constants
11
+ export {
12
+ NODE_WIDTH,
13
+ NODE_HEIGHT,
14
+ COMPACT_NODE_WIDTH,
15
+ COMPACT_NODE_HEIGHT,
16
+ DEPENDS_ON_COLOR,
17
+ toneClasses,
18
+ toneBgColors,
19
+ } from './constants';
@@ -0,0 +1,38 @@
1
+ import { ProjectDependencyGraphClient } from './dependencies-client';
2
+ import type { ProjectDependencyGraph } from '@/app/api/dependencies/route';
3
+
4
+ async function getDependencyGraph(): Promise<ProjectDependencyGraph> {
5
+ const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3000';
6
+ const res = await fetch(`${baseUrl}/api/dependencies`, {
7
+ cache: 'no-store',
8
+ });
9
+
10
+ if (!res.ok) {
11
+ throw new Error('Failed to fetch dependency graph');
12
+ }
13
+
14
+ return res.json();
15
+ }
16
+
17
+ export default async function DependenciesPage() {
18
+ const data = await getDependencyGraph();
19
+
20
+ if (data.nodes.length === 0) {
21
+ return (
22
+ <div className="container mx-auto p-6">
23
+ <div className="rounded-lg border border-border bg-muted/30 p-8 text-center">
24
+ <h2 className="text-xl font-semibold mb-2">No Specs Found</h2>
25
+ <p className="text-muted-foreground">
26
+ There are no specifications with dependencies to visualize yet.
27
+ </p>
28
+ </div>
29
+ </div>
30
+ );
31
+ }
32
+
33
+ return (
34
+ <div className="container mx-auto p-6 h-[calc(100vh-7rem)]">
35
+ <ProjectDependencyGraphClient data={data} />
36
+ </div>
37
+ );
38
+ }