@shepai/cli 1.107.1 → 1.107.2
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.map +1 -1
- package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.js +2 -2
- package/dist/packages/core/src/application/use-cases/features/create/metadata-generator.d.ts +5 -0
- package/dist/packages/core/src/application/use-cases/features/create/metadata-generator.d.ts.map +1 -1
- package/dist/packages/core/src/application/use-cases/features/create/metadata-generator.js +31 -13
- package/dist/packages/core/src/application/use-cases/repositories/add-repository.use-case.d.ts.map +1 -1
- package/dist/packages/core/src/application/use-cases/repositories/add-repository.use-case.js +6 -2
- package/dist/packages/core/src/infrastructure/di/container.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/di/container.js +13 -3
- package/dist/packages/core/src/infrastructure/repositories/sqlite-feature.repository.js +4 -4
- package/dist/packages/core/src/infrastructure/repositories/sqlite-repository.repository.js +4 -4
- package/dist/packages/core/src/infrastructure/services/agents/common/executors/claude-code-executor.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/common/executors/claude-code-executor.service.js +9 -0
- package/dist/packages/core/src/infrastructure/services/agents/common/executors/cursor-executor.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/common/executors/cursor-executor.service.js +17 -7
- package/dist/packages/core/src/infrastructure/services/agents/common/executors/dev-executor.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/common/executors/dev-executor.service.js +5 -3
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.js +1 -0
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/evidence-prompts.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/evidence-prompts.js +4 -3
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/repair.node.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/repair.node.js +2 -1
- package/dist/packages/core/src/infrastructure/services/agents/sessions/claude-code-session.repository.js +1 -1
- package/dist/packages/core/src/infrastructure/services/attachment-storage.service.js +3 -3
- package/dist/packages/core/src/infrastructure/services/deployment/deployment.service.d.ts +1 -1
- package/dist/packages/core/src/infrastructure/services/deployment/deployment.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/deployment/deployment.service.js +8 -7
- package/dist/packages/core/src/infrastructure/services/git/worktree.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/git/worktree.service.js +18 -5
- package/dist/packages/core/src/infrastructure/services/ide-launchers/compute-worktree-path.js +1 -1
- package/dist/packages/core/src/infrastructure/services/ide-launchers/json-driven-ide-launcher.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/ide-launchers/json-driven-ide-launcher.service.js +6 -8
- package/dist/packages/core/src/infrastructure/services/process/tree-kill.d.ts +12 -0
- package/dist/packages/core/src/infrastructure/services/process/tree-kill.d.ts.map +1 -0
- package/dist/packages/core/src/infrastructure/services/process/tree-kill.js +11 -0
- package/dist/packages/core/src/infrastructure/services/tool-installer/binary-exists.d.ts +25 -0
- package/dist/packages/core/src/infrastructure/services/tool-installer/binary-exists.d.ts.map +1 -0
- package/dist/packages/core/src/infrastructure/services/tool-installer/binary-exists.js +32 -0
- package/dist/packages/core/src/infrastructure/services/tool-installer/tool-installer.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/tool-installer/tool-installer.service.js +4 -27
- package/dist/packages/core/src/infrastructure/services/tool-installer/tools/alacritty.json +3 -2
- package/dist/packages/core/src/infrastructure/services/tool-installer/tools/claude-code.json +3 -2
- package/dist/packages/core/src/infrastructure/services/tool-installer/tools/cursor-cli.json +3 -2
- package/dist/packages/core/src/infrastructure/services/tool-installer/tools/cursor.json +2 -1
- package/dist/packages/core/src/infrastructure/services/tool-installer/tools/gh.json +2 -1
- package/dist/packages/core/src/infrastructure/services/tool-installer/tools/git.json +2 -1
- package/dist/packages/core/src/infrastructure/services/tool-installer/tools/vscode.json +2 -1
- package/dist/packages/core/src/infrastructure/services/tool-installer/tools/warp.json +3 -2
- package/dist/packages/core/src/infrastructure/services/tool-installer/tools/windsurf.json +3 -2
- package/dist/packages/core/src/infrastructure/services/tool-installer/tools/zed.json +3 -2
- package/dist/src/presentation/cli/commands/daemon/start-daemon.d.ts.map +1 -1
- package/dist/src/presentation/cli/commands/daemon/start-daemon.js +1 -0
- package/dist/src/presentation/cli/commands/daemon/stop-daemon.d.ts.map +1 -1
- package/dist/src/presentation/cli/commands/daemon/stop-daemon.js +5 -4
- package/dist/src/presentation/cli/commands/upgrade.command.d.ts.map +1 -1
- package/dist/src/presentation/cli/commands/upgrade.command.js +4 -0
- package/dist/src/presentation/web/app/actions/deploy-repository.d.ts.map +1 -1
- package/dist/src/presentation/web/app/actions/deploy-repository.js +2 -1
- package/dist/src/presentation/web/app/actions/open-folder.d.ts.map +1 -1
- package/dist/src/presentation/web/app/actions/open-folder.js +4 -2
- package/dist/src/presentation/web/app/actions/open-ide.d.ts.map +1 -1
- package/dist/src/presentation/web/app/actions/open-ide.js +2 -1
- package/dist/src/presentation/web/app/actions/open-shell.d.ts.map +1 -1
- package/dist/src/presentation/web/app/actions/open-shell.js +4 -2
- package/dist/src/presentation/web/app/build-graph-nodes.d.ts.map +1 -1
- package/dist/src/presentation/web/app/build-graph-nodes.js +8 -5
- 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 +10 -20
- package/dist/src/presentation/web/components/common/merge-review/diff-view.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 +12 -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 +32 -8
- package/dist/src/presentation/web/hooks/use-graph-state.d.ts +14 -0
- package/dist/src/presentation/web/hooks/use-graph-state.d.ts.map +1 -1
- package/dist/src/presentation/web/hooks/use-graph-state.js +35 -1
- package/dist/src/presentation/web/lib/derive-graph.d.ts.map +1 -1
- package/dist/src/presentation/web/lib/derive-graph.js +4 -2
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +10 -6
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/required-server-files.js +2 -2
- package/web/.next/required-server-files.json +2 -2
- package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +20 -20
- 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]/[tab]/page/server-reference-manifest.json +26 -26
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +26 -26
- 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 +18 -18
- 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 +20 -20
- 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]/[tab]/page/server-reference-manifest.json +26 -26
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +26 -26
- 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 +18 -18
- 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 +18 -18
- 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/settings/page/server-reference-manifest.json +5 -5
- package/web/.next/server/app/settings/page.js.nft.json +1 -1
- package/web/.next/server/app/settings/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_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_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/403f9_next_dist_esm_build_templates_app-route_370c43b1.js +1 -1
- package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_370c43b1.js.map +1 -1
- package/web/.next/server/chunks/[root-of-the-server]__a402b567._.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_a1104008.js +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_a1104008.js.map +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_92642200.js +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_92642200.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__039967e3._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__039967e3._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__07a39ff2._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__24dd0927._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__24dd0927._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__2773312c._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__2773312c._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__5d87af2e._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__845a9d2a._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__845a9d2a._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__89e8a5cc._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__89e8a5cc._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__cbd8062c._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__cbd8062c._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__e70c5627._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__e70c5627._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_135edb9c._.js +1 -1
- package/web/.next/server/chunks/ssr/_135edb9c._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_8dacd1e7._.js +1 -1
- package/web/.next/server/chunks/ssr/_8dacd1e7._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_cc77a143._.js +1 -1
- package/web/.next/server/chunks/ssr/_cc77a143._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{_3c0076bc._.js → _e2681489._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_3c0076bc._.js.map → _e2681489._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/{_89745126._.js → _f3612e5a._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_89745126._.js.map → _f3612e5a._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/{_4d49c223._.js → _f891c054._.js} +2 -2
- package/web/.next/server/chunks/ssr/_f891c054._.js.map +1 -0
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_888633e1.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_888633e1.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_ce7cbc3c._.js +2 -2
- package/web/.next/server/chunks/ssr/src_presentation_web_ce7cbc3c._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_bf50c527._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_de814675._.js +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_e2604413._.js +2 -2
- package/web/.next/server/chunks/ssr/src_presentation_web_e2604413._.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 +31 -31
- package/web/.next/static/chunks/{9751a0c871920841.js → 2ffe2f11f4ca9bb4.js} +1 -1
- package/web/.next/static/chunks/{fa27e698e0daf727.js → 430e372a1ef8e0a1.js} +1 -1
- package/web/.next/static/chunks/{e429067d6277ac20.js → 87488a543d25cd04.js} +1 -1
- package/web/.next/static/chunks/{dd30ae99bcec5f69.js → 8f1322aea6f75e2d.js} +2 -2
- package/web/.next/static/chunks/{2c6298885de1484c.js → acce61815a6260e7.js} +1 -1
- package/web/.next/static/chunks/{eed22af4cf55faea.js → c3253a63b95ec66e.js} +1 -1
- package/web/.next/static/chunks/{2eab3bb5550eccac.js → f60e223f91508b6e.js} +1 -1
- package/web/.next/static/chunks/{5ebc63ae73de50a3.js → f7d21f36df719f6f.js} +1 -1
- package/web/.next/static/chunks/f8672a435cc52ee8.js +2 -0
- package/web/.next/static/chunks/{f28d2411c36c308b.js → fc986ec4aa601f4d.js} +1 -1
- package/web/.next/server/chunks/ssr/_4d49c223._.js.map +0 -1
- package/web/.next/static/chunks/281782a712ea79cb.js +0 -2
- /package/web/.next/static/{OYucSKP83TUMTV1zRbeB8 → ITseKg4TnnrOWm79esrvr}/_buildManifest.js +0 -0
- /package/web/.next/static/{OYucSKP83TUMTV1zRbeB8 → ITseKg4TnnrOWm79esrvr}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{OYucSKP83TUMTV1zRbeB8 → ITseKg4TnnrOWm79esrvr}/_ssgManifest.js +0 -0
package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.js
CHANGED
|
@@ -10,7 +10,6 @@ import { rejectFeature } from '../../../app/actions/reject-feature.js';
|
|
|
10
10
|
import { getFeatureArtifact } from '../../../app/actions/get-feature-artifact.js';
|
|
11
11
|
import { getResearchArtifact } from '../../../app/actions/get-research-artifact.js';
|
|
12
12
|
import { getMergeReviewData } from '../../../app/actions/get-merge-review-data.js';
|
|
13
|
-
import { deleteFeature } from '../../../app/actions/delete-feature.js';
|
|
14
13
|
import { useFeatureFlags } from '../../../hooks/feature-flags-context.js';
|
|
15
14
|
import { useSoundAction } from '../../../hooks/use-sound-action.js';
|
|
16
15
|
import { useGuardedDrawerClose } from '../../../hooks/drawer-close-guard.js';
|
|
@@ -33,7 +32,6 @@ import { useDrawerSync } from './use-drawer-sync.js';
|
|
|
33
32
|
export function FeatureDrawerClient({ view: initialView, urlTab }) {
|
|
34
33
|
const featureFlags = useFeatureFlags();
|
|
35
34
|
const router = useRouter();
|
|
36
|
-
const deleteSound = useSoundAction('delete');
|
|
37
35
|
const rejectSound = useSoundAction('reject');
|
|
38
36
|
// Track the view locally so SSE events can update the drawer type in real-time
|
|
39
37
|
const [view, setView] = useState(initialView);
|
|
@@ -255,24 +253,16 @@ export function FeatureDrawerClient({ view: initialView, urlTab }) {
|
|
|
255
253
|
const handleMergeApprove = useCallback(() => handleSimpleApprove('Merge'), [handleSimpleApprove]);
|
|
256
254
|
const handleDelete = useCallback(async (featureId, cleanup) => {
|
|
257
255
|
setIsDeleting(true);
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
catch {
|
|
270
|
-
toast.error('Failed to delete feature');
|
|
271
|
-
}
|
|
272
|
-
finally {
|
|
273
|
-
setIsDeleting(false);
|
|
274
|
-
}
|
|
275
|
-
}, [router, deleteSound]);
|
|
256
|
+
// Close the delete dialog and drawer before the server action so the
|
|
257
|
+
// user sees immediate feedback. We dispatch a DOM event so the canvas
|
|
258
|
+
// control center can run its own optimistic delete flow (deleting state,
|
|
259
|
+
// mutation guard, node removal) in parallel.
|
|
260
|
+
setDeleteDialogOpen(false);
|
|
261
|
+
window.dispatchEvent(new CustomEvent('shep:feature-delete-requested', {
|
|
262
|
+
detail: { featureId, cleanup },
|
|
263
|
+
}));
|
|
264
|
+
router.push('/');
|
|
265
|
+
}, [router]);
|
|
276
266
|
const handleRetry = useCallback(async (featureId) => {
|
|
277
267
|
const result = await resumeFeature(featureId);
|
|
278
268
|
if (result.error) {
|
|
@@ -26,5 +26,5 @@ function FileDiffItem({ file }) {
|
|
|
26
26
|
export function DiffView({ fileDiffs }) {
|
|
27
27
|
if (fileDiffs.length === 0)
|
|
28
28
|
return null;
|
|
29
|
-
return (_jsxs("div", { className: "border-border rounded-lg border", children: [_jsx("div", { className: "px-4 py-3", children: _jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx(FileText, { className: "text-muted-foreground h-4 w-4" }), _jsx("span", { className: "text-foreground text-xs font-semibold", children: "Changed Files" }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", fileDiffs.length, ")"] })] }) }), _jsx("div", { className: "border-border border-t", children: fileDiffs.map((file) => (_jsx(FileDiffItem, { file: file }, file.path))) })] }));
|
|
29
|
+
return (_jsxs("div", { className: "border-border rounded-lg border", children: [_jsx("div", { className: "px-4 py-3", children: _jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx(FileText, { className: "text-muted-foreground h-4 w-4" }), _jsx("span", { className: "text-foreground text-xs font-semibold", children: "Changed Files" }), _jsxs("span", { className: "text-muted-foreground text-[10px]", children: ["(", fileDiffs.length, ")"] })] }) }), _jsx("div", { className: "border-border border-t", children: fileDiffs.map((file) => (_jsx(FileDiffItem, { file: file }, `${file.status}-${file.path}`))) })] }));
|
|
30
30
|
}
|
package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-center-inner.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/control-center/control-center-inner.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAY,MAAM,eAAe,CAAC;AAGpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAsB5E,UAAU,uBAAuB;IAC/B,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,YAAY,EAAE,IAAI,EAAE,CAAC;CACtB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,uBAAuB,
|
|
1
|
+
{"version":3,"file":"control-center-inner.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/control-center/control-center-inner.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAY,MAAM,eAAe,CAAC;AAGpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAsB5E,UAAU,uBAAuB;IAC/B,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,YAAY,EAAE,IAAI,EAAE,CAAC;CACtB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,uBAAuB,2CAoUzF"}
|
package/dist/src/presentation/web/components/features/control-center/control-center-inner.js
CHANGED
|
@@ -174,6 +174,18 @@ export function ControlCenterInner({ initialNodes, initialEdges }) {
|
|
|
174
174
|
window.addEventListener('shep:feature-created', handler);
|
|
175
175
|
return () => window.removeEventListener('shep:feature-created', handler);
|
|
176
176
|
}, [nodes, createFeatureNode]);
|
|
177
|
+
// Listen for delete requests from the feature drawer (fires when the user
|
|
178
|
+
// confirms delete inside the drawer). Delegates to handleDeleteFeature so
|
|
179
|
+
// the canvas gets optimistic state, mutation guard, and node removal.
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
const handler = (e) => {
|
|
182
|
+
const { featureId, cleanup } = e
|
|
183
|
+
.detail;
|
|
184
|
+
handleDeleteFeature(featureId, cleanup);
|
|
185
|
+
};
|
|
186
|
+
window.addEventListener('shep:feature-delete-requested', handler);
|
|
187
|
+
return () => window.removeEventListener('shep:feature-delete-requested', handler);
|
|
188
|
+
}, [handleDeleteFeature]);
|
|
177
189
|
// Wire callbacks into derived node data (via ref — no re-render).
|
|
178
190
|
useEffect(() => {
|
|
179
191
|
setCallbacks({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-control-center-state.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/control-center/use-control-center-state.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAC;AAcjC,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAM7E,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,aAAa,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,cAAc,CAAC,EAAE,KAAK,IAAI,CAAC;IAC/D,aAAa,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;IAChD,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/E,YAAY,EAAE,CAAC,SAAS,EAAE,eAAe,KAAK,IAAI,CAAC;IACnD,mBAAmB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACpE,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,sBAAsB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,iBAAiB,EAAE,CACjB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,YAAY,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,EACvC,QAAQ,CAAC,EAAE,MAAM,KACd,MAAM,CAAC;IACZ,wDAAwD;IACxD,wBAAwB,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACxE,gDAAgD;IAChD,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,kBAAkB,GAAG,SAAS,CAAC;IACtE,0EAA0E;IAC1E,YAAY,EAAE,CAAC,SAAS,EAAE,cAAc,KAAK,IAAI,CAAC;CACnD;AAQD,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,cAAc,EAAE,EAC9B,YAAY,EAAE,IAAI,EAAE,GACnB,kBAAkB,
|
|
1
|
+
{"version":3,"file":"use-control-center-state.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/features/control-center/use-control-center-state.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAC;AAcjC,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAM7E,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,aAAa,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,cAAc,CAAC,EAAE,KAAK,IAAI,CAAC;IAC/D,aAAa,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;IAChD,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/E,YAAY,EAAE,CAAC,SAAS,EAAE,eAAe,KAAK,IAAI,CAAC;IACnD,mBAAmB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACpE,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,sBAAsB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,iBAAiB,EAAE,CACjB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,YAAY,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,EACvC,QAAQ,CAAC,EAAE,MAAM,KACd,MAAM,CAAC;IACZ,wDAAwD;IACxD,wBAAwB,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACxE,gDAAgD;IAChD,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,kBAAkB,GAAG,SAAS,CAAC;IACtE,0EAA0E;IAC1E,YAAY,EAAE,CAAC,SAAS,EAAE,cAAc,KAAK,IAAI,CAAC;CACnD;AAQD,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,cAAc,EAAE,EAC9B,YAAY,EAAE,IAAI,EAAE,GACnB,kBAAkB,CAudpB"}
|
package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js
CHANGED
|
@@ -23,7 +23,7 @@ export function useControlCenterState(initialNodes, initialEdges) {
|
|
|
23
23
|
const router = useRouter();
|
|
24
24
|
const deleteSound = useSoundAction('delete');
|
|
25
25
|
const createSound = useSoundAction('create');
|
|
26
|
-
const { nodes, edges, reconcile, updateFeature, addPendingFeature, removeFeature, restoreFeature, addRepository: addRepositoryToMap, removeRepository, replaceRepository, getFeatureRepositoryPath, getRepositoryData, getRepoMapSize, setCallbacks, } = useGraphState(initialNodes, initialEdges);
|
|
26
|
+
const { nodes, edges, reconcile, updateFeature, addPendingFeature, removeFeature, restoreFeature, addRepository: addRepositoryToMap, removeRepository, replaceRepository, getFeatureRepositoryPath, getRepositoryData, getRepoMapSize, setCallbacks, beginMutation, endMutation, isMutating, } = useGraphState(initialNodes, initialEdges);
|
|
27
27
|
// Refs for stable access to latest nodes/edges without callback recreation
|
|
28
28
|
const nodesRef = useRef(nodes);
|
|
29
29
|
const edgesRef = useRef(edges);
|
|
@@ -156,6 +156,10 @@ export function useControlCenterState(initialNodes, initialEdges) {
|
|
|
156
156
|
useEffect(() => {
|
|
157
157
|
log.debug(`polling enabled (${POLL_INTERVAL_MS}ms interval)`);
|
|
158
158
|
const timer = setInterval(async () => {
|
|
159
|
+
// Skip fetch entirely while a mutation is in-flight — the response
|
|
160
|
+
// would contain pre-mutation data that reconcile would discard anyway.
|
|
161
|
+
if (isMutating())
|
|
162
|
+
return;
|
|
159
163
|
try {
|
|
160
164
|
// Use a plain fetch instead of a server action so the poll
|
|
161
165
|
// doesn't trigger the Next.js "Rendering…" indicator.
|
|
@@ -173,7 +177,7 @@ export function useControlCenterState(initialNodes, initialEdges) {
|
|
|
173
177
|
log.debug('polling disabled');
|
|
174
178
|
clearInterval(timer);
|
|
175
179
|
};
|
|
176
|
-
}, [reconcile]);
|
|
180
|
+
}, [reconcile, isMutating]);
|
|
177
181
|
// onNodesChange is a no-op: nodes are derived from domain Maps.
|
|
178
182
|
// Since nodesDraggable=false and elementsSelectable=false, only React Flow's
|
|
179
183
|
// internal replace/dimensions changes come through — we ignore them all.
|
|
@@ -205,6 +209,7 @@ export function useControlCenterState(initialNodes, initialEdges) {
|
|
|
205
209
|
const handleRetryFeature = useCallback((featureId) => {
|
|
206
210
|
const nodeId = `feat-${featureId}`;
|
|
207
211
|
// Optimistic: switch to running state
|
|
212
|
+
beginMutation();
|
|
208
213
|
updateFeature(nodeId, { state: 'running' });
|
|
209
214
|
resumeFeature(featureId)
|
|
210
215
|
.then((result) => {
|
|
@@ -219,8 +224,9 @@ export function useControlCenterState(initialNodes, initialEdges) {
|
|
|
219
224
|
.catch(() => {
|
|
220
225
|
updateFeature(nodeId, { state: 'error' });
|
|
221
226
|
toast.error('Failed to resume feature');
|
|
222
|
-
})
|
|
223
|
-
|
|
227
|
+
})
|
|
228
|
+
.finally(() => endMutation());
|
|
229
|
+
}, [updateFeature, beginMutation, endMutation]);
|
|
224
230
|
const handleDeleteFeature = useCallback((featureId, cleanup) => {
|
|
225
231
|
const nodeId = `feat-${featureId}`;
|
|
226
232
|
// Collect all descendant feature node IDs (children, grandchildren, etc.)
|
|
@@ -244,6 +250,7 @@ export function useControlCenterState(initialNodes, initialEdges) {
|
|
|
244
250
|
}
|
|
245
251
|
}
|
|
246
252
|
// Optimistic: show "deleting" state on parent AND all descendants
|
|
253
|
+
beginMutation();
|
|
247
254
|
updateFeature(nodeId, { state: 'deleting' });
|
|
248
255
|
for (const childId of descendants) {
|
|
249
256
|
updateFeature(childId, { state: 'deleting' });
|
|
@@ -276,8 +283,9 @@ export function useControlCenterState(initialNodes, initialEdges) {
|
|
|
276
283
|
updateFeature(nid, { state: prevState });
|
|
277
284
|
}
|
|
278
285
|
toast.error('Failed to delete feature');
|
|
279
|
-
})
|
|
280
|
-
|
|
286
|
+
})
|
|
287
|
+
.finally(() => endMutation());
|
|
288
|
+
}, [router, deleteSound, updateFeature, removeFeature, beginMutation, endMutation]);
|
|
281
289
|
const handleDeleteRepository = useCallback(async (repositoryId) => {
|
|
282
290
|
const repoNodeId = `repo-${repositoryId}`;
|
|
283
291
|
// Find children of this repo via edges
|
|
@@ -299,6 +307,7 @@ export function useControlCenterState(initialNodes, initialEdges) {
|
|
|
299
307
|
}
|
|
300
308
|
};
|
|
301
309
|
// Optimistic: remove repo + children
|
|
310
|
+
beginMutation();
|
|
302
311
|
removeRepository(repoNodeId);
|
|
303
312
|
for (const childId of childFeatureIds) {
|
|
304
313
|
removeFeature(childId);
|
|
@@ -316,6 +325,9 @@ export function useControlCenterState(initialNodes, initialEdges) {
|
|
|
316
325
|
toast.error('Failed to remove repository');
|
|
317
326
|
rollback();
|
|
318
327
|
}
|
|
328
|
+
finally {
|
|
329
|
+
endMutation();
|
|
330
|
+
}
|
|
319
331
|
}, [
|
|
320
332
|
deleteSound,
|
|
321
333
|
removeRepository,
|
|
@@ -323,6 +335,8 @@ export function useControlCenterState(initialNodes, initialEdges) {
|
|
|
323
335
|
addRepositoryToMap,
|
|
324
336
|
restoreFeature,
|
|
325
337
|
getRepositoryData,
|
|
338
|
+
beginMutation,
|
|
339
|
+
endMutation,
|
|
326
340
|
]);
|
|
327
341
|
const handleLayout = useCallback((direction) => {
|
|
328
342
|
// Layout is applied via reconcile on next server prop update.
|
|
@@ -340,6 +354,7 @@ export function useControlCenterState(initialNodes, initialEdges) {
|
|
|
340
354
|
.replace(/[\\/]+$/, '')
|
|
341
355
|
.split(/[\\/]/)
|
|
342
356
|
.pop() ?? path;
|
|
357
|
+
beginMutation();
|
|
343
358
|
addRepositoryToMap(tempId, {
|
|
344
359
|
name: repoName,
|
|
345
360
|
repositoryPath: path,
|
|
@@ -366,9 +381,18 @@ export function useControlCenterState(initialNodes, initialEdges) {
|
|
|
366
381
|
.catch(() => {
|
|
367
382
|
removeRepository(tempId);
|
|
368
383
|
toast.error('Failed to add repository');
|
|
369
|
-
})
|
|
384
|
+
})
|
|
385
|
+
.finally(() => endMutation());
|
|
370
386
|
return { wasEmpty, repoPath: path };
|
|
371
|
-
}, [
|
|
387
|
+
}, [
|
|
388
|
+
addRepositoryToMap,
|
|
389
|
+
removeRepository,
|
|
390
|
+
replaceRepository,
|
|
391
|
+
createSound,
|
|
392
|
+
getRepoMapSize,
|
|
393
|
+
beginMutation,
|
|
394
|
+
endMutation,
|
|
395
|
+
]);
|
|
372
396
|
return {
|
|
373
397
|
nodes,
|
|
374
398
|
edges,
|
|
@@ -12,6 +12,7 @@ export interface UseGraphStateReturn {
|
|
|
12
12
|
/**
|
|
13
13
|
* Reconcile domain Maps with fresh server data (new initialNodes/initialEdges).
|
|
14
14
|
* Preserves pending (creating) nodes unless a matching real feature appears.
|
|
15
|
+
* No-ops when a mutation is in-flight (see beginMutation/endMutation).
|
|
15
16
|
*/
|
|
16
17
|
reconcile: (newNodes: CanvasNodeType[], newEdges: Edge[]) => void;
|
|
17
18
|
/** Update a feature's state/lifecycle/name (e.g., from SSE events). */
|
|
@@ -38,6 +39,19 @@ export interface UseGraphStateReturn {
|
|
|
38
39
|
getRepoMapSize: () => number;
|
|
39
40
|
/** Update callbacks injected into node data (does NOT trigger re-render). */
|
|
40
41
|
setCallbacks: (callbacks: GraphCallbacks) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Signal that an optimistic mutation has started. While any mutation is
|
|
44
|
+
* in-flight, `reconcile` becomes a no-op so stale poll data cannot
|
|
45
|
+
* overwrite optimistic state. Calls are ref-counted — nest freely.
|
|
46
|
+
*/
|
|
47
|
+
beginMutation: () => void;
|
|
48
|
+
/**
|
|
49
|
+
* Signal that an optimistic mutation has resolved. Adds a one-poll-interval
|
|
50
|
+
* cooldown (default 3 s) so the next poll fetches post-mutation data.
|
|
51
|
+
*/
|
|
52
|
+
endMutation: (cooldownMs?: number) => void;
|
|
53
|
+
/** Whether a mutation is currently in-flight (for polling skip logic). */
|
|
54
|
+
isMutating: () => boolean;
|
|
41
55
|
}
|
|
42
56
|
type FeatureDataUpdates = Partial<Pick<FeatureNodeData, 'state' | 'lifecycle' | 'name' | 'description'>>;
|
|
43
57
|
export declare function useGraphState(initialNodes: CanvasNodeType[], initialEdges: Edge[]): UseGraphStateReturn;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-graph-state.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/hooks/use-graph-state.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAY,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,EAEL,KAAK,YAAY,EAEjB,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;AAG5B,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,WAAW,mBAAmB;IAClC,mDAAmD;IACnD,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,mDAAmD;IACnD,KAAK,EAAE,IAAI,EAAE,CAAC;IACd
|
|
1
|
+
{"version":3,"file":"use-graph-state.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/hooks/use-graph-state.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAY,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,EAEL,KAAK,YAAY,EAEjB,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;AAG5B,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,WAAW,mBAAmB;IAClC,mDAAmD;IACnD,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,mDAAmD;IACnD,KAAK,EAAE,IAAI,EAAE,CAAC;IACd;;;;OAIG;IACH,SAAS,EAAE,CAAC,QAAQ,EAAE,cAAc,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IAClE,uEAAuE;IACvE,aAAa,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC5E,kEAAkE;IAClE,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1F,yDAAyD;IACzD,oBAAoB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,sDAAsD;IACtD,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,uDAAuD;IACvD,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC9D,2DAA2D;IAC3D,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAClE,gCAAgC;IAChC,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,yDAAyD;IACzD,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACtF,gEAAgE;IAChE,wBAAwB,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACxE,yDAAyD;IACzD,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,kBAAkB,GAAG,SAAS,CAAC;IACtE,+EAA+E;IAC/E,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B,6EAA6E;IAC7E,YAAY,EAAE,CAAC,SAAS,EAAE,cAAc,KAAK,IAAI,CAAC;IAClD;;;;OAIG;IACH,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B;;;OAGG;IACH,WAAW,EAAE,CAAC,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,0EAA0E;IAC1E,UAAU,EAAE,MAAM,OAAO,CAAC;CAC3B;AAuCD,KAAK,kBAAkB,GAAG,OAAO,CAC/B,IAAI,CAAC,eAAe,EAAE,OAAO,GAAG,WAAW,GAAG,MAAM,GAAG,aAAa,CAAC,CACtE,CAAC;AA8BF,wBAAgB,aAAa,CAC3B,YAAY,EAAE,cAAc,EAAE,EAC9B,YAAY,EAAE,IAAI,EAAE,GACnB,mBAAmB,CA6ZrB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useState, useMemo, useCallback, useRef } from 'react';
|
|
2
|
+
import { useState, useMemo, useCallback, useRef, useEffect } from 'react';
|
|
3
3
|
import { deriveGraph, } from '../lib/derive-graph.js';
|
|
4
4
|
import { layoutWithDagre, CANVAS_LAYOUT_DEFAULTS } from '../lib/layout-with-dagre.js';
|
|
5
5
|
/** Parse initialNodes + initialEdges into domain Maps. */
|
|
@@ -71,6 +71,10 @@ export function useGraphState(initialNodes, initialEdges) {
|
|
|
71
71
|
// This prevents a stale poll (that fetched data before the server action completed)
|
|
72
72
|
// from wiping a repo that the client has already added.
|
|
73
73
|
const protectedRepoIdsRef = useRef(new Set());
|
|
74
|
+
// Mutation guard: ref-counted counter. While > 0, reconcile is a no-op
|
|
75
|
+
// so stale poll data cannot overwrite optimistic state.
|
|
76
|
+
const mutationCountRef = useRef(0);
|
|
77
|
+
const mutationTimersRef = useRef(new Set());
|
|
74
78
|
// Track 'deleting' features that the server no longer returns.
|
|
75
79
|
// Retained for one extra reconcile cycle so the user sees the deleting state
|
|
76
80
|
// before the node disappears from the canvas.
|
|
@@ -154,6 +158,11 @@ export function useGraphState(initialNodes, initialEdges) {
|
|
|
154
158
|
}, [derived]);
|
|
155
159
|
// --- Mutations ---
|
|
156
160
|
const reconcile = useCallback((newNodes, newEdges) => {
|
|
161
|
+
// Skip reconciliation while an optimistic mutation is in-flight.
|
|
162
|
+
// Stale poll data fetched before the mutation completed would overwrite
|
|
163
|
+
// the optimistic state, causing flicker (nodes disappearing/reappearing).
|
|
164
|
+
if (mutationCountRef.current > 0)
|
|
165
|
+
return;
|
|
157
166
|
const { featureMap: newFeatureMap, repoMap: newRepoMap } = parseMaps(newNodes, newEdges);
|
|
158
167
|
// Precompute match keys so we don't scan newFeatureMap.values() per pending entry
|
|
159
168
|
const realFeatureKeys = new Set([...newFeatureMap.values()].map((e) => `${e.data.name}\0${e.data.repositoryPath}`));
|
|
@@ -355,6 +364,28 @@ export function useGraphState(initialNodes, initialEdges) {
|
|
|
355
364
|
const setCallbacks = useCallback((callbacks) => {
|
|
356
365
|
callbacksRef.current = callbacks;
|
|
357
366
|
}, []);
|
|
367
|
+
const beginMutation = useCallback(() => {
|
|
368
|
+
mutationCountRef.current++;
|
|
369
|
+
}, []);
|
|
370
|
+
const endMutation = useCallback((cooldownMs = 3_000) => {
|
|
371
|
+
// Delay decrement by one poll interval so the next fetch after the
|
|
372
|
+
// mutation returns post-mutation data, not a stale in-flight response.
|
|
373
|
+
const timer = setTimeout(() => {
|
|
374
|
+
mutationCountRef.current = Math.max(0, mutationCountRef.current - 1);
|
|
375
|
+
mutationTimersRef.current.delete(timer);
|
|
376
|
+
}, cooldownMs);
|
|
377
|
+
mutationTimersRef.current.add(timer);
|
|
378
|
+
}, []);
|
|
379
|
+
const isMutating = useCallback(() => mutationCountRef.current > 0, []);
|
|
380
|
+
// Cleanup cooldown timers on unmount
|
|
381
|
+
useEffect(() => {
|
|
382
|
+
const timers = mutationTimersRef.current;
|
|
383
|
+
return () => {
|
|
384
|
+
for (const timer of timers)
|
|
385
|
+
clearTimeout(timer);
|
|
386
|
+
timers.clear();
|
|
387
|
+
};
|
|
388
|
+
}, []);
|
|
358
389
|
return {
|
|
359
390
|
nodes,
|
|
360
391
|
edges,
|
|
@@ -371,5 +402,8 @@ export function useGraphState(initialNodes, initialEdges) {
|
|
|
371
402
|
getRepositoryData,
|
|
372
403
|
getRepoMapSize,
|
|
373
404
|
setCallbacks,
|
|
405
|
+
beginMutation,
|
|
406
|
+
endMutation,
|
|
407
|
+
isMutating,
|
|
374
408
|
};
|
|
375
409
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derive-graph.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/lib/derive-graph.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,qDAAqD;AACrD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,eAAe,CAAC;IACtB,sGAAsG;IACtG,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wDAAwD;AACxD,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,kBAAkB,CAAC;CAC1B;AAED,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC7B,4FAA4F;IAC5F,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,qEAAqE;IACrE,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,8CAA8C;IAC9C,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACjE,6EAA6E;IAC7E,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,iDAAiD;IACjD,kBAAkB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,qDAAqD;IACrD,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EACrC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EACrC,SAAS,CAAC,EAAE,cAAc,GACzB;IAAE,KAAK,EAAE,cAAc,EAAE,CAAC;IAAC,KAAK,EAAE,IAAI,EAAE,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"derive-graph.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/lib/derive-graph.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAC5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,qDAAqD;AACrD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,eAAe,CAAC;IACtB,sGAAsG;IACtG,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wDAAwD;AACxD,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,kBAAkB,CAAC;CAC1B;AAED,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC7B,4FAA4F;IAC5F,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,qEAAqE;IACrE,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,8CAA8C;IAC9C,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACjE,6EAA6E;IAC7E,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,iDAAiD;IACjD,kBAAkB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,qDAAqD;IACrD,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EACrC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EACrC,SAAS,CAAC,EAAE,cAAc,GACzB;IAAE,KAAK,EAAE,cAAc,EAAE,CAAC;IAAC,KAAK,EAAE,IAAI,EAAE,CAAA;CAAE,CAwI5C"}
|
|
@@ -17,11 +17,13 @@
|
|
|
17
17
|
export function deriveGraph(featureMap, repoMap, pendingMap, callbacks) {
|
|
18
18
|
const nodes = [];
|
|
19
19
|
const edges = [];
|
|
20
|
+
// Normalize path separators so Windows backslash paths match forward-slash paths
|
|
21
|
+
const normalizePath = (p) => p.replace(/\\/g, '/');
|
|
20
22
|
// Build a lookup: repositoryPath → repoNodeId
|
|
21
23
|
const repoByPath = new Map();
|
|
22
24
|
for (const [nodeId, entry] of repoMap) {
|
|
23
25
|
if (entry.data.repositoryPath) {
|
|
24
|
-
repoByPath.set(entry.data.repositoryPath, nodeId);
|
|
26
|
+
repoByPath.set(normalizePath(entry.data.repositoryPath), nodeId);
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
// Virtual repo nodes created for orphaned features (not in repoMap)
|
|
@@ -119,7 +121,7 @@ export function deriveGraph(featureMap, repoMap, pendingMap, callbacks) {
|
|
|
119
121
|
}
|
|
120
122
|
else {
|
|
121
123
|
// Repo→feature edge (matched by repositoryPath)
|
|
122
|
-
const repositoryPath = entry.data.repositoryPath;
|
|
124
|
+
const repositoryPath = normalizePath(entry.data.repositoryPath);
|
|
123
125
|
const repoNodeId = repoByPath.get(repositoryPath) ?? getOrCreateVirtualRepo(repositoryPath);
|
|
124
126
|
edges.push({
|
|
125
127
|
id: `edge-${repoNodeId}-${nodeId}`,
|