@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.
Files changed (211) hide show
  1. package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.d.ts.map +1 -1
  2. package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.js +2 -2
  3. package/dist/packages/core/src/application/use-cases/features/create/metadata-generator.d.ts +5 -0
  4. package/dist/packages/core/src/application/use-cases/features/create/metadata-generator.d.ts.map +1 -1
  5. package/dist/packages/core/src/application/use-cases/features/create/metadata-generator.js +31 -13
  6. package/dist/packages/core/src/application/use-cases/repositories/add-repository.use-case.d.ts.map +1 -1
  7. package/dist/packages/core/src/application/use-cases/repositories/add-repository.use-case.js +6 -2
  8. package/dist/packages/core/src/infrastructure/di/container.d.ts.map +1 -1
  9. package/dist/packages/core/src/infrastructure/di/container.js +13 -3
  10. package/dist/packages/core/src/infrastructure/repositories/sqlite-feature.repository.js +4 -4
  11. package/dist/packages/core/src/infrastructure/repositories/sqlite-repository.repository.js +4 -4
  12. package/dist/packages/core/src/infrastructure/services/agents/common/executors/claude-code-executor.service.d.ts.map +1 -1
  13. package/dist/packages/core/src/infrastructure/services/agents/common/executors/claude-code-executor.service.js +9 -0
  14. package/dist/packages/core/src/infrastructure/services/agents/common/executors/cursor-executor.service.d.ts.map +1 -1
  15. package/dist/packages/core/src/infrastructure/services/agents/common/executors/cursor-executor.service.js +17 -7
  16. package/dist/packages/core/src/infrastructure/services/agents/common/executors/dev-executor.service.d.ts.map +1 -1
  17. package/dist/packages/core/src/infrastructure/services/agents/common/executors/dev-executor.service.js +5 -3
  18. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.d.ts.map +1 -1
  19. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.js +1 -0
  20. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/evidence-prompts.d.ts.map +1 -1
  21. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/prompts/evidence-prompts.js +4 -3
  22. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/repair.node.d.ts.map +1 -1
  23. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/repair.node.js +2 -1
  24. package/dist/packages/core/src/infrastructure/services/agents/sessions/claude-code-session.repository.js +1 -1
  25. package/dist/packages/core/src/infrastructure/services/attachment-storage.service.js +3 -3
  26. package/dist/packages/core/src/infrastructure/services/deployment/deployment.service.d.ts +1 -1
  27. package/dist/packages/core/src/infrastructure/services/deployment/deployment.service.d.ts.map +1 -1
  28. package/dist/packages/core/src/infrastructure/services/deployment/deployment.service.js +8 -7
  29. package/dist/packages/core/src/infrastructure/services/git/worktree.service.d.ts.map +1 -1
  30. package/dist/packages/core/src/infrastructure/services/git/worktree.service.js +18 -5
  31. package/dist/packages/core/src/infrastructure/services/ide-launchers/compute-worktree-path.js +1 -1
  32. package/dist/packages/core/src/infrastructure/services/ide-launchers/json-driven-ide-launcher.service.d.ts.map +1 -1
  33. package/dist/packages/core/src/infrastructure/services/ide-launchers/json-driven-ide-launcher.service.js +6 -8
  34. package/dist/packages/core/src/infrastructure/services/process/tree-kill.d.ts +12 -0
  35. package/dist/packages/core/src/infrastructure/services/process/tree-kill.d.ts.map +1 -0
  36. package/dist/packages/core/src/infrastructure/services/process/tree-kill.js +11 -0
  37. package/dist/packages/core/src/infrastructure/services/tool-installer/binary-exists.d.ts +25 -0
  38. package/dist/packages/core/src/infrastructure/services/tool-installer/binary-exists.d.ts.map +1 -0
  39. package/dist/packages/core/src/infrastructure/services/tool-installer/binary-exists.js +32 -0
  40. package/dist/packages/core/src/infrastructure/services/tool-installer/tool-installer.service.d.ts.map +1 -1
  41. package/dist/packages/core/src/infrastructure/services/tool-installer/tool-installer.service.js +4 -27
  42. package/dist/packages/core/src/infrastructure/services/tool-installer/tools/alacritty.json +3 -2
  43. package/dist/packages/core/src/infrastructure/services/tool-installer/tools/claude-code.json +3 -2
  44. package/dist/packages/core/src/infrastructure/services/tool-installer/tools/cursor-cli.json +3 -2
  45. package/dist/packages/core/src/infrastructure/services/tool-installer/tools/cursor.json +2 -1
  46. package/dist/packages/core/src/infrastructure/services/tool-installer/tools/gh.json +2 -1
  47. package/dist/packages/core/src/infrastructure/services/tool-installer/tools/git.json +2 -1
  48. package/dist/packages/core/src/infrastructure/services/tool-installer/tools/vscode.json +2 -1
  49. package/dist/packages/core/src/infrastructure/services/tool-installer/tools/warp.json +3 -2
  50. package/dist/packages/core/src/infrastructure/services/tool-installer/tools/windsurf.json +3 -2
  51. package/dist/packages/core/src/infrastructure/services/tool-installer/tools/zed.json +3 -2
  52. package/dist/src/presentation/cli/commands/daemon/start-daemon.d.ts.map +1 -1
  53. package/dist/src/presentation/cli/commands/daemon/start-daemon.js +1 -0
  54. package/dist/src/presentation/cli/commands/daemon/stop-daemon.d.ts.map +1 -1
  55. package/dist/src/presentation/cli/commands/daemon/stop-daemon.js +5 -4
  56. package/dist/src/presentation/cli/commands/upgrade.command.d.ts.map +1 -1
  57. package/dist/src/presentation/cli/commands/upgrade.command.js +4 -0
  58. package/dist/src/presentation/web/app/actions/deploy-repository.d.ts.map +1 -1
  59. package/dist/src/presentation/web/app/actions/deploy-repository.js +2 -1
  60. package/dist/src/presentation/web/app/actions/open-folder.d.ts.map +1 -1
  61. package/dist/src/presentation/web/app/actions/open-folder.js +4 -2
  62. package/dist/src/presentation/web/app/actions/open-ide.d.ts.map +1 -1
  63. package/dist/src/presentation/web/app/actions/open-ide.js +2 -1
  64. package/dist/src/presentation/web/app/actions/open-shell.d.ts.map +1 -1
  65. package/dist/src/presentation/web/app/actions/open-shell.js +4 -2
  66. package/dist/src/presentation/web/app/build-graph-nodes.d.ts.map +1 -1
  67. package/dist/src/presentation/web/app/build-graph-nodes.js +8 -5
  68. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.d.ts.map +1 -1
  69. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.js +10 -20
  70. package/dist/src/presentation/web/components/common/merge-review/diff-view.js +1 -1
  71. package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
  72. package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +12 -0
  73. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
  74. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +32 -8
  75. package/dist/src/presentation/web/hooks/use-graph-state.d.ts +14 -0
  76. package/dist/src/presentation/web/hooks/use-graph-state.d.ts.map +1 -1
  77. package/dist/src/presentation/web/hooks/use-graph-state.js +35 -1
  78. package/dist/src/presentation/web/lib/derive-graph.d.ts.map +1 -1
  79. package/dist/src/presentation/web/lib/derive-graph.js +4 -2
  80. package/dist/tsconfig.build.tsbuildinfo +1 -1
  81. package/package.json +10 -6
  82. package/web/.next/BUILD_ID +1 -1
  83. package/web/.next/build-manifest.json +2 -2
  84. package/web/.next/fallback-build-manifest.json +2 -2
  85. package/web/.next/prerender-manifest.json +3 -3
  86. package/web/.next/required-server-files.js +2 -2
  87. package/web/.next/required-server-files.json +2 -2
  88. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +20 -20
  89. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  90. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  91. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page/server-reference-manifest.json +26 -26
  92. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js.nft.json +1 -1
  93. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
  94. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +26 -26
  95. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  96. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  97. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +18 -18
  98. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  99. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  100. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +20 -20
  101. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  102. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  103. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page/server-reference-manifest.json +26 -26
  104. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page.js.nft.json +1 -1
  105. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
  106. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +26 -26
  107. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  108. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  109. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +18 -18
  110. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  111. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  112. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +18 -18
  113. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  114. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  115. package/web/.next/server/app/_global-error.html +2 -2
  116. package/web/.next/server/app/_global-error.rsc +1 -1
  117. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  118. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  119. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  120. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  121. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  122. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  123. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  124. package/web/.next/server/app/settings/page/server-reference-manifest.json +5 -5
  125. package/web/.next/server/app/settings/page.js.nft.json +1 -1
  126. package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  127. package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  128. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  129. package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  130. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  131. package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  132. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  133. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_370c43b1.js +1 -1
  134. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_370c43b1.js.map +1 -1
  135. package/web/.next/server/chunks/[root-of-the-server]__a402b567._.js +1 -1
  136. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  137. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  138. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_a1104008.js +1 -1
  139. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_a1104008.js.map +1 -1
  140. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_92642200.js +1 -1
  141. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_92642200.js.map +1 -1
  142. package/web/.next/server/chunks/ssr/[root-of-the-server]__039967e3._.js +1 -1
  143. package/web/.next/server/chunks/ssr/[root-of-the-server]__039967e3._.js.map +1 -1
  144. package/web/.next/server/chunks/ssr/[root-of-the-server]__07a39ff2._.js +1 -1
  145. package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js +1 -1
  146. package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js.map +1 -1
  147. package/web/.next/server/chunks/ssr/[root-of-the-server]__24dd0927._.js +2 -2
  148. package/web/.next/server/chunks/ssr/[root-of-the-server]__24dd0927._.js.map +1 -1
  149. package/web/.next/server/chunks/ssr/[root-of-the-server]__2773312c._.js +1 -1
  150. package/web/.next/server/chunks/ssr/[root-of-the-server]__2773312c._.js.map +1 -1
  151. package/web/.next/server/chunks/ssr/[root-of-the-server]__5d87af2e._.js +1 -1
  152. package/web/.next/server/chunks/ssr/[root-of-the-server]__845a9d2a._.js +1 -1
  153. package/web/.next/server/chunks/ssr/[root-of-the-server]__845a9d2a._.js.map +1 -1
  154. package/web/.next/server/chunks/ssr/[root-of-the-server]__89e8a5cc._.js +2 -2
  155. package/web/.next/server/chunks/ssr/[root-of-the-server]__89e8a5cc._.js.map +1 -1
  156. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +2 -2
  157. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
  158. package/web/.next/server/chunks/ssr/[root-of-the-server]__cbd8062c._.js +1 -1
  159. package/web/.next/server/chunks/ssr/[root-of-the-server]__cbd8062c._.js.map +1 -1
  160. package/web/.next/server/chunks/ssr/[root-of-the-server]__e70c5627._.js +1 -1
  161. package/web/.next/server/chunks/ssr/[root-of-the-server]__e70c5627._.js.map +1 -1
  162. package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js +1 -1
  163. package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js.map +1 -1
  164. package/web/.next/server/chunks/ssr/_135edb9c._.js +1 -1
  165. package/web/.next/server/chunks/ssr/_135edb9c._.js.map +1 -1
  166. package/web/.next/server/chunks/ssr/_8dacd1e7._.js +1 -1
  167. package/web/.next/server/chunks/ssr/_8dacd1e7._.js.map +1 -1
  168. package/web/.next/server/chunks/ssr/_cc77a143._.js +1 -1
  169. package/web/.next/server/chunks/ssr/_cc77a143._.js.map +1 -1
  170. package/web/.next/server/chunks/ssr/{_3c0076bc._.js → _e2681489._.js} +2 -2
  171. package/web/.next/server/chunks/ssr/{_3c0076bc._.js.map → _e2681489._.js.map} +1 -1
  172. package/web/.next/server/chunks/ssr/{_89745126._.js → _f3612e5a._.js} +2 -2
  173. package/web/.next/server/chunks/ssr/{_89745126._.js.map → _f3612e5a._.js.map} +1 -1
  174. package/web/.next/server/chunks/ssr/{_4d49c223._.js → _f891c054._.js} +2 -2
  175. package/web/.next/server/chunks/ssr/_f891c054._.js.map +1 -0
  176. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
  177. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
  178. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_888633e1.js +1 -1
  179. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_888633e1.js.map +1 -1
  180. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js +1 -1
  181. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js.map +1 -1
  182. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js +1 -1
  183. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js.map +1 -1
  184. package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js +1 -1
  185. package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js.map +1 -1
  186. package/web/.next/server/chunks/ssr/src_presentation_web_ce7cbc3c._.js +2 -2
  187. package/web/.next/server/chunks/ssr/src_presentation_web_ce7cbc3c._.js.map +1 -1
  188. package/web/.next/server/chunks/ssr/src_presentation_web_components_bf50c527._.js +1 -1
  189. package/web/.next/server/chunks/ssr/src_presentation_web_components_de814675._.js +1 -1
  190. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  191. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  192. package/web/.next/server/chunks/ssr/src_presentation_web_e2604413._.js +2 -2
  193. package/web/.next/server/chunks/ssr/src_presentation_web_e2604413._.js.map +1 -1
  194. package/web/.next/server/pages/500.html +2 -2
  195. package/web/.next/server/server-reference-manifest.js +1 -1
  196. package/web/.next/server/server-reference-manifest.json +31 -31
  197. package/web/.next/static/chunks/{9751a0c871920841.js → 2ffe2f11f4ca9bb4.js} +1 -1
  198. package/web/.next/static/chunks/{fa27e698e0daf727.js → 430e372a1ef8e0a1.js} +1 -1
  199. package/web/.next/static/chunks/{e429067d6277ac20.js → 87488a543d25cd04.js} +1 -1
  200. package/web/.next/static/chunks/{dd30ae99bcec5f69.js → 8f1322aea6f75e2d.js} +2 -2
  201. package/web/.next/static/chunks/{2c6298885de1484c.js → acce61815a6260e7.js} +1 -1
  202. package/web/.next/static/chunks/{eed22af4cf55faea.js → c3253a63b95ec66e.js} +1 -1
  203. package/web/.next/static/chunks/{2eab3bb5550eccac.js → f60e223f91508b6e.js} +1 -1
  204. package/web/.next/static/chunks/{5ebc63ae73de50a3.js → f7d21f36df719f6f.js} +1 -1
  205. package/web/.next/static/chunks/f8672a435cc52ee8.js +2 -0
  206. package/web/.next/static/chunks/{f28d2411c36c308b.js → fc986ec4aa601f4d.js} +1 -1
  207. package/web/.next/server/chunks/ssr/_4d49c223._.js.map +0 -1
  208. package/web/.next/static/chunks/281782a712ea79cb.js +0 -2
  209. /package/web/.next/static/{OYucSKP83TUMTV1zRbeB8 → ITseKg4TnnrOWm79esrvr}/_buildManifest.js +0 -0
  210. /package/web/.next/static/{OYucSKP83TUMTV1zRbeB8 → ITseKg4TnnrOWm79esrvr}/_clientMiddlewareManifest.json +0 -0
  211. /package/web/.next/static/{OYucSKP83TUMTV1zRbeB8 → ITseKg4TnnrOWm79esrvr}/_ssgManifest.js +0 -0
@@ -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
- try {
259
- const result = await deleteFeature(featureId, cleanup);
260
- if (result.error) {
261
- toast.error(result.error);
262
- return;
263
- }
264
- deleteSound.play();
265
- toast.success('Feature deleted successfully');
266
- router.push('/');
267
- router.refresh();
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
  }
@@ -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,2CAuTzF"}
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"}
@@ -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,CA6bpB"}
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"}
@@ -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
- }, [updateFeature]);
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
- }, [router, deleteSound, updateFeature, removeFeature]);
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
- }, [addRepositoryToMap, removeRepository, replaceRepository, createSound, getRepoMapSize]);
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;;;OAGG;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;CACnD;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,CAuXrB"}
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,CAqI5C"}
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}`,