@shepai/cli 1.72.9-pr218.000ada0 → 1.72.9-pr220.99e5e13
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 -2
- package/dist/src/presentation/web/components/common/index.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/index.js +1 -2
- 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 +25 -7
- 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 +143 -348
- 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 +42 -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 +257 -0
- package/dist/src/presentation/web/hooks/use-notifications.d.ts +1 -5
- package/dist/src/presentation/web/hooks/use-notifications.d.ts.map +1 -1
- package/dist/src/presentation/web/hooks/use-notifications.js +1 -26
- package/dist/src/presentation/web/hooks/use-notifications.stories.d.ts.map +1 -1
- package/dist/src/presentation/web/hooks/use-notifications.stories.js +28 -38
- 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/_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/server/chunks/ssr/_9fd79ac5._.js +6 -0
- package/web/.next/server/chunks/ssr/_9fd79ac5._.js.map +1 -0
- 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/_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/_9fd79ac5._.js +6 -0
- 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 -6
- 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 +36 -29
- 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 +175 -428
- 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 +376 -0
- package/web/.next/standalone/src/presentation/web/hooks/use-notifications.stories.tsx +2 -30
- package/web/.next/standalone/src/presentation/web/hooks/use-notifications.ts +2 -36
- 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/{99381b9c479936d5.js → 0b409f42a82665ec.js} +1 -1
- package/web/.next/static/chunks/0e035a073754dde7.js +1 -0
- package/web/.next/static/chunks/109d6898972d7b78.js +1 -0
- package/web/.next/static/chunks/{1249c7c8745b972e.js → 5238c7e71fc81e4d.js} +1 -1
- package/web/.next/static/chunks/6b4899963367ea96.js +4 -0
- package/web/.next/static/chunks/71c9ecdceac1939f.css +2 -0
- package/web/.next/static/chunks/752846e217931a38.js +1 -0
- package/web/.next/static/chunks/9f9e6f5083600784.js +1 -0
- package/web/.next/static/chunks/ae00da647a8354b9.js +1 -0
- package/web/.next/static/chunks/cb41849bcfaa304e.js +1 -0
- package/web/.next/static/chunks/dc23a5773f5cd610.js +1 -0
- 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/0eeb65b8300fb7cd.js +0 -1
- package/web/.next/static/chunks/1c15ab8668ecf473.js +0 -1
- package/web/.next/static/chunks/2cf89d17ee7717a6.js +0 -4
- package/web/.next/static/chunks/4a8df922364596f6.js +0 -1
- package/web/.next/static/chunks/981b8df9ad045742.js +0 -1
- package/web/.next/static/chunks/9be617fc61b893f2.js +0 -1
- package/web/.next/static/chunks/a676f15c03fa66b9.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 → JYBKfZ0ROsNn817Id2TJ7}/_buildManifest.js +0 -0
- /package/web/.next/static/{nnR12fAsrSagAfeV6xj6D → JYBKfZ0ROsNn817Id2TJ7}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{nnR12fAsrSagAfeV6xj6D → JYBKfZ0ROsNn817Id2TJ7}/_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,16 @@ 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';
|
|
27
|
+
import type { FeatureEntry } from '@/lib/derive-graph';
|
|
25
28
|
|
|
26
29
|
export interface ControlCenterState {
|
|
27
30
|
nodes: CanvasNodeType[];
|
|
@@ -37,8 +40,17 @@ export interface ControlCenterState {
|
|
|
37
40
|
dataOverride?: Partial<FeatureNodeData>,
|
|
38
41
|
edgeType?: string
|
|
39
42
|
) => string;
|
|
43
|
+
/** Stable lookup: repositoryPath for a feature node. */
|
|
44
|
+
getFeatureRepositoryPath: (featureNodeId: string) => string | undefined;
|
|
45
|
+
/** Stable lookup: repository data by nodeId. */
|
|
46
|
+
getRepositoryData: (nodeId: string) => RepositoryNodeData | undefined;
|
|
47
|
+
/** Sync callbacks into derived node data (does not trigger re-render). */
|
|
48
|
+
setCallbacks: (callbacks: GraphCallbacks) => void;
|
|
40
49
|
}
|
|
41
50
|
|
|
51
|
+
/** Must match the message string emitted by the SSE route in agent-events/route.ts */
|
|
52
|
+
const METADATA_UPDATED_MESSAGE = 'Feature metadata updated';
|
|
53
|
+
|
|
42
54
|
let nextFeatureId = 0;
|
|
43
55
|
|
|
44
56
|
export function useControlCenterState(
|
|
@@ -46,37 +58,31 @@ export function useControlCenterState(
|
|
|
46
58
|
initialEdges: Edge[]
|
|
47
59
|
): ControlCenterState {
|
|
48
60
|
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
61
|
const deleteSound = useSoundAction('delete');
|
|
64
62
|
const createSound = useSoundAction('create');
|
|
65
63
|
|
|
66
|
-
|
|
64
|
+
const {
|
|
65
|
+
nodes,
|
|
66
|
+
edges,
|
|
67
|
+
reconcile,
|
|
68
|
+
updateFeature,
|
|
69
|
+
addPendingFeature,
|
|
70
|
+
removeFeature,
|
|
71
|
+
restoreFeature,
|
|
72
|
+
addRepository: addRepositoryToMap,
|
|
73
|
+
removeRepository,
|
|
74
|
+
replaceRepository,
|
|
75
|
+
getFeatureRepositoryPath,
|
|
76
|
+
getRepositoryData,
|
|
77
|
+
setCallbacks,
|
|
78
|
+
} = useGraphState(initialNodes, initialEdges);
|
|
79
|
+
|
|
80
|
+
// Sync server props into domain Maps when initialNodes/initialEdges change.
|
|
81
|
+
// Keyed by a stable string so we don't re-reconcile on every render.
|
|
67
82
|
const initialNodeKey = initialNodes
|
|
68
83
|
.map((n) => n.id)
|
|
69
84
|
.sort()
|
|
70
85
|
.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
86
|
const initialDataKey = initialNodes
|
|
81
87
|
.filter((n) => n.type === 'featureNode')
|
|
82
88
|
.map((n) => {
|
|
@@ -86,83 +92,31 @@ export function useControlCenterState(
|
|
|
86
92
|
.sort()
|
|
87
93
|
.join(',');
|
|
88
94
|
|
|
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
|
-
}
|
|
133
|
-
|
|
134
|
-
if (needsRelayout) {
|
|
135
|
-
const layoutResult = layoutWithDagre(merged, initialEdges, CANVAS_LAYOUT_DEFAULTS);
|
|
136
|
-
setEdges(layoutResult.edges);
|
|
137
|
-
return layoutResult.nodes;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return merged;
|
|
141
|
-
});
|
|
142
|
-
}, [initialNodeKey, initialNodes, initialEdges, setEdges]);
|
|
95
|
+
const prevReconcileKey = useRef('');
|
|
143
96
|
|
|
144
97
|
useEffect(() => {
|
|
145
|
-
|
|
146
|
-
|
|
98
|
+
const key = `${initialNodeKey}|${initialDataKey}`;
|
|
99
|
+
if (key !== prevReconcileKey.current) {
|
|
100
|
+
prevReconcileKey.current = key;
|
|
101
|
+
reconcile(initialNodes, initialEdges);
|
|
102
|
+
}
|
|
103
|
+
}, [initialNodeKey, initialDataKey, initialNodes, initialEdges, reconcile]);
|
|
104
|
+
|
|
105
|
+
// Track previous feature states for fallback notifications
|
|
106
|
+
const prevFeatureStatesRef = useRef<Map<string, FeatureNodeData['state']>>(new Map());
|
|
147
107
|
|
|
148
|
-
// Fallback notifications from server-prop state transitions.
|
|
149
|
-
// Fires only when SSE didn't already deliver the matching event for a feature.
|
|
150
108
|
const { events } = useAgentEventsContext();
|
|
151
109
|
|
|
152
110
|
useEffect(() => {
|
|
153
111
|
const prevStates = prevFeatureStatesRef.current;
|
|
154
|
-
|
|
155
112
|
for (const node of initialNodes) {
|
|
156
113
|
if (node.type !== 'featureNode') continue;
|
|
157
114
|
const data = node.data as FeatureNodeData;
|
|
158
115
|
const prev = prevStates.get(node.id);
|
|
159
|
-
|
|
160
116
|
if (prev !== undefined && prev !== data.state) {
|
|
161
|
-
// Check if SSE already delivered a matching event for this feature
|
|
162
117
|
const sseAlreadyCovered = events.some(
|
|
163
118
|
(e) => e.featureId === data.featureId && mapEventTypeToState(e.eventType) === data.state
|
|
164
119
|
);
|
|
165
|
-
|
|
166
120
|
if (!sseAlreadyCovered) {
|
|
167
121
|
if (data.state === 'done') {
|
|
168
122
|
toast.success(data.name, { description: 'Feature completed!' });
|
|
@@ -171,9 +125,7 @@ export function useControlCenterState(
|
|
|
171
125
|
description: 'Waiting for your approval',
|
|
172
126
|
action: {
|
|
173
127
|
label: 'Review',
|
|
174
|
-
onClick: () => {
|
|
175
|
-
router.push(`/feature/${data.featureId}`);
|
|
176
|
-
},
|
|
128
|
+
onClick: () => router.push(`/feature/${data.featureId}`),
|
|
177
129
|
},
|
|
178
130
|
});
|
|
179
131
|
} else if (data.state === 'error') {
|
|
@@ -181,186 +133,83 @@ export function useControlCenterState(
|
|
|
181
133
|
}
|
|
182
134
|
}
|
|
183
135
|
}
|
|
184
|
-
|
|
185
136
|
prevStates.set(node.id, data.state);
|
|
186
137
|
}
|
|
187
138
|
}, [initialDataKey, initialNodes, events, router]);
|
|
188
139
|
|
|
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.
|
|
140
|
+
// SSE effect: only state + lifecycle updates
|
|
192
141
|
const processedEventCountRef = useRef(0);
|
|
193
142
|
|
|
194
143
|
useEffect(() => {
|
|
195
144
|
if (events.length <= processedEventCountRef.current) return;
|
|
196
|
-
|
|
197
145
|
const newEvents = events.slice(processedEventCountRef.current);
|
|
198
146
|
processedEventCountRef.current = events.length;
|
|
199
147
|
|
|
148
|
+
// PhaseCompleted must NOT set state when WaitingApproval is in the same poll batch —
|
|
149
|
+
// they arrive together and PhaseCompleted → 'running' would revert 'action-required'.
|
|
150
|
+
// But when PhaseCompleted arrives WITHOUT WaitingApproval (e.g. after approval), we
|
|
151
|
+
// must allow it to update state as a fallback for any missed AgentStarted event.
|
|
152
|
+
const waitingApprovalFeatures = new Set(
|
|
153
|
+
newEvents
|
|
154
|
+
.filter((e) => e.eventType === NotificationEventType.WaitingApproval)
|
|
155
|
+
.map((e) => e.featureId)
|
|
156
|
+
);
|
|
157
|
+
|
|
200
158
|
for (const event of newEvents) {
|
|
201
|
-
const newState =
|
|
159
|
+
const newState =
|
|
160
|
+
event.eventType === NotificationEventType.PhaseCompleted &&
|
|
161
|
+
waitingApprovalFeatures.has(event.featureId)
|
|
162
|
+
? undefined
|
|
163
|
+
: mapEventTypeToState(event.eventType);
|
|
202
164
|
const newLifecycle = mapPhaseNameToLifecycle(event.phaseName);
|
|
203
165
|
|
|
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
|
-
);
|
|
166
|
+
const nodeId = `feat-${event.featureId}`;
|
|
167
|
+
if (newState !== undefined || newLifecycle !== undefined) {
|
|
168
|
+
updateFeature(nodeId, {
|
|
169
|
+
...(newState !== undefined && { state: newState }),
|
|
170
|
+
...(newLifecycle !== undefined && { lifecycle: newLifecycle }),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
221
173
|
}
|
|
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;
|
|
174
|
+
}, [events, updateFeature]);
|
|
239
175
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
});
|
|
176
|
+
// Separate effect: fetch metadata (name + description) when SSE reports it changed
|
|
177
|
+
const metadataFetchedRef = useRef<Set<string>>(new Set());
|
|
178
|
+
const processedMetadataCountRef = useRef(0);
|
|
277
179
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
});
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
if (events.length <= processedMetadataCountRef.current) return;
|
|
182
|
+
const newEvents = events.slice(processedMetadataCountRef.current);
|
|
183
|
+
processedMetadataCountRef.current = events.length;
|
|
292
184
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const trueNewNodes = newServerNodes.filter((n) => !replacedIds.has(n.id));
|
|
304
|
-
if (trueNewNodes.length > 0) {
|
|
305
|
-
changed = true;
|
|
306
|
-
return [...merged, ...trueNewNodes];
|
|
307
|
-
}
|
|
185
|
+
for (const event of newEvents) {
|
|
186
|
+
if (event.message !== METADATA_UPDATED_MESSAGE) 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;
|
|
335
|
-
|
|
336
|
-
setNodes((currentNodes) => {
|
|
337
|
-
const sourceNode = currentNodes.find((n) => n.id === connection.source);
|
|
338
|
-
if (sourceNode?.type !== 'repositoryNode') return currentNodes;
|
|
201
|
+
}, [events, updateFeature]);
|
|
339
202
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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,114 @@ 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
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
)
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
remainingEdges,
|
|
509
|
-
CANVAS_LAYOUT_DEFAULTS
|
|
510
|
-
);
|
|
511
|
-
setEdges(layoutResult.edges);
|
|
512
|
-
return layoutResult.nodes;
|
|
513
|
-
});
|
|
287
|
+
const prevRepoData = getRepositoryData(repoNodeId);
|
|
288
|
+
const childSnapshots = new Map<string, FeatureEntry>();
|
|
289
|
+
for (const childId of childFeatureIds) {
|
|
290
|
+
const childNode = nodes.find((n) => n.id === childId);
|
|
291
|
+
if (childNode) {
|
|
292
|
+
childSnapshots.set(childId, { nodeId: childId, data: childNode.data as FeatureNodeData });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const rollback = () => {
|
|
297
|
+
if (prevRepoData) addRepositoryToMap(repoNodeId, prevRepoData);
|
|
298
|
+
for (const [childId, entry] of childSnapshots) {
|
|
299
|
+
restoreFeature(childId, entry);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// Optimistic: remove repo + children
|
|
304
|
+
removeRepository(repoNodeId);
|
|
305
|
+
for (const childId of childFeatureIds) {
|
|
306
|
+
removeFeature(childId);
|
|
307
|
+
}
|
|
514
308
|
|
|
515
309
|
try {
|
|
516
310
|
const result = await deleteRepository(repositoryId);
|
|
517
|
-
|
|
518
311
|
if (!result.success) {
|
|
519
312
|
toast.error(result.error ?? 'Failed to remove repository');
|
|
520
|
-
|
|
521
|
-
setEdges(prevEdges);
|
|
313
|
+
rollback();
|
|
522
314
|
return;
|
|
523
315
|
}
|
|
524
|
-
|
|
525
316
|
deleteSound.play();
|
|
526
317
|
toast.success('Repository removed');
|
|
527
318
|
} catch {
|
|
528
319
|
toast.error('Failed to remove repository');
|
|
529
|
-
|
|
530
|
-
setEdges(prevEdges);
|
|
320
|
+
rollback();
|
|
531
321
|
}
|
|
532
322
|
},
|
|
533
|
-
[
|
|
323
|
+
[
|
|
324
|
+
edges,
|
|
325
|
+
nodes,
|
|
326
|
+
deleteSound,
|
|
327
|
+
removeRepository,
|
|
328
|
+
removeFeature,
|
|
329
|
+
addRepositoryToMap,
|
|
330
|
+
restoreFeature,
|
|
331
|
+
getRepositoryData,
|
|
332
|
+
]
|
|
534
333
|
);
|
|
535
334
|
|
|
536
335
|
const handleLayout = useCallback(
|
|
537
336
|
(direction: LayoutDirection) => {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
});
|
|
543
|
-
setEdges(result.edges);
|
|
544
|
-
return result.nodes;
|
|
545
|
-
});
|
|
337
|
+
// Layout is applied via reconcile on next server prop update.
|
|
338
|
+
// For immediate re-layout, we apply dagre and trigger a reconcile-like update.
|
|
339
|
+
const result = layoutWithDagre(nodes, edges, { ...CANVAS_LAYOUT_DEFAULTS, direction });
|
|
340
|
+
reconcile(result.nodes, result.edges);
|
|
546
341
|
},
|
|
547
|
-
[
|
|
342
|
+
[nodes, edges, reconcile]
|
|
548
343
|
);
|
|
549
344
|
|
|
550
345
|
const handleAddRepository = useCallback(
|
|
@@ -556,81 +351,30 @@ export function useControlCenterState(
|
|
|
556
351
|
.split(/[\\/]/)
|
|
557
352
|
.pop() ?? path;
|
|
558
353
|
|
|
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
|
|
354
|
+
addRepositoryToMap(tempId, { name: repoName, repositoryPath: path, id: tempId });
|
|
355
|
+
|
|
575
356
|
addRepository({ path, name: repoName })
|
|
576
357
|
.then((result) => {
|
|
577
358
|
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
|
-
});
|
|
359
|
+
removeRepository(tempId);
|
|
589
360
|
toast.error(result.error);
|
|
590
361
|
return;
|
|
591
362
|
}
|
|
592
|
-
|
|
593
|
-
// Replace temp ID with real repository ID
|
|
594
363
|
const repo = result.repository!;
|
|
595
364
|
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
|
-
|
|
365
|
+
replaceRepository(tempId, realId, {
|
|
366
|
+
name: repo.name,
|
|
367
|
+
repositoryPath: repo.path,
|
|
368
|
+
id: repo.id,
|
|
369
|
+
});
|
|
616
370
|
createSound.play();
|
|
617
371
|
})
|
|
618
372
|
.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
|
-
});
|
|
373
|
+
removeRepository(tempId);
|
|
630
374
|
toast.error('Failed to add repository');
|
|
631
375
|
});
|
|
632
376
|
},
|
|
633
|
-
[
|
|
377
|
+
[addRepositoryToMap, removeRepository, replaceRepository, createSound]
|
|
634
378
|
);
|
|
635
379
|
|
|
636
380
|
return {
|
|
@@ -643,5 +387,8 @@ export function useControlCenterState(
|
|
|
643
387
|
handleDeleteFeature,
|
|
644
388
|
handleDeleteRepository,
|
|
645
389
|
createFeatureNode,
|
|
390
|
+
getFeatureRepositoryPath,
|
|
391
|
+
getRepositoryData,
|
|
392
|
+
setCallbacks,
|
|
646
393
|
};
|
|
647
394
|
}
|