@shepai/cli 1.72.9 → 1.73.0
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 +15 -0
- 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/app/layout.js +1 -1
- 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 +74 -144
- 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/repository-node.js +1 -1
- 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/common/sidebar-section-header/sidebar-section-header.d.ts +1 -3
- package/dist/src/presentation/web/components/common/sidebar-section-header/sidebar-section-header.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/sidebar-section-header/sidebar-section-header.js +3 -7
- package/dist/src/presentation/web/components/common/sidebar-section-header/sidebar-section-header.stories.d.ts +0 -1
- package/dist/src/presentation/web/components/common/sidebar-section-header/sidebar-section-header.stories.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/sidebar-section-header/sidebar-section-header.stories.js +0 -11
- 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 +160 -354
- 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/layouts/app-sidebar/app-sidebar.d.ts +1 -3
- package/dist/src/presentation/web/components/layouts/app-sidebar/app-sidebar.d.ts.map +1 -1
- package/dist/src/presentation/web/components/layouts/app-sidebar/app-sidebar.js +2 -2
- 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/components/ui/scroll-area.js +1 -1
- 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 +18 -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 +1 -2
- package/dist/src/presentation/web/lib/logger.d.ts +7 -0
- package/dist/src/presentation/web/lib/logger.d.ts.map +1 -0
- package/dist/src/presentation/web/lib/logger.js +11 -0
- 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/required-server-files.js +1 -1
- package/web/.next/required-server-files.json +1 -1
- 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]__249c74f6._.js +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/server/chunks/ssr/{[root-of-the-server]__e5e09f72._.js → [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/_dee726e3._.js +6 -0
- package/web/.next/server/chunks/ssr/_dee726e3._.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_67178b88._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_67178b88._.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/required-server-files.json +1 -1
- 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]__249c74f6._.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/standalone/src/presentation/web/.next/server/chunks/ssr/{[root-of-the-server]__e5e09f72._.js → [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/_dee726e3._.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_67178b88._.js +1 -1
- 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/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/app/layout.tsx +1 -1
- 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 +105 -141
- 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/repository-node.tsx +2 -2
- 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/common/sidebar-section-header/sidebar-section-header.stories.tsx +0 -13
- package/web/.next/standalone/src/presentation/web/components/common/sidebar-section-header/sidebar-section-header.tsx +1 -45
- 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 +203 -440
- 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/layouts/app-sidebar/app-sidebar.tsx +2 -14
- 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/components/ui/scroll-area.tsx +1 -1
- 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 +19 -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 +1 -2
- package/web/.next/standalone/src/presentation/web/lib/logger.ts +12 -0
- package/web/.next/standalone/src/presentation/web/server.js +1 -1
- package/web/.next/static/chunks/0ffccd7be73305c8.css +2 -0
- package/web/.next/static/chunks/15ceb197ec725da2.js +1 -0
- package/web/.next/static/chunks/3f205fd6adbfd20a.js +7 -0
- package/web/.next/static/chunks/{d4492cd12fdf0891.js → 6691ad02dc1efd1f.js} +1 -1
- package/web/.next/static/chunks/{64850cf4081a2950.js → 7bff5fbcbf157df5.js} +1 -1
- package/web/.next/static/chunks/8959112899679e25.js +1 -0
- package/web/.next/static/chunks/{c83897e9213881bf.js → 8f508ef906cbf0fa.js} +1 -1
- package/web/.next/static/chunks/9cbb42a6bd516d3b.js +1 -0
- package/web/.next/static/chunks/c5603ed8a95d460d.js +1 -0
- package/web/.next/static/chunks/cb41849bcfaa304e.js +1 -0
- package/web/.next/static/chunks/d7e0cd32364a8d76.js +1 -0
- package/web/.next/static/chunks/e69d79bd3f19dec8.js +4 -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/_38d24563._.js +0 -6
- package/web/.next/server/chunks/ssr/_38d24563._.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/_38d24563._.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/0c54314c8c9c9739.js +0 -7
- package/web/.next/static/chunks/10cd08dae8c210ff.js +0 -1
- package/web/.next/static/chunks/15bf7ee1da91eb46.js +0 -4
- package/web/.next/static/chunks/3c79bca4d3f18f66.js +0 -1
- package/web/.next/static/chunks/67f2a7a5e8dc358a.css +0 -2
- package/web/.next/static/chunks/6cc840197626843b.js +0 -1
- package/web/.next/static/chunks/e3239b6c18cf8b96.js +0 -1
- package/web/.next/static/chunks/e9cc53ce8a7bdea6.js +0 -1
- package/web/.next/static/chunks/ef916db8c537211a.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/{Atrl1oUfRQej2kgJ972uS → 1AvkeBASQdtzZt9cm9UYg}/_buildManifest.js +0 -0
- /package/web/.next/static/{Atrl1oUfRQej2kgJ972uS → 1AvkeBASQdtzZt9cm9UYg}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{Atrl1oUfRQej2kgJ972uS → 1AvkeBASQdtzZt9cm9UYg}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useMemo, useCallback, useRef } from 'react';
|
|
4
|
+
import type { Edge, Position } from '@xyflow/react';
|
|
5
|
+
import type { CanvasNodeType } from '@/components/features/features-canvas';
|
|
6
|
+
import type { FeatureNodeData } from '@/components/common/feature-node';
|
|
7
|
+
import type { RepositoryNodeData } from '@/components/common/repository-node';
|
|
8
|
+
import {
|
|
9
|
+
deriveGraph,
|
|
10
|
+
type FeatureEntry,
|
|
11
|
+
type RepoEntry,
|
|
12
|
+
type GraphCallbacks,
|
|
13
|
+
} from '@/lib/derive-graph';
|
|
14
|
+
import { layoutWithDagre, CANVAS_LAYOUT_DEFAULTS } from '@/lib/layout-with-dagre';
|
|
15
|
+
|
|
16
|
+
export type { GraphCallbacks } from '@/lib/derive-graph';
|
|
17
|
+
|
|
18
|
+
export interface UseGraphStateReturn {
|
|
19
|
+
/** Derived React Flow nodes (from domain Maps). */
|
|
20
|
+
nodes: CanvasNodeType[];
|
|
21
|
+
/** Derived React Flow edges (from domain Maps). */
|
|
22
|
+
edges: Edge[];
|
|
23
|
+
/**
|
|
24
|
+
* Reconcile domain Maps with fresh server data (new initialNodes/initialEdges).
|
|
25
|
+
* Preserves pending (creating) nodes unless a matching real feature appears.
|
|
26
|
+
*/
|
|
27
|
+
reconcile: (newNodes: CanvasNodeType[], newEdges: Edge[]) => void;
|
|
28
|
+
/** Update a feature's state/lifecycle/name (e.g., from SSE events). */
|
|
29
|
+
updateFeature: (featureNodeId: string, updates: FeatureDataUpdates) => void;
|
|
30
|
+
/** Add a pending (optimistic) feature node (state='creating'). */
|
|
31
|
+
addPendingFeature: (nodeId: string, data: FeatureNodeData, parentNodeId?: string) => void;
|
|
32
|
+
/** Remove a pending feature node (e.g., on rollback). */
|
|
33
|
+
removePendingFeature: (nodeId: string) => void;
|
|
34
|
+
/** Remove a real feature node from the domain Map. */
|
|
35
|
+
removeFeature: (nodeId: string) => void;
|
|
36
|
+
/** Restore a previously removed feature (rollback). */
|
|
37
|
+
restoreFeature: (nodeId: string, entry: FeatureEntry) => void;
|
|
38
|
+
/** Add a repository node (may use a temp ID initially). */
|
|
39
|
+
addRepository: (nodeId: string, data: RepositoryNodeData) => void;
|
|
40
|
+
/** Remove a repository node. */
|
|
41
|
+
removeRepository: (nodeId: string) => void;
|
|
42
|
+
/** Replace a temp repo ID with the real one (atomic). */
|
|
43
|
+
replaceRepository: (tempId: string, realId: string, data: RepositoryNodeData) => void;
|
|
44
|
+
/** Stable lookup: get the repositoryPath for a feature node. */
|
|
45
|
+
getFeatureRepositoryPath: (featureNodeId: string) => string | undefined;
|
|
46
|
+
/** Stable lookup: get repository node data by nodeId. */
|
|
47
|
+
getRepositoryData: (nodeId: string) => RepositoryNodeData | undefined;
|
|
48
|
+
/** Update callbacks injected into node data (does NOT trigger re-render). */
|
|
49
|
+
setCallbacks: (callbacks: GraphCallbacks) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Parse initialNodes + initialEdges into domain Maps. */
|
|
53
|
+
function parseMaps(
|
|
54
|
+
initialNodes: CanvasNodeType[],
|
|
55
|
+
initialEdges: Edge[]
|
|
56
|
+
): {
|
|
57
|
+
featureMap: Map<string, FeatureEntry>;
|
|
58
|
+
repoMap: Map<string, RepoEntry>;
|
|
59
|
+
} {
|
|
60
|
+
// Build parentNodeId map from dependency edges
|
|
61
|
+
const parentByChild = new Map<string, string>();
|
|
62
|
+
for (const edge of initialEdges) {
|
|
63
|
+
if (edge.type === 'dependencyEdge') {
|
|
64
|
+
parentByChild.set(edge.target, edge.source);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const featureMap = new Map<string, FeatureEntry>();
|
|
69
|
+
const repoMap = new Map<string, RepoEntry>();
|
|
70
|
+
|
|
71
|
+
for (const node of initialNodes) {
|
|
72
|
+
if (node.type === 'featureNode') {
|
|
73
|
+
featureMap.set(node.id, {
|
|
74
|
+
nodeId: node.id,
|
|
75
|
+
data: node.data as FeatureNodeData,
|
|
76
|
+
parentNodeId: parentByChild.get(node.id),
|
|
77
|
+
});
|
|
78
|
+
} else if (node.type === 'repositoryNode') {
|
|
79
|
+
repoMap.set(node.id, {
|
|
80
|
+
nodeId: node.id,
|
|
81
|
+
data: node.data as RepositoryNodeData,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { featureMap, repoMap };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type FeatureDataUpdates = Partial<
|
|
90
|
+
Pick<FeatureNodeData, 'state' | 'lifecycle' | 'name' | 'description'>
|
|
91
|
+
>;
|
|
92
|
+
|
|
93
|
+
function isFeatureDataUnchanged(data: FeatureNodeData, updates: FeatureDataUpdates): boolean {
|
|
94
|
+
return (
|
|
95
|
+
(updates.state === undefined || updates.state === data.state) &&
|
|
96
|
+
(updates.lifecycle === undefined || updates.lifecycle === data.lifecycle) &&
|
|
97
|
+
(updates.name === undefined || updates.name === data.name) &&
|
|
98
|
+
(updates.description === undefined || updates.description === data.description)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function useGraphState(
|
|
103
|
+
initialNodes: CanvasNodeType[],
|
|
104
|
+
initialEdges: Edge[]
|
|
105
|
+
): UseGraphStateReturn {
|
|
106
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentional: parse only on mount (like useState initializer)
|
|
107
|
+
const init = useMemo(() => parseMaps(initialNodes, initialEdges), []);
|
|
108
|
+
|
|
109
|
+
const [featureMap, setFeatureMap] = useState<Map<string, FeatureEntry>>(init.featureMap);
|
|
110
|
+
const [repoMap, setRepoMap] = useState<Map<string, RepoEntry>>(init.repoMap);
|
|
111
|
+
const [pendingMap, setPendingMap] = useState<Map<string, FeatureEntry>>(
|
|
112
|
+
new Map<string, FeatureEntry>()
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Buffer for SSE updates that arrive before their feature is in featureMap.
|
|
116
|
+
// When reconcile adds new features, buffered updates are applied automatically.
|
|
117
|
+
const pendingUpdatesRef = useRef<
|
|
118
|
+
Map<string, Partial<Pick<FeatureNodeData, 'state' | 'lifecycle' | 'name' | 'description'>>>
|
|
119
|
+
>(new Map());
|
|
120
|
+
|
|
121
|
+
// Callbacks stored in a ref so changing them doesn't trigger re-render.
|
|
122
|
+
// The stable wrapper object reads from the ref so node closures always use latest callbacks.
|
|
123
|
+
const callbacksRef = useRef<GraphCallbacks>({});
|
|
124
|
+
const stableCallbacks = useMemo<GraphCallbacks>(
|
|
125
|
+
() => ({
|
|
126
|
+
onNodeAction: (nodeId) => callbacksRef.current.onNodeAction?.(nodeId),
|
|
127
|
+
onNodeSettings: (nodeId) => callbacksRef.current.onNodeSettings?.(nodeId),
|
|
128
|
+
onFeatureDelete: (featureId) => callbacksRef.current.onFeatureDelete?.(featureId),
|
|
129
|
+
onRepositoryAdd: (nodeId) => callbacksRef.current.onRepositoryAdd?.(nodeId),
|
|
130
|
+
onRepositoryClick: (nodeId) => callbacksRef.current.onRepositoryClick?.(nodeId),
|
|
131
|
+
onRepositoryDelete: (repositoryId) => callbacksRef.current.onRepositoryDelete?.(repositoryId),
|
|
132
|
+
}),
|
|
133
|
+
[]
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// Stable refs for domain Maps (for use in callbacks without depending on state)
|
|
137
|
+
const featureMapRef = useRef(featureMap);
|
|
138
|
+
featureMapRef.current = featureMap;
|
|
139
|
+
const repoMapRef = useRef(repoMap);
|
|
140
|
+
repoMapRef.current = repoMap;
|
|
141
|
+
|
|
142
|
+
// Derive graph from domain Maps (runs on every Map change, but dagre only on topology change)
|
|
143
|
+
const derived = useMemo(
|
|
144
|
+
() => deriveGraph(featureMap, repoMap, pendingMap, stableCallbacks),
|
|
145
|
+
[featureMap, repoMap, pendingMap, stableCallbacks]
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// Cache dagre layout positions — only re-run when node set or edge connections change
|
|
149
|
+
const layoutCacheRef = useRef<{
|
|
150
|
+
key: string;
|
|
151
|
+
positions: Map<
|
|
152
|
+
string,
|
|
153
|
+
{ position: { x: number; y: number }; targetPosition: Position; sourcePosition: Position }
|
|
154
|
+
>;
|
|
155
|
+
}>({ key: '', positions: new Map() });
|
|
156
|
+
|
|
157
|
+
const { nodes, edges } = useMemo(() => {
|
|
158
|
+
const nodeIds = derived.nodes
|
|
159
|
+
.map((n) => n.id)
|
|
160
|
+
.sort()
|
|
161
|
+
.join(',');
|
|
162
|
+
const edgeKeys = derived.edges
|
|
163
|
+
.map((e) => `${e.source}-${e.target}`)
|
|
164
|
+
.sort()
|
|
165
|
+
.join(',');
|
|
166
|
+
const topologyKey = `${nodeIds}|${edgeKeys}`;
|
|
167
|
+
|
|
168
|
+
if (topologyKey !== layoutCacheRef.current.key) {
|
|
169
|
+
// Topology changed — re-run dagre
|
|
170
|
+
const result = layoutWithDagre(derived.nodes, derived.edges, CANVAS_LAYOUT_DEFAULTS);
|
|
171
|
+
const positions = new Map<
|
|
172
|
+
string,
|
|
173
|
+
{ position: { x: number; y: number }; targetPosition: Position; sourcePosition: Position }
|
|
174
|
+
>();
|
|
175
|
+
for (const node of result.nodes) {
|
|
176
|
+
positions.set(node.id, {
|
|
177
|
+
position: node.position,
|
|
178
|
+
targetPosition: (node as Record<string, unknown>).targetPosition as Position,
|
|
179
|
+
sourcePosition: (node as Record<string, unknown>).sourcePosition as Position,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
layoutCacheRef.current = { key: topologyKey, positions };
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Data-only change — apply cached positions without re-running dagre
|
|
187
|
+
const { positions } = layoutCacheRef.current;
|
|
188
|
+
const nodes = derived.nodes.map((node) => {
|
|
189
|
+
const cached = positions.get(node.id);
|
|
190
|
+
return cached ? { ...node, ...cached } : node;
|
|
191
|
+
});
|
|
192
|
+
return { nodes, edges: derived.edges };
|
|
193
|
+
}, [derived]);
|
|
194
|
+
|
|
195
|
+
// --- Mutations ---
|
|
196
|
+
|
|
197
|
+
const reconcile = useCallback((newNodes: CanvasNodeType[], newEdges: Edge[]) => {
|
|
198
|
+
const { featureMap: newFeatureMap, repoMap: newRepoMap } = parseMaps(newNodes, newEdges);
|
|
199
|
+
|
|
200
|
+
// Precompute match keys so we don't scan newFeatureMap.values() per pending entry
|
|
201
|
+
const realFeatureKeys = new Set(
|
|
202
|
+
[...newFeatureMap.values()].map((e) => `${e.data.name}\0${e.data.repositoryPath}`)
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
setFeatureMap((currentFeatureMap) => {
|
|
206
|
+
// Preserve pending (creating) nodes unless matched by name+repositoryPath from newFeatureMap
|
|
207
|
+
const pendingEntries = [...currentFeatureMap.entries()].filter(
|
|
208
|
+
([, e]) => e.data.state === 'creating'
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const merged = new Map(newFeatureMap);
|
|
212
|
+
|
|
213
|
+
// Keep pending nodes that don't have a real counterpart in new data
|
|
214
|
+
for (const [tempId, pendingEntry] of pendingEntries) {
|
|
215
|
+
const key = `${pendingEntry.data.name}\0${pendingEntry.data.repositoryPath}`;
|
|
216
|
+
if (!realFeatureKeys.has(key)) {
|
|
217
|
+
merged.set(tempId, pendingEntry);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Apply any buffered SSE updates to features that now exist in the map
|
|
222
|
+
for (const [nodeId, updates] of pendingUpdatesRef.current) {
|
|
223
|
+
const entry = merged.get(nodeId);
|
|
224
|
+
if (entry) {
|
|
225
|
+
merged.set(nodeId, { ...entry, data: { ...entry.data, ...updates } });
|
|
226
|
+
pendingUpdatesRef.current.delete(nodeId);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return merged;
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Clean pendingMap entries that now have a real counterpart in server data
|
|
234
|
+
setPendingMap((currentPendingMap) => {
|
|
235
|
+
if (currentPendingMap.size === 0) return currentPendingMap;
|
|
236
|
+
let changed = false;
|
|
237
|
+
const next = new Map(currentPendingMap);
|
|
238
|
+
for (const [tempId, pendingEntry] of currentPendingMap) {
|
|
239
|
+
const key = `${pendingEntry.data.name}\0${pendingEntry.data.repositoryPath}`;
|
|
240
|
+
if (realFeatureKeys.has(key)) {
|
|
241
|
+
next.delete(tempId);
|
|
242
|
+
changed = true;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return changed ? next : currentPendingMap;
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
setRepoMap(newRepoMap);
|
|
249
|
+
}, []);
|
|
250
|
+
|
|
251
|
+
const updateFeature = useCallback((featureNodeId: string, updates: FeatureDataUpdates) => {
|
|
252
|
+
setFeatureMap((prev) => {
|
|
253
|
+
const entry = prev.get(featureNodeId);
|
|
254
|
+
if (!entry) {
|
|
255
|
+
// Feature not yet in map — buffer update for when reconcile adds it
|
|
256
|
+
pendingUpdatesRef.current.set(featureNodeId, {
|
|
257
|
+
...pendingUpdatesRef.current.get(featureNodeId),
|
|
258
|
+
...updates,
|
|
259
|
+
});
|
|
260
|
+
return prev;
|
|
261
|
+
}
|
|
262
|
+
// Clear any buffered update for this feature since it's now in the map
|
|
263
|
+
pendingUpdatesRef.current.delete(featureNodeId);
|
|
264
|
+
if (isFeatureDataUnchanged(entry.data, updates)) return prev;
|
|
265
|
+
const next = new Map(prev);
|
|
266
|
+
next.set(featureNodeId, { ...entry, data: { ...entry.data, ...updates } });
|
|
267
|
+
return next;
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Also check pendingMap
|
|
271
|
+
setPendingMap((prev) => {
|
|
272
|
+
const entry = prev.get(featureNodeId);
|
|
273
|
+
if (!entry) return prev;
|
|
274
|
+
if (isFeatureDataUnchanged(entry.data, updates)) return prev;
|
|
275
|
+
const next = new Map(prev);
|
|
276
|
+
next.set(featureNodeId, { ...entry, data: { ...entry.data, ...updates } });
|
|
277
|
+
return next;
|
|
278
|
+
});
|
|
279
|
+
}, []);
|
|
280
|
+
|
|
281
|
+
const addPendingFeature = useCallback(
|
|
282
|
+
(nodeId: string, data: FeatureNodeData, parentNodeId?: string) => {
|
|
283
|
+
setPendingMap((prev) => {
|
|
284
|
+
const next = new Map(prev);
|
|
285
|
+
next.set(nodeId, { nodeId, data, parentNodeId });
|
|
286
|
+
return next;
|
|
287
|
+
});
|
|
288
|
+
},
|
|
289
|
+
[]
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const removePendingFeature = useCallback((nodeId: string) => {
|
|
293
|
+
setPendingMap((prev) => {
|
|
294
|
+
if (!prev.has(nodeId)) return prev;
|
|
295
|
+
const next = new Map(prev);
|
|
296
|
+
next.delete(nodeId);
|
|
297
|
+
return next;
|
|
298
|
+
});
|
|
299
|
+
}, []);
|
|
300
|
+
|
|
301
|
+
const removeFeature = useCallback((nodeId: string) => {
|
|
302
|
+
setFeatureMap((prev) => {
|
|
303
|
+
if (!prev.has(nodeId)) return prev;
|
|
304
|
+
const next = new Map(prev);
|
|
305
|
+
next.delete(nodeId);
|
|
306
|
+
return next;
|
|
307
|
+
});
|
|
308
|
+
}, []);
|
|
309
|
+
|
|
310
|
+
const restoreFeature = useCallback((nodeId: string, entry: FeatureEntry) => {
|
|
311
|
+
setFeatureMap((prev) => {
|
|
312
|
+
const next = new Map(prev);
|
|
313
|
+
next.set(nodeId, entry);
|
|
314
|
+
return next;
|
|
315
|
+
});
|
|
316
|
+
}, []);
|
|
317
|
+
|
|
318
|
+
const addRepository = useCallback((nodeId: string, data: RepositoryNodeData) => {
|
|
319
|
+
setRepoMap((prev) => {
|
|
320
|
+
const next = new Map(prev);
|
|
321
|
+
next.set(nodeId, { nodeId, data });
|
|
322
|
+
return next;
|
|
323
|
+
});
|
|
324
|
+
}, []);
|
|
325
|
+
|
|
326
|
+
const removeRepository = useCallback((nodeId: string) => {
|
|
327
|
+
setRepoMap((prev) => {
|
|
328
|
+
if (!prev.has(nodeId)) return prev;
|
|
329
|
+
const next = new Map(prev);
|
|
330
|
+
next.delete(nodeId);
|
|
331
|
+
return next;
|
|
332
|
+
});
|
|
333
|
+
}, []);
|
|
334
|
+
|
|
335
|
+
const replaceRepository = useCallback(
|
|
336
|
+
(tempId: string, realId: string, data: RepositoryNodeData) => {
|
|
337
|
+
setRepoMap((prev) => {
|
|
338
|
+
if (!prev.has(tempId)) return prev;
|
|
339
|
+
const next = new Map(prev);
|
|
340
|
+
next.delete(tempId);
|
|
341
|
+
next.set(realId, { nodeId: realId, data });
|
|
342
|
+
return next;
|
|
343
|
+
});
|
|
344
|
+
},
|
|
345
|
+
[]
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
const getFeatureRepositoryPath = useCallback((featureNodeId: string): string | undefined => {
|
|
349
|
+
return featureMapRef.current.get(featureNodeId)?.data.repositoryPath;
|
|
350
|
+
}, []);
|
|
351
|
+
|
|
352
|
+
const getRepositoryData = useCallback((nodeId: string): RepositoryNodeData | undefined => {
|
|
353
|
+
return repoMapRef.current.get(nodeId)?.data;
|
|
354
|
+
}, []);
|
|
355
|
+
|
|
356
|
+
const setCallbacks = useCallback((callbacks: GraphCallbacks) => {
|
|
357
|
+
callbacksRef.current = callbacks;
|
|
358
|
+
}, []);
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
nodes,
|
|
362
|
+
edges,
|
|
363
|
+
reconcile,
|
|
364
|
+
updateFeature,
|
|
365
|
+
addPendingFeature,
|
|
366
|
+
removePendingFeature,
|
|
367
|
+
removeFeature,
|
|
368
|
+
restoreFeature,
|
|
369
|
+
addRepository,
|
|
370
|
+
removeRepository,
|
|
371
|
+
replaceRepository,
|
|
372
|
+
getFeatureRepositoryPath,
|
|
373
|
+
getRepositoryData,
|
|
374
|
+
setCallbacks,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useMemo
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
3
|
import { toast } from 'sonner';
|
|
4
4
|
import { Toaster } from '@/components/ui/sonner';
|
|
@@ -44,45 +44,17 @@ function NotificationDemo() {
|
|
|
44
44
|
const method = event.severity ?? 'info';
|
|
45
45
|
toast[method](event.featureName, { description: event.message });
|
|
46
46
|
|
|
47
|
-
if (globalThis.Notification?.permission === 'granted') {
|
|
48
|
-
new Notification(event.featureName, { body: event.message });
|
|
49
|
-
}
|
|
50
|
-
|
|
51
47
|
const soundName = SEVERITY_SOUNDS[event.severity];
|
|
52
48
|
soundsByName[soundName]?.play();
|
|
53
49
|
},
|
|
54
50
|
[soundsByName]
|
|
55
51
|
);
|
|
56
52
|
|
|
57
|
-
const [browserPermission, setBrowserPermission] = useState<NotificationPermission>(
|
|
58
|
-
typeof globalThis.Notification !== 'undefined' ? globalThis.Notification.permission : 'default'
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
const requestPermission = async () => {
|
|
62
|
-
if (typeof globalThis.Notification === 'undefined') return;
|
|
63
|
-
const result = await globalThis.Notification.requestPermission();
|
|
64
|
-
setBrowserPermission(result);
|
|
65
|
-
};
|
|
66
|
-
|
|
67
53
|
return (
|
|
68
54
|
<div className="flex w-[420px] flex-col gap-6">
|
|
69
|
-
<div className="flex flex-col gap-2">
|
|
70
|
-
<h3 className="text-sm font-semibold">Browser Notification Permission</h3>
|
|
71
|
-
<div className="flex items-center gap-3">
|
|
72
|
-
<span className="text-muted-foreground text-sm">
|
|
73
|
-
Status: <code className="bg-muted rounded px-1.5 py-0.5">{browserPermission}</code>
|
|
74
|
-
</span>
|
|
75
|
-
<Button size="sm" variant="outline" onClick={requestPermission}>
|
|
76
|
-
Request Permission
|
|
77
|
-
</Button>
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
|
|
81
55
|
<div className="flex flex-col gap-2">
|
|
82
56
|
<h3 className="text-sm font-semibold">Simulate Notification Events</h3>
|
|
83
|
-
<p className="text-muted-foreground text-xs">
|
|
84
|
-
Click a button to fire a toast, browser notification, and sound.
|
|
85
|
-
</p>
|
|
57
|
+
<p className="text-muted-foreground text-xs">Click a button to fire a toast and sound.</p>
|
|
86
58
|
<div className="flex flex-wrap gap-2">
|
|
87
59
|
<Button
|
|
88
60
|
size="sm"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
4
4
|
import { useRouter } from 'next/navigation';
|
|
5
5
|
import { toast } from 'sonner';
|
|
6
6
|
import type { NotificationEvent } from '@shepai/core/domain/generated/output';
|
|
@@ -9,11 +9,6 @@ import { useAgentEventsContext } from './agent-events-provider';
|
|
|
9
9
|
import { useSoundAction } from './use-sound-action';
|
|
10
10
|
import type { SoundAction } from './use-sound-action';
|
|
11
11
|
|
|
12
|
-
export interface UseNotificationsResult {
|
|
13
|
-
requestBrowserPermission: () => Promise<void>;
|
|
14
|
-
browserPermissionState: NotificationPermission;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
12
|
const SEVERITY_TO_TOAST: Record<NotificationSeverity, 'success' | 'error' | 'warning' | 'info'> = {
|
|
18
13
|
[NotificationSeverity.Success]: 'success',
|
|
19
14
|
[NotificationSeverity.Error]: 'error',
|
|
@@ -38,16 +33,6 @@ function dispatchToast(event: NotificationEvent, navigate?: (path: string) => vo
|
|
|
38
33
|
});
|
|
39
34
|
}
|
|
40
35
|
|
|
41
|
-
function dispatchBrowserNotification(event: NotificationEvent): void {
|
|
42
|
-
if (globalThis.Notification?.permission !== 'granted') {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
new Notification(`Shep: ${event.featureName}`, {
|
|
46
|
-
body: event.message,
|
|
47
|
-
icon: '/favicon-light.svg',
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
36
|
const SEVERITY_TO_ACTION: Record<NotificationSeverity, SoundAction> = {
|
|
52
37
|
[NotificationSeverity.Success]: 'notification-success',
|
|
53
38
|
[NotificationSeverity.Error]: 'notification-error',
|
|
@@ -55,7 +40,7 @@ const SEVERITY_TO_ACTION: Record<NotificationSeverity, SoundAction> = {
|
|
|
55
40
|
[NotificationSeverity.Info]: 'notification-info',
|
|
56
41
|
};
|
|
57
42
|
|
|
58
|
-
export function useNotifications():
|
|
43
|
+
export function useNotifications(): void {
|
|
59
44
|
const router = useRouter();
|
|
60
45
|
const { events } = useAgentEventsContext();
|
|
61
46
|
|
|
@@ -74,13 +59,6 @@ export function useNotifications(): UseNotificationsResult {
|
|
|
74
59
|
[successSound, errorSound, warningSound, infoSound]
|
|
75
60
|
);
|
|
76
61
|
|
|
77
|
-
const [browserPermissionState, setBrowserPermissionState] = useState<NotificationPermission>(
|
|
78
|
-
() => {
|
|
79
|
-
if (typeof globalThis.Notification === 'undefined') return 'default';
|
|
80
|
-
return globalThis.Notification.permission;
|
|
81
|
-
}
|
|
82
|
-
);
|
|
83
|
-
|
|
84
62
|
// Track how many events from the array we've already processed.
|
|
85
63
|
// Using the array index (instead of lastEvent) prevents React batching
|
|
86
64
|
// from silently dropping events when multiple SSE messages arrive together.
|
|
@@ -103,21 +81,9 @@ export function useNotifications(): UseNotificationsResult {
|
|
|
103
81
|
}
|
|
104
82
|
|
|
105
83
|
dispatchToast(event, (path) => router.push(path as Parameters<typeof router.push>[0]));
|
|
106
|
-
dispatchBrowserNotification(event);
|
|
107
84
|
|
|
108
85
|
const actionName = SEVERITY_TO_ACTION[event.severity];
|
|
109
86
|
soundsByAction[actionName]?.play();
|
|
110
87
|
}
|
|
111
88
|
}, [events, soundsByAction, router]);
|
|
112
|
-
|
|
113
|
-
const requestBrowserPermission = useCallback(async () => {
|
|
114
|
-
if (typeof globalThis.Notification === 'undefined') return;
|
|
115
|
-
const result = await globalThis.Notification.requestPermission();
|
|
116
|
-
setBrowserPermissionState(result);
|
|
117
|
-
}, []);
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
requestBrowserPermission,
|
|
121
|
-
browserPermissionState,
|
|
122
|
-
};
|
|
123
89
|
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* derive-graph.ts
|
|
3
|
+
*
|
|
4
|
+
* Pure client-side derivation: domain Maps → React Flow nodes + edges.
|
|
5
|
+
* All edges are derived from domain relationships (repositoryPath matching,
|
|
6
|
+
* parentNodeId) — never stored independently.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Edge } from '@xyflow/react';
|
|
10
|
+
import type { CanvasNodeType } from '@/components/features/features-canvas';
|
|
11
|
+
import type { FeatureNodeData } from '@/components/common/feature-node';
|
|
12
|
+
import type { RepositoryNodeData } from '@/components/common/repository-node';
|
|
13
|
+
/** A feature node entry stored in the domain Map. */
|
|
14
|
+
export interface FeatureEntry {
|
|
15
|
+
nodeId: string;
|
|
16
|
+
data: FeatureNodeData;
|
|
17
|
+
/** If set, this feature connects to a parent feature via a dependency edge (no repo→feature edge). */
|
|
18
|
+
parentNodeId?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** A repository node entry stored in the domain Map. */
|
|
22
|
+
export interface RepoEntry {
|
|
23
|
+
nodeId: string;
|
|
24
|
+
data: RepositoryNodeData;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Stable callbacks passed by the consumer, injected into derived node data. */
|
|
28
|
+
export interface GraphCallbacks {
|
|
29
|
+
/** Called when the user triggers an action on a feature node (e.g., adds a sub-feature). */
|
|
30
|
+
onNodeAction?: (nodeId: string) => void;
|
|
31
|
+
/** Called when the user opens settings/details on a feature node. */
|
|
32
|
+
onNodeSettings?: (nodeId: string) => void;
|
|
33
|
+
/** Called when the user deletes a feature. */
|
|
34
|
+
onFeatureDelete?: (featureId: string) => void;
|
|
35
|
+
/** Called when the user clicks the "+" add-feature button on a repo node. */
|
|
36
|
+
onRepositoryAdd?: (repoNodeId: string) => void;
|
|
37
|
+
/** Called when the user clicks a repo node to navigate to its detail page. */
|
|
38
|
+
onRepositoryClick?: (repoNodeId: string) => void;
|
|
39
|
+
/** Called when the user deletes a repository. */
|
|
40
|
+
onRepositoryDelete?: (repositoryId: string) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Derives React Flow nodes and edges from domain Maps.
|
|
45
|
+
*
|
|
46
|
+
* - Feature nodes without a parentNodeId → repo→feature edge (matched by repositoryPath)
|
|
47
|
+
* - Feature nodes without a parentNodeId but with no matching repo → virtual repo node
|
|
48
|
+
* - Feature nodes with a parentNodeId → dependency edge (parentNodeId→nodeId)
|
|
49
|
+
* - Pending feature nodes (creating state) from pendingMap → same rules, no action callbacks
|
|
50
|
+
* - Callbacks are injected into node data via closures
|
|
51
|
+
*/
|
|
52
|
+
export function deriveGraph(
|
|
53
|
+
featureMap: Map<string, FeatureEntry>,
|
|
54
|
+
repoMap: Map<string, RepoEntry>,
|
|
55
|
+
pendingMap: Map<string, FeatureEntry>,
|
|
56
|
+
callbacks?: GraphCallbacks
|
|
57
|
+
): { nodes: CanvasNodeType[]; edges: Edge[] } {
|
|
58
|
+
const nodes: CanvasNodeType[] = [];
|
|
59
|
+
const edges: Edge[] = [];
|
|
60
|
+
|
|
61
|
+
// Build a lookup: repositoryPath → repoNodeId
|
|
62
|
+
const repoByPath = new Map<string, string>();
|
|
63
|
+
for (const [nodeId, entry] of repoMap) {
|
|
64
|
+
if (entry.data.repositoryPath) {
|
|
65
|
+
repoByPath.set(entry.data.repositoryPath, nodeId);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Virtual repo nodes created for orphaned features (not in repoMap)
|
|
70
|
+
const virtualRepos = new Map<string, string>(); // path → virtualNodeId
|
|
71
|
+
|
|
72
|
+
function getOrCreateVirtualRepo(repositoryPath: string): string {
|
|
73
|
+
if (virtualRepos.has(repositoryPath)) {
|
|
74
|
+
return virtualRepos.get(repositoryPath)!;
|
|
75
|
+
}
|
|
76
|
+
const virtualId = `virtual-repo-${repositoryPath}`;
|
|
77
|
+
const repoName = repositoryPath.split('/').filter(Boolean).at(-1) ?? repositoryPath;
|
|
78
|
+
nodes.push({
|
|
79
|
+
id: virtualId,
|
|
80
|
+
type: 'repositoryNode',
|
|
81
|
+
position: { x: 0, y: 0 },
|
|
82
|
+
data: { name: repoName, repositoryPath } satisfies RepositoryNodeData,
|
|
83
|
+
} as CanvasNodeType);
|
|
84
|
+
virtualRepos.set(repositoryPath, virtualId);
|
|
85
|
+
return virtualId;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Add repo nodes (real ones)
|
|
89
|
+
for (const [nodeId, entry] of repoMap) {
|
|
90
|
+
const data: RepositoryNodeData = {
|
|
91
|
+
...entry.data,
|
|
92
|
+
...(callbacks?.onRepositoryAdd && {
|
|
93
|
+
onAdd: () => callbacks.onRepositoryAdd!(nodeId),
|
|
94
|
+
}),
|
|
95
|
+
...(callbacks?.onRepositoryClick && {
|
|
96
|
+
onClick: () => callbacks.onRepositoryClick!(nodeId),
|
|
97
|
+
}),
|
|
98
|
+
...(callbacks?.onRepositoryDelete && {
|
|
99
|
+
onDelete: callbacks.onRepositoryDelete,
|
|
100
|
+
}),
|
|
101
|
+
};
|
|
102
|
+
nodes.push({
|
|
103
|
+
id: nodeId,
|
|
104
|
+
type: 'repositoryNode',
|
|
105
|
+
position: { x: 0, y: 0 },
|
|
106
|
+
data,
|
|
107
|
+
} as CanvasNodeType);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Process feature entries (both real and pending).
|
|
111
|
+
// Skip pendingMap entries that already exist in featureMap (reconcile may not
|
|
112
|
+
// have cleaned them up yet if the AI-generated name differs from the original).
|
|
113
|
+
const allFeatureEntries: [string, FeatureEntry, boolean][] = [
|
|
114
|
+
...[...featureMap.entries()].map(
|
|
115
|
+
([id, e]) => [id, e, false] as [string, FeatureEntry, boolean]
|
|
116
|
+
),
|
|
117
|
+
...[...pendingMap.entries()]
|
|
118
|
+
.filter(([id]) => !featureMap.has(id))
|
|
119
|
+
.map(([id, e]) => [id, e, true] as [string, FeatureEntry, boolean]),
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
for (const [nodeId, entry, isPending] of allFeatureEntries) {
|
|
123
|
+
const isCreating = entry.data.state === 'creating' || isPending;
|
|
124
|
+
|
|
125
|
+
const data: FeatureNodeData = {
|
|
126
|
+
...entry.data,
|
|
127
|
+
// Callbacks only for non-creating nodes
|
|
128
|
+
...(!isCreating &&
|
|
129
|
+
callbacks?.onNodeAction && {
|
|
130
|
+
onAction: () => callbacks.onNodeAction!(nodeId),
|
|
131
|
+
}),
|
|
132
|
+
...(!isCreating &&
|
|
133
|
+
callbacks?.onNodeSettings && {
|
|
134
|
+
onSettings: () => callbacks.onNodeSettings!(nodeId),
|
|
135
|
+
}),
|
|
136
|
+
...(!isCreating &&
|
|
137
|
+
callbacks?.onFeatureDelete && {
|
|
138
|
+
onDelete: callbacks.onFeatureDelete,
|
|
139
|
+
}),
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
nodes.push({
|
|
143
|
+
id: nodeId,
|
|
144
|
+
type: 'featureNode',
|
|
145
|
+
position: { x: 0, y: 0 },
|
|
146
|
+
data,
|
|
147
|
+
} as CanvasNodeType);
|
|
148
|
+
|
|
149
|
+
// Edge derivation
|
|
150
|
+
if (entry.parentNodeId) {
|
|
151
|
+
// Dependency edge (parent→child feature)
|
|
152
|
+
edges.push({
|
|
153
|
+
id: `dep-${entry.parentNodeId}-${nodeId}`,
|
|
154
|
+
source: entry.parentNodeId,
|
|
155
|
+
target: nodeId,
|
|
156
|
+
type: 'dependencyEdge',
|
|
157
|
+
});
|
|
158
|
+
} else {
|
|
159
|
+
// Repo→feature edge (matched by repositoryPath)
|
|
160
|
+
const repositoryPath = entry.data.repositoryPath;
|
|
161
|
+
const repoNodeId = repoByPath.get(repositoryPath) ?? getOrCreateVirtualRepo(repositoryPath);
|
|
162
|
+
edges.push({
|
|
163
|
+
id: `edge-${repoNodeId}-${nodeId}`,
|
|
164
|
+
source: repoNodeId,
|
|
165
|
+
target: nodeId,
|
|
166
|
+
style: { strokeDasharray: '5 5' },
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Inject showHandles based on whether there are any edges
|
|
172
|
+
const hasEdges = edges.length > 0;
|
|
173
|
+
for (const node of nodes) {
|
|
174
|
+
(node.data as Record<string, unknown>).showHandles = hasEdges;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return { nodes, edges };
|
|
178
|
+
}
|
|
@@ -24,8 +24,7 @@ export const CANVAS_LAYOUT_DEFAULTS: LayoutOptions = {
|
|
|
24
24
|
/** Known node-type dimensions for the canvas node types */
|
|
25
25
|
const NODE_DIMENSIONS: Record<string, { width: number; height: number }> = {
|
|
26
26
|
featureNode: { width: 288, height: 140 },
|
|
27
|
-
repositoryNode: { width:
|
|
28
|
-
addRepositoryNode: { width: 224, height: 50 },
|
|
27
|
+
repositoryNode: { width: 328, height: 50 },
|
|
29
28
|
};
|
|
30
29
|
|
|
31
30
|
function getNodeSize(
|