vibepulse 0.2.2 → 0.3.0-beta.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 (253) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +1 -0
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/cache/.previewinfo +1 -1
  5. package/.next/cache/.rscinfo +1 -1
  6. package/.next/cache/.tsbuildinfo +1 -1
  7. package/.next/cache/config.json +3 -3
  8. package/.next/fallback-build-manifest.json +2 -2
  9. package/.next/prerender-manifest.json +3 -3
  10. package/.next/routes-manifest.json +8 -0
  11. package/.next/server/app/_global-error/page.js +1 -1
  12. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  13. package/.next/server/app/_global-error.html +2 -2
  14. package/.next/server/app/_global-error.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  16. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  17. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  18. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  19. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  20. package/.next/server/app/_not-found/page.js +1 -1
  21. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/.next/server/app/_not-found.html +1 -1
  24. package/.next/server/app/_not-found.rsc +2 -2
  25. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  26. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  27. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  28. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  29. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  30. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  31. package/.next/server/app/api/node/sessions/[id]/open-editor/route.js.nft.json +1 -1
  32. package/.next/server/app/api/node/sessions/route.js +5 -3
  33. package/.next/server/app/api/node/sessions/route.js.nft.json +1 -1
  34. package/.next/server/app/api/opencode-config/route.js.nft.json +1 -1
  35. package/.next/server/app/api/opencode-config/status/route.js.nft.json +1 -1
  36. package/.next/server/app/api/profiles/[id]/apply/route.js.nft.json +1 -1
  37. package/.next/server/app/api/profiles/[id]/export/route.js.nft.json +1 -1
  38. package/.next/server/app/api/profiles/[id]/route.js.nft.json +1 -1
  39. package/.next/server/app/api/profiles/import/route.js.nft.json +1 -1
  40. package/.next/server/app/api/profiles/route.js.nft.json +1 -1
  41. package/.next/server/app/api/sessions/[id]/archive/route.js +3 -2
  42. package/.next/server/app/api/sessions/[id]/archive/route.js.nft.json +1 -1
  43. package/.next/server/app/api/sessions/[id]/delete/route.js +3 -2
  44. package/.next/server/app/api/sessions/[id]/delete/route.js.nft.json +1 -1
  45. package/.next/server/app/api/sessions/[id]/open-editor/route.js +1 -1
  46. package/.next/server/app/api/sessions/[id]/open-editor/route.js.nft.json +1 -1
  47. package/.next/server/app/api/sessions/[id]/restore/route/app-paths-manifest.json +3 -0
  48. package/.next/server/app/api/sessions/[id]/restore/route/build-manifest.json +11 -0
  49. package/.next/server/app/api/sessions/[id]/restore/route/server-reference-manifest.json +4 -0
  50. package/.next/server/app/api/sessions/[id]/restore/route.js +8 -0
  51. package/.next/server/app/api/sessions/[id]/restore/route.js.map +5 -0
  52. package/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -0
  53. package/.next/server/app/api/sessions/[id]/restore/route_client-reference-manifest.js +2 -0
  54. package/.next/server/app/api/sessions/route.js +4 -2
  55. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  56. package/.next/server/app/index.html +1 -1
  57. package/.next/server/app/index.rsc +3 -3
  58. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  59. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  60. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  61. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  62. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/page_client-reference-manifest.js +1 -1
  64. package/.next/server/app-paths-manifest.json +1 -0
  65. package/.next/server/chunks/[root-of-the-server]__31d19c5c._.js +3 -0
  66. package/.next/server/chunks/[root-of-the-server]__31d19c5c._.js.map +1 -0
  67. package/.next/server/chunks/[root-of-the-server]__56f5f249._.js +1 -1
  68. package/.next/server/chunks/[root-of-the-server]__56f5f249._.js.map +1 -1
  69. package/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js +3 -0
  70. package/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js.map +1 -0
  71. package/.next/server/chunks/[root-of-the-server]__98073dd6._.js +3 -0
  72. package/.next/server/chunks/[root-of-the-server]__98073dd6._.js.map +1 -0
  73. package/.next/server/chunks/[root-of-the-server]__b7b717eb._.js +3 -0
  74. package/.next/server/chunks/[root-of-the-server]__b7b717eb._.js.map +1 -0
  75. package/.next/server/chunks/[root-of-the-server]__d8e61048._.js +1 -1
  76. package/.next/server/chunks/[root-of-the-server]__d8e61048._.js.map +1 -1
  77. package/.next/server/chunks/[root-of-the-server]__f441109e._.js +3 -0
  78. package/.next/server/chunks/[root-of-the-server]__f441109e._.js.map +1 -0
  79. package/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js +3 -0
  80. package/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js.map +1 -0
  81. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js +3 -0
  82. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js.map +1 -0
  83. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js +3 -0
  84. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js.map +1 -0
  85. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js +1 -1
  86. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js.map +1 -1
  87. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js +1 -1
  88. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js.map +1 -1
  89. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js +3 -0
  90. package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js.map +1 -0
  91. package/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js +3 -0
  92. package/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js.map +1 -0
  93. package/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js → [root-of-the-server]__c91a8380._.js} +2 -2
  94. package/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js.map → [root-of-the-server]__c91a8380._.js.map} +1 -1
  95. package/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js +3 -3
  96. package/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js.map +1 -1
  97. package/.next/server/pages/404.html +1 -1
  98. package/.next/server/pages/500.html +2 -2
  99. package/.next/server/server-reference-manifest.js +1 -1
  100. package/.next/server/server-reference-manifest.json +1 -1
  101. package/.next/standalone/.next/BUILD_ID +1 -1
  102. package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
  103. package/.next/standalone/.next/build-manifest.json +2 -2
  104. package/.next/standalone/.next/prerender-manifest.json +3 -3
  105. package/.next/standalone/.next/routes-manifest.json +8 -0
  106. package/.next/standalone/.next/server/app/_global-error/page.js +1 -1
  107. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  108. package/.next/standalone/.next/server/app/_global-error.html +2 -2
  109. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  110. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  111. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  112. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  113. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  114. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  115. package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
  116. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  117. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  118. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  119. package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
  120. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  121. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  122. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  123. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  124. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  125. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  126. package/.next/standalone/.next/server/app/api/node/sessions/[id]/open-editor/route.js.nft.json +1 -1
  127. package/.next/standalone/.next/server/app/api/node/sessions/route.js +5 -3
  128. package/.next/standalone/.next/server/app/api/node/sessions/route.js.nft.json +1 -1
  129. package/.next/standalone/.next/server/app/api/opencode-config/route.js.nft.json +1 -1
  130. package/.next/standalone/.next/server/app/api/opencode-config/status/route.js.nft.json +1 -1
  131. package/.next/standalone/.next/server/app/api/profiles/[id]/apply/route.js.nft.json +1 -1
  132. package/.next/standalone/.next/server/app/api/profiles/[id]/export/route.js.nft.json +1 -1
  133. package/.next/standalone/.next/server/app/api/profiles/[id]/route.js.nft.json +1 -1
  134. package/.next/standalone/.next/server/app/api/profiles/import/route.js.nft.json +1 -1
  135. package/.next/standalone/.next/server/app/api/profiles/route.js.nft.json +1 -1
  136. package/.next/standalone/.next/server/app/api/sessions/[id]/archive/route.js +3 -2
  137. package/.next/standalone/.next/server/app/api/sessions/[id]/archive/route.js.nft.json +1 -1
  138. package/.next/standalone/.next/server/app/api/sessions/[id]/delete/route.js +3 -2
  139. package/.next/standalone/.next/server/app/api/sessions/[id]/delete/route.js.nft.json +1 -1
  140. package/.next/standalone/.next/server/app/api/sessions/[id]/open-editor/route.js +1 -1
  141. package/.next/standalone/.next/server/app/api/sessions/[id]/open-editor/route.js.nft.json +1 -1
  142. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/app-paths-manifest.json +3 -0
  143. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/build-manifest.json +11 -0
  144. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/server-reference-manifest.json +4 -0
  145. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js +8 -0
  146. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js.map +5 -0
  147. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -0
  148. package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route_client-reference-manifest.js +2 -0
  149. package/.next/standalone/.next/server/app/api/sessions/route.js +4 -2
  150. package/.next/standalone/.next/server/app/api/sessions/route.js.nft.json +1 -1
  151. package/.next/standalone/.next/server/app/index.html +1 -1
  152. package/.next/standalone/.next/server/app/index.rsc +3 -3
  153. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  154. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  155. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  156. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
  157. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  158. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  159. package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
  160. package/.next/standalone/.next/server/chunks/[root-of-the-server]__31d19c5c._.js +3 -0
  161. package/.next/standalone/.next/server/chunks/[root-of-the-server]__56f5f249._.js +1 -1
  162. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js +3 -0
  163. package/.next/standalone/.next/server/chunks/[root-of-the-server]__98073dd6._.js +3 -0
  164. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b7b717eb._.js +3 -0
  165. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d8e61048._.js +1 -1
  166. package/.next/standalone/.next/server/chunks/[root-of-the-server]__f441109e._.js +3 -0
  167. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js +3 -0
  168. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js +3 -0
  169. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js +3 -0
  170. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js +1 -1
  171. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js +1 -1
  172. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js +3 -0
  173. package/.next/standalone/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js +3 -0
  174. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js → [root-of-the-server]__c91a8380._.js} +2 -2
  175. package/.next/standalone/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js +3 -3
  176. package/.next/standalone/.next/server/pages/404.html +1 -1
  177. package/.next/standalone/.next/server/pages/500.html +2 -2
  178. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  179. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  180. package/.next/standalone/.next/static/chunks/b3bc362202331708.css +3 -0
  181. package/.next/standalone/.next/static/chunks/{65d5354ba0add961.js → c1294e057d8d4681.js} +3 -3
  182. package/.next/standalone/README.md +29 -5
  183. package/.next/standalone/docs/session-status-detection.md +36 -0
  184. package/.next/standalone/docs/superpowers/specs/2026-04-09-claude-capability-alignment-design.md +39 -0
  185. package/.next/standalone/package-lock.json +2 -2
  186. package/.next/standalone/package.json +1 -1
  187. package/.next/standalone/src/app/api/node/sessions/[id]/archive/route.test.ts +60 -1
  188. package/.next/standalone/src/app/api/node/sessions/[id]/archive/route.ts +77 -22
  189. package/.next/standalone/src/app/api/node/sessions/route.test.ts +282 -0
  190. package/.next/standalone/src/app/api/node/sessions/route.ts +141 -17
  191. package/.next/standalone/src/app/api/opencode-events/route.test.ts +3 -1
  192. package/.next/standalone/src/app/api/sessions/[id]/archive/route.test.ts +101 -0
  193. package/.next/standalone/src/app/api/sessions/[id]/archive/route.ts +47 -12
  194. package/.next/standalone/src/app/api/sessions/[id]/delete/route.test.ts +92 -0
  195. package/.next/standalone/src/app/api/sessions/[id]/delete/route.ts +45 -10
  196. package/.next/standalone/src/app/api/sessions/[id]/open-editor/route.test.ts +74 -0
  197. package/.next/standalone/src/app/api/sessions/[id]/open-editor/route.ts +22 -2
  198. package/.next/standalone/src/app/api/sessions/[id]/restore/route.test.ts +186 -0
  199. package/.next/standalone/src/app/api/sessions/[id]/restore/route.ts +184 -0
  200. package/.next/standalone/src/app/api/sessions/route.test.ts +1889 -107
  201. package/.next/standalone/src/app/api/sessions/route.ts +365 -981
  202. package/.next/standalone/src/components/KanbanBoard.test.tsx +307 -1
  203. package/.next/standalone/src/components/KanbanBoard.tsx +105 -18
  204. package/.next/standalone/src/components/ProjectCard.test.tsx +416 -2
  205. package/.next/standalone/src/components/ProjectCard.tsx +238 -86
  206. package/.next/standalone/src/components/SessionCard.test.tsx +253 -2
  207. package/.next/standalone/src/components/SessionCard.tsx +182 -76
  208. package/.next/standalone/src/hooks/useOpencodeSync.test.ts +321 -1
  209. package/.next/standalone/src/hooks/useOpencodeSync.ts +16 -12
  210. package/.next/standalone/src/lib/claudeSessionOverrides.test.ts +75 -0
  211. package/.next/standalone/src/lib/claudeSessionOverrides.ts +169 -0
  212. package/.next/standalone/src/lib/session-providers/claudeCode.test.ts +2288 -0
  213. package/.next/standalone/src/lib/session-providers/claudeCode.ts +1083 -0
  214. package/.next/standalone/src/lib/session-providers/localAggregator.test.ts +322 -0
  215. package/.next/standalone/src/lib/session-providers/localAggregator.ts +302 -0
  216. package/.next/standalone/src/lib/session-providers/opencodeProvider.ts +723 -0
  217. package/.next/standalone/src/lib/session-providers/providerIds.test.ts +337 -0
  218. package/.next/standalone/src/lib/session-providers/providerIds.ts +176 -0
  219. package/.next/standalone/src/lib/session-providers/types.ts +131 -0
  220. package/.next/standalone/src/lib/transform.test.ts +253 -0
  221. package/.next/standalone/src/lib/transform.ts +96 -37
  222. package/.next/standalone/src/types/index.ts +23 -17
  223. package/.next/static/chunks/b3bc362202331708.css +3 -0
  224. package/.next/static/chunks/{65d5354ba0add961.js → c1294e057d8d4681.js} +3 -3
  225. package/.next/trace +1 -1
  226. package/.next/trace-build +1 -1
  227. package/.next/types/routes.d.ts +2 -1
  228. package/.next/types/validator.ts +9 -0
  229. package/README.md +29 -5
  230. package/package.json +1 -1
  231. package/.next/server/chunks/[root-of-the-server]__2f981540._.js +0 -3
  232. package/.next/server/chunks/[root-of-the-server]__2f981540._.js.map +0 -1
  233. package/.next/server/chunks/[root-of-the-server]__3745b314._.js +0 -3
  234. package/.next/server/chunks/[root-of-the-server]__3745b314._.js.map +0 -1
  235. package/.next/server/chunks/[root-of-the-server]__6c428a24._.js +0 -3
  236. package/.next/server/chunks/[root-of-the-server]__6c428a24._.js.map +0 -1
  237. package/.next/server/chunks/[root-of-the-server]__73a00b88._.js +0 -3
  238. package/.next/server/chunks/[root-of-the-server]__73a00b88._.js.map +0 -1
  239. package/.next/server/chunks/[root-of-the-server]__db285678._.js +0 -3
  240. package/.next/server/chunks/[root-of-the-server]__db285678._.js.map +0 -1
  241. package/.next/standalone/.next/server/chunks/[root-of-the-server]__2f981540._.js +0 -3
  242. package/.next/standalone/.next/server/chunks/[root-of-the-server]__3745b314._.js +0 -3
  243. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6c428a24._.js +0 -3
  244. package/.next/standalone/.next/server/chunks/[root-of-the-server]__73a00b88._.js +0 -3
  245. package/.next/standalone/.next/server/chunks/[root-of-the-server]__db285678._.js +0 -3
  246. package/.next/standalone/.next/static/chunks/f42202943f6742e5.css +0 -3
  247. package/.next/static/chunks/f42202943f6742e5.css +0 -3
  248. /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_buildManifest.js +0 -0
  249. /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_clientMiddlewareManifest.json +0 -0
  250. /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_ssgManifest.js +0 -0
  251. /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_buildManifest.js +0 -0
  252. /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_clientMiddlewareManifest.json +0 -0
  253. /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_ssgManifest.js +0 -0
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
2
  import * as TestingLibraryReact from '@testing-library/react';
3
- import { KanbanBoard } from './KanbanBoard';
3
+ import { KanbanBoard, detectStatusTransitionSounds } from './KanbanBoard';
4
4
  import { useHostSources } from '@/hooks/useHostSources';
5
5
 
6
6
  type RenderFn = (ui: React.ReactElement) => unknown;
@@ -10,6 +10,7 @@ type Screen = {
10
10
  getByText: (text: RegExp | string) => HTMLElement;
11
11
  getAllByText: (text: RegExp | string) => HTMLElement[];
12
12
  getByTitle: (title: RegExp | string) => HTMLElement;
13
+ queryByTitle: (title: RegExp | string) => HTMLElement | null;
13
14
  queryAllByTitle: (title: RegExp | string) => HTMLElement[];
14
15
  };
15
16
  type FireEventFn = {
@@ -33,6 +34,7 @@ vi.mock('@tanstack/react-query', () => ({
33
34
 
34
35
  vi.mock('@/lib/notificationSound', () => ({
35
36
  playCompleteSound: vi.fn(),
37
+ playAttentionSound: vi.fn(),
36
38
  }));
37
39
 
38
40
  import { useQuery } from '@tanstack/react-query';
@@ -79,6 +81,21 @@ function renderBoard(filterDays = 7, hostSources = createHostSourcesState(), isN
79
81
  return render(<KanbanBoard filterDays={filterDays} hostSources={hostSources} isNodeMode={isNodeMode} />);
80
82
  }
81
83
 
84
+ function getSessionsQueryOptions(): { refetchInterval?: (query: { state: { fetchStatus: string; data?: unknown } }) => number | false } | undefined {
85
+ const useQueryMock = useQuery as unknown as {
86
+ mock: {
87
+ calls: Array<[Record<string, unknown>]>;
88
+ };
89
+ };
90
+
91
+ const matchedCall = useQueryMock.mock.calls.find(([options]) => (
92
+ Array.isArray(options.queryKey)
93
+ && (options.queryKey as unknown[])[0] === 'sessions'
94
+ ));
95
+
96
+ return matchedCall?.[0] as { refetchInterval?: (query: { state: { fetchStatus: string; data?: unknown } }) => number | false } | undefined;
97
+ }
98
+
82
99
  describe('KanbanBoard Host Filter', () => {
83
100
  let hostSourcesState: HostSourcesState;
84
101
 
@@ -292,6 +309,34 @@ describe('KanbanBoard Host Filter', () => {
292
309
  });
293
310
  });
294
311
 
312
+ describe('KanbanBoard sounds', () => {
313
+ it('detects review sound transition when card moves into review', () => {
314
+ const previous = {
315
+ 'session-1': 'busy',
316
+ } as const;
317
+
318
+ const next = {
319
+ 'session-1': 'review',
320
+ } as const;
321
+
322
+ const transitions = detectStatusTransitionSounds(previous, next);
323
+ expect(transitions).toEqual({ shouldPlayReview: true, shouldPlayComplete: false });
324
+ });
325
+
326
+ it('detects completion sound transition when card moves into idle', () => {
327
+ const previous = {
328
+ 'session-1': 'review',
329
+ } as const;
330
+
331
+ const next = {
332
+ 'session-1': 'idle',
333
+ } as const;
334
+
335
+ const transitions = detectStatusTransitionSounds(previous, next);
336
+ expect(transitions).toEqual({ shouldPlayReview: false, shouldPlayComplete: true });
337
+ });
338
+ });
339
+
295
340
  describe('KanbanBoard Fetch Behavior and Error UX', () => {
296
341
  let mockFetch: { mockResolvedValue: (val: unknown) => void; mockRestore: () => void; mock: { calls: unknown[][] } };
297
342
  let hostSourcesState: HostSourcesState;
@@ -551,6 +596,10 @@ describe('KanbanBoard Fetch Behavior and Error UX', () => {
551
596
  });
552
597
 
553
598
  expect(screen.getByTestId('host-filter')).toBeTruthy();
599
+ const openButton = screen.getByTitle('Open project') as HTMLButtonElement;
600
+ expect(openButton.disabled).toBe(true);
601
+ expect(screen.queryByTitle('Batch actions')).toBeNull();
602
+ expect(screen.queryAllByTitle('Actions')).toHaveLength(0);
554
603
  });
555
604
 
556
605
  it('dedupes legacy raw Local snapshot sessions against degraded namespaced Local sessions', async () => {
@@ -627,6 +676,263 @@ describe('KanbanBoard Fetch Behavior and Error UX', () => {
627
676
  });
628
677
  });
629
678
 
679
+ it('keeps descendant-carrying intermediate cards when child-id deduplicating board cards', async () => {
680
+ const now = Date.now();
681
+ mockUseQuery.mockReturnValue({
682
+ data: {
683
+ sessions: [
684
+ {
685
+ id: 'local:root-session',
686
+ sourceSessionKey: 'local:root-session',
687
+ rawSessionId: 'root-session',
688
+ slug: 'session_root_agent',
689
+ title: 'Root Session',
690
+ directory: '/tmp/local',
691
+ projectName: 'Shared Project',
692
+ hostId: 'local',
693
+ hostLabel: 'Local',
694
+ hostKind: 'local',
695
+ readOnly: false,
696
+ time: { created: now - 3_000, updated: now - 2_000 },
697
+ realTimeStatus: 'busy',
698
+ waitingForUser: false,
699
+ children: [
700
+ {
701
+ id: 'local:intermediate-session',
702
+ parentID: 'local:root-session',
703
+ rawSessionId: 'intermediate-session',
704
+ sourceSessionKey: 'local:intermediate-session',
705
+ title: 'Intermediate Child Row',
706
+ realTimeStatus: 'busy',
707
+ waitingForUser: false,
708
+ time: { created: now - 2_000, updated: now - 1_500 },
709
+ },
710
+ ],
711
+ },
712
+ {
713
+ id: 'local:intermediate-session',
714
+ sourceSessionKey: 'local:intermediate-session',
715
+ rawSessionId: 'intermediate-session',
716
+ slug: 'session_intermediate_agent',
717
+ title: 'Intermediate Top-level Session',
718
+ directory: '/tmp/local',
719
+ projectName: 'Shared Project',
720
+ hostId: 'local',
721
+ hostLabel: 'Local',
722
+ hostKind: 'local',
723
+ readOnly: false,
724
+ time: { created: now - 2_000, updated: now - 1_000 },
725
+ realTimeStatus: 'busy',
726
+ waitingForUser: false,
727
+ children: [
728
+ {
729
+ id: 'local:grandchild-session',
730
+ parentID: 'local:intermediate-session',
731
+ rawSessionId: 'grandchild-session',
732
+ sourceSessionKey: 'local:grandchild-session',
733
+ title: 'Grandchild Session',
734
+ realTimeStatus: 'busy',
735
+ waitingForUser: false,
736
+ time: { created: now - 1_000, updated: now - 500 },
737
+ },
738
+ ],
739
+ },
740
+ ],
741
+ processHints: [],
742
+ hostStatuses: [
743
+ { hostId: 'local', hostLabel: 'Local', hostKind: 'local', online: true },
744
+ ],
745
+ },
746
+ isLoading: false,
747
+ error: null,
748
+ isFetching: false,
749
+ failureCount: 0,
750
+ dataUpdatedAt: now,
751
+ refetch: vi.fn(),
752
+ });
753
+
754
+ renderBoard(7, hostSourcesState);
755
+
756
+ await waitFor(() => {
757
+ expect(screen.getByText('Root Session')).toBeTruthy();
758
+ expect(screen.getByText('Intermediate Top-level Session')).toBeTruthy();
759
+ expect(screen.getByText('Grandchild Session')).toBeTruthy();
760
+ });
761
+ });
762
+
763
+ it('uses faster polling while Claude waiting sessions are present', () => {
764
+ mockUseQuery.mockImplementation((opts: unknown) => {
765
+ const options = opts as { queryKey: string[] };
766
+ if (options.queryKey[0] === 'opencode-config') {
767
+ return {
768
+ data: { vibepulse: { sessionsRefreshIntervalMs: 5000 } },
769
+ isLoading: false,
770
+ };
771
+ }
772
+
773
+ if (options.queryKey[0] === 'sessions') {
774
+ return {
775
+ data: {
776
+ sessions: [],
777
+ processHints: [],
778
+ hostStatuses: [{ hostId: 'local', hostLabel: 'Local', hostKind: 'local', online: true }],
779
+ },
780
+ isLoading: false,
781
+ error: null,
782
+ dataUpdatedAt: Date.now(),
783
+ refetch: vi.fn(),
784
+ isFetching: false,
785
+ failureCount: 0,
786
+ };
787
+ }
788
+
789
+ return {
790
+ data: undefined,
791
+ isLoading: false,
792
+ };
793
+ });
794
+
795
+ renderBoard(7, hostSourcesState);
796
+
797
+ const queryOptions = getSessionsQueryOptions();
798
+ const refetchInterval = queryOptions?.refetchInterval;
799
+
800
+ expect(refetchInterval).toBeTypeOf('function');
801
+ expect(refetchInterval?.({
802
+ state: {
803
+ fetchStatus: 'idle',
804
+ data: {
805
+ sessions: [{ id: 'local:claude~abc', provider: 'claude-code', waitingForUser: true }],
806
+ },
807
+ },
808
+ })).toBe(1500);
809
+ expect(refetchInterval?.({
810
+ state: {
811
+ fetchStatus: 'idle',
812
+ data: {
813
+ sessions: [{ id: 'local:abc', provider: 'opencode', waitingForUser: true }],
814
+ },
815
+ },
816
+ })).toBe(5000);
817
+ expect(refetchInterval?.({
818
+ state: {
819
+ fetchStatus: 'fetching',
820
+ data: {
821
+ sessions: [{ id: 'local:claude~abc', provider: 'claude-code', waitingForUser: true }],
822
+ },
823
+ },
824
+ })).toBe(false);
825
+ });
826
+
827
+ it('uses faster polling when Claude waiting appears in nested child sessions', () => {
828
+ mockUseQuery.mockImplementation((opts: unknown) => {
829
+ const options = opts as { queryKey: string[] };
830
+ if (options.queryKey[0] === 'opencode-config') {
831
+ return {
832
+ data: { vibepulse: { sessionsRefreshIntervalMs: 5000 } },
833
+ isLoading: false,
834
+ };
835
+ }
836
+
837
+ if (options.queryKey[0] === 'sessions') {
838
+ return {
839
+ data: {
840
+ sessions: [],
841
+ processHints: [],
842
+ hostStatuses: [{ hostId: 'local', hostLabel: 'Local', hostKind: 'local', online: true }],
843
+ },
844
+ isLoading: false,
845
+ error: null,
846
+ dataUpdatedAt: Date.now(),
847
+ refetch: vi.fn(),
848
+ isFetching: false,
849
+ failureCount: 0,
850
+ };
851
+ }
852
+
853
+ return {
854
+ data: undefined,
855
+ isLoading: false,
856
+ };
857
+ });
858
+
859
+ renderBoard(7, hostSourcesState);
860
+
861
+ const queryOptions = getSessionsQueryOptions();
862
+ const refetchInterval = queryOptions?.refetchInterval;
863
+
864
+ expect(refetchInterval).toBeTypeOf('function');
865
+ expect(refetchInterval?.({
866
+ state: {
867
+ fetchStatus: 'idle',
868
+ data: {
869
+ sessions: [
870
+ {
871
+ id: 'local:parent',
872
+ provider: 'opencode',
873
+ waitingForUser: false,
874
+ children: [
875
+ {
876
+ id: 'local:claude~child',
877
+ provider: 'claude-code',
878
+ waitingForUser: true,
879
+ },
880
+ ],
881
+ },
882
+ ],
883
+ },
884
+ },
885
+ })).toBe(1500);
886
+ });
887
+
888
+ it('keeps configured fast polling when it is already faster than the Claude waiting boost', () => {
889
+ mockUseQuery.mockImplementation((opts: unknown) => {
890
+ const options = opts as { queryKey: string[] };
891
+ if (options.queryKey[0] === 'opencode-config') {
892
+ return {
893
+ data: { vibepulse: { sessionsRefreshIntervalMs: 1000 } },
894
+ isLoading: false,
895
+ };
896
+ }
897
+
898
+ if (options.queryKey[0] === 'sessions') {
899
+ return {
900
+ data: {
901
+ sessions: [],
902
+ processHints: [],
903
+ hostStatuses: [{ hostId: 'local', hostLabel: 'Local', hostKind: 'local', online: true }],
904
+ },
905
+ isLoading: false,
906
+ error: null,
907
+ dataUpdatedAt: Date.now(),
908
+ refetch: vi.fn(),
909
+ isFetching: false,
910
+ failureCount: 0,
911
+ };
912
+ }
913
+
914
+ return {
915
+ data: undefined,
916
+ isLoading: false,
917
+ };
918
+ });
919
+
920
+ renderBoard(7, hostSourcesState);
921
+
922
+ const queryOptions = getSessionsQueryOptions();
923
+ const refetchInterval = queryOptions?.refetchInterval;
924
+
925
+ expect(refetchInterval).toBeTypeOf('function');
926
+ expect(refetchInterval?.({
927
+ state: {
928
+ fetchStatus: 'idle',
929
+ data: {
930
+ sessions: [{ id: 'local:claude~abc', provider: 'claude-code', waitingForUser: true }],
931
+ },
932
+ },
933
+ })).toBe(1000);
934
+ });
935
+
630
936
  it('does not repeatedly emit unchanged host statuses but emits when status values change', () => {
631
937
  const onHostStatusesChange = vi.fn() as unknown as (statuses: import('./KanbanBoard').SessionHostStatus[]) => void;
632
938
 
@@ -5,7 +5,7 @@ import { KanbanColumn, KanbanCard, OpencodeSession } from '@/types';
5
5
  import { ProjectCard } from './ProjectCard';
6
6
  import { transformSessions } from '@/lib/transform';
7
7
  import { LoadingState } from './LoadingState';
8
- import { playCompleteSound } from '@/lib/notificationSound';
8
+ import { playAttentionSound, playCompleteSound } from '@/lib/notificationSound';
9
9
  import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
10
10
  import { getSseStatusSnapshot } from '@/hooks/useOpencodeSync';
11
11
  import { useHostSources } from '@/hooks/useHostSources';
@@ -19,6 +19,7 @@ const CARD_ANIMATION_DURATION_MS = 250;
19
19
  const SESSIONS_ERROR_DISPLAY_THRESHOLD = 3;
20
20
  const DEGRADED_MERGE_MAX_SNAPSHOT_AGE_MS = 10 * 60 * 1000;
21
21
  const WAITING_PERSIST_MAX_AGE_MS = 10 * 60 * 1000;
22
+ const CLAUDE_WAITING_FAST_REFRESH_INTERVAL_MS = 1500;
22
23
 
23
24
  const LOCAL_SOURCE = {
24
25
  hostId: 'local',
@@ -117,9 +118,30 @@ function areHostStatusesEqual(
117
118
  return true;
118
119
  }
119
120
 
121
+ export function detectStatusTransitionSounds(
122
+ previous: Record<string, KanbanColumn>,
123
+ next: Record<string, KanbanColumn>
124
+ ): { shouldPlayReview: boolean; shouldPlayComplete: boolean } {
125
+ const shouldPlayReview = Object.entries(next).some(([id, currentStatus]) => {
126
+ const previousStatus = previous[id];
127
+ return !!previousStatus && previousStatus !== 'review' && currentStatus === 'review';
128
+ });
129
+
130
+ const shouldPlayComplete = Object.entries(next).some(([id, currentStatus]) => {
131
+ const previousStatus = previous[id];
132
+ return !!previousStatus && previousStatus !== 'idle' && currentStatus === 'idle';
133
+ });
134
+
135
+ return { shouldPlayReview, shouldPlayComplete };
136
+ }
137
+
120
138
  function getLocalWaitingPersistenceKey(
121
- session: Pick<OpencodeSession, 'id' | 'sourceSessionKey' | 'hostId' | 'hostKind'>
139
+ session: Pick<OpencodeSession, 'id' | 'sourceSessionKey' | 'hostId' | 'hostKind' | 'provider'>
122
140
  ): string | null {
141
+ if (session.provider === 'claude-code') {
142
+ return null;
143
+ }
144
+
123
145
  const sourceKey = session.sourceSessionKey || session.id;
124
146
  if (session.hostKind === 'local' || session.hostId === 'local' || sourceKey.startsWith('local:')) {
125
147
  return sourceKey;
@@ -150,6 +172,45 @@ function getCanonicalSessionIdentity(
150
172
  return session.id;
151
173
  }
152
174
 
175
+ function hasWaitingClaudeSession(data: unknown): boolean {
176
+ if (!data || typeof data !== 'object') {
177
+ return false;
178
+ }
179
+
180
+ const maybeSessions = (data as { sessions?: unknown }).sessions;
181
+ if (!Array.isArray(maybeSessions) || maybeSessions.length === 0) {
182
+ return false;
183
+ }
184
+
185
+ const queue: Array<{ provider?: string; waitingForUser?: boolean; children?: unknown[] }> = [];
186
+ for (const session of maybeSessions) {
187
+ if (session && typeof session === 'object') {
188
+ queue.push(session as { provider?: string; waitingForUser?: boolean; children?: unknown[] });
189
+ }
190
+ }
191
+
192
+ while (queue.length > 0) {
193
+ const current = queue.shift();
194
+ if (!current) {
195
+ continue;
196
+ }
197
+
198
+ if (current.provider === 'claude-code' && current.waitingForUser === true) {
199
+ return true;
200
+ }
201
+
202
+ if (Array.isArray(current.children) && current.children.length > 0) {
203
+ for (const child of current.children) {
204
+ if (child && typeof child === 'object') {
205
+ queue.push(child as { provider?: string; waitingForUser?: boolean; children?: unknown[] });
206
+ }
207
+ }
208
+ }
209
+ }
210
+
211
+ return false;
212
+ }
213
+
153
214
  export function KanbanBoard({
154
215
  filterDays,
155
216
  onProcessHintsChange,
@@ -245,7 +306,17 @@ export function KanbanBoard({
245
306
  throw fetchError;
246
307
  }
247
308
  },
248
- refetchInterval: (query) => query.state.fetchStatus === 'fetching' ? false : refreshIntervalMs,
309
+ refetchInterval: (query) => {
310
+ if (query.state.fetchStatus === 'fetching') {
311
+ return false;
312
+ }
313
+
314
+ if (hasWaitingClaudeSession(query.state.data)) {
315
+ return Math.min(refreshIntervalMs, CLAUDE_WAITING_FAST_REFRESH_INTERVAL_MS);
316
+ }
317
+
318
+ return refreshIntervalMs;
319
+ },
249
320
  refetchIntervalInBackground: true,
250
321
  refetchOnReconnect: true,
251
322
  retry: false,
@@ -490,7 +561,22 @@ export function KanbanBoard({
490
561
 
491
562
  const cards: KanbanCard[] = useMemo(() => {
492
563
  const allCards = transformSessions(enrichedSessions);
493
- let filtered = allCards;
564
+
565
+ const childSessionIds = new Set<string>();
566
+ for (const card of allCards) {
567
+ for (const child of card.children || []) {
568
+ childSessionIds.add(child.id);
569
+ }
570
+ }
571
+
572
+ let filtered = allCards.filter((card) => {
573
+ if (!childSessionIds.has(card.id)) {
574
+ return true;
575
+ }
576
+
577
+ return (card.children?.length ?? 0) > 0;
578
+ });
579
+
494
580
  if (filteredHostIds) {
495
581
  filtered = filtered.filter(card => {
496
582
  const cardHostId = card.hostId || 'local';
@@ -529,13 +615,17 @@ export function KanbanBoard({
529
615
  return;
530
616
  }
531
617
 
532
- const shouldPlayComplete = Object.entries(nextCardStatus).some(([id, currentStatus]) => {
533
- const previousStatus = cardStatusStateRef.current[id];
534
- return !!previousStatus && previousStatus !== 'idle' && currentStatus === 'idle';
535
- });
618
+ const { shouldPlayReview, shouldPlayComplete } = detectStatusTransitionSounds(
619
+ cardStatusStateRef.current,
620
+ nextCardStatus
621
+ );
536
622
 
537
623
  cardStatusStateRef.current = nextCardStatus;
538
624
 
625
+ if (shouldPlayReview && !isShowingStaleData) {
626
+ setTimeout(() => playAttentionSound(), CARD_ANIMATION_DURATION_MS);
627
+ }
628
+
539
629
  if (shouldPlayComplete && !isShowingStaleData) {
540
630
  setTimeout(() => playCompleteSound(), CARD_ANIMATION_DURATION_MS);
541
631
  }
@@ -753,7 +843,6 @@ export function KanbanBoard({
753
843
  projectName: string;
754
844
  branch?: string;
755
845
  hostLabel?: string;
756
- readOnly: boolean;
757
846
  cards: KanbanCard[];
758
847
  }>();
759
848
 
@@ -767,14 +856,12 @@ export function KanbanBoard({
767
856
  projectName,
768
857
  branch: card.branch,
769
858
  hostLabel: card.hostLabel,
770
- readOnly: !!card.readOnly,
771
859
  cards: [],
772
860
  });
773
861
  }
774
862
 
775
863
  const group = groups.get(key)!;
776
864
  group.cards.push(card);
777
- group.readOnly = group.readOnly || !!card.readOnly;
778
865
 
779
866
  if (!group.branch && card.branch) {
780
867
  group.branch = card.branch;
@@ -832,13 +919,13 @@ export function KanbanBoard({
832
919
  {Array.from(projectGroups.entries()).map(([groupKey, group]) => (
833
920
  <ProjectCard
834
921
  key={groupKey}
835
- projectName={group.projectName}
836
- branch={group.branch}
837
- cards={group.cards}
838
- readOnly={isShowingStaleData || group.readOnly}
839
- hostLabel={group.hostLabel}
840
- multipleHostsEnabled={requestSources.length > 1}
841
- />
922
+ projectName={group.projectName}
923
+ branch={group.branch}
924
+ cards={group.cards}
925
+ readOnly={isShowingStaleData}
926
+ hostLabel={group.hostLabel}
927
+ multipleHostsEnabled={requestSources.length > 1}
928
+ />
842
929
  ))}
843
930
  </div>
844
931
  </div>