@shepai/cli 1.72.9-pr220.f17b60a → 1.73.0

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 (213) hide show
  1. package/dist/src/presentation/web/app/layout.js +1 -1
  2. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.d.ts.map +1 -1
  3. package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.js +45 -124
  4. package/dist/src/presentation/web/components/common/control-center-drawer/use-artifact-fetch.d.ts +13 -0
  5. package/dist/src/presentation/web/components/common/control-center-drawer/use-artifact-fetch.d.ts.map +1 -0
  6. package/dist/src/presentation/web/components/common/control-center-drawer/use-artifact-fetch.js +46 -0
  7. package/dist/src/presentation/web/components/common/feature-drawer/use-feature-actions.d.ts.map +1 -1
  8. package/dist/src/presentation/web/components/common/feature-drawer/use-feature-actions.js +7 -10
  9. package/dist/src/presentation/web/components/common/repository-node/use-repository-actions.d.ts.map +1 -1
  10. package/dist/src/presentation/web/components/common/repository-node/use-repository-actions.js +14 -39
  11. package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +6 -7
  12. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
  13. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +24 -8
  14. package/dist/src/presentation/web/hooks/agent-events-provider.d.ts.map +1 -1
  15. package/dist/src/presentation/web/hooks/agent-events-provider.js +3 -2
  16. package/dist/src/presentation/web/hooks/use-agent-events.d.ts.map +1 -1
  17. package/dist/src/presentation/web/hooks/use-agent-events.js +12 -2
  18. package/dist/src/presentation/web/hooks/use-deploy-action.d.ts.map +1 -1
  19. package/dist/src/presentation/web/hooks/use-deploy-action.js +8 -3
  20. package/dist/src/presentation/web/lib/logger.d.ts +7 -0
  21. package/dist/src/presentation/web/lib/logger.d.ts.map +1 -0
  22. package/dist/src/presentation/web/lib/logger.js +11 -0
  23. package/dist/tsconfig.build.tsbuildinfo +1 -1
  24. package/package.json +1 -1
  25. package/web/.next/BUILD_ID +1 -1
  26. package/web/.next/build-manifest.json +2 -2
  27. package/web/.next/cache/.previewinfo +1 -1
  28. package/web/.next/cache/.rscinfo +1 -1
  29. package/web/.next/cache/.tsbuildinfo +1 -1
  30. package/web/.next/cache/config.json +3 -3
  31. package/web/.next/fallback-build-manifest.json +2 -2
  32. package/web/.next/prerender-manifest.json +3 -3
  33. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +15 -15
  34. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  35. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  36. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +18 -18
  37. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  38. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  39. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +13 -13
  40. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  41. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  42. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +15 -15
  43. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  44. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  45. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +18 -18
  46. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  47. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  48. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +13 -13
  49. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  50. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  51. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +13 -13
  52. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  53. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  54. package/web/.next/server/app/_global-error.html +2 -2
  55. package/web/.next/server/app/_global-error.rsc +1 -1
  56. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  57. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  58. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  59. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  60. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  61. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  62. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  63. package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  64. package/web/.next/server/app/skills/page.js.nft.json +1 -1
  65. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  66. package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  67. package/web/.next/server/app/tools/page.js.nft.json +1 -1
  68. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  69. package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  70. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  71. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  72. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  73. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js +1 -1
  74. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js.map +1 -1
  75. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js +1 -1
  76. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js.map +1 -1
  77. package/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js +2 -2
  78. package/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js.map +1 -1
  79. package/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js +2 -2
  80. package/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js.map +1 -1
  81. package/web/.next/server/chunks/ssr/[root-of-the-server]__6258349c._.js +1 -1
  82. package/web/.next/server/chunks/ssr/[root-of-the-server]__6258349c._.js.map +1 -1
  83. package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
  84. package/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js.map +1 -1
  85. package/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js +2 -2
  86. package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
  87. package/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js.map +1 -1
  88. package/web/.next/server/chunks/ssr/[root-of-the-server]__94a6d275._.js +1 -1
  89. package/web/.next/server/chunks/ssr/[root-of-the-server]__94a6d275._.js.map +1 -1
  90. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
  91. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
  92. package/web/.next/server/chunks/ssr/_0e9d6bf7._.js +1 -1
  93. package/web/.next/server/chunks/ssr/_0e9d6bf7._.js.map +1 -1
  94. package/web/.next/server/chunks/ssr/_61351ed3._.js +1 -1
  95. package/web/.next/server/chunks/ssr/_61351ed3._.js.map +1 -1
  96. package/web/.next/server/chunks/ssr/_8400c8bb._.js +1 -1
  97. package/web/.next/server/chunks/ssr/_8400c8bb._.js.map +1 -1
  98. package/web/.next/server/chunks/ssr/_9f940437._.js +1 -1
  99. package/web/.next/server/chunks/ssr/_9f940437._.js.map +1 -1
  100. package/web/.next/server/chunks/ssr/{_8e87c87b._.js → _dee726e3._.js} +2 -2
  101. package/web/.next/server/chunks/ssr/{_8e87c87b._.js.map → _dee726e3._.js.map} +1 -1
  102. package/web/.next/server/chunks/ssr/src_presentation_web_2b91e4ec._.js +1 -1
  103. package/web/.next/server/chunks/ssr/src_presentation_web_2b91e4ec._.js.map +1 -1
  104. package/web/.next/server/chunks/ssr/src_presentation_web_5bcc1537._.js +1 -1
  105. package/web/.next/server/chunks/ssr/src_presentation_web_5bcc1537._.js.map +1 -1
  106. package/web/.next/server/chunks/ssr/src_presentation_web_67178b88._.js +1 -1
  107. package/web/.next/server/chunks/ssr/src_presentation_web_67178b88._.js.map +1 -1
  108. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_5955c012.js +1 -1
  109. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_5955c012.js.map +1 -1
  110. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  111. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  112. package/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js +2 -2
  113. package/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js.map +1 -1
  114. package/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js +2 -2
  115. package/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js.map +1 -1
  116. package/web/.next/server/pages/500.html +2 -2
  117. package/web/.next/server/server-reference-manifest.js +1 -1
  118. package/web/.next/server/server-reference-manifest.json +21 -21
  119. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  120. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  121. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  122. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +15 -15
  123. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  124. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  125. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +18 -18
  126. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  127. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  128. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +13 -13
  129. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  130. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  131. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +15 -15
  132. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  133. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  134. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +18 -18
  135. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  136. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  137. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +13 -13
  138. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  139. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  140. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +13 -13
  141. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  142. package/web/.next/standalone/src/presentation/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  143. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  144. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  145. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  146. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  147. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  148. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  149. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  150. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  151. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  152. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  153. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page.js.nft.json +1 -1
  154. package/web/.next/standalone/src/presentation/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  155. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  156. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page.js.nft.json +1 -1
  157. package/web/.next/standalone/src/presentation/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  158. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  159. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  160. 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
  161. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_816618e2.js +1 -1
  162. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_ba2e050f.js +1 -1
  163. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__17a611f0._.js +2 -2
  164. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__45dbf93d._.js +2 -2
  165. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6258349c._.js +1 -1
  166. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6b17a22d._.js +1 -1
  167. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6cbc1149._.js +2 -2
  168. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__804c006d._.js +1 -1
  169. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__94a6d275._.js +1 -1
  170. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
  171. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_0e9d6bf7._.js +1 -1
  172. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_61351ed3._.js +1 -1
  173. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_8400c8bb._.js +1 -1
  174. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/_9f940437._.js +1 -1
  175. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{_8e87c87b._.js → _dee726e3._.js} +2 -2
  176. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_2b91e4ec._.js +1 -1
  177. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_5bcc1537._.js +1 -1
  178. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_67178b88._.js +1 -1
  179. 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
  180. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  181. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_d5b0cb86._.js +2 -2
  182. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_e626b63c._.js +2 -2
  183. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  184. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  185. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +21 -21
  186. package/web/.next/standalone/src/presentation/web/app/layout.tsx +1 -1
  187. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.tsx +69 -115
  188. package/web/.next/standalone/src/presentation/web/components/common/control-center-drawer/use-artifact-fetch.ts +56 -0
  189. package/web/.next/standalone/src/presentation/web/components/common/feature-drawer/use-feature-actions.ts +6 -7
  190. package/web/.next/standalone/src/presentation/web/components/common/repository-node/use-repository-actions.ts +29 -54
  191. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +7 -7
  192. package/web/.next/standalone/src/presentation/web/components/features/control-center/use-control-center-state.ts +25 -8
  193. package/web/.next/standalone/src/presentation/web/hooks/agent-events-provider.tsx +7 -2
  194. package/web/.next/standalone/src/presentation/web/hooks/use-agent-events.ts +12 -2
  195. package/web/.next/standalone/src/presentation/web/hooks/use-deploy-action.ts +11 -3
  196. package/web/.next/standalone/src/presentation/web/lib/logger.ts +12 -0
  197. package/web/.next/static/chunks/15ceb197ec725da2.js +1 -0
  198. package/web/.next/static/chunks/{792aac476adb5875.js → 6691ad02dc1efd1f.js} +1 -1
  199. package/web/.next/static/chunks/7bff5fbcbf157df5.js +1 -0
  200. package/web/.next/static/chunks/{d5e95c4482029a39.js → 8959112899679e25.js} +1 -1
  201. package/web/.next/static/chunks/{9b1770f05f1d95e5.js → 8f508ef906cbf0fa.js} +1 -1
  202. package/web/.next/static/chunks/{83463b63971f5723.js → 9cbb42a6bd516d3b.js} +1 -1
  203. package/web/.next/static/chunks/c5603ed8a95d460d.js +1 -0
  204. package/web/.next/static/chunks/{a7f52757dff0cea0.js → d7e0cd32364a8d76.js} +1 -1
  205. package/web/.next/static/chunks/{98ca891a5c156aa5.js → e69d79bd3f19dec8.js} +3 -3
  206. package/web/.next/trace +1 -1
  207. package/web/.next/trace-build +1 -1
  208. package/web/.next/static/chunks/76827713cb2b7333.js +0 -1
  209. package/web/.next/static/chunks/7c8adcfd920a8f74.js +0 -1
  210. package/web/.next/static/chunks/b6becf0de992842a.js +0 -1
  211. /package/web/.next/static/{eg0FTlsA4z_7CXbFfMgAr → 1AvkeBASQdtzZt9cm9UYg}/_buildManifest.js +0 -0
  212. /package/web/.next/static/{eg0FTlsA4z_7CXbFfMgAr → 1AvkeBASQdtzZt9cm9UYg}/_clientMiddlewareManifest.json +0 -0
  213. /package/web/.next/static/{eg0FTlsA4z_7CXbFfMgAr → 1AvkeBASQdtzZt9cm9UYg}/_ssgManifest.js +0 -0
@@ -56,6 +56,7 @@ import type { MergeReviewData } from '@/components/common/merge-review';
56
56
  import { resolveSseEventUpdates } from '@/components/common/feature-node/derive-feature-state';
57
57
  import { deriveFeatureViewType } from './drawer-view';
58
58
  import type { DrawerView } from './drawer-view';
59
+ import { useArtifactFetch } from './use-artifact-fetch';
59
60
 
60
61
  export interface FeatureDrawerClientProps {
61
62
  view: DrawerView;
@@ -87,6 +88,10 @@ export function FeatureDrawerClient({ view: initialView }: FeatureDrawerClientPr
87
88
  const processedCountRef = useRef(0);
88
89
 
89
90
  useEffect(() => {
91
+ // Clamp cursor if events were pruned
92
+ if (processedCountRef.current > events.length) {
93
+ processedCountRef.current = 0;
94
+ }
90
95
  if (!featureNode || events.length <= processedCountRef.current) return;
91
96
 
92
97
  const newEvents = events.slice(processedCountRef.current);
@@ -96,9 +101,9 @@ export function FeatureDrawerClient({ view: initialView }: FeatureDrawerClientPr
96
101
  if (update.featureId !== featureNode.featureId) continue;
97
102
 
98
103
  // Trigger a server refresh to get the latest drawer view,
99
- // but skip when the user has unsaved changes or a rejection is in-flight
100
- // to avoid the refresh interfering with the reject handler's navigation.
101
- if (!isDirtyRef.current && !isRejectingRef.current) router.refresh();
104
+ // but skip when the drawer is closed, the user has unsaved changes,
105
+ // or a rejection is in-flight to avoid unnecessary refreshes.
106
+ if (isOpenRef.current && !isDirtyRef.current && !isRejectingRef.current) router.refresh();
102
107
 
103
108
  if (update.state !== undefined || update.lifecycle !== undefined) {
104
109
  // Optimistically update the node data AND re-derive the view type so the
@@ -129,6 +134,8 @@ export function FeatureDrawerClient({ view: initialView }: FeatureDrawerClientPr
129
134
  // the close animation when the path no longer matches a feature route.
130
135
  const pathname = usePathname();
131
136
  const isOpen = pathname.startsWith('/feature/');
137
+ const isOpenRef = useRef(isOpen);
138
+ isOpenRef.current = isOpen;
132
139
 
133
140
  const onClose = useCallback(() => {
134
141
  router.push('/');
@@ -141,21 +148,17 @@ export function FeatureDrawerClient({ view: initialView }: FeatureDrawerClientPr
141
148
  const [prdData, setPrdData] = useState<PrdQuestionnaireData | null>(null);
142
149
  const [prdSelections, setPrdSelections] = useState<Record<string, string>>({});
143
150
  const [prdDefaultSelections, setPrdDefaultSelections] = useState<Record<string, string>>({});
144
- const [isLoadingPrd, setIsLoadingPrd] = useState(false);
145
151
 
146
152
  // ── Tech state ─────────────────────────────────────────────────────────
147
153
  const [techData, setTechData] = useState<TechDecisionsReviewData | null>(null);
148
- const [isLoadingTech, setIsLoadingTech] = useState(false);
149
154
 
150
155
  // ── Product decisions state (for tech review Product tab) ─────────────
151
156
  const [techProductData, setTechProductData] = useState<
152
157
  ProductDecisionsSummaryData | null | undefined
153
158
  >(undefined);
154
- const [isLoadingTechProduct, setIsLoadingTechProduct] = useState(false);
155
159
 
156
160
  // ── Merge state ────────────────────────────────────────────────────────
157
161
  const [mergeData, setMergeData] = useState<MergeReviewData | null>(null);
158
- const [isLoadingMerge, setIsLoadingMerge] = useState(false);
159
162
 
160
163
  // ── Delete state ───────────────────────────────────────────────────────
161
164
  const [isDeleting, setIsDeleting] = useState(false);
@@ -173,120 +176,71 @@ export function FeatureDrawerClient({ view: initialView }: FeatureDrawerClientPr
173
176
  // ── Data fetching ─────────────────────────────────────────────────────
174
177
 
175
178
  const prdFeatureId = view.type === 'prd-review' ? view.node.featureId : null;
176
- useEffect(() => {
177
- setPrdSelections({});
178
- setPrdDefaultSelections({});
179
- setPrdData(null);
180
- if (!prdFeatureId) return;
181
-
182
- let cancelled = false;
183
- setIsLoadingPrd(true);
184
- getFeatureArtifact(prdFeatureId)
185
- .then((result) => {
186
- if (cancelled) return;
187
- if (result.error) {
188
- toast.error(result.error);
189
- return;
190
- }
191
- if (result.questionnaire) {
192
- setPrdData(result.questionnaire);
193
- const defaults: Record<string, string> = {};
194
- for (const q of result.questionnaire.questions) {
195
- const recommended = q.options.find((o) => o.recommended);
196
- if (recommended) defaults[q.id] = recommended.id;
197
- }
198
- setPrdSelections(defaults);
199
- setPrdDefaultSelections(defaults);
179
+ const isLoadingPrd = useArtifactFetch(
180
+ prdFeatureId,
181
+ getFeatureArtifact,
182
+ (result) => {
183
+ if (result.error) {
184
+ toast.error(result.error);
185
+ return;
186
+ }
187
+ if (result.questionnaire) {
188
+ setPrdData(result.questionnaire);
189
+ const defaults: Record<string, string> = {};
190
+ for (const q of result.questionnaire.questions) {
191
+ const recommended = q.options.find((o) => o.recommended);
192
+ if (recommended) defaults[q.id] = recommended.id;
200
193
  }
201
- })
202
- .catch(() => {
203
- if (!cancelled) toast.error('Failed to load questionnaire');
204
- })
205
- .finally(() => {
206
- if (!cancelled) setIsLoadingPrd(false);
207
- });
208
- return () => {
209
- cancelled = true;
210
- };
211
- }, [prdFeatureId]);
194
+ setPrdSelections(defaults);
195
+ setPrdDefaultSelections(defaults);
196
+ }
197
+ },
198
+ () => {
199
+ setPrdSelections({});
200
+ setPrdDefaultSelections({});
201
+ setPrdData(null);
202
+ },
203
+ 'Failed to load questionnaire'
204
+ );
212
205
 
213
206
  const techFeatureId = view.type === 'tech-review' ? view.node.featureId : null;
214
- useEffect(() => {
215
- setTechData(null);
216
- if (!techFeatureId) return;
217
-
218
- let cancelled = false;
219
- setIsLoadingTech(true);
220
- getResearchArtifact(techFeatureId)
221
- .then((result) => {
222
- if (cancelled) return;
223
- if (result.error) {
224
- toast.error(result.error);
225
- return;
226
- }
227
- if (result.techDecisions) setTechData(result.techDecisions);
228
- })
229
- .catch(() => {
230
- if (!cancelled) toast.error('Failed to load tech decisions');
231
- })
232
- .finally(() => {
233
- if (!cancelled) setIsLoadingTech(false);
234
- });
235
- return () => {
236
- cancelled = true;
237
- };
238
- }, [techFeatureId]);
207
+ const isLoadingTech = useArtifactFetch(
208
+ techFeatureId,
209
+ getResearchArtifact,
210
+ (result) => {
211
+ if (result.error) {
212
+ toast.error(result.error);
213
+ return;
214
+ }
215
+ if (result.techDecisions) setTechData(result.techDecisions);
216
+ },
217
+ () => setTechData(null),
218
+ 'Failed to load tech decisions'
219
+ );
239
220
 
240
- useEffect(() => {
241
- setTechProductData(undefined);
242
- if (!techFeatureId) return;
243
-
244
- let cancelled = false;
245
- setIsLoadingTechProduct(true);
246
- getFeatureArtifact(techFeatureId)
247
- .then((result) => {
248
- if (cancelled) return;
249
- if (result.productDecisions) {
250
- setTechProductData(result.productDecisions);
251
- }
252
- })
253
- .catch(() => {
254
- // Silent failure — the product tab is supplementary
255
- })
256
- .finally(() => {
257
- if (!cancelled) setIsLoadingTechProduct(false);
258
- });
259
- return () => {
260
- cancelled = true;
261
- };
262
- }, [techFeatureId]);
221
+ const isLoadingTechProduct = useArtifactFetch(
222
+ techFeatureId,
223
+ getFeatureArtifact,
224
+ (result) => {
225
+ if (result.productDecisions) setTechProductData(result.productDecisions);
226
+ },
227
+ () => setTechProductData(undefined)
228
+ );
263
229
 
264
230
  const mergeFeatureId = view.type === 'merge-review' ? view.node.featureId : null;
265
- useEffect(() => {
266
- setMergeData(null);
267
- if (!mergeFeatureId) return;
268
-
269
- let cancelled = false;
270
- setIsLoadingMerge(true);
271
- getMergeReviewData(mergeFeatureId)
272
- .then((result) => {
273
- if (cancelled) return;
274
- if ('error' in result) {
275
- toast.error(result.error);
276
- return;
277
- }
278
- setMergeData(result);
279
- })
280
- .catch(() => {
281
- if (!cancelled) toast.error('Failed to load merge review data');
282
- })
283
- .finally(() => {
284
- if (!cancelled) setIsLoadingMerge(false);
285
- });
286
- return () => {
287
- cancelled = true;
288
- };
289
- }, [mergeFeatureId]);
231
+ const isLoadingMerge = useArtifactFetch(
232
+ mergeFeatureId,
233
+ getMergeReviewData,
234
+ (result) => {
235
+ if ('error' in result) {
236
+ toast.error(result.error);
237
+ return;
238
+ }
239
+ setMergeData(result);
240
+ },
241
+ () => setMergeData(null),
242
+ 'Failed to load merge review data'
243
+ );
290
244
 
291
245
  // ── Close guard ──────────────────────────────────────────────────────
292
246
  const isChatDirty = chatInput.trim().length > 0;
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useRef } from 'react';
4
+ import { toast } from 'sonner';
5
+
6
+ /**
7
+ * Generic hook for fetching drawer artifact data when a feature ID changes.
8
+ * Handles loading state, cancellation on unmount/re-fetch, and error toasting.
9
+ *
10
+ * @param featureId - The feature ID to fetch for, or null to reset.
11
+ * @param fetcher - Async function that fetches the artifact data.
12
+ * @param onSuccess - Called with the fetch result when successful and not cancelled.
13
+ * @param onReset - Called immediately when featureId changes (before fetching).
14
+ * @param errorMessage - Optional toast message shown on fetch failure.
15
+ * @returns Whether the fetch is currently in progress.
16
+ */
17
+ export function useArtifactFetch<TResult>(
18
+ featureId: string | null,
19
+ fetcher: (id: string) => Promise<TResult>,
20
+ onSuccess: (result: TResult) => void,
21
+ onReset: () => void,
22
+ errorMessage?: string
23
+ ): boolean {
24
+ const [isLoading, setIsLoading] = useState(false);
25
+
26
+ // Use refs to avoid stale callbacks in the effect without triggering re-runs.
27
+ const onSuccessRef = useRef(onSuccess);
28
+ const onResetRef = useRef(onReset);
29
+ onSuccessRef.current = onSuccess;
30
+ onResetRef.current = onReset;
31
+
32
+ useEffect(() => {
33
+ onResetRef.current();
34
+ if (!featureId) return;
35
+
36
+ let cancelled = false;
37
+ setIsLoading(true);
38
+
39
+ fetcher(featureId)
40
+ .then((result) => {
41
+ if (!cancelled) onSuccessRef.current(result);
42
+ })
43
+ .catch(() => {
44
+ if (!cancelled && errorMessage) toast.error(errorMessage);
45
+ })
46
+ .finally(() => {
47
+ if (!cancelled) setIsLoading(false);
48
+ });
49
+
50
+ return () => {
51
+ cancelled = true;
52
+ };
53
+ }, [featureId, fetcher, errorMessage]);
54
+
55
+ return isLoading;
56
+ }
@@ -37,15 +37,14 @@ export function useFeatureActions(input: FeatureActionsInput | null): FeatureAct
37
37
  const shellTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
38
38
  const specsTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
39
39
 
40
- // Clear timers on unmount
40
+ // Clear timers on unmount — read .current inside cleanup so we get the
41
+ // actual timer value at teardown time, not the always-null value at mount.
41
42
  useEffect(() => {
42
- const ideTimer = ideTimerRef.current;
43
- const shellTimer = shellTimerRef.current;
44
- const specsTimer = specsTimerRef.current;
43
+ const refs = [ideTimerRef, shellTimerRef, specsTimerRef];
45
44
  return () => {
46
- if (ideTimer) clearTimeout(ideTimer);
47
- if (shellTimer) clearTimeout(shellTimer);
48
- if (specsTimer) clearTimeout(specsTimer);
45
+ for (const ref of refs) {
46
+ if (ref.current) clearTimeout(ref.current);
47
+ }
49
48
  };
50
49
  }, []);
51
50
 
@@ -37,22 +37,19 @@ export function useRepositoryActions(input: RepositoryActionsInput | null): Repo
37
37
 
38
38
  // Clear timers on unmount
39
39
  useEffect(() => {
40
- const ideTimer = ideTimerRef.current;
41
- const shellTimer = shellTimerRef.current;
42
- const folderTimer = folderTimerRef.current;
40
+ const ideRef = ideTimerRef;
41
+ const shellRef = shellTimerRef;
42
+ const folderRef = folderTimerRef;
43
43
  return () => {
44
- if (ideTimer) clearTimeout(ideTimer);
45
- if (shellTimer) clearTimeout(shellTimer);
46
- if (folderTimer) clearTimeout(folderTimer);
44
+ if (ideRef.current) clearTimeout(ideRef.current);
45
+ if (shellRef.current) clearTimeout(shellRef.current);
46
+ if (folderRef.current) clearTimeout(folderRef.current);
47
47
  };
48
48
  }, []);
49
49
 
50
- const performToolbarAction = useCallback(
50
+ const performAction = useCallback(
51
51
  async (
52
- action: (input: { repositoryPath: string }) => Promise<{
53
- success: boolean;
54
- error?: string;
55
- }>,
52
+ action: () => Promise<{ success: boolean; error?: string }>,
56
53
  setLoading: (v: boolean) => void,
57
54
  setError: (v: string | null) => void,
58
55
  timerRef: React.RefObject<ReturnType<typeof setTimeout> | null>,
@@ -60,48 +57,13 @@ export function useRepositoryActions(input: RepositoryActionsInput | null): Repo
60
57
  ) => {
61
58
  if (!input || isLoading) return;
62
59
 
63
- // Clear any existing timer
64
60
  if (timerRef.current) clearTimeout(timerRef.current);
65
61
 
66
62
  setLoading(true);
67
63
  setError(null);
68
64
 
69
65
  try {
70
- const result = await action({ repositoryPath: input.repositoryPath });
71
-
72
- if (!result.success) {
73
- const errorMessage = result.error ?? 'An unexpected error occurred';
74
- setError(errorMessage);
75
- timerRef.current = setTimeout(() => setError(null), ERROR_CLEAR_DELAY);
76
- }
77
- } catch (err: unknown) {
78
- const errorMessage = err instanceof Error ? err.message : 'An unexpected error occurred';
79
- setError(errorMessage);
80
- timerRef.current = setTimeout(() => setError(null), ERROR_CLEAR_DELAY);
81
- } finally {
82
- setLoading(false);
83
- }
84
- },
85
- [input]
86
- );
87
-
88
- const performFolderAction = useCallback(
89
- async (
90
- action: (repositoryPath: string) => Promise<{ success: boolean; error?: string }>,
91
- setLoading: (v: boolean) => void,
92
- setError: (v: string | null) => void,
93
- timerRef: React.RefObject<ReturnType<typeof setTimeout> | null>,
94
- isLoading: boolean
95
- ) => {
96
- if (!input || isLoading) return;
97
-
98
- if (timerRef.current) clearTimeout(timerRef.current);
99
-
100
- setLoading(true);
101
- setError(null);
102
-
103
- try {
104
- const result = await action(input.repositoryPath);
66
+ const result = await action();
105
67
 
106
68
  if (!result.success) {
107
69
  const errorMessage = result.error ?? 'An unexpected error occurred';
@@ -120,26 +82,39 @@ export function useRepositoryActions(input: RepositoryActionsInput | null): Repo
120
82
  );
121
83
 
122
84
  const handleOpenIde = useCallback(
123
- () => performToolbarAction(openIde, setIdeLoading, setIdeError, ideTimerRef, ideLoading),
124
- [performToolbarAction, ideLoading]
85
+ () =>
86
+ performAction(
87
+ () => openIde({ repositoryPath: input!.repositoryPath }),
88
+ setIdeLoading,
89
+ setIdeError,
90
+ ideTimerRef,
91
+ ideLoading
92
+ ),
93
+ [performAction, ideLoading, input]
125
94
  );
126
95
 
127
96
  const handleOpenShell = useCallback(
128
97
  () =>
129
- performToolbarAction(openShell, setShellLoading, setShellError, shellTimerRef, shellLoading),
130
- [performToolbarAction, shellLoading]
98
+ performAction(
99
+ () => openShell({ repositoryPath: input!.repositoryPath }),
100
+ setShellLoading,
101
+ setShellError,
102
+ shellTimerRef,
103
+ shellLoading
104
+ ),
105
+ [performAction, shellLoading, input]
131
106
  );
132
107
 
133
108
  const handleOpenFolder = useCallback(
134
109
  () =>
135
- performFolderAction(
136
- openFolder,
110
+ performAction(
111
+ () => openFolder(input!.repositoryPath),
137
112
  setFolderLoading,
138
113
  setFolderError,
139
114
  folderTimerRef,
140
115
  folderLoading
141
116
  ),
142
- [performFolderAction, folderLoading]
117
+ [performAction, folderLoading, input]
143
118
  );
144
119
 
145
120
  return {
@@ -44,20 +44,20 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
44
44
  // Publish sidebar features to context whenever feature node data changes
45
45
  const { setFeatures: setSidebarFeatures } = useSidebarFeaturesContext();
46
46
 
47
+ const featureNodes = useMemo(() => nodes.filter((n) => n.type === 'featureNode'), [nodes]);
48
+
47
49
  const sidebarKey = useMemo(() => {
48
- return nodes
49
- .filter((n) => n.type === 'featureNode')
50
+ return featureNodes
50
51
  .map((n) => {
51
52
  const d = n.data as FeatureNodeData;
52
- return `${d.featureId}:${d.state}:${d.runtime ?? ''}:${d.startedAt ?? ''}`;
53
+ return `${d.featureId}:${d.state}:${d.name}`;
53
54
  })
54
55
  .sort()
55
56
  .join(',');
56
- }, [nodes]);
57
+ }, [featureNodes]);
57
58
 
58
59
  useEffect(() => {
59
- const sidebarItems = nodes
60
- .filter((n) => n.type === 'featureNode')
60
+ const sidebarItems = featureNodes
61
61
  .map((n) => {
62
62
  const d = n.data as FeatureNodeData;
63
63
  const status = mapNodeStateToSidebarStatus(d.state);
@@ -79,7 +79,7 @@ export function ControlCenterInner({ initialNodes, initialEdges }: ControlCenter
79
79
  }[];
80
80
 
81
81
  setSidebarFeatures(sidebarItems);
82
- }, [sidebarKey, nodes, setSidebarFeatures]);
82
+ }, [sidebarKey, featureNodes, setSidebarFeatures]);
83
83
 
84
84
  // ── URL-based navigation handlers ────────────────────────────────────
85
85
 
@@ -76,6 +76,14 @@ export function useControlCenterState(
76
76
  setCallbacks,
77
77
  } = useGraphState(initialNodes, initialEdges);
78
78
 
79
+ // Refs for stable access to latest nodes/edges without callback recreation
80
+ const nodesRef = useRef(nodes);
81
+ const edgesRef = useRef(edges);
82
+ useEffect(() => {
83
+ nodesRef.current = nodes;
84
+ edgesRef.current = edges;
85
+ }, [nodes, edges]);
86
+
79
87
  // Sync server props into domain Maps when initialNodes/initialEdges change.
80
88
  // Keyed by a stable string so we don't re-reconcile on every render.
81
89
  const initialNodeKey = useMemo(
@@ -148,6 +156,10 @@ export function useControlCenterState(
148
156
  const processedEventCountRef = useRef(0);
149
157
 
150
158
  useEffect(() => {
159
+ // Clamp cursor if events were pruned
160
+ if (processedEventCountRef.current > events.length) {
161
+ processedEventCountRef.current = 0;
162
+ }
151
163
  if (events.length <= processedEventCountRef.current) return;
152
164
  const newEvents = events.slice(processedEventCountRef.current);
153
165
  processedEventCountRef.current = events.length;
@@ -177,6 +189,10 @@ export function useControlCenterState(
177
189
  const processedMetadataCountRef = useRef(0);
178
190
 
179
191
  useEffect(() => {
192
+ // Clamp cursor if events were pruned
193
+ if (processedMetadataCountRef.current > events.length) {
194
+ processedMetadataCountRef.current = 0;
195
+ }
180
196
  if (events.length <= processedMetadataCountRef.current) return;
181
197
  const newEvents = events.slice(processedMetadataCountRef.current);
182
198
  processedMetadataCountRef.current = events.length;
@@ -245,7 +261,7 @@ export function useControlCenterState(
245
261
  const nodeId = `feat-${featureId}`;
246
262
 
247
263
  // Find the current entry for rollback
248
- const prevEntry = nodes
264
+ const prevEntry = nodesRef.current
249
265
  .filter((n) => n.id === nodeId)
250
266
  .map((n) => ({
251
267
  nodeId: n.id,
@@ -270,7 +286,7 @@ export function useControlCenterState(
270
286
  toast.error('Failed to delete feature');
271
287
  });
272
288
  },
273
- [nodes, router, deleteSound, removeFeature, restoreFeature]
289
+ [router, deleteSound, removeFeature, restoreFeature]
274
290
  );
275
291
 
276
292
  const handleDeleteRepository = useCallback(
@@ -279,14 +295,14 @@ export function useControlCenterState(
279
295
 
280
296
  // Find children of this repo via edges
281
297
  const childFeatureIds = new Set(
282
- edges.filter((e) => e.source === repoNodeId).map((e) => e.target)
298
+ edgesRef.current.filter((e) => e.source === repoNodeId).map((e) => e.target)
283
299
  );
284
300
 
285
301
  // Snapshot for rollback
286
302
  const prevRepoData = getRepositoryData(repoNodeId);
287
303
  const childSnapshots = new Map<string, FeatureEntry>();
288
304
  for (const childId of childFeatureIds) {
289
- const childNode = nodes.find((n) => n.id === childId);
305
+ const childNode = nodesRef.current.find((n) => n.id === childId);
290
306
  if (childNode) {
291
307
  childSnapshots.set(childId, { nodeId: childId, data: childNode.data as FeatureNodeData });
292
308
  }
@@ -320,8 +336,6 @@ export function useControlCenterState(
320
336
  }
321
337
  },
322
338
  [
323
- edges,
324
- nodes,
325
339
  deleteSound,
326
340
  removeRepository,
327
341
  removeFeature,
@@ -335,10 +349,13 @@ export function useControlCenterState(
335
349
  (direction: LayoutDirection) => {
336
350
  // Layout is applied via reconcile on next server prop update.
337
351
  // For immediate re-layout, we apply dagre and trigger a reconcile-like update.
338
- const result = layoutWithDagre(nodes, edges, { ...CANVAS_LAYOUT_DEFAULTS, direction });
352
+ const result = layoutWithDagre(nodesRef.current, edgesRef.current, {
353
+ ...CANVAS_LAYOUT_DEFAULTS,
354
+ direction,
355
+ });
339
356
  reconcile(result.nodes, result.edges);
340
357
  },
341
- [nodes, edges, reconcile]
358
+ [reconcile]
342
359
  );
343
360
 
344
361
  const handleAddRepository = useCallback(
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { createContext, useContext, type ReactNode } from 'react';
3
+ import { createContext, useContext, useMemo, type ReactNode } from 'react';
4
4
  import {
5
5
  useAgentEvents,
6
6
  type UseAgentEventsOptions,
@@ -18,7 +18,12 @@ interface AgentEventsProviderProps extends UseAgentEventsOptions {
18
18
  * Wrap the app once; use `useAgentEventsContext()` to read.
19
19
  */
20
20
  export function AgentEventsProvider({ children, runId }: AgentEventsProviderProps) {
21
- const value = useAgentEvents({ runId });
21
+ const { events, lastEvent, connectionStatus } = useAgentEvents({ runId });
22
+
23
+ const value = useMemo<UseAgentEventsResult>(
24
+ () => ({ events, lastEvent, connectionStatus }),
25
+ [events, lastEvent, connectionStatus]
26
+ );
22
27
 
23
28
  return <AgentEventsContext.Provider value={value}>{children}</AgentEventsContext.Provider>;
24
29
  }
@@ -16,6 +16,8 @@ export interface UseAgentEventsResult {
16
16
  }
17
17
 
18
18
  const SW_PATH = '/agent-events-sw.js';
19
+ const MAX_EVENTS = 500;
20
+ const PRUNE_KEEP = 250;
19
21
 
20
22
  /**
21
23
  * Hook that receives real-time agent notification events via a Service Worker.
@@ -38,7 +40,12 @@ export function useAgentEvents(options?: UseAgentEventsOptions): UseAgentEventsR
38
40
 
39
41
  if (msg.type === 'notification') {
40
42
  const parsed = msg.data as NotificationEvent;
41
- setEvents((prev) => [...prev, parsed]);
43
+ // eslint-disable-next-line no-console
44
+ console.log('[SSE] event received:', parsed.eventType, parsed);
45
+ setEvents((prev) => {
46
+ const next = [...prev, parsed];
47
+ return next.length > MAX_EVENTS ? next.slice(-PRUNE_KEEP) : next;
48
+ });
42
49
  setLastEvent(parsed);
43
50
  } else if (msg.type === 'status') {
44
51
  setConnectionStatus(msg.status as ConnectionStatus);
@@ -197,7 +204,10 @@ function connectDirectEventSource(
197
204
 
198
205
  es.addEventListener('notification', ((event: MessageEvent) => {
199
206
  const parsed: NotificationEvent = JSON.parse(event.data);
200
- setEvents((prev) => [...prev, parsed]);
207
+ setEvents((prev) => {
208
+ const next = [...prev, parsed];
209
+ return next.length > MAX_EVENTS ? next.slice(-PRUNE_KEEP) : next;
210
+ });
201
211
  setLastEvent(parsed);
202
212
  }) as EventListener);
203
213
  }
@@ -48,6 +48,12 @@ export function useDeployAction(input: DeployActionInput | null): DeployActionSt
48
48
  const errorTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
49
49
  const pollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
50
50
  const mountedRef = useRef(true);
51
+ const statusRef = useRef(status);
52
+
53
+ // Keep statusRef in sync with latest status
54
+ useEffect(() => {
55
+ statusRef.current = status;
56
+ }, [status]);
51
57
 
52
58
  // Track mounted state
53
59
  useEffect(() => {
@@ -96,15 +102,17 @@ export function useDeployAction(input: DeployActionInput | null): DeployActionSt
96
102
  setUrl(null);
97
103
  stopPolling();
98
104
  } else {
99
- if (result.state !== status) {
100
- log.info(`poll state changed: ${status} → ${result.state}, url=${result.url}`);
105
+ if (result.state !== statusRef.current) {
106
+ log.info(
107
+ `poll state changed: ${statusRef.current} → ${result.state}, url=${result.url}`
108
+ );
101
109
  }
102
110
  setStatus(result.state as DeploymentState);
103
111
  setUrl(result.url);
104
112
  }
105
113
  }, POLL_INTERVAL);
106
114
  },
107
- [stopPolling, status]
115
+ [stopPolling]
108
116
  );
109
117
 
110
118
  const handleDeploy = useCallback(async () => {