@shepai/cli 1.53.1 → 1.54.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 (268) hide show
  1. package/apis/json-schema/NotificationEvent.yaml +4 -0
  2. package/dist/packages/core/src/domain/generated/output.d.ts +4 -0
  3. package/dist/packages/core/src/domain/generated/output.d.ts.map +1 -1
  4. package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.d.ts +4 -0
  5. package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.d.ts.map +1 -1
  6. package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.js +19 -6
  7. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts +5 -3
  8. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts.map +1 -1
  9. package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.js +21 -13
  10. package/dist/src/presentation/cli/commands/ui.command.d.ts.map +1 -1
  11. package/dist/src/presentation/cli/commands/ui.command.js +2 -1
  12. package/dist/src/presentation/web/app/api/agent-events/health/route.d.ts +13 -0
  13. package/dist/src/presentation/web/app/api/agent-events/health/route.d.ts.map +1 -0
  14. package/dist/src/presentation/web/app/api/agent-events/health/route.js +60 -0
  15. package/dist/src/presentation/web/app/api/agent-events/route.d.ts +8 -4
  16. package/dist/src/presentation/web/app/api/agent-events/route.d.ts.map +1 -1
  17. package/dist/src/presentation/web/app/api/agent-events/route.js +227 -55
  18. package/dist/src/presentation/web/app/layout.d.ts.map +1 -1
  19. package/dist/src/presentation/web/app/layout.js +14 -0
  20. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts +5 -1
  21. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts.map +1 -1
  22. package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.js +29 -1
  23. package/dist/src/presentation/web/components/common/repository-node/repository-node.d.ts.map +1 -1
  24. package/dist/src/presentation/web/components/common/repository-node/repository-node.js +1 -1
  25. package/dist/src/presentation/web/components/common/sound-toggle/index.d.ts +2 -0
  26. package/dist/src/presentation/web/components/common/sound-toggle/index.d.ts.map +1 -0
  27. package/dist/src/presentation/web/components/common/sound-toggle/index.js +1 -0
  28. package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.d.ts +2 -0
  29. package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.d.ts.map +1 -0
  30. package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.js +9 -0
  31. package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.stories.d.ts +8 -0
  32. package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.stories.d.ts.map +1 -0
  33. package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.stories.js +17 -0
  34. package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
  35. package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +14 -2
  36. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts +1 -0
  37. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
  38. package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +151 -19
  39. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.js +1 -1
  40. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.stories.d.ts +1 -0
  41. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.stories.d.ts.map +1 -1
  42. package/dist/src/presentation/web/components/features/features-canvas/features-canvas.stories.js +31 -1
  43. package/dist/src/presentation/web/components/layouts/app-shell/app-shell.d.ts.map +1 -1
  44. package/dist/src/presentation/web/components/layouts/app-shell/app-shell.js +2 -1
  45. package/dist/src/presentation/web/dev-server.js +14 -0
  46. package/dist/src/presentation/web/hooks/use-agent-events.d.ts +7 -0
  47. package/dist/src/presentation/web/hooks/use-agent-events.d.ts.map +1 -1
  48. package/dist/src/presentation/web/hooks/use-agent-events.js +135 -47
  49. package/dist/src/presentation/web/hooks/use-notifications.d.ts.map +1 -1
  50. package/dist/src/presentation/web/hooks/use-notifications.js +56 -16
  51. package/dist/src/presentation/web/hooks/use-notifications.stories.d.ts.map +1 -1
  52. package/dist/src/presentation/web/hooks/use-notifications.stories.js +28 -9
  53. package/dist/src/presentation/web/hooks/use-sound-enabled.d.ts +6 -0
  54. package/dist/src/presentation/web/hooks/use-sound-enabled.d.ts.map +1 -0
  55. package/dist/src/presentation/web/hooks/use-sound-enabled.js +27 -0
  56. package/dist/src/presentation/web/hooks/use-sound.d.ts +15 -0
  57. package/dist/src/presentation/web/hooks/use-sound.d.ts.map +1 -0
  58. package/dist/src/presentation/web/hooks/use-sound.js +83 -0
  59. package/dist/src/presentation/web/hooks/use-sound.stories.d.ts +8 -0
  60. package/dist/src/presentation/web/hooks/use-sound.stories.d.ts.map +1 -0
  61. package/dist/src/presentation/web/hooks/use-sound.stories.js +67 -0
  62. package/dist/src/presentation/web/public/agent-events-sw.d.ts +35 -0
  63. package/dist/src/presentation/web/public/agent-events-sw.d.ts.map +1 -0
  64. package/dist/src/presentation/web/public/agent-events-sw.js +140 -0
  65. package/dist/tsconfig.build.tsbuildinfo +1 -1
  66. package/package.json +1 -1
  67. package/web/.next/BUILD_ID +1 -1
  68. package/web/.next/app-path-routes-manifest.json +1 -0
  69. package/web/.next/build-manifest.json +2 -2
  70. package/web/.next/cache/.previewinfo +1 -1
  71. package/web/.next/cache/.rscinfo +1 -1
  72. package/web/.next/cache/.tsbuildinfo +1 -1
  73. package/web/.next/cache/config.json +3 -3
  74. package/web/.next/fallback-build-manifest.json +2 -2
  75. package/web/.next/prerender-manifest.json +3 -3
  76. package/web/.next/required-server-files.js +1 -1
  77. package/web/.next/required-server-files.json +1 -1
  78. package/web/.next/routes-manifest.json +6 -0
  79. package/web/.next/server/app/_global-error.html +2 -2
  80. package/web/.next/server/app/_global-error.rsc +1 -1
  81. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  82. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  83. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  84. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  85. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  86. package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  87. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  88. package/web/.next/server/app/api/agent-events/health/route/app-paths-manifest.json +3 -0
  89. package/web/.next/server/app/api/agent-events/health/route/build-manifest.json +11 -0
  90. package/web/.next/server/app/api/agent-events/health/route/server-reference-manifest.json +4 -0
  91. package/web/.next/server/app/api/agent-events/health/route.js +6 -0
  92. package/web/.next/server/app/api/agent-events/health/route.js.map +5 -0
  93. package/web/.next/server/app/api/agent-events/health/route.js.nft.json +1 -0
  94. package/web/.next/server/app/api/agent-events/health/route_client-reference-manifest.js +2 -0
  95. package/web/.next/server/app/api/agent-events/route.js +2 -1
  96. package/web/.next/server/app/api/agent-events/route.js.nft.json +1 -1
  97. package/web/.next/server/app/page/server-reference-manifest.json +14 -14
  98. package/web/.next/server/app/page.js.nft.json +1 -1
  99. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  100. package/web/.next/server/app/version/page.js +1 -1
  101. package/web/.next/server/app/version/page.js.nft.json +1 -1
  102. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  103. package/web/.next/server/app-paths-manifest.json +1 -0
  104. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +6 -0
  105. package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js.map +1 -0
  106. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_agent-events_health_route_actions_4d471160.js +3 -0
  107. package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_agent-events_health_route_actions_4d471160.js.map +1 -0
  108. package/web/.next/server/chunks/[root-of-the-server]__3b22a1eb._.js +3 -0
  109. package/web/.next/server/chunks/[root-of-the-server]__3b22a1eb._.js.map +1 -0
  110. package/web/.next/server/chunks/[root-of-the-server]__d9d410a8._.js +3 -0
  111. package/web/.next/server/chunks/[root-of-the-server]__d9d410a8._.js.map +1 -0
  112. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
  113. package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js.map +1 -1
  114. package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
  115. package/web/.next/{standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js → server/chunks/ssr/[root-of-the-server]__6e8b5181._.js} +2 -2
  116. package/web/.next/server/chunks/ssr/{_1fafa9db._.js → _1632a21f._.js} +2 -2
  117. package/web/.next/server/chunks/ssr/_1632a21f._.js.map +1 -0
  118. package/web/.next/server/chunks/ssr/src_presentation_web_67178b88._.js +1 -1
  119. package/web/.next/server/chunks/ssr/src_presentation_web_67178b88._.js.map +1 -1
  120. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  121. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  122. package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js.map +1 -1
  123. package/web/.next/server/pages/500.html +2 -2
  124. package/web/.next/server/server-reference-manifest.js +1 -1
  125. package/web/.next/server/server-reference-manifest.json +15 -15
  126. package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
  127. package/web/.next/standalone/src/presentation/web/.next/app-path-routes-manifest.json +1 -0
  128. package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
  129. package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
  130. package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
  131. package/web/.next/standalone/src/presentation/web/.next/routes-manifest.json +6 -0
  132. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
  133. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
  134. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  135. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  136. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  137. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  138. package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  139. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  140. package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  141. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route/app-paths-manifest.json +3 -0
  142. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route/build-manifest.json +11 -0
  143. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route/server-reference-manifest.json +4 -0
  144. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route.js +6 -0
  145. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route.js.map +5 -0
  146. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route.js.nft.json +1 -0
  147. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route_client-reference-manifest.js +2 -0
  148. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/route.js +2 -1
  149. package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/route.js.nft.json +1 -1
  150. package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +14 -14
  151. package/web/.next/standalone/src/presentation/web/.next/server/app/page.js.nft.json +1 -1
  152. package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
  153. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js +1 -1
  154. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js.nft.json +1 -1
  155. package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  156. package/web/.next/standalone/src/presentation/web/.next/server/app-paths-manifest.json +1 -0
  157. package/web/.next/standalone/src/presentation/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +6 -0
  158. package/web/.next/standalone/src/presentation/web/.next/server/chunks/744ca_web__next-internal_server_app_api_agent-events_health_route_actions_4d471160.js +3 -0
  159. package/web/.next/standalone/src/presentation/web/.next/server/chunks/[root-of-the-server]__3b22a1eb._.js +3 -0
  160. package/web/.next/standalone/src/presentation/web/.next/server/chunks/[root-of-the-server]__d9d410a8._.js +3 -0
  161. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
  162. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
  163. package/web/.next/{server/chunks/ssr/[root-of-the-server]__249c74f6._.js → standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6e8b5181._.js} +2 -2
  164. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{_1fafa9db._.js → _1632a21f._.js} +2 -2
  165. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_67178b88._.js +1 -1
  166. package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  167. package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
  168. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
  169. package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +15 -15
  170. package/web/.next/standalone/src/presentation/web/app/api/agent-events/health/route.ts +69 -0
  171. package/web/.next/standalone/src/presentation/web/app/api/agent-events/route.ts +258 -54
  172. package/web/.next/standalone/src/presentation/web/app/layout.tsx +14 -0
  173. package/web/.next/standalone/src/presentation/web/components/common/feature-node/derive-feature-state.ts +39 -2
  174. package/web/.next/standalone/src/presentation/web/components/common/repository-node/repository-node.tsx +1 -0
  175. package/web/.next/standalone/src/presentation/web/components/common/sound-toggle/index.ts +1 -0
  176. package/web/.next/standalone/src/presentation/web/components/common/sound-toggle/sound-toggle.stories.tsx +27 -0
  177. package/web/.next/standalone/src/presentation/web/components/common/sound-toggle/sound-toggle.tsx +21 -0
  178. package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +15 -1
  179. package/web/.next/standalone/src/presentation/web/components/features/control-center/use-control-center-state.ts +194 -31
  180. package/web/.next/standalone/src/presentation/web/components/features/features-canvas/features-canvas.stories.tsx +41 -0
  181. package/web/.next/standalone/src/presentation/web/components/features/features-canvas/features-canvas.tsx +1 -1
  182. package/web/.next/standalone/src/presentation/web/components/layouts/app-shell/app-shell.tsx +3 -1
  183. package/web/.next/standalone/src/presentation/web/dev-server.ts +21 -0
  184. package/web/.next/standalone/src/presentation/web/hooks/use-agent-events.ts +161 -52
  185. package/web/.next/standalone/src/presentation/web/hooks/use-notifications.stories.tsx +39 -10
  186. package/web/.next/standalone/src/presentation/web/hooks/use-notifications.ts +68 -15
  187. package/web/.next/standalone/src/presentation/web/hooks/use-sound-enabled.ts +38 -0
  188. package/web/.next/standalone/src/presentation/web/hooks/use-sound.stories.tsx +109 -0
  189. package/web/.next/standalone/src/presentation/web/hooks/use-sound.ts +101 -0
  190. package/web/.next/standalone/src/presentation/web/public/agent-events-sw.js +164 -0
  191. package/web/.next/standalone/src/presentation/web/public/sounds/button.wav +0 -0
  192. package/web/.next/standalone/src/presentation/web/public/sounds/caution.wav +0 -0
  193. package/web/.next/standalone/src/presentation/web/public/sounds/celebration.wav +0 -0
  194. package/web/.next/standalone/src/presentation/web/public/sounds/disabled.wav +0 -0
  195. package/web/.next/standalone/src/presentation/web/public/sounds/notification.wav +0 -0
  196. package/web/.next/standalone/src/presentation/web/public/sounds/progress_loop.wav +0 -0
  197. package/web/.next/standalone/src/presentation/web/public/sounds/ringtone_loop.wav +0 -0
  198. package/web/.next/standalone/src/presentation/web/public/sounds/select.wav +0 -0
  199. package/web/.next/standalone/src/presentation/web/public/sounds/swipe.wav +0 -0
  200. package/web/.next/standalone/src/presentation/web/public/sounds/swipe_01.wav +0 -0
  201. package/web/.next/standalone/src/presentation/web/public/sounds/swipe_02.wav +0 -0
  202. package/web/.next/standalone/src/presentation/web/public/sounds/swipe_03.wav +0 -0
  203. package/web/.next/standalone/src/presentation/web/public/sounds/swipe_04.wav +0 -0
  204. package/web/.next/standalone/src/presentation/web/public/sounds/swipe_05.wav +0 -0
  205. package/web/.next/standalone/src/presentation/web/public/sounds/tap_01.wav +0 -0
  206. package/web/.next/standalone/src/presentation/web/public/sounds/tap_02.wav +0 -0
  207. package/web/.next/standalone/src/presentation/web/public/sounds/tap_03.wav +0 -0
  208. package/web/.next/standalone/src/presentation/web/public/sounds/tap_04.wav +0 -0
  209. package/web/.next/standalone/src/presentation/web/public/sounds/tap_05.wav +0 -0
  210. package/web/.next/standalone/src/presentation/web/public/sounds/toggle_off.wav +0 -0
  211. package/web/.next/standalone/src/presentation/web/public/sounds/toggle_on.wav +0 -0
  212. package/web/.next/standalone/src/presentation/web/public/sounds/transition_down.wav +0 -0
  213. package/web/.next/standalone/src/presentation/web/public/sounds/transition_up.wav +0 -0
  214. package/web/.next/standalone/src/presentation/web/public/sounds/type_01.wav +0 -0
  215. package/web/.next/standalone/src/presentation/web/public/sounds/type_02.wav +0 -0
  216. package/web/.next/standalone/src/presentation/web/public/sounds/type_03.wav +0 -0
  217. package/web/.next/standalone/src/presentation/web/public/sounds/type_04.wav +0 -0
  218. package/web/.next/standalone/src/presentation/web/public/sounds/type_05.wav +0 -0
  219. package/web/.next/standalone/src/presentation/web/server.js +1 -1
  220. package/web/.next/static/chunks/1f13bc20e938335c.css +2 -0
  221. package/web/.next/static/chunks/702384d364482e4b.js +10 -0
  222. package/web/.next/static/chunks/{68fb91b6eca14536.js → 8f4461aee9a05741.js} +2 -2
  223. package/web/.next/static/chunks/9a82d8c76f175235.js +1 -0
  224. package/web/.next/trace +1 -1
  225. package/web/.next/trace-build +1 -1
  226. package/web/.next/types/link.d.ts +1 -0
  227. package/web/.next/types/routes.d.ts +2 -1
  228. package/web/.next/types/validator.ts +9 -0
  229. package/web/public/agent-events-sw.js +164 -0
  230. package/web/public/sounds/button.wav +0 -0
  231. package/web/public/sounds/caution.wav +0 -0
  232. package/web/public/sounds/celebration.wav +0 -0
  233. package/web/public/sounds/disabled.wav +0 -0
  234. package/web/public/sounds/notification.wav +0 -0
  235. package/web/public/sounds/progress_loop.wav +0 -0
  236. package/web/public/sounds/ringtone_loop.wav +0 -0
  237. package/web/public/sounds/select.wav +0 -0
  238. package/web/public/sounds/swipe.wav +0 -0
  239. package/web/public/sounds/swipe_01.wav +0 -0
  240. package/web/public/sounds/swipe_02.wav +0 -0
  241. package/web/public/sounds/swipe_03.wav +0 -0
  242. package/web/public/sounds/swipe_04.wav +0 -0
  243. package/web/public/sounds/swipe_05.wav +0 -0
  244. package/web/public/sounds/tap_01.wav +0 -0
  245. package/web/public/sounds/tap_02.wav +0 -0
  246. package/web/public/sounds/tap_03.wav +0 -0
  247. package/web/public/sounds/tap_04.wav +0 -0
  248. package/web/public/sounds/tap_05.wav +0 -0
  249. package/web/public/sounds/toggle_off.wav +0 -0
  250. package/web/public/sounds/toggle_on.wav +0 -0
  251. package/web/public/sounds/transition_down.wav +0 -0
  252. package/web/public/sounds/transition_up.wav +0 -0
  253. package/web/public/sounds/type_01.wav +0 -0
  254. package/web/public/sounds/type_02.wav +0 -0
  255. package/web/public/sounds/type_03.wav +0 -0
  256. package/web/public/sounds/type_04.wav +0 -0
  257. package/web/public/sounds/type_05.wav +0 -0
  258. package/web/.next/server/chunks/[root-of-the-server]__7d5c8b74._.js +0 -6
  259. package/web/.next/server/chunks/[root-of-the-server]__7d5c8b74._.js.map +0 -1
  260. package/web/.next/server/chunks/ssr/_1fafa9db._.js.map +0 -1
  261. package/web/.next/standalone/src/presentation/web/.next/server/chunks/[root-of-the-server]__7d5c8b74._.js +0 -6
  262. package/web/.next/static/chunks/212e3c682d3b7f50.js +0 -10
  263. package/web/.next/static/chunks/22f30ce91a3c79c1.js +0 -1
  264. package/web/.next/static/chunks/476fe030d242c153.css +0 -2
  265. /package/web/.next/server/chunks/ssr/{[root-of-the-server]__249c74f6._.js.map → [root-of-the-server]__6e8b5181._.js.map} +0 -0
  266. /package/web/.next/static/{EWkET8BUeCcZJxcYQpxDY → RwzxYU9D2fPc_4j5RYu0v}/_buildManifest.js +0 -0
  267. /package/web/.next/static/{EWkET8BUeCcZJxcYQpxDY → RwzxYU9D2fPc_4j5RYu0v}/_clientMiddlewareManifest.json +0 -0
  268. /package/web/.next/static/{EWkET8BUeCcZJxcYQpxDY → RwzxYU9D2fPc_4j5RYu0v}/_ssgManifest.js +0 -0
@@ -15,66 +15,178 @@ export interface UseAgentEventsResult {
15
15
  connectionStatus: ConnectionStatus;
16
16
  }
17
17
 
18
- const BASE_BACKOFF_MS = 1000;
19
- const MAX_BACKOFF_MS = 30_000;
20
- /** Only reset backoff after connection is stable for this long */
21
- const STABLE_CONNECTION_MS = 5_000;
22
-
18
+ const SW_PATH = '/agent-events-sw.js';
19
+
20
+ /**
21
+ * Hook that receives real-time agent notification events via a Service Worker.
22
+ *
23
+ * The SW maintains a single SSE connection to /api/agent-events and
24
+ * broadcasts events to all open tabs. Falls back to direct EventSource
25
+ * when Service Workers are unavailable.
26
+ */
23
27
  export function useAgentEvents(options?: UseAgentEventsOptions): UseAgentEventsResult {
24
28
  const [events, setEvents] = useState<NotificationEvent[]>([]);
25
29
  const [lastEvent, setLastEvent] = useState<NotificationEvent | null>(null);
26
30
  const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>('disconnected');
27
31
 
28
- const backoffRef = useRef(BASE_BACKOFF_MS);
29
- const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
30
- const stableTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
31
- const esRef = useRef<EventSource | null>(null);
32
-
33
32
  const runId = options?.runId;
33
+ const swRef = useRef<ServiceWorker | null>(null);
34
+
35
+ const onMessage = useCallback((event: MessageEvent) => {
36
+ const msg = event.data;
37
+ if (!msg || typeof msg !== 'object') return;
38
+
39
+ if (msg.type === 'notification') {
40
+ const parsed = msg.data as NotificationEvent;
41
+ setEvents((prev) => [...prev, parsed]);
42
+ setLastEvent(parsed);
43
+ } else if (msg.type === 'status') {
44
+ setConnectionStatus(msg.status as ConnectionStatus);
45
+ }
46
+ }, []);
47
+
48
+ useEffect(() => {
49
+ if (typeof window === 'undefined') return;
50
+
51
+ // ?sse=direct bypasses the Service Worker for debugging
52
+ const bypassSW =
53
+ typeof location !== 'undefined' && new URLSearchParams(location.search).has('sse');
54
+
55
+ // Fallback: direct EventSource when SW not supported or bypassed
56
+ if (!navigator.serviceWorker || bypassSW) {
57
+ return connectDirectEventSource(runId, setEvents, setLastEvent, setConnectionStatus);
58
+ }
59
+
60
+ let cancelled = false;
61
+ const fallbackCleanupRef = { current: undefined as (() => void) | undefined };
34
62
 
35
- const connect = useCallback(() => {
36
- if (typeof window === 'undefined' || typeof EventSource === 'undefined') {
37
- return;
63
+ function subscribeToWorker(worker: ServiceWorker) {
64
+ if (cancelled) return;
65
+ swRef.current = worker;
66
+ worker.postMessage({ type: 'subscribe', runId });
67
+ setConnectionStatus('connecting');
38
68
  }
39
69
 
40
- // Prevent duplicate connections
41
- if (esRef.current) {
42
- return;
70
+ // Listen for messages from whatever SW controls this page
71
+ navigator.serviceWorker.addEventListener('message', onMessage);
72
+
73
+ // Re-subscribe when SW controller changes (e.g., after SW update via skipWaiting)
74
+ function handleControllerChange() {
75
+ if (cancelled) return;
76
+ const newController = navigator.serviceWorker.controller;
77
+ if (newController) {
78
+ swRef.current = newController;
79
+ newController.postMessage({ type: 'subscribe', runId });
80
+ setConnectionStatus('connecting');
81
+ }
43
82
  }
83
+ navigator.serviceWorker.addEventListener('controllerchange', handleControllerChange);
84
+
85
+ navigator.serviceWorker
86
+ .register(SW_PATH, { scope: '/' })
87
+ .then((registration) => {
88
+ if (cancelled) return;
89
+
90
+ // Use the active worker, or wait for it to activate
91
+ const sw = registration.active ?? registration.installing ?? registration.waiting;
92
+ if (!sw) {
93
+ // No worker at all — fall back to direct EventSource
94
+ fallbackCleanupRef.current = connectDirectEventSource(
95
+ runId,
96
+ setEvents,
97
+ setLastEvent,
98
+ setConnectionStatus
99
+ );
100
+ return;
101
+ }
102
+
103
+ if (sw.state === 'activated') {
104
+ subscribeToWorker(sw);
105
+ } else {
106
+ sw.addEventListener('statechange', () => {
107
+ if (sw.state === 'activated') subscribeToWorker(sw);
108
+ });
109
+ }
110
+ })
111
+ .catch(() => {
112
+ // SW registration failed — fall back to direct EventSource
113
+ if (!cancelled) {
114
+ fallbackCleanupRef.current = connectDirectEventSource(
115
+ runId,
116
+ setEvents,
117
+ setLastEvent,
118
+ setConnectionStatus
119
+ );
120
+ }
121
+ });
122
+
123
+ return () => {
124
+ cancelled = true;
125
+ navigator.serviceWorker.removeEventListener('message', onMessage);
126
+ navigator.serviceWorker.removeEventListener('controllerchange', handleControllerChange);
127
+ swRef.current?.postMessage({ type: 'unsubscribe' });
128
+ swRef.current = null;
129
+ fallbackCleanupRef.current?.();
130
+ };
131
+ }, [runId, onMessage]);
132
+
133
+ return { events, lastEvent, connectionStatus };
134
+ }
135
+
136
+ // --- EventSource fallback (identical to the previous implementation) ---
137
+
138
+ const BASE_BACKOFF_MS = 1000;
139
+ const MAX_BACKOFF_MS = 30_000;
140
+ const STABLE_CONNECTION_MS = 5_000;
141
+
142
+ function connectDirectEventSource(
143
+ runId: string | undefined,
144
+ setEvents: React.Dispatch<React.SetStateAction<NotificationEvent[]>>,
145
+ setLastEvent: React.Dispatch<React.SetStateAction<NotificationEvent | null>>,
146
+ setConnectionStatus: React.Dispatch<React.SetStateAction<ConnectionStatus>>
147
+ ): () => void {
148
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
149
+ if (typeof EventSource === 'undefined') return () => {};
150
+
151
+ let es: EventSource | null = null;
152
+ let stopped = false;
153
+ let backoff = BASE_BACKOFF_MS;
154
+ let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
155
+ let stableTimer: ReturnType<typeof setTimeout> | null = null;
156
+
157
+ function connect() {
158
+ if (stopped) return;
44
159
 
45
160
  const url = runId
46
161
  ? `/api/agent-events?runId=${encodeURIComponent(runId)}`
47
162
  : '/api/agent-events';
48
163
 
49
- const es = new EventSource(url);
50
- esRef.current = es;
164
+ es = new EventSource(url);
51
165
  setConnectionStatus('connecting');
52
166
 
53
167
  es.onopen = () => {
54
168
  setConnectionStatus('connected');
55
- // Only reset backoff after connection stays open for a while
56
- stableTimerRef.current = setTimeout(() => {
57
- stableTimerRef.current = null;
58
- backoffRef.current = BASE_BACKOFF_MS;
169
+ stableTimer = setTimeout(() => {
170
+ stableTimer = null;
171
+ backoff = BASE_BACKOFF_MS;
59
172
  }, STABLE_CONNECTION_MS);
60
173
  };
61
174
 
62
175
  es.onerror = () => {
63
- es.close();
64
- esRef.current = null;
176
+ es?.close();
177
+ es = null;
65
178
  setConnectionStatus('disconnected');
66
179
 
67
- // Cancel stable timer — connection wasn't stable
68
- if (stableTimerRef.current !== null) {
69
- clearTimeout(stableTimerRef.current);
70
- stableTimerRef.current = null;
180
+ if (stableTimer !== null) {
181
+ clearTimeout(stableTimer);
182
+ stableTimer = null;
71
183
  }
72
184
 
73
- const delay = backoffRef.current;
74
- backoffRef.current = Math.min(delay * 2, MAX_BACKOFF_MS);
185
+ const delay = backoff;
186
+ backoff = Math.min(delay * 2, MAX_BACKOFF_MS);
75
187
 
76
- reconnectTimerRef.current = setTimeout(() => {
77
- reconnectTimerRef.current = null;
188
+ reconnectTimer = setTimeout(() => {
189
+ reconnectTimer = null;
78
190
  connect();
79
191
  }, delay);
80
192
  };
@@ -84,26 +196,23 @@ export function useAgentEvents(options?: UseAgentEventsOptions): UseAgentEventsR
84
196
  setEvents((prev) => [...prev, parsed]);
85
197
  setLastEvent(parsed);
86
198
  }) as EventListener);
87
- }, [runId]);
88
-
89
- useEffect(() => {
90
- connect();
199
+ }
91
200
 
92
- return () => {
93
- if (reconnectTimerRef.current !== null) {
94
- clearTimeout(reconnectTimerRef.current);
95
- reconnectTimerRef.current = null;
96
- }
97
- if (stableTimerRef.current !== null) {
98
- clearTimeout(stableTimerRef.current);
99
- stableTimerRef.current = null;
100
- }
101
- if (esRef.current) {
102
- esRef.current.close();
103
- esRef.current = null;
104
- }
105
- };
106
- }, [connect]);
201
+ connect();
107
202
 
108
- return { events, lastEvent, connectionStatus };
203
+ return () => {
204
+ stopped = true;
205
+ if (reconnectTimer !== null) {
206
+ clearTimeout(reconnectTimer);
207
+ reconnectTimer = null;
208
+ }
209
+ if (stableTimer !== null) {
210
+ clearTimeout(stableTimer);
211
+ stableTimer = null;
212
+ }
213
+ if (es) {
214
+ es.close();
215
+ es = null;
216
+ }
217
+ };
109
218
  }
@@ -1,12 +1,20 @@
1
- import { useState } from 'react';
1
+ import { useCallback, useMemo, useState } from 'react';
2
2
  import type { Meta, StoryObj } from '@storybook/react';
3
3
  import { toast } from 'sonner';
4
4
  import { Toaster } from '@/components/ui/sonner';
5
5
  import { Button } from '@/components/ui/button';
6
+ import { useSound } from './use-sound';
6
7
 
7
8
  /** Mirrors NotificationSeverity enum values (inlined to avoid @shepai/core import in Storybook). */
8
9
  type Severity = 'info' | 'warning' | 'success' | 'error';
9
10
 
11
+ const SEVERITY_SOUNDS: Record<Severity, string> = {
12
+ success: 'celebration',
13
+ error: 'caution',
14
+ warning: 'notification',
15
+ info: 'button',
16
+ };
17
+
10
18
  interface DemoEvent {
11
19
  eventType: string;
12
20
  severity: Severity;
@@ -15,16 +23,37 @@ interface DemoEvent {
15
23
  phaseName?: string;
16
24
  }
17
25
 
18
- function simulateNotification(event: DemoEvent): void {
19
- const method: 'success' | 'error' | 'warning' | 'info' = event.severity ?? 'info';
20
- toast[method](event.featureName, { description: event.message });
26
+ function NotificationDemo() {
27
+ const successSound = useSound('celebration', { volume: 0.5 });
28
+ const errorSound = useSound('caution', { volume: 0.5 });
29
+ const warningSound = useSound('notification', { volume: 0.5 });
30
+ const infoSound = useSound('button', { volume: 0.5 });
21
31
 
22
- if (globalThis.Notification?.permission === 'granted') {
23
- new Notification(event.featureName, { body: event.message });
24
- }
25
- }
32
+ const soundsByName = useMemo<Record<string, { play: () => void }>>(
33
+ () => ({
34
+ celebration: successSound,
35
+ caution: errorSound,
36
+ notification: warningSound,
37
+ button: infoSound,
38
+ }),
39
+ [successSound, errorSound, warningSound, infoSound]
40
+ );
41
+
42
+ const simulateNotification = useCallback(
43
+ (event: DemoEvent) => {
44
+ const method = event.severity ?? 'info';
45
+ toast[method](event.featureName, { description: event.message });
46
+
47
+ if (globalThis.Notification?.permission === 'granted') {
48
+ new Notification(event.featureName, { body: event.message });
49
+ }
50
+
51
+ const soundName = SEVERITY_SOUNDS[event.severity];
52
+ soundsByName[soundName]?.play();
53
+ },
54
+ [soundsByName]
55
+ );
26
56
 
27
- function NotificationDemo() {
28
57
  const [browserPermission, setBrowserPermission] = useState<NotificationPermission>(
29
58
  typeof globalThis.Notification !== 'undefined' ? globalThis.Notification.permission : 'default'
30
59
  );
@@ -52,7 +81,7 @@ function NotificationDemo() {
52
81
  <div className="flex flex-col gap-2">
53
82
  <h3 className="text-sm font-semibold">Simulate Notification Events</h3>
54
83
  <p className="text-muted-foreground text-xs">
55
- Click a button to fire a toast and browser notification.
84
+ Click a button to fire a toast, browser notification, and sound.
56
85
  </p>
57
86
  <div className="flex flex-wrap gap-2">
58
87
  <Button
@@ -1,10 +1,11 @@
1
1
  'use client';
2
2
 
3
- import { useCallback, useEffect, useRef, useState } from 'react';
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { toast } from 'sonner';
5
5
  import type { NotificationEvent } from '@shepai/core/domain/generated/output';
6
- import { NotificationSeverity } from '@shepai/core/domain/generated/output';
6
+ import { NotificationEventType, NotificationSeverity } from '@shepai/core/domain/generated/output';
7
7
  import { useAgentEventsContext } from './agent-events-provider';
8
+ import { useSound } from './use-sound';
8
9
 
9
10
  export interface UseNotificationsResult {
10
11
  requestBrowserPermission: () => Promise<void>;
@@ -20,18 +21,56 @@ const SEVERITY_TO_TOAST: Record<NotificationSeverity, 'success' | 'error' | 'war
20
21
 
21
22
  function dispatchToast(event: NotificationEvent): void {
22
23
  const method = SEVERITY_TO_TOAST[event.severity] ?? 'info';
23
- toast[method](event.featureName, { description: event.message });
24
+ const isActionable = event.eventType === NotificationEventType.WaitingApproval;
25
+ toast[method](event.featureName, {
26
+ description: event.message,
27
+ ...(isActionable && {
28
+ action: {
29
+ label: 'Review',
30
+ onClick: () => {
31
+ window.dispatchEvent(
32
+ new CustomEvent('shep:select-feature', { detail: { featureId: event.featureId } })
33
+ );
34
+ },
35
+ },
36
+ }),
37
+ });
24
38
  }
25
39
 
26
40
  function dispatchBrowserNotification(event: NotificationEvent): void {
27
41
  if (globalThis.Notification?.permission !== 'granted') {
28
42
  return;
29
43
  }
30
- new Notification(event.featureName, { body: event.message });
44
+ new Notification(`Shep: ${event.featureName}`, {
45
+ body: event.message,
46
+ icon: '/favicon-light.svg',
47
+ });
31
48
  }
32
49
 
50
+ const SEVERITY_TO_SOUND = {
51
+ [NotificationSeverity.Success]: 'celebration',
52
+ [NotificationSeverity.Error]: 'caution',
53
+ [NotificationSeverity.Warning]: 'notification',
54
+ [NotificationSeverity.Info]: 'button',
55
+ } as const;
56
+
33
57
  export function useNotifications(): UseNotificationsResult {
34
- const { lastEvent } = useAgentEventsContext();
58
+ const { events } = useAgentEventsContext();
59
+
60
+ const successSound = useSound('celebration', { volume: 0.5 });
61
+ const errorSound = useSound('caution', { volume: 0.5 });
62
+ const warningSound = useSound('notification', { volume: 0.5 });
63
+ const infoSound = useSound('button', { volume: 0.5 });
64
+
65
+ const soundsByName = useMemo<Record<string, { play: () => void }>>(
66
+ () => ({
67
+ celebration: successSound,
68
+ caution: errorSound,
69
+ notification: warningSound,
70
+ button: infoSound,
71
+ }),
72
+ [successSound, errorSound, warningSound, infoSound]
73
+ );
35
74
 
36
75
  const [browserPermissionState, setBrowserPermissionState] = useState<NotificationPermission>(
37
76
  () => {
@@ -40,20 +79,34 @@ export function useNotifications(): UseNotificationsResult {
40
79
  }
41
80
  );
42
81
 
43
- // Track which events we've already dispatched to avoid re-dispatching on re-render
44
- const processedRef = useRef<Set<string>>(new Set());
82
+ // Track how many events from the array we've already processed.
83
+ // Using the array index (instead of lastEvent) prevents React batching
84
+ // from silently dropping events when multiple SSE messages arrive together.
85
+ const processedCountRef = useRef(0);
45
86
 
46
87
  useEffect(() => {
47
- if (!lastEvent) return;
88
+ if (events.length <= processedCountRef.current) return;
48
89
 
49
- // Create a key for deduplication
50
- const key = `${lastEvent.agentRunId}-${lastEvent.eventType}-${lastEvent.timestamp}`;
51
- if (processedRef.current.has(key)) return;
52
- processedRef.current.add(key);
90
+ const newEvents = events.slice(processedCountRef.current);
91
+ processedCountRef.current = events.length;
53
92
 
54
- dispatchToast(lastEvent);
55
- dispatchBrowserNotification(lastEvent);
56
- }, [lastEvent]);
93
+ for (const event of newEvents) {
94
+ // Only notify for actionable events and completion celebrations
95
+ if (
96
+ event.severity !== NotificationSeverity.Error &&
97
+ event.severity !== NotificationSeverity.Warning &&
98
+ event.severity !== NotificationSeverity.Success
99
+ ) {
100
+ continue;
101
+ }
102
+
103
+ dispatchToast(event);
104
+ dispatchBrowserNotification(event);
105
+
106
+ const soundName = SEVERITY_TO_SOUND[event.severity];
107
+ soundsByName[soundName]?.play();
108
+ }
109
+ }, [events, soundsByName]);
57
110
 
58
111
  const requestBrowserPermission = useCallback(async () => {
59
112
  if (typeof globalThis.Notification === 'undefined') return;
@@ -0,0 +1,38 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useEffect, useState } from 'react';
4
+
5
+ const STORAGE_KEY = 'shep-sound-enabled';
6
+ const SYNC_EVENT = 'shep:sound-toggle';
7
+
8
+ export interface UseSoundEnabledResult {
9
+ enabled: boolean;
10
+ toggle: () => void;
11
+ }
12
+
13
+ export function useSoundEnabled(): UseSoundEnabledResult {
14
+ const [enabled, setEnabled] = useState(true);
15
+
16
+ useEffect(() => {
17
+ const stored = localStorage.getItem(STORAGE_KEY);
18
+ if (stored === 'false') setEnabled(false);
19
+
20
+ // Sync across all useSoundEnabled instances in the same tab
21
+ const onSync = (e: Event) => {
22
+ setEnabled((e as CustomEvent<boolean>).detail);
23
+ };
24
+ window.addEventListener(SYNC_EVENT, onSync);
25
+ return () => window.removeEventListener(SYNC_EVENT, onSync);
26
+ }, []);
27
+
28
+ const toggle = useCallback(() => {
29
+ setEnabled((prev) => {
30
+ const next = !prev;
31
+ localStorage.setItem(STORAGE_KEY, String(next));
32
+ window.dispatchEvent(new CustomEvent(SYNC_EVENT, { detail: next }));
33
+ return next;
34
+ });
35
+ }, []);
36
+
37
+ return { enabled, toggle };
38
+ }
@@ -0,0 +1,109 @@
1
+ import { useState } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { Button } from '@/components/ui/button';
4
+ import { useSound, SOUND_NAMES, type SoundName } from './use-sound';
5
+
6
+ /* ------------------------------------------------------------------ */
7
+ /* Helpers */
8
+ /* ------------------------------------------------------------------ */
9
+
10
+ const CATEGORIES: Record<string, SoundName[]> = {
11
+ 'UI Actions': ['button', 'select', 'disabled'],
12
+ Toggles: ['toggle_on', 'toggle_off'],
13
+ Transitions: ['transition_up', 'transition_down'],
14
+ Alerts: ['notification', 'caution', 'celebration'],
15
+ Swipes: ['swipe', 'swipe_01', 'swipe_02', 'swipe_03', 'swipe_04', 'swipe_05'],
16
+ Taps: ['tap_01', 'tap_02', 'tap_03', 'tap_04', 'tap_05'],
17
+ Typing: ['type_01', 'type_02', 'type_03', 'type_04', 'type_05'],
18
+ Loops: ['progress_loop', 'ringtone_loop'],
19
+ };
20
+
21
+ /* ------------------------------------------------------------------ */
22
+ /* Single sound player (used per button) */
23
+ /* ------------------------------------------------------------------ */
24
+
25
+ function SoundButton({ name, volume }: { name: SoundName; volume: number }) {
26
+ const isLoop = name.includes('loop');
27
+ const { play, stop } = useSound(name, { volume, loop: isLoop });
28
+ const [playing, setPlaying] = useState(false);
29
+
30
+ const handleClick = () => {
31
+ if (isLoop && playing) {
32
+ stop();
33
+ setPlaying(false);
34
+ } else {
35
+ play();
36
+ setPlaying(true);
37
+ if (!isLoop) {
38
+ setTimeout(() => setPlaying(false), 2000);
39
+ }
40
+ }
41
+ };
42
+
43
+ return (
44
+ <Button
45
+ size="sm"
46
+ variant={playing ? 'default' : 'outline'}
47
+ onClick={handleClick}
48
+ className="min-w-[120px] font-mono text-xs"
49
+ >
50
+ {isLoop && playing ? `Stop ${name}` : name}
51
+ </Button>
52
+ );
53
+ }
54
+
55
+ /* ------------------------------------------------------------------ */
56
+ /* Full catalog demo */
57
+ /* ------------------------------------------------------------------ */
58
+
59
+ function SoundCatalog({ volume }: { volume: number }) {
60
+ return (
61
+ <div className="flex w-[560px] flex-col gap-6">
62
+ <div className="flex flex-col gap-1">
63
+ <h2 className="text-base font-semibold">Sound Library</h2>
64
+ <p className="text-muted-foreground text-sm">
65
+ {SOUND_NAMES.length} sounds available at{' '}
66
+ <code className="bg-muted rounded px-1 py-0.5 text-xs">/sounds/*.wav</code>
67
+ </p>
68
+ </div>
69
+
70
+ {Object.entries(CATEGORIES).map(([category, sounds]) => (
71
+ <div key={category} className="flex flex-col gap-2">
72
+ <h3 className="text-sm font-semibold">{category}</h3>
73
+ <div className="flex flex-wrap gap-2">
74
+ {sounds.map((name) => (
75
+ <SoundButton key={name} name={name} volume={volume} />
76
+ ))}
77
+ </div>
78
+ </div>
79
+ ))}
80
+ </div>
81
+ );
82
+ }
83
+
84
+ /* ------------------------------------------------------------------ */
85
+ /* Storybook meta */
86
+ /* ------------------------------------------------------------------ */
87
+
88
+ const meta: Meta = {
89
+ title: 'Hooks/useSound',
90
+ parameters: { layout: 'centered' },
91
+ tags: ['autodocs'],
92
+ argTypes: {
93
+ volume: {
94
+ control: { type: 'range', min: 0, max: 1, step: 0.1 },
95
+ description: 'Playback volume (0 to 1)',
96
+ defaultValue: 0.5,
97
+ },
98
+ },
99
+ args: {
100
+ volume: 0.5,
101
+ },
102
+ };
103
+
104
+ export default meta;
105
+ type Story = StoryObj<{ volume: number }>;
106
+
107
+ export const Catalog: Story = {
108
+ render: ({ volume }) => <SoundCatalog volume={volume} />,
109
+ };