@shepai/cli 1.72.9-pr219.08421ee → 1.72.9-pr220.68aacbc
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/packages/core/src/infrastructure/services/notifications/notification.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/notifications/notification.service.js +2 -4
- 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 +16 -17
- package/dist/src/presentation/web/app/build-feature-node-data.d.ts.map +1 -1
- package/dist/src/presentation/web/app/build-feature-node-data.js +2 -26
- package/dist/src/presentation/web/app/build-graph-nodes.d.ts.map +1 -1
- package/dist/src/presentation/web/app/build-graph-nodes.js +2 -26
- 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 -18
- package/dist/src/presentation/web/components/common/control-center-drawer/drawer-view.d.ts +2 -0
- package/dist/src/presentation/web/components/common/control-center-drawer/drawer-view.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/control-center-drawer/drawer-view.js +11 -11
- 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 +69 -141
- package/dist/src/presentation/web/components/common/control-center-drawer/index.d.ts +0 -1
- package/dist/src/presentation/web/components/common/control-center-drawer/index.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/control-center-drawer/index.js +0 -1
- package/dist/src/presentation/web/components/common/control-center-drawer/use-artifact-fetch.d.ts +13 -0
- package/dist/src/presentation/web/components/common/control-center-drawer/use-artifact-fetch.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/control-center-drawer/use-artifact-fetch.js +46 -0
- package/dist/src/presentation/web/components/common/feature-drawer/use-feature-actions.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-drawer/use-feature-actions.js +7 -10
- package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts +25 -0
- package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.js +45 -2
- package/dist/src/presentation/web/components/common/index.d.ts +2 -3
- package/dist/src/presentation/web/components/common/index.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/index.js +2 -3
- package/dist/src/presentation/web/components/common/repository-node/use-repository-actions.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/repository-node/use-repository-actions.js +14 -39
- 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 +31 -14
- 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 +158 -396
- 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 +11 -54
- 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/components/ui/alert-dialog.d.ts.map +1 -1
- package/dist/src/presentation/web/components/ui/alert-dialog.js +2 -2
- package/dist/src/presentation/web/components/ui/dialog.d.ts.map +1 -1
- package/dist/src/presentation/web/components/ui/dialog.js +2 -2
- package/dist/src/presentation/web/hooks/agent-events-provider.d.ts.map +1 -1
- package/dist/src/presentation/web/hooks/agent-events-provider.js +3 -2
- package/dist/src/presentation/web/hooks/use-agent-events.d.ts.map +1 -1
- package/dist/src/presentation/web/hooks/use-agent-events.js +16 -5
- package/dist/src/presentation/web/hooks/use-deploy-action.d.ts.map +1 -1
- package/dist/src/presentation/web/hooks/use-deploy-action.js +8 -3
- 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]__2018e25f._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__2018e25f._.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/_07c2a32a._.js +1 -1
- package/web/.next/server/chunks/ssr/_07c2a32a._.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/_ad111e34._.js +6 -0
- package/web/.next/server/chunks/ssr/_ad111e34._.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]__2018e25f._.js +1 -1
- 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/_07c2a32a._.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/_ad111e34._.js +6 -0
- package/web/.next/{server/chunks/ssr/src_presentation_web_7188a290._.js → standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_2b91e4ec._.js} +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{src_presentation_web_7188a290._.js → 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 +18 -19
- package/web/.next/standalone/src/presentation/web/app/build-feature-node-data.ts +3 -30
- package/web/.next/standalone/src/presentation/web/app/build-graph-nodes.ts +3 -30
- 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 -21
- package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/drawer-view.ts +12 -13
- package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.tsx +99 -137
- package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/index.ts +0 -1
- package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/use-artifact-fetch.ts +56 -0
- package/web/.next/standalone/src/presentation/web/components/common/feature-drawer/use-feature-actions.ts +6 -7
- package/web/.next/standalone/src/presentation/web/components/common/feature-node/derive-feature-state.ts +63 -2
- package/web/.next/standalone/src/presentation/web/components/common/index.ts +2 -12
- package/web/.next/standalone/src/presentation/web/components/common/repository-node/use-repository-actions.ts +29 -54
- 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 +43 -36
- 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 +202 -495
- 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 -76
- package/web/.next/standalone/src/presentation/web/components/layouts/app-shell/app-shell.tsx +1 -1
- package/web/.next/standalone/src/presentation/web/components/ui/alert-dialog.tsx +2 -5
- package/web/.next/standalone/src/presentation/web/components/ui/dialog.tsx +2 -5
- package/web/.next/standalone/src/presentation/web/hooks/agent-events-provider.tsx +7 -2
- package/web/.next/standalone/src/presentation/web/hooks/use-agent-events.ts +17 -5
- package/web/.next/standalone/src/presentation/web/hooks/use-deploy-action.ts +11 -3
- 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/0ffccd7be73305c8.css +2 -0
- package/web/.next/static/chunks/3e25d4932e1dd406.js +1 -0
- package/web/.next/static/chunks/3f205fd6adbfd20a.js +7 -0
- package/web/.next/static/chunks/68ded4375df20ac5.js +1 -0
- package/web/.next/static/chunks/{1d281f045b623a45.js → 6c13863b031221ea.js} +1 -1
- package/web/.next/static/chunks/8ecedb1a498fb468.js +1 -0
- package/web/.next/static/chunks/95682021c08d3636.js +1 -0
- package/web/.next/static/chunks/ab808dc1cdf66a9c.js +4 -0
- package/web/.next/static/chunks/cb41849bcfaa304e.js +1 -0
- package/web/.next/static/chunks/{cd9099d3eae9dd04.js → ee1a546ece509b1d.js} +1 -1
- package/web/.next/static/chunks/f200e62f7185c8da.js +1 -0
- package/web/.next/static/chunks/fc0f263fd83b43f9.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/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.d.ts +0 -12
- package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.d.ts.map +0 -1
- package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.js +0 -337
- package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.stories.d.ts +0 -33
- package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.stories.d.ts.map +0 -1
- package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.stories.js +0 -214
- package/dist/src/presentation/web/components/common/notification-permission-banner/index.d.ts +0 -2
- package/dist/src/presentation/web/components/common/notification-permission-banner/index.d.ts.map +0 -1
- package/dist/src/presentation/web/components/common/notification-permission-banner/index.js +0 -1
- package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.d.ts +0 -2
- package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.d.ts.map +0 -1
- package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.js +0 -33
- package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.stories.d.ts +0 -6
- package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.stories.d.ts.map +0 -1
- package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.stories.js +0 -24
- 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/_4469c284._.js +0 -6
- package/web/.next/server/chunks/ssr/_4469c284._.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/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/_4469c284._.js +0 -6
- 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/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/standalone/src/presentation/web/components/common/control-center-drawer/control-center-drawer.stories.tsx +0 -293
- package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/control-center-drawer.tsx +0 -745
- package/web/.next/standalone/src/presentation/web/components/common/notification-permission-banner/index.ts +0 -1
- package/web/.next/standalone/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.stories.tsx +0 -52
- package/web/.next/standalone/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.tsx +0 -55
- package/web/.next/static/chunks/073b31d7a4974504.js +0 -1
- package/web/.next/static/chunks/0c54314c8c9c9739.js +0 -7
- package/web/.next/static/chunks/0e588a3a4dd7d1c0.js +0 -1
- package/web/.next/static/chunks/2433994d844b7d39.js +0 -1
- package/web/.next/static/chunks/3e041a9008bac888.js +0 -1
- package/web/.next/static/chunks/798624a651d68855.js +0 -4
- package/web/.next/static/chunks/977b9d2b9ef19f7d.js +0 -1
- package/web/.next/static/chunks/9be617fc61b893f2.js +0 -1
- package/web/.next/static/chunks/a18b0561724d11d4.js +0 -1
- package/web/.next/static/chunks/acce8a15d6896dea.css +0 -2
- /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/{pzwrJGWI__eUhJsxxW2S- → wSVxDvqh-ObIC1qlefqgl}/_buildManifest.js +0 -0
- /package/web/.next/static/{pzwrJGWI__eUhJsxxW2S- → wSVxDvqh-ObIC1qlefqgl}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{pzwrJGWI__eUhJsxxW2S- → wSVxDvqh-ObIC1qlefqgl}/_ssgManifest.js +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useCallback, useEffect, useMemo, 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
|
+
resolveSseEventUpdates,
|
|
24
24
|
} from '@/components/common/feature-node/derive-feature-state';
|
|
25
|
+
import { useGraphState, type GraphCallbacks } from '@/hooks/use-graph-state';
|
|
26
|
+
import type { FeatureEntry } from '@/lib/derive-graph';
|
|
25
27
|
|
|
26
28
|
export interface ControlCenterState {
|
|
27
29
|
nodes: CanvasNodeType[];
|
|
@@ -37,8 +39,17 @@ 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
|
|
|
50
|
+
/** Must match the message string emitted by the SSE route in agent-events/route.ts */
|
|
51
|
+
const METADATA_UPDATED_MESSAGE = 'Feature metadata updated';
|
|
52
|
+
|
|
42
53
|
let nextFeatureId = 0;
|
|
43
54
|
|
|
44
55
|
export function useControlCenterState(
|
|
@@ -46,123 +57,81 @@ export function useControlCenterState(
|
|
|
46
57
|
initialEdges: Edge[]
|
|
47
58
|
): ControlCenterState {
|
|
48
59
|
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
60
|
const deleteSound = useSoundAction('delete');
|
|
64
61
|
const createSound = useSoundAction('create');
|
|
65
62
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
})
|
|
86
|
-
.sort()
|
|
87
|
-
.join(',');
|
|
88
|
-
|
|
89
|
-
// Sync server props into local state — keyed by derived strings (initialNodeKey/initialEdgeKey)
|
|
90
|
-
// to avoid infinite re-renders from unstable array references.
|
|
63
|
+
const {
|
|
64
|
+
nodes,
|
|
65
|
+
edges,
|
|
66
|
+
reconcile,
|
|
67
|
+
updateFeature,
|
|
68
|
+
addPendingFeature,
|
|
69
|
+
removeFeature,
|
|
70
|
+
restoreFeature,
|
|
71
|
+
addRepository: addRepositoryToMap,
|
|
72
|
+
removeRepository,
|
|
73
|
+
replaceRepository,
|
|
74
|
+
getFeatureRepositoryPath,
|
|
75
|
+
getRepositoryData,
|
|
76
|
+
setCallbacks,
|
|
77
|
+
} = useGraphState(initialNodes, initialEdges);
|
|
78
|
+
|
|
79
|
+
// Refs for stable access to latest nodes/edges without callback recreation
|
|
80
|
+
const nodesRef = useRef(nodes);
|
|
81
|
+
const edgesRef = useRef(edges);
|
|
91
82
|
useEffect(() => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
}
|
|
83
|
+
nodesRef.current = nodes;
|
|
84
|
+
edgesRef.current = edges;
|
|
85
|
+
}, [nodes, edges]);
|
|
86
|
+
|
|
87
|
+
// Sync server props into domain Maps when initialNodes/initialEdges change.
|
|
88
|
+
// Keyed by a stable string so we don't re-reconcile on every render.
|
|
89
|
+
const initialNodeKey = useMemo(
|
|
90
|
+
() =>
|
|
91
|
+
initialNodes
|
|
92
|
+
.map((n) => n.id)
|
|
93
|
+
.sort()
|
|
94
|
+
.join(','),
|
|
95
|
+
[initialNodes]
|
|
96
|
+
);
|
|
97
|
+
const initialDataKey = useMemo(
|
|
98
|
+
() =>
|
|
99
|
+
initialNodes
|
|
100
|
+
.filter((n) => n.type === 'featureNode')
|
|
101
|
+
.map((n) => {
|
|
102
|
+
const d = n.data as FeatureNodeData;
|
|
103
|
+
return `${n.id}:${d.state}:${d.lifecycle}`;
|
|
104
|
+
})
|
|
105
|
+
.sort()
|
|
106
|
+
.join(','),
|
|
107
|
+
[initialNodes]
|
|
108
|
+
);
|
|
139
109
|
|
|
140
|
-
|
|
141
|
-
});
|
|
142
|
-
}, [initialNodeKey, initialNodes, initialEdges, setEdges]);
|
|
110
|
+
const prevReconcileKey = useRef('');
|
|
143
111
|
|
|
144
112
|
useEffect(() => {
|
|
145
|
-
|
|
146
|
-
|
|
113
|
+
const key = `${initialNodeKey}|${initialDataKey}`;
|
|
114
|
+
if (key !== prevReconcileKey.current) {
|
|
115
|
+
prevReconcileKey.current = key;
|
|
116
|
+
reconcile(initialNodes, initialEdges);
|
|
117
|
+
}
|
|
118
|
+
}, [initialNodeKey, initialDataKey, initialNodes, initialEdges, reconcile]);
|
|
119
|
+
|
|
120
|
+
// Track previous feature states for fallback notifications
|
|
121
|
+
const prevFeatureStatesRef = useRef<Map<string, FeatureNodeData['state']>>(new Map());
|
|
147
122
|
|
|
148
|
-
// Fallback notifications from server-prop state transitions.
|
|
149
|
-
// Fires only when SSE didn't already deliver the matching event for a feature.
|
|
150
123
|
const { events } = useAgentEventsContext();
|
|
151
124
|
|
|
152
125
|
useEffect(() => {
|
|
153
126
|
const prevStates = prevFeatureStatesRef.current;
|
|
154
|
-
|
|
155
127
|
for (const node of initialNodes) {
|
|
156
128
|
if (node.type !== 'featureNode') continue;
|
|
157
129
|
const data = node.data as FeatureNodeData;
|
|
158
130
|
const prev = prevStates.get(node.id);
|
|
159
|
-
|
|
160
131
|
if (prev !== undefined && prev !== data.state) {
|
|
161
|
-
// Check if SSE already delivered a matching event for this feature
|
|
162
132
|
const sseAlreadyCovered = events.some(
|
|
163
133
|
(e) => e.featureId === data.featureId && mapEventTypeToState(e.eventType) === data.state
|
|
164
134
|
);
|
|
165
|
-
|
|
166
135
|
if (!sseAlreadyCovered) {
|
|
167
136
|
if (data.state === 'done') {
|
|
168
137
|
toast.success(data.name, { description: 'Feature completed!' });
|
|
@@ -171,9 +140,7 @@ export function useControlCenterState(
|
|
|
171
140
|
description: 'Waiting for your approval',
|
|
172
141
|
action: {
|
|
173
142
|
label: 'Review',
|
|
174
|
-
onClick: () => {
|
|
175
|
-
router.push(`/feature/${data.featureId}`);
|
|
176
|
-
},
|
|
143
|
+
onClick: () => router.push(`/feature/${data.featureId}`),
|
|
177
144
|
},
|
|
178
145
|
});
|
|
179
146
|
} else if (data.state === 'error') {
|
|
@@ -181,242 +148,83 @@ export function useControlCenterState(
|
|
|
181
148
|
}
|
|
182
149
|
}
|
|
183
150
|
}
|
|
184
|
-
|
|
185
151
|
prevStates.set(node.id, data.state);
|
|
186
152
|
}
|
|
187
153
|
}, [initialDataKey, initialNodes, events, router]);
|
|
188
154
|
|
|
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.
|
|
155
|
+
// SSE effect: only state + lifecycle updates
|
|
192
156
|
const processedEventCountRef = useRef(0);
|
|
193
157
|
|
|
194
|
-
// Track known feature IDs so we can detect when SSE reports features not yet on the canvas.
|
|
195
|
-
// Initialised from initialNodes to avoid a race between first render and SSE arrival.
|
|
196
|
-
const knownFeatureIdsRef = useRef<Set<string>>(
|
|
197
|
-
new Set(
|
|
198
|
-
initialNodes
|
|
199
|
-
.filter((n) => n.type === 'featureNode')
|
|
200
|
-
.map((n) => (n.data as FeatureNodeData).featureId)
|
|
201
|
-
)
|
|
202
|
-
);
|
|
203
|
-
// Keep ref in sync as nodes change (new nodes added, temp nodes replaced, etc.)
|
|
204
|
-
useEffect(() => {
|
|
205
|
-
knownFeatureIdsRef.current = new Set(
|
|
206
|
-
nodes
|
|
207
|
-
.filter((n) => n.type === 'featureNode')
|
|
208
|
-
.map((n) => (n.data as FeatureNodeData).featureId)
|
|
209
|
-
);
|
|
210
|
-
}, [nodes]);
|
|
211
|
-
|
|
212
|
-
// Guard against concurrent fetchGraphData calls triggered by unknown-feature detection
|
|
213
|
-
const fetchingGraphRef = useRef(false);
|
|
214
|
-
|
|
215
158
|
useEffect(() => {
|
|
159
|
+
// Clamp cursor if events were pruned
|
|
160
|
+
if (processedEventCountRef.current > events.length) {
|
|
161
|
+
processedEventCountRef.current = 0;
|
|
162
|
+
}
|
|
216
163
|
if (events.length <= processedEventCountRef.current) return;
|
|
217
|
-
|
|
218
164
|
const newEvents = events.slice(processedEventCountRef.current);
|
|
219
165
|
processedEventCountRef.current = events.length;
|
|
220
166
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
hasUnknownFeature = true;
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const newState = mapEventTypeToState(event.eventType);
|
|
231
|
-
const newLifecycle = mapPhaseNameToLifecycle(event.phaseName);
|
|
232
|
-
|
|
233
|
-
// Targeted node update — only clone the matched node
|
|
234
|
-
setNodes((prev) =>
|
|
235
|
-
prev.map((node) => {
|
|
236
|
-
if (node.type !== 'featureNode') return node;
|
|
237
|
-
const data = node.data as FeatureNodeData;
|
|
238
|
-
if (data.featureId !== event.featureId) return node;
|
|
239
|
-
|
|
240
|
-
return {
|
|
241
|
-
...node,
|
|
242
|
-
data: {
|
|
243
|
-
...data,
|
|
244
|
-
state: newState,
|
|
245
|
-
...(newLifecycle !== undefined && { lifecycle: newLifecycle }),
|
|
246
|
-
},
|
|
247
|
-
};
|
|
248
|
-
})
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// SSE reported features not on the canvas — fetch full graph data so new
|
|
253
|
-
// repository nodes, feature nodes, and edges all appear together.
|
|
254
|
-
if (hasUnknownFeature && !fetchingGraphRef.current) {
|
|
255
|
-
fetchingGraphRef.current = true;
|
|
256
|
-
fetchGraphData()
|
|
257
|
-
.then(({ nodes: serverNodes, edges: serverEdges }) => {
|
|
258
|
-
setNodes((currentNodes) => {
|
|
259
|
-
// Preserve positions of nodes already on the canvas
|
|
260
|
-
const currentById = new Map(currentNodes.map((n) => [n.id, n]));
|
|
261
|
-
const merged = serverNodes.map((serverNode) => {
|
|
262
|
-
const existing = currentById.get(serverNode.id);
|
|
263
|
-
return existing ? { ...serverNode, position: existing.position } : serverNode;
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
const layoutResult = layoutWithDagre(merged, serverEdges, CANVAS_LAYOUT_DEFAULTS);
|
|
267
|
-
setEdges(layoutResult.edges);
|
|
268
|
-
return layoutResult.nodes;
|
|
269
|
-
});
|
|
270
|
-
})
|
|
271
|
-
.catch(() => {
|
|
272
|
-
// Silently ignore — next SSE event or poll will retry
|
|
273
|
-
})
|
|
274
|
-
.finally(() => {
|
|
275
|
-
fetchingGraphRef.current = false;
|
|
167
|
+
for (const { featureId, state, lifecycle } of resolveSseEventUpdates(newEvents)) {
|
|
168
|
+
if (state !== undefined || lifecycle !== undefined) {
|
|
169
|
+
updateFeature(`feat-${featureId}`, {
|
|
170
|
+
...(state !== undefined && { state }),
|
|
171
|
+
...(lifecycle !== undefined && { lifecycle }),
|
|
276
172
|
});
|
|
173
|
+
}
|
|
277
174
|
}
|
|
278
|
-
}, [events,
|
|
279
|
-
|
|
280
|
-
// Lightweight server-action polling fallback: fetches fresh graph data every 5s
|
|
281
|
-
// when any feature is in an active state. This avoids full router.refresh() while
|
|
282
|
-
// ensuring the UI stays in sync even if SSE events are missed.
|
|
283
|
-
//
|
|
284
|
-
// We derive a stable boolean so the interval isn't torn down on every node change.
|
|
285
|
-
const hasActiveFeature = nodes.some((n) => {
|
|
286
|
-
if (n.type !== 'featureNode') return false;
|
|
287
|
-
const data = n.data as FeatureNodeData;
|
|
288
|
-
return (
|
|
289
|
-
data.state === 'running' || data.state === 'action-required' || data.state === 'creating'
|
|
290
|
-
);
|
|
291
|
-
});
|
|
175
|
+
}, [events, updateFeature]);
|
|
292
176
|
|
|
177
|
+
// Listen for optimistic approval events from the drawer (fires before SSE arrives)
|
|
293
178
|
useEffect(() => {
|
|
294
|
-
|
|
179
|
+
const handler = (e: Event) => {
|
|
180
|
+
const { featureId } = (e as CustomEvent<{ featureId: string }>).detail;
|
|
181
|
+
updateFeature(`feat-${featureId}`, { state: 'running' });
|
|
182
|
+
};
|
|
183
|
+
window.addEventListener('shep:feature-approved', handler);
|
|
184
|
+
return () => window.removeEventListener('shep:feature-approved', handler);
|
|
185
|
+
}, [updateFeature]);
|
|
186
|
+
|
|
187
|
+
// Separate effect: fetch metadata (name + description) when SSE reports it changed
|
|
188
|
+
const metadataFetchedRef = useRef<Set<string>>(new Set());
|
|
189
|
+
const processedMetadataCountRef = useRef(0);
|
|
295
190
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const serverNode = serverById.get(node.id);
|
|
305
|
-
if (!serverNode || node.type !== 'featureNode') return node;
|
|
306
|
-
const currentData = node.data as FeatureNodeData;
|
|
307
|
-
const serverData = serverNode.data as FeatureNodeData;
|
|
308
|
-
|
|
309
|
-
// Only update if server data has different state or lifecycle
|
|
310
|
-
if (
|
|
311
|
-
currentData.state === serverData.state &&
|
|
312
|
-
currentData.lifecycle === serverData.lifecycle
|
|
313
|
-
) {
|
|
314
|
-
return node;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
changed = true;
|
|
318
|
-
return {
|
|
319
|
-
...node,
|
|
320
|
-
data: {
|
|
321
|
-
...currentData,
|
|
322
|
-
state: serverData.state,
|
|
323
|
-
lifecycle: serverData.lifecycle,
|
|
324
|
-
progress: serverData.progress,
|
|
325
|
-
...(serverData.errorMessage && { errorMessage: serverData.errorMessage }),
|
|
326
|
-
...(serverData.runtime && { runtime: serverData.runtime }),
|
|
327
|
-
...(serverData.agentType && { agentType: serverData.agentType }),
|
|
328
|
-
...(serverData.pr && { pr: serverData.pr }),
|
|
329
|
-
...(serverData.blockedBy && { blockedBy: serverData.blockedBy }),
|
|
330
|
-
},
|
|
331
|
-
};
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
// Find new server nodes not yet on the client
|
|
335
|
-
const clientIds = new Set(currentNodes.map((n) => n.id));
|
|
336
|
-
const newServerNodes = serverNodes.filter((n) => !clientIds.has(n.id));
|
|
337
|
-
|
|
338
|
-
// Replace optimistic "creating" temp nodes with their real server
|
|
339
|
-
// counterparts. Temp nodes have IDs like "feature-<ts>" while real
|
|
340
|
-
// nodes use "feat-<uuid>". Match by repositoryPath + name.
|
|
341
|
-
if (newServerNodes.length > 0) {
|
|
342
|
-
const creatingIndices: number[] = [];
|
|
343
|
-
merged.forEach((n, i) => {
|
|
344
|
-
if (n.type === 'featureNode' && (n.data as FeatureNodeData).state === 'creating') {
|
|
345
|
-
creatingIndices.push(i);
|
|
346
|
-
}
|
|
347
|
-
});
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
// Clamp cursor if events were pruned
|
|
193
|
+
if (processedMetadataCountRef.current > events.length) {
|
|
194
|
+
processedMetadataCountRef.current = 0;
|
|
195
|
+
}
|
|
196
|
+
if (events.length <= processedMetadataCountRef.current) return;
|
|
197
|
+
const newEvents = events.slice(processedMetadataCountRef.current);
|
|
198
|
+
processedMetadataCountRef.current = events.length;
|
|
348
199
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const trueNewNodes = newServerNodes.filter((n) => !replacedIds.has(n.id));
|
|
360
|
-
if (trueNewNodes.length > 0) {
|
|
361
|
-
changed = true;
|
|
362
|
-
return [...merged, ...trueNewNodes];
|
|
363
|
-
}
|
|
200
|
+
for (const event of newEvents) {
|
|
201
|
+
if (event.message !== METADATA_UPDATED_MESSAGE) continue;
|
|
202
|
+
if (metadataFetchedRef.current.has(event.featureId)) continue;
|
|
203
|
+
metadataFetchedRef.current.add(event.featureId);
|
|
204
|
+
|
|
205
|
+
const nodeId = `feat-${event.featureId}`;
|
|
206
|
+
getFeatureMetadata(event.featureId)
|
|
207
|
+
.then((meta) => {
|
|
208
|
+
if (meta) {
|
|
209
|
+
updateFeature(nodeId, { name: meta.name, description: meta.description });
|
|
364
210
|
}
|
|
365
|
-
|
|
366
|
-
|
|
211
|
+
})
|
|
212
|
+
.catch(() => {
|
|
213
|
+
// Silent: metadata fetch failure is non-critical
|
|
367
214
|
});
|
|
368
|
-
} catch {
|
|
369
|
-
// Silently ignore polling errors — SSE is the primary mechanism
|
|
370
|
-
}
|
|
371
|
-
}, 5_000);
|
|
372
|
-
|
|
373
|
-
return () => clearInterval(interval);
|
|
374
|
-
}, [hasActiveFeature]);
|
|
375
|
-
|
|
376
|
-
const onNodesChange = useCallback((changes: NodeChange<CanvasNodeType>[]) => {
|
|
377
|
-
// Filter out 'replace' changes — in controlled mode these come from
|
|
378
|
-
// React Flow's StoreUpdater syncing the nodes prop back into state.
|
|
379
|
-
// Applying them creates a circular update loop (nodes → enrichedNodes →
|
|
380
|
-
// StoreUpdater → onNodesChange → setNodes → nodes → …) that both causes
|
|
381
|
-
// excessive re-renders and overwrites SSE-driven state changes with stale data.
|
|
382
|
-
const applicable = changes.filter((c) => c.type !== 'replace');
|
|
383
|
-
if (applicable.length > 0) {
|
|
384
|
-
setNodes((ns) => applyNodeChanges(applicable, ns));
|
|
385
215
|
}
|
|
386
|
-
}, []);
|
|
387
|
-
|
|
388
|
-
const handleConnect = useCallback(
|
|
389
|
-
(connection: Connection) => {
|
|
390
|
-
if (!connection.source || !connection.target) return;
|
|
216
|
+
}, [events, updateFeature]);
|
|
391
217
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
const targetAlreadyHasRepo = currentEdges.some((e) => {
|
|
399
|
-
const edgeSource = currentNodes.find((n) => n.id === e.source);
|
|
400
|
-
return edgeSource?.type === 'repositoryNode' && e.target === connection.target;
|
|
401
|
-
});
|
|
402
|
-
if (targetAlreadyHasRepo) return currentEdges;
|
|
403
|
-
|
|
404
|
-
return [
|
|
405
|
-
...currentEdges,
|
|
406
|
-
{
|
|
407
|
-
id: `edge-${connection.source}-${connection.target}`,
|
|
408
|
-
source: connection.source,
|
|
409
|
-
target: connection.target,
|
|
410
|
-
style: { strokeDasharray: '5 5' },
|
|
411
|
-
},
|
|
412
|
-
];
|
|
413
|
-
});
|
|
218
|
+
// onNodesChange is a no-op: nodes are derived from domain Maps.
|
|
219
|
+
// Since nodesDraggable=false and elementsSelectable=false, only React Flow's
|
|
220
|
+
// internal replace/dimensions changes come through — we ignore them all.
|
|
221
|
+
const onNodesChange = useCallback((_changes: NodeChange<CanvasNodeType>[]) => {
|
|
222
|
+
// Intentional no-op: domain Maps are the source of truth.
|
|
223
|
+
}, []);
|
|
414
224
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
[setEdges]
|
|
419
|
-
);
|
|
225
|
+
const handleConnect = useCallback((_connection: Connection) => {
|
|
226
|
+
// Connections are managed via domain operations, not direct edge manipulation.
|
|
227
|
+
}, []);
|
|
420
228
|
|
|
421
229
|
const createFeatureNode = useCallback(
|
|
422
230
|
(
|
|
@@ -424,11 +232,14 @@ export function useControlCenterState(
|
|
|
424
232
|
dataOverride?: Partial<FeatureNodeData>,
|
|
425
233
|
edgeType?: string
|
|
426
234
|
): string => {
|
|
427
|
-
|
|
235
|
+
// Use real feature ID when available (from server), otherwise temp ID
|
|
236
|
+
const id = dataOverride?.featureId
|
|
237
|
+
? `feat-${dataOverride.featureId}`
|
|
238
|
+
: `feature-${Date.now()}-${nextFeatureId++}`;
|
|
428
239
|
const newFeatureData: FeatureNodeData = {
|
|
429
240
|
name: dataOverride?.name ?? 'New Feature',
|
|
430
241
|
description: dataOverride?.description ?? 'Describe what this feature does',
|
|
431
|
-
featureId: `#${id.slice(-4)}`,
|
|
242
|
+
featureId: dataOverride?.featureId ?? `#${id.slice(-4)}`,
|
|
432
243
|
lifecycle: 'requirements',
|
|
433
244
|
state: dataOverride?.state ?? 'running',
|
|
434
245
|
progress: 0,
|
|
@@ -436,171 +247,115 @@ export function useControlCenterState(
|
|
|
436
247
|
branch: dataOverride?.branch ?? '',
|
|
437
248
|
};
|
|
438
249
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
type: 'featureNode' as const,
|
|
443
|
-
position: { x: 0, y: 0 },
|
|
444
|
-
data: newFeatureData,
|
|
445
|
-
} as CanvasNodeType;
|
|
446
|
-
|
|
447
|
-
const newEdge = sourceNodeId
|
|
448
|
-
? {
|
|
449
|
-
id:
|
|
450
|
-
edgeType === 'dependencyEdge'
|
|
451
|
-
? `dep-${sourceNodeId}-${id}`
|
|
452
|
-
: `edge-${sourceNodeId}-${id}`,
|
|
453
|
-
source: sourceNodeId,
|
|
454
|
-
target: id,
|
|
455
|
-
...(edgeType ? { type: edgeType } : { style: { strokeDasharray: '5 5' } }),
|
|
456
|
-
}
|
|
457
|
-
: null;
|
|
458
|
-
|
|
459
|
-
const allEdges = newEdge
|
|
460
|
-
? [...edgesRef.current.filter((e) => e.id !== newEdge.id), newEdge]
|
|
461
|
-
: edgesRef.current;
|
|
462
|
-
|
|
463
|
-
// Insert the new node after the last sibling connected to the same source,
|
|
464
|
-
// so dagre's input-order preservation keeps repo groups together.
|
|
465
|
-
let insertIndex = currentNodes.length;
|
|
466
|
-
if (sourceNodeId) {
|
|
467
|
-
const siblingIds = new Set(
|
|
468
|
-
allEdges.filter((e) => e.source === sourceNodeId).map((e) => e.target)
|
|
469
|
-
);
|
|
470
|
-
for (let i = currentNodes.length - 1; i >= 0; i--) {
|
|
471
|
-
if (siblingIds.has(currentNodes[i].id)) {
|
|
472
|
-
insertIndex = i + 1;
|
|
473
|
-
break;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
const allNodes = [
|
|
478
|
-
...currentNodes.slice(0, insertIndex),
|
|
479
|
-
newNode,
|
|
480
|
-
...currentNodes.slice(insertIndex),
|
|
481
|
-
];
|
|
482
|
-
|
|
483
|
-
// Run dagre layout on the full graph so the new node is positioned consistently
|
|
484
|
-
const layoutResult = layoutWithDagre(allNodes, allEdges, CANVAS_LAYOUT_DEFAULTS);
|
|
485
|
-
setEdges(layoutResult.edges);
|
|
486
|
-
return layoutResult.nodes;
|
|
487
|
-
});
|
|
250
|
+
const parentNodeId = edgeType === 'dependencyEdge' && sourceNodeId ? sourceNodeId : undefined;
|
|
251
|
+
|
|
252
|
+
addPendingFeature(id, newFeatureData, parentNodeId);
|
|
488
253
|
|
|
489
254
|
return id;
|
|
490
255
|
},
|
|
491
|
-
[
|
|
256
|
+
[addPendingFeature]
|
|
492
257
|
);
|
|
493
258
|
|
|
494
259
|
const handleDeleteFeature = useCallback(
|
|
495
260
|
(featureId: string) => {
|
|
496
261
|
const nodeId = `feat-${featureId}`;
|
|
497
262
|
|
|
498
|
-
//
|
|
499
|
-
const
|
|
500
|
-
|
|
263
|
+
// Find the current entry for rollback
|
|
264
|
+
const prevEntry = nodesRef.current
|
|
265
|
+
.filter((n) => n.id === nodeId)
|
|
266
|
+
.map((n) => ({
|
|
267
|
+
nodeId: n.id,
|
|
268
|
+
data: n.data as FeatureNodeData,
|
|
269
|
+
}))[0];
|
|
501
270
|
|
|
502
|
-
// Optimistic removal
|
|
503
|
-
|
|
504
|
-
const remainingNodes = currentNodes.filter((n) => n.id !== nodeId);
|
|
505
|
-
const remainingEdges = edgesRef.current.filter(
|
|
506
|
-
(e) => e.source !== nodeId && e.target !== nodeId
|
|
507
|
-
);
|
|
508
|
-
const layoutResult = layoutWithDagre(
|
|
509
|
-
remainingNodes,
|
|
510
|
-
remainingEdges,
|
|
511
|
-
CANVAS_LAYOUT_DEFAULTS
|
|
512
|
-
);
|
|
513
|
-
setEdges(layoutResult.edges);
|
|
514
|
-
return layoutResult.nodes;
|
|
515
|
-
});
|
|
271
|
+
// Optimistic removal
|
|
272
|
+
removeFeature(nodeId);
|
|
516
273
|
deleteSound.play();
|
|
517
274
|
toast.success('Feature deleted successfully');
|
|
518
275
|
router.push('/');
|
|
519
276
|
|
|
520
|
-
// Persist in background — rollback on failure
|
|
521
277
|
deleteFeature(featureId)
|
|
522
278
|
.then((result) => {
|
|
523
279
|
if (result.error) {
|
|
524
|
-
|
|
525
|
-
setEdges(prevEdges);
|
|
280
|
+
if (prevEntry) restoreFeature(nodeId, prevEntry);
|
|
526
281
|
toast.error(result.error);
|
|
527
282
|
}
|
|
528
283
|
})
|
|
529
284
|
.catch(() => {
|
|
530
|
-
|
|
531
|
-
setEdges(prevEdges);
|
|
285
|
+
if (prevEntry) restoreFeature(nodeId, prevEntry);
|
|
532
286
|
toast.error('Failed to delete feature');
|
|
533
287
|
});
|
|
534
288
|
},
|
|
535
|
-
[
|
|
289
|
+
[router, deleteSound, removeFeature, restoreFeature]
|
|
536
290
|
);
|
|
537
291
|
|
|
538
292
|
const handleDeleteRepository = useCallback(
|
|
539
293
|
async (repositoryId: string) => {
|
|
540
294
|
const repoNodeId = `repo-${repositoryId}`;
|
|
541
295
|
|
|
296
|
+
// Find children of this repo via edges
|
|
297
|
+
const childFeatureIds = new Set(
|
|
298
|
+
edgesRef.current.filter((e) => e.source === repoNodeId).map((e) => e.target)
|
|
299
|
+
);
|
|
300
|
+
|
|
542
301
|
// Snapshot for rollback
|
|
543
|
-
const
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
)
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
remainingEdges,
|
|
565
|
-
CANVAS_LAYOUT_DEFAULTS
|
|
566
|
-
);
|
|
567
|
-
setEdges(layoutResult.edges);
|
|
568
|
-
return layoutResult.nodes;
|
|
569
|
-
});
|
|
302
|
+
const prevRepoData = getRepositoryData(repoNodeId);
|
|
303
|
+
const childSnapshots = new Map<string, FeatureEntry>();
|
|
304
|
+
for (const childId of childFeatureIds) {
|
|
305
|
+
const childNode = nodesRef.current.find((n) => n.id === childId);
|
|
306
|
+
if (childNode) {
|
|
307
|
+
childSnapshots.set(childId, { nodeId: childId, data: childNode.data as FeatureNodeData });
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const rollback = () => {
|
|
312
|
+
if (prevRepoData) addRepositoryToMap(repoNodeId, prevRepoData);
|
|
313
|
+
for (const [childId, entry] of childSnapshots) {
|
|
314
|
+
restoreFeature(childId, entry);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// Optimistic: remove repo + children
|
|
319
|
+
removeRepository(repoNodeId);
|
|
320
|
+
for (const childId of childFeatureIds) {
|
|
321
|
+
removeFeature(childId);
|
|
322
|
+
}
|
|
570
323
|
|
|
571
324
|
try {
|
|
572
325
|
const result = await deleteRepository(repositoryId);
|
|
573
|
-
|
|
574
326
|
if (!result.success) {
|
|
575
327
|
toast.error(result.error ?? 'Failed to remove repository');
|
|
576
|
-
|
|
577
|
-
setEdges(prevEdges);
|
|
328
|
+
rollback();
|
|
578
329
|
return;
|
|
579
330
|
}
|
|
580
|
-
|
|
581
331
|
deleteSound.play();
|
|
582
332
|
toast.success('Repository removed');
|
|
583
333
|
} catch {
|
|
584
334
|
toast.error('Failed to remove repository');
|
|
585
|
-
|
|
586
|
-
setEdges(prevEdges);
|
|
335
|
+
rollback();
|
|
587
336
|
}
|
|
588
337
|
},
|
|
589
|
-
[
|
|
338
|
+
[
|
|
339
|
+
deleteSound,
|
|
340
|
+
removeRepository,
|
|
341
|
+
removeFeature,
|
|
342
|
+
addRepositoryToMap,
|
|
343
|
+
restoreFeature,
|
|
344
|
+
getRepositoryData,
|
|
345
|
+
]
|
|
590
346
|
);
|
|
591
347
|
|
|
592
348
|
const handleLayout = useCallback(
|
|
593
349
|
(direction: LayoutDirection) => {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
setEdges(result.edges);
|
|
600
|
-
return result.nodes;
|
|
350
|
+
// Layout is applied via reconcile on next server prop update.
|
|
351
|
+
// For immediate re-layout, we apply dagre and trigger a reconcile-like update.
|
|
352
|
+
const result = layoutWithDagre(nodesRef.current, edgesRef.current, {
|
|
353
|
+
...CANVAS_LAYOUT_DEFAULTS,
|
|
354
|
+
direction,
|
|
601
355
|
});
|
|
356
|
+
reconcile(result.nodes, result.edges);
|
|
602
357
|
},
|
|
603
|
-
[
|
|
358
|
+
[reconcile]
|
|
604
359
|
);
|
|
605
360
|
|
|
606
361
|
const handleAddRepository = useCallback(
|
|
@@ -612,81 +367,30 @@ export function useControlCenterState(
|
|
|
612
367
|
.split(/[\\/]/)
|
|
613
368
|
.pop() ?? path;
|
|
614
369
|
|
|
615
|
-
|
|
616
|
-
setNodes((currentNodes) => {
|
|
617
|
-
const newNode = {
|
|
618
|
-
id: tempId,
|
|
619
|
-
type: 'repositoryNode' as const,
|
|
620
|
-
position: { x: 0, y: 0 },
|
|
621
|
-
data: { name: repoName, repositoryPath: path, id: tempId },
|
|
622
|
-
} as CanvasNodeType;
|
|
623
|
-
|
|
624
|
-
const allNodes = [...currentNodes, newNode];
|
|
625
|
-
const layoutResult = layoutWithDagre(allNodes, edgesRef.current, CANVAS_LAYOUT_DEFAULTS);
|
|
626
|
-
setEdges(layoutResult.edges);
|
|
627
|
-
return layoutResult.nodes;
|
|
628
|
-
});
|
|
370
|
+
addRepositoryToMap(tempId, { name: repoName, repositoryPath: path, id: tempId });
|
|
629
371
|
|
|
630
|
-
// Persist via server action
|
|
631
372
|
addRepository({ path, name: repoName })
|
|
632
373
|
.then((result) => {
|
|
633
374
|
if (result.error) {
|
|
634
|
-
|
|
635
|
-
setNodes((prev) => {
|
|
636
|
-
const remaining = prev.filter((n) => n.id !== tempId);
|
|
637
|
-
const layoutResult = layoutWithDagre(
|
|
638
|
-
remaining,
|
|
639
|
-
edgesRef.current,
|
|
640
|
-
CANVAS_LAYOUT_DEFAULTS
|
|
641
|
-
);
|
|
642
|
-
setEdges(layoutResult.edges);
|
|
643
|
-
return layoutResult.nodes;
|
|
644
|
-
});
|
|
375
|
+
removeRepository(tempId);
|
|
645
376
|
toast.error(result.error);
|
|
646
377
|
return;
|
|
647
378
|
}
|
|
648
|
-
|
|
649
|
-
// Replace temp ID with real repository ID
|
|
650
379
|
const repo = result.repository!;
|
|
651
380
|
const realId = `repo-${repo.id}`;
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
id: realId,
|
|
658
|
-
data: { ...n.data, id: repo.id, repositoryPath: repo.path },
|
|
659
|
-
} as CanvasNodeType)
|
|
660
|
-
: n
|
|
661
|
-
)
|
|
662
|
-
);
|
|
663
|
-
setEdges((prev) =>
|
|
664
|
-
prev.map((e) => ({
|
|
665
|
-
...e,
|
|
666
|
-
source: e.source === tempId ? realId : e.source,
|
|
667
|
-
target: e.target === tempId ? realId : e.target,
|
|
668
|
-
id: e.id.replace(tempId, realId),
|
|
669
|
-
}))
|
|
670
|
-
);
|
|
671
|
-
|
|
381
|
+
replaceRepository(tempId, realId, {
|
|
382
|
+
name: repo.name,
|
|
383
|
+
repositoryPath: repo.path,
|
|
384
|
+
id: repo.id,
|
|
385
|
+
});
|
|
672
386
|
createSound.play();
|
|
673
387
|
})
|
|
674
388
|
.catch(() => {
|
|
675
|
-
|
|
676
|
-
setNodes((prev) => {
|
|
677
|
-
const remaining = prev.filter((n) => n.id !== tempId);
|
|
678
|
-
const layoutResult = layoutWithDagre(
|
|
679
|
-
remaining,
|
|
680
|
-
edgesRef.current,
|
|
681
|
-
CANVAS_LAYOUT_DEFAULTS
|
|
682
|
-
);
|
|
683
|
-
setEdges(layoutResult.edges);
|
|
684
|
-
return layoutResult.nodes;
|
|
685
|
-
});
|
|
389
|
+
removeRepository(tempId);
|
|
686
390
|
toast.error('Failed to add repository');
|
|
687
391
|
});
|
|
688
392
|
},
|
|
689
|
-
[
|
|
393
|
+
[addRepositoryToMap, removeRepository, replaceRepository, createSound]
|
|
690
394
|
);
|
|
691
395
|
|
|
692
396
|
return {
|
|
@@ -699,5 +403,8 @@ export function useControlCenterState(
|
|
|
699
403
|
handleDeleteFeature,
|
|
700
404
|
handleDeleteRepository,
|
|
701
405
|
createFeatureNode,
|
|
406
|
+
getFeatureRepositoryPath,
|
|
407
|
+
getRepositoryData,
|
|
408
|
+
setCallbacks,
|
|
702
409
|
};
|
|
703
410
|
}
|