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

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 (337) 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 -2
  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 -2
  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 +25 -7
  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 +143 -348
  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 +42 -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 +257 -0
  43. package/dist/src/presentation/web/hooks/use-notifications.d.ts +1 -5
  44. package/dist/src/presentation/web/hooks/use-notifications.d.ts.map +1 -1
  45. package/dist/src/presentation/web/hooks/use-notifications.js +1 -26
  46. package/dist/src/presentation/web/hooks/use-notifications.stories.d.ts.map +1 -1
  47. package/dist/src/presentation/web/hooks/use-notifications.stories.js +28 -38
  48. package/dist/src/presentation/web/lib/derive-graph.d.ts +52 -0
  49. package/dist/src/presentation/web/lib/derive-graph.d.ts.map +1 -0
  50. package/dist/src/presentation/web/lib/derive-graph.js +126 -0
  51. package/dist/src/presentation/web/lib/layout-with-dagre.d.ts.map +1 -1
  52. package/dist/src/presentation/web/lib/layout-with-dagre.js +0 -1
  53. package/dist/tsconfig.build.tsbuildinfo +1 -1
  54. package/package.json +1 -1
  55. package/web/.next/BUILD_ID +1 -1
  56. package/web/.next/build-manifest.json +2 -2
  57. package/web/.next/cache/.previewinfo +1 -1
  58. package/web/.next/cache/.rscinfo +1 -1
  59. package/web/.next/cache/.tsbuildinfo +1 -1
  60. package/web/.next/cache/config.json +3 -3
  61. package/web/.next/fallback-build-manifest.json +2 -2
  62. package/web/.next/prerender-manifest.json +3 -3
  63. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +34 -34
  64. package/web/.next/server/app/(dashboard)/@drawer/create/page.js +3 -3
  65. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  66. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  67. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +40 -40
  68. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js +3 -3
  69. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  70. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  71. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +30 -30
  72. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js +3 -3
  73. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  74. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  75. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +34 -34
  76. package/web/.next/server/app/(dashboard)/create/page.js +3 -3
  77. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  78. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  79. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +40 -40
  80. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js +3 -3
  81. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  82. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  83. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +30 -30
  84. package/web/.next/server/app/(dashboard)/page.js +3 -3
  85. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  86. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  87. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +30 -30
  88. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js +3 -3
  89. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  90. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  91. package/web/.next/server/app/_global-error.html +2 -2
  92. package/web/.next/server/app/_global-error.rsc +1 -1
  93. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  94. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  95. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  96. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  97. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  98. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  99. package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  100. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  101. package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  102. package/web/.next/server/app/skills/page.js.nft.json +1 -1
  103. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  104. package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  105. package/web/.next/server/app/tools/page.js.nft.json +1 -1
  106. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  107. package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  108. package/web/.next/server/app/version/page.js.nft.json +1 -1
  109. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  110. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +2 -2
  111. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js.map +1 -1
  112. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  113. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  114. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js +3 -0
  115. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js.map +1 -0
  116. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js +3 -0
  117. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js.map +1 -0
  118. package/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js +2 -2
  119. package/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js.map +1 -1
  120. package/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js +2 -2
  121. package/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js.map +1 -1
  122. package/web/.next/server/chunks/ssr/{[root-of-the-server]__cbae9863._.js → [root-of-the-server]__6258349c._.js} +2 -2
  123. package/web/.next/server/chunks/ssr/[root-of-the-server]__6258349c._.js.map +1 -0
  124. package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
  125. package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js.map +1 -1
  126. package/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js +2 -2
  127. package/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js.map +1 -1
  128. package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
  129. package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js.map +1 -1
  130. 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
  131. package/web/.next/server/chunks/ssr/[root-of-the-server]__94a6d275._.js.map +1 -0
  132. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
  133. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
  134. package/web/.next/server/chunks/ssr/_0e9d6bf7._.js +1 -1
  135. package/web/.next/server/chunks/ssr/_0e9d6bf7._.js.map +1 -1
  136. package/web/.next/server/chunks/ssr/_250a63ae._.js +3 -0
  137. package/web/.next/server/chunks/ssr/_250a63ae._.js.map +1 -0
  138. package/web/.next/server/chunks/ssr/_61351ed3._.js +1 -1
  139. package/web/.next/server/chunks/ssr/_61351ed3._.js.map +1 -1
  140. package/web/.next/server/chunks/ssr/_8400c8bb._.js +1 -1
  141. package/web/.next/server/chunks/ssr/_8400c8bb._.js.map +1 -1
  142. package/web/.next/server/chunks/ssr/_9f940437._.js +1 -1
  143. package/web/.next/server/chunks/ssr/_9f940437._.js.map +1 -1
  144. package/web/.next/server/chunks/ssr/_9fd79ac5._.js +6 -0
  145. package/web/.next/server/chunks/ssr/_9fd79ac5._.js.map +1 -0
  146. 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
  147. package/web/.next/server/chunks/ssr/{src_presentation_web_7188a290._.js.map → src_presentation_web_2b91e4ec._.js.map} +1 -1
  148. package/web/.next/server/chunks/ssr/{src_presentation_web_5ea689db._.js → src_presentation_web_5bcc1537._.js} +2 -2
  149. package/web/.next/server/chunks/ssr/{src_presentation_web_5ea689db._.js.map → src_presentation_web_5bcc1537._.js.map} +1 -1
  150. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_5955c012.js +3 -0
  151. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_5955c012.js.map +1 -0
  152. package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js +3 -0
  153. package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js.map +1 -0
  154. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  155. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  156. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  157. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  158. package/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js +8 -0
  159. package/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js.map +1 -0
  160. package/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js +8 -0
  161. package/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js.map +1 -0
  162. package/web/.next/server/pages/500.html +2 -2
  163. package/web/.next/server/server-reference-manifest.js +1 -1
  164. package/web/.next/server/server-reference-manifest.json +142 -142
  165. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  166. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  167. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  168. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +34 -34
  169. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page.js +3 -3
  170. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  171. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  172. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +40 -40
  173. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js +3 -3
  174. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  175. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  176. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +30 -30
  177. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js +3 -3
  178. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  179. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  180. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +34 -34
  181. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page.js +3 -3
  182. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  183. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  184. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +40 -40
  185. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page.js +3 -3
  186. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  187. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  188. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +30 -30
  189. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page.js +3 -3
  190. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  191. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  192. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +30 -30
  193. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js +3 -3
  194. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  195. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  196. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  197. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  198. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  199. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  200. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  201. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  202. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  203. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  204. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  205. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  206. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  207. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page.js.nft.json +1 -1
  208. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  209. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  210. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page.js.nft.json +1 -1
  211. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  212. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  213. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js.nft.json +1 -1
  214. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  215. package/web/.next/standalone/src/presentation/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +2 -2
  216. 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
  217. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js +3 -0
  218. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js +3 -0
  219. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js +2 -2
  220. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js +2 -2
  221. 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
  222. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
  223. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js +2 -2
  224. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
  225. 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
  226. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
  227. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_0e9d6bf7._.js +1 -1
  228. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_250a63ae._.js +3 -0
  229. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_61351ed3._.js +1 -1
  230. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_8400c8bb._.js +1 -1
  231. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_9f940437._.js +1 -1
  232. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_9fd79ac5._.js +6 -0
  233. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{src_presentation_web_7188a290._.js → src_presentation_web_2b91e4ec._.js} +2 -2
  234. 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
  235. 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
  236. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js +3 -0
  237. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  238. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  239. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js +8 -0
  240. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js +8 -0
  241. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  242. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  243. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +142 -142
  244. package/web/.next/standalone/src/presentation/web/CLAUDE.md +77 -33
  245. package/web/.next/standalone/src/presentation/web/app/actions/create-feature.ts +27 -2
  246. package/web/.next/standalone/src/presentation/web/app/actions/get-feature-metadata.ts +18 -0
  247. package/web/.next/standalone/src/presentation/web/app/api/agent-events/route.ts +17 -0
  248. package/web/.next/standalone/src/presentation/web/components/common/add-repository-button/index.ts +1 -0
  249. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/create-drawer-client.tsx +15 -19
  250. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.tsx +44 -18
  251. package/web/.next/standalone/src/presentation/web/components/common/index.ts +1 -6
  252. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-empty-state.tsx +1 -1
  253. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +36 -29
  254. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center.stories.tsx +2 -41
  255. package/web/.next/standalone/src/presentation/web/components/features/control-center/use-control-center-state.ts +175 -428
  256. package/web/.next/standalone/src/presentation/web/components/features/features-canvas/features-canvas.stories.tsx +0 -48
  257. package/web/.next/standalone/src/presentation/web/components/features/features-canvas/features-canvas.tsx +13 -59
  258. package/web/.next/standalone/src/presentation/web/components/layouts/app-shell/app-shell.tsx +1 -1
  259. package/web/.next/standalone/src/presentation/web/hooks/use-graph-state.ts +376 -0
  260. package/web/.next/standalone/src/presentation/web/hooks/use-notifications.stories.tsx +2 -30
  261. package/web/.next/standalone/src/presentation/web/hooks/use-notifications.ts +2 -36
  262. package/web/.next/standalone/src/presentation/web/lib/derive-graph.ts +178 -0
  263. package/web/.next/standalone/src/presentation/web/lib/layout-with-dagre.ts +0 -1
  264. package/web/.next/static/chunks/{99381b9c479936d5.js → 0b409f42a82665ec.js} +1 -1
  265. package/web/.next/static/chunks/0e035a073754dde7.js +1 -0
  266. package/web/.next/static/chunks/109d6898972d7b78.js +1 -0
  267. package/web/.next/static/chunks/{1249c7c8745b972e.js → 5238c7e71fc81e4d.js} +1 -1
  268. package/web/.next/static/chunks/6b4899963367ea96.js +4 -0
  269. package/web/.next/static/chunks/71c9ecdceac1939f.css +2 -0
  270. package/web/.next/static/chunks/752846e217931a38.js +1 -0
  271. package/web/.next/static/chunks/9f9e6f5083600784.js +1 -0
  272. package/web/.next/static/chunks/ae00da647a8354b9.js +1 -0
  273. package/web/.next/static/chunks/cb41849bcfaa304e.js +1 -0
  274. package/web/.next/static/chunks/dc23a5773f5cd610.js +1 -0
  275. package/web/.next/trace +1 -1
  276. package/web/.next/trace-build +1 -1
  277. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-button.d.ts.map +0 -1
  278. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node-config.d.ts +0 -7
  279. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node-config.d.ts.map +0 -1
  280. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node-config.js +0 -1
  281. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.d.ts +0 -6
  282. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.d.ts.map +0 -1
  283. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.js +0 -23
  284. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.d.ts +0 -10
  285. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.d.ts.map +0 -1
  286. package/dist/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.js +0 -33
  287. package/dist/src/presentation/web/components/common/add-repository-node/index.d.ts +0 -4
  288. package/dist/src/presentation/web/components/common/add-repository-node/index.d.ts.map +0 -1
  289. package/dist/src/presentation/web/components/common/add-repository-node/pick-folder.d.ts.map +0 -1
  290. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_3a02d8a5.js +0 -3
  291. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_3a02d8a5.js.map +0 -1
  292. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_a3044852.js +0 -3
  293. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_a3044852.js.map +0 -1
  294. package/web/.next/server/chunks/ssr/[root-of-the-server]__cbae9863._.js.map +0 -1
  295. package/web/.next/server/chunks/ssr/[root-of-the-server]__e5e09f72._.js.map +0 -1
  296. package/web/.next/server/chunks/ssr/_6e08ce21._.js +0 -3
  297. package/web/.next/server/chunks/ssr/_6e08ce21._.js.map +0 -1
  298. package/web/.next/server/chunks/ssr/_6ef06ca9._.js +0 -6
  299. package/web/.next/server/chunks/ssr/_6ef06ca9._.js.map +0 -1
  300. package/web/.next/server/chunks/ssr/src_presentation_web_0a49d9ee._.js +0 -8
  301. package/web/.next/server/chunks/ssr/src_presentation_web_0a49d9ee._.js.map +0 -1
  302. package/web/.next/server/chunks/ssr/src_presentation_web_79b0305a._.js +0 -8
  303. package/web/.next/server/chunks/ssr/src_presentation_web_79b0305a._.js.map +0 -1
  304. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_cc16bd01.js +0 -3
  305. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_cc16bd01.js.map +0 -1
  306. package/web/.next/server/chunks/ssr/src_presentation_web_app_(dashboard)_get-graph-data_ts_b018483e._.js +0 -3
  307. package/web/.next/server/chunks/ssr/src_presentation_web_app_(dashboard)_get-graph-data_ts_b018483e._.js.map +0 -1
  308. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_3a02d8a5.js +0 -3
  309. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_a3044852.js +0 -3
  310. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_6e08ce21._.js +0 -3
  311. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_6ef06ca9._.js +0 -6
  312. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_0a49d9ee._.js +0 -8
  313. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_79b0305a._.js +0 -8
  314. 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
  315. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_app_(dashboard)_get-graph-data_ts_b018483e._.js +0 -3
  316. package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/add-repository-node-config.ts +0 -8
  317. package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/add-repository-node.stories.tsx +0 -70
  318. package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/add-repository-node.tsx +0 -49
  319. package/web/.next/standalone/src/presentation/web/components/common/add-repository-node/index.ts +0 -3
  320. package/web/.next/static/chunks/0eeb65b8300fb7cd.js +0 -1
  321. package/web/.next/static/chunks/1c15ab8668ecf473.js +0 -1
  322. package/web/.next/static/chunks/2cf89d17ee7717a6.js +0 -4
  323. package/web/.next/static/chunks/4a8df922364596f6.js +0 -1
  324. package/web/.next/static/chunks/981b8df9ad045742.js +0 -1
  325. package/web/.next/static/chunks/9be617fc61b893f2.js +0 -1
  326. package/web/.next/static/chunks/a676f15c03fa66b9.js +0 -1
  327. package/web/.next/static/chunks/acce8a15d6896dea.css +0 -2
  328. package/web/.next/static/chunks/b579fd590a79bf35.js +0 -1
  329. /package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/add-repository-button.d.ts +0 -0
  330. /package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/add-repository-button.js +0 -0
  331. /package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/pick-folder.d.ts +0 -0
  332. /package/dist/src/presentation/web/components/common/{add-repository-node → add-repository-button}/pick-folder.js +0 -0
  333. /package/web/.next/standalone/src/presentation/web/components/common/{add-repository-node → add-repository-button}/add-repository-button.tsx +0 -0
  334. /package/web/.next/standalone/src/presentation/web/components/common/{add-repository-node → add-repository-button}/pick-folder.ts +0 -0
  335. /package/web/.next/static/{nnR12fAsrSagAfeV6xj6D → JYBKfZ0ROsNn817Id2TJ7}/_buildManifest.js +0 -0
  336. /package/web/.next/static/{nnR12fAsrSagAfeV6xj6D → JYBKfZ0ROsNn817Id2TJ7}/_clientMiddlewareManifest.json +0 -0
  337. /package/web/.next/static/{nnR12fAsrSagAfeV6xj6D → JYBKfZ0ROsNn817Id2TJ7}/_ssgManifest.js +0 -0
@@ -37,6 +37,66 @@ pnpm dev:cli ui
37
37
  | WebSocket upgrades | Forwarded (HMR works) | NOT forwarded in `WebServerService` |
38
38
  | Next.js mode | Always dev | dev when run from source, prod when installed |
39
39
 
40
+ ## Graph State Architecture (Domain-Model-Driven)
41
+
42
+ All canvas node/edge state is managed through **domain Maps as the single source of truth**.
43
+
44
+ ```
45
+ Server (layout.tsx) Client (useGraphState)
46
+ ┌──────────────────┐ ┌─────────────────────────────┐
47
+ │ initialNodes[] │──parseMaps──→ │ Domain Maps (source of truth)│
48
+ │ initialEdges[] │ │ featureMap: Map<id, Entry> │
49
+ └──────────────────┘ │ repoMap: Map<id, Entry> │
50
+ │ pendingMap: Map<id, Entry> │
51
+ SSE Events │ │
52
+ ┌──────────────────┐ └──────────┬──────────────────┘
53
+ │ NotificationEvent│──updateFeature──→ │
54
+ └──────────────────┘ │ deriveGraph() + layoutWithDagre()
55
+ ▼
56
+ Optimistic UI ┌─────────────────────────────┐
57
+ ┌──────────────────┐ │ Derived (read-only) │
58
+ │ shep:feature- │──addPending───→ │ nodes: CanvasNodeType[] │
59
+ │ created event │ │ edges: Edge[] │
60
+ └──────────────────┘ │ (callbacks + layout applied)│
61
+ └─────────────────────────────┘
62
+ ```
63
+
64
+ ### Key Files
65
+
66
+ | File | Purpose |
67
+ | ---------------------------------------------------------------- | -------------------------------------------------------------------- |
68
+ | `lib/derive-graph.ts` | Pure function: domain Maps → nodes + edges (with callbacks injected) |
69
+ | `hooks/use-graph-state.ts` | Hook: domain Maps state + reconciliation + derivation + layout |
70
+ | `components/features/control-center/use-control-center-state.ts` | Wraps useGraphState, handles server actions + SSE events |
71
+ | `components/features/control-center/control-center-inner.tsx` | Wires callbacks via `setCallbacks()`, passes nodes/edges to canvas |
72
+ | `components/features/features-canvas/features-canvas.tsx` | Renders React Flow; only adds `selectedFeatureId` highlighting |
73
+
74
+ ### Design Principles
75
+
76
+ 1. **Domain Maps are the source of truth** — all mutations go through Maps (featureMap, repoMap, pendingMap)
77
+ 2. **Edges are derived, never stored** — repo→feature edges from `repositoryPath` matching, dep edges from `parentNodeId`
78
+ 3. **Callbacks via ref** — `callbacksRef` + stable `useMemo([])` wrapper prevents circular re-renders
79
+ 4. **Layout in derivation** — `layoutWithDagre` runs inside `useMemo` after `deriveGraph()`
80
+ 5. **`onNodesChange` is a no-op** — since `nodesDraggable=false`, we ignore all React Flow changes
81
+
82
+ ### Mutation Paths
83
+
84
+ | Operation | Flow |
85
+ | ----------------- | ------------------------------------------------------------------------------------------------------- |
86
+ | SSE event | `useAgentEventsContext` → `updateFeature(nodeId, {state, lifecycle})` |
87
+ | Server prop sync | `useEffect` watches `initialNodeKey` → `reconcile(newNodes, newEdges)` |
88
+ | Optimistic create | `shep:feature-created` event → `createFeatureNode()` → `addPendingFeature()` |
89
+ | Delete feature | `handleDeleteFeature()` → `removeFeature()` → server action → `restoreFeature()` on error |
90
+ | Add repository | `handleAddRepository()` → `addRepository(tempId)` → server action → `replaceRepository(tempId, realId)` |
91
+
92
+ ### Reconciliation
93
+
94
+ `reconcile(newNodes, newEdges)` merges server data into domain Maps:
95
+
96
+ - Replaces featureMap and repoMap with fresh server data
97
+ - Preserves pending (creating) features in featureMap unless matched by `name + repositoryPath`
98
+ - Cleans pendingMap entries that now have a real counterpart in server data
99
+
40
100
  ## SSE / Real-Time Update Architecture
41
101
 
42
102
  ```
@@ -45,8 +105,8 @@ DB polling (500ms) -> /api/agent-events (SSE route)
45
105
  -> postMessage to all browser tabs
46
106
  -> useAgentEvents hook (hooks/use-agent-events.ts)
47
107
  -> AgentEventsProvider context (hooks/agent-events-provider.tsx)
48
- -> useControlCenterState (optimistic node updates via setNodes)
49
- -> React state updates -> sidebar + React Flow canvas
108
+ -> useControlCenterState (SSE effect calls updateFeature)
109
+ -> domain Maps update -> derivation -> React Flow canvas
50
110
  ```
51
111
 
52
112
  ### SSE Key Files
@@ -58,18 +118,9 @@ DB polling (500ms) -> /api/agent-events (SSE route)
58
118
  | `public/agent-events-sw.js` | Service Worker: multiplexes single SSE to all tabs |
59
119
  | `hooks/use-agent-events.ts` | Client hook: registers with SW, receives events |
60
120
  | `hooks/agent-events-provider.tsx` | React context wrapping `useAgentEvents` |
61
- | `components/features/control-center/use-control-center-state.ts` | Processes SSE events into React Flow node updates |
121
+ | `components/features/control-center/use-control-center-state.ts` | SSE effect calls `updateFeature()` on domain Maps |
62
122
  | `components/common/feature-node/derive-feature-state.ts` | Maps event types/phases to UI state/lifecycle |
63
123
 
64
- ### SSE Event Flow Details
65
-
66
- 1. **SSE route** seeds a per-connection cache on first poll (no events emitted for initial state)
67
- 2. Subsequent polls compare DB state against cache, emit only deltas
68
- 3. **Service Worker** connects to `/api/agent-events` via EventSource on first subscriber
69
- 4. SW broadcasts `{ type: 'notification', data }` to all tabs via `clients.matchAll()`
70
- 5. **useControlCenterState** effect processes new events, calls `setNodes()` to update node state/lifecycle
71
- 6. **Polling fallback** (5s interval) fetches via `fetchGraphData()` server action for active features
72
-
73
124
  ### SSE Lifecycle Mappings
74
125
 
75
126
  The SSE route maps `SdlcLifecycle` enum -> agent graph node names -> client `FeatureLifecyclePhase`:
@@ -94,21 +145,17 @@ React Flow v12 (`@xyflow/react` ^12.10.0) has specific behaviors that affect rea
94
145
  - **Internal Zustand store**: React Flow syncs props to an internal `nodeLookup` Map via `StoreUpdater`
95
146
  - **Memoized NodeWrapper**: Each node is wrapped in `memo(NodeWrapper)` which subscribes to store via `useStore(selector, shallow)`
96
147
  - **NodeWrapper gets node from store**, NOT from props directly. It does `s.nodeLookup.get(id)` with `shallow` comparison
97
- - **Single ReactFlowProvider**: There must be exactly ONE `ReactFlowProvider` ancestor. Multiple nested providers create separate stores, causing the outer one's `StoreUpdater` to sync nodes to a store that the inner components don't read from
148
+ - **Single ReactFlowProvider**: There must be exactly ONE `ReactFlowProvider` ancestor. Multiple nested providers create separate stores
98
149
 
99
- ### Circular Update Loop (FIXED — do not reintroduce)
150
+ ### Circular Update Loop Prevention
100
151
 
101
- In controlled mode, `enrichedNodes` (useMemo in FeaturesCanvas) creates new node objects every render
102
- (adding callbacks to `data`). StoreUpdater sees different references and emits `replace` changes via
103
- `onNodesChange`. If `onNodesChange` applies these 'replace' changes back to state via `applyNodeChanges`,
104
- it overwrites SSE-driven state updates with stale data from the previous render, AND triggers another
105
- `enrichedNodes` recalculation → infinite loop (110+ renders on page load).
152
+ With the domain-model-driven architecture, the circular update loop is prevented by design:
106
153
 
107
- **Fix**: `onNodesChange` in `use-control-center-state.ts` filters out `replace` changes. Only `dimensions`,
108
- `position`, `select`, etc. are applied. StoreUpdater still syncs props to its internal store regardless —
109
- our state remains the source of truth.
154
+ 1. **Callbacks are stable** — injected via `callbacksRef` + stable `useMemo([])` wrapper, so node objects don't change when callbacks change
155
+ 2. **`onNodesChange` is a no-op** — domain Maps are the source of truth, React Flow's internal changes are ignored
156
+ 3. **`enrichedNodes` only adds `selectedFeatureId`** — no callback injection in FeaturesCanvas
110
157
 
111
- **NEVER** remove the `c.type !== 'replace'` filter from `onNodesChange`.
158
+ **NEVER** make `onNodesChange` apply `replace` changes back to state.
112
159
 
113
160
  ## Testing Real-Time Updates
114
161
 
@@ -124,24 +171,21 @@ our state remains the source of truth.
124
171
 
125
172
  When a user creates a feature, the UI uses optimistic updates:
126
173
 
127
- 1. **Create drawer** dispatches `shep:feature-created` → canvas adds a temp node (`feature-<ts>` ID, state `creating`)
174
+ 1. **Create drawer** dispatches `shep:feature-created` → `createFeatureNode()` → `addPendingFeature()` adds temp node to pendingMap
128
175
  2. Drawer closes immediately via `router.push('/')`
129
176
  3. Server action `createFeature` runs in background → agent starts
130
- 4. SSE events arrive with real feature ID (`feat-<uuid>`) — **these won't match the temp node**
131
- 5. **Polling fallback** (5s) detects new server node → replaces temp `creating` node in-place
177
+ 4. SSE events arrive with real feature ID (`feat-<uuid>`) — these update featureMap via `updateFeature()`
178
+ 5. **Server prop sync** (rerender with new initialNodes) → `reconcile()` detects matching real feature → removes pending from both featureMap and pendingMap
132
179
 
133
180
  ### Key constraints
134
181
 
135
- - **Temp node IDs don't match SSE events**: Optimistic nodes use `feature-<ts>` IDs, server uses `feat-<uuid>`.
136
- SSE-driven `setNodes()` can't find the temp node. The polling fallback handles the swap.
137
- - **Create drawer `isSubmitting`**: Must NOT reset until pathname leaves `/create`. Resetting in `.finally()`
138
- causes the drawer to flash open because `router.push('/')` is async and pathname hasn't changed yet.
139
- - **Parallel routes preserve state**: The `CreateDrawerClient` component is NOT unmounted on navigation.
140
- `isOpen` is derived from pathname, not mount/unmount lifecycle.
182
+ - **Temp node IDs don't match SSE events**: Optimistic nodes use `feature-<ts>` IDs, server uses `feat-<uuid>`. SSE events call `updateFeature('feat-<uuid>', ...)` which doesn't match the temp ID. The reconcile on next server prop update handles the swap.
183
+ - **Create drawer `isSubmitting`**: Must NOT reset until pathname leaves `/create`. Resetting in `.finally()` causes the drawer to flash open because `router.push('/')` is async and pathname hasn't changed yet.
184
+ - **Parallel routes preserve state**: The `CreateDrawerClient` component is NOT unmounted on navigation. `isOpen` is derived from pathname, not mount/unmount lifecycle.
141
185
 
142
186
  ## Common Pitfalls
143
187
 
144
188
  - **No events when nothing changes**: SSE only emits deltas. If no feature state changes, no events are sent. This is correct behavior.
145
- - **Polling fallback dependency**: The 5s polling `useEffect` depends on a derived `hasActiveFeature` boolean (not `nodes` directly). Using `[nodes]` as a dependency would reset the interval on every node change, potentially preventing it from ever firing.
146
189
  - **Storybook mocks**: New server actions need mocks in `.storybook/mocks/app/actions/` or Storybook build breaks.
147
190
  - **`output: 'standalone'`** in next.config.ts: Production builds use standalone mode. Route handlers are bundled differently.
191
+ - **Map insertion order affects dagre layout**: After remove+restore of a feature, the Map insertion order may change, causing slightly different dagre positions. This is expected behavior — don't write tests asserting exact position preservation across remove/restore cycles.
@@ -72,15 +72,40 @@ export async function createFeature(
72
72
 
73
73
  try {
74
74
  const createFeatureUseCase = resolve<CreateFeatureUseCase>('CreateFeatureUseCase');
75
- const result = await createFeatureUseCase.execute({
75
+
76
+ // Phase 1 (fast): create DB record with real UUID — returns immediately
77
+ const { feature, shouldSpawn } = await createFeatureUseCase.createRecord({
76
78
  userInput,
77
79
  repositoryPath,
78
80
  approvalGates: gates,
79
81
  push: push ?? false,
80
82
  openPr: openPr ?? false,
81
83
  ...(parentId ? { parentId } : {}),
84
+ name,
85
+ description,
82
86
  });
83
- return { feature: result.feature };
87
+
88
+ // Phase 2 (background): metadata generation, worktree, spec, agent spawn
89
+ // Fire-and-forget — the UI gets the real feature ID immediately
90
+ createFeatureUseCase
91
+ .initializeAndSpawn(
92
+ feature,
93
+ {
94
+ userInput,
95
+ repositoryPath,
96
+ approvalGates: gates,
97
+ push: push ?? false,
98
+ openPr: openPr ?? false,
99
+ ...(parentId ? { parentId } : {}),
100
+ },
101
+ shouldSpawn
102
+ )
103
+ .catch((err: unknown) => {
104
+ // eslint-disable-next-line no-console
105
+ console.error('[createFeature] initializeAndSpawn failed:', err);
106
+ });
107
+
108
+ return { feature };
84
109
  } catch (error: unknown) {
85
110
  const message = error instanceof Error ? error.message : 'Failed to create feature';
86
111
  return { error: message };
@@ -0,0 +1,18 @@
1
+ 'use server';
2
+
3
+ import { resolve } from '@/lib/server-container';
4
+ import type { IFeatureRepository } from '@shepai/core/application/ports/output/repositories/feature-repository.interface';
5
+
6
+ /**
7
+ * Lightweight server action to fetch a single feature's name and description.
8
+ * Used by the SSE effect to update node metadata after AI metadata generation
9
+ * without a full graph reconcile (which can overwrite SSE-driven state).
10
+ */
11
+ export async function getFeatureMetadata(
12
+ featureId: string
13
+ ): Promise<{ name: string; description: string } | null> {
14
+ const featureRepo = resolve<IFeatureRepository>('IFeatureRepository');
15
+ const feature = await featureRepo.findById(featureId);
16
+ if (!feature) return null;
17
+ return { name: feature.name, description: feature.description };
18
+ }
@@ -191,6 +191,23 @@ export function GET(request: Request): Response {
191
191
  }
192
192
  }
193
193
 
194
+ // Check for feature name change (AI metadata generation updates name)
195
+ if (prev.featureName !== feature.name) {
196
+ prev.featureName = feature.name;
197
+ const nodeName =
198
+ LIFECYCLE_TO_NODE[feature.lifecycle as SdlcLifecycle] ?? 'requirements';
199
+ emitEvent({
200
+ eventType: NotificationEventType.PhaseCompleted,
201
+ agentRunId: run.id,
202
+ featureId: feature.id,
203
+ featureName: feature.name,
204
+ phaseName: nodeName,
205
+ message: `Feature metadata updated`,
206
+ severity: NotificationSeverity.Info,
207
+ timestamp: new Date().toISOString(),
208
+ });
209
+ }
210
+
194
211
  // Check for lifecycle change (agent stays "running" but moves through phases)
195
212
  if (prev.lifecycle !== feature.lifecycle) {
196
213
  prev.lifecycle = feature.lifecycle;
@@ -0,0 +1 @@
1
+ export { AddRepositoryButton } from './add-repository-button';
@@ -8,7 +8,6 @@ import { FeatureCreateDrawer } from '@/components/common/feature-create-drawer';
8
8
  import type { FeatureCreatePayload } from '@/components/common/feature-create-drawer';
9
9
  import type { ParentFeatureOption } from '@/components/common/feature-create-drawer/feature-create-drawer';
10
10
  import type { WorkflowDefaults } from '@/app/actions/get-workflow-defaults';
11
- import { useSoundAction } from '@/hooks/use-sound-action';
12
11
 
13
12
  export interface CreateDrawerClientProps {
14
13
  repositoryPath: string;
@@ -24,7 +23,6 @@ export function CreateDrawerClient({
24
23
  workflowDefaults,
25
24
  }: CreateDrawerClientProps) {
26
25
  const router = useRouter();
27
- const createSound = useSoundAction('create');
28
26
  const [isSubmitting, setIsSubmitting] = useState(false);
29
27
 
30
28
  // Derive open state from the URL. Next.js parallel routes preserve slot
@@ -54,40 +52,38 @@ export function CreateDrawerClient({
54
52
  (data: FeatureCreatePayload) => {
55
53
  setIsSubmitting(true);
56
54
 
57
- // Dispatch event for optimistic canvas update before navigating
58
- window.dispatchEvent(
59
- new CustomEvent('shep:feature-created', {
60
- detail: {
61
- name: data.name,
62
- description: data.description,
63
- repositoryPath: data.repositoryPath,
64
- parentId: data.parentId,
65
- },
66
- })
67
- );
68
-
69
55
  // Close the drawer immediately for responsive UI
70
56
  router.push('/');
71
57
 
72
- // Fire server action in the background
58
+ // Server action Phase 1 returns fast with real feature ID (DB record created)
59
+ // Phase 2 (metadata, worktree, agent) runs in background on server
73
60
  createFeature(data)
74
61
  .then((result) => {
75
62
  if (result.error) {
76
63
  toast.error(result.error);
77
- // Dispatch rollback event
78
64
  window.dispatchEvent(new CustomEvent('shep:feature-create-failed'));
79
65
  return;
80
66
  }
81
- createSound.play();
67
+ // Dispatch event with real feature ID so control center adds it to featureMap
68
+ window.dispatchEvent(
69
+ new CustomEvent('shep:feature-created', {
70
+ detail: {
71
+ featureId: result.feature!.id,
72
+ name: result.feature!.name,
73
+ description: result.feature!.description,
74
+ repositoryPath: result.feature!.repositoryPath,
75
+ parentId: data.parentId,
76
+ },
77
+ })
78
+ );
82
79
  })
83
80
  .catch(() => {
84
81
  toast.error('Failed to create feature');
85
82
  window.dispatchEvent(new CustomEvent('shep:feature-create-failed'));
86
- // Reset on error so the user can retry without navigating away
87
83
  setIsSubmitting(false);
88
84
  });
89
85
  },
90
- [router, createSound]
86
+ [router]
91
87
  );
92
88
 
93
89
  return (
@@ -8,7 +8,7 @@ import type {
8
8
  PrdApprovalPayload,
9
9
  QuestionSelectionChange,
10
10
  } from '@shepai/core/domain/generated/output';
11
- import { PrStatus } from '@shepai/core/domain/generated/output';
11
+ import { PrStatus, NotificationEventType } from '@shepai/core/domain/generated/output';
12
12
  import { approveFeature } from '@/app/actions/approve-feature';
13
13
  import { rejectFeature } from '@/app/actions/reject-feature';
14
14
  import { getFeatureArtifact } from '@/app/actions/get-feature-artifact';
@@ -94,9 +94,22 @@ export function FeatureDrawerClient({ view: initialView }: FeatureDrawerClientPr
94
94
  const newEvents = events.slice(processedCountRef.current);
95
95
  processedCountRef.current = events.length;
96
96
 
97
+ // PhaseCompleted must NOT set state when WaitingApproval is in the same poll batch.
98
+ // But when it arrives without WaitingApproval (e.g. post-approval), allow it as
99
+ // a fallback for missed AgentStarted events.
100
+ const waitingApprovalInBatch = newEvents.some(
101
+ (e) =>
102
+ e.featureId === featureNode.featureId &&
103
+ e.eventType === NotificationEventType.WaitingApproval
104
+ );
105
+
97
106
  for (const event of newEvents) {
98
107
  if (event.featureId !== featureNode.featureId) continue;
99
- const newState = mapEventTypeToState(event.eventType);
108
+
109
+ const newState =
110
+ event.eventType === NotificationEventType.PhaseCompleted && waitingApprovalInBatch
111
+ ? undefined
112
+ : mapEventTypeToState(event.eventType);
100
113
  const newLifecycle = mapPhaseNameToLifecycle(event.phaseName);
101
114
 
102
115
  // Trigger a server refresh to get the latest drawer view,
@@ -104,24 +117,26 @@ export function FeatureDrawerClient({ view: initialView }: FeatureDrawerClientPr
104
117
  // to avoid the refresh interfering with the reject handler's navigation.
105
118
  if (!isDirtyRef.current && !isRejectingRef.current) router.refresh();
106
119
 
107
- // Optimistically update the node data for immediate UI feedback
108
- setView((prev) => {
109
- if (
110
- prev.type !== 'feature' &&
111
- prev.type !== 'prd-review' &&
112
- prev.type !== 'tech-review' &&
113
- prev.type !== 'merge-review'
114
- )
115
- return prev;
116
- return {
117
- ...prev,
118
- node: {
120
+ if (newState !== undefined || newLifecycle !== undefined) {
121
+ // Optimistically update the node data AND re-derive the view type so the
122
+ // drawer switches immediately (e.g. prd-review → feature when agent resumes)
123
+ // without waiting for the router.refresh() round-trip.
124
+ setView((prev) => {
125
+ if (
126
+ prev.type !== 'feature' &&
127
+ prev.type !== 'prd-review' &&
128
+ prev.type !== 'tech-review' &&
129
+ prev.type !== 'merge-review'
130
+ )
131
+ return prev;
132
+ const updatedNode = {
119
133
  ...prev.node,
120
- state: newState,
134
+ ...(newState !== undefined && { state: newState }),
121
135
  ...(newLifecycle !== undefined && { lifecycle: newLifecycle }),
122
- },
123
- };
124
- });
136
+ };
137
+ return { ...prev, type: deriveViewType(updatedNode), node: updatedNode };
138
+ });
139
+ }
125
140
  }
126
141
  }, [events, featureNode, router]);
127
142
 
@@ -765,3 +780,14 @@ function DetailRow({ label, value }: { label: string; value: string }) {
765
780
  </div>
766
781
  );
767
782
  }
783
+
784
+ /** Mirrors computeDrawerView logic — derives view type from live node data. */
785
+ function deriveViewType(
786
+ node: FeatureNodeData
787
+ ): 'feature' | 'prd-review' | 'tech-review' | 'merge-review' {
788
+ if (node.lifecycle === 'requirements' && node.state === 'action-required') return 'prd-review';
789
+ if (node.lifecycle === 'implementation' && node.state === 'action-required') return 'tech-review';
790
+ if (node.lifecycle === 'review' && (node.state === 'action-required' || node.state === 'error'))
791
+ return 'merge-review';
792
+ return 'feature';
793
+ }
@@ -4,11 +4,7 @@ export {
4
4
  computeDrawerView,
5
5
  type DrawerView,
6
6
  } from './control-center-drawer';
7
- export {
8
- AddRepositoryNode,
9
- type AddRepositoryNodeData,
10
- type AddRepositoryNodeType,
11
- } from './add-repository-node';
7
+ export { AddRepositoryButton } from './add-repository-button';
12
8
  export { ElapsedTime, formatElapsed } from './elapsed-time';
13
9
  export { EmptyState, type EmptyStateProps } from './empty-state';
14
10
  export {
@@ -35,7 +31,6 @@ export {
35
31
  export { FeatureStatusBadges, type FeatureStatusBadgesProps } from './feature-status-badges';
36
32
  export { FeatureStatusGroup, type FeatureStatusGroupProps } from './feature-status-group';
37
33
  export { LoadingSkeleton } from './loading-skeleton';
38
- export { NotificationPermissionBanner } from './notification-permission-banner';
39
34
  export {
40
35
  RepositoryNode,
41
36
  type RepositoryNodeData,
@@ -3,7 +3,7 @@
3
3
  import { useState, useCallback } from 'react';
4
4
  import { Github, Plus, TerminalSquare, Copy, Check, Loader2 } from 'lucide-react';
5
5
  import { cn } from '@/lib/utils';
6
- import { pickFolder } from '@/components/common/add-repository-node/pick-folder';
6
+ import { pickFolder } from '@/components/common/add-repository-button/pick-folder';
7
7
 
8
8
  export interface ControlCenterEmptyStateProps {
9
9
  onRepositorySelect?: (path: string) => void;
@@ -7,7 +7,6 @@ import { FeaturesCanvas } from '@/components/features/features-canvas';
7
7
  import type { CanvasNodeType } from '@/components/features/features-canvas';
8
8
  import type { FeatureNodeData } from '@/components/common/feature-node';
9
9
  import type { RepositoryNodeData } from '@/components/common/repository-node';
10
- import { NotificationPermissionBanner } from '@/components/common/notification-permission-banner';
11
10
  import {
12
11
  useSidebarFeaturesContext,
13
12
  mapNodeStateToSidebarStatus,
@@ -39,6 +38,7 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
39
38
  handleDeleteFeature,
40
39
  handleDeleteRepository,
41
40
  createFeatureNode,
41
+ setCallbacks,
42
42
  } = useControlCenterState(initialNodes, initialEdges);
43
43
 
44
44
  // Publish sidebar features to context whenever feature node data changes
@@ -163,11 +163,12 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
163
163
  return () => window.removeEventListener('shep:add-repository', handler);
164
164
  }, [handleAddRepository]);
165
165
 
166
- // Listen for optimistic create events from the create drawer
166
+ // Listen for create events from the create drawer (with real feature ID from server)
167
167
  useEffect(() => {
168
168
  const handler = (e: Event) => {
169
169
  const detail = (
170
170
  e as CustomEvent<{
171
+ featureId: string;
171
172
  name: string;
172
173
  description?: string;
173
174
  repositoryPath: string;
@@ -183,6 +184,7 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
183
184
  parentNodeId,
184
185
  {
185
186
  state: 'creating',
187
+ featureId: detail.featureId,
186
188
  name: detail.name,
187
189
  description: detail.description,
188
190
  repositoryPath: detail.repositoryPath,
@@ -200,7 +202,8 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
200
202
  );
201
203
 
202
204
  createFeatureNode(repoNode?.id ?? null, {
203
- state: 'creating',
205
+ state: 'running',
206
+ featureId: detail.featureId,
204
207
  name: detail.name,
205
208
  description: detail.description,
206
209
  repositoryPath: detail.repositoryPath,
@@ -210,37 +213,41 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
210
213
  return () => window.removeEventListener('shep:feature-created', handler);
211
214
  }, [nodes, createFeatureNode]);
212
215
 
216
+ // Wire callbacks into derived node data (via ref — no re-render).
217
+ useEffect(() => {
218
+ setCallbacks({
219
+ onNodeAction: handleAddFeatureToFeature,
220
+ onFeatureDelete: handleDeleteFeature,
221
+ onRepositoryAdd: handleAddFeatureToRepo,
222
+ onRepositoryClick: handleRepositoryClick,
223
+ onRepositoryDelete: handleDeleteRepository,
224
+ });
225
+ }, [
226
+ setCallbacks,
227
+ handleAddFeatureToFeature,
228
+ handleDeleteFeature,
229
+ handleAddFeatureToRepo,
230
+ handleRepositoryClick,
231
+ handleDeleteRepository,
232
+ ]);
233
+
213
234
  const hasRepositories = nodes.some((n) => n.type === 'repositoryNode');
214
235
 
215
236
  if (!hasRepositories) {
216
- return (
217
- <>
218
- <NotificationPermissionBanner />
219
- <ControlCenterEmptyState onRepositorySelect={handleAddRepository} />
220
- </>
221
- );
237
+ return <ControlCenterEmptyState onRepositorySelect={handleAddRepository} />;
222
238
  }
223
239
 
224
240
  return (
225
- <>
226
- <NotificationPermissionBanner />
227
- <FeaturesCanvas
228
- nodes={nodes}
229
- edges={edges}
230
- selectedFeatureId={selectedFeatureId}
231
- onNodesChange={onNodesChange}
232
- onConnect={handleConnect}
233
- onAddFeature={handleAddFeature}
234
- onNodeAction={handleAddFeatureToFeature}
235
- onNodeClick={handleNodeClick}
236
- onPaneClick={handleClearDrawers}
237
- onRepositoryAdd={handleAddFeatureToRepo}
238
- onRepositoryClick={handleRepositoryClick}
239
- onRepositoryDelete={handleDeleteRepository}
240
- onFeatureDelete={handleDeleteFeature}
241
- onRepositorySelect={handleAddRepository}
242
- emptyState={<ControlCenterEmptyState onRepositorySelect={handleAddRepository} />}
243
- />
244
- </>
241
+ <FeaturesCanvas
242
+ nodes={nodes}
243
+ edges={edges}
244
+ selectedFeatureId={selectedFeatureId}
245
+ onNodesChange={onNodesChange}
246
+ onConnect={handleConnect}
247
+ onAddFeature={handleAddFeature}
248
+ onNodeClick={handleNodeClick}
249
+ onPaneClick={handleClearDrawers}
250
+ emptyState={<ControlCenterEmptyState onRepositorySelect={handleAddRepository} />}
251
+ />
245
252
  );
246
253
  }
@@ -4,7 +4,6 @@ import { ControlCenter } from './control-center';
4
4
  import type { CanvasNodeType } from '@/components/features/features-canvas';
5
5
  import type { FeatureNodeType } from '@/components/common/feature-node';
6
6
  import type { RepositoryNodeType } from '@/components/common/repository-node';
7
- import type { AddRepositoryNodeType } from '@/components/common/add-repository-node';
8
7
  import { layoutWithDagre } from '@/lib/layout-with-dagre';
9
8
  import { AgentEventsProvider } from '@/hooks/agent-events-provider';
10
9
  import { DrawerCloseGuardProvider } from '@/hooks/drawer-close-guard';
@@ -102,13 +101,6 @@ const repoNode: RepositoryNodeType = {
102
101
  data: { name: 'shep-ai/cli' },
103
102
  };
104
103
 
105
- const addRepoNode: AddRepositoryNodeType = {
106
- id: 'add-repo',
107
- type: 'addRepositoryNode',
108
- position: { x: 50, y: 302 },
109
- data: {},
110
- };
111
-
112
104
  const dashedEdge = { style: { strokeDasharray: '5 5' } };
113
105
 
114
106
  export const Empty: Story = {
@@ -137,7 +129,7 @@ export const WithToolbar: Story = {
137
129
 
138
130
  export const WithNodeActions: Story = {
139
131
  args: {
140
- initialNodes: [repoNode, addRepoNode, ...featureNodes] as CanvasNodeType[],
132
+ initialNodes: [repoNode, ...featureNodes] as CanvasNodeType[],
141
133
  initialEdges: [
142
134
  { id: 'e-repo-f1', source: 'repo-1', target: 'feat-1', ...dashedEdge },
143
135
  { id: 'e-repo-f2', source: 'repo-1', target: 'feat-2', ...dashedEdge },
@@ -146,30 +138,6 @@ export const WithNodeActions: Story = {
146
138
  },
147
139
  };
148
140
 
149
- /**
150
- * Interactive story — starts with just an "Add Repository" button.
151
- *
152
- * User flow:
153
- * 1. Click "Add Repository" → native folder picker → select a folder → repo node appears
154
- * 2. Hover the repo node → click (+) → a new feature node appears connected to the repo
155
- * 3. Hover the feature node → click (+) → another feature appears connected to the first
156
- *
157
- * You can also use the toolbar "Add Feature" button to create unconnected features.
158
- */
159
- export const Interactive: Story = {
160
- args: {
161
- initialNodes: [
162
- {
163
- id: 'add-repo',
164
- type: 'addRepositoryNode',
165
- position: { x: 50, y: 50 },
166
- data: {},
167
- } as AddRepositoryNodeType,
168
- ] as CanvasNodeType[],
169
- initialEdges: [],
170
- },
171
- };
172
-
173
141
  // --- Dagre-layouted story: multiple repos with feature trees ---
174
142
 
175
143
  const dagreRepoNodes: RepositoryNodeType[] = [
@@ -266,14 +234,7 @@ const dagreFeatureNodes: FeatureNodeType[] = [
266
234
  },
267
235
  ];
268
236
 
269
- const dagreAddRepo: AddRepositoryNodeType = {
270
- id: 'add-repo',
271
- type: 'addRepositoryNode',
272
- position: { x: 0, y: 0 },
273
- data: {},
274
- };
275
-
276
- const dagreNodesRaw: CanvasNodeType[] = [...dagreRepoNodes, ...dagreFeatureNodes, dagreAddRepo];
237
+ const dagreNodesRaw: CanvasNodeType[] = [...dagreRepoNodes, ...dagreFeatureNodes];
277
238
 
278
239
  const dagreEdges: Edge[] = [
279
240
  // cli repo → Auth, SSO