@shepai/cli 1.72.9-pr218.000ada0 → 1.72.9-pr220.e1bfec0

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 (326) 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/src/presentation/web/app/actions/create-feature.d.ts.map +1 -1
  7. package/dist/src/presentation/web/app/actions/create-feature.js +20 -2
  8. package/dist/src/presentation/web/app/actions/get-feature-metadata.d.ts +10 -0
  9. package/dist/src/presentation/web/app/actions/get-feature-metadata.d.ts.map +1 -0
  10. package/dist/src/presentation/web/app/actions/get-feature-metadata.js +14 -0
  11. package/dist/src/presentation/web/app/api/agent-events/route.d.ts.map +1 -1
  12. package/dist/src/presentation/web/app/api/agent-events/route.js +15 -0
  13. package/dist/src/presentation/web/components/common/add-repository-button/add-repository-button.d.ts.map +1 -0
  14. package/dist/src/presentation/web/components/common/add-repository-button/index.d.ts +2 -0
  15. package/dist/src/presentation/web/components/common/add-repository-button/index.d.ts.map +1 -0
  16. package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/index.js +0 -1
  17. package/dist/src/presentation/web/components/common/add-repository-button/pick-folder.d.ts.map +1 -0
  18. package/dist/src/presentation/web/components/common/control-center-drawer/create-drawer-client.d.ts.map +1 -1
  19. package/dist/src/presentation/web/components/common/control-center-drawer/create-drawer-client.js +13 -16
  20. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.d.ts.map +1 -1
  21. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.js +35 -16
  22. package/dist/src/presentation/web/components/common/index.d.ts +1 -1
  23. package/dist/src/presentation/web/components/common/index.d.ts.map +1 -1
  24. package/dist/src/presentation/web/components/common/index.js +1 -1
  25. package/dist/src/presentation/web/components/features/control-center/control-center-empty-state.js +1 -1
  26. package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
  27. package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +23 -4
  28. package/dist/src/presentation/web/components/features/control-center/control-center.stories.d.ts +0 -11
  29. package/dist/src/presentation/web/components/features/control-center/control-center.stories.d.ts.map +1 -1
  30. package/dist/src/presentation/web/components/features/control-center/control-center.stories.js +2 -37
  31. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts +8 -0
  32. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
  33. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +152 -350
  34. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.d.ts +2 -10
  35. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.d.ts.map +1 -1
  36. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.js +10 -42
  37. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.stories.d.ts.map +1 -1
  38. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.stories.js +0 -46
  39. package/dist/src/presentation/web/components/layouts/app-shell/app-shell.js +1 -1
  40. package/dist/src/presentation/web/hooks/use-graph-state.d.ts +41 -0
  41. package/dist/src/presentation/web/hooks/use-graph-state.d.ts.map +1 -0
  42. package/dist/src/presentation/web/hooks/use-graph-state.js +230 -0
  43. package/dist/src/presentation/web/lib/derive-graph.d.ts +52 -0
  44. package/dist/src/presentation/web/lib/derive-graph.d.ts.map +1 -0
  45. package/dist/src/presentation/web/lib/derive-graph.js +126 -0
  46. package/dist/src/presentation/web/lib/layout-with-dagre.d.ts.map +1 -1
  47. package/dist/src/presentation/web/lib/layout-with-dagre.js +0 -1
  48. package/dist/tsconfig.build.tsbuildinfo +1 -1
  49. package/package.json +1 -1
  50. package/web/.next/BUILD_ID +1 -1
  51. package/web/.next/build-manifest.json +2 -2
  52. package/web/.next/cache/.previewinfo +1 -1
  53. package/web/.next/cache/.rscinfo +1 -1
  54. package/web/.next/cache/.tsbuildinfo +1 -1
  55. package/web/.next/cache/config.json +3 -3
  56. package/web/.next/fallback-build-manifest.json +2 -2
  57. package/web/.next/prerender-manifest.json +3 -3
  58. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +34 -34
  59. package/web/.next/server/app/(dashboard)/@drawer/create/page.js +3 -3
  60. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  61. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  62. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +40 -40
  63. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js +3 -3
  64. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  65. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  66. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +30 -30
  67. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js +3 -3
  68. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  69. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  70. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +34 -34
  71. package/web/.next/server/app/(dashboard)/create/page.js +3 -3
  72. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  73. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  74. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +40 -40
  75. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js +3 -3
  76. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  77. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  78. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +30 -30
  79. package/web/.next/server/app/(dashboard)/page.js +3 -3
  80. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  81. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  82. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +30 -30
  83. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js +3 -3
  84. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  85. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  86. package/web/.next/server/app/_global-error.html +2 -2
  87. package/web/.next/server/app/_global-error.rsc +1 -1
  88. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  89. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  90. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  91. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  92. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  93. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  94. package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  95. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  96. package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  97. package/web/.next/server/app/skills/page.js.nft.json +1 -1
  98. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  99. package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  100. package/web/.next/server/app/tools/page.js.nft.json +1 -1
  101. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  102. package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  103. package/web/.next/server/app/version/page.js.nft.json +1 -1
  104. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  105. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +2 -2
  106. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js.map +1 -1
  107. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  108. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  109. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js +3 -0
  110. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js.map +1 -0
  111. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js +3 -0
  112. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js.map +1 -0
  113. package/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js +2 -2
  114. package/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js.map +1 -1
  115. package/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js +2 -2
  116. package/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js.map +1 -1
  117. package/web/.next/server/chunks/ssr/{[root-of-the-server]__cbae9863._.js → [root-of-the-server]__6258349c._.js} +2 -2
  118. package/web/.next/server/chunks/ssr/[root-of-the-server]__6258349c._.js.map +1 -0
  119. package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
  120. package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js.map +1 -1
  121. package/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js +2 -2
  122. package/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js.map +1 -1
  123. package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
  124. package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js.map +1 -1
  125. 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
  126. package/web/.next/server/chunks/ssr/[root-of-the-server]__94a6d275._.js.map +1 -0
  127. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
  128. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
  129. package/web/.next/server/chunks/ssr/_0e9d6bf7._.js +1 -1
  130. package/web/.next/server/chunks/ssr/_0e9d6bf7._.js.map +1 -1
  131. package/web/.next/server/chunks/ssr/_250a63ae._.js +3 -0
  132. package/web/.next/server/chunks/ssr/_250a63ae._.js.map +1 -0
  133. package/web/.next/server/chunks/ssr/_61351ed3._.js +1 -1
  134. package/web/.next/server/chunks/ssr/_61351ed3._.js.map +1 -1
  135. package/web/.next/server/chunks/ssr/_63688261._.js +6 -0
  136. package/web/.next/server/chunks/ssr/_63688261._.js.map +1 -0
  137. package/web/.next/server/chunks/ssr/_8400c8bb._.js +1 -1
  138. package/web/.next/server/chunks/ssr/_8400c8bb._.js.map +1 -1
  139. package/web/.next/server/chunks/ssr/_9f940437._.js +1 -1
  140. package/web/.next/server/chunks/ssr/_9f940437._.js.map +1 -1
  141. 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
  142. package/web/.next/server/chunks/ssr/{src_presentation_web_7188a290._.js.map → src_presentation_web_2b91e4ec._.js.map} +1 -1
  143. package/web/.next/server/chunks/ssr/{src_presentation_web_5ea689db._.js → src_presentation_web_5bcc1537._.js} +2 -2
  144. package/web/.next/server/chunks/ssr/{src_presentation_web_5ea689db._.js.map → src_presentation_web_5bcc1537._.js.map} +1 -1
  145. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_5955c012.js +3 -0
  146. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_5955c012.js.map +1 -0
  147. package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js +3 -0
  148. package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js.map +1 -0
  149. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  150. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  151. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  152. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  153. package/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js +8 -0
  154. package/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js.map +1 -0
  155. package/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js +8 -0
  156. package/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js.map +1 -0
  157. package/web/.next/server/pages/500.html +2 -2
  158. package/web/.next/server/server-reference-manifest.js +1 -1
  159. package/web/.next/server/server-reference-manifest.json +142 -142
  160. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  161. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  162. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  163. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +34 -34
  164. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page.js +3 -3
  165. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  166. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  167. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +40 -40
  168. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js +3 -3
  169. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  170. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  171. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +30 -30
  172. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js +3 -3
  173. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  174. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  175. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +34 -34
  176. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page.js +3 -3
  177. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  178. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  179. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +40 -40
  180. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page.js +3 -3
  181. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  182. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  183. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +30 -30
  184. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page.js +3 -3
  185. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  186. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  187. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +30 -30
  188. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js +3 -3
  189. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  190. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  191. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  192. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  193. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  194. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  195. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  196. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  197. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  198. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  199. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  200. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  201. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  202. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page.js.nft.json +1 -1
  203. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  204. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  205. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page.js.nft.json +1 -1
  206. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  207. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  208. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js.nft.json +1 -1
  209. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  210. package/web/.next/standalone/src/presentation/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +2 -2
  211. 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
  212. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js +3 -0
  213. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js +3 -0
  214. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js +2 -2
  215. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js +2 -2
  216. 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
  217. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
  218. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js +2 -2
  219. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
  220. 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
  221. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
  222. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_0e9d6bf7._.js +1 -1
  223. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_250a63ae._.js +3 -0
  224. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_61351ed3._.js +1 -1
  225. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_63688261._.js +6 -0
  226. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_8400c8bb._.js +1 -1
  227. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_9f940437._.js +1 -1
  228. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{src_presentation_web_7188a290._.js → src_presentation_web_2b91e4ec._.js} +2 -2
  229. package/web/.next/{server/chunks/ssr/src_presentation_web_7188a290._.js → standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_5bcc1537._.js} +2 -2
  230. 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
  231. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js +3 -0
  232. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  233. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  234. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js +8 -0
  235. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js +8 -0
  236. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  237. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  238. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +142 -142
  239. package/web/.next/standalone/src/presentation/web/CLAUDE.md +77 -33
  240. package/web/.next/standalone/src/presentation/web/app/actions/create-feature.ts +27 -2
  241. package/web/.next/standalone/src/presentation/web/app/actions/get-feature-metadata.ts +18 -0
  242. package/web/.next/standalone/src/presentation/web/app/api/agent-events/route.ts +17 -0
  243. package/web/.next/standalone/src/presentation/web/components/common/add-repository-button/index.ts +1 -0
  244. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/create-drawer-client.tsx +15 -19
  245. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.tsx +44 -18
  246. package/web/.next/standalone/src/presentation/web/components/common/index.ts +1 -5
  247. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-empty-state.tsx +1 -1
  248. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +24 -8
  249. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center.stories.tsx +2 -41
  250. package/web/.next/standalone/src/presentation/web/components/features/control-center/use-control-center-state.ts +180 -429
  251. package/web/.next/standalone/src/presentation/web/components/features/features-canvas/features-canvas.stories.tsx +0 -48
  252. package/web/.next/standalone/src/presentation/web/components/features/features-canvas/features-canvas.tsx +13 -59
  253. package/web/.next/standalone/src/presentation/web/components/layouts/app-shell/app-shell.tsx +1 -1
  254. package/web/.next/standalone/src/presentation/web/hooks/use-graph-state.ts +343 -0
  255. package/web/.next/standalone/src/presentation/web/lib/derive-graph.ts +178 -0
  256. package/web/.next/standalone/src/presentation/web/lib/layout-with-dagre.ts +0 -1
  257. package/web/.next/static/chunks/{0eeb65b8300fb7cd.js → 0a883e2efecfd563.js} +1 -1
  258. package/web/.next/static/chunks/{4a8df922364596f6.js → 596ca28d78baae25.js} +1 -1
  259. package/web/.next/static/chunks/{99381b9c479936d5.js → 638824249d103905.js} +1 -1
  260. package/web/.next/static/chunks/63a80e9d4c4b0253.js +4 -0
  261. package/web/.next/static/chunks/71c9ecdceac1939f.css +2 -0
  262. package/web/.next/static/chunks/a040b058d79f4ade.js +1 -0
  263. package/web/.next/static/chunks/{1249c7c8745b972e.js → c575ab63c138645b.js} +1 -1
  264. package/web/.next/static/chunks/cdc92cdf81005c84.js +1 -0
  265. package/web/.next/static/chunks/{a676f15c03fa66b9.js → e05eb1cc84ee254a.js} +1 -1
  266. package/web/.next/static/chunks/ef8f8940eac6c542.js +1 -0
  267. package/web/.next/static/chunks/{9be617fc61b893f2.js → f32c98a709949c5b.js} +1 -1
  268. package/web/.next/trace +1 -1
  269. package/web/.next/trace-build +1 -1
  270. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-button.d.ts.map +0 -1
  271. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node-config.d.ts +0 -7
  272. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node-config.d.ts.map +0 -1
  273. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node-config.js +0 -1
  274. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.d.ts +0 -6
  275. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.d.ts.map +0 -1
  276. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.js +0 -23
  277. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.d.ts +0 -10
  278. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.d.ts.map +0 -1
  279. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.js +0 -33
  280. package/dist/src/presentation/web/components/common/add-repository-node/index.d.ts +0 -4
  281. package/dist/src/presentation/web/components/common/add-repository-node/index.d.ts.map +0 -1
  282. package/dist/src/presentation/web/components/common/add-repository-node/pick-folder.d.ts.map +0 -1
  283. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_3a02d8a5.js +0 -3
  284. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_3a02d8a5.js.map +0 -1
  285. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_a3044852.js +0 -3
  286. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_a3044852.js.map +0 -1
  287. package/web/.next/server/chunks/ssr/[root-of-the-server]__cbae9863._.js.map +0 -1
  288. package/web/.next/server/chunks/ssr/[root-of-the-server]__e5e09f72._.js.map +0 -1
  289. package/web/.next/server/chunks/ssr/_6e08ce21._.js +0 -3
  290. package/web/.next/server/chunks/ssr/_6e08ce21._.js.map +0 -1
  291. package/web/.next/server/chunks/ssr/_6ef06ca9._.js +0 -6
  292. package/web/.next/server/chunks/ssr/_6ef06ca9._.js.map +0 -1
  293. package/web/.next/server/chunks/ssr/src_presentation_web_0a49d9ee._.js +0 -8
  294. package/web/.next/server/chunks/ssr/src_presentation_web_0a49d9ee._.js.map +0 -1
  295. package/web/.next/server/chunks/ssr/src_presentation_web_79b0305a._.js +0 -8
  296. package/web/.next/server/chunks/ssr/src_presentation_web_79b0305a._.js.map +0 -1
  297. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_cc16bd01.js +0 -3
  298. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_cc16bd01.js.map +0 -1
  299. package/web/.next/server/chunks/ssr/src_presentation_web_app_(dashboard)_get-graph-data_ts_b018483e._.js +0 -3
  300. package/web/.next/server/chunks/ssr/src_presentation_web_app_(dashboard)_get-graph-data_ts_b018483e._.js.map +0 -1
  301. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_3a02d8a5.js +0 -3
  302. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_a3044852.js +0 -3
  303. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_6e08ce21._.js +0 -3
  304. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_6ef06ca9._.js +0 -6
  305. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_0a49d9ee._.js +0 -8
  306. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_79b0305a._.js +0 -8
  307. 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
  308. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_app_(dashboard)_get-graph-data_ts_b018483e._.js +0 -3
  309. package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/add-repository-node-config.ts +0 -8
  310. package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.tsx +0 -70
  311. package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/add-repository-node.tsx +0 -49
  312. package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/index.ts +0 -3
  313. package/web/.next/static/chunks/1c15ab8668ecf473.js +0 -1
  314. package/web/.next/static/chunks/2cf89d17ee7717a6.js +0 -4
  315. package/web/.next/static/chunks/981b8df9ad045742.js +0 -1
  316. package/web/.next/static/chunks/acce8a15d6896dea.css +0 -2
  317. package/web/.next/static/chunks/b579fd590a79bf35.js +0 -1
  318. /package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/add-repository-button.d.ts +0 -0
  319. /package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/add-repository-button.js +0 -0
  320. /package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/pick-folder.d.ts +0 -0
  321. /package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/pick-folder.js +0 -0
  322. /package/web/.next/standalone/src/presentation/web/components/common/{add-repository-node → add-repository-button}/add-repository-button.tsx +0 -0
  323. /package/web/.next/standalone/src/presentation/web/components/common/{add-repository-node → add-repository-button}/pick-folder.ts +0 -0
  324. /package/web/.next/static/{nnR12fAsrSagAfeV6xj6D → uMxPSDPwZCludF5408rWJ}/_buildManifest.js +0 -0
  325. /package/web/.next/static/{nnR12fAsrSagAfeV6xj6D → uMxPSDPwZCludF5408rWJ}/_clientMiddlewareManifest.json +0 -0
  326. /package/web/.next/static/{nnR12fAsrSagAfeV6xj6D → uMxPSDPwZCludF5408rWJ}/_ssgManifest.js +0 -0
@@ -1,11 +1,11 @@
1
1
  'use client';
2
2
 
3
- import { useState, useCallback, useEffect, useRef } from 'react';
3
+ import { useCallback, useEffect, useRef } from 'react';
4
4
  import { useRouter } from 'next/navigation';
5
5
  import { toast } from 'sonner';
6
- import { applyNodeChanges } from '@xyflow/react';
7
6
  import type { Connection, Edge, NodeChange } from '@xyflow/react';
8
7
  import type { FeatureNodeData } from '@/components/common/feature-node';
8
+ import type { RepositoryNodeData } from '@/components/common/repository-node';
9
9
  import type { CanvasNodeType } from '@/components/features/features-canvas';
10
10
  import {
11
11
  layoutWithDagre,
@@ -15,13 +15,15 @@ import {
15
15
  import { deleteFeature } from '@/app/actions/delete-feature';
16
16
  import { addRepository } from '@/app/actions/add-repository';
17
17
  import { deleteRepository } from '@/app/actions/delete-repository';
18
- import { fetchGraphData } from '@/app/actions/get-graph-data';
18
+ import { getFeatureMetadata } from '@/app/actions/get-feature-metadata';
19
19
  import { useAgentEventsContext } from '@/hooks/agent-events-provider';
20
20
  import { useSoundAction } from '@/hooks/use-sound-action';
21
21
  import {
22
22
  mapEventTypeToState,
23
23
  mapPhaseNameToLifecycle,
24
24
  } from '@/components/common/feature-node/derive-feature-state';
25
+ import { NotificationEventType } from '@shepai/core/domain/generated/output';
26
+ import { useGraphState, type GraphCallbacks } from '@/hooks/use-graph-state';
25
27
 
26
28
  export interface ControlCenterState {
27
29
  nodes: CanvasNodeType[];
@@ -37,6 +39,12 @@ export interface ControlCenterState {
37
39
  dataOverride?: Partial<FeatureNodeData>,
38
40
  edgeType?: string
39
41
  ) => string;
42
+ /** Stable lookup: repositoryPath for a feature node. */
43
+ getFeatureRepositoryPath: (featureNodeId: string) => string | undefined;
44
+ /** Stable lookup: repository data by nodeId. */
45
+ getRepositoryData: (nodeId: string) => RepositoryNodeData | undefined;
46
+ /** Sync callbacks into derived node data (does not trigger re-render). */
47
+ setCallbacks: (callbacks: GraphCallbacks) => void;
40
48
  }
41
49
 
42
50
  let nextFeatureId = 0;
@@ -46,37 +54,31 @@ export function useControlCenterState(
46
54
  initialEdges: Edge[]
47
55
  ): ControlCenterState {
48
56
  const router = useRouter();
49
- const [nodes, setNodes] = useState<CanvasNodeType[]>(initialNodes);
50
- // eslint-disable-next-line react/hook-use-state -- raw setter renamed; public setEdges wrapper keeps edgesRef in sync
51
- const [edges, setEdgesRaw] = useState<Edge[]>(initialEdges);
52
- const edgesRef = useRef<Edge[]>(initialEdges);
53
-
54
- // Wrapper that keeps edgesRef in sync with edges state, allowing
55
- // createFeatureNode to read current edges without a closure dependency.
56
- const setEdges = useCallback((update: Edge[] | ((prev: Edge[]) => Edge[])) => {
57
- setEdgesRaw((prev) => {
58
- const next = typeof update === 'function' ? update(prev) : update;
59
- edgesRef.current = next;
60
- return next;
61
- });
62
- }, []);
63
57
  const deleteSound = useSoundAction('delete');
64
58
  const createSound = useSoundAction('create');
65
59
 
66
- // Sync server props into local state when parent re-renders with new data
60
+ const {
61
+ nodes,
62
+ edges,
63
+ reconcile,
64
+ updateFeature,
65
+ addPendingFeature,
66
+ removeFeature,
67
+ restoreFeature,
68
+ addRepository: addRepositoryToMap,
69
+ removeRepository,
70
+ replaceRepository,
71
+ getFeatureRepositoryPath,
72
+ getRepositoryData,
73
+ setCallbacks,
74
+ } = useGraphState(initialNodes, initialEdges);
75
+
76
+ // Sync server props into domain Maps when initialNodes/initialEdges change.
77
+ // Keyed by a stable string so we don't re-reconcile on every render.
67
78
  const initialNodeKey = initialNodes
68
79
  .map((n) => n.id)
69
80
  .sort()
70
81
  .join(',');
71
- const initialEdgeKey = initialEdges
72
- .map((e) => e.id)
73
- .sort()
74
- .join(',');
75
-
76
- // Track previous feature states so we can detect transitions on server refresh
77
- const prevFeatureStatesRef = useRef<Map<string, FeatureNodeData['state']>>(new Map());
78
-
79
- // Stable key that changes when feature DATA changes (state/lifecycle), not just IDs.
80
82
  const initialDataKey = initialNodes
81
83
  .filter((n) => n.type === 'featureNode')
82
84
  .map((n) => {
@@ -86,83 +88,40 @@ export function useControlCenterState(
86
88
  .sort()
87
89
  .join(',');
88
90
 
89
- // Sync server props into local state — keyed by derived strings (initialNodeKey/initialEdgeKey)
90
- // to avoid infinite re-renders from unstable array references.
91
- useEffect(() => {
92
- setNodes((currentNodes) => {
93
- // Build a lookup of current node positions by ID
94
- const currentById = new Map(currentNodes.map((n) => [n.id, n]));
95
-
96
- // Identify optimistic "creating" nodes (they have temp IDs not in server data)
97
- const serverIds = new Set(initialNodes.map((n) => n.id));
98
- const creatingNodes = currentNodes.filter(
99
- (n) =>
100
- n.type === 'featureNode' &&
101
- (n.data as FeatureNodeData).state === 'creating' &&
102
- !serverIds.has(n.id)
103
- );
104
-
105
- // Merge server nodes with client positions, tracking if relayout is needed
106
- let needsRelayout = false;
107
- const merged = initialNodes.map((serverNode) => {
108
- // Node already exists on canvas — keep its position, update data
109
- const existing = currentById.get(serverNode.id);
110
- if (existing) {
111
- return { ...serverNode, position: existing.position };
112
- }
113
-
114
- // New server node — inherit position from an optimistic creating node if available
115
- if (serverNode.type === 'featureNode' && creatingNodes.length > 0) {
116
- const donor = creatingNodes.shift()!;
117
- return { ...serverNode, position: donor.position };
118
- }
119
-
120
- // Truly new node with no optimistic counterpart — needs relayout
121
- needsRelayout = true;
122
- return serverNode;
123
- });
124
-
125
- // Also check if non-creating nodes were removed (graph got smaller)
126
- if (!needsRelayout) {
127
- needsRelayout = currentNodes.some(
128
- (cn) =>
129
- !serverIds.has(cn.id) &&
130
- !(cn.type === 'featureNode' && (cn.data as FeatureNodeData).state === 'creating')
131
- );
132
- }
91
+ const prevReconcileKey = useRef('');
133
92
 
134
- if (needsRelayout) {
135
- const layoutResult = layoutWithDagre(merged, initialEdges, CANVAS_LAYOUT_DEFAULTS);
136
- setEdges(layoutResult.edges);
137
- return layoutResult.nodes;
138
- }
93
+ useEffect(() => {
94
+ const key = `${initialNodeKey}|${initialDataKey}`;
95
+ if (key !== prevReconcileKey.current) {
96
+ prevReconcileKey.current = key;
97
+ reconcile(initialNodes, initialEdges);
98
+ }
99
+ }, [initialNodeKey, initialDataKey, initialNodes, initialEdges, reconcile]);
139
100
 
140
- return merged;
141
- });
142
- }, [initialNodeKey, initialNodes, initialEdges, setEdges]);
101
+ // Track previous feature states for fallback notifications
102
+ const prevFeatureStatesRef = useRef<Map<string, FeatureNodeData['state']>>(new Map());
143
103
 
144
- useEffect(() => {
145
- setEdges(initialEdges);
146
- }, [initialEdgeKey, initialEdges, setEdges]);
104
+ const initialFeatureDataKey = initialNodes
105
+ .filter((n) => n.type === 'featureNode')
106
+ .map((n) => {
107
+ const d = n.data as FeatureNodeData;
108
+ return `${n.id}:${d.state}`;
109
+ })
110
+ .sort()
111
+ .join(',');
147
112
 
148
- // Fallback notifications from server-prop state transitions.
149
- // Fires only when SSE didn't already deliver the matching event for a feature.
150
113
  const { events } = useAgentEventsContext();
151
114
 
152
115
  useEffect(() => {
153
116
  const prevStates = prevFeatureStatesRef.current;
154
-
155
117
  for (const node of initialNodes) {
156
118
  if (node.type !== 'featureNode') continue;
157
119
  const data = node.data as FeatureNodeData;
158
120
  const prev = prevStates.get(node.id);
159
-
160
121
  if (prev !== undefined && prev !== data.state) {
161
- // Check if SSE already delivered a matching event for this feature
162
122
  const sseAlreadyCovered = events.some(
163
123
  (e) => e.featureId === data.featureId && mapEventTypeToState(e.eventType) === data.state
164
124
  );
165
-
166
125
  if (!sseAlreadyCovered) {
167
126
  if (data.state === 'done') {
168
127
  toast.success(data.name, { description: 'Feature completed!' });
@@ -171,9 +130,7 @@ export function useControlCenterState(
171
130
  description: 'Waiting for your approval',
172
131
  action: {
173
132
  label: 'Review',
174
- onClick: () => {
175
- router.push(`/feature/${data.featureId}`);
176
- },
133
+ onClick: () => router.push(`/feature/${data.featureId}`),
177
134
  },
178
135
  });
179
136
  } else if (data.state === 'error') {
@@ -181,186 +138,78 @@ export function useControlCenterState(
181
138
  }
182
139
  }
183
140
  }
184
-
185
141
  prevStates.set(node.id, data.state);
186
142
  }
187
- }, [initialDataKey, initialNodes, events, router]);
143
+ }, [initialFeatureDataKey, initialNodes, events, router]);
188
144
 
189
- // Targeted optimistic updates from SSE agent events.
190
- // Uses `events` array (not `lastEvent`) so that React batching cannot silently
191
- // drop events when multiple SSE messages arrive in the same tick.
145
+ // SSE effect: only state + lifecycle updates
192
146
  const processedEventCountRef = useRef(0);
193
147
 
194
148
  useEffect(() => {
195
149
  if (events.length <= processedEventCountRef.current) return;
196
-
197
150
  const newEvents = events.slice(processedEventCountRef.current);
198
151
  processedEventCountRef.current = events.length;
199
152
 
153
+ // PhaseCompleted must NOT set state when WaitingApproval is in the same poll batch —
154
+ // they arrive together and PhaseCompleted → 'running' would revert 'action-required'.
155
+ // But when PhaseCompleted arrives WITHOUT WaitingApproval (e.g. after approval), we
156
+ // must allow it to update state as a fallback for any missed AgentStarted event.
157
+ const waitingApprovalFeatures = new Set(
158
+ newEvents
159
+ .filter((e) => e.eventType === NotificationEventType.WaitingApproval)
160
+ .map((e) => e.featureId)
161
+ );
162
+
200
163
  for (const event of newEvents) {
201
- const newState = mapEventTypeToState(event.eventType);
164
+ const newState =
165
+ event.eventType === NotificationEventType.PhaseCompleted &&
166
+ waitingApprovalFeatures.has(event.featureId)
167
+ ? undefined
168
+ : mapEventTypeToState(event.eventType);
202
169
  const newLifecycle = mapPhaseNameToLifecycle(event.phaseName);
203
170
 
204
- // Targeted node update — only clone the matched node
205
- setNodes((prev) =>
206
- prev.map((node) => {
207
- if (node.type !== 'featureNode') return node;
208
- const data = node.data as FeatureNodeData;
209
- if (data.featureId !== event.featureId) return node;
210
-
211
- return {
212
- ...node,
213
- data: {
214
- ...data,
215
- state: newState,
216
- ...(newLifecycle !== undefined && { lifecycle: newLifecycle }),
217
- },
218
- };
219
- })
220
- );
171
+ const nodeId = `feat-${event.featureId}`;
172
+ if (newState !== undefined || newLifecycle !== undefined) {
173
+ updateFeature(nodeId, {
174
+ ...(newState !== undefined && { state: newState }),
175
+ ...(newLifecycle !== undefined && { lifecycle: newLifecycle }),
176
+ });
177
+ }
221
178
  }
222
- }, [events]);
223
-
224
- // Lightweight server-action polling fallback: fetches fresh graph data every 5s
225
- // when any feature is in an active state. This avoids full router.refresh() while
226
- // ensuring the UI stays in sync even if SSE events are missed.
227
- //
228
- // We derive a stable boolean so the interval isn't torn down on every node change.
229
- const hasActiveFeature = nodes.some((n) => {
230
- if (n.type !== 'featureNode') return false;
231
- const data = n.data as FeatureNodeData;
232
- return (
233
- data.state === 'running' || data.state === 'action-required' || data.state === 'creating'
234
- );
235
- });
236
-
237
- useEffect(() => {
238
- if (!hasActiveFeature) return;
239
-
240
- const interval = setInterval(async () => {
241
- try {
242
- const { nodes: serverNodes } = await fetchGraphData();
243
- setNodes((currentNodes) => {
244
- const serverById = new Map(serverNodes.map((n) => [n.id, n]));
245
- let changed = false;
246
-
247
- const merged = currentNodes.map((node) => {
248
- const serverNode = serverById.get(node.id);
249
- if (!serverNode || node.type !== 'featureNode') return node;
250
- const currentData = node.data as FeatureNodeData;
251
- const serverData = serverNode.data as FeatureNodeData;
252
-
253
- // Only update if server data has different state or lifecycle
254
- if (
255
- currentData.state === serverData.state &&
256
- currentData.lifecycle === serverData.lifecycle
257
- ) {
258
- return node;
259
- }
260
-
261
- changed = true;
262
- return {
263
- ...node,
264
- data: {
265
- ...currentData,
266
- state: serverData.state,
267
- lifecycle: serverData.lifecycle,
268
- progress: serverData.progress,
269
- ...(serverData.errorMessage && { errorMessage: serverData.errorMessage }),
270
- ...(serverData.runtime && { runtime: serverData.runtime }),
271
- ...(serverData.agentType && { agentType: serverData.agentType }),
272
- ...(serverData.pr && { pr: serverData.pr }),
273
- ...(serverData.blockedBy && { blockedBy: serverData.blockedBy }),
274
- },
275
- };
276
- });
179
+ }, [events, updateFeature]);
277
180
 
278
- // Find new server nodes not yet on the client
279
- const clientIds = new Set(currentNodes.map((n) => n.id));
280
- const newServerNodes = serverNodes.filter((n) => !clientIds.has(n.id));
281
-
282
- // Replace optimistic "creating" temp nodes with their real server
283
- // counterparts. Temp nodes have IDs like "feature-<ts>" while real
284
- // nodes use "feat-<uuid>". Match by repositoryPath + name.
285
- if (newServerNodes.length > 0) {
286
- const creatingIndices: number[] = [];
287
- merged.forEach((n, i) => {
288
- if (n.type === 'featureNode' && (n.data as FeatureNodeData).state === 'creating') {
289
- creatingIndices.push(i);
290
- }
291
- });
292
-
293
- for (const serverNode of newServerNodes) {
294
- if (serverNode.type !== 'featureNode' || creatingIndices.length === 0) continue;
295
- const idx = creatingIndices.shift()!;
296
- // Replace the temp node in-place, keeping its position
297
- merged[idx] = { ...serverNode, position: merged[idx].position };
298
- changed = true;
299
- }
181
+ // Separate effect: fetch metadata (name + description) when SSE reports it changed
182
+ const metadataFetchedRef = useRef<Set<string>>(new Set());
300
183
 
301
- // Any remaining new server nodes that didn't replace a temp node
302
- const replacedIds = new Set(merged.map((n) => n.id));
303
- const trueNewNodes = newServerNodes.filter((n) => !replacedIds.has(n.id));
304
- if (trueNewNodes.length > 0) {
305
- changed = true;
306
- return [...merged, ...trueNewNodes];
307
- }
184
+ useEffect(() => {
185
+ for (const event of events) {
186
+ if (event.message !== 'Feature metadata updated') continue;
187
+ if (metadataFetchedRef.current.has(event.featureId)) continue;
188
+ metadataFetchedRef.current.add(event.featureId);
189
+
190
+ const nodeId = `feat-${event.featureId}`;
191
+ getFeatureMetadata(event.featureId)
192
+ .then((meta) => {
193
+ if (meta) {
194
+ updateFeature(nodeId, { name: meta.name, description: meta.description });
308
195
  }
309
-
310
- return changed ? merged : currentNodes;
196
+ })
197
+ .catch(() => {
198
+ // Silent: metadata fetch failure is non-critical
311
199
  });
312
- } catch {
313
- // Silently ignore polling errors — SSE is the primary mechanism
314
- }
315
- }, 5_000);
316
-
317
- return () => clearInterval(interval);
318
- }, [hasActiveFeature]);
319
-
320
- const onNodesChange = useCallback((changes: NodeChange<CanvasNodeType>[]) => {
321
- // Filter out 'replace' changes — in controlled mode these come from
322
- // React Flow's StoreUpdater syncing the nodes prop back into state.
323
- // Applying them creates a circular update loop (nodes → enrichedNodes →
324
- // StoreUpdater → onNodesChange → setNodes → nodes → …) that both causes
325
- // excessive re-renders and overwrites SSE-driven state changes with stale data.
326
- const applicable = changes.filter((c) => c.type !== 'replace');
327
- if (applicable.length > 0) {
328
- setNodes((ns) => applyNodeChanges(applicable, ns));
329
200
  }
330
- }, []);
331
-
332
- const handleConnect = useCallback(
333
- (connection: Connection) => {
334
- if (!connection.source || !connection.target) return;
201
+ }, [events, updateFeature]);
335
202
 
336
- setNodes((currentNodes) => {
337
- const sourceNode = currentNodes.find((n) => n.id === connection.source);
338
- if (sourceNode?.type !== 'repositoryNode') return currentNodes;
339
-
340
- // Block if target feature already has a repo source
341
- setEdges((currentEdges) => {
342
- const targetAlreadyHasRepo = currentEdges.some((e) => {
343
- const edgeSource = currentNodes.find((n) => n.id === e.source);
344
- return edgeSource?.type === 'repositoryNode' && e.target === connection.target;
345
- });
346
- if (targetAlreadyHasRepo) return currentEdges;
347
-
348
- return [
349
- ...currentEdges,
350
- {
351
- id: `edge-${connection.source}-${connection.target}`,
352
- source: connection.source,
353
- target: connection.target,
354
- style: { strokeDasharray: '5 5' },
355
- },
356
- ];
357
- });
203
+ // onNodesChange is a no-op: nodes are derived from domain Maps.
204
+ // Since nodesDraggable=false and elementsSelectable=false, only React Flow's
205
+ // internal replace/dimensions changes come through — we ignore them all.
206
+ const onNodesChange = useCallback((_changes: NodeChange<CanvasNodeType>[]) => {
207
+ // Intentional no-op: domain Maps are the source of truth.
208
+ }, []);
358
209
 
359
- return currentNodes;
360
- });
361
- },
362
- [setEdges]
363
- );
210
+ const handleConnect = useCallback((_connection: Connection) => {
211
+ // Connections are managed via domain operations, not direct edge manipulation.
212
+ }, []);
364
213
 
365
214
  const createFeatureNode = useCallback(
366
215
  (
@@ -368,11 +217,14 @@ export function useControlCenterState(
368
217
  dataOverride?: Partial<FeatureNodeData>,
369
218
  edgeType?: string
370
219
  ): string => {
371
- const id = `feature-${Date.now()}-${nextFeatureId++}`;
220
+ // Use real feature ID when available (from server), otherwise temp ID
221
+ const id = dataOverride?.featureId
222
+ ? `feat-${dataOverride.featureId}`
223
+ : `feature-${Date.now()}-${nextFeatureId++}`;
372
224
  const newFeatureData: FeatureNodeData = {
373
225
  name: dataOverride?.name ?? 'New Feature',
374
226
  description: dataOverride?.description ?? 'Describe what this feature does',
375
- featureId: `#${id.slice(-4)}`,
227
+ featureId: dataOverride?.featureId ?? `#${id.slice(-4)}`,
376
228
  lifecycle: 'requirements',
377
229
  state: dataOverride?.state ?? 'running',
378
230
  progress: 0,
@@ -380,171 +232,118 @@ export function useControlCenterState(
380
232
  branch: dataOverride?.branch ?? '',
381
233
  };
382
234
 
383
- setNodes((currentNodes) => {
384
- const newNode = {
385
- id,
386
- type: 'featureNode' as const,
387
- position: { x: 0, y: 0 },
388
- data: newFeatureData,
389
- } as CanvasNodeType;
390
-
391
- const newEdge = sourceNodeId
392
- ? {
393
- id:
394
- edgeType === 'dependencyEdge'
395
- ? `dep-${sourceNodeId}-${id}`
396
- : `edge-${sourceNodeId}-${id}`,
397
- source: sourceNodeId,
398
- target: id,
399
- ...(edgeType ? { type: edgeType } : { style: { strokeDasharray: '5 5' } }),
400
- }
401
- : null;
402
-
403
- const allEdges = newEdge
404
- ? [...edgesRef.current.filter((e) => e.id !== newEdge.id), newEdge]
405
- : edgesRef.current;
406
-
407
- // Insert the new node after the last sibling connected to the same source,
408
- // so dagre's input-order preservation keeps repo groups together.
409
- let insertIndex = currentNodes.length;
410
- if (sourceNodeId) {
411
- const siblingIds = new Set(
412
- allEdges.filter((e) => e.source === sourceNodeId).map((e) => e.target)
413
- );
414
- for (let i = currentNodes.length - 1; i >= 0; i--) {
415
- if (siblingIds.has(currentNodes[i].id)) {
416
- insertIndex = i + 1;
417
- break;
418
- }
419
- }
420
- }
421
- const allNodes = [
422
- ...currentNodes.slice(0, insertIndex),
423
- newNode,
424
- ...currentNodes.slice(insertIndex),
425
- ];
426
-
427
- // Run dagre layout on the full graph so the new node is positioned consistently
428
- const layoutResult = layoutWithDagre(allNodes, allEdges, CANVAS_LAYOUT_DEFAULTS);
429
- setEdges(layoutResult.edges);
430
- return layoutResult.nodes;
431
- });
235
+ const parentNodeId = edgeType === 'dependencyEdge' && sourceNodeId ? sourceNodeId : undefined;
236
+
237
+ addPendingFeature(id, newFeatureData, parentNodeId);
432
238
 
433
239
  return id;
434
240
  },
435
- [setEdges]
241
+ [addPendingFeature]
436
242
  );
437
243
 
438
244
  const handleDeleteFeature = useCallback(
439
245
  (featureId: string) => {
440
246
  const nodeId = `feat-${featureId}`;
441
247
 
442
- // Snapshot current state for rollback
443
- const prevNodes = nodes;
444
- const prevEdges = edgesRef.current;
248
+ // Find the current entry for rollback
249
+ const prevEntry = nodes
250
+ .filter((n) => n.id === nodeId)
251
+ .map((n) => ({
252
+ nodeId: n.id,
253
+ data: n.data as FeatureNodeData,
254
+ }))[0];
445
255
 
446
- // Optimistic removal — update UI immediately
447
- setNodes((currentNodes) => {
448
- const remainingNodes = currentNodes.filter((n) => n.id !== nodeId);
449
- const remainingEdges = edgesRef.current.filter(
450
- (e) => e.source !== nodeId && e.target !== nodeId
451
- );
452
- const layoutResult = layoutWithDagre(
453
- remainingNodes,
454
- remainingEdges,
455
- CANVAS_LAYOUT_DEFAULTS
456
- );
457
- setEdges(layoutResult.edges);
458
- return layoutResult.nodes;
459
- });
256
+ // Optimistic removal
257
+ removeFeature(nodeId);
460
258
  deleteSound.play();
461
259
  toast.success('Feature deleted successfully');
462
260
  router.push('/');
463
261
 
464
- // Persist in background — rollback on failure
465
262
  deleteFeature(featureId)
466
263
  .then((result) => {
467
264
  if (result.error) {
468
- setNodes(prevNodes);
469
- setEdges(prevEdges);
265
+ if (prevEntry) restoreFeature(nodeId, prevEntry);
470
266
  toast.error(result.error);
471
267
  }
472
268
  })
473
269
  .catch(() => {
474
- setNodes(prevNodes);
475
- setEdges(prevEdges);
270
+ if (prevEntry) restoreFeature(nodeId, prevEntry);
476
271
  toast.error('Failed to delete feature');
477
272
  });
478
273
  },
479
- [nodes, router, deleteSound, setEdges]
274
+ [nodes, router, deleteSound, removeFeature, restoreFeature]
480
275
  );
481
276
 
482
277
  const handleDeleteRepository = useCallback(
483
278
  async (repositoryId: string) => {
484
279
  const repoNodeId = `repo-${repositoryId}`;
485
280
 
281
+ // Find children of this repo via edges
282
+ const childFeatureIds = new Set(
283
+ edges.filter((e) => e.source === repoNodeId).map((e) => e.target)
284
+ );
285
+
486
286
  // Snapshot for rollback
487
- const prevNodes = nodes;
488
- const prevEdges = edgesRef.current;
489
-
490
- // Optimistic: remove repo node, its child feature nodes, and all related edges
491
- setNodes((currentNodes) => {
492
- // Find feature node IDs connected to this repo via edges
493
- const childFeatureIds = new Set(
494
- edgesRef.current.filter((e) => e.source === repoNodeId).map((e) => e.target)
495
- );
496
- const remainingNodes = currentNodes.filter(
497
- (n) => n.id !== repoNodeId && !childFeatureIds.has(n.id)
498
- );
499
- const remainingEdges = edgesRef.current.filter(
500
- (e) =>
501
- e.source !== repoNodeId &&
502
- e.target !== repoNodeId &&
503
- !childFeatureIds.has(e.source) &&
504
- !childFeatureIds.has(e.target)
505
- );
506
- const layoutResult = layoutWithDagre(
507
- remainingNodes,
508
- remainingEdges,
509
- CANVAS_LAYOUT_DEFAULTS
510
- );
511
- setEdges(layoutResult.edges);
512
- return layoutResult.nodes;
513
- });
287
+ const prevRepoData = getRepositoryData(repoNodeId);
288
+
289
+ // Optimistic: remove repo + children
290
+ removeRepository(repoNodeId);
291
+ for (const childId of childFeatureIds) {
292
+ removeFeature(childId);
293
+ }
514
294
 
515
295
  try {
516
296
  const result = await deleteRepository(repositoryId);
517
-
518
297
  if (!result.success) {
519
298
  toast.error(result.error ?? 'Failed to remove repository');
520
- setNodes(prevNodes);
521
- setEdges(prevEdges);
299
+ if (prevRepoData) addRepositoryToMap(repoNodeId, prevRepoData);
300
+ for (const childId of childFeatureIds) {
301
+ const childNode = nodes.find((n) => n.id === childId);
302
+ if (childNode) {
303
+ restoreFeature(childId, {
304
+ nodeId: childId,
305
+ data: childNode.data as FeatureNodeData,
306
+ });
307
+ }
308
+ }
522
309
  return;
523
310
  }
524
-
525
311
  deleteSound.play();
526
312
  toast.success('Repository removed');
527
313
  } catch {
528
314
  toast.error('Failed to remove repository');
529
- setNodes(prevNodes);
530
- setEdges(prevEdges);
315
+ if (prevRepoData) addRepositoryToMap(repoNodeId, prevRepoData);
316
+ for (const childId of childFeatureIds) {
317
+ const childNode = nodes.find((n) => n.id === childId);
318
+ if (childNode) {
319
+ restoreFeature(childId, {
320
+ nodeId: childId,
321
+ data: childNode.data as FeatureNodeData,
322
+ });
323
+ }
324
+ }
531
325
  }
532
326
  },
533
- [nodes, deleteSound, setEdges]
327
+ [
328
+ edges,
329
+ nodes,
330
+ deleteSound,
331
+ removeRepository,
332
+ removeFeature,
333
+ addRepositoryToMap,
334
+ restoreFeature,
335
+ getRepositoryData,
336
+ ]
534
337
  );
535
338
 
536
339
  const handleLayout = useCallback(
537
340
  (direction: LayoutDirection) => {
538
- setNodes((currentNodes) => {
539
- const result = layoutWithDagre(currentNodes, edgesRef.current, {
540
- ...CANVAS_LAYOUT_DEFAULTS,
541
- direction,
542
- });
543
- setEdges(result.edges);
544
- return result.nodes;
545
- });
341
+ // Layout is applied via reconcile on next server prop update.
342
+ // For immediate re-layout, we apply dagre and trigger a reconcile-like update.
343
+ const result = layoutWithDagre(nodes, edges, { ...CANVAS_LAYOUT_DEFAULTS, direction });
344
+ reconcile(result.nodes, result.edges);
546
345
  },
547
- [setEdges]
346
+ [nodes, edges, reconcile]
548
347
  );
549
348
 
550
349
  const handleAddRepository = useCallback(
@@ -556,81 +355,30 @@ export function useControlCenterState(
556
355
  .split(/[\\/]/)
557
356
  .pop() ?? path;
558
357
 
559
- // Optimistic UI: add node and re-layout immediately
560
- setNodes((currentNodes) => {
561
- const newNode = {
562
- id: tempId,
563
- type: 'repositoryNode' as const,
564
- position: { x: 0, y: 0 },
565
- data: { name: repoName, repositoryPath: path, id: tempId },
566
- } as CanvasNodeType;
567
-
568
- const allNodes = [...currentNodes, newNode];
569
- const layoutResult = layoutWithDagre(allNodes, edgesRef.current, CANVAS_LAYOUT_DEFAULTS);
570
- setEdges(layoutResult.edges);
571
- return layoutResult.nodes;
572
- });
573
-
574
- // Persist via server action
358
+ addRepositoryToMap(tempId, { name: repoName, repositoryPath: path, id: tempId });
359
+
575
360
  addRepository({ path, name: repoName })
576
361
  .then((result) => {
577
362
  if (result.error) {
578
- // Rollback optimistic node and re-layout
579
- setNodes((prev) => {
580
- const remaining = prev.filter((n) => n.id !== tempId);
581
- const layoutResult = layoutWithDagre(
582
- remaining,
583
- edgesRef.current,
584
- CANVAS_LAYOUT_DEFAULTS
585
- );
586
- setEdges(layoutResult.edges);
587
- return layoutResult.nodes;
588
- });
363
+ removeRepository(tempId);
589
364
  toast.error(result.error);
590
365
  return;
591
366
  }
592
-
593
- // Replace temp ID with real repository ID
594
367
  const repo = result.repository!;
595
368
  const realId = `repo-${repo.id}`;
596
- setNodes((prev) =>
597
- prev.map((n) =>
598
- n.id === tempId
599
- ? ({
600
- ...n,
601
- id: realId,
602
- data: { ...n.data, id: repo.id, repositoryPath: repo.path },
603
- } as CanvasNodeType)
604
- : n
605
- )
606
- );
607
- setEdges((prev) =>
608
- prev.map((e) => ({
609
- ...e,
610
- source: e.source === tempId ? realId : e.source,
611
- target: e.target === tempId ? realId : e.target,
612
- id: e.id.replace(tempId, realId),
613
- }))
614
- );
615
-
369
+ replaceRepository(tempId, realId, {
370
+ name: repo.name,
371
+ repositoryPath: repo.path,
372
+ id: repo.id,
373
+ });
616
374
  createSound.play();
617
375
  })
618
376
  .catch(() => {
619
- // Rollback optimistic node and re-layout
620
- setNodes((prev) => {
621
- const remaining = prev.filter((n) => n.id !== tempId);
622
- const layoutResult = layoutWithDagre(
623
- remaining,
624
- edgesRef.current,
625
- CANVAS_LAYOUT_DEFAULTS
626
- );
627
- setEdges(layoutResult.edges);
628
- return layoutResult.nodes;
629
- });
377
+ removeRepository(tempId);
630
378
  toast.error('Failed to add repository');
631
379
  });
632
380
  },
633
- [createSound, setEdges]
381
+ [addRepositoryToMap, removeRepository, replaceRepository, createSound]
634
382
  );
635
383
 
636
384
  return {
@@ -643,5 +391,8 @@ export function useControlCenterState(
643
391
  handleDeleteFeature,
644
392
  handleDeleteRepository,
645
393
  createFeatureNode,
394
+ getFeatureRepositoryPath,
395
+ getRepositoryData,
396
+ setCallbacks,
646
397
  };
647
398
  }