@a5c-ai/babysitter-observer-dashboard 5.0.1-staging.7a8768ec

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 (513) hide show
  1. package/.next/BUILD_ID +1 -0
  2. package/.next/app-path-routes-manifest.json +16 -0
  3. package/.next/build-manifest.json +23 -0
  4. package/.next/export-marker.json +6 -0
  5. package/.next/fallback-build-manifest.json +13 -0
  6. package/.next/images-manifest.json +68 -0
  7. package/.next/next-minimal-server.js.nft.json +1 -0
  8. package/.next/next-server.js.nft.json +1 -0
  9. package/.next/package.json +1 -0
  10. package/.next/prerender-manifest.json +114 -0
  11. package/.next/required-server-files.json +334 -0
  12. package/.next/routes-manifest.json +139 -0
  13. package/.next/server/app/_global-error/page/app-paths-manifest.json +3 -0
  14. package/.next/server/app/_global-error/page/build-manifest.json +19 -0
  15. package/.next/server/app/_global-error/page/next-font-manifest.json +6 -0
  16. package/.next/server/app/_global-error/page/react-loadable-manifest.json +1 -0
  17. package/.next/server/app/_global-error/page/server-reference-manifest.json +4 -0
  18. package/.next/server/app/_global-error/page.js +9 -0
  19. package/.next/server/app/_global-error/page.js.map +5 -0
  20. package/.next/server/app/_global-error/page.js.nft.json +1 -0
  21. package/.next/server/app/_global-error/page_client-reference-manifest.js +3 -0
  22. package/.next/server/app/_global-error.html +1 -0
  23. package/.next/server/app/_global-error.meta +15 -0
  24. package/.next/server/app/_global-error.rsc +14 -0
  25. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +5 -0
  26. package/.next/server/app/_global-error.segments/_full.segment.rsc +14 -0
  27. package/.next/server/app/_global-error.segments/_head.segment.rsc +5 -0
  28. package/.next/server/app/_global-error.segments/_index.segment.rsc +5 -0
  29. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -0
  30. package/.next/server/app/_not-found/page/app-paths-manifest.json +3 -0
  31. package/.next/server/app/_not-found/page/build-manifest.json +19 -0
  32. package/.next/server/app/_not-found/page/next-font-manifest.json +6 -0
  33. package/.next/server/app/_not-found/page/react-loadable-manifest.json +1 -0
  34. package/.next/server/app/_not-found/page/server-reference-manifest.json +4 -0
  35. package/.next/server/app/_not-found/page.js +13 -0
  36. package/.next/server/app/_not-found/page.js.map +5 -0
  37. package/.next/server/app/_not-found/page.js.nft.json +1 -0
  38. package/.next/server/app/_not-found/page_client-reference-manifest.js +3 -0
  39. package/.next/server/app/_not-found.html +1 -0
  40. package/.next/server/app/_not-found.meta +16 -0
  41. package/.next/server/app/_not-found.rsc +19 -0
  42. package/.next/server/app/_not-found.segments/_full.segment.rsc +19 -0
  43. package/.next/server/app/_not-found.segments/_head.segment.rsc +6 -0
  44. package/.next/server/app/_not-found.segments/_index.segment.rsc +8 -0
  45. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +6 -0
  46. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +5 -0
  47. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -0
  48. package/.next/server/app/api/config/route/app-paths-manifest.json +3 -0
  49. package/.next/server/app/api/config/route/build-manifest.json +9 -0
  50. package/.next/server/app/api/config/route/server-reference-manifest.json +4 -0
  51. package/.next/server/app/api/config/route.js +8 -0
  52. package/.next/server/app/api/config/route.js.map +5 -0
  53. package/.next/server/app/api/config/route.js.nft.json +1 -0
  54. package/.next/server/app/api/config/route_client-reference-manifest.js +3 -0
  55. package/.next/server/app/api/digest/route/app-paths-manifest.json +3 -0
  56. package/.next/server/app/api/digest/route/build-manifest.json +9 -0
  57. package/.next/server/app/api/digest/route/server-reference-manifest.json +4 -0
  58. package/.next/server/app/api/digest/route.js +9 -0
  59. package/.next/server/app/api/digest/route.js.map +5 -0
  60. package/.next/server/app/api/digest/route.js.nft.json +1 -0
  61. package/.next/server/app/api/digest/route_client-reference-manifest.js +3 -0
  62. package/.next/server/app/api/runs/[runId]/events/route/app-paths-manifest.json +3 -0
  63. package/.next/server/app/api/runs/[runId]/events/route/build-manifest.json +9 -0
  64. package/.next/server/app/api/runs/[runId]/events/route/server-reference-manifest.json +4 -0
  65. package/.next/server/app/api/runs/[runId]/events/route.js +8 -0
  66. package/.next/server/app/api/runs/[runId]/events/route.js.map +5 -0
  67. package/.next/server/app/api/runs/[runId]/events/route.js.nft.json +1 -0
  68. package/.next/server/app/api/runs/[runId]/events/route_client-reference-manifest.js +3 -0
  69. package/.next/server/app/api/runs/[runId]/route/app-paths-manifest.json +3 -0
  70. package/.next/server/app/api/runs/[runId]/route/build-manifest.json +9 -0
  71. package/.next/server/app/api/runs/[runId]/route/server-reference-manifest.json +4 -0
  72. package/.next/server/app/api/runs/[runId]/route.js +9 -0
  73. package/.next/server/app/api/runs/[runId]/route.js.map +5 -0
  74. package/.next/server/app/api/runs/[runId]/route.js.nft.json +1 -0
  75. package/.next/server/app/api/runs/[runId]/route_client-reference-manifest.js +3 -0
  76. package/.next/server/app/api/runs/[runId]/tasks/[effectId]/route/app-paths-manifest.json +3 -0
  77. package/.next/server/app/api/runs/[runId]/tasks/[effectId]/route/build-manifest.json +9 -0
  78. package/.next/server/app/api/runs/[runId]/tasks/[effectId]/route/server-reference-manifest.json +4 -0
  79. package/.next/server/app/api/runs/[runId]/tasks/[effectId]/route.js +8 -0
  80. package/.next/server/app/api/runs/[runId]/tasks/[effectId]/route.js.map +5 -0
  81. package/.next/server/app/api/runs/[runId]/tasks/[effectId]/route.js.nft.json +1 -0
  82. package/.next/server/app/api/runs/[runId]/tasks/[effectId]/route_client-reference-manifest.js +3 -0
  83. package/.next/server/app/api/runs/route/app-paths-manifest.json +3 -0
  84. package/.next/server/app/api/runs/route/build-manifest.json +9 -0
  85. package/.next/server/app/api/runs/route/server-reference-manifest.json +4 -0
  86. package/.next/server/app/api/runs/route.js +9 -0
  87. package/.next/server/app/api/runs/route.js.map +5 -0
  88. package/.next/server/app/api/runs/route.js.nft.json +1 -0
  89. package/.next/server/app/api/runs/route_client-reference-manifest.js +3 -0
  90. package/.next/server/app/api/stream/route/app-paths-manifest.json +3 -0
  91. package/.next/server/app/api/stream/route/build-manifest.json +9 -0
  92. package/.next/server/app/api/stream/route/server-reference-manifest.json +4 -0
  93. package/.next/server/app/api/stream/route.js +8 -0
  94. package/.next/server/app/api/stream/route.js.map +5 -0
  95. package/.next/server/app/api/stream/route.js.nft.json +1 -0
  96. package/.next/server/app/api/stream/route_client-reference-manifest.js +3 -0
  97. package/.next/server/app/api/test/route/app-paths-manifest.json +3 -0
  98. package/.next/server/app/api/test/route/build-manifest.json +9 -0
  99. package/.next/server/app/api/test/route/server-reference-manifest.json +4 -0
  100. package/.next/server/app/api/test/route.js +6 -0
  101. package/.next/server/app/api/test/route.js.map +5 -0
  102. package/.next/server/app/api/test/route.js.nft.json +1 -0
  103. package/.next/server/app/api/test/route_client-reference-manifest.js +3 -0
  104. package/.next/server/app/api/version/route/app-paths-manifest.json +3 -0
  105. package/.next/server/app/api/version/route/build-manifest.json +9 -0
  106. package/.next/server/app/api/version/route/server-reference-manifest.json +4 -0
  107. package/.next/server/app/api/version/route.js +7 -0
  108. package/.next/server/app/api/version/route.js.map +5 -0
  109. package/.next/server/app/api/version/route.js.nft.json +1 -0
  110. package/.next/server/app/api/version/route_client-reference-manifest.js +3 -0
  111. package/.next/server/app/icon.svg/route/app-paths-manifest.json +3 -0
  112. package/.next/server/app/icon.svg/route/build-manifest.json +9 -0
  113. package/.next/server/app/icon.svg/route.js +7 -0
  114. package/.next/server/app/icon.svg/route.js.map +5 -0
  115. package/.next/server/app/icon.svg/route.js.nft.json +1 -0
  116. package/.next/server/app/icon.svg.body +20 -0
  117. package/.next/server/app/icon.svg.meta +1 -0
  118. package/.next/server/app/index.html +1 -0
  119. package/.next/server/app/index.meta +14 -0
  120. package/.next/server/app/index.rsc +21 -0
  121. package/.next/server/app/index.segments/__PAGE__.segment.rsc +9 -0
  122. package/.next/server/app/index.segments/_full.segment.rsc +21 -0
  123. package/.next/server/app/index.segments/_head.segment.rsc +6 -0
  124. package/.next/server/app/index.segments/_index.segment.rsc +8 -0
  125. package/.next/server/app/index.segments/_tree.segment.rsc +2 -0
  126. package/.next/server/app/page/app-paths-manifest.json +3 -0
  127. package/.next/server/app/page/build-manifest.json +19 -0
  128. package/.next/server/app/page/next-font-manifest.json +6 -0
  129. package/.next/server/app/page/react-loadable-manifest.json +1 -0
  130. package/.next/server/app/page/server-reference-manifest.json +17 -0
  131. package/.next/server/app/page.js +15 -0
  132. package/.next/server/app/page.js.map +5 -0
  133. package/.next/server/app/page.js.nft.json +1 -0
  134. package/.next/server/app/page_client-reference-manifest.js +3 -0
  135. package/.next/server/app/runs/[runId]/page/app-paths-manifest.json +3 -0
  136. package/.next/server/app/runs/[runId]/page/build-manifest.json +19 -0
  137. package/.next/server/app/runs/[runId]/page/next-font-manifest.json +6 -0
  138. package/.next/server/app/runs/[runId]/page/react-loadable-manifest.json +22 -0
  139. package/.next/server/app/runs/[runId]/page/server-reference-manifest.json +17 -0
  140. package/.next/server/app/runs/[runId]/page.js +15 -0
  141. package/.next/server/app/runs/[runId]/page.js.map +5 -0
  142. package/.next/server/app/runs/[runId]/page.js.nft.json +1 -0
  143. package/.next/server/app/runs/[runId]/page_client-reference-manifest.js +3 -0
  144. package/.next/server/app-paths-manifest.json +16 -0
  145. package/.next/server/chunks/01oi_server_app_api_runs_[runId]_tasks_[effectId]_route_actions_0r72yai.js +3 -0
  146. package/.next/server/chunks/01oi_server_app_api_runs_[runId]_tasks_[effectId]_route_actions_0r72yai.js.map +1 -0
  147. package/.next/server/chunks/0h.v__next-internal_server_app_api_runs_[runId]_events_route_actions_0~msldk.js +3 -0
  148. package/.next/server/chunks/0h.v__next-internal_server_app_api_runs_[runId]_events_route_actions_0~msldk.js.map +1 -0
  149. package/.next/server/chunks/0h.v__next-internal_server_app_api_runs_[runId]_route_actions_09iz0n6.js +3 -0
  150. package/.next/server/chunks/0h.v__next-internal_server_app_api_runs_[runId]_route_actions_09iz0n6.js.map +1 -0
  151. package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_config_route_actions_0~eypoa.js +3 -0
  152. package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_config_route_actions_0~eypoa.js.map +1 -0
  153. package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_digest_route_actions_04jj5zs.js +3 -0
  154. package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_digest_route_actions_04jj5zs.js.map +1 -0
  155. package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_runs_route_actions_0~-t-o4.js +3 -0
  156. package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_runs_route_actions_0~-t-o4.js.map +1 -0
  157. package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_stream_route_actions_0fkmv2_.js +3 -0
  158. package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_stream_route_actions_0fkmv2_.js.map +1 -0
  159. package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_test_route_actions_00ugava.js +3 -0
  160. package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_test_route_actions_00ugava.js.map +1 -0
  161. package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_version_route_actions_0~v3ojm.js +3 -0
  162. package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_api_version_route_actions_0~v3ojm.js.map +1 -0
  163. package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_icon_svg_route_actions_0yypxkm.js +3 -0
  164. package/.next/server/chunks/0juq_observer-dashboard__next-internal_server_app_icon_svg_route_actions_0yypxkm.js.map +1 -0
  165. package/.next/server/chunks/[root-of-the-server]__0.6bt.6._.js +3 -0
  166. package/.next/server/chunks/[root-of-the-server]__0.6bt.6._.js.map +1 -0
  167. package/.next/server/chunks/[root-of-the-server]__08kwev1._.js +3 -0
  168. package/.next/server/chunks/[root-of-the-server]__08kwev1._.js.map +1 -0
  169. package/.next/server/chunks/[root-of-the-server]__096el.d._.js +3 -0
  170. package/.next/server/chunks/[root-of-the-server]__096el.d._.js.map +1 -0
  171. package/.next/server/chunks/[root-of-the-server]__0_bmt4z._.js +3 -0
  172. package/.next/server/chunks/[root-of-the-server]__0_bmt4z._.js.map +1 -0
  173. package/.next/server/chunks/[root-of-the-server]__0_ln2d2._.js +3 -0
  174. package/.next/server/chunks/[root-of-the-server]__0_ln2d2._.js.map +1 -0
  175. package/.next/server/chunks/[root-of-the-server]__0al3v65._.js +3 -0
  176. package/.next/server/chunks/[root-of-the-server]__0al3v65._.js.map +1 -0
  177. package/.next/server/chunks/[root-of-the-server]__0gf516b._.js +3 -0
  178. package/.next/server/chunks/[root-of-the-server]__0gf516b._.js.map +1 -0
  179. package/.next/server/chunks/[root-of-the-server]__0kdfw4x._.js +3 -0
  180. package/.next/server/chunks/[root-of-the-server]__0kdfw4x._.js.map +1 -0
  181. package/.next/server/chunks/[root-of-the-server]__0pxt00h._.js +3 -0
  182. package/.next/server/chunks/[root-of-the-server]__0pxt00h._.js.map +1 -0
  183. package/.next/server/chunks/[root-of-the-server]__0rubnza._.js +3 -0
  184. package/.next/server/chunks/[root-of-the-server]__0rubnza._.js.map +1 -0
  185. package/.next/server/chunks/[root-of-the-server]__0tn9iud._.js +3 -0
  186. package/.next/server/chunks/[root-of-the-server]__0tn9iud._.js.map +1 -0
  187. package/.next/server/chunks/[root-of-the-server]__0ws5o6i._.js +3 -0
  188. package/.next/server/chunks/[root-of-the-server]__0ws5o6i._.js.map +1 -0
  189. package/.next/server/chunks/[turbopack]_runtime.js +903 -0
  190. package/.next/server/chunks/[turbopack]_runtime.js.map +11 -0
  191. package/.next/server/chunks/node_modules_next_04~_e52._.js +13 -0
  192. package/.next/server/chunks/node_modules_next_04~_e52._.js.map +1 -0
  193. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0nyyph-.js +9 -0
  194. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0nyyph-.js.map +1 -0
  195. package/.next/server/chunks/packages_observer-dashboard_src_lib_02.vvb.._.js +3 -0
  196. package/.next/server/chunks/packages_observer-dashboard_src_lib_02.vvb.._.js.map +1 -0
  197. package/.next/server/chunks/packages_observer-dashboard_src_lib_0rqgpk0._.js +3 -0
  198. package/.next/server/chunks/packages_observer-dashboard_src_lib_0rqgpk0._.js.map +1 -0
  199. package/.next/server/chunks/ssr/0juq_observer-dashboard__next-internal_server_app__global-error_page_actions_0ekoxmy.js +3 -0
  200. package/.next/server/chunks/ssr/0juq_observer-dashboard__next-internal_server_app__global-error_page_actions_0ekoxmy.js.map +1 -0
  201. package/.next/server/chunks/ssr/0juq_observer-dashboard__next-internal_server_app__not-found_page_actions_09b3ti3.js +3 -0
  202. package/.next/server/chunks/ssr/0juq_observer-dashboard__next-internal_server_app__not-found_page_actions_09b3ti3.js.map +1 -0
  203. package/.next/server/chunks/ssr/[root-of-the-server]__006f7~t._.js +4 -0
  204. package/.next/server/chunks/ssr/[root-of-the-server]__006f7~t._.js.map +1 -0
  205. package/.next/server/chunks/ssr/[root-of-the-server]__02jgvbi._.js +3 -0
  206. package/.next/server/chunks/ssr/[root-of-the-server]__02jgvbi._.js.map +1 -0
  207. package/.next/server/chunks/ssr/[root-of-the-server]__04j8t~1._.js +3 -0
  208. package/.next/server/chunks/ssr/[root-of-the-server]__04j8t~1._.js.map +1 -0
  209. package/.next/server/chunks/ssr/[root-of-the-server]__09c~s.0._.js +33 -0
  210. package/.next/server/chunks/ssr/[root-of-the-server]__09c~s.0._.js.map +1 -0
  211. package/.next/server/chunks/ssr/[root-of-the-server]__0cpy61n._.js +3 -0
  212. package/.next/server/chunks/ssr/[root-of-the-server]__0cpy61n._.js.map +1 -0
  213. package/.next/server/chunks/ssr/[root-of-the-server]__0cwa32j._.js +3 -0
  214. package/.next/server/chunks/ssr/[root-of-the-server]__0cwa32j._.js.map +1 -0
  215. package/.next/server/chunks/ssr/[root-of-the-server]__0fk_g0j._.js +19 -0
  216. package/.next/server/chunks/ssr/[root-of-the-server]__0fk_g0j._.js.map +1 -0
  217. package/.next/server/chunks/ssr/[root-of-the-server]__0pddpic._.js +3 -0
  218. package/.next/server/chunks/ssr/[root-of-the-server]__0pddpic._.js.map +1 -0
  219. package/.next/server/chunks/ssr/[root-of-the-server]__0pvtneq._.js +3 -0
  220. package/.next/server/chunks/ssr/[root-of-the-server]__0pvtneq._.js.map +1 -0
  221. package/.next/server/chunks/ssr/[root-of-the-server]__0xc-2vm._.js +3 -0
  222. package/.next/server/chunks/ssr/[root-of-the-server]__0xc-2vm._.js.map +1 -0
  223. package/.next/server/chunks/ssr/[root-of-the-server]__10xgshr._.js +33 -0
  224. package/.next/server/chunks/ssr/[root-of-the-server]__10xgshr._.js.map +1 -0
  225. package/.next/server/chunks/ssr/[turbopack]_runtime.js +903 -0
  226. package/.next/server/chunks/ssr/[turbopack]_runtime.js.map +11 -0
  227. package/.next/server/chunks/ssr/_00yo1im._.js +3 -0
  228. package/.next/server/chunks/ssr/_00yo1im._.js.map +1 -0
  229. package/.next/server/chunks/ssr/_03sbc.o._.js +3 -0
  230. package/.next/server/chunks/ssr/_03sbc.o._.js.map +1 -0
  231. package/.next/server/chunks/ssr/_04z5ea0._.js +3 -0
  232. package/.next/server/chunks/ssr/_04z5ea0._.js.map +1 -0
  233. package/.next/server/chunks/ssr/_0gmb3g_._.js +3 -0
  234. package/.next/server/chunks/ssr/_0gmb3g_._.js.map +1 -0
  235. package/.next/server/chunks/ssr/_0okz9j8._.js +6 -0
  236. package/.next/server/chunks/ssr/_0okz9j8._.js.map +1 -0
  237. package/.next/server/chunks/ssr/_0wrbwro._.js +6 -0
  238. package/.next/server/chunks/ssr/_0wrbwro._.js.map +1 -0
  239. package/.next/server/chunks/ssr/node_modules_09w7yel._.js +33 -0
  240. package/.next/server/chunks/ssr/node_modules_09w7yel._.js.map +1 -0
  241. package/.next/server/chunks/ssr/node_modules_next_dist_0avqw4q._.js +3 -0
  242. package/.next/server/chunks/ssr/node_modules_next_dist_0avqw4q._.js.map +1 -0
  243. package/.next/server/chunks/ssr/node_modules_next_dist_0h9llsw._.js +6 -0
  244. package/.next/server/chunks/ssr/node_modules_next_dist_0h9llsw._.js.map +1 -0
  245. package/.next/server/chunks/ssr/node_modules_next_dist_0i_._k3._.js +3 -0
  246. package/.next/server/chunks/ssr/node_modules_next_dist_0i_._k3._.js.map +1 -0
  247. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0ee1czk._.js +3 -0
  248. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_0ee1czk._.js.map +1 -0
  249. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0lgvd_..js +3 -0
  250. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0lgvd_..js.map +1 -0
  251. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0cjv-23.js +3 -0
  252. package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_unauthorized_0cjv-23.js.map +1 -0
  253. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0amzg6z.js +4 -0
  254. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0amzg6z.js.map +1 -0
  255. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dw1x0d.js +4 -0
  256. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dw1x0d.js.map +1 -0
  257. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0rc3ul_.js +4 -0
  258. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0rc3ul_.js.map +1 -0
  259. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_10iomok.js +4 -0
  260. package/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_10iomok.js.map +1 -0
  261. package/.next/server/chunks/ssr/packages_observer-dashboard_0~a2tmu._.js +3 -0
  262. package/.next/server/chunks/ssr/packages_observer-dashboard_0~a2tmu._.js.map +1 -0
  263. package/.next/server/chunks/ssr/packages_observer-dashboard_src_0f.ozc-._.js +3 -0
  264. package/.next/server/chunks/ssr/packages_observer-dashboard_src_0f.ozc-._.js.map +1 -0
  265. package/.next/server/chunks/ssr/packages_observer-dashboard_src_components_providers_tsx_03_vrrn._.js +7 -0
  266. package/.next/server/chunks/ssr/packages_observer-dashboard_src_components_providers_tsx_03_vrrn._.js.map +1 -0
  267. package/.next/server/functions-config-manifest.json +4 -0
  268. package/.next/server/interception-route-rewrite-manifest.js +1 -0
  269. package/.next/server/middleware-build-manifest.js +23 -0
  270. package/.next/server/middleware-manifest.json +6 -0
  271. package/.next/server/next-font-manifest.js +1 -0
  272. package/.next/server/next-font-manifest.json +6 -0
  273. package/.next/server/pages/404.html +1 -0
  274. package/.next/server/pages/500.html +1 -0
  275. package/.next/server/pages-manifest.json +4 -0
  276. package/.next/server/prefetch-hints.json +1 -0
  277. package/.next/server/server-reference-manifest.js +1 -0
  278. package/.next/server/server-reference-manifest.json +24 -0
  279. package/.next/static/MvfPly4ZXHqaveDVOG1qq/_buildManifest.js +11 -0
  280. package/.next/static/MvfPly4ZXHqaveDVOG1qq/_clientMiddlewareManifest.js +1 -0
  281. package/.next/static/MvfPly4ZXHqaveDVOG1qq/_ssgManifest.js +1 -0
  282. package/.next/static/chunks/00c87uqn~kk2z.js +1 -0
  283. package/.next/static/chunks/01xlw8hd842-c.js +1 -0
  284. package/.next/static/chunks/02p9dpa.0oxiz.js +1 -0
  285. package/.next/static/chunks/034i63v_muq~d.js +1 -0
  286. package/.next/static/chunks/03mo-6~4pxdio.js +1 -0
  287. package/.next/static/chunks/03~yq9q893hmn.js +1 -0
  288. package/.next/static/chunks/04dm5qn77.fc2.js +1 -0
  289. package/.next/static/chunks/07uz2g0_38qia.js +4 -0
  290. package/.next/static/chunks/08nqnwvixoxnk.js +2 -0
  291. package/.next/static/chunks/0d3shmwh5_nmn.js +1 -0
  292. package/.next/static/chunks/0e_wyjw3nx.a..js +1 -0
  293. package/.next/static/chunks/0mstyq17cbf8-.js +1 -0
  294. package/.next/static/chunks/0ntuxw9.how-q.js +1 -0
  295. package/.next/static/chunks/0t3uzajv1qzmo.js +1 -0
  296. package/.next/static/chunks/0u~_nwr5-v.xp.js +1 -0
  297. package/.next/static/chunks/0wxcxh6eyzams.js +1 -0
  298. package/.next/static/chunks/0x6y7yt4kiddp.css +1 -0
  299. package/.next/static/chunks/0z6-vonyxr0dn.js +4 -0
  300. package/.next/static/chunks/0ze4gu236oq96.js +31 -0
  301. package/.next/static/chunks/0zwozael9msy1.js +1 -0
  302. package/.next/static/chunks/10qd__0r7a~.l.js +1 -0
  303. package/.next/static/chunks/11jh_u1ynmatg.js +1 -0
  304. package/.next/static/chunks/13xer3cb9shu-.js +5 -0
  305. package/.next/static/chunks/142zlnch_xmgd.js +1 -0
  306. package/.next/static/chunks/144kcri75qczu.js +1 -0
  307. package/.next/static/chunks/turbopack-0-ww6fe7b37qt.js +1 -0
  308. package/.next/static/media/icon.08ljfy7xai2x_.svg +20 -0
  309. package/LICENSE +21 -0
  310. package/README.md +490 -0
  311. package/next.config.mjs +25 -0
  312. package/package.json +104 -0
  313. package/postcss.config.mjs +8 -0
  314. package/src/app/actions/__tests__/approve-breakpoint.test.ts +246 -0
  315. package/src/app/actions/approve-breakpoint.ts +145 -0
  316. package/src/app/api/config/route.ts +137 -0
  317. package/src/app/api/digest/route.ts +45 -0
  318. package/src/app/api/runs/[runId]/events/route.ts +56 -0
  319. package/src/app/api/runs/[runId]/route.ts +84 -0
  320. package/src/app/api/runs/[runId]/tasks/[effectId]/route.ts +44 -0
  321. package/src/app/api/runs/route.ts +48 -0
  322. package/src/app/api/stream/route.ts +136 -0
  323. package/src/app/api/test/route.ts +1 -0
  324. package/src/app/api/version/route.ts +57 -0
  325. package/src/app/globals.css +555 -0
  326. package/src/app/icon.svg +20 -0
  327. package/src/app/layout.tsx +39 -0
  328. package/src/app/not-found.tsx +16 -0
  329. package/src/app/page.tsx +120 -0
  330. package/src/app/runs/[runId]/page.tsx +279 -0
  331. package/src/cli.ts +271 -0
  332. package/src/components/breakpoint/__tests__/breakpoint-approval.test.tsx +212 -0
  333. package/src/components/breakpoint/__tests__/breakpoint-panel.test.tsx +130 -0
  334. package/src/components/breakpoint/__tests__/file-preview.test.tsx +313 -0
  335. package/src/components/breakpoint/breakpoint-approval.tsx +138 -0
  336. package/src/components/breakpoint/breakpoint-panel.tsx +95 -0
  337. package/src/components/breakpoint/file-preview.tsx +215 -0
  338. package/src/components/dashboard/.gitkeep +0 -0
  339. package/src/components/dashboard/__tests__/breakpoint-banner.test.tsx +177 -0
  340. package/src/components/dashboard/__tests__/catch-up-banner.test.tsx +141 -0
  341. package/src/components/dashboard/__tests__/executive-summary-banner.test.tsx +164 -0
  342. package/src/components/dashboard/__tests__/kpi-grid.test.tsx +101 -0
  343. package/src/components/dashboard/__tests__/pagination-controls.test.tsx +125 -0
  344. package/src/components/dashboard/__tests__/project-accordion.test.tsx +97 -0
  345. package/src/components/dashboard/__tests__/project-list-view.test.tsx +174 -0
  346. package/src/components/dashboard/__tests__/project-search-input.test.tsx +110 -0
  347. package/src/components/dashboard/__tests__/project-section-header.test.tsx +91 -0
  348. package/src/components/dashboard/__tests__/project-section.test.tsx +151 -0
  349. package/src/components/dashboard/__tests__/run-card.test.tsx +164 -0
  350. package/src/components/dashboard/__tests__/run-filter-bar.test.tsx +109 -0
  351. package/src/components/dashboard/__tests__/run-list.test.tsx +123 -0
  352. package/src/components/dashboard/__tests__/search-filter.test.tsx +150 -0
  353. package/src/components/dashboard/__tests__/virtualized-run-list.test.tsx +179 -0
  354. package/src/components/dashboard/breakpoint-banner.tsx +301 -0
  355. package/src/components/dashboard/catch-up-banner.tsx +88 -0
  356. package/src/components/dashboard/executive-summary-banner.tsx +174 -0
  357. package/src/components/dashboard/global-search.tsx +323 -0
  358. package/src/components/dashboard/kpi-grid.tsx +140 -0
  359. package/src/components/dashboard/pagination-controls.tsx +100 -0
  360. package/src/components/dashboard/project-accordion.tsx +72 -0
  361. package/src/components/dashboard/project-health-card.tsx +536 -0
  362. package/src/components/dashboard/project-list-view.tsx +246 -0
  363. package/src/components/dashboard/project-search-input.tsx +41 -0
  364. package/src/components/dashboard/project-section-header.tsx +73 -0
  365. package/src/components/dashboard/project-section.tsx +89 -0
  366. package/src/components/dashboard/run-card.tsx +218 -0
  367. package/src/components/dashboard/run-filter-bar.tsx +100 -0
  368. package/src/components/dashboard/run-list.tsx +77 -0
  369. package/src/components/dashboard/search-filter.tsx +69 -0
  370. package/src/components/dashboard/virtualized-run-list.tsx +130 -0
  371. package/src/components/details/.gitkeep +0 -0
  372. package/src/components/details/__tests__/agent-panel.test.tsx +236 -0
  373. package/src/components/details/__tests__/json-tree.test.tsx +347 -0
  374. package/src/components/details/__tests__/log-viewer.test.tsx +168 -0
  375. package/src/components/details/__tests__/task-detail.test.tsx +212 -0
  376. package/src/components/details/__tests__/timing-panel.test.tsx +271 -0
  377. package/src/components/details/agent-panel.tsx +234 -0
  378. package/src/components/details/json-tree/categorize.ts +131 -0
  379. package/src/components/details/json-tree/index.tsx +120 -0
  380. package/src/components/details/json-tree/json-node.tsx +223 -0
  381. package/src/components/details/json-tree/smart-summary.tsx +596 -0
  382. package/src/components/details/json-tree/tree-controls.tsx +47 -0
  383. package/src/components/details/json-tree.tsx +9 -0
  384. package/src/components/details/log-viewer.tsx +140 -0
  385. package/src/components/details/task-detail.tsx +114 -0
  386. package/src/components/details/timing-panel.tsx +247 -0
  387. package/src/components/events/.gitkeep +0 -0
  388. package/src/components/events/__tests__/event-item.test.tsx +211 -0
  389. package/src/components/events/__tests__/event-stream.test.tsx +225 -0
  390. package/src/components/events/event-item.tsx +121 -0
  391. package/src/components/events/event-stream.tsx +260 -0
  392. package/src/components/notifications/.gitkeep +0 -0
  393. package/src/components/notifications/__tests__/notification-panel.test.tsx +287 -0
  394. package/src/components/notifications/__tests__/notification-provider.test.tsx +585 -0
  395. package/src/components/notifications/__tests__/toast-stack.test.tsx +217 -0
  396. package/src/components/notifications/notification-panel.tsx +124 -0
  397. package/src/components/notifications/notification-provider.tsx +175 -0
  398. package/src/components/notifications/toast-stack.tsx +75 -0
  399. package/src/components/pipeline/.gitkeep +0 -0
  400. package/src/components/pipeline/__tests__/parallel-group.test.tsx +88 -0
  401. package/src/components/pipeline/__tests__/pipeline-view.test.tsx +345 -0
  402. package/src/components/pipeline/__tests__/step-card.test.tsx +330 -0
  403. package/src/components/pipeline/parallel-group.tsx +39 -0
  404. package/src/components/pipeline/pipeline-view.tsx +197 -0
  405. package/src/components/pipeline/step-card.tsx +166 -0
  406. package/src/components/providers/event-stream-provider.tsx +29 -0
  407. package/src/components/providers.tsx +24 -0
  408. package/src/components/shared/.gitkeep +0 -0
  409. package/src/components/shared/__tests__/empty-state.test.tsx +49 -0
  410. package/src/components/shared/__tests__/friendly-id.test.tsx +47 -0
  411. package/src/components/shared/__tests__/kbd.test.tsx +45 -0
  412. package/src/components/shared/__tests__/kind-badge.test.tsx +71 -0
  413. package/src/components/shared/__tests__/metrics-row.test.tsx +74 -0
  414. package/src/components/shared/__tests__/outcome-banner.test.tsx +71 -0
  415. package/src/components/shared/__tests__/progress-bar.test.tsx +89 -0
  416. package/src/components/shared/__tests__/session-pill.test.tsx +62 -0
  417. package/src/components/shared/__tests__/settings-modal.test.tsx +201 -0
  418. package/src/components/shared/__tests__/shortcuts-help.test.tsx +103 -0
  419. package/src/components/shared/__tests__/status-badge.test.tsx +98 -0
  420. package/src/components/shared/__tests__/theme-provider.test.tsx +100 -0
  421. package/src/components/shared/__tests__/truncated-id.test.tsx +53 -0
  422. package/src/components/shared/app-footer.tsx +80 -0
  423. package/src/components/shared/app-header.tsx +160 -0
  424. package/src/components/shared/empty-state.tsx +18 -0
  425. package/src/components/shared/error-boundary.tsx +81 -0
  426. package/src/components/shared/friendly-id.tsx +48 -0
  427. package/src/components/shared/kbd.tsx +15 -0
  428. package/src/components/shared/kind-badge.tsx +51 -0
  429. package/src/components/shared/metrics-row.tsx +106 -0
  430. package/src/components/shared/outcome-banner.tsx +56 -0
  431. package/src/components/shared/progress-bar.tsx +42 -0
  432. package/src/components/shared/session-pill.tsx +69 -0
  433. package/src/components/shared/settings-modal.tsx +509 -0
  434. package/src/components/shared/shortcuts-help.tsx +113 -0
  435. package/src/components/shared/status-badge.tsx +110 -0
  436. package/src/components/shared/theme-provider.tsx +46 -0
  437. package/src/components/shared/truncated-id.tsx +51 -0
  438. package/src/components/ui/.gitkeep +0 -0
  439. package/src/components/ui/__tests__/accordion.test.tsx +96 -0
  440. package/src/components/ui/__tests__/badge.test.tsx +69 -0
  441. package/src/components/ui/__tests__/button.test.tsx +113 -0
  442. package/src/components/ui/__tests__/tabs.test.tsx +75 -0
  443. package/src/components/ui/__tests__/tooltip.test.tsx +90 -0
  444. package/src/components/ui/accordion.tsx +61 -0
  445. package/src/components/ui/badge.tsx +25 -0
  446. package/src/components/ui/button.tsx +40 -0
  447. package/src/components/ui/card.tsx +21 -0
  448. package/src/components/ui/scroll-area.tsx +35 -0
  449. package/src/components/ui/separator.tsx +24 -0
  450. package/src/components/ui/tabs.tsx +64 -0
  451. package/src/components/ui/tooltip.tsx +37 -0
  452. package/src/hooks/.gitkeep +0 -0
  453. package/src/hooks/__tests__/use-animated-number.test.ts +184 -0
  454. package/src/hooks/__tests__/use-batched-updates.test.ts +315 -0
  455. package/src/hooks/__tests__/use-event-stream.test.ts +243 -0
  456. package/src/hooks/__tests__/use-keyboard.test.ts +217 -0
  457. package/src/hooks/__tests__/use-notifications.test.ts +230 -0
  458. package/src/hooks/__tests__/use-polling.test.ts +274 -0
  459. package/src/hooks/__tests__/use-project-runs.test.ts +163 -0
  460. package/src/hooks/__tests__/use-projects.test.ts +248 -0
  461. package/src/hooks/__tests__/use-run-dashboard.test.ts +168 -0
  462. package/src/hooks/__tests__/use-run-detail.test.ts +273 -0
  463. package/src/hooks/__tests__/use-smart-polling.test.ts +305 -0
  464. package/src/hooks/use-animated-number.ts +87 -0
  465. package/src/hooks/use-batched-updates.ts +150 -0
  466. package/src/hooks/use-event-stream.ts +150 -0
  467. package/src/hooks/use-keyboard.ts +45 -0
  468. package/src/hooks/use-notifications.ts +82 -0
  469. package/src/hooks/use-persisted-state.ts +60 -0
  470. package/src/hooks/use-polling.ts +60 -0
  471. package/src/hooks/use-project-runs.ts +51 -0
  472. package/src/hooks/use-projects.ts +26 -0
  473. package/src/hooks/use-run-dashboard.ts +207 -0
  474. package/src/hooks/use-run-detail.ts +77 -0
  475. package/src/hooks/use-smart-polling.ts +144 -0
  476. package/src/lib/.gitkeep +0 -0
  477. package/src/lib/__tests__/cn.test.ts +69 -0
  478. package/src/lib/__tests__/config-loader.test.ts +210 -0
  479. package/src/lib/__tests__/config.test.ts +561 -0
  480. package/src/lib/__tests__/error-handler.test.ts +143 -0
  481. package/src/lib/__tests__/fetcher.test.ts +517 -0
  482. package/src/lib/__tests__/global-registry.test.ts +214 -0
  483. package/src/lib/__tests__/parser.test.ts +1532 -0
  484. package/src/lib/__tests__/path-resolver.test.ts +112 -0
  485. package/src/lib/__tests__/run-cache.test.ts +591 -0
  486. package/src/lib/__tests__/server-init.test.ts +512 -0
  487. package/src/lib/__tests__/source-discovery.test.ts +246 -0
  488. package/src/lib/__tests__/utils.test.ts +160 -0
  489. package/src/lib/__tests__/watcher.test.ts +227 -0
  490. package/src/lib/cn.ts +6 -0
  491. package/src/lib/config-loader.ts +195 -0
  492. package/src/lib/config.ts +20 -0
  493. package/src/lib/error-handler.ts +76 -0
  494. package/src/lib/fetcher.ts +394 -0
  495. package/src/lib/global-registry.ts +117 -0
  496. package/src/lib/parser.ts +794 -0
  497. package/src/lib/path-resolver.ts +16 -0
  498. package/src/lib/run-cache.ts +404 -0
  499. package/src/lib/server-init.ts +226 -0
  500. package/src/lib/services/__tests__/run-query-service.test.ts +819 -0
  501. package/src/lib/services/run-query-service.ts +286 -0
  502. package/src/lib/source-discovery.ts +216 -0
  503. package/src/lib/utils.ts +103 -0
  504. package/src/lib/watcher.ts +265 -0
  505. package/src/test/fixtures.ts +269 -0
  506. package/src/test/mocks/handlers.ts +110 -0
  507. package/src/test/mocks/server.ts +17 -0
  508. package/src/test/setup.ts +200 -0
  509. package/src/test/test-utils.tsx +36 -0
  510. package/src/types/.gitkeep +0 -0
  511. package/src/types/breakpoint.ts +17 -0
  512. package/src/types/index.ts +214 -0
  513. package/tsconfig.json +50 -0
@@ -0,0 +1,305 @@
1
+ import { renderHook, act } from '@testing-library/react';
2
+ import { useSmartPolling } from '../use-smart-polling';
3
+
4
+ // Track unsubscribers for cleanup
5
+ let activeUnsubscribers: Array<() => void> = [];
6
+
7
+ type MockEventSourceInstance = {
8
+ onopen: ((event: Event) => void) | null;
9
+ onmessage: ((event: MessageEvent) => void) | null;
10
+ onerror: ((event: Event) => void) | null;
11
+ close: ReturnType<typeof vi.fn>;
12
+ readyState: number;
13
+ url: string;
14
+ };
15
+
16
+ let mockEventSourceInstances: MockEventSourceInstance[] = [];
17
+
18
+ class MockEventSource {
19
+ static CONNECTING = 0;
20
+ static OPEN = 1;
21
+ static CLOSED = 2;
22
+
23
+ onopen: ((event: Event) => void) | null = null;
24
+ onmessage: ((event: MessageEvent) => void) | null = null;
25
+ onerror: ((event: Event) => void) | null = null;
26
+ close = vi.fn();
27
+ readyState = MockEventSource.OPEN;
28
+ url: string;
29
+
30
+ constructor(url: string) {
31
+ this.url = url;
32
+ mockEventSourceInstances.push(this);
33
+ }
34
+ }
35
+
36
+ function mockFetchSuccess(data: unknown) {
37
+ return vi.fn().mockResolvedValue(
38
+ new Response(JSON.stringify(data), {
39
+ status: 200,
40
+ headers: { 'Content-Type': 'application/json' },
41
+ })
42
+ );
43
+ }
44
+
45
+ describe('useSmartPolling', () => {
46
+ beforeEach(() => {
47
+ vi.useFakeTimers();
48
+ mockEventSourceInstances = [];
49
+ vi.stubGlobal('EventSource', MockEventSource);
50
+ vi.stubGlobal('fetch', mockFetchSuccess({ items: [1, 2, 3] }));
51
+ });
52
+
53
+ afterEach(() => {
54
+ for (const unsub of activeUnsubscribers) {
55
+ unsub();
56
+ }
57
+ activeUnsubscribers = [];
58
+ vi.useRealTimers();
59
+ vi.restoreAllMocks();
60
+ });
61
+
62
+ it('fetches data immediately on mount', async () => {
63
+ const { result } = renderHook(() =>
64
+ useSmartPolling<{ items: number[] }>('/api/data', { interval: 5000 })
65
+ );
66
+
67
+ expect(result.current.loading).toBe(true);
68
+
69
+ await act(async () => {
70
+ await vi.advanceTimersByTimeAsync(0);
71
+ });
72
+
73
+ expect(result.current.data).toEqual({ items: [1, 2, 3] });
74
+ expect(result.current.loading).toBe(false);
75
+ expect(result.current.error).toBeNull();
76
+ });
77
+
78
+ it('polls at specified interval', async () => {
79
+ renderHook(() =>
80
+ useSmartPolling('/api/data', { interval: 5000 })
81
+ );
82
+
83
+ await act(async () => {
84
+ await vi.advanceTimersByTimeAsync(0);
85
+ });
86
+ expect(fetch).toHaveBeenCalledTimes(1);
87
+
88
+ await act(async () => {
89
+ await vi.advanceTimersByTimeAsync(5000);
90
+ });
91
+ expect(fetch).toHaveBeenCalledTimes(2);
92
+ });
93
+
94
+ it('does not fetch when enabled is false', async () => {
95
+ const { result } = renderHook(() =>
96
+ useSmartPolling('/api/data', { enabled: false })
97
+ );
98
+
99
+ await act(async () => {
100
+ await vi.advanceTimersByTimeAsync(10000);
101
+ });
102
+
103
+ expect(fetch).not.toHaveBeenCalled();
104
+ expect(result.current.loading).toBe(false);
105
+ });
106
+
107
+ it('handles fetch errors', async () => {
108
+ // Use 422 (not 404, because 404 is retryable in resilientFetch)
109
+ vi.stubGlobal(
110
+ 'fetch',
111
+ vi.fn().mockResolvedValue(
112
+ new Response('HTTP 422', { status: 422 })
113
+ )
114
+ );
115
+
116
+ const { result } = renderHook(() =>
117
+ useSmartPolling('/api/data', { interval: 5000 })
118
+ );
119
+
120
+ await act(async () => {
121
+ await vi.advanceTimersByTimeAsync(0);
122
+ });
123
+
124
+ expect(result.current.error).toBe('HTTP 422');
125
+ expect(result.current.loading).toBe(false);
126
+ });
127
+
128
+ it('triggers debounced refetch when SSE event matches filter', async () => {
129
+ const sseFilter = vi.fn().mockReturnValue(true);
130
+
131
+ renderHook(() =>
132
+ useSmartPolling('/api/data', {
133
+ interval: 5000,
134
+ sseFilter,
135
+ })
136
+ );
137
+
138
+ // Initial fetch
139
+ await act(async () => {
140
+ await vi.advanceTimersByTimeAsync(0);
141
+ });
142
+
143
+ const initialCallCount = (fetch as ReturnType<typeof vi.fn>).mock.calls.length;
144
+
145
+ // Simulate SSE message via the EventSource
146
+ const instance = mockEventSourceInstances[0];
147
+ if (instance?.onmessage) {
148
+ act(() => {
149
+ instance.onmessage!(
150
+ new MessageEvent('message', {
151
+ data: JSON.stringify({ type: 'run_updated', runId: 'run-1' }),
152
+ })
153
+ );
154
+ });
155
+ }
156
+
157
+ // Before debounce window (1500ms), no additional fetch
158
+ await act(async () => {
159
+ await vi.advanceTimersByTimeAsync(1000);
160
+ });
161
+ expect((fetch as ReturnType<typeof vi.fn>).mock.calls.length).toBe(initialCallCount);
162
+
163
+ // After debounce window completes (1500ms total)
164
+ await act(async () => {
165
+ await vi.advanceTimersByTimeAsync(500);
166
+ });
167
+
168
+ // Should have triggered an additional fetch after 1500ms debounce
169
+ expect((fetch as ReturnType<typeof vi.fn>).mock.calls.length).toBeGreaterThan(initialCallCount);
170
+ });
171
+
172
+ it('coalesces rapid SSE events within debounce window into single fetch', async () => {
173
+ const sseFilter = vi.fn().mockReturnValue(true);
174
+
175
+ renderHook(() =>
176
+ useSmartPolling('/api/data', {
177
+ interval: 5000,
178
+ sseFilter,
179
+ })
180
+ );
181
+
182
+ // Initial fetch
183
+ await act(async () => {
184
+ await vi.advanceTimersByTimeAsync(0);
185
+ });
186
+
187
+ const initialCallCount = (fetch as ReturnType<typeof vi.fn>).mock.calls.length;
188
+
189
+ const instance = mockEventSourceInstances[0];
190
+ if (instance?.onmessage) {
191
+ // Fire 3 rapid SSE events within 100ms
192
+ act(() => {
193
+ instance.onmessage!(
194
+ new MessageEvent('message', {
195
+ data: JSON.stringify({ type: 'update', runId: 'run-1' }),
196
+ })
197
+ );
198
+ });
199
+ await act(async () => {
200
+ await vi.advanceTimersByTimeAsync(50);
201
+ });
202
+ act(() => {
203
+ instance.onmessage!(
204
+ new MessageEvent('message', {
205
+ data: JSON.stringify({ type: 'update', runId: 'run-2' }),
206
+ })
207
+ );
208
+ });
209
+ await act(async () => {
210
+ await vi.advanceTimersByTimeAsync(50);
211
+ });
212
+ act(() => {
213
+ instance.onmessage!(
214
+ new MessageEvent('message', {
215
+ data: JSON.stringify({ type: 'update', runId: 'run-3' }),
216
+ })
217
+ );
218
+ });
219
+ }
220
+
221
+ // Wait for the debounce to fire (1500ms from last event)
222
+ await act(async () => {
223
+ await vi.advanceTimersByTimeAsync(1500);
224
+ });
225
+
226
+ // Should only trigger ONE additional fetch despite 3 events
227
+ expect((fetch as ReturnType<typeof vi.fn>).mock.calls.length).toBe(initialCallCount + 1);
228
+ });
229
+
230
+ it('does not refetch when SSE event does not match filter', async () => {
231
+ const sseFilter = vi.fn().mockReturnValue(false);
232
+
233
+ renderHook(() =>
234
+ useSmartPolling('/api/data', {
235
+ interval: 5000,
236
+ sseFilter,
237
+ })
238
+ );
239
+
240
+ // Initial fetch
241
+ await act(async () => {
242
+ await vi.advanceTimersByTimeAsync(0);
243
+ });
244
+
245
+ const callCount = (fetch as ReturnType<typeof vi.fn>).mock.calls.length;
246
+
247
+ // Simulate SSE message that does NOT match filter
248
+ const instance = mockEventSourceInstances[0];
249
+ if (instance?.onmessage) {
250
+ act(() => {
251
+ instance.onmessage!(
252
+ new MessageEvent('message', {
253
+ data: JSON.stringify({ type: 'run_updated', runId: 'run-2' }),
254
+ })
255
+ );
256
+ });
257
+ }
258
+
259
+ await act(async () => {
260
+ await vi.advanceTimersByTimeAsync(0);
261
+ });
262
+
263
+ // No additional fetch should have happened
264
+ expect((fetch as ReturnType<typeof vi.fn>).mock.calls.length).toBe(callCount);
265
+ });
266
+
267
+ it('provides a manual refresh function', async () => {
268
+ const { result } = renderHook(() =>
269
+ useSmartPolling('/api/data', { interval: 5000 })
270
+ );
271
+
272
+ await act(async () => {
273
+ await vi.advanceTimersByTimeAsync(0);
274
+ });
275
+ expect(fetch).toHaveBeenCalledTimes(1);
276
+
277
+ await act(async () => {
278
+ result.current.refresh();
279
+ await vi.advanceTimersByTimeAsync(0);
280
+ });
281
+ expect(fetch).toHaveBeenCalledTimes(2);
282
+ });
283
+
284
+ it('cleans up polling on unmount', async () => {
285
+ const { unmount } = renderHook(() =>
286
+ useSmartPolling('/api/data', {
287
+ interval: 5000,
288
+ })
289
+ );
290
+
291
+ await act(async () => {
292
+ await vi.advanceTimersByTimeAsync(0);
293
+ });
294
+ const callsAfterMount = (fetch as ReturnType<typeof vi.fn>).mock.calls.length;
295
+
296
+ unmount();
297
+
298
+ // Advance time - no more fetches should happen
299
+ await act(async () => {
300
+ await vi.advanceTimersByTimeAsync(30000);
301
+ });
302
+
303
+ expect((fetch as ReturnType<typeof vi.fn>).mock.calls.length).toBe(callsAfterMount);
304
+ });
305
+ });
@@ -0,0 +1,87 @@
1
+ "use client";
2
+ import { useState, useEffect, useRef, useCallback } from "react";
3
+
4
+ /**
5
+ * Hook that smoothly animates a number from its previous value to the target.
6
+ * Uses requestAnimationFrame for smooth 60fps transitions.
7
+ *
8
+ * @param target - The number to animate towards
9
+ * @param duration - Animation duration in ms (default 600)
10
+ * @returns The current animated value (integer)
11
+ */
12
+ export function useAnimatedNumber(target: number, duration = 600): number {
13
+ const [display, setDisplay] = useState(target);
14
+ const animRef = useRef<number | null>(null);
15
+ const startRef = useRef<{ value: number; time: number } | null>(null);
16
+ const targetRef = useRef(target);
17
+ const displayRef = useRef(target);
18
+
19
+ // Easing function: ease-out cubic for a natural deceleration feel
20
+ const easeOutCubic = useCallback((t: number): number => {
21
+ return 1 - Math.pow(1 - t, 3);
22
+ }, []);
23
+
24
+ useEffect(() => {
25
+ const prevTarget = targetRef.current;
26
+ targetRef.current = target;
27
+
28
+ // No change — skip animation
29
+ if (prevTarget === target) return;
30
+
31
+ // Cancel any running animation
32
+ if (animRef.current !== null) {
33
+ cancelAnimationFrame(animRef.current);
34
+ animRef.current = null;
35
+ }
36
+
37
+ const startValue = displayRef.current;
38
+ const diff = target - startValue;
39
+
40
+ // If diff is 0, just snap
41
+ if (diff === 0) {
42
+ setDisplay(target);
43
+ displayRef.current = target;
44
+ return;
45
+ }
46
+
47
+ // For very small changes (1-2), snap immediately
48
+ if (Math.abs(diff) <= 2) {
49
+ setDisplay(target);
50
+ displayRef.current = target;
51
+ return;
52
+ }
53
+
54
+ const startTime = performance.now();
55
+ startRef.current = { value: startValue, time: startTime };
56
+
57
+ function animate(now: number) {
58
+ const elapsed = now - startTime;
59
+ const progress = Math.min(elapsed / duration, 1);
60
+ const easedProgress = easeOutCubic(progress);
61
+
62
+ const current = Math.round(startValue + diff * easedProgress);
63
+ displayRef.current = current;
64
+ setDisplay(current);
65
+
66
+ if (progress < 1) {
67
+ animRef.current = requestAnimationFrame(animate);
68
+ } else {
69
+ animRef.current = null;
70
+ // Ensure we land exactly on target
71
+ displayRef.current = target;
72
+ setDisplay(target);
73
+ }
74
+ }
75
+
76
+ animRef.current = requestAnimationFrame(animate);
77
+
78
+ return () => {
79
+ if (animRef.current !== null) {
80
+ cancelAnimationFrame(animRef.current);
81
+ animRef.current = null;
82
+ }
83
+ };
84
+ }, [target, duration, easeOutCubic]);
85
+
86
+ return display;
87
+ }
@@ -0,0 +1,150 @@
1
+ "use client";
2
+ import { useState, useEffect, useRef, useCallback } from "react";
3
+ import { subscribe, StreamEvent } from "./use-event-stream";
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Configuration
7
+ // ---------------------------------------------------------------------------
8
+
9
+ /** Window (ms) for counting rapid SSE events to detect burst mode. */
10
+ const BURST_WINDOW_MS = 5000;
11
+
12
+ /** Number of SSE events within the burst window that triggers catch-up mode. */
13
+ const BURST_THRESHOLD = 10;
14
+
15
+ /** How long (ms) to hold catch-up mode after the burst subsides. */
16
+ const CATCHUP_HOLD_MS = 3000;
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Types
20
+ // ---------------------------------------------------------------------------
21
+
22
+ export interface CatchUpState {
23
+ /** Whether we are in catch-up mode (burst of events detected). */
24
+ active: boolean;
25
+ /** Number of batched/buffered updates while in catch-up mode. */
26
+ bufferedCount: number;
27
+ /** Dismiss catch-up mode and apply buffered updates immediately. */
28
+ flush: () => void;
29
+ }
30
+
31
+ export interface UseBatchedUpdatesOptions {
32
+ /** SSE filter: only count events matching this predicate for burst detection. */
33
+ sseFilter?: (event: StreamEvent) => boolean;
34
+ /** Callback invoked when catch-up mode ends (either by timeout or flush). */
35
+ onFlush?: () => void;
36
+ /** Whether the hook is enabled (default true). */
37
+ enabled?: boolean;
38
+ }
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Hook
42
+ // ---------------------------------------------------------------------------
43
+
44
+ /**
45
+ * Monitors SSE event rate and activates "catch-up mode" when a burst of
46
+ * events is detected (e.g. opening dashboard after overnight runs).
47
+ *
48
+ * In catch-up mode, the caller should suppress real-time UI updates and
49
+ * instead show a summary notification ("12 runs updated"). When the burst
50
+ * subsides or the user clicks "refresh now", catch-up mode ends and the
51
+ * caller should do a single full refresh.
52
+ */
53
+ export function useBatchedUpdates(
54
+ options: UseBatchedUpdatesOptions = {}
55
+ ): CatchUpState {
56
+ const { sseFilter, onFlush, enabled = true } = options;
57
+ const [active, setActive] = useState(false);
58
+ const [bufferedCount, setBufferedCount] = useState(0);
59
+
60
+ // Track SSE event timestamps within the burst window
61
+ const eventTimestampsRef = useRef<number[]>([]);
62
+ const catchUpActiveRef = useRef(false);
63
+ const holdTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
64
+ const sseFilterRef = useRef(sseFilter);
65
+ sseFilterRef.current = sseFilter;
66
+ const onFlushRef = useRef(onFlush);
67
+ onFlushRef.current = onFlush;
68
+
69
+ const exitCatchUp = useCallback(() => {
70
+ catchUpActiveRef.current = false;
71
+ setActive(false);
72
+ setBufferedCount(0);
73
+ eventTimestampsRef.current = [];
74
+ if (holdTimerRef.current) {
75
+ clearTimeout(holdTimerRef.current);
76
+ holdTimerRef.current = null;
77
+ }
78
+ onFlushRef.current?.();
79
+ }, []);
80
+
81
+ const flush = useCallback(() => {
82
+ if (catchUpActiveRef.current) {
83
+ exitCatchUp();
84
+ }
85
+ }, [exitCatchUp]);
86
+
87
+ useEffect(() => {
88
+ if (!enabled) return;
89
+
90
+ const unsubscribe = subscribe((event: StreamEvent) => {
91
+ // Ignore non-data events
92
+ if (
93
+ event.type === "connected" ||
94
+ event.type === "disconnect" ||
95
+ event.type === "error"
96
+ ) {
97
+ return;
98
+ }
99
+
100
+ // Apply optional filter
101
+ if (sseFilterRef.current && !sseFilterRef.current(event)) return;
102
+
103
+ const now = Date.now();
104
+
105
+ // Record timestamp and prune old entries
106
+ eventTimestampsRef.current.push(now);
107
+ eventTimestampsRef.current = eventTimestampsRef.current.filter(
108
+ (t) => now - t < BURST_WINDOW_MS
109
+ );
110
+
111
+ if (catchUpActiveRef.current) {
112
+ // Already in catch-up mode — increment buffered count
113
+ setBufferedCount((c) => c + 1);
114
+
115
+ // Reset the hold timer since events are still arriving
116
+ if (holdTimerRef.current) {
117
+ clearTimeout(holdTimerRef.current);
118
+ }
119
+ holdTimerRef.current = setTimeout(() => {
120
+ exitCatchUp();
121
+ }, CATCHUP_HOLD_MS);
122
+ } else {
123
+ // Check if we should enter catch-up mode
124
+ if (eventTimestampsRef.current.length >= BURST_THRESHOLD) {
125
+ catchUpActiveRef.current = true;
126
+ setActive(true);
127
+ setBufferedCount(eventTimestampsRef.current.length);
128
+
129
+ // Set initial hold timer
130
+ holdTimerRef.current = setTimeout(() => {
131
+ exitCatchUp();
132
+ }, CATCHUP_HOLD_MS);
133
+ }
134
+ }
135
+ });
136
+
137
+ return () => {
138
+ unsubscribe();
139
+ if (holdTimerRef.current) {
140
+ clearTimeout(holdTimerRef.current);
141
+ holdTimerRef.current = null;
142
+ }
143
+ };
144
+ }, [enabled, exitCatchUp]);
145
+
146
+ return { active, bufferedCount, flush };
147
+ }
148
+
149
+ // Export constants for testing
150
+ export { BURST_WINDOW_MS, BURST_THRESHOLD, CATCHUP_HOLD_MS };
@@ -0,0 +1,150 @@
1
+ "use client";
2
+ import { useState, useEffect, useRef } from "react";
3
+
4
+ export interface StreamEvent {
5
+ type: string;
6
+ runId?: string;
7
+ /** Batched runIds from leading-edge debounce (SSE broadcast level). */
8
+ runIds?: string[];
9
+ status?: string;
10
+ timestamp?: number;
11
+ }
12
+
13
+ type EventCallback = (event: StreamEvent) => void;
14
+
15
+ // Module-level state for shared EventSource connection
16
+ let sharedEventSource: EventSource | null = null;
17
+ let subscriberCount = 0;
18
+ let reconnectAttempts = 0;
19
+ let reconnectTimeout: NodeJS.Timeout | null = null;
20
+ const subscribers: Set<EventCallback> = new Set();
21
+
22
+ function getReconnectDelay(): number {
23
+ const delays = [1000, 2000, 4000, 8000, 30000];
24
+ return delays[Math.min(reconnectAttempts, delays.length - 1)];
25
+ }
26
+
27
+ function createEventSource() {
28
+ if (typeof EventSource === "undefined") {
29
+ // eslint-disable-next-line no-console
30
+ console.warn("EventSource not supported in this environment");
31
+ return null;
32
+ }
33
+
34
+ const source = new EventSource("/api/stream");
35
+
36
+ source.onopen = () => {
37
+ // eslint-disable-next-line no-console
38
+ console.log("SSE connected");
39
+ reconnectAttempts = 0;
40
+ };
41
+
42
+ source.onmessage = (event) => {
43
+ try {
44
+ const data = JSON.parse(event.data);
45
+ subscribers.forEach((callback) => callback(data));
46
+ } catch (err) {
47
+ console.error("Failed to parse SSE message:", err);
48
+ }
49
+ };
50
+
51
+ source.onerror = () => {
52
+ console.error("SSE connection error");
53
+ source.close();
54
+ sharedEventSource = null;
55
+
56
+ // Notify subscribers of disconnect so they can fall back to polling
57
+ subscribers.forEach((callback) => callback({ type: "disconnect" }));
58
+
59
+ // Auto-reconnect with exponential backoff
60
+ if (subscriberCount > 0) {
61
+ reconnectAttempts++;
62
+ const delay = getReconnectDelay();
63
+ // eslint-disable-next-line no-console
64
+ console.log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts})`);
65
+ reconnectTimeout = setTimeout(() => {
66
+ if (subscriberCount > 0) {
67
+ sharedEventSource = createEventSource();
68
+ }
69
+ }, delay);
70
+ }
71
+ };
72
+
73
+ return source;
74
+ }
75
+
76
+ export function subscribe(callback: EventCallback): () => void {
77
+ subscribers.add(callback);
78
+ subscriberCount++;
79
+
80
+ // Open connection on first subscriber
81
+ if (subscriberCount === 1 && !sharedEventSource) {
82
+ sharedEventSource = createEventSource();
83
+ }
84
+
85
+ // Return unsubscribe function
86
+ return () => {
87
+ subscribers.delete(callback);
88
+ subscriberCount--;
89
+
90
+ // Close connection on last unsubscribe
91
+ if (subscriberCount === 0) {
92
+ if (reconnectTimeout) {
93
+ clearTimeout(reconnectTimeout);
94
+ reconnectTimeout = null;
95
+ }
96
+ if (sharedEventSource) {
97
+ sharedEventSource.close();
98
+ sharedEventSource = null;
99
+ }
100
+ reconnectAttempts = 0;
101
+ }
102
+ };
103
+ }
104
+
105
+ export function useEventStream() {
106
+ const [connected, setConnected] = useState(false);
107
+ const [lastEvent, setLastEvent] = useState<StreamEvent | null>(null);
108
+ const [error, setError] = useState<string | null>(null);
109
+ const mountedRef = useRef(true);
110
+
111
+ useEffect(() => {
112
+ mountedRef.current = true;
113
+
114
+ if (typeof EventSource === "undefined") {
115
+ setError("EventSource not supported");
116
+ setConnected(false);
117
+ return;
118
+ }
119
+
120
+ const handleEvent = (event: StreamEvent) => {
121
+ if (mountedRef.current) {
122
+ setLastEvent(event);
123
+ // Only mark as connected for real data events, not disconnect/error
124
+ if (event.type !== "disconnect" && event.type !== "error") {
125
+ setConnected(true);
126
+ setError(null);
127
+ }
128
+ }
129
+ };
130
+
131
+ const unsubscribe = subscribe(handleEvent);
132
+
133
+ // Check connection status
134
+ const checkConnection = setInterval(() => {
135
+ if (mountedRef.current) {
136
+ setConnected(
137
+ sharedEventSource !== null && sharedEventSource.readyState === EventSource.OPEN
138
+ );
139
+ }
140
+ }, 1000);
141
+
142
+ return () => {
143
+ mountedRef.current = false;
144
+ unsubscribe();
145
+ clearInterval(checkConnection);
146
+ };
147
+ }, []);
148
+
149
+ return { connected, lastEvent, error };
150
+ }