@polderlabs/bizar 10.4.4 → 10.6.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 (252) hide show
  1. package/bizar-dash/dist/assets/{ActivityFeed-RQeyFifx.js → ActivityFeed-Bw4Cai7r.js} +1 -1
  2. package/bizar-dash/dist/assets/{ActivityFeed-RQeyFifx.js.map → ActivityFeed-Bw4Cai7r.js.map} +1 -1
  3. package/bizar-dash/dist/assets/{ActivityView-CibyssPM.js → ActivityView-BC0QQ_j9.js} +1 -1
  4. package/bizar-dash/dist/assets/{ActivityView-CibyssPM.js.map → ActivityView-BC0QQ_j9.js.map} +1 -1
  5. package/bizar-dash/dist/assets/{AdminView-BjczABeB.js → AdminView-CLTNKFL4.js} +1 -1
  6. package/bizar-dash/dist/assets/{AdminView-BjczABeB.js.map → AdminView-CLTNKFL4.js.map} +1 -1
  7. package/bizar-dash/dist/assets/{AgentActivity-Bywt1twv.js → AgentActivity-DBWDByjk.js} +1 -1
  8. package/bizar-dash/dist/assets/{AgentActivity-Bywt1twv.js.map → AgentActivity-DBWDByjk.js.map} +1 -1
  9. package/bizar-dash/dist/assets/{AgentsView-BnOjjM74.js → AgentsView-DD6bCIbA.js} +1 -1
  10. package/bizar-dash/dist/assets/{AgentsView-BnOjjM74.js.map → AgentsView-DD6bCIbA.js.map} +1 -1
  11. package/bizar-dash/dist/assets/{Alert-BZGk39mB.js → Alert-pzFISNtS.js} +1 -1
  12. package/bizar-dash/dist/assets/{Alert-BZGk39mB.js.map → Alert-pzFISNtS.js.map} +1 -1
  13. package/bizar-dash/dist/assets/ArtifactsView-BVU5lfo7.js +21 -0
  14. package/bizar-dash/dist/assets/ArtifactsView-BVU5lfo7.js.map +1 -0
  15. package/bizar-dash/dist/assets/{AuthView-D4frSShw.js → AuthView-BZ-bPIOM.js} +2 -2
  16. package/bizar-dash/dist/assets/AuthView-BZ-bPIOM.js.map +1 -0
  17. package/bizar-dash/dist/assets/{Avatar-BYg0rkPA.js → Avatar-CRW81ewE.js} +1 -1
  18. package/bizar-dash/dist/assets/{Avatar-BYg0rkPA.js.map → Avatar-CRW81ewE.js.map} +1 -1
  19. package/bizar-dash/dist/assets/BackgroundJobsView-w8OZtXyI.js +1 -0
  20. package/bizar-dash/dist/assets/BackgroundJobsView-w8OZtXyI.js.map +1 -0
  21. package/bizar-dash/dist/assets/{BackupView-B875KQa4.js → BackupView-u_O3j0TG.js} +1 -1
  22. package/bizar-dash/dist/assets/{BackupView-B875KQa4.js.map → BackupView-u_O3j0TG.js.map} +1 -1
  23. package/bizar-dash/dist/assets/{Banner-C8HEcT5l.js → Banner-Du-3l05O.js} +1 -1
  24. package/bizar-dash/dist/assets/{Banner-C8HEcT5l.js.map → Banner-Du-3l05O.js.map} +1 -1
  25. package/bizar-dash/dist/assets/{Card-BtsmkhJa.js → Card-Clkvh43Q.js} +1 -1
  26. package/bizar-dash/dist/assets/{Card-BtsmkhJa.js.map → Card-Clkvh43Q.js.map} +1 -1
  27. package/bizar-dash/dist/assets/{ChatView-CkNS0SD8.js → ChatView-BTjb3mIh.js} +1 -1
  28. package/bizar-dash/dist/assets/{ChatView-CkNS0SD8.js.map → ChatView-BTjb3mIh.js.map} +1 -1
  29. package/bizar-dash/dist/assets/{Chip-XPOk0d_E.js → Chip-B7ovcm01.js} +1 -1
  30. package/bizar-dash/dist/assets/{Chip-XPOk0d_E.js.map → Chip-B7ovcm01.js.map} +1 -1
  31. package/bizar-dash/dist/assets/ClaudeSessionsView-BoZS5ldi.js +1 -0
  32. package/bizar-dash/dist/assets/ClaudeSessionsView-BoZS5ldi.js.map +1 -0
  33. package/bizar-dash/dist/assets/ClipboardView-Cb6XgYZI.js +1 -0
  34. package/bizar-dash/dist/assets/{ClipboardView-CPlTU5VG.js.map → ClipboardView-Cb6XgYZI.js.map} +1 -1
  35. package/bizar-dash/dist/assets/{ConfigView-BntUrW7s.js → ConfigView-DfTJk1uL.js} +1 -1
  36. package/bizar-dash/dist/assets/{ConfigView-BntUrW7s.js.map → ConfigView-DfTJk1uL.js.map} +1 -1
  37. package/bizar-dash/dist/assets/{DiagnosticsView-4x0Zu3yP.js → DiagnosticsView-D5Wy9xlJ.js} +1 -1
  38. package/bizar-dash/dist/assets/{DiagnosticsView-4x0Zu3yP.js.map → DiagnosticsView-D5Wy9xlJ.js.map} +1 -1
  39. package/bizar-dash/dist/assets/{DialogsView-Xd02dVAX.js → DialogsView-HVyfrXpq.js} +1 -1
  40. package/bizar-dash/dist/assets/{DialogsView-Xd02dVAX.js.map → DialogsView-HVyfrXpq.js.map} +1 -1
  41. package/bizar-dash/dist/assets/{DoctorView-DepBdQGR.js → DoctorView-Dm94WI2z.js} +2 -2
  42. package/bizar-dash/dist/assets/{DoctorView-DepBdQGR.js.map → DoctorView-Dm94WI2z.js.map} +1 -1
  43. package/bizar-dash/dist/assets/{EmptyState-DjKtY1Qi.js → EmptyState-ccT__JZG.js} +1 -1
  44. package/bizar-dash/dist/assets/{EmptyState-DjKtY1Qi.js.map → EmptyState-ccT__JZG.js.map} +1 -1
  45. package/bizar-dash/dist/assets/{EnvVarsView-S4Pbm128.js → EnvVarsView-BhcIuJ6V.js} +2 -2
  46. package/bizar-dash/dist/assets/{EnvVarsView-S4Pbm128.js.map → EnvVarsView-BhcIuJ6V.js.map} +1 -1
  47. package/bizar-dash/dist/assets/{ErrorState-CBJbEQ8B.js → ErrorState-BSqf65ka.js} +1 -1
  48. package/bizar-dash/dist/assets/{ErrorState-CBJbEQ8B.js.map → ErrorState-BSqf65ka.js.map} +1 -1
  49. package/bizar-dash/dist/assets/{EvalView-BeAyMCD1.js → EvalView-2cbWVBUA.js} +1 -1
  50. package/bizar-dash/dist/assets/{EvalView-BeAyMCD1.js.map → EvalView-2cbWVBUA.js.map} +1 -1
  51. package/bizar-dash/dist/assets/{GoalsView-BZr8In4c.js → GoalsView-C3QG5p7T.js} +1 -1
  52. package/bizar-dash/dist/assets/{GoalsView-BZr8In4c.js.map → GoalsView-C3QG5p7T.js.map} +1 -1
  53. package/bizar-dash/dist/assets/{Grid--WKvqu5h.js → Grid-Bojq8Sg7.js} +1 -1
  54. package/bizar-dash/dist/assets/{Grid--WKvqu5h.js.map → Grid-Bojq8Sg7.js.map} +1 -1
  55. package/bizar-dash/dist/assets/{HistoryView-djEbzqtk.js → HistoryView-Bum2TMMw.js} +1 -1
  56. package/bizar-dash/dist/assets/{HistoryView-djEbzqtk.js.map → HistoryView-Bum2TMMw.js.map} +1 -1
  57. package/bizar-dash/dist/assets/{Input-h_nSOtwm.js → Input-DV-vdaV8.js} +1 -1
  58. package/bizar-dash/dist/assets/{Input-h_nSOtwm.js.map → Input-DV-vdaV8.js.map} +1 -1
  59. package/bizar-dash/dist/assets/{LibrariesView-Vez8CvNq.js → LibrariesView-DH2E2uNb.js} +1 -1
  60. package/bizar-dash/dist/assets/{LibrariesView-Vez8CvNq.js.map → LibrariesView-DH2E2uNb.js.map} +1 -1
  61. package/bizar-dash/dist/assets/LightRAGView-BFCcj7Tl.js +6 -0
  62. package/bizar-dash/dist/assets/LightRAGView-BFCcj7Tl.js.map +1 -0
  63. package/bizar-dash/dist/assets/{ListHeader-CM8QoIWG.js → ListHeader-CCD_nIHb.js} +1 -1
  64. package/bizar-dash/dist/assets/{ListHeader-CM8QoIWG.js.map → ListHeader-CCD_nIHb.js.map} +1 -1
  65. package/bizar-dash/dist/assets/{MemoryView-DnL8E2vI.js → MemoryView-Cr-obIQY.js} +1 -1
  66. package/bizar-dash/dist/assets/{MemoryView-DnL8E2vI.js.map → MemoryView-Cr-obIQY.js.map} +1 -1
  67. package/bizar-dash/dist/assets/{MiscView-B2YQXhZ_.js → MiscView-CDUQpYuV.js} +1 -1
  68. package/bizar-dash/dist/assets/{MiscView-B2YQXhZ_.js.map → MiscView-CDUQpYuV.js.map} +1 -1
  69. package/bizar-dash/dist/assets/{ModsView-BVM3X5xc.js → ModsView-B6OR-a4B.js} +2 -2
  70. package/bizar-dash/dist/assets/{ModsView-BVM3X5xc.js.map → ModsView-B6OR-a4B.js.map} +1 -1
  71. package/bizar-dash/dist/assets/{NotificationsView-A-Xs8BL1.js → NotificationsView-CIqtY6D0.js} +1 -1
  72. package/bizar-dash/dist/assets/{NotificationsView-A-Xs8BL1.js.map → NotificationsView-CIqtY6D0.js.map} +1 -1
  73. package/bizar-dash/dist/assets/{ObsidianView-mI3qW9OP.js → ObsidianView-Nahah8QC.js} +1 -1
  74. package/bizar-dash/dist/assets/{ObsidianView-mI3qW9OP.js.map → ObsidianView-Nahah8QC.js.map} +1 -1
  75. package/bizar-dash/dist/assets/{OverviewView-By69Cr5H.js → OverviewView-B6L_wa4M.js} +1 -1
  76. package/bizar-dash/dist/assets/{OverviewView-By69Cr5H.js.map → OverviewView-B6L_wa4M.js.map} +1 -1
  77. package/bizar-dash/dist/assets/{ProgressBar-BnaBqv-W.js → ProgressBar-KC4VXsl_.js} +1 -1
  78. package/bizar-dash/dist/assets/{ProgressBar-BnaBqv-W.js.map → ProgressBar-KC4VXsl_.js.map} +1 -1
  79. package/bizar-dash/dist/assets/{ProjectsView-C81WTFka.js → ProjectsView-DgxWiqHb.js} +1 -1
  80. package/bizar-dash/dist/assets/{ProjectsView-C81WTFka.js.map → ProjectsView-DgxWiqHb.js.map} +1 -1
  81. package/bizar-dash/dist/assets/{ProvidersView-2cn-TALE.js → ProvidersView-BOqN9omv.js} +1 -1
  82. package/bizar-dash/dist/assets/{ProvidersView-2cn-TALE.js.map → ProvidersView-BOqN9omv.js.map} +1 -1
  83. package/bizar-dash/dist/assets/SchedulesView-yMaG3Dtd.js +1 -0
  84. package/bizar-dash/dist/assets/SchedulesView-yMaG3Dtd.js.map +1 -0
  85. package/bizar-dash/dist/assets/{Select-CiGWEN8I.js → Select-Bf_P0S6H.js} +1 -1
  86. package/bizar-dash/dist/assets/{Select-CiGWEN8I.js.map → Select-Bf_P0S6H.js.map} +1 -1
  87. package/bizar-dash/dist/assets/{SettingsRow-CflGJymA.js → SettingsRow-CJb-F94Y.js} +1 -1
  88. package/bizar-dash/dist/assets/{SettingsRow-CflGJymA.js.map → SettingsRow-CJb-F94Y.js.map} +1 -1
  89. package/bizar-dash/dist/assets/{SettingsView-2HSFJPPK.js → SettingsView-BvOGcgMS.js} +2 -2
  90. package/bizar-dash/dist/assets/SettingsView-BvOGcgMS.js.map +1 -0
  91. package/bizar-dash/dist/assets/{Sheet-B-CMP9-_.js → Sheet-BwMVGN-8.js} +1 -1
  92. package/bizar-dash/dist/assets/{Sheet-B-CMP9-_.js.map → Sheet-BwMVGN-8.js.map} +1 -1
  93. package/bizar-dash/dist/assets/{Slider-Z4TP_CXY.js → Slider-AjjzqI-5.js} +1 -1
  94. package/bizar-dash/dist/assets/{Slider-Z4TP_CXY.js.map → Slider-AjjzqI-5.js.map} +1 -1
  95. package/bizar-dash/dist/assets/{Sparkline-cvkv2HbU.js → Sparkline-Z4y0YUZS.js} +1 -1
  96. package/bizar-dash/dist/assets/{Sparkline-cvkv2HbU.js.map → Sparkline-Z4y0YUZS.js.map} +1 -1
  97. package/bizar-dash/dist/assets/{Switch-jcYo4lmp.js → Switch-B79a2t-G.js} +1 -1
  98. package/bizar-dash/dist/assets/{Switch-jcYo4lmp.js.map → Switch-B79a2t-G.js.map} +1 -1
  99. package/bizar-dash/dist/assets/{TasksView-CQaSFzWY.js → TasksView-G-s_pwwr.js} +1 -1
  100. package/bizar-dash/dist/assets/{TasksView-CQaSFzWY.js.map → TasksView-G-s_pwwr.js.map} +1 -1
  101. package/bizar-dash/dist/assets/{Textarea-xnAybFGd.js → Textarea-BfXGMXQ7.js} +1 -1
  102. package/bizar-dash/dist/assets/{Textarea-xnAybFGd.js.map → Textarea-BfXGMXQ7.js.map} +1 -1
  103. package/bizar-dash/dist/assets/{UpdateView-BUAEcRCX.js → UpdateView-BKx3u9X8.js} +1 -1
  104. package/bizar-dash/dist/assets/{UpdateView-BUAEcRCX.js.map → UpdateView-BKx3u9X8.js.map} +1 -1
  105. package/bizar-dash/dist/assets/{UsageView-DMFbBOfH.js → UsageView-M01G6bZQ.js} +1 -1
  106. package/bizar-dash/dist/assets/{UsageView-DMFbBOfH.js.map → UsageView-M01G6bZQ.js.map} +1 -1
  107. package/bizar-dash/dist/assets/{ViewHeader-C-t1U2cQ.js → ViewHeader-BsnaspKw.js} +1 -1
  108. package/bizar-dash/dist/assets/{ViewHeader-C-t1U2cQ.js.map → ViewHeader-BsnaspKw.js.map} +1 -1
  109. package/bizar-dash/dist/assets/VoiceView-rWE8vziD.js +1 -0
  110. package/bizar-dash/dist/assets/{VoiceView-aoPVT6LJ.js.map → VoiceView-rWE8vziD.js.map} +1 -1
  111. package/bizar-dash/dist/assets/{brain-DhAzcHUk.js → brain-D7k5s8vH.js} +1 -1
  112. package/bizar-dash/dist/assets/{brain-DhAzcHUk.js.map → brain-D7k5s8vH.js.map} +1 -1
  113. package/bizar-dash/dist/assets/{calendar-B9Wbemh7.js → calendar-CAZoDd-l.js} +1 -1
  114. package/bizar-dash/dist/assets/{calendar-B9Wbemh7.js.map → calendar-CAZoDd-l.js.map} +1 -1
  115. package/bizar-dash/dist/assets/{circle-6NJF_Xl6.js → circle-D99cZJBr.js} +1 -1
  116. package/bizar-dash/dist/assets/{circle-6NJF_Xl6.js.map → circle-D99cZJBr.js.map} +1 -1
  117. package/bizar-dash/dist/assets/{circle-alert-Dz8lGfCZ.js → circle-alert-QQTU4Quz.js} +1 -1
  118. package/bizar-dash/dist/assets/{circle-alert-Dz8lGfCZ.js.map → circle-alert-QQTU4Quz.js.map} +1 -1
  119. package/bizar-dash/dist/assets/{circle-check-DFB50ojt.js → circle-check-BMbe7Kkr.js} +1 -1
  120. package/bizar-dash/dist/assets/{circle-check-DFB50ojt.js.map → circle-check-BMbe7Kkr.js.map} +1 -1
  121. package/bizar-dash/dist/assets/{clock-ClVA6-hQ.js → clock-OtT5mvF3.js} +1 -1
  122. package/bizar-dash/dist/assets/{clock-ClVA6-hQ.js.map → clock-OtT5mvF3.js.map} +1 -1
  123. package/bizar-dash/dist/assets/{download-BvJddD8D.js → download-B-mPX5P_.js} +1 -1
  124. package/bizar-dash/dist/assets/{download-BvJddD8D.js.map → download-B-mPX5P_.js.map} +1 -1
  125. package/bizar-dash/dist/assets/{external-link-DvL89gaZ.js → external-link-DPDQM1YO.js} +1 -1
  126. package/bizar-dash/dist/assets/{external-link-DvL89gaZ.js.map → external-link-DPDQM1YO.js.map} +1 -1
  127. package/bizar-dash/dist/assets/{file-text-BOroDtZE.js → file-text-BsCrv5aP.js} +1 -1
  128. package/bizar-dash/dist/assets/{file-text-BOroDtZE.js.map → file-text-BsCrv5aP.js.map} +1 -1
  129. package/bizar-dash/dist/assets/{filter-DwPjMRQt.js → filter-Do63x17E.js} +1 -1
  130. package/bizar-dash/dist/assets/{filter-DwPjMRQt.js.map → filter-Do63x17E.js.map} +1 -1
  131. package/bizar-dash/dist/assets/{git-pull-request-TDb1inux.js → git-pull-request-CpRAmERC.js} +1 -1
  132. package/bizar-dash/dist/assets/{git-pull-request-TDb1inux.js.map → git-pull-request-CpRAmERC.js.map} +1 -1
  133. package/bizar-dash/dist/assets/main-BRmJZcHt.js +277 -0
  134. package/bizar-dash/dist/assets/main-BRmJZcHt.js.map +1 -0
  135. package/bizar-dash/dist/assets/main-D0TY0myE.css +1 -0
  136. package/bizar-dash/dist/assets/{network-BcCoCzIY.js → network-DJnHwHX3.js} +1 -1
  137. package/bizar-dash/dist/assets/{network-BcCoCzIY.js.map → network-DJnHwHX3.js.map} +1 -1
  138. package/bizar-dash/dist/assets/{pause-BllItT2_.js → pause-HitDUnk_.js} +1 -1
  139. package/bizar-dash/dist/assets/{pause-BllItT2_.js.map → pause-HitDUnk_.js.map} +1 -1
  140. package/bizar-dash/dist/assets/{pencil-DAwM-CC3.js → pencil-Dkks7eIC.js} +1 -1
  141. package/bizar-dash/dist/assets/{pencil-DAwM-CC3.js.map → pencil-Dkks7eIC.js.map} +1 -1
  142. package/bizar-dash/dist/assets/{play-mJh67AXo.js → play-blqh0msT.js} +1 -1
  143. package/bizar-dash/dist/assets/{play-mJh67AXo.js.map → play-blqh0msT.js.map} +1 -1
  144. package/bizar-dash/dist/assets/{plug-BjfntCMC.js → plug-DVvSSe40.js} +1 -1
  145. package/bizar-dash/dist/assets/{plug-BjfntCMC.js.map → plug-DVvSSe40.js.map} +1 -1
  146. package/bizar-dash/dist/assets/{plus-Cm6-fo14.js → plus-Cps-8lcG.js} +1 -1
  147. package/bizar-dash/dist/assets/{plus-Cm6-fo14.js.map → plus-Cps-8lcG.js.map} +1 -1
  148. package/bizar-dash/dist/assets/{power-CXN9y2KV.js → power-DEX4_Xoa.js} +1 -1
  149. package/bizar-dash/dist/assets/{power-CXN9y2KV.js.map → power-DEX4_Xoa.js.map} +1 -1
  150. package/bizar-dash/dist/assets/{power-off-DUZqwMf9.js → power-off-upZ4xjaG.js} +1 -1
  151. package/bizar-dash/dist/assets/{power-off-DUZqwMf9.js.map → power-off-upZ4xjaG.js.map} +1 -1
  152. package/bizar-dash/dist/assets/{refresh-ccw-DA8_IB9l.js → refresh-ccw-DN_zCB1h.js} +1 -1
  153. package/bizar-dash/dist/assets/{refresh-ccw-DA8_IB9l.js.map → refresh-ccw-DN_zCB1h.js.map} +1 -1
  154. package/bizar-dash/dist/assets/{refresh-cw-DwlP59en.js → refresh-cw-KGm5eQgv.js} +1 -1
  155. package/bizar-dash/dist/assets/{refresh-cw-DwlP59en.js.map → refresh-cw-KGm5eQgv.js.map} +1 -1
  156. package/bizar-dash/dist/assets/{rotate-ccw-DMTASfAn.js → rotate-ccw-D5fkDRgk.js} +1 -1
  157. package/bizar-dash/dist/assets/{rotate-ccw-DMTASfAn.js.map → rotate-ccw-D5fkDRgk.js.map} +1 -1
  158. package/bizar-dash/dist/assets/{rotate-cw-BDX5SE2s.js → rotate-cw-CClSVUaX.js} +1 -1
  159. package/bizar-dash/dist/assets/{rotate-cw-BDX5SE2s.js.map → rotate-cw-CClSVUaX.js.map} +1 -1
  160. package/bizar-dash/dist/assets/{save-Y0jPK5D6.js → save-BY4ViPku.js} +1 -1
  161. package/bizar-dash/dist/assets/{save-Y0jPK5D6.js.map → save-BY4ViPku.js.map} +1 -1
  162. package/bizar-dash/dist/assets/{send-C8wwPHhR.js → send-CaOL1yQD.js} +1 -1
  163. package/bizar-dash/dist/assets/{send-C8wwPHhR.js.map → send-CaOL1yQD.js.map} +1 -1
  164. package/bizar-dash/dist/assets/{shield-alert-DEvq9UG0.js → shield-alert-DA9K6uZO.js} +1 -1
  165. package/bizar-dash/dist/assets/{shield-alert-DEvq9UG0.js.map → shield-alert-DA9K6uZO.js.map} +1 -1
  166. package/bizar-dash/dist/assets/{trash-2-D6qePV_q.js → trash-2-B_Y6HrfN.js} +1 -1
  167. package/bizar-dash/dist/assets/{trash-2-D6qePV_q.js.map → trash-2-B_Y6HrfN.js.map} +1 -1
  168. package/bizar-dash/dist/assets/{trending-up-BZzo_1ic.js → trending-up-qKTUQKIT.js} +1 -1
  169. package/bizar-dash/dist/assets/{trending-up-BZzo_1ic.js.map → trending-up-qKTUQKIT.js.map} +1 -1
  170. package/bizar-dash/dist/assets/{triangle-alert-B2oHbmoh.js → triangle-alert-BeY8LAv6.js} +1 -1
  171. package/bizar-dash/dist/assets/{triangle-alert-B2oHbmoh.js.map → triangle-alert-BeY8LAv6.js.map} +1 -1
  172. package/bizar-dash/dist/assets/{upload-Dy59aZn6.js → upload-BZCtmqIh.js} +1 -1
  173. package/bizar-dash/dist/assets/{upload-Dy59aZn6.js.map → upload-BZCtmqIh.js.map} +1 -1
  174. package/bizar-dash/dist/assets/{zap-DENVhJnO.js → zap-Bz1Z8y1Q.js} +1 -1
  175. package/bizar-dash/dist/assets/{zap-DENVhJnO.js.map → zap-Bz1Z8y1Q.js.map} +1 -1
  176. package/bizar-dash/dist/index.html +2 -2
  177. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  178. package/bizar-dash/src/server/agent-bus.mjs +485 -0
  179. package/bizar-dash/src/server/api.mjs +6 -0
  180. package/bizar-dash/src/server/loop-runtime.mjs +429 -0
  181. package/bizar-dash/src/server/routes/agent-bus.mjs +185 -0
  182. package/bizar-dash/src/server/routes/loops.mjs +138 -0
  183. package/bizar-dash/src/server/task-splitter.mjs +241 -0
  184. package/bizar-dash/src/web/v8/shell/Sidebar.tsx +41 -31
  185. package/bizar-dash/src/web/v8/shell/Topbar.tsx +63 -23
  186. package/bizar-dash/src/web/v8/ui/feedback/NotificationsPopover.tsx +3 -0
  187. package/bizar-dash/src/web/v8/ui/feedback/useFocusTrap.ts +58 -0
  188. package/bizar-dash/src/web/v8/ui/navigation/CommandPalette.tsx +1 -0
  189. package/bizar-dash/src/web/v8/ui/styles/tokens.css +21 -0
  190. package/bizar-dash/src/web/v8/views/Artifacts/ClaudeArtifactView.tsx +1 -1
  191. package/bizar-dash/src/web/v8/views/Auth/AuthView.tsx +10 -6
  192. package/bizar-dash/src/web/v8/views/BackgroundJobs/BackgroundJobsView.tsx +38 -13
  193. package/bizar-dash/src/web/v8/views/ClaudeSessions/ClaudeSessionDetail.tsx +16 -5
  194. package/bizar-dash/src/web/v8/views/LightRAG/LightRAGView.tsx +23 -2
  195. package/bizar-dash/src/web/v8/views/Schedules/SchedulesView.tsx +48 -13
  196. package/bizar-dash/src/web/v8/views/Settings/SettingsView.tsx +3 -3
  197. package/bizar-dash/tests/agent-bus.test.mjs +289 -0
  198. package/bizar-dash/tests/auth-inputs-labels.test.mjs +45 -0
  199. package/bizar-dash/tests/background-jobs-action-labels.test.mjs +41 -0
  200. package/bizar-dash/tests/background-jobs-states.test.mjs +80 -0
  201. package/bizar-dash/tests/banner-tones.test.mjs +50 -0
  202. package/bizar-dash/tests/callout-tokens.test.mjs +30 -0
  203. package/bizar-dash/tests/claude-session-detail-states.test.mjs +67 -0
  204. package/bizar-dash/tests/components/BackgroundJobsSkeleton.test.tsx +38 -0
  205. package/bizar-dash/tests/density-tokens.test.mjs +46 -0
  206. package/bizar-dash/tests/doctor-route-shape.test.mjs +67 -0
  207. package/bizar-dash/tests/errorstate-retry.test.mjs +42 -0
  208. package/bizar-dash/tests/lightrag-skeleton.test.mjs +31 -0
  209. package/bizar-dash/tests/lightrag-states.test.mjs +60 -0
  210. package/bizar-dash/tests/loop-runtime.test.mjs +312 -0
  211. package/bizar-dash/tests/loops-agent-bus-routes.test.mjs +293 -0
  212. package/bizar-dash/tests/modal-focus-trap.test.mjs +114 -0
  213. package/bizar-dash/tests/orphan-tests-audit.test.mjs +80 -0
  214. package/bizar-dash/tests/palette-keyboard-nav.test.mjs +123 -0
  215. package/bizar-dash/tests/schedules-skeleton.test.mjs +25 -0
  216. package/bizar-dash/tests/schedules-states.test.mjs +67 -0
  217. package/bizar-dash/tests/sidebar-collapse-animation.test.mjs +37 -0
  218. package/bizar-dash/tests/sidebar-section-styles.test.mjs +55 -0
  219. package/bizar-dash/tests/skeleton-height.test.mjs +47 -0
  220. package/bizar-dash/tests/space-tokens.test.mjs +36 -0
  221. package/bizar-dash/tests/stattile-loading.test.mjs +28 -0
  222. package/bizar-dash/tests/task-splitter.test.mjs +268 -0
  223. package/bizar-dash/tests/topbar-popover-styles.test.mjs +53 -0
  224. package/bizar-dash/tests/update-channels.test.mjs +67 -0
  225. package/package.json +1 -1
  226. package/packages/sdk/${HOME}/.bizar_home/memory/projects/sdk/sessions/2026-07-16-e16c2f59.md +14 -0
  227. package/packages/sdk/.bizar/learning/instincts.jsonl +5 -0
  228. package/packages/sdk/package.json +1 -1
  229. package/scripts/loop-log.txt +29 -0
  230. package/scripts/loop-tasks.md +79 -0
  231. package/scripts/overnight-loop.mjs +60 -0
  232. package/bizar-dash/dist/assets/ArtifactsView-CfYB1le1.js +0 -21
  233. package/bizar-dash/dist/assets/ArtifactsView-CfYB1le1.js.map +0 -1
  234. package/bizar-dash/dist/assets/AuthView-D4frSShw.js.map +0 -1
  235. package/bizar-dash/dist/assets/BackgroundJobsView-DEStvILz.js +0 -1
  236. package/bizar-dash/dist/assets/BackgroundJobsView-DEStvILz.js.map +0 -1
  237. package/bizar-dash/dist/assets/ClaudeSessionsView-BprGo67K.js +0 -1
  238. package/bizar-dash/dist/assets/ClaudeSessionsView-BprGo67K.js.map +0 -1
  239. package/bizar-dash/dist/assets/ClipboardView-CPlTU5VG.js +0 -1
  240. package/bizar-dash/dist/assets/LightRAGView-DvqzQoBp.js +0 -6
  241. package/bizar-dash/dist/assets/LightRAGView-DvqzQoBp.js.map +0 -1
  242. package/bizar-dash/dist/assets/SchedulesView-o9AtIsH9.js +0 -1
  243. package/bizar-dash/dist/assets/SchedulesView-o9AtIsH9.js.map +0 -1
  244. package/bizar-dash/dist/assets/SettingsView-2HSFJPPK.js.map +0 -1
  245. package/bizar-dash/dist/assets/VoiceView-aoPVT6LJ.js +0 -1
  246. package/bizar-dash/dist/assets/main-B7D_IMTh.css +0 -1
  247. package/bizar-dash/dist/assets/main-BURAlktW.js +0 -277
  248. package/bizar-dash/dist/assets/main-BURAlktW.js.map +0 -1
  249. package/bizar-dash/tests/chat-session-create.test.mjs +0 -391
  250. package/bizar-dash/tests/chat-session-stream.test.mjs +0 -308
  251. package/bizar-dash/tests/cline-runner.test.mjs +0 -172
  252. package/bizar-dash/tests/cline-sessions-detail.test.mjs +0 -434
@@ -0,0 +1,138 @@
1
+ /**
2
+ * src/server/routes/loops.mjs
3
+ *
4
+ * G-autoloop Phase 5 — HTTP surface for the loop runtime.
5
+ *
6
+ * Endpoints (mounted under `/api/loops`):
7
+ * GET /api/loops — snapshot of all known loops
8
+ * GET /api/loops/:id — single loop state
9
+ * POST /api/loops — create a new pending loop
10
+ * PATCH /api/loops/:id — patch fields (intervalMs, maxIterations, etc.)
11
+ * POST /api/loops/:id/start — flip status to 'running' (idempotent)
12
+ * POST /api/loops/:id/stop — flip status to 'stopped' (idempotent)
13
+ * DELETE /api/loops/:id — delete the state file
14
+ *
15
+ * The actual scheduling (in-process setInterval) happens in
16
+ * `loop-runtime.mjs`. This router owns only the HTTP wiring — keeps
17
+ * the runtime pure + testable.
18
+ *
19
+ * Phase 5 deliberately stops short of dispatching real tasks here.
20
+ * That's wired in a follow-up so we can ship the loop infrastructure
21
+ * (CRUD + scheduler) and let a dedicated route pair it with the real
22
+ * `taskDelegator` + project-store integration.
23
+ */
24
+
25
+ import { Router } from 'express';
26
+ import {
27
+ createLoop,
28
+ readLoop,
29
+ patchLoop,
30
+ deleteLoop,
31
+ listLoops,
32
+ snapshotLoops,
33
+ startLoop,
34
+ stopLoop,
35
+ } from '../loop-runtime.mjs';
36
+ import { wrap } from './_shared.mjs';
37
+
38
+ const VALID_PATCH_FIELDS = new Set([
39
+ 'name',
40
+ 'intervalMs',
41
+ 'maxIterations',
42
+ 'projectId',
43
+ ]);
44
+
45
+ /**
46
+ * @param {Object} deps
47
+ * @param {Function} [deps.broadcast]
48
+ */
49
+ export function createLoopsRouter({ broadcast = () => {} } = {}) {
50
+ const router = Router();
51
+
52
+ router.get('/loops', wrap(async (req, res) => {
53
+ const snap = snapshotLoops();
54
+ res.json({ loops: Object.values(snap), count: Object.keys(snap).length });
55
+ }));
56
+
57
+ router.get('/loops/:id', wrap(async (req, res) => {
58
+ const s = readLoop(req.params.id);
59
+ if (!s) {
60
+ res.status(404).json({ error: 'not_found' });
61
+ return;
62
+ }
63
+ res.json(s);
64
+ }));
65
+
66
+ router.post('/loops', wrap(async (req, res) => {
67
+ const body = req.body || {};
68
+ if (!body.projectId) {
69
+ res.status(400).json({ error: 'bad_request', message: 'projectId required' });
70
+ return;
71
+ }
72
+ const s = createLoop({
73
+ projectId: body.projectId,
74
+ name: body.name,
75
+ intervalMs: body.intervalMs,
76
+ maxIterations: body.maxIterations,
77
+ });
78
+ broadcast({ type: 'loops:change', loop: s });
79
+ res.status(201).json(s);
80
+ }));
81
+
82
+ router.patch('/loops/:id', wrap(async (req, res) => {
83
+ const patch = {};
84
+ for (const [k, v] of Object.entries(req.body || {})) {
85
+ if (VALID_PATCH_FIELDS.has(k)) patch[k] = v;
86
+ }
87
+ const next = patchLoop(req.params.id, patch);
88
+ if (!next) {
89
+ res.status(404).json({ error: 'not_found' });
90
+ return;
91
+ }
92
+ broadcast({ type: 'loops:change', loop: next });
93
+ res.json(next);
94
+ }));
95
+
96
+ // Start a loop. The runtime owns the timer; this just flips state.
97
+ // We re-use `startLoop` from the runtime so the in-process scheduler
98
+ // is the single source of truth. If the caller passed a `dry` flag,
99
+ // we only flip the state without engaging the timer — useful for
100
+ // the dashboard's "arm only" toggle.
101
+ router.post('/loops/:id/start', wrap(async (req, res) => {
102
+ const dry = req.body && req.body.dry === true;
103
+ let next;
104
+ try {
105
+ next = dry ? patchLoop(req.params.id, { status: 'running' }) : startLoop(req.params.id, {});
106
+ } catch (err) {
107
+ res.status(404).json({ error: 'not_found', message: err.message });
108
+ return;
109
+ }
110
+ if (!next) {
111
+ res.status(404).json({ error: 'not_found' });
112
+ return;
113
+ }
114
+ broadcast({ type: 'loops:change', loop: next });
115
+ res.json(next);
116
+ }));
117
+
118
+ router.post('/loops/:id/stop', wrap(async (req, res) => {
119
+ const next = stopLoop(req.params.id);
120
+ if (!next) {
121
+ res.status(404).json({ error: 'not_found' });
122
+ return;
123
+ }
124
+ broadcast({ type: 'loops:change', loop: next });
125
+ res.json(next);
126
+ }));
127
+
128
+ router.delete('/loops/:id', wrap(async (req, res) => {
129
+ // Stop the in-process timer first so it doesn't keep firing on a
130
+ // deleted state.json.
131
+ stopLoop(req.params.id);
132
+ deleteLoop(req.params.id);
133
+ broadcast({ type: 'loops:deleted', loopId: req.params.id });
134
+ res.status(204).end();
135
+ }));
136
+
137
+ return router;
138
+ }
@@ -0,0 +1,241 @@
1
+ /**
2
+ * src/server/task-splitter.mjs
3
+ *
4
+ * v10.6.0 — G-autoloop Phase 3. Task ordering + auto-splitting.
5
+ *
6
+ * The "best order" problem the user cares about has three pieces:
7
+ *
8
+ * 1. Order. Which ready task should the loop pick next? Already
9
+ * solved in `loop-runtime.mjs:pickReadyTasks` (priority
10
+ * then age, dependencies gated on `done` status).
11
+ *
12
+ * 2. Size. How big is each task? Operators want to know
13
+ * "`tsk_abc` is L-sized" before they let the loop loose
14
+ * on it. Size is a heuristic over title/description
15
+ * length, presence of linked goal/KR, and historical
16
+ * completion times.
17
+ *
18
+ * 3. Split. Tasks tagged `size ∈ { L, XL }` get routed to the
19
+ * `task-decomposer` agent, which returns 2-N smaller
20
+ * tasks. The new tasks link back via `parentTaskId`
21
+ * so progress aggregates up. The parent stays open
22
+ * until all children are `done`.
23
+ *
24
+ * This module implements pure functions for (2) and (3). Splitting
25
+ * itself delegates to a caller-supplied `decompose` function so we
26
+ * don't bake an LLM call into the harness — that's the agent's job
27
+ * (per our architecture boundary: skills/MCP define agent behaviour,
28
+ * not the dash server).
29
+ */
30
+
31
+ const SIZE_ORDER = ['S', 'M', 'L', 'XL'];
32
+
33
+ /**
34
+ * Estimate a task's size bucket from its title, description, linked
35
+ * goals, and history. Heuristic, no LLM. Buckets: S, M, L, XL.
36
+ *
37
+ * Signals:
38
+ * - title word count : +0.2 per word over 6
39
+ * - description char count : +0.001 per char over 200
40
+ * - linked goal/KR : +1 size point each
41
+ * - avg historical duration : +1 if > 2h, +2 if > 6h
42
+ *
43
+ * @param {Object} task
44
+ * @param {string} task.title
45
+ * @param {string} [task.description]
46
+ * @param {Object} [task.metadata] may carry goalId/krId
47
+ * @param {Array} [history] prior tasks from this assignee;
48
+ * we use title-length buckets and
49
+ * avg `timeSpent` minutes as a
50
+ * proxy for difficulty.
51
+ * @returns {'S'|'M'|'L'|'XL'}
52
+ */
53
+ export function estimateSize(task, history = []) {
54
+ if (!task || typeof task !== 'object') return 'M';
55
+ const title = String(task.title || '');
56
+ const desc = String(task.description || '');
57
+ const meta = task.metadata || {};
58
+
59
+ let score = 0;
60
+ // Title length signal — every word over 6 adds 0.2 to the score.
61
+ const words = title.trim().split(/\s+/).filter(Boolean);
62
+ if (words.length > 6) score += (words.length - 6) * 0.2;
63
+
64
+ // Description length signal — chars over 200 add 0.001 each.
65
+ if (desc.length > 200) score += (desc.length - 200) * 0.001;
66
+
67
+ // Linked goal / KR — both count.
68
+ if (meta.goalId) score += 1;
69
+ if (meta.krId) score += 1;
70
+
71
+ // Historical signal — if prior similar-sized tasks took > 2h, push up.
72
+ const historicalMinutes = avgHistoricalMinutes(task, history);
73
+ if (historicalMinutes > 360) score += 2; // > 6h
74
+ else if (historicalMinutes > 120) score += 1; // > 2h
75
+
76
+ return scoreToBucket(score);
77
+ }
78
+
79
+ function scoreToBucket(score) {
80
+ if (score < 0.5) return 'S';
81
+ if (score < 1.5) return 'M';
82
+ if (score < 3.0) return 'L';
83
+ return 'XL';
84
+ }
85
+
86
+ /**
87
+ * Average minutes spent on historical tasks that share at least 2
88
+ * title-words with the candidate. We treat very short histories
89
+ * (< 2 items) as unreliable and return 0.
90
+ *
91
+ * @param {Object} task
92
+ * @param {Array} history
93
+ * @returns {number}
94
+ */
95
+ export function avgHistoricalMinutes(task, history) {
96
+ if (!task || !Array.isArray(history) || history.length < 2) return 0;
97
+ const titleWords = new Set(
98
+ String(task.title || '').toLowerCase().split(/\s+/).filter((w) => w.length > 3),
99
+ );
100
+ if (titleWords.size === 0) return 0;
101
+ const similar = history.filter((h) => {
102
+ if (!h || !h.title) return false;
103
+ const hw = new Set(String(h.title).toLowerCase().split(/\s+/).filter((w) => w.length > 3));
104
+ let overlap = 0;
105
+ for (const w of titleWords) if (hw.has(w)) overlap++;
106
+ return overlap >= 2;
107
+ });
108
+ if (similar.length === 0) return 0;
109
+ let total = 0;
110
+ let count = 0;
111
+ for (const h of similar) {
112
+ const mins = Number(h.timeSpent) || 0;
113
+ if (mins > 0) {
114
+ total += mins;
115
+ count++;
116
+ }
117
+ }
118
+ return count > 0 ? total / count : 0;
119
+ }
120
+
121
+ /**
122
+ * Decide whether a task should be split. Threshold: `size ∈ { L, XL }`
123
+ * OR explicit `metadata.autoSplit === true`.
124
+ *
125
+ * @param {Object} task
126
+ * @param {Array} [history]
127
+ * @returns {boolean}
128
+ */
129
+ export function shouldSplit(task, history = []) {
130
+ if (!task) return false;
131
+ if (task.metadata && task.metadata.autoSplit === true) return true;
132
+ if (task.metadata && task.metadata.autoSplit === false) return false;
133
+ const size = estimateSize(task, history);
134
+ return size === 'L' || size === 'XL';
135
+ }
136
+
137
+ /**
138
+ * Plan a split. Returns the **call shape** the caller dispatches to a
139
+ * `task-decomposer` agent (LLM-step), plus the metadata that should
140
+ * be patched back onto the parent. The agent must return:
141
+ *
142
+ * [{ title, description?, size?, priority? }, ...] // 2..N tasks
143
+ *
144
+ * @param {Object} parent
145
+ * @returns {Object} plan
146
+ * - kind: 'task-decompose'
147
+ * - parentTaskId
148
+ * - prompt: natural-language instruction for the agent
149
+ * - constraints: { min: 2, max: 5 } — the agent may not exceed this
150
+ */
151
+ export function planSplit(parent) {
152
+ if (!parent || !parent.id) throw new TypeError('planSplit: task id required');
153
+ const p = parent;
154
+ return {
155
+ kind: 'task-decompose',
156
+ parentTaskId: p.id,
157
+ prompt: [
158
+ `Break the following task into 2-5 smaller, independently-completable subtasks.`,
159
+ `Each subtask should be completable in under 30 minutes by a single subagent.`,
160
+ ``,
161
+ `Title: ${p.title || '(no title)'}`,
162
+ `Description: ${p.description || '(no description)'}`,
163
+ p.assignee ? `Original assignee: ${p.assignee}` : null,
164
+ p.metadata && p.metadata.goalId ? `Linked goal: ${p.metadata.goalId}` : null,
165
+ ].filter(Boolean).join('\n'),
166
+ constraints: { min: 2, max: 5 },
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Validate a `task-decompose` response. Returns an array of task
172
+ * seeds (without ids — those are minted by the task store on create).
173
+ *
174
+ * @param {Array} candidates
175
+ * @param {Object} [opts]
176
+ * @param {number} [opts.min=2]
177
+ * @param {number} [opts.max=5]
178
+ * @returns {Array} cleaned seeds, ready to be passed to `tasksStore.create`
179
+ */
180
+ export function acceptSplit(candidates, { min = 2, max = 5 } = {}) {
181
+ if (!Array.isArray(candidates)) return [];
182
+ const out = [];
183
+ for (const c of candidates) {
184
+ if (!c || typeof c !== 'object') continue;
185
+ if (!c.title || typeof c.title !== 'string') continue;
186
+ out.push({
187
+ title: String(c.title).slice(0, 200),
188
+ description: c.description ? String(c.description).slice(0, 4000) : '',
189
+ priority: Number.isFinite(c.priority) ? c.priority : 5,
190
+ tags: Array.isArray(c.tags) ? c.tags.filter((t) => typeof t === 'string') : ['autosplit'],
191
+ });
192
+ if (out.length >= max) break;
193
+ }
194
+ if (out.length < min) return [];
195
+ return out;
196
+ }
197
+
198
+ /**
199
+ * Roll up child task progress into the parent's `metadata.progress`
200
+ * (0..1) and auto-complete the parent when all children are `done`.
201
+ *
202
+ * Pure function — the caller is responsible for writing back to the
203
+ * task store. Returns `{ ratio, allDone }`. If `parent` has no
204
+ * children array, returns `{ ratio: 0, allDone: false }`.
205
+ *
206
+ * @param {Object} parent
207
+ * @returns {Object}
208
+ */
209
+ export function rollupProgress(parent) {
210
+ if (!parent) return { ratio: 0, allDone: false };
211
+ const children = Array.isArray(parent.subtasks) ? parent.subtasks : [];
212
+ if (children.length === 0) return { ratio: 0, allDone: false };
213
+ let done = 0;
214
+ for (const c of children) {
215
+ if (c && c.status === 'done') done++;
216
+ }
217
+ const ratio = done / children.length;
218
+ return { ratio, allDone: done === children.length };
219
+ }
220
+
221
+ /**
222
+ * Suggest a goal-aware priority bump. If a task links to a goal whose
223
+ * status is `at-risk` or `blocked`, bump its priority by 1; if `done`,
224
+ * drop the task to backlog. Pure helper; caller decides what to do
225
+ * with the result.
226
+ *
227
+ * @param {Object} task
228
+ * @param {Object} [goal] parsed goal object (from progress-parser)
229
+ * @returns {{ priority: number, action: 'bump'|'drop'|'none' }}
230
+ */
231
+ export function goalAwarePriority(task, goal) {
232
+ if (!task || !goal) return { priority: Number.isFinite(task && task.priority) ? task.priority : 5, action: 'none' };
233
+ const base = Number.isFinite(task.priority) ? task.priority : 5;
234
+ if (goal.status === 'at-risk') return { priority: Math.max(1, base - 1), action: 'bump' };
235
+ if (goal.status === 'blocked') return { priority: base, action: 'none' };
236
+ if (goal.status === 'done') return { priority: 9, action: 'drop' };
237
+ return { priority: base, action: 'none' };
238
+ }
239
+
240
+ /** Exported for tests + UI. */
241
+ export const __sizeBuckets = SIZE_ORDER;
@@ -1,8 +1,44 @@
1
- import { useState, type ReactNode } from 'react';
1
+ import { useState, type CSSProperties, type ReactNode } from 'react';
2
2
  import { Box } from '../ui/primitives/Box.js';
3
3
  import { Stack } from '../ui/primitives/Stack.js';
4
4
  import { Separator } from '../ui/primitives/Separator.js';
5
5
  import { ScrollArea } from '../ui/primitives/ScrollArea.js';
6
+
7
+ const sidebarSectionHeaderStyle: CSSProperties = {
8
+ display: 'flex',
9
+ alignItems: 'center',
10
+ gap: 'var(--space-2)',
11
+ };
12
+
13
+ const sidebarSectionLabelStyle: CSSProperties = {
14
+ fontSize: 'var(--fs-12)',
15
+ fontWeight: 600,
16
+ color: 'var(--fg-subtle)',
17
+ letterSpacing: 'var(--tracking-wide)',
18
+ textTransform: 'uppercase',
19
+ flex: 1,
20
+ padding: 'var(--space-1) var(--space-3)',
21
+ };
22
+
23
+ const sidebarSectionToggleStyle: CSSProperties = {
24
+ display: 'inline-flex',
25
+ alignItems: 'center',
26
+ justifyContent: 'center',
27
+ width: 20,
28
+ height: 20,
29
+ background: 'transparent',
30
+ border: 0,
31
+ color: 'var(--fg-subtle)',
32
+ cursor: 'pointer',
33
+ padding: 0,
34
+ };
35
+
36
+ const sidebarSectionChevronStyle = (open: boolean): CSSProperties => ({
37
+ display: 'inline-block',
38
+ transition: 'transform var(--motion-fast) var(--ease-out)',
39
+ transform: open ? 'rotate(90deg)' : 'rotate(0deg)',
40
+ fontSize: 10,
41
+ });
6
42
  import {
7
43
  Activity,
8
44
  Bell,
@@ -362,18 +398,8 @@ function SidebarSectionView({
362
398
  return (
363
399
  <Stack gap={1} data-sidebar-section={section.id}>
364
400
  {!collapsed && (
365
- <div role="presentation" style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)' }}>
366
- <span
367
- style={{
368
- fontSize: 'var(--fs-12)',
369
- fontWeight: 600,
370
- color: 'var(--fg-subtle)',
371
- letterSpacing: 'var(--tracking-wide)',
372
- textTransform: 'uppercase',
373
- flex: 1,
374
- padding: 'var(--space-1) var(--space-3)',
375
- }}
376
- >
401
+ <div role="presentation" style={sidebarSectionHeaderStyle}>
402
+ <span style={sidebarSectionLabelStyle}>
377
403
  {section.label}
378
404
  </span>
379
405
  <button
@@ -383,28 +409,12 @@ function SidebarSectionView({
383
409
  aria-controls={`sidebar-section-${section.id}`}
384
410
  data-section-toggle={section.id}
385
411
  data-testid={`sidebar-section-toggle-${section.id}`}
386
- style={{
387
- display: 'inline-flex',
388
- alignItems: 'center',
389
- justifyContent: 'center',
390
- width: 20,
391
- height: 20,
392
- background: 'transparent',
393
- border: 0,
394
- color: 'var(--fg-subtle)',
395
- cursor: 'pointer',
396
- padding: 0,
397
- }}
412
+ style={sidebarSectionToggleStyle}
398
413
  >
399
414
  <span
400
415
  aria-hidden="true"
401
416
  data-testid={`sidebar-section-chevron-${section.id}`}
402
- style={{
403
- display: 'inline-block',
404
- transition: 'transform var(--motion-fast) var(--ease-out)',
405
- transform: sectionOpen ? 'rotate(90deg)' : 'rotate(0deg)',
406
- fontSize: 10,
407
- }}
417
+ style={sidebarSectionChevronStyle(sectionOpen)}
408
418
  >
409
419
 
410
420
  </span>
@@ -1,4 +1,4 @@
1
- import { useState, type ReactNode } from 'react';
1
+ import { useState, type CSSProperties, type ReactNode } from 'react';
2
2
  import { ChevronDown, Search } from 'lucide-react';
3
3
  import { Box } from '../ui/primitives/Box.js';
4
4
  import { Inline } from '../ui/primitives/Inline.js';
@@ -11,6 +11,60 @@ import { useConnectionState } from '../data/useWebSocket.js';
11
11
  import { NotificationsPopover } from '../ui/feedback/NotificationsPopover.js';
12
12
  import { fetchJson, FetchError } from '../data/fetcher.js';
13
13
 
14
+ const popoverLabelStyle: CSSProperties = {
15
+ fontSize: 'var(--fs-12)',
16
+ color: 'var(--fg-muted)',
17
+ marginBottom: 8,
18
+ };
19
+
20
+ const popoverEmptyStyle: CSSProperties = {
21
+ fontSize: 'var(--fs-12)',
22
+ color: 'var(--fg-muted)',
23
+ };
24
+
25
+ const popoverListStyle: CSSProperties = {
26
+ listStyle: 'none',
27
+ margin: 0,
28
+ padding: 0,
29
+ display: 'flex',
30
+ flexDirection: 'column',
31
+ gap: 2,
32
+ };
33
+
34
+ const popoverItemStyle = (isActive: boolean, busy: boolean): CSSProperties => ({
35
+ display: 'flex',
36
+ width: '100%',
37
+ alignItems: 'center',
38
+ justifyContent: 'space-between',
39
+ gap: 8,
40
+ padding: '6px 8px',
41
+ background: isActive ? 'var(--surface-2)' : 'transparent',
42
+ border: 0,
43
+ borderRadius: 'var(--radius-sm)',
44
+ color: 'var(--fg)',
45
+ cursor: busy ? 'wait' : 'pointer',
46
+ fontSize: 'var(--fs-13)',
47
+ textAlign: 'left',
48
+ });
49
+
50
+ const popoverItemLabelStyle: CSSProperties = {
51
+ minWidth: 0,
52
+ overflow: 'hidden',
53
+ textOverflow: 'ellipsis',
54
+ whiteSpace: 'nowrap',
55
+ };
56
+
57
+ const popoverItemMarkerStyle = (active: boolean): CSSProperties => ({
58
+ fontSize: 'var(--fs-11)',
59
+ color: active ? 'var(--accent)' : 'var(--fg-muted)',
60
+ });
61
+
62
+ const popoverErrorStyle: CSSProperties = {
63
+ marginTop: 8,
64
+ fontSize: 'var(--fs-12)',
65
+ color: 'var(--danger)',
66
+ };
67
+
14
68
  /**
15
69
  * Topbar — the 56px horizontal bar at the top of the v8 shell.
16
70
  *
@@ -141,13 +195,13 @@ function BrandPlaceholder(): JSX.Element {
141
195
  </button>
142
196
  </PopoverTrigger>
143
197
  <PopoverContent align="start" sideOffset={6}>
144
- <Box style={{ fontSize: 'var(--fs-12)', color: 'var(--fg-muted)', marginBottom: 8 }}>
198
+ <Box style={popoverLabelStyle}>
145
199
  Switch project
146
200
  </Box>
147
201
  {list.length === 0 ? (
148
- <Box style={{ fontSize: 'var(--fs-12)', color: 'var(--fg-muted)' }}>No projects registered</Box>
202
+ <Box style={popoverEmptyStyle}>No projects registered</Box>
149
203
  ) : (
150
- <ul role="menu" style={{ listStyle: 'none', margin: 0, padding: 0, display: 'flex', flexDirection: 'column', gap: 2 }}>
204
+ <ul role="menu" style={popoverListStyle}>
151
205
  {list.map((p) => {
152
206
  const isActive = p.id === activeId;
153
207
  return (
@@ -159,27 +213,13 @@ function BrandPlaceholder(): JSX.Element {
159
213
  onClick={() => { void activate(p.id); }}
160
214
  data-testid={`topbar-project-option-${p.id}`}
161
215
  aria-current={isActive ? 'true' : undefined}
162
- style={{
163
- display: 'flex',
164
- width: '100%',
165
- alignItems: 'center',
166
- justifyContent: 'space-between',
167
- gap: 8,
168
- padding: '6px 8px',
169
- background: isActive ? 'var(--surface-2)' : 'transparent',
170
- border: 0,
171
- borderRadius: 'var(--radius-sm)',
172
- color: 'var(--fg)',
173
- cursor: busy !== null ? 'wait' : 'pointer',
174
- fontSize: 'var(--fs-13)',
175
- textAlign: 'left',
176
- }}
216
+ style={popoverItemStyle(isActive, busy !== null)}
177
217
  >
178
- <span style={{ minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
218
+ <span style={popoverItemLabelStyle}>
179
219
  {p.name || p.id}
180
220
  </span>
181
- {isActive && <span style={{ fontSize: 'var(--fs-11)', color: 'var(--accent)' }}>●</span>}
182
- {!isActive && busy === p.id && <span style={{ fontSize: 'var(--fs-11)', color: 'var(--fg-muted)' }}>…</span>}
221
+ {isActive && <span style={popoverItemMarkerStyle(true)}>●</span>}
222
+ {!isActive && busy === p.id && <span style={popoverItemMarkerStyle(false)}>…</span>}
183
223
  </button>
184
224
  </li>
185
225
  );
@@ -187,7 +227,7 @@ function BrandPlaceholder(): JSX.Element {
187
227
  </ul>
188
228
  )}
189
229
  {error !== null && (
190
- <Box role="alert" data-testid="topbar-project-error" style={{ marginTop: 8, fontSize: 'var(--fs-12)', color: 'var(--danger)' }}>
230
+ <Box role="alert" data-testid="topbar-project-error" style={popoverErrorStyle}>
191
231
  {error}
192
232
  </Box>
193
233
  )}
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  import { useEffect, useRef, useState } from 'react';
13
+ import { useFocusTrap } from './useFocusTrap.js';
13
14
  import { Bell, Check } from 'lucide-react';
14
15
  import { Box } from '../primitives/Box.js';
15
16
  import { Inline } from '../primitives/Inline.js';
@@ -109,6 +110,8 @@ export function NotificationsPopover(): JSX.Element {
109
110
  };
110
111
 
111
112
  const unread = items.length;
113
+
114
+ useFocusTrap(ref, open);
112
115
  return (
113
116
  <Box style={{ position: 'relative' }} ref={ref}>
114
117
  <button
@@ -0,0 +1,58 @@
1
+ /**
2
+ * useFocusTrap — traps Tab/Shift+Tab focus inside a container.
3
+ *
4
+ * Used by popover/dialog primitives that don't use @radix-ui/react-dialog.
5
+ * Radix Dialog components get this for free; this hook covers the few
6
+ * custom role="dialog" surfaces that remain.
7
+ *
8
+ * ponytail: edge-case (disabled elements, inert) — upgrade to focus-trap npm pkg when needed.
9
+ */
10
+
11
+ import { useEffect, useRef } from 'react';
12
+
13
+ const FOCUSABLE = [
14
+ 'a[href]',
15
+ 'button:not([disabled])',
16
+ 'input:not([disabled])',
17
+ 'select:not([disabled])',
18
+ 'textarea:not([disabled])',
19
+ '[tabindex]:not([tabindex="-1"])',
20
+ 'details > summary',
21
+ ].join(',');
22
+
23
+ export function useFocusTrap(containerRef: React.RefObject<HTMLElement | null>, active: boolean): void {
24
+ const previousFocus = useRef<Element | null>(null);
25
+
26
+ useEffect(() => {
27
+ if (!active || !containerRef.current) return;
28
+
29
+ // Restore focus on unmount.
30
+ previousFocus.current = document.activeElement;
31
+ const container = containerRef.current;
32
+
33
+ // Focus first focusable child on mount.
34
+ const first = container.querySelector<HTMLElement>(FOCUSABLE);
35
+ first?.focus();
36
+
37
+ const onKeyDown = (e: KeyboardEvent): void => {
38
+ if (e.key !== 'Tab') return;
39
+ const focusable = Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE));
40
+ if (focusable.length === 0) { e.preventDefault(); return; }
41
+
42
+ const first = focusable[0];
43
+ const last = focusable[focusable.length - 1];
44
+
45
+ if (e.shiftKey) {
46
+ if (document.activeElement === first) { e.preventDefault(); last.focus(); }
47
+ } else {
48
+ if (document.activeElement === last) { e.preventDefault(); first.focus(); }
49
+ }
50
+ };
51
+
52
+ container.addEventListener('keydown', onKeyDown);
53
+ return () => {
54
+ container.removeEventListener('keydown', onKeyDown);
55
+ if (previousFocus.current instanceof HTMLElement) previousFocus.current.focus();
56
+ };
57
+ }, [active, containerRef]);
58
+ }
@@ -49,6 +49,7 @@ export function CommandPalette(props: CommandPaletteProps) {
49
49
  className={cx('v8-command', className)}
50
50
  label="Command palette"
51
51
  shouldFilter
52
+ loop
52
53
  >
53
54
  <div
54
55
  style={{