@shepai/cli 1.72.9-pr218.000ada0 → 1.72.9-pr220.e1bfec0
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/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.d.ts +20 -1
- package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.d.ts.map +1 -1
- package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.js +68 -41
- package/dist/packages/core/src/application/use-cases/features/create/types.d.ts +10 -0
- package/dist/packages/core/src/application/use-cases/features/create/types.d.ts.map +1 -1
- package/dist/src/presentation/web/app/actions/create-feature.d.ts.map +1 -1
- package/dist/src/presentation/web/app/actions/create-feature.js +20 -2
- package/dist/src/presentation/web/app/actions/get-feature-metadata.d.ts +10 -0
- package/dist/src/presentation/web/app/actions/get-feature-metadata.d.ts.map +1 -0
- package/dist/src/presentation/web/app/actions/get-feature-metadata.js +14 -0
- package/dist/src/presentation/web/app/api/agent-events/route.d.ts.map +1 -1
- package/dist/src/presentation/web/app/api/agent-events/route.js +15 -0
- package/dist/src/presentation/web/components/common/add-repository-button/add-repository-button.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/add-repository-button/index.d.ts +2 -0
- package/dist/src/presentation/web/components/common/add-repository-button/index.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/index.js +0 -1
- package/dist/src/presentation/web/components/common/add-repository-button/pick-folder.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/control-center-drawer/create-drawer-client.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/control-center-drawer/create-drawer-client.js +13 -16
- package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.js +35 -16
- package/dist/src/presentation/web/components/common/index.d.ts +1 -1
- package/dist/src/presentation/web/components/common/index.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/index.js +1 -1
- package/dist/src/presentation/web/components/features/control-center/control-center-empty-state.js +1 -1
- package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +23 -4
- package/dist/src/presentation/web/components/features/control-center/control-center.stories.d.ts +0 -11
- package/dist/src/presentation/web/components/features/control-center/control-center.stories.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/control-center.stories.js +2 -37
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts +8 -0
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +152 -350
- package/dist/src/presentation/web/components/features/features-canvas/features-canvas.d.ts +2 -10
- package/dist/src/presentation/web/components/features/features-canvas/features-canvas.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/features-canvas/features-canvas.js +10 -42
- package/dist/src/presentation/web/components/features/features-canvas/features-canvas.stories.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/features-canvas/features-canvas.stories.js +0 -46
- package/dist/src/presentation/web/components/layouts/app-shell/app-shell.js +1 -1
- package/dist/src/presentation/web/hooks/use-graph-state.d.ts +41 -0
- package/dist/src/presentation/web/hooks/use-graph-state.d.ts.map +1 -0
- package/dist/src/presentation/web/hooks/use-graph-state.js +230 -0
- package/dist/src/presentation/web/lib/derive-graph.d.ts +52 -0
- package/dist/src/presentation/web/lib/derive-graph.d.ts.map +1 -0
- package/dist/src/presentation/web/lib/derive-graph.js +126 -0
- package/dist/src/presentation/web/lib/layout-with-dagre.d.ts.map +1 -1
- package/dist/src/presentation/web/lib/layout-with-dagre.js +0 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/cache/.previewinfo +1 -1
- package/web/.next/cache/.rscinfo +1 -1
- package/web/.next/cache/.tsbuildinfo +1 -1
- package/web/.next/cache/config.json +3 -3
- package/web/.next/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +34 -34
- package/web/.next/server/app/(dashboard)/@drawer/create/page.js +3 -3
- package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +40 -40
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js +3 -3
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +30 -30
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js +3 -3
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +34 -34
- package/web/.next/server/app/(dashboard)/create/page.js +3 -3
- package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +40 -40
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js +3 -3
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +30 -30
- package/web/.next/server/app/(dashboard)/page.js +3 -3
- package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +30 -30
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js +3 -3
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
- package/web/.next/server/app/skills/page.js.nft.json +1 -1
- package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
- package/web/.next/server/app/tools/page.js.nft.json +1 -1
- package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
- package/web/.next/server/app/version/page.js.nft.json +1 -1
- package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +2 -2
- package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js.map +1 -1
- package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
- package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js +3 -0
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js.map +1 -0
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js +3 -0
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js.map +1 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{[root-of-the-server]__cbae9863._.js → [root-of-the-server]__6258349c._.js} +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__6258349c._.js.map +1 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js.map +1 -1
- package/web/.next/{standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__e5e09f72._.js → server/chunks/ssr/[root-of-the-server]__94a6d275._.js} +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__94a6d275._.js.map +1 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_0e9d6bf7._.js +1 -1
- package/web/.next/server/chunks/ssr/_0e9d6bf7._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_250a63ae._.js +3 -0
- package/web/.next/server/chunks/ssr/_250a63ae._.js.map +1 -0
- package/web/.next/server/chunks/ssr/_61351ed3._.js +1 -1
- package/web/.next/server/chunks/ssr/_61351ed3._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_63688261._.js +6 -0
- package/web/.next/server/chunks/ssr/_63688261._.js.map +1 -0
- package/web/.next/server/chunks/ssr/_8400c8bb._.js +1 -1
- package/web/.next/server/chunks/ssr/_8400c8bb._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_9f940437._.js +1 -1
- package/web/.next/server/chunks/ssr/_9f940437._.js.map +1 -1
- package/web/.next/{standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_5ea689db._.js → server/chunks/ssr/src_presentation_web_2b91e4ec._.js} +2 -2
- package/web/.next/server/chunks/ssr/{src_presentation_web_7188a290._.js.map → src_presentation_web_2b91e4ec._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/{src_presentation_web_5ea689db._.js → src_presentation_web_5bcc1537._.js} +2 -2
- package/web/.next/server/chunks/ssr/{src_presentation_web_5ea689db._.js.map → src_presentation_web_5bcc1537._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_5955c012.js +3 -0
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_5955c012.js.map +1 -0
- package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js +3 -0
- package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js.map +1 -0
- package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js +8 -0
- package/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js.map +1 -0
- package/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js +8 -0
- package/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js.map +1 -0
- package/web/.next/server/pages/500.html +2 -2
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +142 -142
- package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +34 -34
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page.js +3 -3
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +40 -40
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js +3 -3
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +30 -30
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js +3 -3
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +34 -34
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page.js +3 -3
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +40 -40
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page.js +3 -3
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +30 -30
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page.js +3 -3
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +30 -30
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js +3 -3
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
- package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
- package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js +3 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js +3 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{[root-of-the-server]__cbae9863._.js → [root-of-the-server]__6258349c._.js} +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
- package/web/.next/{server/chunks/ssr/[root-of-the-server]__e5e09f72._.js → standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__94a6d275._.js} +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_0e9d6bf7._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_250a63ae._.js +3 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_61351ed3._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_63688261._.js +6 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_8400c8bb._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_9f940437._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{src_presentation_web_7188a290._.js → src_presentation_web_2b91e4ec._.js} +2 -2
- package/web/.next/{server/chunks/ssr/src_presentation_web_7188a290._.js → standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_5bcc1537._.js} +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_5955c012.js +3 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js +3 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js +8 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js +8 -0
- package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +142 -142
- package/web/.next/standalone/src/presentation/web/CLAUDE.md +77 -33
- package/web/.next/standalone/src/presentation/web/app/actions/create-feature.ts +27 -2
- package/web/.next/standalone/src/presentation/web/app/actions/get-feature-metadata.ts +18 -0
- package/web/.next/standalone/src/presentation/web/app/api/agent-events/route.ts +17 -0
- package/web/.next/standalone/src/presentation/web/components/common/add-repository-button/index.ts +1 -0
- package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/create-drawer-client.tsx +15 -19
- package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.tsx +44 -18
- package/web/.next/standalone/src/presentation/web/components/common/index.ts +1 -5
- package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-empty-state.tsx +1 -1
- package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +24 -8
- package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center.stories.tsx +2 -41
- package/web/.next/standalone/src/presentation/web/components/features/control-center/use-control-center-state.ts +180 -429
- package/web/.next/standalone/src/presentation/web/components/features/features-canvas/features-canvas.stories.tsx +0 -48
- package/web/.next/standalone/src/presentation/web/components/features/features-canvas/features-canvas.tsx +13 -59
- package/web/.next/standalone/src/presentation/web/components/layouts/app-shell/app-shell.tsx +1 -1
- package/web/.next/standalone/src/presentation/web/hooks/use-graph-state.ts +343 -0
- package/web/.next/standalone/src/presentation/web/lib/derive-graph.ts +178 -0
- package/web/.next/standalone/src/presentation/web/lib/layout-with-dagre.ts +0 -1
- package/web/.next/static/chunks/{0eeb65b8300fb7cd.js → 0a883e2efecfd563.js} +1 -1
- package/web/.next/static/chunks/{4a8df922364596f6.js → 596ca28d78baae25.js} +1 -1
- package/web/.next/static/chunks/{99381b9c479936d5.js → 638824249d103905.js} +1 -1
- package/web/.next/static/chunks/63a80e9d4c4b0253.js +4 -0
- package/web/.next/static/chunks/71c9ecdceac1939f.css +2 -0
- package/web/.next/static/chunks/a040b058d79f4ade.js +1 -0
- package/web/.next/static/chunks/{1249c7c8745b972e.js → c575ab63c138645b.js} +1 -1
- package/web/.next/static/chunks/cdc92cdf81005c84.js +1 -0
- package/web/.next/static/chunks/{a676f15c03fa66b9.js → e05eb1cc84ee254a.js} +1 -1
- package/web/.next/static/chunks/ef8f8940eac6c542.js +1 -0
- package/web/.next/static/chunks/{9be617fc61b893f2.js → f32c98a709949c5b.js} +1 -1
- package/web/.next/trace +1 -1
- package/web/.next/trace-build +1 -1
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-button.d.ts.map +0 -1
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node-config.d.ts +0 -7
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node-config.d.ts.map +0 -1
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node-config.js +0 -1
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.d.ts +0 -6
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.d.ts.map +0 -1
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.js +0 -23
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.d.ts +0 -10
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.d.ts.map +0 -1
- package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.js +0 -33
- package/dist/src/presentation/web/components/common/add-repository-node/index.d.ts +0 -4
- package/dist/src/presentation/web/components/common/add-repository-node/index.d.ts.map +0 -1
- package/dist/src/presentation/web/components/common/add-repository-node/pick-folder.d.ts.map +0 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_3a02d8a5.js +0 -3
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_3a02d8a5.js.map +0 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_a3044852.js +0 -3
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_a3044852.js.map +0 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__cbae9863._.js.map +0 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__e5e09f72._.js.map +0 -1
- package/web/.next/server/chunks/ssr/_6e08ce21._.js +0 -3
- package/web/.next/server/chunks/ssr/_6e08ce21._.js.map +0 -1
- package/web/.next/server/chunks/ssr/_6ef06ca9._.js +0 -6
- package/web/.next/server/chunks/ssr/_6ef06ca9._.js.map +0 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_0a49d9ee._.js +0 -8
- package/web/.next/server/chunks/ssr/src_presentation_web_0a49d9ee._.js.map +0 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_79b0305a._.js +0 -8
- package/web/.next/server/chunks/ssr/src_presentation_web_79b0305a._.js.map +0 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_cc16bd01.js +0 -3
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_cc16bd01.js.map +0 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_app_(dashboard)_get-graph-data_ts_b018483e._.js +0 -3
- package/web/.next/server/chunks/ssr/src_presentation_web_app_(dashboard)_get-graph-data_ts_b018483e._.js.map +0 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_3a02d8a5.js +0 -3
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_a3044852.js +0 -3
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_6e08ce21._.js +0 -3
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_6ef06ca9._.js +0 -6
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_0a49d9ee._.js +0 -8
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_79b0305a._.js +0 -8
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_cc16bd01.js +0 -3
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_app_(dashboard)_get-graph-data_ts_b018483e._.js +0 -3
- package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/add-repository-node-config.ts +0 -8
- package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.tsx +0 -70
- package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/add-repository-node.tsx +0 -49
- package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/index.ts +0 -3
- package/web/.next/static/chunks/1c15ab8668ecf473.js +0 -1
- package/web/.next/static/chunks/2cf89d17ee7717a6.js +0 -4
- package/web/.next/static/chunks/981b8df9ad045742.js +0 -1
- package/web/.next/static/chunks/acce8a15d6896dea.css +0 -2
- package/web/.next/static/chunks/b579fd590a79bf35.js +0 -1
- /package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/add-repository-button.d.ts +0 -0
- /package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/add-repository-button.js +0 -0
- /package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/pick-folder.d.ts +0 -0
- /package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/pick-folder.js +0 -0
- /package/web/.next/standalone/src/presentation/web/components/common/{add-repository-node → add-repository-button}/add-repository-button.tsx +0 -0
- /package/web/.next/standalone/src/presentation/web/components/common/{add-repository-node → add-repository-button}/pick-folder.ts +0 -0
- /package/web/.next/static/{nnR12fAsrSagAfeV6xj6D → uMxPSDPwZCludF5408rWJ}/_buildManifest.js +0 -0
- /package/web/.next/static/{nnR12fAsrSagAfeV6xj6D → uMxPSDPwZCludF5408rWJ}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{nnR12fAsrSagAfeV6xj6D → uMxPSDPwZCludF5408rWJ}/_ssgManifest.js +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
4
4
|
import { useRouter } from 'next/navigation';
|
|
5
5
|
import { toast } from 'sonner';
|
|
6
|
-
import { applyNodeChanges } from '@xyflow/react';
|
|
7
6
|
import type { Connection, Edge, NodeChange } from '@xyflow/react';
|
|
8
7
|
import type { FeatureNodeData } from '@/components/common/feature-node';
|
|
8
|
+
import type { RepositoryNodeData } from '@/components/common/repository-node';
|
|
9
9
|
import type { CanvasNodeType } from '@/components/features/features-canvas';
|
|
10
10
|
import {
|
|
11
11
|
layoutWithDagre,
|
|
@@ -15,13 +15,15 @@ import {
|
|
|
15
15
|
import { deleteFeature } from '@/app/actions/delete-feature';
|
|
16
16
|
import { addRepository } from '@/app/actions/add-repository';
|
|
17
17
|
import { deleteRepository } from '@/app/actions/delete-repository';
|
|
18
|
-
import {
|
|
18
|
+
import { getFeatureMetadata } from '@/app/actions/get-feature-metadata';
|
|
19
19
|
import { useAgentEventsContext } from '@/hooks/agent-events-provider';
|
|
20
20
|
import { useSoundAction } from '@/hooks/use-sound-action';
|
|
21
21
|
import {
|
|
22
22
|
mapEventTypeToState,
|
|
23
23
|
mapPhaseNameToLifecycle,
|
|
24
24
|
} from '@/components/common/feature-node/derive-feature-state';
|
|
25
|
+
import { NotificationEventType } from '@shepai/core/domain/generated/output';
|
|
26
|
+
import { useGraphState, type GraphCallbacks } from '@/hooks/use-graph-state';
|
|
25
27
|
|
|
26
28
|
export interface ControlCenterState {
|
|
27
29
|
nodes: CanvasNodeType[];
|
|
@@ -37,6 +39,12 @@ export interface ControlCenterState {
|
|
|
37
39
|
dataOverride?: Partial<FeatureNodeData>,
|
|
38
40
|
edgeType?: string
|
|
39
41
|
) => string;
|
|
42
|
+
/** Stable lookup: repositoryPath for a feature node. */
|
|
43
|
+
getFeatureRepositoryPath: (featureNodeId: string) => string | undefined;
|
|
44
|
+
/** Stable lookup: repository data by nodeId. */
|
|
45
|
+
getRepositoryData: (nodeId: string) => RepositoryNodeData | undefined;
|
|
46
|
+
/** Sync callbacks into derived node data (does not trigger re-render). */
|
|
47
|
+
setCallbacks: (callbacks: GraphCallbacks) => void;
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
let nextFeatureId = 0;
|
|
@@ -46,37 +54,31 @@ export function useControlCenterState(
|
|
|
46
54
|
initialEdges: Edge[]
|
|
47
55
|
): ControlCenterState {
|
|
48
56
|
const router = useRouter();
|
|
49
|
-
const [nodes, setNodes] = useState<CanvasNodeType[]>(initialNodes);
|
|
50
|
-
// eslint-disable-next-line react/hook-use-state -- raw setter renamed; public setEdges wrapper keeps edgesRef in sync
|
|
51
|
-
const [edges, setEdgesRaw] = useState<Edge[]>(initialEdges);
|
|
52
|
-
const edgesRef = useRef<Edge[]>(initialEdges);
|
|
53
|
-
|
|
54
|
-
// Wrapper that keeps edgesRef in sync with edges state, allowing
|
|
55
|
-
// createFeatureNode to read current edges without a closure dependency.
|
|
56
|
-
const setEdges = useCallback((update: Edge[] | ((prev: Edge[]) => Edge[])) => {
|
|
57
|
-
setEdgesRaw((prev) => {
|
|
58
|
-
const next = typeof update === 'function' ? update(prev) : update;
|
|
59
|
-
edgesRef.current = next;
|
|
60
|
-
return next;
|
|
61
|
-
});
|
|
62
|
-
}, []);
|
|
63
57
|
const deleteSound = useSoundAction('delete');
|
|
64
58
|
const createSound = useSoundAction('create');
|
|
65
59
|
|
|
66
|
-
|
|
60
|
+
const {
|
|
61
|
+
nodes,
|
|
62
|
+
edges,
|
|
63
|
+
reconcile,
|
|
64
|
+
updateFeature,
|
|
65
|
+
addPendingFeature,
|
|
66
|
+
removeFeature,
|
|
67
|
+
restoreFeature,
|
|
68
|
+
addRepository: addRepositoryToMap,
|
|
69
|
+
removeRepository,
|
|
70
|
+
replaceRepository,
|
|
71
|
+
getFeatureRepositoryPath,
|
|
72
|
+
getRepositoryData,
|
|
73
|
+
setCallbacks,
|
|
74
|
+
} = useGraphState(initialNodes, initialEdges);
|
|
75
|
+
|
|
76
|
+
// Sync server props into domain Maps when initialNodes/initialEdges change.
|
|
77
|
+
// Keyed by a stable string so we don't re-reconcile on every render.
|
|
67
78
|
const initialNodeKey = initialNodes
|
|
68
79
|
.map((n) => n.id)
|
|
69
80
|
.sort()
|
|
70
81
|
.join(',');
|
|
71
|
-
const initialEdgeKey = initialEdges
|
|
72
|
-
.map((e) => e.id)
|
|
73
|
-
.sort()
|
|
74
|
-
.join(',');
|
|
75
|
-
|
|
76
|
-
// Track previous feature states so we can detect transitions on server refresh
|
|
77
|
-
const prevFeatureStatesRef = useRef<Map<string, FeatureNodeData['state']>>(new Map());
|
|
78
|
-
|
|
79
|
-
// Stable key that changes when feature DATA changes (state/lifecycle), not just IDs.
|
|
80
82
|
const initialDataKey = initialNodes
|
|
81
83
|
.filter((n) => n.type === 'featureNode')
|
|
82
84
|
.map((n) => {
|
|
@@ -86,83 +88,40 @@ export function useControlCenterState(
|
|
|
86
88
|
.sort()
|
|
87
89
|
.join(',');
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
// to avoid infinite re-renders from unstable array references.
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
setNodes((currentNodes) => {
|
|
93
|
-
// Build a lookup of current node positions by ID
|
|
94
|
-
const currentById = new Map(currentNodes.map((n) => [n.id, n]));
|
|
95
|
-
|
|
96
|
-
// Identify optimistic "creating" nodes (they have temp IDs not in server data)
|
|
97
|
-
const serverIds = new Set(initialNodes.map((n) => n.id));
|
|
98
|
-
const creatingNodes = currentNodes.filter(
|
|
99
|
-
(n) =>
|
|
100
|
-
n.type === 'featureNode' &&
|
|
101
|
-
(n.data as FeatureNodeData).state === 'creating' &&
|
|
102
|
-
!serverIds.has(n.id)
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
// Merge server nodes with client positions, tracking if relayout is needed
|
|
106
|
-
let needsRelayout = false;
|
|
107
|
-
const merged = initialNodes.map((serverNode) => {
|
|
108
|
-
// Node already exists on canvas — keep its position, update data
|
|
109
|
-
const existing = currentById.get(serverNode.id);
|
|
110
|
-
if (existing) {
|
|
111
|
-
return { ...serverNode, position: existing.position };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// New server node — inherit position from an optimistic creating node if available
|
|
115
|
-
if (serverNode.type === 'featureNode' && creatingNodes.length > 0) {
|
|
116
|
-
const donor = creatingNodes.shift()!;
|
|
117
|
-
return { ...serverNode, position: donor.position };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Truly new node with no optimistic counterpart — needs relayout
|
|
121
|
-
needsRelayout = true;
|
|
122
|
-
return serverNode;
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// Also check if non-creating nodes were removed (graph got smaller)
|
|
126
|
-
if (!needsRelayout) {
|
|
127
|
-
needsRelayout = currentNodes.some(
|
|
128
|
-
(cn) =>
|
|
129
|
-
!serverIds.has(cn.id) &&
|
|
130
|
-
!(cn.type === 'featureNode' && (cn.data as FeatureNodeData).state === 'creating')
|
|
131
|
-
);
|
|
132
|
-
}
|
|
91
|
+
const prevReconcileKey = useRef('');
|
|
133
92
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
const key = `${initialNodeKey}|${initialDataKey}`;
|
|
95
|
+
if (key !== prevReconcileKey.current) {
|
|
96
|
+
prevReconcileKey.current = key;
|
|
97
|
+
reconcile(initialNodes, initialEdges);
|
|
98
|
+
}
|
|
99
|
+
}, [initialNodeKey, initialDataKey, initialNodes, initialEdges, reconcile]);
|
|
139
100
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}, [initialNodeKey, initialNodes, initialEdges, setEdges]);
|
|
101
|
+
// Track previous feature states for fallback notifications
|
|
102
|
+
const prevFeatureStatesRef = useRef<Map<string, FeatureNodeData['state']>>(new Map());
|
|
143
103
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
104
|
+
const initialFeatureDataKey = initialNodes
|
|
105
|
+
.filter((n) => n.type === 'featureNode')
|
|
106
|
+
.map((n) => {
|
|
107
|
+
const d = n.data as FeatureNodeData;
|
|
108
|
+
return `${n.id}:${d.state}`;
|
|
109
|
+
})
|
|
110
|
+
.sort()
|
|
111
|
+
.join(',');
|
|
147
112
|
|
|
148
|
-
// Fallback notifications from server-prop state transitions.
|
|
149
|
-
// Fires only when SSE didn't already deliver the matching event for a feature.
|
|
150
113
|
const { events } = useAgentEventsContext();
|
|
151
114
|
|
|
152
115
|
useEffect(() => {
|
|
153
116
|
const prevStates = prevFeatureStatesRef.current;
|
|
154
|
-
|
|
155
117
|
for (const node of initialNodes) {
|
|
156
118
|
if (node.type !== 'featureNode') continue;
|
|
157
119
|
const data = node.data as FeatureNodeData;
|
|
158
120
|
const prev = prevStates.get(node.id);
|
|
159
|
-
|
|
160
121
|
if (prev !== undefined && prev !== data.state) {
|
|
161
|
-
// Check if SSE already delivered a matching event for this feature
|
|
162
122
|
const sseAlreadyCovered = events.some(
|
|
163
123
|
(e) => e.featureId === data.featureId && mapEventTypeToState(e.eventType) === data.state
|
|
164
124
|
);
|
|
165
|
-
|
|
166
125
|
if (!sseAlreadyCovered) {
|
|
167
126
|
if (data.state === 'done') {
|
|
168
127
|
toast.success(data.name, { description: 'Feature completed!' });
|
|
@@ -171,9 +130,7 @@ export function useControlCenterState(
|
|
|
171
130
|
description: 'Waiting for your approval',
|
|
172
131
|
action: {
|
|
173
132
|
label: 'Review',
|
|
174
|
-
onClick: () => {
|
|
175
|
-
router.push(`/feature/${data.featureId}`);
|
|
176
|
-
},
|
|
133
|
+
onClick: () => router.push(`/feature/${data.featureId}`),
|
|
177
134
|
},
|
|
178
135
|
});
|
|
179
136
|
} else if (data.state === 'error') {
|
|
@@ -181,186 +138,78 @@ export function useControlCenterState(
|
|
|
181
138
|
}
|
|
182
139
|
}
|
|
183
140
|
}
|
|
184
|
-
|
|
185
141
|
prevStates.set(node.id, data.state);
|
|
186
142
|
}
|
|
187
|
-
}, [
|
|
143
|
+
}, [initialFeatureDataKey, initialNodes, events, router]);
|
|
188
144
|
|
|
189
|
-
//
|
|
190
|
-
// Uses `events` array (not `lastEvent`) so that React batching cannot silently
|
|
191
|
-
// drop events when multiple SSE messages arrive in the same tick.
|
|
145
|
+
// SSE effect: only state + lifecycle updates
|
|
192
146
|
const processedEventCountRef = useRef(0);
|
|
193
147
|
|
|
194
148
|
useEffect(() => {
|
|
195
149
|
if (events.length <= processedEventCountRef.current) return;
|
|
196
|
-
|
|
197
150
|
const newEvents = events.slice(processedEventCountRef.current);
|
|
198
151
|
processedEventCountRef.current = events.length;
|
|
199
152
|
|
|
153
|
+
// PhaseCompleted must NOT set state when WaitingApproval is in the same poll batch —
|
|
154
|
+
// they arrive together and PhaseCompleted → 'running' would revert 'action-required'.
|
|
155
|
+
// But when PhaseCompleted arrives WITHOUT WaitingApproval (e.g. after approval), we
|
|
156
|
+
// must allow it to update state as a fallback for any missed AgentStarted event.
|
|
157
|
+
const waitingApprovalFeatures = new Set(
|
|
158
|
+
newEvents
|
|
159
|
+
.filter((e) => e.eventType === NotificationEventType.WaitingApproval)
|
|
160
|
+
.map((e) => e.featureId)
|
|
161
|
+
);
|
|
162
|
+
|
|
200
163
|
for (const event of newEvents) {
|
|
201
|
-
const newState =
|
|
164
|
+
const newState =
|
|
165
|
+
event.eventType === NotificationEventType.PhaseCompleted &&
|
|
166
|
+
waitingApprovalFeatures.has(event.featureId)
|
|
167
|
+
? undefined
|
|
168
|
+
: mapEventTypeToState(event.eventType);
|
|
202
169
|
const newLifecycle = mapPhaseNameToLifecycle(event.phaseName);
|
|
203
170
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return {
|
|
212
|
-
...node,
|
|
213
|
-
data: {
|
|
214
|
-
...data,
|
|
215
|
-
state: newState,
|
|
216
|
-
...(newLifecycle !== undefined && { lifecycle: newLifecycle }),
|
|
217
|
-
},
|
|
218
|
-
};
|
|
219
|
-
})
|
|
220
|
-
);
|
|
171
|
+
const nodeId = `feat-${event.featureId}`;
|
|
172
|
+
if (newState !== undefined || newLifecycle !== undefined) {
|
|
173
|
+
updateFeature(nodeId, {
|
|
174
|
+
...(newState !== undefined && { state: newState }),
|
|
175
|
+
...(newLifecycle !== undefined && { lifecycle: newLifecycle }),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
221
178
|
}
|
|
222
|
-
}, [events]);
|
|
223
|
-
|
|
224
|
-
// Lightweight server-action polling fallback: fetches fresh graph data every 5s
|
|
225
|
-
// when any feature is in an active state. This avoids full router.refresh() while
|
|
226
|
-
// ensuring the UI stays in sync even if SSE events are missed.
|
|
227
|
-
//
|
|
228
|
-
// We derive a stable boolean so the interval isn't torn down on every node change.
|
|
229
|
-
const hasActiveFeature = nodes.some((n) => {
|
|
230
|
-
if (n.type !== 'featureNode') return false;
|
|
231
|
-
const data = n.data as FeatureNodeData;
|
|
232
|
-
return (
|
|
233
|
-
data.state === 'running' || data.state === 'action-required' || data.state === 'creating'
|
|
234
|
-
);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
useEffect(() => {
|
|
238
|
-
if (!hasActiveFeature) return;
|
|
239
|
-
|
|
240
|
-
const interval = setInterval(async () => {
|
|
241
|
-
try {
|
|
242
|
-
const { nodes: serverNodes } = await fetchGraphData();
|
|
243
|
-
setNodes((currentNodes) => {
|
|
244
|
-
const serverById = new Map(serverNodes.map((n) => [n.id, n]));
|
|
245
|
-
let changed = false;
|
|
246
|
-
|
|
247
|
-
const merged = currentNodes.map((node) => {
|
|
248
|
-
const serverNode = serverById.get(node.id);
|
|
249
|
-
if (!serverNode || node.type !== 'featureNode') return node;
|
|
250
|
-
const currentData = node.data as FeatureNodeData;
|
|
251
|
-
const serverData = serverNode.data as FeatureNodeData;
|
|
252
|
-
|
|
253
|
-
// Only update if server data has different state or lifecycle
|
|
254
|
-
if (
|
|
255
|
-
currentData.state === serverData.state &&
|
|
256
|
-
currentData.lifecycle === serverData.lifecycle
|
|
257
|
-
) {
|
|
258
|
-
return node;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
changed = true;
|
|
262
|
-
return {
|
|
263
|
-
...node,
|
|
264
|
-
data: {
|
|
265
|
-
...currentData,
|
|
266
|
-
state: serverData.state,
|
|
267
|
-
lifecycle: serverData.lifecycle,
|
|
268
|
-
progress: serverData.progress,
|
|
269
|
-
...(serverData.errorMessage && { errorMessage: serverData.errorMessage }),
|
|
270
|
-
...(serverData.runtime && { runtime: serverData.runtime }),
|
|
271
|
-
...(serverData.agentType && { agentType: serverData.agentType }),
|
|
272
|
-
...(serverData.pr && { pr: serverData.pr }),
|
|
273
|
-
...(serverData.blockedBy && { blockedBy: serverData.blockedBy }),
|
|
274
|
-
},
|
|
275
|
-
};
|
|
276
|
-
});
|
|
179
|
+
}, [events, updateFeature]);
|
|
277
180
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const newServerNodes = serverNodes.filter((n) => !clientIds.has(n.id));
|
|
281
|
-
|
|
282
|
-
// Replace optimistic "creating" temp nodes with their real server
|
|
283
|
-
// counterparts. Temp nodes have IDs like "feature-<ts>" while real
|
|
284
|
-
// nodes use "feat-<uuid>". Match by repositoryPath + name.
|
|
285
|
-
if (newServerNodes.length > 0) {
|
|
286
|
-
const creatingIndices: number[] = [];
|
|
287
|
-
merged.forEach((n, i) => {
|
|
288
|
-
if (n.type === 'featureNode' && (n.data as FeatureNodeData).state === 'creating') {
|
|
289
|
-
creatingIndices.push(i);
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
for (const serverNode of newServerNodes) {
|
|
294
|
-
if (serverNode.type !== 'featureNode' || creatingIndices.length === 0) continue;
|
|
295
|
-
const idx = creatingIndices.shift()!;
|
|
296
|
-
// Replace the temp node in-place, keeping its position
|
|
297
|
-
merged[idx] = { ...serverNode, position: merged[idx].position };
|
|
298
|
-
changed = true;
|
|
299
|
-
}
|
|
181
|
+
// Separate effect: fetch metadata (name + description) when SSE reports it changed
|
|
182
|
+
const metadataFetchedRef = useRef<Set<string>>(new Set());
|
|
300
183
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
for (const event of events) {
|
|
186
|
+
if (event.message !== 'Feature metadata updated') continue;
|
|
187
|
+
if (metadataFetchedRef.current.has(event.featureId)) continue;
|
|
188
|
+
metadataFetchedRef.current.add(event.featureId);
|
|
189
|
+
|
|
190
|
+
const nodeId = `feat-${event.featureId}`;
|
|
191
|
+
getFeatureMetadata(event.featureId)
|
|
192
|
+
.then((meta) => {
|
|
193
|
+
if (meta) {
|
|
194
|
+
updateFeature(nodeId, { name: meta.name, description: meta.description });
|
|
308
195
|
}
|
|
309
|
-
|
|
310
|
-
|
|
196
|
+
})
|
|
197
|
+
.catch(() => {
|
|
198
|
+
// Silent: metadata fetch failure is non-critical
|
|
311
199
|
});
|
|
312
|
-
} catch {
|
|
313
|
-
// Silently ignore polling errors — SSE is the primary mechanism
|
|
314
|
-
}
|
|
315
|
-
}, 5_000);
|
|
316
|
-
|
|
317
|
-
return () => clearInterval(interval);
|
|
318
|
-
}, [hasActiveFeature]);
|
|
319
|
-
|
|
320
|
-
const onNodesChange = useCallback((changes: NodeChange<CanvasNodeType>[]) => {
|
|
321
|
-
// Filter out 'replace' changes — in controlled mode these come from
|
|
322
|
-
// React Flow's StoreUpdater syncing the nodes prop back into state.
|
|
323
|
-
// Applying them creates a circular update loop (nodes → enrichedNodes →
|
|
324
|
-
// StoreUpdater → onNodesChange → setNodes → nodes → …) that both causes
|
|
325
|
-
// excessive re-renders and overwrites SSE-driven state changes with stale data.
|
|
326
|
-
const applicable = changes.filter((c) => c.type !== 'replace');
|
|
327
|
-
if (applicable.length > 0) {
|
|
328
|
-
setNodes((ns) => applyNodeChanges(applicable, ns));
|
|
329
200
|
}
|
|
330
|
-
}, []);
|
|
331
|
-
|
|
332
|
-
const handleConnect = useCallback(
|
|
333
|
-
(connection: Connection) => {
|
|
334
|
-
if (!connection.source || !connection.target) return;
|
|
201
|
+
}, [events, updateFeature]);
|
|
335
202
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const targetAlreadyHasRepo = currentEdges.some((e) => {
|
|
343
|
-
const edgeSource = currentNodes.find((n) => n.id === e.source);
|
|
344
|
-
return edgeSource?.type === 'repositoryNode' && e.target === connection.target;
|
|
345
|
-
});
|
|
346
|
-
if (targetAlreadyHasRepo) return currentEdges;
|
|
347
|
-
|
|
348
|
-
return [
|
|
349
|
-
...currentEdges,
|
|
350
|
-
{
|
|
351
|
-
id: `edge-${connection.source}-${connection.target}`,
|
|
352
|
-
source: connection.source,
|
|
353
|
-
target: connection.target,
|
|
354
|
-
style: { strokeDasharray: '5 5' },
|
|
355
|
-
},
|
|
356
|
-
];
|
|
357
|
-
});
|
|
203
|
+
// onNodesChange is a no-op: nodes are derived from domain Maps.
|
|
204
|
+
// Since nodesDraggable=false and elementsSelectable=false, only React Flow's
|
|
205
|
+
// internal replace/dimensions changes come through — we ignore them all.
|
|
206
|
+
const onNodesChange = useCallback((_changes: NodeChange<CanvasNodeType>[]) => {
|
|
207
|
+
// Intentional no-op: domain Maps are the source of truth.
|
|
208
|
+
}, []);
|
|
358
209
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
[setEdges]
|
|
363
|
-
);
|
|
210
|
+
const handleConnect = useCallback((_connection: Connection) => {
|
|
211
|
+
// Connections are managed via domain operations, not direct edge manipulation.
|
|
212
|
+
}, []);
|
|
364
213
|
|
|
365
214
|
const createFeatureNode = useCallback(
|
|
366
215
|
(
|
|
@@ -368,11 +217,14 @@ export function useControlCenterState(
|
|
|
368
217
|
dataOverride?: Partial<FeatureNodeData>,
|
|
369
218
|
edgeType?: string
|
|
370
219
|
): string => {
|
|
371
|
-
|
|
220
|
+
// Use real feature ID when available (from server), otherwise temp ID
|
|
221
|
+
const id = dataOverride?.featureId
|
|
222
|
+
? `feat-${dataOverride.featureId}`
|
|
223
|
+
: `feature-${Date.now()}-${nextFeatureId++}`;
|
|
372
224
|
const newFeatureData: FeatureNodeData = {
|
|
373
225
|
name: dataOverride?.name ?? 'New Feature',
|
|
374
226
|
description: dataOverride?.description ?? 'Describe what this feature does',
|
|
375
|
-
featureId: `#${id.slice(-4)}`,
|
|
227
|
+
featureId: dataOverride?.featureId ?? `#${id.slice(-4)}`,
|
|
376
228
|
lifecycle: 'requirements',
|
|
377
229
|
state: dataOverride?.state ?? 'running',
|
|
378
230
|
progress: 0,
|
|
@@ -380,171 +232,118 @@ export function useControlCenterState(
|
|
|
380
232
|
branch: dataOverride?.branch ?? '',
|
|
381
233
|
};
|
|
382
234
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
type: 'featureNode' as const,
|
|
387
|
-
position: { x: 0, y: 0 },
|
|
388
|
-
data: newFeatureData,
|
|
389
|
-
} as CanvasNodeType;
|
|
390
|
-
|
|
391
|
-
const newEdge = sourceNodeId
|
|
392
|
-
? {
|
|
393
|
-
id:
|
|
394
|
-
edgeType === 'dependencyEdge'
|
|
395
|
-
? `dep-${sourceNodeId}-${id}`
|
|
396
|
-
: `edge-${sourceNodeId}-${id}`,
|
|
397
|
-
source: sourceNodeId,
|
|
398
|
-
target: id,
|
|
399
|
-
...(edgeType ? { type: edgeType } : { style: { strokeDasharray: '5 5' } }),
|
|
400
|
-
}
|
|
401
|
-
: null;
|
|
402
|
-
|
|
403
|
-
const allEdges = newEdge
|
|
404
|
-
? [...edgesRef.current.filter((e) => e.id !== newEdge.id), newEdge]
|
|
405
|
-
: edgesRef.current;
|
|
406
|
-
|
|
407
|
-
// Insert the new node after the last sibling connected to the same source,
|
|
408
|
-
// so dagre's input-order preservation keeps repo groups together.
|
|
409
|
-
let insertIndex = currentNodes.length;
|
|
410
|
-
if (sourceNodeId) {
|
|
411
|
-
const siblingIds = new Set(
|
|
412
|
-
allEdges.filter((e) => e.source === sourceNodeId).map((e) => e.target)
|
|
413
|
-
);
|
|
414
|
-
for (let i = currentNodes.length - 1; i >= 0; i--) {
|
|
415
|
-
if (siblingIds.has(currentNodes[i].id)) {
|
|
416
|
-
insertIndex = i + 1;
|
|
417
|
-
break;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
const allNodes = [
|
|
422
|
-
...currentNodes.slice(0, insertIndex),
|
|
423
|
-
newNode,
|
|
424
|
-
...currentNodes.slice(insertIndex),
|
|
425
|
-
];
|
|
426
|
-
|
|
427
|
-
// Run dagre layout on the full graph so the new node is positioned consistently
|
|
428
|
-
const layoutResult = layoutWithDagre(allNodes, allEdges, CANVAS_LAYOUT_DEFAULTS);
|
|
429
|
-
setEdges(layoutResult.edges);
|
|
430
|
-
return layoutResult.nodes;
|
|
431
|
-
});
|
|
235
|
+
const parentNodeId = edgeType === 'dependencyEdge' && sourceNodeId ? sourceNodeId : undefined;
|
|
236
|
+
|
|
237
|
+
addPendingFeature(id, newFeatureData, parentNodeId);
|
|
432
238
|
|
|
433
239
|
return id;
|
|
434
240
|
},
|
|
435
|
-
[
|
|
241
|
+
[addPendingFeature]
|
|
436
242
|
);
|
|
437
243
|
|
|
438
244
|
const handleDeleteFeature = useCallback(
|
|
439
245
|
(featureId: string) => {
|
|
440
246
|
const nodeId = `feat-${featureId}`;
|
|
441
247
|
|
|
442
|
-
//
|
|
443
|
-
const
|
|
444
|
-
|
|
248
|
+
// Find the current entry for rollback
|
|
249
|
+
const prevEntry = nodes
|
|
250
|
+
.filter((n) => n.id === nodeId)
|
|
251
|
+
.map((n) => ({
|
|
252
|
+
nodeId: n.id,
|
|
253
|
+
data: n.data as FeatureNodeData,
|
|
254
|
+
}))[0];
|
|
445
255
|
|
|
446
|
-
// Optimistic removal
|
|
447
|
-
|
|
448
|
-
const remainingNodes = currentNodes.filter((n) => n.id !== nodeId);
|
|
449
|
-
const remainingEdges = edgesRef.current.filter(
|
|
450
|
-
(e) => e.source !== nodeId && e.target !== nodeId
|
|
451
|
-
);
|
|
452
|
-
const layoutResult = layoutWithDagre(
|
|
453
|
-
remainingNodes,
|
|
454
|
-
remainingEdges,
|
|
455
|
-
CANVAS_LAYOUT_DEFAULTS
|
|
456
|
-
);
|
|
457
|
-
setEdges(layoutResult.edges);
|
|
458
|
-
return layoutResult.nodes;
|
|
459
|
-
});
|
|
256
|
+
// Optimistic removal
|
|
257
|
+
removeFeature(nodeId);
|
|
460
258
|
deleteSound.play();
|
|
461
259
|
toast.success('Feature deleted successfully');
|
|
462
260
|
router.push('/');
|
|
463
261
|
|
|
464
|
-
// Persist in background — rollback on failure
|
|
465
262
|
deleteFeature(featureId)
|
|
466
263
|
.then((result) => {
|
|
467
264
|
if (result.error) {
|
|
468
|
-
|
|
469
|
-
setEdges(prevEdges);
|
|
265
|
+
if (prevEntry) restoreFeature(nodeId, prevEntry);
|
|
470
266
|
toast.error(result.error);
|
|
471
267
|
}
|
|
472
268
|
})
|
|
473
269
|
.catch(() => {
|
|
474
|
-
|
|
475
|
-
setEdges(prevEdges);
|
|
270
|
+
if (prevEntry) restoreFeature(nodeId, prevEntry);
|
|
476
271
|
toast.error('Failed to delete feature');
|
|
477
272
|
});
|
|
478
273
|
},
|
|
479
|
-
[nodes, router, deleteSound,
|
|
274
|
+
[nodes, router, deleteSound, removeFeature, restoreFeature]
|
|
480
275
|
);
|
|
481
276
|
|
|
482
277
|
const handleDeleteRepository = useCallback(
|
|
483
278
|
async (repositoryId: string) => {
|
|
484
279
|
const repoNodeId = `repo-${repositoryId}`;
|
|
485
280
|
|
|
281
|
+
// Find children of this repo via edges
|
|
282
|
+
const childFeatureIds = new Set(
|
|
283
|
+
edges.filter((e) => e.source === repoNodeId).map((e) => e.target)
|
|
284
|
+
);
|
|
285
|
+
|
|
486
286
|
// Snapshot for rollback
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
edgesRef.current.filter((e) => e.source === repoNodeId).map((e) => e.target)
|
|
495
|
-
);
|
|
496
|
-
const remainingNodes = currentNodes.filter(
|
|
497
|
-
(n) => n.id !== repoNodeId && !childFeatureIds.has(n.id)
|
|
498
|
-
);
|
|
499
|
-
const remainingEdges = edgesRef.current.filter(
|
|
500
|
-
(e) =>
|
|
501
|
-
e.source !== repoNodeId &&
|
|
502
|
-
e.target !== repoNodeId &&
|
|
503
|
-
!childFeatureIds.has(e.source) &&
|
|
504
|
-
!childFeatureIds.has(e.target)
|
|
505
|
-
);
|
|
506
|
-
const layoutResult = layoutWithDagre(
|
|
507
|
-
remainingNodes,
|
|
508
|
-
remainingEdges,
|
|
509
|
-
CANVAS_LAYOUT_DEFAULTS
|
|
510
|
-
);
|
|
511
|
-
setEdges(layoutResult.edges);
|
|
512
|
-
return layoutResult.nodes;
|
|
513
|
-
});
|
|
287
|
+
const prevRepoData = getRepositoryData(repoNodeId);
|
|
288
|
+
|
|
289
|
+
// Optimistic: remove repo + children
|
|
290
|
+
removeRepository(repoNodeId);
|
|
291
|
+
for (const childId of childFeatureIds) {
|
|
292
|
+
removeFeature(childId);
|
|
293
|
+
}
|
|
514
294
|
|
|
515
295
|
try {
|
|
516
296
|
const result = await deleteRepository(repositoryId);
|
|
517
|
-
|
|
518
297
|
if (!result.success) {
|
|
519
298
|
toast.error(result.error ?? 'Failed to remove repository');
|
|
520
|
-
|
|
521
|
-
|
|
299
|
+
if (prevRepoData) addRepositoryToMap(repoNodeId, prevRepoData);
|
|
300
|
+
for (const childId of childFeatureIds) {
|
|
301
|
+
const childNode = nodes.find((n) => n.id === childId);
|
|
302
|
+
if (childNode) {
|
|
303
|
+
restoreFeature(childId, {
|
|
304
|
+
nodeId: childId,
|
|
305
|
+
data: childNode.data as FeatureNodeData,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
522
309
|
return;
|
|
523
310
|
}
|
|
524
|
-
|
|
525
311
|
deleteSound.play();
|
|
526
312
|
toast.success('Repository removed');
|
|
527
313
|
} catch {
|
|
528
314
|
toast.error('Failed to remove repository');
|
|
529
|
-
|
|
530
|
-
|
|
315
|
+
if (prevRepoData) addRepositoryToMap(repoNodeId, prevRepoData);
|
|
316
|
+
for (const childId of childFeatureIds) {
|
|
317
|
+
const childNode = nodes.find((n) => n.id === childId);
|
|
318
|
+
if (childNode) {
|
|
319
|
+
restoreFeature(childId, {
|
|
320
|
+
nodeId: childId,
|
|
321
|
+
data: childNode.data as FeatureNodeData,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
531
325
|
}
|
|
532
326
|
},
|
|
533
|
-
[
|
|
327
|
+
[
|
|
328
|
+
edges,
|
|
329
|
+
nodes,
|
|
330
|
+
deleteSound,
|
|
331
|
+
removeRepository,
|
|
332
|
+
removeFeature,
|
|
333
|
+
addRepositoryToMap,
|
|
334
|
+
restoreFeature,
|
|
335
|
+
getRepositoryData,
|
|
336
|
+
]
|
|
534
337
|
);
|
|
535
338
|
|
|
536
339
|
const handleLayout = useCallback(
|
|
537
340
|
(direction: LayoutDirection) => {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
});
|
|
543
|
-
setEdges(result.edges);
|
|
544
|
-
return result.nodes;
|
|
545
|
-
});
|
|
341
|
+
// Layout is applied via reconcile on next server prop update.
|
|
342
|
+
// For immediate re-layout, we apply dagre and trigger a reconcile-like update.
|
|
343
|
+
const result = layoutWithDagre(nodes, edges, { ...CANVAS_LAYOUT_DEFAULTS, direction });
|
|
344
|
+
reconcile(result.nodes, result.edges);
|
|
546
345
|
},
|
|
547
|
-
[
|
|
346
|
+
[nodes, edges, reconcile]
|
|
548
347
|
);
|
|
549
348
|
|
|
550
349
|
const handleAddRepository = useCallback(
|
|
@@ -556,81 +355,30 @@ export function useControlCenterState(
|
|
|
556
355
|
.split(/[\\/]/)
|
|
557
356
|
.pop() ?? path;
|
|
558
357
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
const newNode = {
|
|
562
|
-
id: tempId,
|
|
563
|
-
type: 'repositoryNode' as const,
|
|
564
|
-
position: { x: 0, y: 0 },
|
|
565
|
-
data: { name: repoName, repositoryPath: path, id: tempId },
|
|
566
|
-
} as CanvasNodeType;
|
|
567
|
-
|
|
568
|
-
const allNodes = [...currentNodes, newNode];
|
|
569
|
-
const layoutResult = layoutWithDagre(allNodes, edgesRef.current, CANVAS_LAYOUT_DEFAULTS);
|
|
570
|
-
setEdges(layoutResult.edges);
|
|
571
|
-
return layoutResult.nodes;
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
// Persist via server action
|
|
358
|
+
addRepositoryToMap(tempId, { name: repoName, repositoryPath: path, id: tempId });
|
|
359
|
+
|
|
575
360
|
addRepository({ path, name: repoName })
|
|
576
361
|
.then((result) => {
|
|
577
362
|
if (result.error) {
|
|
578
|
-
|
|
579
|
-
setNodes((prev) => {
|
|
580
|
-
const remaining = prev.filter((n) => n.id !== tempId);
|
|
581
|
-
const layoutResult = layoutWithDagre(
|
|
582
|
-
remaining,
|
|
583
|
-
edgesRef.current,
|
|
584
|
-
CANVAS_LAYOUT_DEFAULTS
|
|
585
|
-
);
|
|
586
|
-
setEdges(layoutResult.edges);
|
|
587
|
-
return layoutResult.nodes;
|
|
588
|
-
});
|
|
363
|
+
removeRepository(tempId);
|
|
589
364
|
toast.error(result.error);
|
|
590
365
|
return;
|
|
591
366
|
}
|
|
592
|
-
|
|
593
|
-
// Replace temp ID with real repository ID
|
|
594
367
|
const repo = result.repository!;
|
|
595
368
|
const realId = `repo-${repo.id}`;
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
id: realId,
|
|
602
|
-
data: { ...n.data, id: repo.id, repositoryPath: repo.path },
|
|
603
|
-
} as CanvasNodeType)
|
|
604
|
-
: n
|
|
605
|
-
)
|
|
606
|
-
);
|
|
607
|
-
setEdges((prev) =>
|
|
608
|
-
prev.map((e) => ({
|
|
609
|
-
...e,
|
|
610
|
-
source: e.source === tempId ? realId : e.source,
|
|
611
|
-
target: e.target === tempId ? realId : e.target,
|
|
612
|
-
id: e.id.replace(tempId, realId),
|
|
613
|
-
}))
|
|
614
|
-
);
|
|
615
|
-
|
|
369
|
+
replaceRepository(tempId, realId, {
|
|
370
|
+
name: repo.name,
|
|
371
|
+
repositoryPath: repo.path,
|
|
372
|
+
id: repo.id,
|
|
373
|
+
});
|
|
616
374
|
createSound.play();
|
|
617
375
|
})
|
|
618
376
|
.catch(() => {
|
|
619
|
-
|
|
620
|
-
setNodes((prev) => {
|
|
621
|
-
const remaining = prev.filter((n) => n.id !== tempId);
|
|
622
|
-
const layoutResult = layoutWithDagre(
|
|
623
|
-
remaining,
|
|
624
|
-
edgesRef.current,
|
|
625
|
-
CANVAS_LAYOUT_DEFAULTS
|
|
626
|
-
);
|
|
627
|
-
setEdges(layoutResult.edges);
|
|
628
|
-
return layoutResult.nodes;
|
|
629
|
-
});
|
|
377
|
+
removeRepository(tempId);
|
|
630
378
|
toast.error('Failed to add repository');
|
|
631
379
|
});
|
|
632
380
|
},
|
|
633
|
-
[
|
|
381
|
+
[addRepositoryToMap, removeRepository, replaceRepository, createSound]
|
|
634
382
|
);
|
|
635
383
|
|
|
636
384
|
return {
|
|
@@ -643,5 +391,8 @@ export function useControlCenterState(
|
|
|
643
391
|
handleDeleteFeature,
|
|
644
392
|
handleDeleteRepository,
|
|
645
393
|
createFeatureNode,
|
|
394
|
+
getFeatureRepositoryPath,
|
|
395
|
+
getRepositoryData,
|
|
396
|
+
setCallbacks,
|
|
646
397
|
};
|
|
647
398
|
}
|