@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.
- package/.next/standalone/packages/ui/.next/BUILD_ID +1 -1
- package/.next/standalone/packages/ui/.next/app-path-routes-manifest.json +4 -0
- package/.next/standalone/packages/ui/.next/build-manifest.json +4 -4
- package/.next/standalone/packages/ui/.next/prerender-manifest.json +3 -3
- package/.next/standalone/packages/ui/.next/routes-manifest.json +24 -0
- package/.next/standalone/packages/ui/.next/server/app/_global-error/page/build-manifest.json +2 -2
- package/.next/standalone/packages/ui/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/packages/ui/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_not-found/page/build-manifest.json +2 -2
- package/.next/standalone/packages/ui/.next/server/app/_not-found/page.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/_not-found.html +2 -2
- package/.next/standalone/packages/ui/.next/server/app/_not-found.rsc +19 -19
- package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_full.segment.rsc +19 -19
- package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
- package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +3 -3
- package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_tree.segment.rsc +6 -6
- package/.next/standalone/packages/ui/.next/server/app/api/context/route/app-paths-manifest.json +3 -0
- package/.next/standalone/packages/ui/.next/server/app/api/context/route/build-manifest.json +11 -0
- package/.next/standalone/packages/ui/.next/server/app/api/context/route/server-reference-manifest.json +4 -0
- package/.next/standalone/packages/ui/.next/server/app/api/context/route.js +8 -0
- package/.next/standalone/packages/ui/.next/server/app/api/context/route.js.map +5 -0
- package/.next/standalone/packages/ui/.next/server/app/api/context/route.js.nft.json +1 -0
- package/.next/standalone/packages/ui/.next/server/app/api/context/route_client-reference-manifest.js +2 -0
- package/.next/standalone/packages/ui/.next/server/app/api/dependencies/route/app-paths-manifest.json +3 -0
- package/.next/standalone/packages/ui/.next/server/app/api/dependencies/route/build-manifest.json +11 -0
- package/.next/standalone/packages/ui/.next/server/app/api/dependencies/route/server-reference-manifest.json +4 -0
- package/.next/standalone/packages/ui/.next/server/app/api/dependencies/route.js +8 -0
- package/.next/standalone/packages/ui/.next/server/app/api/dependencies/route.js.map +5 -0
- package/.next/standalone/packages/ui/.next/server/app/api/dependencies/route.js.nft.json +1 -0
- package/.next/standalone/packages/ui/.next/server/app/api/dependencies/route_client-reference-manifest.js +2 -0
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/[id]/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/discover/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/list-directory/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/local-projects/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/specs/[spec]/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/specs/[spec]/status/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/specs/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/stats/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/revalidate/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/dependency-graph/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/status/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/subspecs/[file]/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/api/stats/route.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/context/page/app-paths-manifest.json +3 -0
- package/.next/standalone/packages/ui/.next/server/app/context/page/build-manifest.json +18 -0
- package/.next/standalone/packages/ui/.next/server/app/context/page/next-font-manifest.json +6 -0
- package/.next/standalone/packages/ui/.next/server/app/context/page/react-loadable-manifest.json +1 -0
- package/.next/standalone/packages/ui/.next/server/app/context/page/server-reference-manifest.json +4 -0
- package/.next/standalone/packages/ui/.next/server/app/context/page.js +19 -0
- package/.next/standalone/packages/ui/.next/server/app/context/page.js.map +5 -0
- package/.next/standalone/packages/ui/.next/server/app/context/page.js.nft.json +1 -0
- package/.next/standalone/packages/ui/.next/server/app/context/page_client-reference-manifest.js +2 -0
- package/.next/standalone/packages/ui/.next/server/app/dependencies/page/app-paths-manifest.json +3 -0
- package/.next/standalone/packages/ui/.next/server/app/dependencies/page/build-manifest.json +18 -0
- package/.next/standalone/packages/ui/.next/server/app/dependencies/page/next-font-manifest.json +6 -0
- package/.next/standalone/packages/ui/.next/server/app/dependencies/page/react-loadable-manifest.json +1 -0
- package/.next/standalone/packages/ui/.next/server/app/dependencies/page/server-reference-manifest.json +4 -0
- package/.next/standalone/packages/ui/.next/server/app/dependencies/page.js +19 -0
- package/.next/standalone/packages/ui/.next/server/app/dependencies/page.js.map +5 -0
- package/.next/standalone/packages/ui/.next/server/app/dependencies/page.js.nft.json +1 -0
- package/.next/standalone/packages/ui/.next/server/app/dependencies/page_client-reference-manifest.js +2 -0
- package/.next/standalone/packages/ui/.next/server/app/page/build-manifest.json +2 -2
- package/.next/standalone/packages/ui/.next/server/app/page.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/page/build-manifest.json +2 -2
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/page.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/[specId]/page/build-manifest.json +2 -2
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/[specId]/page.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/[specId]/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/[specId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/page/build-manifest.json +2 -2
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/page.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/page/build-manifest.json +2 -2
- package/.next/standalone/packages/ui/.next/server/app/projects/page.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/projects.html +2 -2
- package/.next/standalone/packages/ui/.next/server/app/projects.rsc +24 -23
- package/.next/standalone/packages/ui/.next/server/app/projects.segments/_full.segment.rsc +24 -23
- package/.next/standalone/packages/ui/.next/server/app/projects.segments/_index.segment.rsc +9 -9
- package/.next/standalone/packages/ui/.next/server/app/projects.segments/_tree.segment.rsc +3 -3
- package/.next/standalone/packages/ui/.next/server/app/projects.segments/projects/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/packages/ui/.next/server/app/projects.segments/projects.segment.rsc +1 -1
- package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page/build-manifest.json +2 -2
- package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/specs/page/build-manifest.json +2 -2
- package/.next/standalone/packages/ui/.next/server/app/specs/page.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/specs/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/specs/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/stats/page/build-manifest.json +2 -2
- package/.next/standalone/packages/ui/.next/server/app/stats/page.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app/stats/page.js.nft.json +1 -1
- package/.next/standalone/packages/ui/.next/server/app/stats/page_client-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/app-paths-manifest.json +4 -0
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__3559376c._.js +2 -2
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__65667b70._.js +1 -1
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__803d07f0._.js +1 -1
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__84cdc14a._.js +1 -1
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__8a9ab1a3._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__b0969111._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__bdc3963a._.js +1 -1
- package/.next/standalone/packages/ui/.next/server/chunks/[root-of-the-server]__f5c6d6b8._.js +1 -1
- package/.next/standalone/packages/ui/.next/server/chunks/packages_ui__next-internal_server_app_api_context_route_actions_dead6daa.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/packages_ui__next-internal_server_app_api_dependencies_route_actions_cf6b14c3.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__12b4eb41._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__3c77d95b._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__44b603f9._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__69a0d63a._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/{[root-of-the-server]__daee3355._.js → [root-of-the-server]__8608a6fa._.js} +2 -2
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/{[root-of-the-server]__fd80e4dd._.js → [root-of-the-server]__a965a67b._.js} +2 -2
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__daedd80e._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__dc176c61._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/{[root-of-the-server]__5382b397._.js → [root-of-the-server]__ead1539c._.js} +2 -2
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_000dd317._.js +1 -1
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_03aa8d19._.js +7 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_28fe1532._.js +5 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_8cec504f._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_959ad3d8._.js +4 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/{_22274047._.js → _adb9d7cb._.js} +2 -2
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_da18b655._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_ea899b87._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_f38e75b7._.js +4 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_fe120f8a._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/node_modules__pnpm_015f83ca._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/node_modules__pnpm_1120b57c._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/node_modules__pnpm_151891de._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/node_modules__pnpm_1c916fe3._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/node_modules__pnpm_80605a06._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/node_modules__pnpm_bb80de48._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/node_modules__pnpm_e8075f9b._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/node_modules__pnpm_f919ef4a._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui__next-internal_server_app_context_page_actions_1a062d48.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui__next-internal_server_app_dependencies_page_actions_57387d47.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_app_context_context-client_tsx_4ba99a62._.js +12 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_app_dependencies_dependencies-client_tsx_0e82443a._.js +4 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_app_specs_specs-client_tsx_0bb8f8f8._.js +1 -1
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_components_spec-detail-wrapper_tsx_fd35401c._.js +3 -0
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_components_specs-nav-sidebar_tsx_8237ed13._.js +1 -1
- package/.next/standalone/packages/ui/.next/server/middleware-build-manifest.js +2 -2
- package/.next/standalone/packages/ui/.next/server/pages/404.html +2 -2
- package/.next/standalone/packages/ui/.next/server/pages/500.html +2 -2
- package/.next/standalone/packages/ui/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/packages/ui/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/packages/ui/.next/static/chunks/094cf9f4e3553261.js +1 -0
- package/.next/standalone/packages/ui/.next/static/chunks/1c015eb9eaaf9f9c.js +1 -0
- package/.next/standalone/packages/ui/.next/static/chunks/1d36660b2877d213.js +3 -0
- package/.next/standalone/packages/ui/.next/static/chunks/46275d9d67603bf5.js +1 -0
- package/.next/standalone/packages/ui/.next/static/chunks/4d29ca0fa6843070.js +2 -0
- package/.next/standalone/packages/ui/.next/static/chunks/59854b15bf046467.js +1 -0
- package/.next/standalone/packages/ui/.next/static/chunks/{cca4441cde342ae3.js → 5fd8101e32b076b1.js} +1 -1
- package/.next/standalone/packages/ui/.next/static/chunks/6d938a49daa10208.js +2 -0
- package/.next/standalone/packages/ui/.next/static/chunks/979625373d5474b6.js +1 -0
- package/.next/standalone/packages/ui/.next/static/chunks/a33f10af6abef4df.js +10 -0
- package/.next/{static/chunks/794f3931f1ca12d2.js → standalone/packages/ui/.next/static/chunks/a3d7e1be47de010b.js} +1 -1
- package/.next/standalone/packages/ui/.next/static/chunks/a5e25b9fa6b88eee.js +1 -0
- package/.next/standalone/packages/ui/.next/static/chunks/b53250480fde6816.js +1 -0
- package/.next/standalone/packages/ui/.next/static/chunks/b7f19087afe1d2c9.css +1 -0
- package/.next/standalone/packages/ui/.next/static/chunks/c658e22a605e0ed1.js +1 -0
- package/.next/standalone/packages/ui/.next/static/chunks/c92660d8d0c4763d.js +1 -0
- package/.next/standalone/packages/ui/.next/static/chunks/ddd87cd0d26bc2f5.js +1 -0
- package/.next/standalone/packages/ui/.next/static/chunks/eeec245955b3b600.js +5 -0
- package/.next/standalone/packages/ui/.next/static/chunks/ff11efb770d5a0bc.js +1 -0
- package/.next/{static/chunks/turbopack-5fa55215af0efb15.js → standalone/packages/ui/.next/static/chunks/turbopack-261c5dcdd873f310.js} +1 -1
- package/.next/standalone/packages/ui/package.json +5 -1
- package/.next/standalone/packages/ui/src/app/api/context/route.ts +22 -0
- package/.next/standalone/packages/ui/src/app/api/dependencies/route.ts +81 -0
- package/.next/standalone/packages/ui/src/app/context/context-client.tsx +393 -0
- package/.next/standalone/packages/ui/src/app/context/page.tsx +17 -0
- package/.next/standalone/packages/ui/src/app/dependencies/constants.ts +24 -0
- package/.next/standalone/packages/ui/src/app/dependencies/dependencies-client.tsx +625 -0
- package/.next/standalone/packages/ui/src/app/dependencies/index.ts +19 -0
- package/.next/standalone/packages/ui/src/app/dependencies/page.tsx +38 -0
- package/.next/standalone/packages/ui/src/app/dependencies/spec-node.tsx +80 -0
- package/.next/standalone/packages/ui/src/app/dependencies/spec-sidebar.tsx +198 -0
- package/.next/standalone/packages/ui/src/app/dependencies/types.ts +37 -0
- package/.next/standalone/packages/ui/src/app/dependencies/utils.ts +194 -0
- package/.next/standalone/packages/ui/src/app/globals.css +16 -16
- package/.next/standalone/packages/ui/src/app/layout.tsx +4 -7
- package/.next/standalone/packages/ui/src/components/context-file-detail.tsx +308 -0
- package/.next/standalone/packages/ui/src/components/context-file-viewer.tsx +385 -0
- package/.next/standalone/packages/ui/src/components/main-sidebar.tsx +19 -1
- package/.next/standalone/packages/ui/src/components/spec-detail-client.tsx +181 -134
- package/.next/standalone/packages/ui/src/components/spec-detail-wrapper.tsx +20 -0
- package/.next/standalone/packages/ui/src/components/specs-nav-sidebar.tsx +3 -2
- package/.next/standalone/packages/ui/src/components/ui/accordion.tsx +58 -0
- package/.next/standalone/packages/ui/src/lib/db/service-queries.ts +172 -3
- package/.next/standalone/packages/ui/src/lib/specs/types.ts +44 -0
- package/.next/standalone/packages/ui/tsconfig.tsbuildinfo +1 -1
- package/.next/static/chunks/094cf9f4e3553261.js +1 -0
- package/.next/static/chunks/1c015eb9eaaf9f9c.js +1 -0
- package/.next/static/chunks/1d36660b2877d213.js +3 -0
- package/.next/static/chunks/46275d9d67603bf5.js +1 -0
- package/.next/static/chunks/4d29ca0fa6843070.js +2 -0
- package/.next/static/chunks/59854b15bf046467.js +1 -0
- package/.next/static/chunks/{cca4441cde342ae3.js → 5fd8101e32b076b1.js} +1 -1
- package/.next/static/chunks/6d938a49daa10208.js +2 -0
- package/.next/static/chunks/979625373d5474b6.js +1 -0
- package/.next/static/chunks/a33f10af6abef4df.js +10 -0
- package/.next/{standalone/packages/ui/.next/static/chunks/794f3931f1ca12d2.js → static/chunks/a3d7e1be47de010b.js} +1 -1
- package/.next/static/chunks/a5e25b9fa6b88eee.js +1 -0
- package/.next/static/chunks/b53250480fde6816.js +1 -0
- package/.next/static/chunks/b7f19087afe1d2c9.css +1 -0
- package/.next/static/chunks/c658e22a605e0ed1.js +1 -0
- package/.next/static/chunks/c92660d8d0c4763d.js +1 -0
- package/.next/static/chunks/ddd87cd0d26bc2f5.js +1 -0
- package/.next/static/chunks/eeec245955b3b600.js +5 -0
- package/.next/static/chunks/ff11efb770d5a0bc.js +1 -0
- package/.next/{standalone/packages/ui/.next/static/chunks/turbopack-5fa55215af0efb15.js → static/chunks/turbopack-261c5dcdd873f310.js} +1 -1
- package/package.json +6 -2
- package/.next/standalone/node_modules/.pnpm/source-map@0.8.0-beta.0/node_modules/source-map/package.json +0 -95
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__1d0c2012._.js +0 -3
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__73f60f12._.js +0 -7
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__a7ae8552._.js +0 -7
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_0f9ffe32._.js +0 -3
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_14118969._.js +0 -3
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_4129cc0f._.js +0 -3
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_497c8b73._.js +0 -3
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_ac867463._.js +0 -3
- package/.next/standalone/packages/ui/.next/server/chunks/ssr/_c2f54661._.js +0 -5
- package/.next/standalone/packages/ui/.next/static/chunks/16ff9833ae1bb3ae.js +0 -1
- package/.next/standalone/packages/ui/.next/static/chunks/2204c0f16b23ec4c.js +0 -3
- package/.next/standalone/packages/ui/.next/static/chunks/294dea6dbec43ca6.js +0 -1
- package/.next/standalone/packages/ui/.next/static/chunks/7590e65bcaa41e8b.js +0 -1
- package/.next/standalone/packages/ui/.next/static/chunks/b6976cf6c48996e5.js +0 -1
- package/.next/standalone/packages/ui/.next/static/chunks/b8353eb8c6fb895e.js +0 -1
- package/.next/standalone/packages/ui/.next/static/chunks/b845813463167db0.js +0 -5
- package/.next/standalone/packages/ui/.next/static/chunks/bd9893e28f8f6a9a.css +0 -1
- package/.next/standalone/packages/ui/.next/static/chunks/d784d84d5b880e48.js +0 -1
- package/.next/static/chunks/16ff9833ae1bb3ae.js +0 -1
- package/.next/static/chunks/2204c0f16b23ec4c.js +0 -3
- package/.next/static/chunks/294dea6dbec43ca6.js +0 -1
- package/.next/static/chunks/7590e65bcaa41e8b.js +0 -1
- package/.next/static/chunks/b6976cf6c48996e5.js +0 -1
- package/.next/static/chunks/b8353eb8c6fb895e.js +0 -1
- package/.next/static/chunks/b845813463167db0.js +0 -5
- package/.next/static/chunks/bd9893e28f8f6a9a.css +0 -1
- package/.next/static/chunks/d784d84d5b880e48.js +0 -1
- /package/.next/standalone/packages/ui/.next/static/{6nq7WNafPv5Bf_7mWgRQ- → 8PwFWS-09QjFSbDbfp15t}/_buildManifest.js +0 -0
- /package/.next/standalone/packages/ui/.next/static/{6nq7WNafPv5Bf_7mWgRQ- → 8PwFWS-09QjFSbDbfp15t}/_clientMiddlewareManifest.json +0 -0
- /package/.next/standalone/packages/ui/.next/static/{6nq7WNafPv5Bf_7mWgRQ- → 8PwFWS-09QjFSbDbfp15t}/_ssgManifest.js +0 -0
- /package/.next/static/{6nq7WNafPv5Bf_7mWgRQ- → 8PwFWS-09QjFSbDbfp15t}/_buildManifest.js +0 -0
- /package/.next/static/{6nq7WNafPv5Bf_7mWgRQ- → 8PwFWS-09QjFSbDbfp15t}/_clientMiddlewareManifest.json +0 -0
- /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
|
+
}
|