@shepai/cli 1.72.9-pr219.08421ee → 1.72.9-pr220.68aacbc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (410) hide show
  1. package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.d.ts +20 -1
  2. package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.d.ts.map +1 -1
  3. package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.js +68 -41
  4. package/dist/packages/core/src/application/use-cases/features/create/types.d.ts +10 -0
  5. package/dist/packages/core/src/application/use-cases/features/create/types.d.ts.map +1 -1
  6. package/dist/packages/core/src/infrastructure/services/notifications/notification.service.d.ts.map +1 -1
  7. package/dist/packages/core/src/infrastructure/services/notifications/notification.service.js +2 -4
  8. package/dist/src/presentation/web/app/actions/create-feature.d.ts.map +1 -1
  9. package/dist/src/presentation/web/app/actions/create-feature.js +20 -2
  10. package/dist/src/presentation/web/app/actions/get-feature-metadata.d.ts +10 -0
  11. package/dist/src/presentation/web/app/actions/get-feature-metadata.d.ts.map +1 -0
  12. package/dist/src/presentation/web/app/actions/get-feature-metadata.js +14 -0
  13. package/dist/src/presentation/web/app/api/agent-events/route.d.ts.map +1 -1
  14. package/dist/src/presentation/web/app/api/agent-events/route.js +16 -17
  15. package/dist/src/presentation/web/app/build-feature-node-data.d.ts.map +1 -1
  16. package/dist/src/presentation/web/app/build-feature-node-data.js +2 -26
  17. package/dist/src/presentation/web/app/build-graph-nodes.d.ts.map +1 -1
  18. package/dist/src/presentation/web/app/build-graph-nodes.js +2 -26
  19. package/dist/src/presentation/web/components/common/add-repository-button/add-repository-button.d.ts.map +1 -0
  20. package/dist/src/presentation/web/components/common/add-repository-button/index.d.ts +2 -0
  21. package/dist/src/presentation/web/components/common/add-repository-button/index.d.ts.map +1 -0
  22. package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/index.js +0 -1
  23. package/dist/src/presentation/web/components/common/add-repository-button/pick-folder.d.ts.map +1 -0
  24. package/dist/src/presentation/web/components/common/control-center-drawer/create-drawer-client.d.ts.map +1 -1
  25. package/dist/src/presentation/web/components/common/control-center-drawer/create-drawer-client.js +13 -18
  26. package/dist/src/presentation/web/components/common/control-center-drawer/drawer-view.d.ts +2 -0
  27. package/dist/src/presentation/web/components/common/control-center-drawer/drawer-view.d.ts.map +1 -1
  28. package/dist/src/presentation/web/components/common/control-center-drawer/drawer-view.js +11 -11
  29. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.d.ts.map +1 -1
  30. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.js +69 -141
  31. package/dist/src/presentation/web/components/common/control-center-drawer/index.d.ts +0 -1
  32. package/dist/src/presentation/web/components/common/control-center-drawer/index.d.ts.map +1 -1
  33. package/dist/src/presentation/web/components/common/control-center-drawer/index.js +0 -1
  34. package/dist/src/presentation/web/components/common/control-center-drawer/use-artifact-fetch.d.ts +13 -0
  35. package/dist/src/presentation/web/components/common/control-center-drawer/use-artifact-fetch.d.ts.map +1 -0
  36. package/dist/src/presentation/web/components/common/control-center-drawer/use-artifact-fetch.js +46 -0
  37. package/dist/src/presentation/web/components/common/feature-drawer/use-feature-actions.d.ts.map +1 -1
  38. package/dist/src/presentation/web/components/common/feature-drawer/use-feature-actions.js +7 -10
  39. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts +25 -0
  40. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts.map +1 -1
  41. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.js +45 -2
  42. package/dist/src/presentation/web/components/common/index.d.ts +2 -3
  43. package/dist/src/presentation/web/components/common/index.d.ts.map +1 -1
  44. package/dist/src/presentation/web/components/common/index.js +2 -3
  45. package/dist/src/presentation/web/components/common/repository-node/use-repository-actions.d.ts.map +1 -1
  46. package/dist/src/presentation/web/components/common/repository-node/use-repository-actions.js +14 -39
  47. package/dist/src/presentation/web/components/features/control-center/control-center-empty-state.js +1 -1
  48. package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
  49. package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +31 -14
  50. package/dist/src/presentation/web/components/features/control-center/control-center.stories.d.ts +0 -11
  51. package/dist/src/presentation/web/components/features/control-center/control-center.stories.d.ts.map +1 -1
  52. package/dist/src/presentation/web/components/features/control-center/control-center.stories.js +2 -37
  53. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts +8 -0
  54. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
  55. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +158 -396
  56. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.d.ts +2 -10
  57. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.d.ts.map +1 -1
  58. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.js +11 -54
  59. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.stories.d.ts.map +1 -1
  60. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.stories.js +0 -46
  61. package/dist/src/presentation/web/components/layouts/app-shell/app-shell.js +1 -1
  62. package/dist/src/presentation/web/components/ui/alert-dialog.d.ts.map +1 -1
  63. package/dist/src/presentation/web/components/ui/alert-dialog.js +2 -2
  64. package/dist/src/presentation/web/components/ui/dialog.d.ts.map +1 -1
  65. package/dist/src/presentation/web/components/ui/dialog.js +2 -2
  66. package/dist/src/presentation/web/hooks/agent-events-provider.d.ts.map +1 -1
  67. package/dist/src/presentation/web/hooks/agent-events-provider.js +3 -2
  68. package/dist/src/presentation/web/hooks/use-agent-events.d.ts.map +1 -1
  69. package/dist/src/presentation/web/hooks/use-agent-events.js +16 -5
  70. package/dist/src/presentation/web/hooks/use-deploy-action.d.ts.map +1 -1
  71. package/dist/src/presentation/web/hooks/use-deploy-action.js +8 -3
  72. package/dist/src/presentation/web/hooks/use-graph-state.d.ts +42 -0
  73. package/dist/src/presentation/web/hooks/use-graph-state.d.ts.map +1 -0
  74. package/dist/src/presentation/web/hooks/use-graph-state.js +257 -0
  75. package/dist/src/presentation/web/hooks/use-notifications.d.ts +1 -5
  76. package/dist/src/presentation/web/hooks/use-notifications.d.ts.map +1 -1
  77. package/dist/src/presentation/web/hooks/use-notifications.js +1 -26
  78. package/dist/src/presentation/web/hooks/use-notifications.stories.d.ts.map +1 -1
  79. package/dist/src/presentation/web/hooks/use-notifications.stories.js +28 -38
  80. package/dist/src/presentation/web/lib/derive-graph.d.ts +52 -0
  81. package/dist/src/presentation/web/lib/derive-graph.d.ts.map +1 -0
  82. package/dist/src/presentation/web/lib/derive-graph.js +126 -0
  83. package/dist/src/presentation/web/lib/layout-with-dagre.d.ts.map +1 -1
  84. package/dist/src/presentation/web/lib/layout-with-dagre.js +0 -1
  85. package/dist/tsconfig.build.tsbuildinfo +1 -1
  86. package/package.json +1 -1
  87. package/web/.next/BUILD_ID +1 -1
  88. package/web/.next/build-manifest.json +2 -2
  89. package/web/.next/cache/.previewinfo +1 -1
  90. package/web/.next/cache/.rscinfo +1 -1
  91. package/web/.next/cache/.tsbuildinfo +1 -1
  92. package/web/.next/cache/config.json +3 -3
  93. package/web/.next/fallback-build-manifest.json +2 -2
  94. package/web/.next/prerender-manifest.json +3 -3
  95. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +34 -34
  96. package/web/.next/server/app/(dashboard)/@drawer/create/page.js +3 -3
  97. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  98. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  99. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +40 -40
  100. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js +3 -3
  101. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  102. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  103. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +30 -30
  104. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js +3 -3
  105. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  106. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  107. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +34 -34
  108. package/web/.next/server/app/(dashboard)/create/page.js +3 -3
  109. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  110. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  111. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +40 -40
  112. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js +3 -3
  113. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  114. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  115. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +30 -30
  116. package/web/.next/server/app/(dashboard)/page.js +3 -3
  117. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  118. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  119. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +30 -30
  120. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js +3 -3
  121. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  122. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  123. package/web/.next/server/app/_global-error.html +2 -2
  124. package/web/.next/server/app/_global-error.rsc +1 -1
  125. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  126. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  127. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  128. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  129. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  130. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  131. package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  132. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  133. package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  134. package/web/.next/server/app/skills/page.js.nft.json +1 -1
  135. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  136. package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  137. package/web/.next/server/app/tools/page.js.nft.json +1 -1
  138. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  139. package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  140. package/web/.next/server/app/version/page.js.nft.json +1 -1
  141. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  142. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +2 -2
  143. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js.map +1 -1
  144. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  145. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  146. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js +3 -0
  147. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js.map +1 -0
  148. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js +3 -0
  149. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js.map +1 -0
  150. package/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js +2 -2
  151. package/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js.map +1 -1
  152. package/web/.next/server/chunks/ssr/[root-of-the-server]__2018e25f._.js +1 -1
  153. package/web/.next/server/chunks/ssr/[root-of-the-server]__2018e25f._.js.map +1 -1
  154. package/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js +2 -2
  155. package/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js.map +1 -1
  156. package/web/.next/server/chunks/ssr/{[root-of-the-server]__cbae9863._.js → [root-of-the-server]__6258349c._.js} +2 -2
  157. package/web/.next/server/chunks/ssr/[root-of-the-server]__6258349c._.js.map +1 -0
  158. package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
  159. package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js.map +1 -1
  160. package/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js +2 -2
  161. package/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js.map +1 -1
  162. package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
  163. package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js.map +1 -1
  164. package/web/.next/{standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__e5e09f72._.js → server/chunks/ssr/[root-of-the-server]__94a6d275._.js} +2 -2
  165. package/web/.next/server/chunks/ssr/[root-of-the-server]__94a6d275._.js.map +1 -0
  166. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
  167. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
  168. package/web/.next/server/chunks/ssr/_07c2a32a._.js +1 -1
  169. package/web/.next/server/chunks/ssr/_07c2a32a._.js.map +1 -1
  170. package/web/.next/server/chunks/ssr/_0e9d6bf7._.js +1 -1
  171. package/web/.next/server/chunks/ssr/_0e9d6bf7._.js.map +1 -1
  172. package/web/.next/server/chunks/ssr/_250a63ae._.js +3 -0
  173. package/web/.next/server/chunks/ssr/_250a63ae._.js.map +1 -0
  174. package/web/.next/server/chunks/ssr/_61351ed3._.js +1 -1
  175. package/web/.next/server/chunks/ssr/_61351ed3._.js.map +1 -1
  176. package/web/.next/server/chunks/ssr/_8400c8bb._.js +1 -1
  177. package/web/.next/server/chunks/ssr/_8400c8bb._.js.map +1 -1
  178. package/web/.next/server/chunks/ssr/_9f940437._.js +1 -1
  179. package/web/.next/server/chunks/ssr/_9f940437._.js.map +1 -1
  180. package/web/.next/server/chunks/ssr/_ad111e34._.js +6 -0
  181. package/web/.next/server/chunks/ssr/_ad111e34._.js.map +1 -0
  182. package/web/.next/{standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_5ea689db._.js → server/chunks/ssr/src_presentation_web_2b91e4ec._.js} +2 -2
  183. package/web/.next/server/chunks/ssr/{src_presentation_web_7188a290._.js.map → src_presentation_web_2b91e4ec._.js.map} +1 -1
  184. package/web/.next/server/chunks/ssr/{src_presentation_web_5ea689db._.js → src_presentation_web_5bcc1537._.js} +2 -2
  185. package/web/.next/server/chunks/ssr/{src_presentation_web_5ea689db._.js.map → src_presentation_web_5bcc1537._.js.map} +1 -1
  186. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_5955c012.js +3 -0
  187. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_5955c012.js.map +1 -0
  188. package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js +3 -0
  189. package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js.map +1 -0
  190. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  191. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  192. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  193. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  194. package/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js +8 -0
  195. package/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js.map +1 -0
  196. package/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js +8 -0
  197. package/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js.map +1 -0
  198. package/web/.next/server/pages/500.html +2 -2
  199. package/web/.next/server/server-reference-manifest.js +1 -1
  200. package/web/.next/server/server-reference-manifest.json +142 -142
  201. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  202. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  203. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  204. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +34 -34
  205. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page.js +3 -3
  206. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  207. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  208. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +40 -40
  209. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js +3 -3
  210. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  211. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  212. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +30 -30
  213. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js +3 -3
  214. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  215. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  216. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +34 -34
  217. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page.js +3 -3
  218. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  219. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  220. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +40 -40
  221. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page.js +3 -3
  222. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  223. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  224. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +30 -30
  225. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page.js +3 -3
  226. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  227. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  228. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +30 -30
  229. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js +3 -3
  230. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  231. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  232. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  233. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  234. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  235. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  236. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  237. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  238. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  239. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  240. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  241. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  242. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  243. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page.js.nft.json +1 -1
  244. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  245. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  246. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page.js.nft.json +1 -1
  247. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  248. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  249. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js.nft.json +1 -1
  250. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  251. package/web/.next/standalone/src/presentation/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +2 -2
  252. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  253. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js +3 -0
  254. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js +3 -0
  255. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js +2 -2
  256. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__2018e25f._.js +1 -1
  257. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js +2 -2
  258. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{[root-of-the-server]__cbae9863._.js → [root-of-the-server]__6258349c._.js} +2 -2
  259. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
  260. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js +2 -2
  261. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
  262. package/web/.next/{server/chunks/ssr/[root-of-the-server]__e5e09f72._.js → standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__94a6d275._.js} +2 -2
  263. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
  264. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_07c2a32a._.js +1 -1
  265. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_0e9d6bf7._.js +1 -1
  266. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_250a63ae._.js +3 -0
  267. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_61351ed3._.js +1 -1
  268. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_8400c8bb._.js +1 -1
  269. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_9f940437._.js +1 -1
  270. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_ad111e34._.js +6 -0
  271. package/web/.next/{server/chunks/ssr/src_presentation_web_7188a290._.js → standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_2b91e4ec._.js} +2 -2
  272. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{src_presentation_web_7188a290._.js → src_presentation_web_5bcc1537._.js} +2 -2
  273. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_5955c012.js +3 -0
  274. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js +3 -0
  275. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  276. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  277. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js +8 -0
  278. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js +8 -0
  279. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  280. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  281. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +142 -142
  282. package/web/.next/standalone/src/presentation/web/CLAUDE.md +77 -33
  283. package/web/.next/standalone/src/presentation/web/app/actions/create-feature.ts +27 -2
  284. package/web/.next/standalone/src/presentation/web/app/actions/get-feature-metadata.ts +18 -0
  285. package/web/.next/standalone/src/presentation/web/app/api/agent-events/route.ts +18 -19
  286. package/web/.next/standalone/src/presentation/web/app/build-feature-node-data.ts +3 -30
  287. package/web/.next/standalone/src/presentation/web/app/build-graph-nodes.ts +3 -30
  288. package/web/.next/standalone/src/presentation/web/components/common/add-repository-button/index.ts +1 -0
  289. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/create-drawer-client.tsx +15 -21
  290. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/drawer-view.ts +12 -13
  291. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.tsx +99 -137
  292. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/index.ts +0 -1
  293. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/use-artifact-fetch.ts +56 -0
  294. package/web/.next/standalone/src/presentation/web/components/common/feature-drawer/use-feature-actions.ts +6 -7
  295. package/web/.next/standalone/src/presentation/web/components/common/feature-node/derive-feature-state.ts +63 -2
  296. package/web/.next/standalone/src/presentation/web/components/common/index.ts +2 -12
  297. package/web/.next/standalone/src/presentation/web/components/common/repository-node/use-repository-actions.ts +29 -54
  298. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-empty-state.tsx +1 -1
  299. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +43 -36
  300. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center.stories.tsx +2 -41
  301. package/web/.next/standalone/src/presentation/web/components/features/control-center/use-control-center-state.ts +202 -495
  302. package/web/.next/standalone/src/presentation/web/components/features/features-canvas/features-canvas.stories.tsx +0 -48
  303. package/web/.next/standalone/src/presentation/web/components/features/features-canvas/features-canvas.tsx +13 -76
  304. package/web/.next/standalone/src/presentation/web/components/layouts/app-shell/app-shell.tsx +1 -1
  305. package/web/.next/standalone/src/presentation/web/components/ui/alert-dialog.tsx +2 -5
  306. package/web/.next/standalone/src/presentation/web/components/ui/dialog.tsx +2 -5
  307. package/web/.next/standalone/src/presentation/web/hooks/agent-events-provider.tsx +7 -2
  308. package/web/.next/standalone/src/presentation/web/hooks/use-agent-events.ts +17 -5
  309. package/web/.next/standalone/src/presentation/web/hooks/use-deploy-action.ts +11 -3
  310. package/web/.next/standalone/src/presentation/web/hooks/use-graph-state.ts +376 -0
  311. package/web/.next/standalone/src/presentation/web/hooks/use-notifications.stories.tsx +2 -30
  312. package/web/.next/standalone/src/presentation/web/hooks/use-notifications.ts +2 -36
  313. package/web/.next/standalone/src/presentation/web/lib/derive-graph.ts +178 -0
  314. package/web/.next/standalone/src/presentation/web/lib/layout-with-dagre.ts +0 -1
  315. package/web/.next/static/chunks/0ffccd7be73305c8.css +2 -0
  316. package/web/.next/static/chunks/3e25d4932e1dd406.js +1 -0
  317. package/web/.next/static/chunks/3f205fd6adbfd20a.js +7 -0
  318. package/web/.next/static/chunks/68ded4375df20ac5.js +1 -0
  319. package/web/.next/static/chunks/{1d281f045b623a45.js → 6c13863b031221ea.js} +1 -1
  320. package/web/.next/static/chunks/8ecedb1a498fb468.js +1 -0
  321. package/web/.next/static/chunks/95682021c08d3636.js +1 -0
  322. package/web/.next/static/chunks/ab808dc1cdf66a9c.js +4 -0
  323. package/web/.next/static/chunks/cb41849bcfaa304e.js +1 -0
  324. package/web/.next/static/chunks/{cd9099d3eae9dd04.js → ee1a546ece509b1d.js} +1 -1
  325. package/web/.next/static/chunks/f200e62f7185c8da.js +1 -0
  326. package/web/.next/static/chunks/fc0f263fd83b43f9.js +1 -0
  327. package/web/.next/trace +1 -1
  328. package/web/.next/trace-build +1 -1
  329. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-button.d.ts.map +0 -1
  330. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node-config.d.ts +0 -7
  331. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node-config.d.ts.map +0 -1
  332. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node-config.js +0 -1
  333. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.d.ts +0 -6
  334. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.d.ts.map +0 -1
  335. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.js +0 -23
  336. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.d.ts +0 -10
  337. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.d.ts.map +0 -1
  338. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.js +0 -33
  339. package/dist/src/presentation/web/components/common/add-repository-node/index.d.ts +0 -4
  340. package/dist/src/presentation/web/components/common/add-repository-node/index.d.ts.map +0 -1
  341. package/dist/src/presentation/web/components/common/add-repository-node/pick-folder.d.ts.map +0 -1
  342. package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.d.ts +0 -12
  343. package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.d.ts.map +0 -1
  344. package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.js +0 -337
  345. package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.stories.d.ts +0 -33
  346. package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.stories.d.ts.map +0 -1
  347. package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.stories.js +0 -214
  348. package/dist/src/presentation/web/components/common/notification-permission-banner/index.d.ts +0 -2
  349. package/dist/src/presentation/web/components/common/notification-permission-banner/index.d.ts.map +0 -1
  350. package/dist/src/presentation/web/components/common/notification-permission-banner/index.js +0 -1
  351. package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.d.ts +0 -2
  352. package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.d.ts.map +0 -1
  353. package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.js +0 -33
  354. package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.stories.d.ts +0 -6
  355. package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.stories.d.ts.map +0 -1
  356. package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.stories.js +0 -24
  357. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_3a02d8a5.js +0 -3
  358. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_3a02d8a5.js.map +0 -1
  359. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_a3044852.js +0 -3
  360. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_a3044852.js.map +0 -1
  361. package/web/.next/server/chunks/ssr/[root-of-the-server]__cbae9863._.js.map +0 -1
  362. package/web/.next/server/chunks/ssr/[root-of-the-server]__e5e09f72._.js.map +0 -1
  363. package/web/.next/server/chunks/ssr/_4469c284._.js +0 -6
  364. package/web/.next/server/chunks/ssr/_4469c284._.js.map +0 -1
  365. package/web/.next/server/chunks/ssr/_6e08ce21._.js +0 -3
  366. package/web/.next/server/chunks/ssr/_6e08ce21._.js.map +0 -1
  367. package/web/.next/server/chunks/ssr/src_presentation_web_0a49d9ee._.js +0 -8
  368. package/web/.next/server/chunks/ssr/src_presentation_web_0a49d9ee._.js.map +0 -1
  369. package/web/.next/server/chunks/ssr/src_presentation_web_79b0305a._.js +0 -8
  370. package/web/.next/server/chunks/ssr/src_presentation_web_79b0305a._.js.map +0 -1
  371. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_cc16bd01.js +0 -3
  372. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_cc16bd01.js.map +0 -1
  373. package/web/.next/server/chunks/ssr/src_presentation_web_app_(dashboard)_get-graph-data_ts_b018483e._.js +0 -3
  374. package/web/.next/server/chunks/ssr/src_presentation_web_app_(dashboard)_get-graph-data_ts_b018483e._.js.map +0 -1
  375. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_3a02d8a5.js +0 -3
  376. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_a3044852.js +0 -3
  377. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_4469c284._.js +0 -6
  378. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_6e08ce21._.js +0 -3
  379. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_0a49d9ee._.js +0 -8
  380. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_79b0305a._.js +0 -8
  381. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_cc16bd01.js +0 -3
  382. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_app_(dashboard)_get-graph-data_ts_b018483e._.js +0 -3
  383. package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/add-repository-node-config.ts +0 -8
  384. package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.tsx +0 -70
  385. package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/add-repository-node.tsx +0 -49
  386. package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/index.ts +0 -3
  387. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/control-center-drawer.stories.tsx +0 -293
  388. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/control-center-drawer.tsx +0 -745
  389. package/web/.next/standalone/src/presentation/web/components/common/notification-permission-banner/index.ts +0 -1
  390. package/web/.next/standalone/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.stories.tsx +0 -52
  391. package/web/.next/standalone/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.tsx +0 -55
  392. package/web/.next/static/chunks/073b31d7a4974504.js +0 -1
  393. package/web/.next/static/chunks/0c54314c8c9c9739.js +0 -7
  394. package/web/.next/static/chunks/0e588a3a4dd7d1c0.js +0 -1
  395. package/web/.next/static/chunks/2433994d844b7d39.js +0 -1
  396. package/web/.next/static/chunks/3e041a9008bac888.js +0 -1
  397. package/web/.next/static/chunks/798624a651d68855.js +0 -4
  398. package/web/.next/static/chunks/977b9d2b9ef19f7d.js +0 -1
  399. package/web/.next/static/chunks/9be617fc61b893f2.js +0 -1
  400. package/web/.next/static/chunks/a18b0561724d11d4.js +0 -1
  401. package/web/.next/static/chunks/acce8a15d6896dea.css +0 -2
  402. /package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/add-repository-button.d.ts +0 -0
  403. /package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/add-repository-button.js +0 -0
  404. /package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/pick-folder.d.ts +0 -0
  405. /package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/pick-folder.js +0 -0
  406. /package/web/.next/standalone/src/presentation/web/components/common/{add-repository-node → add-repository-button}/add-repository-button.tsx +0 -0
  407. /package/web/.next/standalone/src/presentation/web/components/common/{add-repository-node → add-repository-button}/pick-folder.ts +0 -0
  408. /package/web/.next/static/{pzwrJGWI__eUhJsxxW2S- → wSVxDvqh-ObIC1qlefqgl}/_buildManifest.js +0 -0
  409. /package/web/.next/static/{pzwrJGWI__eUhJsxxW2S- → wSVxDvqh-ObIC1qlefqgl}/_clientMiddlewareManifest.json +0 -0
  410. /package/web/.next/static/{pzwrJGWI__eUhJsxxW2S- → wSVxDvqh-ObIC1qlefqgl}/_ssgManifest.js +0 -0
@@ -1,100 +1,55 @@
1
1
  'use client';
2
- import { useState, useCallback, useEffect, useRef } from 'react';
2
+ import { useCallback, useEffect, useMemo, useRef } from 'react';
3
3
  import { useRouter } from 'next/navigation';
4
4
  import { toast } from 'sonner';
5
- import { applyNodeChanges } from '@xyflow/react';
6
5
  import { layoutWithDagre, CANVAS_LAYOUT_DEFAULTS, } from '../../../lib/layout-with-dagre.js';
7
6
  import { deleteFeature } from '../../../app/actions/delete-feature.js';
8
7
  import { addRepository } from '../../../app/actions/add-repository.js';
9
8
  import { deleteRepository } from '../../../app/actions/delete-repository.js';
10
- import { fetchGraphData } from '../../../app/actions/get-graph-data.js';
9
+ import { getFeatureMetadata } from '../../../app/actions/get-feature-metadata.js';
11
10
  import { useAgentEventsContext } from '../../../hooks/agent-events-provider.js';
12
11
  import { useSoundAction } from '../../../hooks/use-sound-action.js';
13
- import { mapEventTypeToState, mapPhaseNameToLifecycle, } from '../../common/feature-node/derive-feature-state.js';
12
+ import { mapEventTypeToState, resolveSseEventUpdates, } from '../../common/feature-node/derive-feature-state.js';
13
+ import { useGraphState } from '../../../hooks/use-graph-state.js';
14
+ /** Must match the message string emitted by the SSE route in agent-events/route.ts */
15
+ const METADATA_UPDATED_MESSAGE = 'Feature metadata updated';
14
16
  let nextFeatureId = 0;
15
17
  export function useControlCenterState(initialNodes, initialEdges) {
16
18
  const router = useRouter();
17
- const [nodes, setNodes] = useState(initialNodes);
18
- // eslint-disable-next-line react/hook-use-state -- raw setter renamed; public setEdges wrapper keeps edgesRef in sync
19
- const [edges, setEdgesRaw] = useState(initialEdges);
20
- const edgesRef = useRef(initialEdges);
21
- // Wrapper that keeps edgesRef in sync with edges state, allowing
22
- // createFeatureNode to read current edges without a closure dependency.
23
- const setEdges = useCallback((update) => {
24
- setEdgesRaw((prev) => {
25
- const next = typeof update === 'function' ? update(prev) : update;
26
- edgesRef.current = next;
27
- return next;
28
- });
29
- }, []);
30
19
  const deleteSound = useSoundAction('delete');
31
20
  const createSound = useSoundAction('create');
32
- // Sync server props into local state when parent re-renders with new data
33
- const initialNodeKey = initialNodes
21
+ const { nodes, edges, reconcile, updateFeature, addPendingFeature, removeFeature, restoreFeature, addRepository: addRepositoryToMap, removeRepository, replaceRepository, getFeatureRepositoryPath, getRepositoryData, setCallbacks, } = useGraphState(initialNodes, initialEdges);
22
+ // Refs for stable access to latest nodes/edges without callback recreation
23
+ const nodesRef = useRef(nodes);
24
+ const edgesRef = useRef(edges);
25
+ useEffect(() => {
26
+ nodesRef.current = nodes;
27
+ edgesRef.current = edges;
28
+ }, [nodes, edges]);
29
+ // Sync server props into domain Maps when initialNodes/initialEdges change.
30
+ // Keyed by a stable string so we don't re-reconcile on every render.
31
+ const initialNodeKey = useMemo(() => initialNodes
34
32
  .map((n) => n.id)
35
33
  .sort()
36
- .join(',');
37
- const initialEdgeKey = initialEdges
38
- .map((e) => e.id)
39
- .sort()
40
- .join(',');
41
- // Track previous feature states so we can detect transitions on server refresh
42
- const prevFeatureStatesRef = useRef(new Map());
43
- // Stable key that changes when feature DATA changes (state/lifecycle), not just IDs.
44
- const initialDataKey = initialNodes
34
+ .join(','), [initialNodes]);
35
+ const initialDataKey = useMemo(() => initialNodes
45
36
  .filter((n) => n.type === 'featureNode')
46
37
  .map((n) => {
47
38
  const d = n.data;
48
39
  return `${n.id}:${d.state}:${d.lifecycle}`;
49
40
  })
50
41
  .sort()
51
- .join(',');
52
- // Sync server props into local state — keyed by derived strings (initialNodeKey/initialEdgeKey)
53
- // to avoid infinite re-renders from unstable array references.
54
- useEffect(() => {
55
- setNodes((currentNodes) => {
56
- // Build a lookup of current node positions by ID
57
- const currentById = new Map(currentNodes.map((n) => [n.id, n]));
58
- // Identify optimistic "creating" nodes (they have temp IDs not in server data)
59
- const serverIds = new Set(initialNodes.map((n) => n.id));
60
- const creatingNodes = currentNodes.filter((n) => n.type === 'featureNode' &&
61
- n.data.state === 'creating' &&
62
- !serverIds.has(n.id));
63
- // Merge server nodes with client positions, tracking if relayout is needed
64
- let needsRelayout = false;
65
- const merged = initialNodes.map((serverNode) => {
66
- // Node already exists on canvas — keep its position, update data
67
- const existing = currentById.get(serverNode.id);
68
- if (existing) {
69
- return { ...serverNode, position: existing.position };
70
- }
71
- // New server node — inherit position from an optimistic creating node if available
72
- if (serverNode.type === 'featureNode' && creatingNodes.length > 0) {
73
- const donor = creatingNodes.shift();
74
- return { ...serverNode, position: donor.position };
75
- }
76
- // Truly new node with no optimistic counterpart — needs relayout
77
- needsRelayout = true;
78
- return serverNode;
79
- });
80
- // Also check if non-creating nodes were removed (graph got smaller)
81
- if (!needsRelayout) {
82
- needsRelayout = currentNodes.some((cn) => !serverIds.has(cn.id) &&
83
- !(cn.type === 'featureNode' && cn.data.state === 'creating'));
84
- }
85
- if (needsRelayout) {
86
- const layoutResult = layoutWithDagre(merged, initialEdges, CANVAS_LAYOUT_DEFAULTS);
87
- setEdges(layoutResult.edges);
88
- return layoutResult.nodes;
89
- }
90
- return merged;
91
- });
92
- }, [initialNodeKey, initialNodes, initialEdges, setEdges]);
42
+ .join(','), [initialNodes]);
43
+ const prevReconcileKey = useRef('');
93
44
  useEffect(() => {
94
- setEdges(initialEdges);
95
- }, [initialEdgeKey, initialEdges, setEdges]);
96
- // Fallback notifications from server-prop state transitions.
97
- // Fires only when SSE didn't already deliver the matching event for a feature.
45
+ const key = `${initialNodeKey}|${initialDataKey}`;
46
+ if (key !== prevReconcileKey.current) {
47
+ prevReconcileKey.current = key;
48
+ reconcile(initialNodes, initialEdges);
49
+ }
50
+ }, [initialNodeKey, initialDataKey, initialNodes, initialEdges, reconcile]);
51
+ // Track previous feature states for fallback notifications
52
+ const prevFeatureStatesRef = useRef(new Map());
98
53
  const { events } = useAgentEventsContext();
99
54
  useEffect(() => {
100
55
  const prevStates = prevFeatureStatesRef.current;
@@ -104,7 +59,6 @@ export function useControlCenterState(initialNodes, initialEdges) {
104
59
  const data = node.data;
105
60
  const prev = prevStates.get(node.id);
106
61
  if (prev !== undefined && prev !== data.state) {
107
- // Check if SSE already delivered a matching event for this feature
108
62
  const sseAlreadyCovered = events.some((e) => e.featureId === data.featureId && mapEventTypeToState(e.eventType) === data.state);
109
63
  if (!sseAlreadyCovered) {
110
64
  if (data.state === 'done') {
@@ -115,9 +69,7 @@ export function useControlCenterState(initialNodes, initialEdges) {
115
69
  description: 'Waiting for your approval',
116
70
  action: {
117
71
  label: 'Review',
118
- onClick: () => {
119
- router.push(`/feature/${data.featureId}`);
120
- },
72
+ onClick: () => router.push(`/feature/${data.featureId}`),
121
73
  },
122
74
  });
123
75
  }
@@ -129,315 +81,151 @@ export function useControlCenterState(initialNodes, initialEdges) {
129
81
  prevStates.set(node.id, data.state);
130
82
  }
131
83
  }, [initialDataKey, initialNodes, events, router]);
132
- // Targeted optimistic updates from SSE agent events.
133
- // Uses `events` array (not `lastEvent`) so that React batching cannot silently
134
- // drop events when multiple SSE messages arrive in the same tick.
84
+ // SSE effect: only state + lifecycle updates
135
85
  const processedEventCountRef = useRef(0);
136
- // Track known feature IDs so we can detect when SSE reports features not yet on the canvas.
137
- // Initialised from initialNodes to avoid a race between first render and SSE arrival.
138
- const knownFeatureIdsRef = useRef(new Set(initialNodes
139
- .filter((n) => n.type === 'featureNode')
140
- .map((n) => n.data.featureId)));
141
- // Keep ref in sync as nodes change (new nodes added, temp nodes replaced, etc.)
142
- useEffect(() => {
143
- knownFeatureIdsRef.current = new Set(nodes
144
- .filter((n) => n.type === 'featureNode')
145
- .map((n) => n.data.featureId));
146
- }, [nodes]);
147
- // Guard against concurrent fetchGraphData calls triggered by unknown-feature detection
148
- const fetchingGraphRef = useRef(false);
149
86
  useEffect(() => {
87
+ // Clamp cursor if events were pruned
88
+ if (processedEventCountRef.current > events.length) {
89
+ processedEventCountRef.current = 0;
90
+ }
150
91
  if (events.length <= processedEventCountRef.current)
151
92
  return;
152
93
  const newEvents = events.slice(processedEventCountRef.current);
153
94
  processedEventCountRef.current = events.length;
154
- let hasUnknownFeature = false;
155
- for (const event of newEvents) {
156
- // If the feature isn't on the canvas yet, flag for a full graph refresh
157
- if (!knownFeatureIdsRef.current.has(event.featureId)) {
158
- hasUnknownFeature = true;
159
- continue;
95
+ for (const { featureId, state, lifecycle } of resolveSseEventUpdates(newEvents)) {
96
+ if (state !== undefined || lifecycle !== undefined) {
97
+ updateFeature(`feat-${featureId}`, {
98
+ ...(state !== undefined && { state }),
99
+ ...(lifecycle !== undefined && { lifecycle }),
100
+ });
160
101
  }
161
- const newState = mapEventTypeToState(event.eventType);
162
- const newLifecycle = mapPhaseNameToLifecycle(event.phaseName);
163
- // Targeted node update — only clone the matched node
164
- setNodes((prev) => prev.map((node) => {
165
- if (node.type !== 'featureNode')
166
- return node;
167
- const data = node.data;
168
- if (data.featureId !== event.featureId)
169
- return node;
170
- return {
171
- ...node,
172
- data: {
173
- ...data,
174
- state: newState,
175
- ...(newLifecycle !== undefined && { lifecycle: newLifecycle }),
176
- },
177
- };
178
- }));
179
102
  }
180
- // SSE reported features not on the canvas — fetch full graph data so new
181
- // repository nodes, feature nodes, and edges all appear together.
182
- if (hasUnknownFeature && !fetchingGraphRef.current) {
183
- fetchingGraphRef.current = true;
184
- fetchGraphData()
185
- .then(({ nodes: serverNodes, edges: serverEdges }) => {
186
- setNodes((currentNodes) => {
187
- // Preserve positions of nodes already on the canvas
188
- const currentById = new Map(currentNodes.map((n) => [n.id, n]));
189
- const merged = serverNodes.map((serverNode) => {
190
- const existing = currentById.get(serverNode.id);
191
- return existing ? { ...serverNode, position: existing.position } : serverNode;
192
- });
193
- const layoutResult = layoutWithDagre(merged, serverEdges, CANVAS_LAYOUT_DEFAULTS);
194
- setEdges(layoutResult.edges);
195
- return layoutResult.nodes;
196
- });
103
+ }, [events, updateFeature]);
104
+ // Listen for optimistic approval events from the drawer (fires before SSE arrives)
105
+ useEffect(() => {
106
+ const handler = (e) => {
107
+ const { featureId } = e.detail;
108
+ updateFeature(`feat-${featureId}`, { state: 'running' });
109
+ };
110
+ window.addEventListener('shep:feature-approved', handler);
111
+ return () => window.removeEventListener('shep:feature-approved', handler);
112
+ }, [updateFeature]);
113
+ // Separate effect: fetch metadata (name + description) when SSE reports it changed
114
+ const metadataFetchedRef = useRef(new Set());
115
+ const processedMetadataCountRef = useRef(0);
116
+ useEffect(() => {
117
+ // Clamp cursor if events were pruned
118
+ if (processedMetadataCountRef.current > events.length) {
119
+ processedMetadataCountRef.current = 0;
120
+ }
121
+ if (events.length <= processedMetadataCountRef.current)
122
+ return;
123
+ const newEvents = events.slice(processedMetadataCountRef.current);
124
+ processedMetadataCountRef.current = events.length;
125
+ for (const event of newEvents) {
126
+ if (event.message !== METADATA_UPDATED_MESSAGE)
127
+ continue;
128
+ if (metadataFetchedRef.current.has(event.featureId))
129
+ continue;
130
+ metadataFetchedRef.current.add(event.featureId);
131
+ const nodeId = `feat-${event.featureId}`;
132
+ getFeatureMetadata(event.featureId)
133
+ .then((meta) => {
134
+ if (meta) {
135
+ updateFeature(nodeId, { name: meta.name, description: meta.description });
136
+ }
197
137
  })
198
138
  .catch(() => {
199
- // Silently ignore — next SSE event or poll will retry
200
- })
201
- .finally(() => {
202
- fetchingGraphRef.current = false;
139
+ // Silent: metadata fetch failure is non-critical
203
140
  });
204
141
  }
205
- }, [events, setEdges]);
206
- // Lightweight server-action polling fallback: fetches fresh graph data every 5s
207
- // when any feature is in an active state. This avoids full router.refresh() while
208
- // ensuring the UI stays in sync even if SSE events are missed.
209
- //
210
- // We derive a stable boolean so the interval isn't torn down on every node change.
211
- const hasActiveFeature = nodes.some((n) => {
212
- if (n.type !== 'featureNode')
213
- return false;
214
- const data = n.data;
215
- return (data.state === 'running' || data.state === 'action-required' || data.state === 'creating');
216
- });
217
- useEffect(() => {
218
- if (!hasActiveFeature)
219
- return;
220
- const interval = setInterval(async () => {
221
- try {
222
- const { nodes: serverNodes } = await fetchGraphData();
223
- setNodes((currentNodes) => {
224
- const serverById = new Map(serverNodes.map((n) => [n.id, n]));
225
- let changed = false;
226
- const merged = currentNodes.map((node) => {
227
- const serverNode = serverById.get(node.id);
228
- if (!serverNode || node.type !== 'featureNode')
229
- return node;
230
- const currentData = node.data;
231
- const serverData = serverNode.data;
232
- // Only update if server data has different state or lifecycle
233
- if (currentData.state === serverData.state &&
234
- currentData.lifecycle === serverData.lifecycle) {
235
- return node;
236
- }
237
- changed = true;
238
- return {
239
- ...node,
240
- data: {
241
- ...currentData,
242
- state: serverData.state,
243
- lifecycle: serverData.lifecycle,
244
- progress: serverData.progress,
245
- ...(serverData.errorMessage && { errorMessage: serverData.errorMessage }),
246
- ...(serverData.runtime && { runtime: serverData.runtime }),
247
- ...(serverData.agentType && { agentType: serverData.agentType }),
248
- ...(serverData.pr && { pr: serverData.pr }),
249
- ...(serverData.blockedBy && { blockedBy: serverData.blockedBy }),
250
- },
251
- };
252
- });
253
- // Find new server nodes not yet on the client
254
- const clientIds = new Set(currentNodes.map((n) => n.id));
255
- const newServerNodes = serverNodes.filter((n) => !clientIds.has(n.id));
256
- // Replace optimistic "creating" temp nodes with their real server
257
- // counterparts. Temp nodes have IDs like "feature-<ts>" while real
258
- // nodes use "feat-<uuid>". Match by repositoryPath + name.
259
- if (newServerNodes.length > 0) {
260
- const creatingIndices = [];
261
- merged.forEach((n, i) => {
262
- if (n.type === 'featureNode' && n.data.state === 'creating') {
263
- creatingIndices.push(i);
264
- }
265
- });
266
- for (const serverNode of newServerNodes) {
267
- if (serverNode.type !== 'featureNode' || creatingIndices.length === 0)
268
- continue;
269
- const idx = creatingIndices.shift();
270
- // Replace the temp node in-place, keeping its position
271
- merged[idx] = { ...serverNode, position: merged[idx].position };
272
- changed = true;
273
- }
274
- // Any remaining new server nodes that didn't replace a temp node
275
- const replacedIds = new Set(merged.map((n) => n.id));
276
- const trueNewNodes = newServerNodes.filter((n) => !replacedIds.has(n.id));
277
- if (trueNewNodes.length > 0) {
278
- changed = true;
279
- return [...merged, ...trueNewNodes];
280
- }
281
- }
282
- return changed ? merged : currentNodes;
283
- });
284
- }
285
- catch {
286
- // Silently ignore polling errors — SSE is the primary mechanism
287
- }
288
- }, 5_000);
289
- return () => clearInterval(interval);
290
- }, [hasActiveFeature]);
291
- const onNodesChange = useCallback((changes) => {
292
- // Filter out 'replace' changes — in controlled mode these come from
293
- // React Flow's StoreUpdater syncing the nodes prop back into state.
294
- // Applying them creates a circular update loop (nodes → enrichedNodes →
295
- // StoreUpdater → onNodesChange → setNodes → nodes → …) that both causes
296
- // excessive re-renders and overwrites SSE-driven state changes with stale data.
297
- const applicable = changes.filter((c) => c.type !== 'replace');
298
- if (applicable.length > 0) {
299
- setNodes((ns) => applyNodeChanges(applicable, ns));
300
- }
142
+ }, [events, updateFeature]);
143
+ // onNodesChange is a no-op: nodes are derived from domain Maps.
144
+ // Since nodesDraggable=false and elementsSelectable=false, only React Flow's
145
+ // internal replace/dimensions changes come through — we ignore them all.
146
+ const onNodesChange = useCallback((_changes) => {
147
+ // Intentional no-op: domain Maps are the source of truth.
148
+ }, []);
149
+ const handleConnect = useCallback((_connection) => {
150
+ // Connections are managed via domain operations, not direct edge manipulation.
301
151
  }, []);
302
- const handleConnect = useCallback((connection) => {
303
- if (!connection.source || !connection.target)
304
- return;
305
- setNodes((currentNodes) => {
306
- const sourceNode = currentNodes.find((n) => n.id === connection.source);
307
- if (sourceNode?.type !== 'repositoryNode')
308
- return currentNodes;
309
- // Block if target feature already has a repo source
310
- setEdges((currentEdges) => {
311
- const targetAlreadyHasRepo = currentEdges.some((e) => {
312
- const edgeSource = currentNodes.find((n) => n.id === e.source);
313
- return edgeSource?.type === 'repositoryNode' && e.target === connection.target;
314
- });
315
- if (targetAlreadyHasRepo)
316
- return currentEdges;
317
- return [
318
- ...currentEdges,
319
- {
320
- id: `edge-${connection.source}-${connection.target}`,
321
- source: connection.source,
322
- target: connection.target,
323
- style: { strokeDasharray: '5 5' },
324
- },
325
- ];
326
- });
327
- return currentNodes;
328
- });
329
- }, [setEdges]);
330
152
  const createFeatureNode = useCallback((sourceNodeId, dataOverride, edgeType) => {
331
- const id = `feature-${Date.now()}-${nextFeatureId++}`;
153
+ // Use real feature ID when available (from server), otherwise temp ID
154
+ const id = dataOverride?.featureId
155
+ ? `feat-${dataOverride.featureId}`
156
+ : `feature-${Date.now()}-${nextFeatureId++}`;
332
157
  const newFeatureData = {
333
158
  name: dataOverride?.name ?? 'New Feature',
334
159
  description: dataOverride?.description ?? 'Describe what this feature does',
335
- featureId: `#${id.slice(-4)}`,
160
+ featureId: dataOverride?.featureId ?? `#${id.slice(-4)}`,
336
161
  lifecycle: 'requirements',
337
162
  state: dataOverride?.state ?? 'running',
338
163
  progress: 0,
339
164
  repositoryPath: dataOverride?.repositoryPath ?? '',
340
165
  branch: dataOverride?.branch ?? '',
341
166
  };
342
- setNodes((currentNodes) => {
343
- const newNode = {
344
- id,
345
- type: 'featureNode',
346
- position: { x: 0, y: 0 },
347
- data: newFeatureData,
348
- };
349
- const newEdge = sourceNodeId
350
- ? {
351
- id: edgeType === 'dependencyEdge'
352
- ? `dep-${sourceNodeId}-${id}`
353
- : `edge-${sourceNodeId}-${id}`,
354
- source: sourceNodeId,
355
- target: id,
356
- ...(edgeType ? { type: edgeType } : { style: { strokeDasharray: '5 5' } }),
357
- }
358
- : null;
359
- const allEdges = newEdge
360
- ? [...edgesRef.current.filter((e) => e.id !== newEdge.id), newEdge]
361
- : edgesRef.current;
362
- // Insert the new node after the last sibling connected to the same source,
363
- // so dagre's input-order preservation keeps repo groups together.
364
- let insertIndex = currentNodes.length;
365
- if (sourceNodeId) {
366
- const siblingIds = new Set(allEdges.filter((e) => e.source === sourceNodeId).map((e) => e.target));
367
- for (let i = currentNodes.length - 1; i >= 0; i--) {
368
- if (siblingIds.has(currentNodes[i].id)) {
369
- insertIndex = i + 1;
370
- break;
371
- }
372
- }
373
- }
374
- const allNodes = [
375
- ...currentNodes.slice(0, insertIndex),
376
- newNode,
377
- ...currentNodes.slice(insertIndex),
378
- ];
379
- // Run dagre layout on the full graph so the new node is positioned consistently
380
- const layoutResult = layoutWithDagre(allNodes, allEdges, CANVAS_LAYOUT_DEFAULTS);
381
- setEdges(layoutResult.edges);
382
- return layoutResult.nodes;
383
- });
167
+ const parentNodeId = edgeType === 'dependencyEdge' && sourceNodeId ? sourceNodeId : undefined;
168
+ addPendingFeature(id, newFeatureData, parentNodeId);
384
169
  return id;
385
- }, [setEdges]);
170
+ }, [addPendingFeature]);
386
171
  const handleDeleteFeature = useCallback((featureId) => {
387
172
  const nodeId = `feat-${featureId}`;
388
- // Snapshot current state for rollback
389
- const prevNodes = nodes;
390
- const prevEdges = edgesRef.current;
391
- // Optimistic removal — update UI immediately
392
- setNodes((currentNodes) => {
393
- const remainingNodes = currentNodes.filter((n) => n.id !== nodeId);
394
- const remainingEdges = edgesRef.current.filter((e) => e.source !== nodeId && e.target !== nodeId);
395
- const layoutResult = layoutWithDagre(remainingNodes, remainingEdges, CANVAS_LAYOUT_DEFAULTS);
396
- setEdges(layoutResult.edges);
397
- return layoutResult.nodes;
398
- });
173
+ // Find the current entry for rollback
174
+ const prevEntry = nodesRef.current
175
+ .filter((n) => n.id === nodeId)
176
+ .map((n) => ({
177
+ nodeId: n.id,
178
+ data: n.data,
179
+ }))[0];
180
+ // Optimistic removal
181
+ removeFeature(nodeId);
399
182
  deleteSound.play();
400
183
  toast.success('Feature deleted successfully');
401
184
  router.push('/');
402
- // Persist in background — rollback on failure
403
185
  deleteFeature(featureId)
404
186
  .then((result) => {
405
187
  if (result.error) {
406
- setNodes(prevNodes);
407
- setEdges(prevEdges);
188
+ if (prevEntry)
189
+ restoreFeature(nodeId, prevEntry);
408
190
  toast.error(result.error);
409
191
  }
410
192
  })
411
193
  .catch(() => {
412
- setNodes(prevNodes);
413
- setEdges(prevEdges);
194
+ if (prevEntry)
195
+ restoreFeature(nodeId, prevEntry);
414
196
  toast.error('Failed to delete feature');
415
197
  });
416
- }, [nodes, router, deleteSound, setEdges]);
198
+ }, [router, deleteSound, removeFeature, restoreFeature]);
417
199
  const handleDeleteRepository = useCallback(async (repositoryId) => {
418
200
  const repoNodeId = `repo-${repositoryId}`;
201
+ // Find children of this repo via edges
202
+ const childFeatureIds = new Set(edgesRef.current.filter((e) => e.source === repoNodeId).map((e) => e.target));
419
203
  // Snapshot for rollback
420
- const prevNodes = nodes;
421
- const prevEdges = edgesRef.current;
422
- // Optimistic: remove repo node, its child feature nodes, and all related edges
423
- setNodes((currentNodes) => {
424
- // Find feature node IDs connected to this repo via edges
425
- const childFeatureIds = new Set(edgesRef.current.filter((e) => e.source === repoNodeId).map((e) => e.target));
426
- const remainingNodes = currentNodes.filter((n) => n.id !== repoNodeId && !childFeatureIds.has(n.id));
427
- const remainingEdges = edgesRef.current.filter((e) => e.source !== repoNodeId &&
428
- e.target !== repoNodeId &&
429
- !childFeatureIds.has(e.source) &&
430
- !childFeatureIds.has(e.target));
431
- const layoutResult = layoutWithDagre(remainingNodes, remainingEdges, CANVAS_LAYOUT_DEFAULTS);
432
- setEdges(layoutResult.edges);
433
- return layoutResult.nodes;
434
- });
204
+ const prevRepoData = getRepositoryData(repoNodeId);
205
+ const childSnapshots = new Map();
206
+ for (const childId of childFeatureIds) {
207
+ const childNode = nodesRef.current.find((n) => n.id === childId);
208
+ if (childNode) {
209
+ childSnapshots.set(childId, { nodeId: childId, data: childNode.data });
210
+ }
211
+ }
212
+ const rollback = () => {
213
+ if (prevRepoData)
214
+ addRepositoryToMap(repoNodeId, prevRepoData);
215
+ for (const [childId, entry] of childSnapshots) {
216
+ restoreFeature(childId, entry);
217
+ }
218
+ };
219
+ // Optimistic: remove repo + children
220
+ removeRepository(repoNodeId);
221
+ for (const childId of childFeatureIds) {
222
+ removeFeature(childId);
223
+ }
435
224
  try {
436
225
  const result = await deleteRepository(repositoryId);
437
226
  if (!result.success) {
438
227
  toast.error(result.error ?? 'Failed to remove repository');
439
- setNodes(prevNodes);
440
- setEdges(prevEdges);
228
+ rollback();
441
229
  return;
442
230
  }
443
231
  deleteSound.play();
@@ -445,82 +233,53 @@ export function useControlCenterState(initialNodes, initialEdges) {
445
233
  }
446
234
  catch {
447
235
  toast.error('Failed to remove repository');
448
- setNodes(prevNodes);
449
- setEdges(prevEdges);
236
+ rollback();
450
237
  }
451
- }, [nodes, deleteSound, setEdges]);
238
+ }, [
239
+ deleteSound,
240
+ removeRepository,
241
+ removeFeature,
242
+ addRepositoryToMap,
243
+ restoreFeature,
244
+ getRepositoryData,
245
+ ]);
452
246
  const handleLayout = useCallback((direction) => {
453
- setNodes((currentNodes) => {
454
- const result = layoutWithDagre(currentNodes, edgesRef.current, {
455
- ...CANVAS_LAYOUT_DEFAULTS,
456
- direction,
457
- });
458
- setEdges(result.edges);
459
- return result.nodes;
247
+ // Layout is applied via reconcile on next server prop update.
248
+ // For immediate re-layout, we apply dagre and trigger a reconcile-like update.
249
+ const result = layoutWithDagre(nodesRef.current, edgesRef.current, {
250
+ ...CANVAS_LAYOUT_DEFAULTS,
251
+ direction,
460
252
  });
461
- }, [setEdges]);
253
+ reconcile(result.nodes, result.edges);
254
+ }, [reconcile]);
462
255
  const handleAddRepository = useCallback((path) => {
463
256
  const tempId = `repo-temp-${Date.now()}`;
464
257
  const repoName = path
465
258
  .replace(/[\\/]+$/, '')
466
259
  .split(/[\\/]/)
467
260
  .pop() ?? path;
468
- // Optimistic UI: add node and re-layout immediately
469
- setNodes((currentNodes) => {
470
- const newNode = {
471
- id: tempId,
472
- type: 'repositoryNode',
473
- position: { x: 0, y: 0 },
474
- data: { name: repoName, repositoryPath: path, id: tempId },
475
- };
476
- const allNodes = [...currentNodes, newNode];
477
- const layoutResult = layoutWithDagre(allNodes, edgesRef.current, CANVAS_LAYOUT_DEFAULTS);
478
- setEdges(layoutResult.edges);
479
- return layoutResult.nodes;
480
- });
481
- // Persist via server action
261
+ addRepositoryToMap(tempId, { name: repoName, repositoryPath: path, id: tempId });
482
262
  addRepository({ path, name: repoName })
483
263
  .then((result) => {
484
264
  if (result.error) {
485
- // Rollback optimistic node and re-layout
486
- setNodes((prev) => {
487
- const remaining = prev.filter((n) => n.id !== tempId);
488
- const layoutResult = layoutWithDagre(remaining, edgesRef.current, CANVAS_LAYOUT_DEFAULTS);
489
- setEdges(layoutResult.edges);
490
- return layoutResult.nodes;
491
- });
265
+ removeRepository(tempId);
492
266
  toast.error(result.error);
493
267
  return;
494
268
  }
495
- // Replace temp ID with real repository ID
496
269
  const repo = result.repository;
497
270
  const realId = `repo-${repo.id}`;
498
- setNodes((prev) => prev.map((n) => n.id === tempId
499
- ? {
500
- ...n,
501
- id: realId,
502
- data: { ...n.data, id: repo.id, repositoryPath: repo.path },
503
- }
504
- : n));
505
- setEdges((prev) => prev.map((e) => ({
506
- ...e,
507
- source: e.source === tempId ? realId : e.source,
508
- target: e.target === tempId ? realId : e.target,
509
- id: e.id.replace(tempId, realId),
510
- })));
271
+ replaceRepository(tempId, realId, {
272
+ name: repo.name,
273
+ repositoryPath: repo.path,
274
+ id: repo.id,
275
+ });
511
276
  createSound.play();
512
277
  })
513
278
  .catch(() => {
514
- // Rollback optimistic node and re-layout
515
- setNodes((prev) => {
516
- const remaining = prev.filter((n) => n.id !== tempId);
517
- const layoutResult = layoutWithDagre(remaining, edgesRef.current, CANVAS_LAYOUT_DEFAULTS);
518
- setEdges(layoutResult.edges);
519
- return layoutResult.nodes;
520
- });
279
+ removeRepository(tempId);
521
280
  toast.error('Failed to add repository');
522
281
  });
523
- }, [createSound, setEdges]);
282
+ }, [addRepositoryToMap, removeRepository, replaceRepository, createSound]);
524
283
  return {
525
284
  nodes,
526
285
  edges,
@@ -531,5 +290,8 @@ export function useControlCenterState(initialNodes, initialEdges) {
531
290
  handleDeleteFeature,
532
291
  handleDeleteRepository,
533
292
  createFeatureNode,
293
+ getFeatureRepositoryPath,
294
+ getRepositoryData,
295
+ setCallbacks,
534
296
  };
535
297
  }