@shepai/cli 1.72.8 → 1.72.9-pr219.08421ee

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 (283) hide show
  1. package/dist/packages/core/src/application/ports/output/agents/feature-agent-process.interface.d.ts +2 -1
  2. package/dist/packages/core/src/application/ports/output/agents/feature-agent-process.interface.d.ts.map +1 -1
  3. package/dist/packages/core/src/application/use-cases/agents/approve-agent-run.use-case.d.ts.map +1 -1
  4. package/dist/packages/core/src/application/use-cases/agents/approve-agent-run.use-case.js +1 -0
  5. package/dist/packages/core/src/application/use-cases/features/resume-feature.use-case.d.ts.map +1 -1
  6. package/dist/packages/core/src/application/use-cases/features/resume-feature.use-case.js +1 -0
  7. package/dist/packages/core/src/domain/factories/settings-defaults.factory.js +1 -1
  8. package/dist/packages/core/src/infrastructure/services/agents/common/executors/dev-executor.service.d.ts.map +1 -1
  9. package/dist/packages/core/src/infrastructure/services/agents/common/executors/dev-executor.service.js +88 -13
  10. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-graph.d.ts +1 -1
  11. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-process.service.d.ts +2 -1
  12. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-process.service.d.ts.map +1 -1
  13. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-process.service.js +3 -0
  14. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-worker.d.ts +2 -0
  15. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-worker.d.ts.map +1 -1
  16. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/feature-agent-worker.js +16 -3
  17. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/merge.node.d.ts.map +1 -1
  18. package/dist/packages/core/src/infrastructure/services/agents/feature-agent/nodes/merge/merge.node.js +5 -1
  19. package/dist/src/presentation/web/app/actions/get-graph-data.d.ts +10 -0
  20. package/dist/src/presentation/web/app/actions/get-graph-data.d.ts.map +1 -0
  21. package/dist/src/presentation/web/app/actions/get-graph-data.js +10 -0
  22. package/dist/src/presentation/web/app/api/agent-events/route.d.ts.map +1 -1
  23. package/dist/src/presentation/web/app/api/agent-events/route.js +20 -1
  24. package/dist/src/presentation/web/components/common/control-center-drawer/create-drawer-client.d.ts.map +1 -1
  25. package/dist/src/presentation/web/components/common/control-center-drawer/create-drawer-client.js +9 -8
  26. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts.map +1 -1
  27. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.js +2 -0
  28. package/dist/src/presentation/web/components/common/repository-node/repository-node.js +1 -1
  29. package/dist/src/presentation/web/components/common/sidebar-section-header/sidebar-section-header.d.ts +1 -3
  30. package/dist/src/presentation/web/components/common/sidebar-section-header/sidebar-section-header.d.ts.map +1 -1
  31. package/dist/src/presentation/web/components/common/sidebar-section-header/sidebar-section-header.js +3 -7
  32. package/dist/src/presentation/web/components/common/sidebar-section-header/sidebar-section-header.stories.d.ts +0 -1
  33. package/dist/src/presentation/web/components/common/sidebar-section-header/sidebar-section-header.stories.d.ts.map +1 -1
  34. package/dist/src/presentation/web/components/common/sidebar-section-header/sidebar-section-header.stories.js +0 -11
  35. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
  36. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +140 -2
  37. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.d.ts.map +1 -1
  38. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.js +2 -2
  39. package/dist/src/presentation/web/components/layouts/app-sidebar/app-sidebar.d.ts +1 -3
  40. package/dist/src/presentation/web/components/layouts/app-sidebar/app-sidebar.d.ts.map +1 -1
  41. package/dist/src/presentation/web/components/layouts/app-sidebar/app-sidebar.js +2 -2
  42. package/dist/src/presentation/web/components/ui/scroll-area.js +1 -1
  43. package/dist/src/presentation/web/lib/layout-with-dagre.js +1 -1
  44. package/dist/tsconfig.build.tsbuildinfo +1 -1
  45. package/package.json +1 -1
  46. package/web/.next/BUILD_ID +1 -1
  47. package/web/.next/build-manifest.json +2 -2
  48. package/web/.next/cache/.previewinfo +1 -1
  49. package/web/.next/cache/.rscinfo +1 -1
  50. package/web/.next/cache/.tsbuildinfo +1 -1
  51. package/web/.next/cache/config.json +3 -3
  52. package/web/.next/fallback-build-manifest.json +2 -2
  53. package/web/.next/prerender-manifest.json +3 -3
  54. package/web/.next/required-server-files.js +1 -1
  55. package/web/.next/required-server-files.json +1 -1
  56. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +43 -28
  57. package/web/.next/server/app/(dashboard)/@drawer/create/page.js +3 -3
  58. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  59. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  60. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +49 -34
  61. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js +3 -3
  62. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  63. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  64. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +39 -24
  65. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js +3 -3
  66. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  67. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  68. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +43 -28
  69. package/web/.next/server/app/(dashboard)/create/page.js +3 -3
  70. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  71. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  72. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +49 -34
  73. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js +3 -3
  74. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  75. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  76. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +39 -24
  77. package/web/.next/server/app/(dashboard)/page.js +3 -3
  78. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  79. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  80. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +39 -24
  81. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js +3 -3
  82. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  83. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  84. package/web/.next/server/app/_global-error.html +2 -2
  85. package/web/.next/server/app/_global-error.rsc +1 -1
  86. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  87. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  88. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  89. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  90. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  91. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  92. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  93. package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  94. package/web/.next/server/app/skills/page.js.nft.json +1 -1
  95. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  96. package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  97. package/web/.next/server/app/tools/page.js.nft.json +1 -1
  98. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  99. package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  100. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  101. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +2 -2
  102. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js.map +1 -1
  103. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  104. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  105. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_3a02d8a5.js +3 -0
  106. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_3a02d8a5.js.map +1 -0
  107. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_a3044852.js +3 -0
  108. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_a3044852.js.map +1 -0
  109. package/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js +2 -2
  110. package/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js.map +1 -1
  111. package/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  112. package/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js +2 -2
  113. package/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js.map +1 -1
  114. package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
  115. package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js.map +1 -1
  116. package/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js +2 -2
  117. package/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js.map +1 -1
  118. package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
  119. package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js.map +1 -1
  120. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
  121. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
  122. package/web/.next/server/chunks/ssr/[root-of-the-server]__cbae9863._.js +1 -1
  123. package/web/.next/server/chunks/ssr/[root-of-the-server]__cbae9863._.js.map +1 -1
  124. package/web/.next/server/chunks/ssr/[root-of-the-server]__e5e09f72._.js +1 -1
  125. package/web/.next/server/chunks/ssr/[root-of-the-server]__e5e09f72._.js.map +1 -1
  126. package/web/.next/server/chunks/ssr/_0e9d6bf7._.js +1 -1
  127. package/web/.next/server/chunks/ssr/_0e9d6bf7._.js.map +1 -1
  128. package/web/.next/server/chunks/ssr/{_47ef589d._.js → _4469c284._.js} +2 -2
  129. package/web/.next/server/chunks/ssr/{_47ef589d._.js.map → _4469c284._.js.map} +1 -1
  130. package/web/.next/server/chunks/ssr/_61351ed3._.js +1 -1
  131. package/web/.next/server/chunks/ssr/_61351ed3._.js.map +1 -1
  132. package/web/.next/server/chunks/ssr/_6e08ce21._.js +3 -0
  133. package/web/.next/server/chunks/ssr/_6e08ce21._.js.map +1 -0
  134. package/web/.next/server/chunks/ssr/_8400c8bb._.js +1 -1
  135. package/web/.next/server/chunks/ssr/_8400c8bb._.js.map +1 -1
  136. package/web/.next/server/chunks/ssr/_9f940437._.js +1 -1
  137. package/web/.next/server/chunks/ssr/_9f940437._.js.map +1 -1
  138. package/web/.next/{standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_1a32dbcb._.js → server/chunks/ssr/src_presentation_web_0a49d9ee._.js} +3 -3
  139. package/web/.next/server/chunks/ssr/{src_presentation_web_1a32dbcb._.js.map → src_presentation_web_0a49d9ee._.js.map} +1 -1
  140. package/web/.next/server/chunks/ssr/{src_presentation_web_398553b0._.js → src_presentation_web_5ea689db._.js} +2 -2
  141. package/web/.next/server/chunks/ssr/{src_presentation_web_398553b0._.js.map → src_presentation_web_5ea689db._.js.map} +1 -1
  142. package/web/.next/{standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_398553b0._.js → server/chunks/ssr/src_presentation_web_7188a290._.js} +2 -2
  143. package/web/.next/server/chunks/ssr/{src_presentation_web_5c87e8e4._.js.map → src_presentation_web_7188a290._.js.map} +1 -1
  144. package/web/.next/server/chunks/ssr/{src_presentation_web_1a32dbcb._.js → src_presentation_web_79b0305a._.js} +3 -3
  145. package/web/.next/server/chunks/ssr/{src_presentation_web_c1bc6240._.js.map → src_presentation_web_79b0305a._.js.map} +1 -1
  146. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_cc16bd01.js +3 -0
  147. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_cc16bd01.js.map +1 -0
  148. package/web/.next/server/chunks/ssr/src_presentation_web_app_(dashboard)_get-graph-data_ts_b018483e._.js +3 -0
  149. package/web/.next/server/chunks/ssr/src_presentation_web_app_(dashboard)_get-graph-data_ts_b018483e._.js.map +1 -0
  150. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  151. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  152. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  153. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  154. package/web/.next/server/pages/500.html +2 -2
  155. package/web/.next/server/server-reference-manifest.js +1 -1
  156. package/web/.next/server/server-reference-manifest.json +175 -118
  157. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  158. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  159. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  160. package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
  161. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +43 -28
  162. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page.js +3 -3
  163. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  164. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  165. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +49 -34
  166. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js +3 -3
  167. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  168. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  169. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +39 -24
  170. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js +3 -3
  171. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  172. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  173. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +43 -28
  174. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page.js +3 -3
  175. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  176. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  177. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +49 -34
  178. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page.js +3 -3
  179. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  180. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  181. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +39 -24
  182. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page.js +3 -3
  183. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  184. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  185. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +39 -24
  186. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js +3 -3
  187. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  188. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  189. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  190. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  191. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  192. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  193. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  194. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  195. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  196. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  197. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  198. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  199. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page.js.nft.json +1 -1
  200. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  201. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  202. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page.js.nft.json +1 -1
  203. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  204. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  205. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  206. package/web/.next/standalone/src/presentation/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +2 -2
  207. 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
  208. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_3a02d8a5.js +3 -0
  209. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_a3044852.js +3 -0
  210. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js +2 -2
  211. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js +1 -1
  212. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js +2 -2
  213. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
  214. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js +2 -2
  215. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
  216. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
  217. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__cbae9863._.js +1 -1
  218. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__e5e09f72._.js +1 -1
  219. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_0e9d6bf7._.js +1 -1
  220. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{_47ef589d._.js → _4469c284._.js} +2 -2
  221. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_61351ed3._.js +1 -1
  222. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_6e08ce21._.js +3 -0
  223. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_8400c8bb._.js +1 -1
  224. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_9f940437._.js +1 -1
  225. package/web/.next/{server/chunks/ssr/src_presentation_web_c1bc6240._.js → standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_0a49d9ee._.js} +3 -3
  226. package/web/.next/{server/chunks/ssr/src_presentation_web_5c87e8e4._.js → standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_5ea689db._.js} +2 -2
  227. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{src_presentation_web_5c87e8e4._.js → src_presentation_web_7188a290._.js} +2 -2
  228. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{src_presentation_web_c1bc6240._.js → src_presentation_web_79b0305a._.js} +3 -3
  229. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_cc16bd01.js +3 -0
  230. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_app_(dashboard)_get-graph-data_ts_b018483e._.js +3 -0
  231. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  232. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  233. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  234. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  235. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +175 -118
  236. package/web/.next/standalone/src/presentation/web/CLAUDE.md +147 -0
  237. package/web/.next/standalone/src/presentation/web/app/actions/get-graph-data.ts +12 -0
  238. package/web/.next/standalone/src/presentation/web/app/api/agent-events/route.ts +23 -2
  239. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/create-drawer-client.tsx +9 -8
  240. package/web/.next/standalone/src/presentation/web/components/common/feature-node/derive-feature-state.ts +2 -0
  241. package/web/.next/standalone/src/presentation/web/components/common/repository-node/repository-node.tsx +2 -2
  242. package/web/.next/standalone/src/presentation/web/components/common/sidebar-section-header/sidebar-section-header.stories.tsx +0 -13
  243. package/web/.next/standalone/src/presentation/web/components/common/sidebar-section-header/sidebar-section-header.tsx +1 -45
  244. package/web/.next/standalone/src/presentation/web/components/features/control-center/use-control-center-state.ts +164 -2
  245. package/web/.next/standalone/src/presentation/web/components/features/features-canvas/features-canvas.tsx +21 -23
  246. package/web/.next/standalone/src/presentation/web/components/layouts/app-sidebar/app-sidebar.tsx +2 -14
  247. package/web/.next/standalone/src/presentation/web/components/ui/scroll-area.tsx +1 -1
  248. package/web/.next/standalone/src/presentation/web/lib/layout-with-dagre.ts +1 -1
  249. package/web/.next/standalone/src/presentation/web/server.js +1 -1
  250. package/web/.next/static/chunks/{8682dcb376de62de.js → 073b31d7a4974504.js} +1 -1
  251. package/web/.next/static/chunks/{ebf172a84c9596ed.js → 0e588a3a4dd7d1c0.js} +1 -1
  252. package/web/.next/static/chunks/{fcafb841e3860687.js → 1d281f045b623a45.js} +1 -1
  253. package/web/.next/static/chunks/{71cf72022f08a8c9.js → 2433994d844b7d39.js} +1 -1
  254. package/web/.next/static/chunks/3e041a9008bac888.js +1 -0
  255. package/web/.next/static/chunks/{5cd24ca47061a21d.js → 798624a651d68855.js} +1 -1
  256. package/web/.next/static/chunks/{c71589df79020ecb.js → 977b9d2b9ef19f7d.js} +1 -1
  257. package/web/.next/static/chunks/9be617fc61b893f2.js +1 -0
  258. package/web/.next/static/chunks/{fabfa54940973cc7.js → a18b0561724d11d4.js} +1 -1
  259. package/web/.next/static/chunks/acce8a15d6896dea.css +2 -0
  260. package/web/.next/static/chunks/{1c07b21eb8b32f25.js → cd9099d3eae9dd04.js} +1 -1
  261. package/web/.next/trace +1 -1
  262. package/web/.next/trace-build +1 -1
  263. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_2b7834f6.js +0 -3
  264. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_2b7834f6.js.map +0 -1
  265. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_6923c575.js +0 -3
  266. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_6923c575.js.map +0 -1
  267. package/web/.next/server/chunks/ssr/_250a63ae._.js +0 -3
  268. package/web/.next/server/chunks/ssr/_250a63ae._.js.map +0 -1
  269. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_f4952172.js +0 -3
  270. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_f4952172.js.map +0 -1
  271. package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js +0 -3
  272. package/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js.map +0 -1
  273. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_2b7834f6.js +0 -3
  274. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_6923c575.js +0 -3
  275. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_250a63ae._.js +0 -3
  276. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_f4952172.js +0 -3
  277. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_ca99d62d._.js +0 -3
  278. package/web/.next/static/chunks/3c79bca4d3f18f66.js +0 -1
  279. package/web/.next/static/chunks/67f2a7a5e8dc358a.css +0 -2
  280. package/web/.next/static/chunks/991d4490d7a338d8.js +0 -1
  281. /package/web/.next/static/{IKuYPxAuMvc4BtwKHlj30 → pzwrJGWI__eUhJsxxW2S-}/_buildManifest.js +0 -0
  282. /package/web/.next/static/{IKuYPxAuMvc4BtwKHlj30 → pzwrJGWI__eUhJsxxW2S-}/_clientMiddlewareManifest.json +0 -0
  283. /package/web/.next/static/{IKuYPxAuMvc4BtwKHlj30 → pzwrJGWI__eUhJsxxW2S-}/_ssgManifest.js +0 -0
@@ -0,0 +1,147 @@
1
+ # Web UI Development Notes
2
+
3
+ ## Running the UI
4
+
5
+ There are two distinct ways to run the web UI. **Always test in BOTH modes.**
6
+
7
+ ### 1. Dev Mode (local development)
8
+
9
+ ```bash
10
+ pnpm dev:web
11
+ ```
12
+
13
+ - Uses `dev-server.ts` which initializes the DI container and starts Next.js in dev mode
14
+ - Turbopack HMR enabled
15
+ - Runs on port 3000 by default
16
+
17
+ ### 2. Production Mode (how users run it)
18
+
19
+ ```bash
20
+ shep ui
21
+ # or
22
+ pnpm dev:cli ui
23
+ ```
24
+
25
+ - The CLI bootstraps the DI container (`src/presentation/cli/index.ts`), then starts Next.js via `WebServerService`
26
+ - Runs on port 4050 by default
27
+ - This is how real users experience the UI
28
+ - **IMPORTANT**: Always test real-time features in this mode, not just dev mode
29
+
30
+ ### Key Differences Between Modes
31
+
32
+ | Aspect | Dev Mode (`pnpm dev:web`) | Production Mode (`shep ui`) |
33
+ | ------------------ | ------------------------------------------------------- | ------------------------------------------------------- |
34
+ | Entry point | `dev-server.ts` | `cli/index.ts` -> `ui.command.ts` -> `WebServerService` |
35
+ | Port | 3000 | 4050 |
36
+ | DI container | Set on `globalThis.__shepContainer` in dev-server.ts:73 | Set in cli/index.ts:76 |
37
+ | WebSocket upgrades | Forwarded (HMR works) | NOT forwarded in `WebServerService` |
38
+ | Next.js mode | Always dev | dev when run from source, prod when installed |
39
+
40
+ ## SSE / Real-Time Update Architecture
41
+
42
+ ```
43
+ DB polling (500ms) -> /api/agent-events (SSE route)
44
+ -> Service Worker (public/agent-events-sw.js, single connection)
45
+ -> postMessage to all browser tabs
46
+ -> useAgentEvents hook (hooks/use-agent-events.ts)
47
+ -> AgentEventsProvider context (hooks/agent-events-provider.tsx)
48
+ -> useControlCenterState (optimistic node updates via setNodes)
49
+ -> React state updates -> sidebar + React Flow canvas
50
+ ```
51
+
52
+ ### SSE Key Files
53
+
54
+ | File | Purpose |
55
+ | ---------------------------------------------------------------- | ------------------------------------------------------ |
56
+ | `app/api/agent-events/route.ts` | SSE endpoint, polls DB every 500ms, emits delta events |
57
+ | `app/api/agent-events/health/route.ts` | Health check: `/api/agent-events/health` |
58
+ | `public/agent-events-sw.js` | Service Worker: multiplexes single SSE to all tabs |
59
+ | `hooks/use-agent-events.ts` | Client hook: registers with SW, receives events |
60
+ | `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 |
62
+ | `components/common/feature-node/derive-feature-state.ts` | Maps event types/phases to UI state/lifecycle |
63
+
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
+ ### SSE Lifecycle Mappings
74
+
75
+ The SSE route maps `SdlcLifecycle` enum -> agent graph node names -> client `FeatureLifecyclePhase`:
76
+
77
+ ```
78
+ SdlcLifecycle.Started -> 'requirements' -> 'requirements'
79
+ SdlcLifecycle.Analyze -> 'analyze' -> 'requirements'
80
+ SdlcLifecycle.Requirements-> 'requirements' -> 'requirements'
81
+ SdlcLifecycle.Research -> 'research' -> 'research'
82
+ SdlcLifecycle.Planning -> 'plan' -> 'implementation'
83
+ SdlcLifecycle.Implementation -> 'implement' -> 'implementation'
84
+ SdlcLifecycle.Review -> 'merge' -> 'review'
85
+ SdlcLifecycle.Maintain -> 'maintain' -> 'maintain'
86
+ SdlcLifecycle.Blocked -> 'blocked' -> 'requirements'
87
+ ```
88
+
89
+ ## React Flow Rendering (CRITICAL)
90
+
91
+ React Flow v12 (`@xyflow/react` ^12.10.0) has specific behaviors that affect real-time updates:
92
+
93
+ - **Controlled mode**: `<ReactFlow nodes={nodes}>` passes nodes as props
94
+ - **Internal Zustand store**: React Flow syncs props to an internal `nodeLookup` Map via `StoreUpdater`
95
+ - **Memoized NodeWrapper**: Each node is wrapped in `memo(NodeWrapper)` which subscribes to store via `useStore(selector, shallow)`
96
+ - **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
98
+
99
+ ### Circular Update Loop (FIXED — do not reintroduce)
100
+
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).
106
+
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.
110
+
111
+ **NEVER** remove the `c.type !== 'replace'` filter from `onNodesChange`.
112
+
113
+ ## Testing Real-Time Updates
114
+
115
+ 1. Start the UI in the mode you want to test
116
+ 2. Check SSE health: `curl http://localhost:<port>/api/agent-events/health`
117
+ 3. Check SW status in browser: `navigator.serviceWorker.controller?.state` should be "activated"
118
+ 4. Trigger state changes: `pnpm dev:cli feat approve <id>` or start a new feature run
119
+ 5. Watch both **sidebar** and **canvas nodes** - they should both update
120
+ 6. Check browser console for errors
121
+ 7. Check server logs for `[SSE] emit:` messages confirming events are sent
122
+
123
+ ## Optimistic Feature Creation Flow
124
+
125
+ When a user creates a feature, the UI uses optimistic updates:
126
+
127
+ 1. **Create drawer** dispatches `shep:feature-created` → canvas adds a temp node (`feature-<ts>` ID, state `creating`)
128
+ 2. Drawer closes immediately via `router.push('/')`
129
+ 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
132
+
133
+ ### Key constraints
134
+
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.
141
+
142
+ ## Common Pitfalls
143
+
144
+ - **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
+ - **Storybook mocks**: New server actions need mocks in `.storybook/mocks/app/actions/` or Storybook build breaks.
147
+ - **`output: 'standalone'`** in next.config.ts: Production builds use standalone mode. Route handlers are bundled differently.
@@ -0,0 +1,12 @@
1
+ 'use server';
2
+
3
+ import { getGraphData } from '@/app/(dashboard)/get-graph-data';
4
+
5
+ /**
6
+ * Server action that fetches fresh graph nodes and edges.
7
+ * Used as a lightweight polling fallback for active features,
8
+ * avoiding full router.refresh() page re-renders.
9
+ */
10
+ export async function fetchGraphData() {
11
+ return getGraphData();
12
+ }
@@ -44,13 +44,16 @@ interface CachedFeatureState {
44
44
  * Maps SdlcLifecycle values to agent graph node names so the client
45
45
  * can derive the correct FeatureLifecyclePhase via mapPhaseNameToLifecycle().
46
46
  */
47
- const LIFECYCLE_TO_NODE: Partial<Record<SdlcLifecycle, string>> = {
47
+ const LIFECYCLE_TO_NODE: Record<SdlcLifecycle, string> = {
48
+ [SdlcLifecycle.Started]: 'requirements',
48
49
  [SdlcLifecycle.Analyze]: 'analyze',
49
50
  [SdlcLifecycle.Requirements]: 'requirements',
50
51
  [SdlcLifecycle.Research]: 'research',
51
52
  [SdlcLifecycle.Planning]: 'plan',
52
53
  [SdlcLifecycle.Implementation]: 'implement',
53
54
  [SdlcLifecycle.Review]: 'merge',
55
+ [SdlcLifecycle.Maintain]: 'maintain',
56
+ [SdlcLifecycle.Blocked]: 'blocked',
54
57
  };
55
58
 
56
59
  const STATUS_TO_EVENT: Partial<
@@ -149,7 +152,8 @@ export function GET(request: Request): Response {
149
152
  const prev = cache.get(feature.id);
150
153
 
151
154
  if (!prev) {
152
- // First time seeing this feature — seed cache, don't emit
155
+ // First time seeing this feature — seed cache and emit so
156
+ // clients discover features created outside the UI (e.g. CLI).
153
157
  const completedPhases = new Set<string>();
154
158
  try {
155
159
  const timings = await phaseTimingRepo.findByRunId(run.id);
@@ -166,6 +170,23 @@ export function GET(request: Request): Response {
166
170
  completedPhases,
167
171
  featureName: feature.name,
168
172
  });
173
+
174
+ // Emit initial state so the client learns about new features
175
+ const mapping = STATUS_TO_EVENT[run.status];
176
+ if (mapping) {
177
+ const phase = resultToPhase(run.result);
178
+ emitEvent({
179
+ eventType: mapping.eventType,
180
+ agentRunId: run.id,
181
+ featureId: feature.id,
182
+ featureName: feature.name,
183
+ ...(phase && { phaseName: phase }),
184
+ message: `Agent status: ${run.status}`,
185
+ severity: mapping.severity,
186
+ timestamp: new Date().toISOString(),
187
+ });
188
+ }
189
+
169
190
  continue;
170
191
  }
171
192
 
@@ -34,15 +34,17 @@ export function CreateDrawerClient({
34
34
  // When submitting, force the drawer closed immediately — router.push('/')
35
35
  // is async and the pathname may not update before the next render.
36
36
  const pathname = usePathname();
37
- const isOpen = !isSubmitting && pathname.startsWith('/create');
37
+ const isOnCreateRoute = pathname.startsWith('/create');
38
+ const isOpen = !isSubmitting && isOnCreateRoute;
38
39
 
39
- // Safety net: if the drawer is closed (e.g. via form submit + isSubmitting)
40
- // but router.push('/') hasn't taken effect yet, force-clear the route.
40
+ // Reset isSubmitting once the route has actually changed away from /create,
41
+ // so the drawer can reopen on future visits. Without this, isSubmitting
42
+ // would stay true forever since parallel routes preserve component state.
41
43
  useEffect(() => {
42
- if (!isOpen && pathname.startsWith('/create')) {
43
- router.replace('/');
44
+ if (!isOnCreateRoute && isSubmitting) {
45
+ setIsSubmitting(false);
44
46
  }
45
- }, [isOpen, pathname, router]);
47
+ }, [isOnCreateRoute, isSubmitting]);
46
48
 
47
49
  const onClose = useCallback(() => {
48
50
  router.push('/');
@@ -81,8 +83,7 @@ export function CreateDrawerClient({
81
83
  .catch(() => {
82
84
  toast.error('Failed to create feature');
83
85
  window.dispatchEvent(new CustomEvent('shep:feature-create-failed'));
84
- })
85
- .finally(() => {
86
+ // Reset on error so the user can retry without navigating away
86
87
  setIsSubmitting(false);
87
88
  });
88
89
  },
@@ -120,6 +120,8 @@ const phaseNameToLifecycle: Record<string, FeatureLifecyclePhase> = {
120
120
  plan: 'implementation',
121
121
  implement: 'implementation',
122
122
  merge: 'review',
123
+ maintain: 'maintain',
124
+ blocked: 'requirements',
123
125
  };
124
126
 
125
127
  export function mapPhaseNameToLifecycle(
@@ -39,7 +39,7 @@ export function RepositoryNode({ data }: { data: RepositoryNodeData; [key: strin
39
39
  const isDeploymentActive = deployAction.status === 'Booting' || deployAction.status === 'Ready';
40
40
 
41
41
  return (
42
- <div className="group relative">
42
+ <div className={cn('group relative', data.onDelete && data.id && 'pl-10')}>
43
43
  {data.showHandles ? (
44
44
  <Handle
45
45
  type="target"
@@ -52,7 +52,7 @@ export function RepositoryNode({ data }: { data: RepositoryNodeData; [key: strin
52
52
  {/* Delete button — visible on hover, positioned to the left */}
53
53
  {data.onDelete && data.id ? (
54
54
  <>
55
- <div className="absolute top-1/2 -left-10 -translate-y-1/2 opacity-0 transition-opacity group-hover:opacity-100">
55
+ <div className="absolute top-1/2 left-0 -translate-y-1/2 opacity-0 transition-opacity group-hover:opacity-100">
56
56
  <TooltipProvider>
57
57
  <Tooltip>
58
58
  <TooltipTrigger asChild>
@@ -28,16 +28,3 @@ export const Default: Story = {
28
28
  label: 'Features',
29
29
  },
30
30
  };
31
-
32
- function noop() {
33
- // Intentional no-op for Storybook interaction demos
34
- return;
35
- }
36
-
37
- export const WithActions: Story = {
38
- args: {
39
- label: 'Features',
40
- onFolderClick: noop,
41
- onMenuClick: noop,
42
- },
43
- };
@@ -1,62 +1,18 @@
1
1
  'use client';
2
2
 
3
- import { Folder, GripHorizontal } from 'lucide-react';
4
3
  import { SidebarGroup } from '@/components/ui/sidebar';
5
- import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
6
- import { useSidebar } from '@/components/ui/sidebar';
7
4
  import { cn } from '@/lib/utils';
8
5
 
9
6
  export interface SidebarSectionHeaderProps {
10
7
  label: string;
11
- onFolderClick?: () => void;
12
- onMenuClick?: () => void;
13
8
  className?: string;
14
9
  }
15
10
 
16
- export function SidebarSectionHeader({
17
- label,
18
- onFolderClick,
19
- onMenuClick,
20
- className,
21
- }: SidebarSectionHeaderProps) {
22
- const { state, isMobile } = useSidebar();
23
-
11
+ export function SidebarSectionHeader({ label, className }: SidebarSectionHeaderProps) {
24
12
  return (
25
13
  <SidebarGroup data-testid="sidebar-section-header" className={cn('px-2 py-1', className)}>
26
14
  <div className="text-sidebar-foreground flex h-7 w-full shrink-0 items-center px-2 text-sm font-medium">
27
15
  <span className="min-w-0 flex-1 truncate">{label}</span>
28
- <span className="text-sidebar-foreground/70 flex shrink-0 items-center gap-0.5">
29
- <Tooltip>
30
- <TooltipTrigger asChild>
31
- <button
32
- type="button"
33
- onClick={onFolderClick}
34
- className="ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex size-7 items-center justify-center rounded-md p-0 outline-hidden transition-colors focus-visible:ring-2 [&>svg]:size-4"
35
- aria-label="Open features folder"
36
- >
37
- <Folder className="size-4" />
38
- </button>
39
- </TooltipTrigger>
40
- <TooltipContent side="right" align="center" hidden={state !== 'collapsed' || isMobile}>
41
- Open features folder
42
- </TooltipContent>
43
- </Tooltip>
44
- <Tooltip>
45
- <TooltipTrigger asChild>
46
- <button
47
- type="button"
48
- onClick={onMenuClick}
49
- className="ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex size-7 items-center justify-center rounded-md p-0 outline-hidden transition-colors focus-visible:ring-2 [&>svg]:size-4"
50
- aria-label="Features options"
51
- >
52
- <GripHorizontal className="size-4" />
53
- </button>
54
- </TooltipTrigger>
55
- <TooltipContent side="right" align="center" hidden={state !== 'collapsed' || isMobile}>
56
- Features options
57
- </TooltipContent>
58
- </Tooltip>
59
- </span>
60
16
  </div>
61
17
  </SidebarGroup>
62
18
  );
@@ -15,6 +15,7 @@ import {
15
15
  import { deleteFeature } from '@/app/actions/delete-feature';
16
16
  import { addRepository } from '@/app/actions/add-repository';
17
17
  import { deleteRepository } from '@/app/actions/delete-repository';
18
+ import { fetchGraphData } from '@/app/actions/get-graph-data';
18
19
  import { useAgentEventsContext } from '@/hooks/agent-events-provider';
19
20
  import { useSoundAction } from '@/hooks/use-sound-action';
20
21
  import {
@@ -190,13 +191,42 @@ export function useControlCenterState(
190
191
  // drop events when multiple SSE messages arrive in the same tick.
191
192
  const processedEventCountRef = useRef(0);
192
193
 
194
+ // Track known feature IDs so we can detect when SSE reports features not yet on the canvas.
195
+ // Initialised from initialNodes to avoid a race between first render and SSE arrival.
196
+ const knownFeatureIdsRef = useRef<Set<string>>(
197
+ new Set(
198
+ initialNodes
199
+ .filter((n) => n.type === 'featureNode')
200
+ .map((n) => (n.data as FeatureNodeData).featureId)
201
+ )
202
+ );
203
+ // Keep ref in sync as nodes change (new nodes added, temp nodes replaced, etc.)
204
+ useEffect(() => {
205
+ knownFeatureIdsRef.current = new Set(
206
+ nodes
207
+ .filter((n) => n.type === 'featureNode')
208
+ .map((n) => (n.data as FeatureNodeData).featureId)
209
+ );
210
+ }, [nodes]);
211
+
212
+ // Guard against concurrent fetchGraphData calls triggered by unknown-feature detection
213
+ const fetchingGraphRef = useRef(false);
214
+
193
215
  useEffect(() => {
194
216
  if (events.length <= processedEventCountRef.current) return;
195
217
 
196
218
  const newEvents = events.slice(processedEventCountRef.current);
197
219
  processedEventCountRef.current = events.length;
198
220
 
221
+ let hasUnknownFeature = false;
222
+
199
223
  for (const event of newEvents) {
224
+ // If the feature isn't on the canvas yet, flag for a full graph refresh
225
+ if (!knownFeatureIdsRef.current.has(event.featureId)) {
226
+ hasUnknownFeature = true;
227
+ continue;
228
+ }
229
+
200
230
  const newState = mapEventTypeToState(event.eventType);
201
231
  const newLifecycle = mapPhaseNameToLifecycle(event.phaseName);
202
232
 
@@ -206,6 +236,7 @@ export function useControlCenterState(
206
236
  if (node.type !== 'featureNode') return node;
207
237
  const data = node.data as FeatureNodeData;
208
238
  if (data.featureId !== event.featureId) return node;
239
+
209
240
  return {
210
241
  ...node,
211
242
  data: {
@@ -217,10 +248,141 @@ export function useControlCenterState(
217
248
  })
218
249
  );
219
250
  }
220
- }, [events]);
251
+
252
+ // SSE reported features not on the canvas — fetch full graph data so new
253
+ // repository nodes, feature nodes, and edges all appear together.
254
+ if (hasUnknownFeature && !fetchingGraphRef.current) {
255
+ fetchingGraphRef.current = true;
256
+ fetchGraphData()
257
+ .then(({ nodes: serverNodes, edges: serverEdges }) => {
258
+ setNodes((currentNodes) => {
259
+ // Preserve positions of nodes already on the canvas
260
+ const currentById = new Map(currentNodes.map((n) => [n.id, n]));
261
+ const merged = serverNodes.map((serverNode) => {
262
+ const existing = currentById.get(serverNode.id);
263
+ return existing ? { ...serverNode, position: existing.position } : serverNode;
264
+ });
265
+
266
+ const layoutResult = layoutWithDagre(merged, serverEdges, CANVAS_LAYOUT_DEFAULTS);
267
+ setEdges(layoutResult.edges);
268
+ return layoutResult.nodes;
269
+ });
270
+ })
271
+ .catch(() => {
272
+ // Silently ignore — next SSE event or poll will retry
273
+ })
274
+ .finally(() => {
275
+ fetchingGraphRef.current = false;
276
+ });
277
+ }
278
+ }, [events, setEdges]);
279
+
280
+ // Lightweight server-action polling fallback: fetches fresh graph data every 5s
281
+ // when any feature is in an active state. This avoids full router.refresh() while
282
+ // ensuring the UI stays in sync even if SSE events are missed.
283
+ //
284
+ // We derive a stable boolean so the interval isn't torn down on every node change.
285
+ const hasActiveFeature = nodes.some((n) => {
286
+ if (n.type !== 'featureNode') return false;
287
+ const data = n.data as FeatureNodeData;
288
+ return (
289
+ data.state === 'running' || data.state === 'action-required' || data.state === 'creating'
290
+ );
291
+ });
292
+
293
+ useEffect(() => {
294
+ if (!hasActiveFeature) return;
295
+
296
+ const interval = setInterval(async () => {
297
+ try {
298
+ const { nodes: serverNodes } = await fetchGraphData();
299
+ setNodes((currentNodes) => {
300
+ const serverById = new Map(serverNodes.map((n) => [n.id, n]));
301
+ let changed = false;
302
+
303
+ const merged = currentNodes.map((node) => {
304
+ const serverNode = serverById.get(node.id);
305
+ if (!serverNode || node.type !== 'featureNode') return node;
306
+ const currentData = node.data as FeatureNodeData;
307
+ const serverData = serverNode.data as FeatureNodeData;
308
+
309
+ // Only update if server data has different state or lifecycle
310
+ if (
311
+ currentData.state === serverData.state &&
312
+ currentData.lifecycle === serverData.lifecycle
313
+ ) {
314
+ return node;
315
+ }
316
+
317
+ changed = true;
318
+ return {
319
+ ...node,
320
+ data: {
321
+ ...currentData,
322
+ state: serverData.state,
323
+ lifecycle: serverData.lifecycle,
324
+ progress: serverData.progress,
325
+ ...(serverData.errorMessage && { errorMessage: serverData.errorMessage }),
326
+ ...(serverData.runtime && { runtime: serverData.runtime }),
327
+ ...(serverData.agentType && { agentType: serverData.agentType }),
328
+ ...(serverData.pr && { pr: serverData.pr }),
329
+ ...(serverData.blockedBy && { blockedBy: serverData.blockedBy }),
330
+ },
331
+ };
332
+ });
333
+
334
+ // Find new server nodes not yet on the client
335
+ const clientIds = new Set(currentNodes.map((n) => n.id));
336
+ const newServerNodes = serverNodes.filter((n) => !clientIds.has(n.id));
337
+
338
+ // Replace optimistic "creating" temp nodes with their real server
339
+ // counterparts. Temp nodes have IDs like "feature-<ts>" while real
340
+ // nodes use "feat-<uuid>". Match by repositoryPath + name.
341
+ if (newServerNodes.length > 0) {
342
+ const creatingIndices: number[] = [];
343
+ merged.forEach((n, i) => {
344
+ if (n.type === 'featureNode' && (n.data as FeatureNodeData).state === 'creating') {
345
+ creatingIndices.push(i);
346
+ }
347
+ });
348
+
349
+ for (const serverNode of newServerNodes) {
350
+ if (serverNode.type !== 'featureNode' || creatingIndices.length === 0) continue;
351
+ const idx = creatingIndices.shift()!;
352
+ // Replace the temp node in-place, keeping its position
353
+ merged[idx] = { ...serverNode, position: merged[idx].position };
354
+ changed = true;
355
+ }
356
+
357
+ // Any remaining new server nodes that didn't replace a temp node
358
+ const replacedIds = new Set(merged.map((n) => n.id));
359
+ const trueNewNodes = newServerNodes.filter((n) => !replacedIds.has(n.id));
360
+ if (trueNewNodes.length > 0) {
361
+ changed = true;
362
+ return [...merged, ...trueNewNodes];
363
+ }
364
+ }
365
+
366
+ return changed ? merged : currentNodes;
367
+ });
368
+ } catch {
369
+ // Silently ignore polling errors — SSE is the primary mechanism
370
+ }
371
+ }, 5_000);
372
+
373
+ return () => clearInterval(interval);
374
+ }, [hasActiveFeature]);
221
375
 
222
376
  const onNodesChange = useCallback((changes: NodeChange<CanvasNodeType>[]) => {
223
- setNodes((ns) => applyNodeChanges(changes, ns));
377
+ // Filter out 'replace' changes — in controlled mode these come from
378
+ // React Flow's StoreUpdater syncing the nodes prop back into state.
379
+ // Applying them creates a circular update loop (nodes → enrichedNodes →
380
+ // StoreUpdater → onNodesChange → setNodes → nodes → …) that both causes
381
+ // excessive re-renders and overwrites SSE-driven state changes with stale data.
382
+ const applicable = changes.filter((c) => c.type !== 'replace');
383
+ if (applicable.length > 0) {
384
+ setNodes((ns) => applyNodeChanges(applicable, ns));
385
+ }
224
386
  }, []);
225
387
 
226
388
  const handleConnect = useCallback(
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useCallback, useMemo } from 'react';
4
- import { ReactFlow, Background, Controls, ReactFlowProvider } from '@xyflow/react';
4
+ import { ReactFlow, Background, Controls } from '@xyflow/react';
5
5
  import type { Connection, Edge, NodeChange } from '@xyflow/react';
6
6
  import { Plus } from 'lucide-react';
7
7
  import { Button } from '@/components/ui/button';
@@ -163,28 +163,26 @@ export function FeaturesCanvas({
163
163
  data-no-drawer-close
164
164
  className="pointer-events-auto h-full w-full"
165
165
  >
166
- <ReactFlowProvider>
167
- <ReactFlow
168
- nodes={enrichedNodes}
169
- edges={edges}
170
- nodeTypes={nodeTypes}
171
- edgeTypes={edgeTypes}
172
- isValidConnection={isValidConnection}
173
- onConnect={onConnect}
174
- onNodesChange={onNodesChange}
175
- onNodeClick={onNodeClick}
176
- onPaneClick={onPaneClick}
177
- onMoveStart={onCanvasDrag}
178
- defaultViewport={{ x: 30, y: 30, zoom: 0.85 }}
179
- nodesDraggable={false}
180
- nodesConnectable={false}
181
- elementsSelectable={false}
182
- >
183
- <Background />
184
- <Controls />
185
- {toolbar}
186
- </ReactFlow>
187
- </ReactFlowProvider>
166
+ <ReactFlow
167
+ nodes={enrichedNodes}
168
+ edges={edges}
169
+ nodeTypes={nodeTypes}
170
+ edgeTypes={edgeTypes}
171
+ isValidConnection={isValidConnection}
172
+ onConnect={onConnect}
173
+ onNodesChange={onNodesChange}
174
+ onNodeClick={onNodeClick}
175
+ onPaneClick={onPaneClick}
176
+ onMoveStart={onCanvasDrag}
177
+ defaultViewport={{ x: 30, y: 30, zoom: 0.85 }}
178
+ nodesDraggable={false}
179
+ nodesConnectable={false}
180
+ elementsSelectable={false}
181
+ >
182
+ <Background />
183
+ <Controls />
184
+ {toolbar}
185
+ </ReactFlow>
188
186
  </div>
189
187
  );
190
188
  }
@@ -37,17 +37,9 @@ export interface AppSidebarProps {
37
37
  features: FeatureItem[];
38
38
  onNewFeature?: () => void;
39
39
  onFeatureClick?: (featureId: string) => void;
40
- onFeaturesFolderClick?: () => void;
41
- onFeaturesMenuClick?: () => void;
42
40
  }
43
41
 
44
- export function AppSidebar({
45
- features,
46
- onNewFeature,
47
- onFeatureClick,
48
- onFeaturesFolderClick,
49
- onFeaturesMenuClick,
50
- }: AppSidebarProps) {
42
+ export function AppSidebar({ features, onNewFeature, onFeatureClick }: AppSidebarProps) {
51
43
  const pathname = usePathname();
52
44
  const { state } = useSidebar();
53
45
  const collapsed = state === 'collapsed';
@@ -109,11 +101,7 @@ export function AppSidebar({
109
101
  expandedVisible ? 'opacity-100' : 'opacity-0',
110
102
  ].join(' ')}
111
103
  >
112
- <SidebarSectionHeader
113
- label="Features"
114
- onFolderClick={onFeaturesFolderClick}
115
- onMenuClick={onFeaturesMenuClick}
116
- />
104
+ <SidebarSectionHeader label="Features" />
117
105
  <ScrollArea>
118
106
  {grouped.map(({ key, label, items }) =>
119
107
  items.length > 0 ? (
@@ -18,7 +18,7 @@ function ScrollArea({
18
18
  >
19
19
  <ScrollAreaPrimitive.Viewport
20
20
  data-slot="scroll-area-viewport"
21
- className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
21
+ className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&>div]:block!"
22
22
  >
23
23
  {children}
24
24
  </ScrollAreaPrimitive.Viewport>