@shepai/cli 1.72.9-pr220.e1bfec0 → 1.72.9-pr220.f17b60a

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 (278) hide show
  1. package/dist/packages/core/src/infrastructure/services/notifications/notification.service.d.ts.map +1 -1
  2. package/dist/packages/core/src/infrastructure/services/notifications/notification.service.js +2 -4
  3. package/dist/src/presentation/web/app/build-feature-node-data.d.ts.map +1 -1
  4. package/dist/src/presentation/web/app/build-feature-node-data.js +2 -26
  5. package/dist/src/presentation/web/app/build-graph-nodes.d.ts.map +1 -1
  6. package/dist/src/presentation/web/app/build-graph-nodes.js +2 -26
  7. package/dist/src/presentation/web/components/common/control-center-drawer/create-drawer-client.d.ts.map +1 -1
  8. package/dist/src/presentation/web/components/common/control-center-drawer/create-drawer-client.js +0 -2
  9. package/dist/src/presentation/web/components/common/control-center-drawer/drawer-view.d.ts +2 -0
  10. package/dist/src/presentation/web/components/common/control-center-drawer/drawer-view.d.ts.map +1 -1
  11. package/dist/src/presentation/web/components/common/control-center-drawer/drawer-view.js +11 -11
  12. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.d.ts.map +1 -1
  13. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.js +17 -27
  14. package/dist/src/presentation/web/components/common/control-center-drawer/index.d.ts +0 -1
  15. package/dist/src/presentation/web/components/common/control-center-drawer/index.d.ts.map +1 -1
  16. package/dist/src/presentation/web/components/common/control-center-drawer/index.js +0 -1
  17. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts +25 -0
  18. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts.map +1 -1
  19. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.js +45 -2
  20. package/dist/src/presentation/web/components/common/index.d.ts +1 -2
  21. package/dist/src/presentation/web/components/common/index.d.ts.map +1 -1
  22. package/dist/src/presentation/web/components/common/index.js +1 -2
  23. package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
  24. package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +3 -4
  25. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
  26. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +46 -58
  27. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.d.ts.map +1 -1
  28. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.js +2 -13
  29. package/dist/src/presentation/web/components/ui/alert-dialog.d.ts.map +1 -1
  30. package/dist/src/presentation/web/components/ui/alert-dialog.js +2 -2
  31. package/dist/src/presentation/web/components/ui/dialog.d.ts.map +1 -1
  32. package/dist/src/presentation/web/components/ui/dialog.js +2 -2
  33. package/dist/src/presentation/web/hooks/use-agent-events.d.ts.map +1 -1
  34. package/dist/src/presentation/web/hooks/use-agent-events.js +6 -3
  35. package/dist/src/presentation/web/hooks/use-graph-state.d.ts +2 -1
  36. package/dist/src/presentation/web/hooks/use-graph-state.d.ts.map +1 -1
  37. package/dist/src/presentation/web/hooks/use-graph-state.js +50 -23
  38. package/dist/src/presentation/web/hooks/use-notifications.d.ts +1 -5
  39. package/dist/src/presentation/web/hooks/use-notifications.d.ts.map +1 -1
  40. package/dist/src/presentation/web/hooks/use-notifications.js +1 -26
  41. package/dist/src/presentation/web/hooks/use-notifications.stories.d.ts.map +1 -1
  42. package/dist/src/presentation/web/hooks/use-notifications.stories.js +28 -38
  43. package/dist/tsconfig.build.tsbuildinfo +1 -1
  44. package/package.json +1 -1
  45. package/web/.next/BUILD_ID +1 -1
  46. package/web/.next/build-manifest.json +2 -2
  47. package/web/.next/cache/.previewinfo +1 -1
  48. package/web/.next/cache/.rscinfo +1 -1
  49. package/web/.next/cache/.tsbuildinfo +1 -1
  50. package/web/.next/cache/config.json +3 -3
  51. package/web/.next/fallback-build-manifest.json +2 -2
  52. package/web/.next/prerender-manifest.json +3 -3
  53. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +15 -15
  54. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  55. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  56. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +18 -18
  57. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  58. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  59. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +13 -13
  60. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  61. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  62. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +15 -15
  63. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  64. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  65. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +18 -18
  66. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  67. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  68. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +13 -13
  69. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  70. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  71. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +13 -13
  72. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  73. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  74. package/web/.next/server/app/_global-error.html +2 -2
  75. package/web/.next/server/app/_global-error.rsc +1 -1
  76. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  77. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  78. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  79. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  80. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  81. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  82. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  83. package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  84. package/web/.next/server/app/skills/page.js.nft.json +1 -1
  85. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  86. package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  87. package/web/.next/server/app/tools/page.js.nft.json +1 -1
  88. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  89. package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  90. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  91. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  92. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  93. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js +1 -1
  94. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js.map +1 -1
  95. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js +1 -1
  96. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js.map +1 -1
  97. package/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js +2 -2
  98. package/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js.map +1 -1
  99. package/web/.next/server/chunks/ssr/[root-of-the-server]__2018e25f._.js +1 -1
  100. package/web/.next/server/chunks/ssr/[root-of-the-server]__2018e25f._.js.map +1 -1
  101. package/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js +2 -2
  102. package/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js.map +1 -1
  103. package/web/.next/server/chunks/ssr/[root-of-the-server]__6258349c._.js +1 -1
  104. package/web/.next/server/chunks/ssr/[root-of-the-server]__6258349c._.js.map +1 -1
  105. package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
  106. package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js.map +1 -1
  107. package/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js +2 -2
  108. package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
  109. package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js.map +1 -1
  110. package/web/.next/server/chunks/ssr/[root-of-the-server]__94a6d275._.js +1 -1
  111. package/web/.next/server/chunks/ssr/[root-of-the-server]__94a6d275._.js.map +1 -1
  112. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
  113. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
  114. package/web/.next/server/chunks/ssr/_07c2a32a._.js +1 -1
  115. package/web/.next/server/chunks/ssr/_07c2a32a._.js.map +1 -1
  116. package/web/.next/server/chunks/ssr/_0e9d6bf7._.js +1 -1
  117. package/web/.next/server/chunks/ssr/_0e9d6bf7._.js.map +1 -1
  118. package/web/.next/server/chunks/ssr/_250a63ae._.js +1 -1
  119. package/web/.next/server/chunks/ssr/_250a63ae._.js.map +1 -1
  120. package/web/.next/server/chunks/ssr/_61351ed3._.js +1 -1
  121. package/web/.next/server/chunks/ssr/_61351ed3._.js.map +1 -1
  122. package/web/.next/server/chunks/ssr/_8400c8bb._.js +1 -1
  123. package/web/.next/server/chunks/ssr/_8400c8bb._.js.map +1 -1
  124. package/web/.next/server/chunks/ssr/{_63688261._.js → _8e87c87b._.js} +2 -2
  125. package/web/.next/server/chunks/ssr/{_63688261._.js.map → _8e87c87b._.js.map} +1 -1
  126. package/web/.next/server/chunks/ssr/_9f940437._.js +1 -1
  127. package/web/.next/server/chunks/ssr/_9f940437._.js.map +1 -1
  128. package/web/.next/server/chunks/ssr/src_presentation_web_2b91e4ec._.js +1 -1
  129. package/web/.next/server/chunks/ssr/src_presentation_web_2b91e4ec._.js.map +1 -1
  130. package/web/.next/server/chunks/ssr/src_presentation_web_5bcc1537._.js +1 -1
  131. package/web/.next/server/chunks/ssr/src_presentation_web_5bcc1537._.js.map +1 -1
  132. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_5955c012.js +1 -1
  133. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_5955c012.js.map +1 -1
  134. package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js +1 -1
  135. package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js.map +1 -1
  136. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  137. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  138. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  139. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  140. package/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js +2 -2
  141. package/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js.map +1 -1
  142. package/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js +2 -2
  143. package/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js.map +1 -1
  144. package/web/.next/server/pages/500.html +2 -2
  145. package/web/.next/server/server-reference-manifest.js +1 -1
  146. package/web/.next/server/server-reference-manifest.json +21 -21
  147. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  148. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  149. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  150. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +15 -15
  151. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  152. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  153. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +18 -18
  154. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  155. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  156. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +13 -13
  157. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  158. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  159. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +15 -15
  160. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  161. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  162. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +18 -18
  163. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  164. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  165. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +13 -13
  166. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  167. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  168. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +13 -13
  169. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  170. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  171. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  172. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  173. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  174. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  175. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  176. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  177. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  178. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  179. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  180. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  181. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page.js.nft.json +1 -1
  182. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  183. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  184. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page.js.nft.json +1 -1
  185. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  186. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  187. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  188. 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
  189. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js +1 -1
  190. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js +1 -1
  191. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js +2 -2
  192. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__2018e25f._.js +1 -1
  193. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js +2 -2
  194. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6258349c._.js +1 -1
  195. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
  196. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js +2 -2
  197. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
  198. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__94a6d275._.js +1 -1
  199. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
  200. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_07c2a32a._.js +1 -1
  201. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_0e9d6bf7._.js +1 -1
  202. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_250a63ae._.js +1 -1
  203. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_61351ed3._.js +1 -1
  204. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_8400c8bb._.js +1 -1
  205. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{_63688261._.js → _8e87c87b._.js} +2 -2
  206. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_9f940437._.js +1 -1
  207. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_2b91e4ec._.js +1 -1
  208. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_5bcc1537._.js +1 -1
  209. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_5955c012.js +1 -1
  210. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js +1 -1
  211. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  212. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  213. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js +2 -2
  214. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js +2 -2
  215. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  216. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  217. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +21 -21
  218. package/web/.next/standalone/src/presentation/web/app/build-feature-node-data.ts +3 -30
  219. package/web/.next/standalone/src/presentation/web/app/build-graph-nodes.ts +3 -30
  220. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/create-drawer-client.tsx +0 -2
  221. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/drawer-view.ts +12 -13
  222. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.tsx +21 -37
  223. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/index.ts +0 -1
  224. package/web/.next/standalone/src/presentation/web/components/common/feature-node/derive-feature-state.ts +63 -2
  225. package/web/.next/standalone/src/presentation/web/components/common/index.ts +1 -7
  226. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +12 -21
  227. package/web/.next/standalone/src/presentation/web/components/features/control-center/use-control-center-state.ts +65 -70
  228. package/web/.next/standalone/src/presentation/web/components/features/features-canvas/features-canvas.tsx +1 -18
  229. package/web/.next/standalone/src/presentation/web/components/ui/alert-dialog.tsx +2 -5
  230. package/web/.next/standalone/src/presentation/web/components/ui/dialog.tsx +2 -5
  231. package/web/.next/standalone/src/presentation/web/hooks/use-agent-events.ts +7 -3
  232. package/web/.next/standalone/src/presentation/web/hooks/use-graph-state.ts +104 -71
  233. package/web/.next/standalone/src/presentation/web/hooks/use-notifications.stories.tsx +2 -30
  234. package/web/.next/standalone/src/presentation/web/hooks/use-notifications.ts +2 -36
  235. package/web/.next/static/chunks/0ffccd7be73305c8.css +2 -0
  236. package/web/.next/static/chunks/3f205fd6adbfd20a.js +7 -0
  237. package/web/.next/static/chunks/76827713cb2b7333.js +1 -0
  238. package/web/.next/static/chunks/{c575ab63c138645b.js → 792aac476adb5875.js} +1 -1
  239. package/web/.next/static/chunks/7c8adcfd920a8f74.js +1 -0
  240. package/web/.next/static/chunks/{cdc92cdf81005c84.js → 83463b63971f5723.js} +1 -1
  241. package/web/.next/static/chunks/{63a80e9d4c4b0253.js → 98ca891a5c156aa5.js} +3 -3
  242. package/web/.next/static/chunks/{638824249d103905.js → 9b1770f05f1d95e5.js} +1 -1
  243. package/web/.next/static/chunks/a7f52757dff0cea0.js +1 -0
  244. package/web/.next/static/chunks/b6becf0de992842a.js +1 -0
  245. package/web/.next/static/chunks/{f32c98a709949c5b.js → cb41849bcfaa304e.js} +1 -1
  246. package/web/.next/static/chunks/d5e95c4482029a39.js +1 -0
  247. package/web/.next/trace +1 -1
  248. package/web/.next/trace-build +1 -1
  249. package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.d.ts +0 -12
  250. package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.d.ts.map +0 -1
  251. package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.js +0 -337
  252. package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.stories.d.ts +0 -33
  253. package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.stories.d.ts.map +0 -1
  254. package/dist/src/presentation/web/components/common/control-center-drawer/control-center-drawer.stories.js +0 -214
  255. package/dist/src/presentation/web/components/common/notification-permission-banner/index.d.ts +0 -2
  256. package/dist/src/presentation/web/components/common/notification-permission-banner/index.d.ts.map +0 -1
  257. package/dist/src/presentation/web/components/common/notification-permission-banner/index.js +0 -1
  258. package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.d.ts +0 -2
  259. package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.d.ts.map +0 -1
  260. package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.js +0 -33
  261. package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.stories.d.ts +0 -6
  262. package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.stories.d.ts.map +0 -1
  263. package/dist/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.stories.js +0 -24
  264. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/control-center-drawer.stories.tsx +0 -293
  265. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/control-center-drawer.tsx +0 -745
  266. package/web/.next/standalone/src/presentation/web/components/common/notification-permission-banner/index.ts +0 -1
  267. package/web/.next/standalone/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.stories.tsx +0 -52
  268. package/web/.next/standalone/src/presentation/web/components/common/notification-permission-banner/notification-permission-banner.tsx +0 -55
  269. package/web/.next/static/chunks/0a883e2efecfd563.js +0 -1
  270. package/web/.next/static/chunks/0c54314c8c9c9739.js +0 -7
  271. package/web/.next/static/chunks/596ca28d78baae25.js +0 -1
  272. package/web/.next/static/chunks/71c9ecdceac1939f.css +0 -2
  273. package/web/.next/static/chunks/a040b058d79f4ade.js +0 -1
  274. package/web/.next/static/chunks/e05eb1cc84ee254a.js +0 -1
  275. package/web/.next/static/chunks/ef8f8940eac6c542.js +0 -1
  276. /package/web/.next/static/{uMxPSDPwZCludF5408rWJ → eg0FTlsA4z_7CXbFfMgAr}/_buildManifest.js +0 -0
  277. /package/web/.next/static/{uMxPSDPwZCludF5408rWJ → eg0FTlsA4z_7CXbFfMgAr}/_clientMiddlewareManifest.json +0 -0
  278. /package/web/.next/static/{uMxPSDPwZCludF5408rWJ → eg0FTlsA4z_7CXbFfMgAr}/_ssgManifest.js +0 -0
@@ -2,47 +2,20 @@ import type { Feature, AgentRun } from '@shepai/core/domain/generated/output';
2
2
  import {
3
3
  deriveNodeState,
4
4
  deriveProgress,
5
+ deriveLifecycle,
5
6
  } from '@/components/common/feature-node/derive-feature-state';
6
- import type { FeatureNodeData, FeatureLifecyclePhase } from '@/components/common/feature-node';
7
-
8
- /** Map domain SdlcLifecycle enum values to UI FeatureLifecyclePhase (1:1). */
9
- const lifecycleMap: Record<string, FeatureLifecyclePhase> = {
10
- Requirements: 'requirements',
11
- Research: 'research',
12
- Implementation: 'implementation',
13
- Review: 'review',
14
- 'Deploy & QA': 'deploy',
15
- Maintain: 'maintain',
16
- };
17
-
18
- /** Map agent graph node names (from agent_run.result) to UI lifecycle phases. */
19
- const nodeToLifecyclePhase: Record<string, FeatureLifecyclePhase> = {
20
- analyze: 'requirements',
21
- requirements: 'requirements',
22
- research: 'research',
23
- plan: 'implementation',
24
- implement: 'implementation',
25
- merge: 'review',
26
- };
7
+ import type { FeatureNodeData } from '@/components/common/feature-node';
27
8
 
28
9
  /**
29
10
  * Builds a FeatureNodeData object from a Feature entity and optional AgentRun.
30
11
  * This is the single-feature version of the logic in `buildGraphNodes`.
31
12
  */
32
13
  export function buildFeatureNodeData(feature: Feature, run: AgentRun | null): FeatureNodeData {
33
- const agentNode = run?.result?.startsWith('node:') ? run.result.slice(5) : undefined;
34
- const lifecycle: FeatureLifecyclePhase =
35
- run?.status === 'completed'
36
- ? 'maintain'
37
- : ((agentNode ? nodeToLifecyclePhase[agentNode] : undefined) ??
38
- lifecycleMap[feature.lifecycle] ??
39
- 'requirements');
40
-
41
14
  return {
42
15
  name: feature.name,
43
16
  description: feature.description ?? feature.slug,
44
17
  featureId: feature.id,
45
- lifecycle,
18
+ lifecycle: deriveLifecycle(feature, run),
46
19
  repositoryPath: feature.repositoryPath,
47
20
  branch: feature.branch,
48
21
  specPath: feature.specPath,
@@ -2,30 +2,11 @@ import type { Feature, Repository, AgentRun } from '@shepai/core/domain/generate
2
2
  import {
3
3
  deriveNodeState,
4
4
  deriveProgress,
5
+ deriveLifecycle,
5
6
  } from '@/components/common/feature-node/derive-feature-state';
6
7
  import type { CanvasNodeType } from '@/components/features/features-canvas';
7
8
  import type { Edge } from '@xyflow/react';
8
- import type { FeatureNodeData, FeatureLifecyclePhase } from '@/components/common/feature-node';
9
-
10
- /** Map domain SdlcLifecycle enum values to UI FeatureLifecyclePhase (1:1). */
11
- const lifecycleMap: Record<string, FeatureLifecyclePhase> = {
12
- Requirements: 'requirements',
13
- Research: 'research',
14
- Implementation: 'implementation',
15
- Review: 'review',
16
- 'Deploy & QA': 'deploy',
17
- Maintain: 'maintain',
18
- };
19
-
20
- /** Map agent graph node names (from agent_run.result) to UI lifecycle phases. */
21
- const nodeToLifecyclePhase: Record<string, FeatureLifecyclePhase> = {
22
- analyze: 'requirements',
23
- requirements: 'requirements',
24
- research: 'research',
25
- plan: 'implementation',
26
- implement: 'implementation',
27
- merge: 'review',
28
- };
9
+ import type { FeatureNodeData } from '@/components/common/feature-node';
29
10
 
30
11
  export interface FeatureWithRun {
31
12
  feature: Feature;
@@ -131,14 +112,6 @@ function appendFeatureNodes(
131
112
  });
132
113
 
133
114
  sorted.forEach(({ feature, run }) => {
134
- const agentNode = run?.result?.startsWith('node:') ? run.result.slice(5) : undefined;
135
- const lifecycle: FeatureLifecyclePhase =
136
- run?.status === 'completed'
137
- ? 'maintain'
138
- : ((agentNode ? nodeToLifecyclePhase[agentNode] : undefined) ??
139
- lifecycleMap[feature.lifecycle] ??
140
- 'requirements');
141
-
142
115
  // Resolve blockedBy display name from parent feature
143
116
  let blockedBy: string | undefined;
144
117
  if (feature.parentId && feature.lifecycle === 'Blocked') {
@@ -152,7 +125,7 @@ function appendFeatureNodes(
152
125
  name: feature.name,
153
126
  description: feature.description ?? feature.slug,
154
127
  featureId: feature.id,
155
- lifecycle,
128
+ lifecycle: deriveLifecycle(feature, run),
156
129
  repositoryPath: feature.repositoryPath,
157
130
  branch: feature.branch,
158
131
  specPath: feature.specPath,
@@ -61,7 +61,6 @@ export function CreateDrawerClient({
61
61
  .then((result) => {
62
62
  if (result.error) {
63
63
  toast.error(result.error);
64
- window.dispatchEvent(new CustomEvent('shep:feature-create-failed'));
65
64
  return;
66
65
  }
67
66
  // Dispatch event with real feature ID so control center adds it to featureMap
@@ -79,7 +78,6 @@ export function CreateDrawerClient({
79
78
  })
80
79
  .catch(() => {
81
80
  toast.error('Failed to create feature');
82
- window.dispatchEvent(new CustomEvent('shep:feature-create-failed'));
83
81
  setIsSubmitting(false);
84
82
  });
85
83
  },
@@ -27,6 +27,17 @@ export type DrawerView =
27
27
  /** Repository node actions */
28
28
  | { type: 'repository'; data: RepositoryNodeData };
29
29
 
30
+ /** Derives the feature-specific view type from node lifecycle + state. */
31
+ export function deriveFeatureViewType(
32
+ node: FeatureNodeData
33
+ ): 'feature' | 'prd-review' | 'tech-review' | 'merge-review' {
34
+ if (node.lifecycle === 'requirements' && node.state === 'action-required') return 'prd-review';
35
+ if (node.lifecycle === 'implementation' && node.state === 'action-required') return 'tech-review';
36
+ if (node.lifecycle === 'review' && (node.state === 'action-required' || node.state === 'error'))
37
+ return 'merge-review';
38
+ return 'feature';
39
+ }
40
+
30
41
  /**
31
42
  * Derives the active DrawerView from control-center state.
32
43
  * Priority: feature-create > selected-node (prd/tech/merge/feature) > repository
@@ -59,19 +70,7 @@ export function computeDrawerView({
59
70
  }
60
71
 
61
72
  if (selectedNode) {
62
- if (selectedNode.lifecycle === 'requirements' && selectedNode.state === 'action-required') {
63
- return { type: 'prd-review', node: selectedNode };
64
- }
65
- if (selectedNode.lifecycle === 'implementation' && selectedNode.state === 'action-required') {
66
- return { type: 'tech-review', node: selectedNode };
67
- }
68
- if (
69
- selectedNode.lifecycle === 'review' &&
70
- (selectedNode.state === 'action-required' || selectedNode.state === 'error')
71
- ) {
72
- return { type: 'merge-review', node: selectedNode };
73
- }
74
- return { type: 'feature', node: selectedNode };
73
+ return { type: deriveFeatureViewType(selectedNode), node: selectedNode };
75
74
  }
76
75
 
77
76
  if (selectedRepoNode) {
@@ -8,7 +8,7 @@ import type {
8
8
  PrdApprovalPayload,
9
9
  QuestionSelectionChange,
10
10
  } from '@shepai/core/domain/generated/output';
11
- import { PrStatus, NotificationEventType } from '@shepai/core/domain/generated/output';
11
+ import { PrStatus } 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';
@@ -53,10 +53,8 @@ import { useFeatureActions } from '@/components/common/feature-drawer/use-featur
53
53
  import type { PrdQuestionnaireData } from '@/components/common/prd-questionnaire';
54
54
  import type { TechDecisionsReviewData } from '@/components/common/tech-decisions-review';
55
55
  import type { MergeReviewData } from '@/components/common/merge-review';
56
- import {
57
- mapEventTypeToState,
58
- mapPhaseNameToLifecycle,
59
- } from '@/components/common/feature-node/derive-feature-state';
56
+ import { resolveSseEventUpdates } from '@/components/common/feature-node/derive-feature-state';
57
+ import { deriveFeatureViewType } from './drawer-view';
60
58
  import type { DrawerView } from './drawer-view';
61
59
 
62
60
  export interface FeatureDrawerClientProps {
@@ -94,30 +92,15 @@ export function FeatureDrawerClient({ view: initialView }: FeatureDrawerClientPr
94
92
  const newEvents = events.slice(processedCountRef.current);
95
93
  processedCountRef.current = events.length;
96
94
 
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
-
106
- for (const event of newEvents) {
107
- if (event.featureId !== featureNode.featureId) continue;
108
-
109
- const newState =
110
- event.eventType === NotificationEventType.PhaseCompleted && waitingApprovalInBatch
111
- ? undefined
112
- : mapEventTypeToState(event.eventType);
113
- const newLifecycle = mapPhaseNameToLifecycle(event.phaseName);
95
+ for (const update of resolveSseEventUpdates(newEvents)) {
96
+ if (update.featureId !== featureNode.featureId) continue;
114
97
 
115
98
  // Trigger a server refresh to get the latest drawer view,
116
99
  // but skip when the user has unsaved changes or a rejection is in-flight
117
100
  // to avoid the refresh interfering with the reject handler's navigation.
118
101
  if (!isDirtyRef.current && !isRejectingRef.current) router.refresh();
119
102
 
120
- if (newState !== undefined || newLifecycle !== undefined) {
103
+ if (update.state !== undefined || update.lifecycle !== undefined) {
121
104
  // Optimistically update the node data AND re-derive the view type so the
122
105
  // drawer switches immediately (e.g. prd-review → feature when agent resumes)
123
106
  // without waiting for the router.refresh() round-trip.
@@ -131,10 +114,10 @@ export function FeatureDrawerClient({ view: initialView }: FeatureDrawerClientPr
131
114
  return prev;
132
115
  const updatedNode = {
133
116
  ...prev.node,
134
- ...(newState !== undefined && { state: newState }),
135
- ...(newLifecycle !== undefined && { lifecycle: newLifecycle }),
117
+ ...(update.state !== undefined && { state: update.state }),
118
+ ...(update.lifecycle !== undefined && { lifecycle: update.lifecycle }),
136
119
  };
137
- return { ...prev, type: deriveViewType(updatedNode), node: updatedNode };
120
+ return { ...prev, type: deriveFeatureViewType(updatedNode), node: updatedNode };
138
121
  });
139
122
  }
140
123
  }
@@ -383,6 +366,12 @@ export function FeatureDrawerClient({ view: initialView }: FeatureDrawerClientPr
383
366
  }
384
367
  setChatInput('');
385
368
  toast.success(`${label} approved — agent resuming`);
369
+ // Optimistically update canvas node before SSE arrives (~500ms delay)
370
+ window.dispatchEvent(
371
+ new CustomEvent('shep:feature-approved', {
372
+ detail: { featureId: reviewNode.featureId },
373
+ })
374
+ );
386
375
  onClose();
387
376
  },
388
377
  [reviewNode, onClose]
@@ -410,6 +399,12 @@ export function FeatureDrawerClient({ view: initialView }: FeatureDrawerClientPr
410
399
  }
411
400
  setChatInput('');
412
401
  toast.success('Requirements approved — agent resuming');
402
+ // Optimistically update canvas node before SSE arrives (~500ms delay)
403
+ window.dispatchEvent(
404
+ new CustomEvent('shep:feature-approved', {
405
+ detail: { featureId: view.node.featureId },
406
+ })
407
+ );
413
408
  setPrdSelections({});
414
409
  onClose();
415
410
  },
@@ -780,14 +775,3 @@ function DetailRow({ label, value }: { label: string; value: string }) {
780
775
  </div>
781
776
  );
782
777
  }
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
- }
@@ -1,2 +1 @@
1
- export { ControlCenterDrawer, type ControlCenterDrawerProps } from './control-center-drawer';
2
1
  export { computeDrawerView, type DrawerView } from './drawer-view';
@@ -41,7 +41,7 @@ export function deriveNodeState(feature: Feature, agentRun?: AgentRun | null): F
41
41
  case AgentRunStatus.cancelled:
42
42
  return 'blocked';
43
43
  case AgentRunStatus.completed:
44
- return feature.lifecycle === SdlcLifecycle.Maintain ? 'done' : 'done';
44
+ return 'done';
45
45
  case AgentRunStatus.running:
46
46
  case AgentRunStatus.pending:
47
47
  return 'running';
@@ -112,7 +112,17 @@ export function mapEventTypeToState(eventType: NotificationEventType): FeatureNo
112
112
  }
113
113
  }
114
114
 
115
- /** Maps an SSE event phaseName to a FeatureLifecyclePhase. Mirrors page.tsx nodeToLifecyclePhase. */
115
+ /** Map domain SdlcLifecycle enum values to UI FeatureLifecyclePhase. */
116
+ export const sdlcLifecycleMap: Record<string, FeatureLifecyclePhase> = {
117
+ Requirements: 'requirements',
118
+ Research: 'research',
119
+ Implementation: 'implementation',
120
+ Review: 'review',
121
+ 'Deploy & QA': 'deploy',
122
+ Maintain: 'maintain',
123
+ };
124
+
125
+ /** Map agent graph node names (from agent_run.result or SSE phaseName) to UI lifecycle phases. */
116
126
  const phaseNameToLifecycle: Record<string, FeatureLifecyclePhase> = {
117
127
  analyze: 'requirements',
118
128
  requirements: 'requirements',
@@ -124,6 +134,57 @@ const phaseNameToLifecycle: Record<string, FeatureLifecyclePhase> = {
124
134
  blocked: 'requirements',
125
135
  };
126
136
 
137
+ /**
138
+ * Derives the UI lifecycle phase from a Feature + AgentRun.
139
+ * Shared by build-feature-node-data.ts and build-graph-nodes.ts.
140
+ */
141
+ export function deriveLifecycle(feature: Feature, run: AgentRun | null): FeatureLifecyclePhase {
142
+ if (run?.status === 'completed') return 'maintain';
143
+ const agentNode = run?.result?.startsWith('node:') ? run.result.slice(5) : undefined;
144
+ return (
145
+ (agentNode ? phaseNameToLifecycle[agentNode] : undefined) ??
146
+ sdlcLifecycleMap[feature.lifecycle] ??
147
+ 'requirements'
148
+ );
149
+ }
150
+
151
+ export interface SseEventUpdate {
152
+ featureId: string;
153
+ state: FeatureNodeState | undefined;
154
+ lifecycle: FeatureLifecyclePhase | undefined;
155
+ eventType: NotificationEventType;
156
+ phaseName?: string;
157
+ }
158
+
159
+ /**
160
+ * Resolves state/lifecycle updates from a batch of SSE events, applying
161
+ * the WaitingApproval batch-suppression rule: when WaitingApproval and
162
+ * PhaseCompleted arrive in the same batch for a feature, PhaseCompleted
163
+ * is fully suppressed to prevent lifecycle regression.
164
+ */
165
+ export function resolveSseEventUpdates(
166
+ events: readonly { featureId: string; eventType: NotificationEventType; phaseName?: string }[]
167
+ ): SseEventUpdate[] {
168
+ const waitingApprovalFeatures = new Set(
169
+ events
170
+ .filter((e) => e.eventType === NotificationEventType.WaitingApproval)
171
+ .map((e) => e.featureId)
172
+ );
173
+
174
+ return events.map((event) => {
175
+ const isSuppressed =
176
+ event.eventType === NotificationEventType.PhaseCompleted &&
177
+ waitingApprovalFeatures.has(event.featureId);
178
+ return {
179
+ featureId: event.featureId,
180
+ state: isSuppressed ? undefined : mapEventTypeToState(event.eventType),
181
+ lifecycle: isSuppressed ? undefined : mapPhaseNameToLifecycle(event.phaseName),
182
+ eventType: event.eventType,
183
+ phaseName: event.phaseName,
184
+ };
185
+ });
186
+ }
187
+
127
188
  export function mapPhaseNameToLifecycle(
128
189
  phaseName: string | undefined
129
190
  ): FeatureLifecyclePhase | undefined {
@@ -1,9 +1,4 @@
1
- export {
2
- ControlCenterDrawer,
3
- type ControlCenterDrawerProps,
4
- computeDrawerView,
5
- type DrawerView,
6
- } from './control-center-drawer';
1
+ export { computeDrawerView, type DrawerView } from './control-center-drawer';
7
2
  export { AddRepositoryButton } from './add-repository-button';
8
3
  export { ElapsedTime, formatElapsed } from './elapsed-time';
9
4
  export { EmptyState, type EmptyStateProps } from './empty-state';
@@ -31,7 +26,6 @@ export {
31
26
  export { FeatureStatusBadges, type FeatureStatusBadgesProps } from './feature-status-badges';
32
27
  export { FeatureStatusGroup, type FeatureStatusGroupProps } from './feature-status-group';
33
28
  export { LoadingSkeleton } from './loading-skeleton';
34
- export { NotificationPermissionBanner } from './notification-permission-banner';
35
29
  export {
36
30
  RepositoryNode,
37
31
  type RepositoryNodeData,
@@ -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,
@@ -235,28 +234,20 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
235
234
  const hasRepositories = nodes.some((n) => n.type === 'repositoryNode');
236
235
 
237
236
  if (!hasRepositories) {
238
- return (
239
- <>
240
- <NotificationPermissionBanner />
241
- <ControlCenterEmptyState onRepositorySelect={handleAddRepository} />
242
- </>
243
- );
237
+ return <ControlCenterEmptyState onRepositorySelect={handleAddRepository} />;
244
238
  }
245
239
 
246
240
  return (
247
- <>
248
- <NotificationPermissionBanner />
249
- <FeaturesCanvas
250
- nodes={nodes}
251
- edges={edges}
252
- selectedFeatureId={selectedFeatureId}
253
- onNodesChange={onNodesChange}
254
- onConnect={handleConnect}
255
- onAddFeature={handleAddFeature}
256
- onNodeClick={handleNodeClick}
257
- onPaneClick={handleClearDrawers}
258
- emptyState={<ControlCenterEmptyState onRepositorySelect={handleAddRepository} />}
259
- />
260
- </>
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
+ />
261
252
  );
262
253
  }
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useCallback, useEffect, useRef } from 'react';
3
+ import { useCallback, useEffect, useMemo, useRef } from 'react';
4
4
  import { useRouter } from 'next/navigation';
5
5
  import { toast } from 'sonner';
6
6
  import type { Connection, Edge, NodeChange } from '@xyflow/react';
@@ -20,10 +20,10 @@ import { useAgentEventsContext } from '@/hooks/agent-events-provider';
20
20
  import { useSoundAction } from '@/hooks/use-sound-action';
21
21
  import {
22
22
  mapEventTypeToState,
23
- mapPhaseNameToLifecycle,
23
+ resolveSseEventUpdates,
24
24
  } from '@/components/common/feature-node/derive-feature-state';
25
- import { NotificationEventType } from '@shepai/core/domain/generated/output';
26
25
  import { useGraphState, type GraphCallbacks } from '@/hooks/use-graph-state';
26
+ import type { FeatureEntry } from '@/lib/derive-graph';
27
27
 
28
28
  export interface ControlCenterState {
29
29
  nodes: CanvasNodeType[];
@@ -47,6 +47,9 @@ export interface ControlCenterState {
47
47
  setCallbacks: (callbacks: GraphCallbacks) => void;
48
48
  }
49
49
 
50
+ /** Must match the message string emitted by the SSE route in agent-events/route.ts */
51
+ const METADATA_UPDATED_MESSAGE = 'Feature metadata updated';
52
+
50
53
  let nextFeatureId = 0;
51
54
 
52
55
  export function useControlCenterState(
@@ -75,18 +78,26 @@ export function useControlCenterState(
75
78
 
76
79
  // Sync server props into domain Maps when initialNodes/initialEdges change.
77
80
  // Keyed by a stable string so we don't re-reconcile on every render.
78
- const initialNodeKey = initialNodes
79
- .map((n) => n.id)
80
- .sort()
81
- .join(',');
82
- const initialDataKey = initialNodes
83
- .filter((n) => n.type === 'featureNode')
84
- .map((n) => {
85
- const d = n.data as FeatureNodeData;
86
- return `${n.id}:${d.state}:${d.lifecycle}`;
87
- })
88
- .sort()
89
- .join(',');
81
+ const initialNodeKey = useMemo(
82
+ () =>
83
+ initialNodes
84
+ .map((n) => n.id)
85
+ .sort()
86
+ .join(','),
87
+ [initialNodes]
88
+ );
89
+ const initialDataKey = useMemo(
90
+ () =>
91
+ initialNodes
92
+ .filter((n) => n.type === 'featureNode')
93
+ .map((n) => {
94
+ const d = n.data as FeatureNodeData;
95
+ return `${n.id}:${d.state}:${d.lifecycle}`;
96
+ })
97
+ .sort()
98
+ .join(','),
99
+ [initialNodes]
100
+ );
90
101
 
91
102
  const prevReconcileKey = useRef('');
92
103
 
@@ -101,15 +112,6 @@ export function useControlCenterState(
101
112
  // Track previous feature states for fallback notifications
102
113
  const prevFeatureStatesRef = useRef<Map<string, FeatureNodeData['state']>>(new Map());
103
114
 
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(',');
112
-
113
115
  const { events } = useAgentEventsContext();
114
116
 
115
117
  useEffect(() => {
@@ -140,7 +142,7 @@ export function useControlCenterState(
140
142
  }
141
143
  prevStates.set(node.id, data.state);
142
144
  }
143
- }, [initialFeatureDataKey, initialNodes, events, router]);
145
+ }, [initialDataKey, initialNodes, events, router]);
144
146
 
145
147
  // SSE effect: only state + lifecycle updates
146
148
  const processedEventCountRef = useRef(0);
@@ -150,40 +152,37 @@ export function useControlCenterState(
150
152
  const newEvents = events.slice(processedEventCountRef.current);
151
153
  processedEventCountRef.current = events.length;
152
154
 
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
-
163
- for (const event of newEvents) {
164
- const newState =
165
- event.eventType === NotificationEventType.PhaseCompleted &&
166
- waitingApprovalFeatures.has(event.featureId)
167
- ? undefined
168
- : mapEventTypeToState(event.eventType);
169
- const newLifecycle = mapPhaseNameToLifecycle(event.phaseName);
170
-
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 }),
155
+ for (const { featureId, state, lifecycle } of resolveSseEventUpdates(newEvents)) {
156
+ if (state !== undefined || lifecycle !== undefined) {
157
+ updateFeature(`feat-${featureId}`, {
158
+ ...(state !== undefined && { state }),
159
+ ...(lifecycle !== undefined && { lifecycle }),
176
160
  });
177
161
  }
178
162
  }
179
163
  }, [events, updateFeature]);
180
164
 
165
+ // Listen for optimistic approval events from the drawer (fires before SSE arrives)
166
+ useEffect(() => {
167
+ const handler = (e: Event) => {
168
+ const { featureId } = (e as CustomEvent<{ featureId: string }>).detail;
169
+ updateFeature(`feat-${featureId}`, { state: 'running' });
170
+ };
171
+ window.addEventListener('shep:feature-approved', handler);
172
+ return () => window.removeEventListener('shep:feature-approved', handler);
173
+ }, [updateFeature]);
174
+
181
175
  // Separate effect: fetch metadata (name + description) when SSE reports it changed
182
176
  const metadataFetchedRef = useRef<Set<string>>(new Set());
177
+ const processedMetadataCountRef = useRef(0);
183
178
 
184
179
  useEffect(() => {
185
- for (const event of events) {
186
- if (event.message !== 'Feature metadata updated') continue;
180
+ if (events.length <= processedMetadataCountRef.current) return;
181
+ const newEvents = events.slice(processedMetadataCountRef.current);
182
+ processedMetadataCountRef.current = events.length;
183
+
184
+ for (const event of newEvents) {
185
+ if (event.message !== METADATA_UPDATED_MESSAGE) continue;
187
186
  if (metadataFetchedRef.current.has(event.featureId)) continue;
188
187
  metadataFetchedRef.current.add(event.featureId);
189
188
 
@@ -285,6 +284,20 @@ export function useControlCenterState(
285
284
 
286
285
  // Snapshot for rollback
287
286
  const prevRepoData = getRepositoryData(repoNodeId);
287
+ const childSnapshots = new Map<string, FeatureEntry>();
288
+ for (const childId of childFeatureIds) {
289
+ const childNode = nodes.find((n) => n.id === childId);
290
+ if (childNode) {
291
+ childSnapshots.set(childId, { nodeId: childId, data: childNode.data as FeatureNodeData });
292
+ }
293
+ }
294
+
295
+ const rollback = () => {
296
+ if (prevRepoData) addRepositoryToMap(repoNodeId, prevRepoData);
297
+ for (const [childId, entry] of childSnapshots) {
298
+ restoreFeature(childId, entry);
299
+ }
300
+ };
288
301
 
289
302
  // Optimistic: remove repo + children
290
303
  removeRepository(repoNodeId);
@@ -296,32 +309,14 @@ export function useControlCenterState(
296
309
  const result = await deleteRepository(repositoryId);
297
310
  if (!result.success) {
298
311
  toast.error(result.error ?? 'Failed to remove repository');
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
- }
312
+ rollback();
309
313
  return;
310
314
  }
311
315
  deleteSound.play();
312
316
  toast.success('Repository removed');
313
317
  } catch {
314
318
  toast.error('Failed to remove repository');
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
- }
319
+ rollback();
325
320
  }
326
321
  },
327
322
  [