@shepai/cli 1.72.9-pr220.f17b60a → 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/src/presentation/web/app/layout.js +1 -1
- 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 +45 -124
- 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/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-inner.js +6 -7
- 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 +24 -8
- 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 +12 -2
- 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/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/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +15 -15
- 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 +18 -18
- 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 +13 -13
- 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 +15 -15
- 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 +18 -18
- 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 +13 -13
- 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 +13 -13
- 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_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_client-reference-manifest.js +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 +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js.map +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__6258349c._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__6258349c._.js.map +1 -1
- 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]__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]__94a6d275._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__94a6d275._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_0e9d6bf7._.js +1 -1
- package/web/.next/server/chunks/ssr/_0e9d6bf7._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_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/{_8e87c87b._.js → _dee726e3._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_8e87c87b._.js.map → _dee726e3._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_2b91e4ec._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_2b91e4ec._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_5bcc1537._.js +1 -1
- package/web/.next/server/chunks/ssr/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 +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_5955c012.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 +2 -2
- package/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js +2 -2
- package/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js.map +1 -1
- 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 +21 -21
- 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 +15 -15
- 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 +18 -18
- 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 +13 -13
- 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 +15 -15
- 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 +18 -18
- 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 +13 -13
- 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 +13 -13
- 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_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_client-reference-manifest.js +1 -1
- 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 +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6258349c._.js +1 -1
- 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]__94a6d275._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_0e9d6bf7._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_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/{_8e87c87b._.js → _dee726e3._.js} +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_2b91e4ec._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_5bcc1537._.js +1 -1
- 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 +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 +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js +2 -2
- 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 +21 -21
- package/web/.next/standalone/src/presentation/web/app/layout.tsx +1 -1
- package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.tsx +69 -115
- 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/repository-node/use-repository-actions.ts +29 -54
- package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +7 -7
- package/web/.next/standalone/src/presentation/web/components/features/control-center/use-control-center-state.ts +25 -8
- 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 +12 -2
- package/web/.next/standalone/src/presentation/web/hooks/use-deploy-action.ts +11 -3
- package/web/.next/standalone/src/presentation/web/lib/logger.ts +12 -0
- package/web/.next/static/chunks/15ceb197ec725da2.js +1 -0
- package/web/.next/static/chunks/{792aac476adb5875.js → 6691ad02dc1efd1f.js} +1 -1
- package/web/.next/static/chunks/7bff5fbcbf157df5.js +1 -0
- package/web/.next/static/chunks/{d5e95c4482029a39.js → 8959112899679e25.js} +1 -1
- package/web/.next/static/chunks/{9b1770f05f1d95e5.js → 8f508ef906cbf0fa.js} +1 -1
- package/web/.next/static/chunks/{83463b63971f5723.js → 9cbb42a6bd516d3b.js} +1 -1
- package/web/.next/static/chunks/c5603ed8a95d460d.js +1 -0
- package/web/.next/static/chunks/{a7f52757dff0cea0.js → d7e0cd32364a8d76.js} +1 -1
- package/web/.next/static/chunks/{98ca891a5c156aa5.js → e69d79bd3f19dec8.js} +3 -3
- package/web/.next/trace +1 -1
- package/web/.next/trace-build +1 -1
- package/web/.next/static/chunks/76827713cb2b7333.js +0 -1
- package/web/.next/static/chunks/7c8adcfd920a8f74.js +0 -1
- package/web/.next/static/chunks/b6becf0de992842a.js +0 -1
- /package/web/.next/static/{eg0FTlsA4z_7CXbFfMgAr → 1AvkeBASQdtzZt9cm9UYg}/_buildManifest.js +0 -0
- /package/web/.next/static/{eg0FTlsA4z_7CXbFfMgAr → 1AvkeBASQdtzZt9cm9UYg}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{eg0FTlsA4z_7CXbFfMgAr → 1AvkeBASQdtzZt9cm9UYg}/_ssgManifest.js +0 -0
|
@@ -56,6 +56,7 @@ import type { MergeReviewData } from '@/components/common/merge-review';
|
|
|
56
56
|
import { resolveSseEventUpdates } from '@/components/common/feature-node/derive-feature-state';
|
|
57
57
|
import { deriveFeatureViewType } from './drawer-view';
|
|
58
58
|
import type { DrawerView } from './drawer-view';
|
|
59
|
+
import { useArtifactFetch } from './use-artifact-fetch';
|
|
59
60
|
|
|
60
61
|
export interface FeatureDrawerClientProps {
|
|
61
62
|
view: DrawerView;
|
|
@@ -87,6 +88,10 @@ export function FeatureDrawerClient({ view: initialView }: FeatureDrawerClientPr
|
|
|
87
88
|
const processedCountRef = useRef(0);
|
|
88
89
|
|
|
89
90
|
useEffect(() => {
|
|
91
|
+
// Clamp cursor if events were pruned
|
|
92
|
+
if (processedCountRef.current > events.length) {
|
|
93
|
+
processedCountRef.current = 0;
|
|
94
|
+
}
|
|
90
95
|
if (!featureNode || events.length <= processedCountRef.current) return;
|
|
91
96
|
|
|
92
97
|
const newEvents = events.slice(processedCountRef.current);
|
|
@@ -96,9 +101,9 @@ export function FeatureDrawerClient({ view: initialView }: FeatureDrawerClientPr
|
|
|
96
101
|
if (update.featureId !== featureNode.featureId) continue;
|
|
97
102
|
|
|
98
103
|
// Trigger a server refresh to get the latest drawer view,
|
|
99
|
-
// but skip when the user has unsaved changes
|
|
100
|
-
//
|
|
101
|
-
if (!isDirtyRef.current && !isRejectingRef.current) router.refresh();
|
|
104
|
+
// but skip when the drawer is closed, the user has unsaved changes,
|
|
105
|
+
// or a rejection is in-flight to avoid unnecessary refreshes.
|
|
106
|
+
if (isOpenRef.current && !isDirtyRef.current && !isRejectingRef.current) router.refresh();
|
|
102
107
|
|
|
103
108
|
if (update.state !== undefined || update.lifecycle !== undefined) {
|
|
104
109
|
// Optimistically update the node data AND re-derive the view type so the
|
|
@@ -129,6 +134,8 @@ export function FeatureDrawerClient({ view: initialView }: FeatureDrawerClientPr
|
|
|
129
134
|
// the close animation when the path no longer matches a feature route.
|
|
130
135
|
const pathname = usePathname();
|
|
131
136
|
const isOpen = pathname.startsWith('/feature/');
|
|
137
|
+
const isOpenRef = useRef(isOpen);
|
|
138
|
+
isOpenRef.current = isOpen;
|
|
132
139
|
|
|
133
140
|
const onClose = useCallback(() => {
|
|
134
141
|
router.push('/');
|
|
@@ -141,21 +148,17 @@ export function FeatureDrawerClient({ view: initialView }: FeatureDrawerClientPr
|
|
|
141
148
|
const [prdData, setPrdData] = useState<PrdQuestionnaireData | null>(null);
|
|
142
149
|
const [prdSelections, setPrdSelections] = useState<Record<string, string>>({});
|
|
143
150
|
const [prdDefaultSelections, setPrdDefaultSelections] = useState<Record<string, string>>({});
|
|
144
|
-
const [isLoadingPrd, setIsLoadingPrd] = useState(false);
|
|
145
151
|
|
|
146
152
|
// ── Tech state ─────────────────────────────────────────────────────────
|
|
147
153
|
const [techData, setTechData] = useState<TechDecisionsReviewData | null>(null);
|
|
148
|
-
const [isLoadingTech, setIsLoadingTech] = useState(false);
|
|
149
154
|
|
|
150
155
|
// ── Product decisions state (for tech review Product tab) ─────────────
|
|
151
156
|
const [techProductData, setTechProductData] = useState<
|
|
152
157
|
ProductDecisionsSummaryData | null | undefined
|
|
153
158
|
>(undefined);
|
|
154
|
-
const [isLoadingTechProduct, setIsLoadingTechProduct] = useState(false);
|
|
155
159
|
|
|
156
160
|
// ── Merge state ────────────────────────────────────────────────────────
|
|
157
161
|
const [mergeData, setMergeData] = useState<MergeReviewData | null>(null);
|
|
158
|
-
const [isLoadingMerge, setIsLoadingMerge] = useState(false);
|
|
159
162
|
|
|
160
163
|
// ── Delete state ───────────────────────────────────────────────────────
|
|
161
164
|
const [isDeleting, setIsDeleting] = useState(false);
|
|
@@ -173,120 +176,71 @@ export function FeatureDrawerClient({ view: initialView }: FeatureDrawerClientPr
|
|
|
173
176
|
// ── Data fetching ─────────────────────────────────────────────────────
|
|
174
177
|
|
|
175
178
|
const prdFeatureId = view.type === 'prd-review' ? view.node.featureId : null;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
if (result.questionnaire) {
|
|
192
|
-
setPrdData(result.questionnaire);
|
|
193
|
-
const defaults: Record<string, string> = {};
|
|
194
|
-
for (const q of result.questionnaire.questions) {
|
|
195
|
-
const recommended = q.options.find((o) => o.recommended);
|
|
196
|
-
if (recommended) defaults[q.id] = recommended.id;
|
|
197
|
-
}
|
|
198
|
-
setPrdSelections(defaults);
|
|
199
|
-
setPrdDefaultSelections(defaults);
|
|
179
|
+
const isLoadingPrd = useArtifactFetch(
|
|
180
|
+
prdFeatureId,
|
|
181
|
+
getFeatureArtifact,
|
|
182
|
+
(result) => {
|
|
183
|
+
if (result.error) {
|
|
184
|
+
toast.error(result.error);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (result.questionnaire) {
|
|
188
|
+
setPrdData(result.questionnaire);
|
|
189
|
+
const defaults: Record<string, string> = {};
|
|
190
|
+
for (const q of result.questionnaire.questions) {
|
|
191
|
+
const recommended = q.options.find((o) => o.recommended);
|
|
192
|
+
if (recommended) defaults[q.id] = recommended.id;
|
|
200
193
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
194
|
+
setPrdSelections(defaults);
|
|
195
|
+
setPrdDefaultSelections(defaults);
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
() => {
|
|
199
|
+
setPrdSelections({});
|
|
200
|
+
setPrdDefaultSelections({});
|
|
201
|
+
setPrdData(null);
|
|
202
|
+
},
|
|
203
|
+
'Failed to load questionnaire'
|
|
204
|
+
);
|
|
212
205
|
|
|
213
206
|
const techFeatureId = view.type === 'tech-review' ? view.node.featureId : null;
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (result.techDecisions) setTechData(result.techDecisions);
|
|
228
|
-
})
|
|
229
|
-
.catch(() => {
|
|
230
|
-
if (!cancelled) toast.error('Failed to load tech decisions');
|
|
231
|
-
})
|
|
232
|
-
.finally(() => {
|
|
233
|
-
if (!cancelled) setIsLoadingTech(false);
|
|
234
|
-
});
|
|
235
|
-
return () => {
|
|
236
|
-
cancelled = true;
|
|
237
|
-
};
|
|
238
|
-
}, [techFeatureId]);
|
|
207
|
+
const isLoadingTech = useArtifactFetch(
|
|
208
|
+
techFeatureId,
|
|
209
|
+
getResearchArtifact,
|
|
210
|
+
(result) => {
|
|
211
|
+
if (result.error) {
|
|
212
|
+
toast.error(result.error);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (result.techDecisions) setTechData(result.techDecisions);
|
|
216
|
+
},
|
|
217
|
+
() => setTechData(null),
|
|
218
|
+
'Failed to load tech decisions'
|
|
219
|
+
);
|
|
239
220
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (cancelled) return;
|
|
249
|
-
if (result.productDecisions) {
|
|
250
|
-
setTechProductData(result.productDecisions);
|
|
251
|
-
}
|
|
252
|
-
})
|
|
253
|
-
.catch(() => {
|
|
254
|
-
// Silent failure — the product tab is supplementary
|
|
255
|
-
})
|
|
256
|
-
.finally(() => {
|
|
257
|
-
if (!cancelled) setIsLoadingTechProduct(false);
|
|
258
|
-
});
|
|
259
|
-
return () => {
|
|
260
|
-
cancelled = true;
|
|
261
|
-
};
|
|
262
|
-
}, [techFeatureId]);
|
|
221
|
+
const isLoadingTechProduct = useArtifactFetch(
|
|
222
|
+
techFeatureId,
|
|
223
|
+
getFeatureArtifact,
|
|
224
|
+
(result) => {
|
|
225
|
+
if (result.productDecisions) setTechProductData(result.productDecisions);
|
|
226
|
+
},
|
|
227
|
+
() => setTechProductData(undefined)
|
|
228
|
+
);
|
|
263
229
|
|
|
264
230
|
const mergeFeatureId = view.type === 'merge-review' ? view.node.featureId : null;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
setMergeData(result);
|
|
279
|
-
})
|
|
280
|
-
.catch(() => {
|
|
281
|
-
if (!cancelled) toast.error('Failed to load merge review data');
|
|
282
|
-
})
|
|
283
|
-
.finally(() => {
|
|
284
|
-
if (!cancelled) setIsLoadingMerge(false);
|
|
285
|
-
});
|
|
286
|
-
return () => {
|
|
287
|
-
cancelled = true;
|
|
288
|
-
};
|
|
289
|
-
}, [mergeFeatureId]);
|
|
231
|
+
const isLoadingMerge = useArtifactFetch(
|
|
232
|
+
mergeFeatureId,
|
|
233
|
+
getMergeReviewData,
|
|
234
|
+
(result) => {
|
|
235
|
+
if ('error' in result) {
|
|
236
|
+
toast.error(result.error);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
setMergeData(result);
|
|
240
|
+
},
|
|
241
|
+
() => setMergeData(null),
|
|
242
|
+
'Failed to load merge review data'
|
|
243
|
+
);
|
|
290
244
|
|
|
291
245
|
// ── Close guard ──────────────────────────────────────────────────────
|
|
292
246
|
const isChatDirty = chatInput.trim().length > 0;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useRef } from 'react';
|
|
4
|
+
import { toast } from 'sonner';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generic hook for fetching drawer artifact data when a feature ID changes.
|
|
8
|
+
* Handles loading state, cancellation on unmount/re-fetch, and error toasting.
|
|
9
|
+
*
|
|
10
|
+
* @param featureId - The feature ID to fetch for, or null to reset.
|
|
11
|
+
* @param fetcher - Async function that fetches the artifact data.
|
|
12
|
+
* @param onSuccess - Called with the fetch result when successful and not cancelled.
|
|
13
|
+
* @param onReset - Called immediately when featureId changes (before fetching).
|
|
14
|
+
* @param errorMessage - Optional toast message shown on fetch failure.
|
|
15
|
+
* @returns Whether the fetch is currently in progress.
|
|
16
|
+
*/
|
|
17
|
+
export function useArtifactFetch<TResult>(
|
|
18
|
+
featureId: string | null,
|
|
19
|
+
fetcher: (id: string) => Promise<TResult>,
|
|
20
|
+
onSuccess: (result: TResult) => void,
|
|
21
|
+
onReset: () => void,
|
|
22
|
+
errorMessage?: string
|
|
23
|
+
): boolean {
|
|
24
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
25
|
+
|
|
26
|
+
// Use refs to avoid stale callbacks in the effect without triggering re-runs.
|
|
27
|
+
const onSuccessRef = useRef(onSuccess);
|
|
28
|
+
const onResetRef = useRef(onReset);
|
|
29
|
+
onSuccessRef.current = onSuccess;
|
|
30
|
+
onResetRef.current = onReset;
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
onResetRef.current();
|
|
34
|
+
if (!featureId) return;
|
|
35
|
+
|
|
36
|
+
let cancelled = false;
|
|
37
|
+
setIsLoading(true);
|
|
38
|
+
|
|
39
|
+
fetcher(featureId)
|
|
40
|
+
.then((result) => {
|
|
41
|
+
if (!cancelled) onSuccessRef.current(result);
|
|
42
|
+
})
|
|
43
|
+
.catch(() => {
|
|
44
|
+
if (!cancelled && errorMessage) toast.error(errorMessage);
|
|
45
|
+
})
|
|
46
|
+
.finally(() => {
|
|
47
|
+
if (!cancelled) setIsLoading(false);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return () => {
|
|
51
|
+
cancelled = true;
|
|
52
|
+
};
|
|
53
|
+
}, [featureId, fetcher, errorMessage]);
|
|
54
|
+
|
|
55
|
+
return isLoading;
|
|
56
|
+
}
|
|
@@ -37,15 +37,14 @@ export function useFeatureActions(input: FeatureActionsInput | null): FeatureAct
|
|
|
37
37
|
const shellTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
38
38
|
const specsTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
39
39
|
|
|
40
|
-
// Clear timers on unmount
|
|
40
|
+
// Clear timers on unmount — read .current inside cleanup so we get the
|
|
41
|
+
// actual timer value at teardown time, not the always-null value at mount.
|
|
41
42
|
useEffect(() => {
|
|
42
|
-
const
|
|
43
|
-
const shellTimer = shellTimerRef.current;
|
|
44
|
-
const specsTimer = specsTimerRef.current;
|
|
43
|
+
const refs = [ideTimerRef, shellTimerRef, specsTimerRef];
|
|
45
44
|
return () => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
for (const ref of refs) {
|
|
46
|
+
if (ref.current) clearTimeout(ref.current);
|
|
47
|
+
}
|
|
49
48
|
};
|
|
50
49
|
}, []);
|
|
51
50
|
|
|
@@ -37,22 +37,19 @@ export function useRepositoryActions(input: RepositoryActionsInput | null): Repo
|
|
|
37
37
|
|
|
38
38
|
// Clear timers on unmount
|
|
39
39
|
useEffect(() => {
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
const
|
|
40
|
+
const ideRef = ideTimerRef;
|
|
41
|
+
const shellRef = shellTimerRef;
|
|
42
|
+
const folderRef = folderTimerRef;
|
|
43
43
|
return () => {
|
|
44
|
-
if (
|
|
45
|
-
if (
|
|
46
|
-
if (
|
|
44
|
+
if (ideRef.current) clearTimeout(ideRef.current);
|
|
45
|
+
if (shellRef.current) clearTimeout(shellRef.current);
|
|
46
|
+
if (folderRef.current) clearTimeout(folderRef.current);
|
|
47
47
|
};
|
|
48
48
|
}, []);
|
|
49
49
|
|
|
50
|
-
const
|
|
50
|
+
const performAction = useCallback(
|
|
51
51
|
async (
|
|
52
|
-
action: (
|
|
53
|
-
success: boolean;
|
|
54
|
-
error?: string;
|
|
55
|
-
}>,
|
|
52
|
+
action: () => Promise<{ success: boolean; error?: string }>,
|
|
56
53
|
setLoading: (v: boolean) => void,
|
|
57
54
|
setError: (v: string | null) => void,
|
|
58
55
|
timerRef: React.RefObject<ReturnType<typeof setTimeout> | null>,
|
|
@@ -60,48 +57,13 @@ export function useRepositoryActions(input: RepositoryActionsInput | null): Repo
|
|
|
60
57
|
) => {
|
|
61
58
|
if (!input || isLoading) return;
|
|
62
59
|
|
|
63
|
-
// Clear any existing timer
|
|
64
60
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
65
61
|
|
|
66
62
|
setLoading(true);
|
|
67
63
|
setError(null);
|
|
68
64
|
|
|
69
65
|
try {
|
|
70
|
-
const result = await action(
|
|
71
|
-
|
|
72
|
-
if (!result.success) {
|
|
73
|
-
const errorMessage = result.error ?? 'An unexpected error occurred';
|
|
74
|
-
setError(errorMessage);
|
|
75
|
-
timerRef.current = setTimeout(() => setError(null), ERROR_CLEAR_DELAY);
|
|
76
|
-
}
|
|
77
|
-
} catch (err: unknown) {
|
|
78
|
-
const errorMessage = err instanceof Error ? err.message : 'An unexpected error occurred';
|
|
79
|
-
setError(errorMessage);
|
|
80
|
-
timerRef.current = setTimeout(() => setError(null), ERROR_CLEAR_DELAY);
|
|
81
|
-
} finally {
|
|
82
|
-
setLoading(false);
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
[input]
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
const performFolderAction = useCallback(
|
|
89
|
-
async (
|
|
90
|
-
action: (repositoryPath: string) => Promise<{ success: boolean; error?: string }>,
|
|
91
|
-
setLoading: (v: boolean) => void,
|
|
92
|
-
setError: (v: string | null) => void,
|
|
93
|
-
timerRef: React.RefObject<ReturnType<typeof setTimeout> | null>,
|
|
94
|
-
isLoading: boolean
|
|
95
|
-
) => {
|
|
96
|
-
if (!input || isLoading) return;
|
|
97
|
-
|
|
98
|
-
if (timerRef.current) clearTimeout(timerRef.current);
|
|
99
|
-
|
|
100
|
-
setLoading(true);
|
|
101
|
-
setError(null);
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
const result = await action(input.repositoryPath);
|
|
66
|
+
const result = await action();
|
|
105
67
|
|
|
106
68
|
if (!result.success) {
|
|
107
69
|
const errorMessage = result.error ?? 'An unexpected error occurred';
|
|
@@ -120,26 +82,39 @@ export function useRepositoryActions(input: RepositoryActionsInput | null): Repo
|
|
|
120
82
|
);
|
|
121
83
|
|
|
122
84
|
const handleOpenIde = useCallback(
|
|
123
|
-
() =>
|
|
124
|
-
|
|
85
|
+
() =>
|
|
86
|
+
performAction(
|
|
87
|
+
() => openIde({ repositoryPath: input!.repositoryPath }),
|
|
88
|
+
setIdeLoading,
|
|
89
|
+
setIdeError,
|
|
90
|
+
ideTimerRef,
|
|
91
|
+
ideLoading
|
|
92
|
+
),
|
|
93
|
+
[performAction, ideLoading, input]
|
|
125
94
|
);
|
|
126
95
|
|
|
127
96
|
const handleOpenShell = useCallback(
|
|
128
97
|
() =>
|
|
129
|
-
|
|
130
|
-
|
|
98
|
+
performAction(
|
|
99
|
+
() => openShell({ repositoryPath: input!.repositoryPath }),
|
|
100
|
+
setShellLoading,
|
|
101
|
+
setShellError,
|
|
102
|
+
shellTimerRef,
|
|
103
|
+
shellLoading
|
|
104
|
+
),
|
|
105
|
+
[performAction, shellLoading, input]
|
|
131
106
|
);
|
|
132
107
|
|
|
133
108
|
const handleOpenFolder = useCallback(
|
|
134
109
|
() =>
|
|
135
|
-
|
|
136
|
-
openFolder,
|
|
110
|
+
performAction(
|
|
111
|
+
() => openFolder(input!.repositoryPath),
|
|
137
112
|
setFolderLoading,
|
|
138
113
|
setFolderError,
|
|
139
114
|
folderTimerRef,
|
|
140
115
|
folderLoading
|
|
141
116
|
),
|
|
142
|
-
[
|
|
117
|
+
[performAction, folderLoading, input]
|
|
143
118
|
);
|
|
144
119
|
|
|
145
120
|
return {
|
|
@@ -44,20 +44,20 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
|
|
|
44
44
|
// Publish sidebar features to context whenever feature node data changes
|
|
45
45
|
const { setFeatures: setSidebarFeatures } = useSidebarFeaturesContext();
|
|
46
46
|
|
|
47
|
+
const featureNodes = useMemo(() => nodes.filter((n) => n.type === 'featureNode'), [nodes]);
|
|
48
|
+
|
|
47
49
|
const sidebarKey = useMemo(() => {
|
|
48
|
-
return
|
|
49
|
-
.filter((n) => n.type === 'featureNode')
|
|
50
|
+
return featureNodes
|
|
50
51
|
.map((n) => {
|
|
51
52
|
const d = n.data as FeatureNodeData;
|
|
52
|
-
return `${d.featureId}:${d.state}:${d.
|
|
53
|
+
return `${d.featureId}:${d.state}:${d.name}`;
|
|
53
54
|
})
|
|
54
55
|
.sort()
|
|
55
56
|
.join(',');
|
|
56
|
-
}, [
|
|
57
|
+
}, [featureNodes]);
|
|
57
58
|
|
|
58
59
|
useEffect(() => {
|
|
59
|
-
const sidebarItems =
|
|
60
|
-
.filter((n) => n.type === 'featureNode')
|
|
60
|
+
const sidebarItems = featureNodes
|
|
61
61
|
.map((n) => {
|
|
62
62
|
const d = n.data as FeatureNodeData;
|
|
63
63
|
const status = mapNodeStateToSidebarStatus(d.state);
|
|
@@ -79,7 +79,7 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
|
|
|
79
79
|
}[];
|
|
80
80
|
|
|
81
81
|
setSidebarFeatures(sidebarItems);
|
|
82
|
-
}, [sidebarKey,
|
|
82
|
+
}, [sidebarKey, featureNodes, setSidebarFeatures]);
|
|
83
83
|
|
|
84
84
|
// ── URL-based navigation handlers ────────────────────────────────────
|
|
85
85
|
|
|
@@ -76,6 +76,14 @@ export function useControlCenterState(
|
|
|
76
76
|
setCallbacks,
|
|
77
77
|
} = useGraphState(initialNodes, initialEdges);
|
|
78
78
|
|
|
79
|
+
// Refs for stable access to latest nodes/edges without callback recreation
|
|
80
|
+
const nodesRef = useRef(nodes);
|
|
81
|
+
const edgesRef = useRef(edges);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
nodesRef.current = nodes;
|
|
84
|
+
edgesRef.current = edges;
|
|
85
|
+
}, [nodes, edges]);
|
|
86
|
+
|
|
79
87
|
// Sync server props into domain Maps when initialNodes/initialEdges change.
|
|
80
88
|
// Keyed by a stable string so we don't re-reconcile on every render.
|
|
81
89
|
const initialNodeKey = useMemo(
|
|
@@ -148,6 +156,10 @@ export function useControlCenterState(
|
|
|
148
156
|
const processedEventCountRef = useRef(0);
|
|
149
157
|
|
|
150
158
|
useEffect(() => {
|
|
159
|
+
// Clamp cursor if events were pruned
|
|
160
|
+
if (processedEventCountRef.current > events.length) {
|
|
161
|
+
processedEventCountRef.current = 0;
|
|
162
|
+
}
|
|
151
163
|
if (events.length <= processedEventCountRef.current) return;
|
|
152
164
|
const newEvents = events.slice(processedEventCountRef.current);
|
|
153
165
|
processedEventCountRef.current = events.length;
|
|
@@ -177,6 +189,10 @@ export function useControlCenterState(
|
|
|
177
189
|
const processedMetadataCountRef = useRef(0);
|
|
178
190
|
|
|
179
191
|
useEffect(() => {
|
|
192
|
+
// Clamp cursor if events were pruned
|
|
193
|
+
if (processedMetadataCountRef.current > events.length) {
|
|
194
|
+
processedMetadataCountRef.current = 0;
|
|
195
|
+
}
|
|
180
196
|
if (events.length <= processedMetadataCountRef.current) return;
|
|
181
197
|
const newEvents = events.slice(processedMetadataCountRef.current);
|
|
182
198
|
processedMetadataCountRef.current = events.length;
|
|
@@ -245,7 +261,7 @@ export function useControlCenterState(
|
|
|
245
261
|
const nodeId = `feat-${featureId}`;
|
|
246
262
|
|
|
247
263
|
// Find the current entry for rollback
|
|
248
|
-
const prevEntry =
|
|
264
|
+
const prevEntry = nodesRef.current
|
|
249
265
|
.filter((n) => n.id === nodeId)
|
|
250
266
|
.map((n) => ({
|
|
251
267
|
nodeId: n.id,
|
|
@@ -270,7 +286,7 @@ export function useControlCenterState(
|
|
|
270
286
|
toast.error('Failed to delete feature');
|
|
271
287
|
});
|
|
272
288
|
},
|
|
273
|
-
[
|
|
289
|
+
[router, deleteSound, removeFeature, restoreFeature]
|
|
274
290
|
);
|
|
275
291
|
|
|
276
292
|
const handleDeleteRepository = useCallback(
|
|
@@ -279,14 +295,14 @@ export function useControlCenterState(
|
|
|
279
295
|
|
|
280
296
|
// Find children of this repo via edges
|
|
281
297
|
const childFeatureIds = new Set(
|
|
282
|
-
|
|
298
|
+
edgesRef.current.filter((e) => e.source === repoNodeId).map((e) => e.target)
|
|
283
299
|
);
|
|
284
300
|
|
|
285
301
|
// Snapshot for rollback
|
|
286
302
|
const prevRepoData = getRepositoryData(repoNodeId);
|
|
287
303
|
const childSnapshots = new Map<string, FeatureEntry>();
|
|
288
304
|
for (const childId of childFeatureIds) {
|
|
289
|
-
const childNode =
|
|
305
|
+
const childNode = nodesRef.current.find((n) => n.id === childId);
|
|
290
306
|
if (childNode) {
|
|
291
307
|
childSnapshots.set(childId, { nodeId: childId, data: childNode.data as FeatureNodeData });
|
|
292
308
|
}
|
|
@@ -320,8 +336,6 @@ export function useControlCenterState(
|
|
|
320
336
|
}
|
|
321
337
|
},
|
|
322
338
|
[
|
|
323
|
-
edges,
|
|
324
|
-
nodes,
|
|
325
339
|
deleteSound,
|
|
326
340
|
removeRepository,
|
|
327
341
|
removeFeature,
|
|
@@ -335,10 +349,13 @@ export function useControlCenterState(
|
|
|
335
349
|
(direction: LayoutDirection) => {
|
|
336
350
|
// Layout is applied via reconcile on next server prop update.
|
|
337
351
|
// For immediate re-layout, we apply dagre and trigger a reconcile-like update.
|
|
338
|
-
const result = layoutWithDagre(
|
|
352
|
+
const result = layoutWithDagre(nodesRef.current, edgesRef.current, {
|
|
353
|
+
...CANVAS_LAYOUT_DEFAULTS,
|
|
354
|
+
direction,
|
|
355
|
+
});
|
|
339
356
|
reconcile(result.nodes, result.edges);
|
|
340
357
|
},
|
|
341
|
-
[
|
|
358
|
+
[reconcile]
|
|
342
359
|
);
|
|
343
360
|
|
|
344
361
|
const handleAddRepository = useCallback(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { createContext, useContext, type ReactNode } from 'react';
|
|
3
|
+
import { createContext, useContext, useMemo, type ReactNode } from 'react';
|
|
4
4
|
import {
|
|
5
5
|
useAgentEvents,
|
|
6
6
|
type UseAgentEventsOptions,
|
|
@@ -18,7 +18,12 @@ interface AgentEventsProviderProps extends UseAgentEventsOptions {
|
|
|
18
18
|
* Wrap the app once; use `useAgentEventsContext()` to read.
|
|
19
19
|
*/
|
|
20
20
|
export function AgentEventsProvider({ children, runId }: AgentEventsProviderProps) {
|
|
21
|
-
const
|
|
21
|
+
const { events, lastEvent, connectionStatus } = useAgentEvents({ runId });
|
|
22
|
+
|
|
23
|
+
const value = useMemo<UseAgentEventsResult>(
|
|
24
|
+
() => ({ events, lastEvent, connectionStatus }),
|
|
25
|
+
[events, lastEvent, connectionStatus]
|
|
26
|
+
);
|
|
22
27
|
|
|
23
28
|
return <AgentEventsContext.Provider value={value}>{children}</AgentEventsContext.Provider>;
|
|
24
29
|
}
|
|
@@ -16,6 +16,8 @@ export interface UseAgentEventsResult {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const SW_PATH = '/agent-events-sw.js';
|
|
19
|
+
const MAX_EVENTS = 500;
|
|
20
|
+
const PRUNE_KEEP = 250;
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Hook that receives real-time agent notification events via a Service Worker.
|
|
@@ -38,7 +40,12 @@ export function useAgentEvents(options?: UseAgentEventsOptions): UseAgentEventsR
|
|
|
38
40
|
|
|
39
41
|
if (msg.type === 'notification') {
|
|
40
42
|
const parsed = msg.data as NotificationEvent;
|
|
41
|
-
|
|
43
|
+
// eslint-disable-next-line no-console
|
|
44
|
+
console.log('[SSE] event received:', parsed.eventType, parsed);
|
|
45
|
+
setEvents((prev) => {
|
|
46
|
+
const next = [...prev, parsed];
|
|
47
|
+
return next.length > MAX_EVENTS ? next.slice(-PRUNE_KEEP) : next;
|
|
48
|
+
});
|
|
42
49
|
setLastEvent(parsed);
|
|
43
50
|
} else if (msg.type === 'status') {
|
|
44
51
|
setConnectionStatus(msg.status as ConnectionStatus);
|
|
@@ -197,7 +204,10 @@ function connectDirectEventSource(
|
|
|
197
204
|
|
|
198
205
|
es.addEventListener('notification', ((event: MessageEvent) => {
|
|
199
206
|
const parsed: NotificationEvent = JSON.parse(event.data);
|
|
200
|
-
setEvents((prev) =>
|
|
207
|
+
setEvents((prev) => {
|
|
208
|
+
const next = [...prev, parsed];
|
|
209
|
+
return next.length > MAX_EVENTS ? next.slice(-PRUNE_KEEP) : next;
|
|
210
|
+
});
|
|
201
211
|
setLastEvent(parsed);
|
|
202
212
|
}) as EventListener);
|
|
203
213
|
}
|
|
@@ -48,6 +48,12 @@ export function useDeployAction(input: DeployActionInput | null): DeployActionSt
|
|
|
48
48
|
const errorTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
49
49
|
const pollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
50
50
|
const mountedRef = useRef(true);
|
|
51
|
+
const statusRef = useRef(status);
|
|
52
|
+
|
|
53
|
+
// Keep statusRef in sync with latest status
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
statusRef.current = status;
|
|
56
|
+
}, [status]);
|
|
51
57
|
|
|
52
58
|
// Track mounted state
|
|
53
59
|
useEffect(() => {
|
|
@@ -96,15 +102,17 @@ export function useDeployAction(input: DeployActionInput | null): DeployActionSt
|
|
|
96
102
|
setUrl(null);
|
|
97
103
|
stopPolling();
|
|
98
104
|
} else {
|
|
99
|
-
if (result.state !==
|
|
100
|
-
log.info(
|
|
105
|
+
if (result.state !== statusRef.current) {
|
|
106
|
+
log.info(
|
|
107
|
+
`poll state changed: ${statusRef.current} → ${result.state}, url=${result.url}`
|
|
108
|
+
);
|
|
101
109
|
}
|
|
102
110
|
setStatus(result.state as DeploymentState);
|
|
103
111
|
setUrl(result.url);
|
|
104
112
|
}
|
|
105
113
|
}, POLL_INTERVAL);
|
|
106
114
|
},
|
|
107
|
-
[stopPolling
|
|
115
|
+
[stopPolling]
|
|
108
116
|
);
|
|
109
117
|
|
|
110
118
|
const handleDeploy = useCallback(async () => {
|