@polderlabs/bizar 10.4.3 → 10.4.4

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 (228) hide show
  1. package/bizar-dash/dist/assets/{ActivityFeed-DC0JiK49.js → ActivityFeed-RQeyFifx.js} +1 -1
  2. package/bizar-dash/dist/assets/{ActivityFeed-DC0JiK49.js.map → ActivityFeed-RQeyFifx.js.map} +1 -1
  3. package/bizar-dash/dist/assets/{ActivityView-ubEDAWMY.js → ActivityView-CibyssPM.js} +1 -1
  4. package/bizar-dash/dist/assets/{ActivityView-ubEDAWMY.js.map → ActivityView-CibyssPM.js.map} +1 -1
  5. package/bizar-dash/dist/assets/AdminView-BjczABeB.js +11 -0
  6. package/bizar-dash/dist/assets/AdminView-BjczABeB.js.map +1 -0
  7. package/bizar-dash/dist/assets/{AgentActivity-BLcqqkt2.js → AgentActivity-Bywt1twv.js} +1 -1
  8. package/bizar-dash/dist/assets/{AgentActivity-BLcqqkt2.js.map → AgentActivity-Bywt1twv.js.map} +1 -1
  9. package/bizar-dash/dist/assets/{AgentsView-CIQkZH8_.js → AgentsView-BnOjjM74.js} +4 -4
  10. package/bizar-dash/dist/assets/AgentsView-BnOjjM74.js.map +1 -0
  11. package/bizar-dash/dist/assets/{Alert-MwVEymjy.js → Alert-BZGk39mB.js} +1 -1
  12. package/bizar-dash/dist/assets/{Alert-MwVEymjy.js.map → Alert-BZGk39mB.js.map} +1 -1
  13. package/bizar-dash/dist/assets/{ArtifactsView-B-YUwZyb.js → ArtifactsView-CfYB1le1.js} +3 -3
  14. package/bizar-dash/dist/assets/ArtifactsView-CfYB1le1.js.map +1 -0
  15. package/bizar-dash/dist/assets/{AuthView-LaQuGo4u.js → AuthView-D4frSShw.js} +1 -1
  16. package/bizar-dash/dist/assets/{AuthView-LaQuGo4u.js.map → AuthView-D4frSShw.js.map} +1 -1
  17. package/bizar-dash/dist/assets/Avatar-BYg0rkPA.js +6 -0
  18. package/bizar-dash/dist/assets/Avatar-BYg0rkPA.js.map +1 -0
  19. package/bizar-dash/dist/assets/{BackgroundJobsView-CO3DEG8R.js → BackgroundJobsView-DEStvILz.js} +1 -1
  20. package/bizar-dash/dist/assets/{BackgroundJobsView-CO3DEG8R.js.map → BackgroundJobsView-DEStvILz.js.map} +1 -1
  21. package/bizar-dash/dist/assets/{BackupView-Cjf23ZWc.js → BackupView-B875KQa4.js} +1 -1
  22. package/bizar-dash/dist/assets/{BackupView-Cjf23ZWc.js.map → BackupView-B875KQa4.js.map} +1 -1
  23. package/bizar-dash/dist/assets/Banner-C8HEcT5l.js +1 -0
  24. package/bizar-dash/dist/assets/Banner-C8HEcT5l.js.map +1 -0
  25. package/bizar-dash/dist/assets/{Card-BmXiT4c6.js → Card-BtsmkhJa.js} +1 -1
  26. package/bizar-dash/dist/assets/{Card-BmXiT4c6.js.map → Card-BtsmkhJa.js.map} +1 -1
  27. package/bizar-dash/dist/assets/{ChatView-DXKFwvgp.js → ChatView-CkNS0SD8.js} +3 -3
  28. package/bizar-dash/dist/assets/{ChatView-DXKFwvgp.js.map → ChatView-CkNS0SD8.js.map} +1 -1
  29. package/bizar-dash/dist/assets/{Chip-Dei66BLx.js → Chip-XPOk0d_E.js} +1 -1
  30. package/bizar-dash/dist/assets/{Chip-Dei66BLx.js.map → Chip-XPOk0d_E.js.map} +1 -1
  31. package/bizar-dash/dist/assets/{ClaudeSessionsView-D-YNmzKi.js → ClaudeSessionsView-BprGo67K.js} +1 -1
  32. package/bizar-dash/dist/assets/{ClaudeSessionsView-D-YNmzKi.js.map → ClaudeSessionsView-BprGo67K.js.map} +1 -1
  33. package/bizar-dash/dist/assets/ClipboardView-CPlTU5VG.js +1 -0
  34. package/bizar-dash/dist/assets/ClipboardView-CPlTU5VG.js.map +1 -0
  35. package/bizar-dash/dist/assets/{ConfigView-BMBgifw5.js → ConfigView-BntUrW7s.js} +1 -1
  36. package/bizar-dash/dist/assets/{ConfigView-BMBgifw5.js.map → ConfigView-BntUrW7s.js.map} +1 -1
  37. package/bizar-dash/dist/assets/{DiagnosticsView-C0sdqhEl.js → DiagnosticsView-4x0Zu3yP.js} +1 -1
  38. package/bizar-dash/dist/assets/{DiagnosticsView-C0sdqhEl.js.map → DiagnosticsView-4x0Zu3yP.js.map} +1 -1
  39. package/bizar-dash/dist/assets/DialogsView-Xd02dVAX.js +6 -0
  40. package/bizar-dash/dist/assets/DialogsView-Xd02dVAX.js.map +1 -0
  41. package/bizar-dash/dist/assets/{DoctorView-CiFwRMW0.js → DoctorView-DepBdQGR.js} +2 -2
  42. package/bizar-dash/dist/assets/{DoctorView-CiFwRMW0.js.map → DoctorView-DepBdQGR.js.map} +1 -1
  43. package/bizar-dash/dist/assets/{EmptyState-CdDQmfEL.js → EmptyState-DjKtY1Qi.js} +1 -1
  44. package/bizar-dash/dist/assets/{EmptyState-CdDQmfEL.js.map → EmptyState-DjKtY1Qi.js.map} +1 -1
  45. package/bizar-dash/dist/assets/{EnvVarsView-CNO03NDJ.js → EnvVarsView-S4Pbm128.js} +3 -3
  46. package/bizar-dash/dist/assets/{EnvVarsView-CNO03NDJ.js.map → EnvVarsView-S4Pbm128.js.map} +1 -1
  47. package/bizar-dash/dist/assets/{ErrorState-BRvj8qZZ.js → ErrorState-CBJbEQ8B.js} +1 -1
  48. package/bizar-dash/dist/assets/{ErrorState-BRvj8qZZ.js.map → ErrorState-CBJbEQ8B.js.map} +1 -1
  49. package/bizar-dash/dist/assets/EvalView-BeAyMCD1.js +1 -0
  50. package/bizar-dash/dist/assets/{EvalView-lgUhlGJh.js.map → EvalView-BeAyMCD1.js.map} +1 -1
  51. package/bizar-dash/dist/assets/{GoalsView-DmkCCs9i.js → GoalsView-BZr8In4c.js} +1 -1
  52. package/bizar-dash/dist/assets/{GoalsView-DmkCCs9i.js.map → GoalsView-BZr8In4c.js.map} +1 -1
  53. package/bizar-dash/dist/assets/{Grid-BThCPJaV.js → Grid--WKvqu5h.js} +1 -1
  54. package/bizar-dash/dist/assets/{Grid-BThCPJaV.js.map → Grid--WKvqu5h.js.map} +1 -1
  55. package/bizar-dash/dist/assets/{HistoryView-Cn2cPiHZ.js → HistoryView-djEbzqtk.js} +1 -1
  56. package/bizar-dash/dist/assets/{HistoryView-Cn2cPiHZ.js.map → HistoryView-djEbzqtk.js.map} +1 -1
  57. package/bizar-dash/dist/assets/{Input-cAQOvR8U.js → Input-h_nSOtwm.js} +1 -1
  58. package/bizar-dash/dist/assets/{Input-cAQOvR8U.js.map → Input-h_nSOtwm.js.map} +1 -1
  59. package/bizar-dash/dist/assets/LibrariesView-Vez8CvNq.js +6 -0
  60. package/bizar-dash/dist/assets/{LibrariesView-Ca_PRjUm.js.map → LibrariesView-Vez8CvNq.js.map} +1 -1
  61. package/bizar-dash/dist/assets/{LightRAGView-CMk9wOae.js → LightRAGView-DvqzQoBp.js} +1 -1
  62. package/bizar-dash/dist/assets/{LightRAGView-CMk9wOae.js.map → LightRAGView-DvqzQoBp.js.map} +1 -1
  63. package/bizar-dash/dist/assets/{ListHeader-BLAeshe0.js → ListHeader-CM8QoIWG.js} +1 -1
  64. package/bizar-dash/dist/assets/{ListHeader-BLAeshe0.js.map → ListHeader-CM8QoIWG.js.map} +1 -1
  65. package/bizar-dash/dist/assets/{MemoryView-wo2Pu7xQ.js → MemoryView-DnL8E2vI.js} +1 -1
  66. package/bizar-dash/dist/assets/{MemoryView-wo2Pu7xQ.js.map → MemoryView-DnL8E2vI.js.map} +1 -1
  67. package/bizar-dash/dist/assets/MiscView-B2YQXhZ_.js +1 -0
  68. package/bizar-dash/dist/assets/MiscView-B2YQXhZ_.js.map +1 -0
  69. package/bizar-dash/dist/assets/{ModsView-CV_RxdzJ.js → ModsView-BVM3X5xc.js} +1 -1
  70. package/bizar-dash/dist/assets/{ModsView-CV_RxdzJ.js.map → ModsView-BVM3X5xc.js.map} +1 -1
  71. package/bizar-dash/dist/assets/NotificationsView-A-Xs8BL1.js +6 -0
  72. package/bizar-dash/dist/assets/NotificationsView-A-Xs8BL1.js.map +1 -0
  73. package/bizar-dash/dist/assets/ObsidianView-mI3qW9OP.js +1 -0
  74. package/bizar-dash/dist/assets/{ObsidianView-C-AvHfpT.js.map → ObsidianView-mI3qW9OP.js.map} +1 -1
  75. package/bizar-dash/dist/assets/OverviewView-By69Cr5H.js +1 -0
  76. package/bizar-dash/dist/assets/OverviewView-By69Cr5H.js.map +1 -0
  77. package/bizar-dash/dist/assets/{ProgressBar-ya2PCj8L.js → ProgressBar-BnaBqv-W.js} +1 -1
  78. package/bizar-dash/dist/assets/{ProgressBar-ya2PCj8L.js.map → ProgressBar-BnaBqv-W.js.map} +1 -1
  79. package/bizar-dash/dist/assets/{ProjectsView-N9zqFZ_O.js → ProjectsView-C81WTFka.js} +1 -1
  80. package/bizar-dash/dist/assets/{ProjectsView-N9zqFZ_O.js.map → ProjectsView-C81WTFka.js.map} +1 -1
  81. package/bizar-dash/dist/assets/{ProvidersView-BcL8jjwK.js → ProvidersView-2cn-TALE.js} +2 -2
  82. package/bizar-dash/dist/assets/{ProvidersView-BcL8jjwK.js.map → ProvidersView-2cn-TALE.js.map} +1 -1
  83. package/bizar-dash/dist/assets/{SchedulesView-8qxH3bg7.js → SchedulesView-o9AtIsH9.js} +1 -1
  84. package/bizar-dash/dist/assets/{SchedulesView-8qxH3bg7.js.map → SchedulesView-o9AtIsH9.js.map} +1 -1
  85. package/bizar-dash/dist/assets/{Select-D5AuVxxV.js → Select-CiGWEN8I.js} +1 -1
  86. package/bizar-dash/dist/assets/{Select-D5AuVxxV.js.map → Select-CiGWEN8I.js.map} +1 -1
  87. package/bizar-dash/dist/assets/{SettingsRow-BBALf9dA.js → SettingsRow-CflGJymA.js} +1 -1
  88. package/bizar-dash/dist/assets/{SettingsRow-BBALf9dA.js.map → SettingsRow-CflGJymA.js.map} +1 -1
  89. package/bizar-dash/dist/assets/{SettingsView-UD403neY.js → SettingsView-2HSFJPPK.js} +1 -1
  90. package/bizar-dash/dist/assets/{SettingsView-UD403neY.js.map → SettingsView-2HSFJPPK.js.map} +1 -1
  91. package/bizar-dash/dist/assets/{Sheet-CxXf6gvC.js → Sheet-B-CMP9-_.js} +1 -1
  92. package/bizar-dash/dist/assets/{Sheet-CxXf6gvC.js.map → Sheet-B-CMP9-_.js.map} +1 -1
  93. package/bizar-dash/dist/assets/{Slider-hvaVD_SW.js → Slider-Z4TP_CXY.js} +1 -1
  94. package/bizar-dash/dist/assets/{Slider-hvaVD_SW.js.map → Slider-Z4TP_CXY.js.map} +1 -1
  95. package/bizar-dash/dist/assets/{Sparkline-DqmQzdJH.js → Sparkline-cvkv2HbU.js} +1 -1
  96. package/bizar-dash/dist/assets/{Sparkline-DqmQzdJH.js.map → Sparkline-cvkv2HbU.js.map} +1 -1
  97. package/bizar-dash/dist/assets/{Switch-C-_zcNdl.js → Switch-jcYo4lmp.js} +1 -1
  98. package/bizar-dash/dist/assets/{Switch-C-_zcNdl.js.map → Switch-jcYo4lmp.js.map} +1 -1
  99. package/bizar-dash/dist/assets/{TasksView-DseftDlx.js → TasksView-CQaSFzWY.js} +2 -2
  100. package/bizar-dash/dist/assets/{TasksView-DseftDlx.js.map → TasksView-CQaSFzWY.js.map} +1 -1
  101. package/bizar-dash/dist/assets/{Textarea-CcQGSgSH.js → Textarea-xnAybFGd.js} +1 -1
  102. package/bizar-dash/dist/assets/{Textarea-CcQGSgSH.js.map → Textarea-xnAybFGd.js.map} +1 -1
  103. package/bizar-dash/dist/assets/UpdateView-BUAEcRCX.js +1 -0
  104. package/bizar-dash/dist/assets/UpdateView-BUAEcRCX.js.map +1 -0
  105. package/bizar-dash/dist/assets/{UsageView-P5_pQ68d.js → UsageView-DMFbBOfH.js} +1 -1
  106. package/bizar-dash/dist/assets/{UsageView-P5_pQ68d.js.map → UsageView-DMFbBOfH.js.map} +1 -1
  107. package/bizar-dash/dist/assets/{ViewHeader-BaCWNuVX.js → ViewHeader-C-t1U2cQ.js} +1 -1
  108. package/bizar-dash/dist/assets/{ViewHeader-BaCWNuVX.js.map → ViewHeader-C-t1U2cQ.js.map} +1 -1
  109. package/bizar-dash/dist/assets/VoiceView-aoPVT6LJ.js +1 -0
  110. package/bizar-dash/dist/assets/VoiceView-aoPVT6LJ.js.map +1 -0
  111. package/bizar-dash/dist/assets/{brain-D6tAeUDE.js → brain-DhAzcHUk.js} +1 -1
  112. package/bizar-dash/dist/assets/{brain-D6tAeUDE.js.map → brain-DhAzcHUk.js.map} +1 -1
  113. package/bizar-dash/dist/assets/{calendar-BrMJf3vl.js → calendar-B9Wbemh7.js} +1 -1
  114. package/bizar-dash/dist/assets/{calendar-BrMJf3vl.js.map → calendar-B9Wbemh7.js.map} +1 -1
  115. package/bizar-dash/dist/assets/{circle-fPTATiOs.js → circle-6NJF_Xl6.js} +1 -1
  116. package/bizar-dash/dist/assets/{circle-fPTATiOs.js.map → circle-6NJF_Xl6.js.map} +1 -1
  117. package/bizar-dash/dist/assets/{circle-alert-D7jn06kH.js → circle-alert-Dz8lGfCZ.js} +1 -1
  118. package/bizar-dash/dist/assets/{circle-alert-D7jn06kH.js.map → circle-alert-Dz8lGfCZ.js.map} +1 -1
  119. package/bizar-dash/dist/assets/{circle-check-DHs37Sot.js → circle-check-DFB50ojt.js} +1 -1
  120. package/bizar-dash/dist/assets/{circle-check-DHs37Sot.js.map → circle-check-DFB50ojt.js.map} +1 -1
  121. package/bizar-dash/dist/assets/{clock-E-G5aGua.js → clock-ClVA6-hQ.js} +1 -1
  122. package/bizar-dash/dist/assets/{clock-E-G5aGua.js.map → clock-ClVA6-hQ.js.map} +1 -1
  123. package/bizar-dash/dist/assets/{download-DBkNb6oI.js → download-BvJddD8D.js} +1 -1
  124. package/bizar-dash/dist/assets/{download-DBkNb6oI.js.map → download-BvJddD8D.js.map} +1 -1
  125. package/bizar-dash/dist/assets/{external-link-C4pHGbz2.js → external-link-DvL89gaZ.js} +1 -1
  126. package/bizar-dash/dist/assets/{external-link-C4pHGbz2.js.map → external-link-DvL89gaZ.js.map} +1 -1
  127. package/bizar-dash/dist/assets/{file-text-CEhDgLq2.js → file-text-BOroDtZE.js} +1 -1
  128. package/bizar-dash/dist/assets/{file-text-CEhDgLq2.js.map → file-text-BOroDtZE.js.map} +1 -1
  129. package/bizar-dash/dist/assets/{filter-CEYjeWFX.js → filter-DwPjMRQt.js} +1 -1
  130. package/bizar-dash/dist/assets/{filter-CEYjeWFX.js.map → filter-DwPjMRQt.js.map} +1 -1
  131. package/bizar-dash/dist/assets/{git-pull-request-Bo_wBZZw.js → git-pull-request-TDb1inux.js} +1 -1
  132. package/bizar-dash/dist/assets/{git-pull-request-Bo_wBZZw.js.map → git-pull-request-TDb1inux.js.map} +1 -1
  133. package/bizar-dash/dist/assets/{main-DDAMz_H5.css → main-B7D_IMTh.css} +1 -1
  134. package/bizar-dash/dist/assets/main-BURAlktW.js +277 -0
  135. package/bizar-dash/dist/assets/main-BURAlktW.js.map +1 -0
  136. package/bizar-dash/dist/assets/{network-BsfwrUA-.js → network-BcCoCzIY.js} +1 -1
  137. package/bizar-dash/dist/assets/{network-BsfwrUA-.js.map → network-BcCoCzIY.js.map} +1 -1
  138. package/bizar-dash/dist/assets/{pause-C8PN1_yO.js → pause-BllItT2_.js} +1 -1
  139. package/bizar-dash/dist/assets/{pause-C8PN1_yO.js.map → pause-BllItT2_.js.map} +1 -1
  140. package/bizar-dash/dist/assets/{pencil---XuvKlz.js → pencil-DAwM-CC3.js} +1 -1
  141. package/bizar-dash/dist/assets/{pencil---XuvKlz.js.map → pencil-DAwM-CC3.js.map} +1 -1
  142. package/bizar-dash/dist/assets/{play-fMLPTihK.js → play-mJh67AXo.js} +2 -2
  143. package/bizar-dash/dist/assets/{play-fMLPTihK.js.map → play-mJh67AXo.js.map} +1 -1
  144. package/bizar-dash/dist/assets/{plug-CG8al2Kq.js → plug-BjfntCMC.js} +1 -1
  145. package/bizar-dash/dist/assets/{plug-CG8al2Kq.js.map → plug-BjfntCMC.js.map} +1 -1
  146. package/bizar-dash/dist/assets/{plus-uA5Ecuch.js → plus-Cm6-fo14.js} +1 -1
  147. package/bizar-dash/dist/assets/{plus-uA5Ecuch.js.map → plus-Cm6-fo14.js.map} +1 -1
  148. package/bizar-dash/dist/assets/{power-DSUlNIuR.js → power-CXN9y2KV.js} +1 -1
  149. package/bizar-dash/dist/assets/{power-DSUlNIuR.js.map → power-CXN9y2KV.js.map} +1 -1
  150. package/bizar-dash/dist/assets/{power-off-1CAQsHHy.js → power-off-DUZqwMf9.js} +1 -1
  151. package/bizar-dash/dist/assets/{power-off-1CAQsHHy.js.map → power-off-DUZqwMf9.js.map} +1 -1
  152. package/bizar-dash/dist/assets/{refresh-ccw-BL7B0NO1.js → refresh-ccw-DA8_IB9l.js} +1 -1
  153. package/bizar-dash/dist/assets/{refresh-ccw-BL7B0NO1.js.map → refresh-ccw-DA8_IB9l.js.map} +1 -1
  154. package/bizar-dash/dist/assets/{refresh-cw-CR9BzJik.js → refresh-cw-DwlP59en.js} +1 -1
  155. package/bizar-dash/dist/assets/{refresh-cw-CR9BzJik.js.map → refresh-cw-DwlP59en.js.map} +1 -1
  156. package/bizar-dash/dist/assets/{rotate-ccw-BtnitLfb.js → rotate-ccw-DMTASfAn.js} +1 -1
  157. package/bizar-dash/dist/assets/{rotate-ccw-BtnitLfb.js.map → rotate-ccw-DMTASfAn.js.map} +1 -1
  158. package/bizar-dash/dist/assets/{rotate-cw-CHgXCO7O.js → rotate-cw-BDX5SE2s.js} +1 -1
  159. package/bizar-dash/dist/assets/{rotate-cw-CHgXCO7O.js.map → rotate-cw-BDX5SE2s.js.map} +1 -1
  160. package/bizar-dash/dist/assets/{save-RcZOXR0U.js → save-Y0jPK5D6.js} +1 -1
  161. package/bizar-dash/dist/assets/{save-RcZOXR0U.js.map → save-Y0jPK5D6.js.map} +1 -1
  162. package/bizar-dash/dist/assets/{send-Bg2X_ibk.js → send-C8wwPHhR.js} +2 -2
  163. package/bizar-dash/dist/assets/{send-Bg2X_ibk.js.map → send-C8wwPHhR.js.map} +1 -1
  164. package/bizar-dash/dist/assets/{shield-alert-Dm48D034.js → shield-alert-DEvq9UG0.js} +1 -1
  165. package/bizar-dash/dist/assets/{shield-alert-Dm48D034.js.map → shield-alert-DEvq9UG0.js.map} +1 -1
  166. package/bizar-dash/dist/assets/{trash-2-B885GY46.js → trash-2-D6qePV_q.js} +2 -2
  167. package/bizar-dash/dist/assets/{trash-2-B885GY46.js.map → trash-2-D6qePV_q.js.map} +1 -1
  168. package/bizar-dash/dist/assets/{trending-up-CqUBvVxw.js → trending-up-BZzo_1ic.js} +1 -1
  169. package/bizar-dash/dist/assets/{trending-up-CqUBvVxw.js.map → trending-up-BZzo_1ic.js.map} +1 -1
  170. package/bizar-dash/dist/assets/{triangle-alert-DLmUhymD.js → triangle-alert-B2oHbmoh.js} +1 -1
  171. package/bizar-dash/dist/assets/{triangle-alert-DLmUhymD.js.map → triangle-alert-B2oHbmoh.js.map} +1 -1
  172. package/bizar-dash/dist/assets/{upload-C-oafpyE.js → upload-Dy59aZn6.js} +1 -1
  173. package/bizar-dash/dist/assets/{upload-C-oafpyE.js.map → upload-Dy59aZn6.js.map} +1 -1
  174. package/bizar-dash/dist/assets/{zap-O_4Ys_hh.js → zap-DENVhJnO.js} +2 -2
  175. package/bizar-dash/dist/assets/{zap-O_4Ys_hh.js.map → zap-DENVhJnO.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/web/v8/App.tsx +25 -4
  179. package/bizar-dash/src/web/v8/shell/Sidebar.tsx +8 -5
  180. package/bizar-dash/src/web/v8/shell/Topbar.tsx +57 -1
  181. package/bizar-dash/src/web/v8/ui/styles/globals.css +14 -0
  182. package/bizar-dash/src/web/v8/ui/theme/ThemeToggle.tsx +11 -6
  183. package/bizar-dash/src/web/v8/views/Admin/AdminView.tsx +32 -5
  184. package/bizar-dash/src/web/v8/views/Agents/AgentsView.tsx +11 -4
  185. package/bizar-dash/src/web/v8/views/Artifacts/ArtifactsView.tsx +1 -1
  186. package/bizar-dash/src/web/v8/views/Clipboard/ClipboardView.tsx +1 -1
  187. package/bizar-dash/src/web/v8/views/Dialogs/DialogsView.tsx +1 -1
  188. package/bizar-dash/src/web/v8/views/Misc/MiscView.tsx +1 -1
  189. package/bizar-dash/src/web/v8/views/Notifications/NotificationsView.tsx +82 -13
  190. package/bizar-dash/src/web/v8/views/Overview/OverviewView.tsx +19 -0
  191. package/bizar-dash/src/web/v8/views/Voice/VoiceView.tsx +1 -1
  192. package/bizar-dash/tests/admin-run-grouping.test.mjs +42 -0
  193. package/bizar-dash/tests/agents-preview-truncate.test.mjs +35 -0
  194. package/bizar-dash/tests/empty-state-docs.test.mjs +55 -0
  195. package/bizar-dash/tests/focus-visible.test.mjs +30 -0
  196. package/bizar-dash/tests/notifications-dismiss-restore.test.mjs +85 -0
  197. package/bizar-dash/tests/palette-shortcut-hint.test.mjs +58 -0
  198. package/bizar-dash/tests/sidebar-distinct-icons.test.mjs +28 -0
  199. package/bizar-dash/tests/theme-toggle-icon.test.mjs +28 -0
  200. package/bizar-dash/tests/topbar-design-system.test.mjs +56 -0
  201. package/bizar-dash/vite.config.ts +8 -0
  202. package/package.json +1 -1
  203. package/packages/sdk/package.json +1 -1
  204. package/bizar-dash/dist/assets/AdminView-CI4zM2Qg.js +0 -11
  205. package/bizar-dash/dist/assets/AdminView-CI4zM2Qg.js.map +0 -1
  206. package/bizar-dash/dist/assets/AgentsView-CIQkZH8_.js.map +0 -1
  207. package/bizar-dash/dist/assets/ArtifactsView-B-YUwZyb.js.map +0 -1
  208. package/bizar-dash/dist/assets/Avatar-Dg4OF4RD.js +0 -6
  209. package/bizar-dash/dist/assets/Avatar-Dg4OF4RD.js.map +0 -1
  210. package/bizar-dash/dist/assets/ClipboardView-BA323V2J.js +0 -1
  211. package/bizar-dash/dist/assets/ClipboardView-BA323V2J.js.map +0 -1
  212. package/bizar-dash/dist/assets/DialogsView-DzTnyX8K.js +0 -6
  213. package/bizar-dash/dist/assets/DialogsView-DzTnyX8K.js.map +0 -1
  214. package/bizar-dash/dist/assets/EvalView-lgUhlGJh.js +0 -1
  215. package/bizar-dash/dist/assets/LibrariesView-Ca_PRjUm.js +0 -6
  216. package/bizar-dash/dist/assets/MiscView-fBT1TOXC.js +0 -1
  217. package/bizar-dash/dist/assets/MiscView-fBT1TOXC.js.map +0 -1
  218. package/bizar-dash/dist/assets/NotificationsView-BKAZLxM2.js +0 -6
  219. package/bizar-dash/dist/assets/NotificationsView-BKAZLxM2.js.map +0 -1
  220. package/bizar-dash/dist/assets/ObsidianView-C-AvHfpT.js +0 -1
  221. package/bizar-dash/dist/assets/OverviewView-CKYzZJe3.js +0 -1
  222. package/bizar-dash/dist/assets/OverviewView-CKYzZJe3.js.map +0 -1
  223. package/bizar-dash/dist/assets/UpdateView-B9Lh4svM.js +0 -6
  224. package/bizar-dash/dist/assets/UpdateView-B9Lh4svM.js.map +0 -1
  225. package/bizar-dash/dist/assets/VoiceView-BwR9gm5e.js +0 -1
  226. package/bizar-dash/dist/assets/VoiceView-BwR9gm5e.js.map +0 -1
  227. package/bizar-dash/dist/assets/main-BZMmD6a8.js +0 -249
  228. package/bizar-dash/dist/assets/main-BZMmD6a8.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { useState, type ReactNode } from 'react';
2
- import { ChevronDown } from 'lucide-react';
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';
5
5
  import { Cluster } from '../ui/primitives/Cluster.js';
@@ -225,10 +225,66 @@ function DefaultActions({ status }: { status?: ReactNode }): JSX.Element {
225
225
  </Inline>
226
226
  )}
227
227
  <Separator orientation="vertical" style={{ height: 16 }} />
228
+ <CommandPaletteTrigger />
229
+ <Separator orientation="vertical" style={{ height: 16 }} />
228
230
  <NotificationsPopover />
229
231
  <Separator orientation="vertical" style={{ height: 16 }} />
230
232
  <DensityToggle />
231
233
  <ThemeToggle />
232
234
  </>
233
235
  );
236
+ }
237
+
238
+ /**
239
+ * CommandPaletteTrigger — a single inline button that opens the command
240
+ * palette. Renders a kbd hint next to the magnifier icon so users discover
241
+ * the ⌘K shortcut. The full palette is wired through `useCommandPaletteHotkey`
242
+ * in App.tsx; this trigger just dispatches the same shortcut keydown.
243
+ */
244
+ function CommandPaletteTrigger(): JSX.Element {
245
+ const isMac = typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
246
+ const kbd = isMac ? '⌘K' : 'Ctrl K';
247
+ return (
248
+ <button
249
+ type="button"
250
+ onClick={() => {
251
+ // Dispatch the same key the global hotkey listens for so we don't
252
+ // duplicate the open/close logic.
253
+ window.dispatchEvent(new KeyboardEvent('keydown', { key: 'k', metaKey: isMac, ctrlKey: !isMac, bubbles: true }));
254
+ }}
255
+ aria-label="Open command palette"
256
+ title={`Open command palette (${kbd})`}
257
+ data-testid="topbar-palette-trigger"
258
+ style={{
259
+ display: 'inline-flex',
260
+ alignItems: 'center',
261
+ gap: 'var(--space-2)',
262
+ height: 28,
263
+ padding: '0 8px 0 6px',
264
+ borderRadius: 'var(--radius-sm)',
265
+ border: '1px solid var(--border)',
266
+ background: 'var(--surface-1)',
267
+ color: 'var(--fg-muted)',
268
+ fontSize: 'var(--fs-12)',
269
+ cursor: 'pointer',
270
+ }}
271
+ >
272
+ <Search size={14} aria-hidden="true" />
273
+ <span>Search…</span>
274
+ <kbd
275
+ aria-hidden="true"
276
+ style={{
277
+ padding: '1px 4px',
278
+ borderRadius: 'var(--radius-sm)',
279
+ border: '1px solid var(--border)',
280
+ background: 'var(--surface-2)',
281
+ fontSize: 'var(--fs-11)',
282
+ fontFamily: 'var(--font-mono)',
283
+ color: 'var(--fg)',
284
+ }}
285
+ >
286
+ {kbd}
287
+ </kbd>
288
+ </button>
289
+ );
234
290
  }
@@ -249,3 +249,17 @@ h4 {
249
249
  .v8-dropdown-menu__radio[data-state='checked'] .v8-dropdown-menu__radio-indicator {
250
250
  display: inline-flex;
251
251
  }
252
+
253
+ /* Universal focus-visible ring for keyboard navigation across custom
254
+ buttons, sidebar items, and design-system Button variants. v10.4.3
255
+ L4 audit fix. */
256
+ .v8-btn:focus-visible,
257
+ .v8-button:focus-visible,
258
+ button[data-section-toggle]:focus-visible,
259
+ button[data-nav-item]:focus-visible,
260
+ .v8-theme-toggle:focus-visible,
261
+ .v8-density-toggle:focus-visible,
262
+ .v8-icon-button:focus-visible {
263
+ outline: 2px solid var(--accent);
264
+ outline-offset: 2px;
265
+ }
@@ -1,4 +1,4 @@
1
- import { Monitor, Moon, Sun } from 'lucide-react';
1
+ import { AlignJustify, Monitor, Moon, Rows3, Sun } from 'lucide-react';
2
2
  import { useTheme } from './useTheme.js';
3
3
  import { useDensity } from './useDensity.js';
4
4
  import { cx } from '../utils/cx.js';
@@ -36,7 +36,10 @@ export interface ThemeToggleProps {
36
36
 
37
37
  export function ThemeToggle({ className }: ThemeToggleProps): JSX.Element {
38
38
  const { mode, resolved, cycle } = useTheme();
39
- const Icon = RESOLVED_ICON[mode];
39
+ // Show the icon for the *resolved* (actually applied) theme so the visual
40
+ // matches what the user is seeing — Sun when light is active, Moon when
41
+ // dark is active, Monitor when system is in effect.
42
+ const Icon = RESOLVED_ICON[resolved];
40
43
  return (
41
44
  <button
42
45
  type="button"
@@ -66,23 +69,25 @@ export interface DensityToggleProps {
66
69
  }
67
70
  export function DensityToggle({ className }: DensityToggleProps): JSX.Element {
68
71
  const { density, toggle } = useDensity();
72
+ const isCozy = density === 'comfortable';
73
+ const Icon = isCozy ? Rows3 : AlignJustify;
69
74
  return (
70
75
  <button
71
76
  type="button"
72
77
  onClick={toggle}
73
- aria-label={`Switch to ${density === 'comfortable' ? 'compact' : 'comfortable'} density`}
78
+ aria-label={`Switch to ${isCozy ? 'compact' : 'comfortable'} density`}
74
79
  title={`Density: ${density}`}
80
+ data-density={density}
75
81
  className={cx(
76
82
  'v8-density-toggle',
77
- 'inline-flex h-8 px-2 items-center justify-center',
83
+ 'inline-flex h-8 w-8 items-center justify-center',
78
84
  'rounded-[var(--radius-sm)]',
79
- 'text-[var(--fs-12)] font-medium tracking-[var(--tracking-wide)] uppercase',
80
85
  'text-[var(--fg-muted)] hover:text-[var(--fg)] hover:bg-[var(--surface-1)]',
81
86
  'transition-colors duration-[var(--motion-fast)]',
82
87
  className,
83
88
  )}
84
89
  >
85
- {density === 'comfortable' ? 'Cozy' : 'Compact'}
90
+ <Icon size={16} aria-hidden="true" />
86
91
  </button>
87
92
  );
88
93
  }
@@ -37,6 +37,7 @@ interface AdminAction {
37
37
  endpoint: string;
38
38
  method: 'POST' | 'GET';
39
39
  destructive: boolean;
40
+ group: 'data' | 'service' | 'logs';
40
41
  }
41
42
 
42
43
  const ACTIONS: AdminAction[] = [
@@ -48,6 +49,7 @@ const ACTIONS: AdminAction[] = [
48
49
  endpoint: '/api/admin/gc',
49
50
  method: 'POST',
50
51
  destructive: false,
52
+ group: 'data',
51
53
  },
52
54
  {
53
55
  id: 'cache-clear',
@@ -57,6 +59,7 @@ const ACTIONS: AdminAction[] = [
57
59
  endpoint: '/api/admin/cache/clear',
58
60
  method: 'POST',
59
61
  destructive: true,
62
+ group: 'data',
60
63
  },
61
64
  {
62
65
  id: 'memory-reindex',
@@ -66,6 +69,7 @@ const ACTIONS: AdminAction[] = [
66
69
  endpoint: '/api/admin/memory/reindex',
67
70
  method: 'POST',
68
71
  destructive: false,
72
+ group: 'data',
69
73
  },
70
74
  {
71
75
  id: 'logs-purge',
@@ -75,6 +79,7 @@ const ACTIONS: AdminAction[] = [
75
79
  endpoint: '/api/admin/logs/purge',
76
80
  method: 'POST',
77
81
  destructive: true,
82
+ group: 'logs',
78
83
  },
79
84
  {
80
85
  id: 'restart',
@@ -84,6 +89,7 @@ const ACTIONS: AdminAction[] = [
84
89
  endpoint: '/api/admin/restart',
85
90
  method: 'POST',
86
91
  destructive: true,
92
+ group: 'service',
87
93
  },
88
94
  {
89
95
  id: 'rebuild',
@@ -93,6 +99,7 @@ const ACTIONS: AdminAction[] = [
93
99
  endpoint: '/api/admin/rebuild',
94
100
  method: 'POST',
95
101
  destructive: true,
102
+ group: 'service',
96
103
  },
97
104
  {
98
105
  id: 'export-activity',
@@ -102,9 +109,16 @@ const ACTIONS: AdminAction[] = [
102
109
  endpoint: '/api/admin/activity/export',
103
110
  method: 'GET',
104
111
  destructive: false,
112
+ group: 'logs',
105
113
  },
106
114
  ];
107
115
 
116
+ const GROUPS: Array<{ id: 'data' | 'service' | 'logs'; title: string }> = [
117
+ { id: 'data', title: 'Data & cache' },
118
+ { id: 'service', title: 'Service' },
119
+ { id: 'logs', title: 'Logs' },
120
+ ];
121
+
108
122
  export function AdminView(): JSX.Element {
109
123
  return (
110
124
  <Stack gap={4} data-testid="admin-view">
@@ -112,11 +126,24 @@ export function AdminView(): JSX.Element {
112
126
  title="Admin"
113
127
  description="Maintenance actions. Destructive actions require an inline confirm."
114
128
  />
115
- <Grid cols={{ base: 1, md: 2, lg: 3 }} gap={3}>
116
- {ACTIONS.map((a) => (
117
- <AdminTile key={a.id} action={a} />
118
- ))}
119
- </Grid>
129
+ <Stack gap={4} data-testid="admin-groups">
130
+ {GROUPS.map((group) => {
131
+ const items = ACTIONS.filter((a) => a.group === group.id);
132
+ if (items.length === 0) return null;
133
+ return (
134
+ <Stack key={group.id} gap={2} data-testid={`admin-group-${group.id}`}>
135
+ <h3 style={{ margin: 0, fontSize: 'var(--fs-12)', textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)', color: 'var(--fg-muted)' }}>
136
+ {group.title}
137
+ </h3>
138
+ <Grid cols={{ base: 1, md: 2, lg: 3 }} gap={3}>
139
+ {items.map((a) => (
140
+ <AdminTile key={a.id} action={a} />
141
+ ))}
142
+ </Grid>
143
+ </Stack>
144
+ );
145
+ })}
146
+ </Stack>
120
147
  </Stack>
121
148
  );
122
149
  }
@@ -108,24 +108,31 @@ function mapBizar(a: BizarAgent): AgentCardProps {
108
108
  };
109
109
  }
110
110
 
111
+ function truncatePreview(s: string, max = 80): string {
112
+ if (!s) return '';
113
+ return s.length <= max ? s : s.slice(0, max - 1) + '…';
114
+ }
115
+
111
116
  function mapCC(a: CCAgent): AgentCardProps {
112
117
  const raw = (a.status || a.state || 'idle').toLowerCase();
113
118
  const status: AgentStatus =
114
119
  raw === 'busy' || raw === 'working' ? 'busy' :
115
120
  raw === 'error' ? 'error' :
116
121
  raw === 'paused' ? 'paused' : 'idle';
122
+ const kind = a.kind === 'background' ? 'background' : 'interactive';
117
123
  return {
118
124
  id: `cc:${a.sessionId || a.id}`,
119
125
  name: a.name || a.sessionId?.slice(0, 8) || 'unknown',
120
- role: a.kind === 'background' ? 'CC background' : 'CC interactive',
126
+ role: kind === 'background' ? 'CC background' : 'CC interactive',
121
127
  status,
122
- currentTask: sanitizeSummary(a.lastMessageSnippet),
128
+ currentTask: truncatePreview(sanitizeSummary(a.lastMessageSnippet)),
123
129
  lastActivity: a.lastMessageAt ? new Date(a.lastMessageAt).toLocaleTimeString() : (a.startedAt ? new Date(a.startedAt).toLocaleTimeString() : undefined),
124
130
  tasksToday: a.messageCount || 0,
125
131
  // CC agents don't expose successRate/tasksSucceeded — only messageCount.
126
132
  // Skip the metric strip for them; the badge + lastActivity carry the
127
- // signal.
128
- badges: [a.cwd?.split('/').slice(-2).join('/') || ''].filter(Boolean),
133
+ // signal. Include a kind chip so users can distinguish CC background
134
+ // sessions from interactive ones.
135
+ badges: [kind, a.cwd?.split('/').slice(-2).join('/') || ''].filter(Boolean),
129
136
  };
130
137
  }
131
138
 
@@ -94,7 +94,7 @@ export function ArtifactsView(): JSX.Element {
94
94
  <Skeleton style={{ height: 48 }} />
95
95
  </Stack>
96
96
  ) : artifacts.length === 0 ? (
97
- <EmptyState icon={<Layers size={28} aria-hidden />} title="No artifacts" description="Create one to get started." />
97
+ <EmptyState icon={<Layers size={28} aria-hidden />} title="No artifacts" description="Artifacts are generated code — HTML canvases, SVG diagrams, and React components — created by the agent during task work. Add one to get started." />
98
98
  ) : (
99
99
  <Stack gap={1}>
100
100
  {artifacts.map((a) => {
@@ -81,7 +81,7 @@ export function ClipboardView(): JSX.Element {
81
81
  <Skeleton style={{ height: 60 }} />
82
82
  </Stack>
83
83
  ) : clips.length === 0 ? (
84
- <EmptyState icon={<ClipboardPaste size={28} aria-hidden />} title="No saved clips" description="Save one with a URL + content." />
84
+ <EmptyState icon={<ClipboardPaste size={28} aria-hidden />} title="No saved clips" description="Clips are saved URL + text pairs captured from the clipboard during agent work. Each becomes a markdown note under clips/. Save one to get started." />
85
85
  ) : (
86
86
  <Stack gap={1}>
87
87
  {clips.map((c) => {
@@ -103,7 +103,7 @@ export function DialogsView(): JSX.Element {
103
103
  <EmptyState
104
104
  icon={<MessageSquare size={28} aria-hidden />}
105
105
  title="No active dialogs"
106
- description="Dialogs enqueued via the slash-command plugin will appear here."
106
+ description="Dialogs are confirmations and prompts the agent raises when it needs user input before proceeding. They appear here when the slash-command plugin queues one."
107
107
  />
108
108
  ) : (
109
109
  <Stack gap={1}>
@@ -45,7 +45,7 @@ interface TailscaleStatus {
45
45
  export function MiscView(): JSX.Element {
46
46
  return (
47
47
  <Stack gap={4} data-testid="misc-view">
48
- <ViewHeader title="Misc" description="Global search + Tailscale." />
48
+ <ViewHeader title="Misc" description="Global fuzzy search across projects, tasks, and agents — plus Tailscale VPN control." />
49
49
  <SearchPanel />
50
50
  <TailscalePanel />
51
51
  </Stack>
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useEffect, useState } from 'react';
2
- import { Bell, CheckCheck, Trash2, RefreshCw } from 'lucide-react';
2
+ import { Bell, CheckCheck, Trash2, RefreshCw, RotateCcw } from 'lucide-react';
3
3
  import { Stack } from '../../ui/primitives/Stack.js';
4
4
  import { Inline } from '../../ui/primitives/Inline.js';
5
5
  import { ViewHeader } from '../../ui/data/ViewHeader.js';
@@ -16,7 +16,8 @@ import type { WsMessage } from '../../data/types.js';
16
16
  /**
17
17
  * NotificationsView — Sprint S35. Per-user notification stream backed
18
18
  * by `/api/notifications`. Pulls unread + read mixed, supports
19
- * mark-one-read, mark-all-read, and dismiss.
19
+ * mark-one-read, mark-all-read, dismiss with 5s undo, and
20
+ * "Show dismissed" with restore.
20
21
  */
21
22
 
22
23
  type Tone = 'neutral' | 'info' | 'success' | 'warning' | 'danger';
@@ -36,6 +37,12 @@ interface NotificationsResponse {
36
37
  stats?: { total: number; unread: number };
37
38
  }
38
39
 
40
+ interface UndoToast {
41
+ id: string;
42
+ item: NotificationItem;
43
+ timer: ReturnType<typeof setTimeout>;
44
+ }
45
+
39
46
  function toneFor(item: NotificationItem): Tone {
40
47
  if (item.tone && ['info', 'success', 'warning', 'danger', 'neutral'].includes(item.tone)) {
41
48
  return item.tone as Tone;
@@ -57,6 +64,9 @@ function fmtRel(iso: string | undefined): string {
57
64
  export function NotificationsView(): JSX.Element {
58
65
  const res = useFetch<NotificationsResponse>('/api/notifications?limit=200');
59
66
  const [items, setItems] = useState<NotificationItem[]>([]);
67
+ const [dismissed, setDismissed] = useState<NotificationItem[]>([]);
68
+ const [showDismissed, setShowDismissed] = useState(false);
69
+ const [undoToast, setUndoToast] = useState<UndoToast | null>(null);
60
70
  const [busy, setBusy] = useState<string | null>(null);
61
71
  const [error, setError] = useState<string | null>(null);
62
72
 
@@ -95,12 +105,29 @@ export function NotificationsView(): JSX.Element {
95
105
  }
96
106
  };
97
107
 
108
+ const restore = (item: NotificationItem): void => {
109
+ clearTimeout(undoToast?.timer);
110
+ setUndoToast(null);
111
+ setDismissed((prev) => prev.filter((n) => n.id !== item.id));
112
+ setItems((prev) => [item, ...prev]);
113
+ };
114
+
98
115
  const dismiss = async (id: string): Promise<void> => {
116
+ const item = items.find((n) => n.id === id);
117
+ if (!item) return;
99
118
  setBusy(id);
100
119
  setError(null);
101
120
  try {
102
121
  await fetchJson(`/api/notifications/${encodeURIComponent(id)}`, { method: 'DELETE' });
103
122
  setItems((prev) => prev.filter((n) => n.id !== id));
123
+ setDismissed((prev) => [item, ...prev]);
124
+
125
+ // 5-second undo window
126
+ const timer = setTimeout(() => {
127
+ setDismissed((prev) => prev.filter((n) => n.id !== id));
128
+ setUndoToast((cur) => (cur?.id === id ? null : cur));
129
+ }, 5_000);
130
+ setUndoToast({ id, item, timer });
104
131
  } catch (err) {
105
132
  setError(err instanceof FetchError ? err.message : (err as Error).message);
106
133
  } finally {
@@ -109,12 +136,13 @@ export function NotificationsView(): JSX.Element {
109
136
  };
110
137
 
111
138
  const unread = items.filter((n) => !n.read).length;
139
+ const visibleItems = showDismissed ? [...items, ...dismissed] : items;
112
140
 
113
141
  return (
114
142
  <Stack gap={5} data-testid="notifications-view">
115
143
  <ViewHeader
116
144
  title="Notifications"
117
- description={`${unread} unread of ${items.length} total. Backed by ~/.config/bizar/notifications.jsonl.`}
145
+ description={`${unread} unread of ${items.length} total${dismissed.length > 0 ? `, ${dismissed.length} dismissed` : ''}. Backed by ~/.config/bizar/notifications.jsonl.`}
118
146
  actions={
119
147
  <Inline gap={2} align="center">
120
148
  {error !== null && <span role="alert" style={{ color: 'var(--danger)', fontSize: 'var(--fs-12)' }}>{error}</span>}
@@ -124,6 +152,11 @@ export function NotificationsView(): JSX.Element {
124
152
  <Button variant="secondary" onClick={() => void markAll()} disabled={busy === 'all' || unread === 0} data-testid="notifications-mark-all">
125
153
  <CheckCheck size={14} aria-hidden /> Mark all read
126
154
  </Button>
155
+ {dismissed.length > 0 && (
156
+ <Button variant="ghost" onClick={() => setShowDismissed((v) => !v)} data-testid="notifications-toggle-dismissed">
157
+ {showDismissed ? 'Hide dismissed' : `Show dismissed (${dismissed.length})`}
158
+ </Button>
159
+ )}
127
160
  </Inline>
128
161
  }
129
162
  />
@@ -133,7 +166,7 @@ export function NotificationsView(): JSX.Element {
133
166
  <Skeleton style={{ height: 64 }} />
134
167
  <Skeleton style={{ height: 64 }} />
135
168
  </Stack>
136
- ) : items.length === 0 ? (
169
+ ) : items.length === 0 && dismissed.length === 0 ? (
137
170
  <EmptyState
138
171
  icon={<Bell size={32} aria-hidden />}
139
172
  title="No notifications"
@@ -141,15 +174,17 @@ export function NotificationsView(): JSX.Element {
141
174
  />
142
175
  ) : (
143
176
  <Stack gap={2}>
144
- {items.map((n) => {
177
+ {visibleItems.map((n) => {
178
+ const isDismissed = dismissed.some((d) => d.id === n.id);
145
179
  const tone = toneFor(n);
146
180
  return (
147
- <Card key={n.id} variant="default" style={{ opacity: n.read ? 0.7 : 1 }}>
181
+ <Card key={n.id} variant="default" style={{ opacity: isDismissed || n.read ? 0.7 : 1 }}>
148
182
  <CardBody>
149
183
  <Inline align="start" justify="between" gap={3}>
150
184
  <Stack gap={1} style={{ flex: 1, minWidth: 0 }}>
151
185
  <Inline align="center" gap={2}>
152
- {!n.read && <span aria-label="Unread" style={{ width: 8, height: 8, borderRadius: 'var(--radius-pill)', background: 'var(--accent)' }} />}
186
+ {isDismissed && <span data-testid={`notification-dismissed-badge-${n.id}`} style={{ fontSize: 'var(--fs-11)', color: 'var(--fg-subtle)' }}>dismissed</span>}
187
+ {!n.read && !isDismissed && <span aria-label="Unread" style={{ width: 8, height: 8, borderRadius: 'var(--radius-pill)', background: 'var(--accent)' }} />}
153
188
  <strong style={{ fontSize: 'var(--fs-13)' }}>{n.title || n.source || n.id}</strong>
154
189
  {n.source && <Badge tone={tone}>{n.source}</Badge>}
155
190
  <span style={{ color: 'var(--fg-muted)', fontSize: 'var(--fs-12)' }}>{fmtRel(n.createdAt)}</span>
@@ -157,14 +192,22 @@ export function NotificationsView(): JSX.Element {
157
192
  {n.body && <span style={{ fontSize: 'var(--fs-13)', color: 'var(--fg-muted)' }}>{n.body}</span>}
158
193
  </Stack>
159
194
  <Inline gap={1}>
160
- {!n.read && (
161
- <Button variant="ghost" onClick={() => void markRead(n.id)} disabled={busy === n.id} data-testid={`notification-read-${n.id}`}>
162
- Mark read
195
+ {isDismissed ? (
196
+ <Button variant="ghost" onClick={() => restore(n)} data-testid={`notification-restore-${n.id}`}>
197
+ <RotateCcw size={12} aria-hidden /> Restore
163
198
  </Button>
199
+ ) : (
200
+ <>
201
+ {!n.read && (
202
+ <Button variant="ghost" onClick={() => void markRead(n.id)} disabled={busy === n.id} data-testid={`notification-read-${n.id}`}>
203
+ Mark read
204
+ </Button>
205
+ )}
206
+ <Button variant="ghost" onClick={() => void dismiss(n.id)} disabled={busy === n.id} data-testid={`notification-dismiss-${n.id}`}>
207
+ <Trash2 size={12} aria-hidden /> Dismiss
208
+ </Button>
209
+ </>
164
210
  )}
165
- <Button variant="ghost" onClick={() => void dismiss(n.id)} disabled={busy === n.id} data-testid={`notification-dismiss-${n.id}`}>
166
- <Trash2 size={12} aria-hidden /> Dismiss
167
- </Button>
168
211
  </Inline>
169
212
  </Inline>
170
213
  </CardBody>
@@ -173,6 +216,32 @@ export function NotificationsView(): JSX.Element {
173
216
  })}
174
217
  </Stack>
175
218
  )}
219
+ {undoToast !== null && (
220
+ <Card variant="default" data-testid="notifications-undo-toast" style={{ border: '1px solid var(--accent)', background: 'var(--surface-1)' }}>
221
+ <CardBody>
222
+ <Inline align="center" justify="between" gap={3}>
223
+ <span style={{ fontSize: 'var(--fs-13)' }}>
224
+ Notification dismissed.{' '}
225
+ <button
226
+ type="button"
227
+ onClick={() => {
228
+ clearTimeout(undoToast.timer);
229
+ setUndoToast(null);
230
+ restore(undoToast.item);
231
+ }}
232
+ style={{ background: 'none', border: 'none', color: 'var(--accent)', cursor: 'pointer', fontSize: 'inherit', padding: 0, textDecoration: 'underline' }}
233
+ data-testid="notifications-undo-btn"
234
+ >
235
+ Undo
236
+ </button>
237
+ </span>
238
+ <Button variant="ghost" size="sm" onClick={() => { clearTimeout(undoToast.timer); setUndoToast(null); }} data-testid="notifications-undo-close">
239
+
240
+ </Button>
241
+ </Inline>
242
+ </CardBody>
243
+ </Card>
244
+ )}
176
245
  </Stack>
177
246
  );
178
247
  }
@@ -13,6 +13,8 @@ import { Stack } from '../../ui/primitives/Stack.js';
13
13
  import { StatTile, StatGrid } from '../../ui/data/StatTile.js';
14
14
  import { Card, CardBody, CardHeader } from '../../ui/data/Card.js';
15
15
  import { Badge } from '../../ui/data/Badge.js';
16
+ import { Banner } from '../../ui/feedback/Banner.js';
17
+ import { Button } from '../../ui/controls/Button.js';
16
18
  import { ActivityFeed, type ActivityItem } from '../../ui/activity/ActivityFeed.js';
17
19
  import { ViewHeader } from '../../ui/data/ViewHeader.js';
18
20
  import { Sparkline } from '../../ui/data/Sparkline.js';
@@ -103,6 +105,19 @@ export function OverviewView(): JSX.Element {
103
105
 
104
106
  return (
105
107
  <Stack gap={5} data-testid="overview-view">
108
+ {snapshot.error && (
109
+ <Banner
110
+ tone="warning"
111
+ data-testid="overview-error-banner"
112
+ action={
113
+ <Button variant="ghost" size="sm" onClick={() => void snapshot.refetch()}>
114
+ Retry
115
+ </Button>
116
+ }
117
+ >
118
+ Snapshot unavailable: {snapshot.error.message || String(snapshot.error)}
119
+ </Banner>
120
+ )}
106
121
  <ViewHeader
107
122
  title="Overview"
108
123
  description="What's moving in the harness right now."
@@ -119,24 +134,28 @@ export function OverviewView(): JSX.Element {
119
134
  value={String((tasks.queued || 0) + (tasks.active || 0))}
120
135
  trend={(tasks.active || 0) > (tasks.queued || 0) ? 'up' : 'flat'}
121
136
  delta={`${tasks.done || 0} done · ${tasks.blocked || 0} blocked`}
137
+ loading={snapshot.loading}
122
138
  />
123
139
  <StatTile
124
140
  label="Goals at risk"
125
141
  value={`${goals.atRisk || 0} / ${goals.total || 0}`}
126
142
  trend={(goals.atRisk || 0) > 0 ? 'down' : 'flat'}
127
143
  delta={`${goals.done || 0} done`}
144
+ loading={snapshot.loading}
128
145
  />
129
146
  <StatTile
130
147
  label="Agents running"
131
148
  value={`${agents.running || 0} / ${agents.total || 0}`}
132
149
  trend={(agents.running || 0) > 0 ? 'up' : 'flat'}
133
150
  delta={`${agents.idle || 0} idle · ${agents.error || 0} error`}
151
+ loading={snapshot.loading}
134
152
  />
135
153
  <StatTile
136
154
  label="Tokens (24h)"
137
155
  value={tokens.last24h ? `${(tokens.last24h / 1_000_000).toFixed(1)}M` : '—'}
138
156
  trend={tokens.trend || 'flat'}
139
157
  delta="last 24h"
158
+ loading={snapshot.loading}
140
159
  sparkline={
141
160
  tokenSeries.length >= 2 ? (
142
161
  <span data-testid="overview-tokens-sparkline">
@@ -79,7 +79,7 @@ export function VoiceView(): JSX.Element {
79
79
  <Skeleton style={{ height: 48 }} />
80
80
  </Stack>
81
81
  ) : memos.length === 0 ? (
82
- <EmptyState icon={<Mic size={28} aria-hidden />} title="No voice memos" description="Upload a recording to get started." />
82
+ <EmptyState icon={<Mic size={28} aria-hidden />} title="No voice memos" description="Voice memos are audio recordings captured during sessions — useful for verbal scratch notes and hands-free capture. Upload a .wav, .mp3, or .m4a file to get started." />
83
83
  ) : (
84
84
  <Stack gap={1}>
85
85
  {memos.map((m) => {
@@ -0,0 +1,42 @@
1
+ // Tests L6 — admin Run actions are grouped into 2-3 semantic sections.
2
+
3
+ import test from 'node:test';
4
+ import assert from 'node:assert/strict';
5
+
6
+ // Mirror the groupings from AdminView.tsx
7
+ const ACTIONS = [
8
+ { id: 'gc', group: 'data' },
9
+ { id: 'cache-clear', group: 'data' },
10
+ { id: 'memory-reindex', group: 'data' },
11
+ { id: 'logs-purge', group: 'logs' },
12
+ { id: 'restart', group: 'service' },
13
+ { id: 'rebuild', group: 'service' },
14
+ { id: 'export-activity', group: 'logs' },
15
+ ];
16
+
17
+ const GROUPS = ['data', 'service', 'logs'];
18
+
19
+ test('three grouping buckets', () => {
20
+ assert.equal(GROUPS.length, 3);
21
+ });
22
+
23
+ test('data group contains 3 actions', () => {
24
+ const c = ACTIONS.filter((a) => a.group === 'data');
25
+ assert.equal(c.length, 3);
26
+ });
27
+
28
+ test('service group contains restart + rebuild', () => {
29
+ const c = ACTIONS.filter((a) => a.group === 'service');
30
+ assert.equal(c.length, 2);
31
+ assert.ok(c.every((a) => ['restart', 'rebuild'].includes(a.id)));
32
+ });
33
+
34
+ test('logs group contains logs-purge + export-activity', () => {
35
+ const c = ACTIONS.filter((a) => a.group === 'logs');
36
+ assert.equal(c.length, 2);
37
+ });
38
+
39
+ test('every action belongs to exactly one group', () => {
40
+ const total = GROUPS.reduce((acc, g) => acc + ACTIONS.filter((a) => a.group === g).length, 0);
41
+ assert.equal(total, ACTIONS.length);
42
+ });
@@ -0,0 +1,35 @@
1
+ // Tests M1 — agent card preview truncation + kind chip.
2
+
3
+ import test from 'node:test';
4
+ import assert from 'node:assert/strict';
5
+
6
+ // Lightweight copy of the truncation helper from AgentsView.tsx to avoid
7
+ // pulling in the React component (which would require jsdom). The real
8
+ // version is exported from AgentsView.tsx as `truncatePreview`.
9
+ function truncatePreview(s, max = 80) {
10
+ if (!s) return '';
11
+ return s.length <= max ? s : s.slice(0, max - 1) + '…';
12
+ }
13
+
14
+ test('truncatePreview passes short strings through unchanged', () => {
15
+ assert.equal(truncatePreview('hello world'), 'hello world');
16
+ });
17
+
18
+ test('truncatePreview caps long strings at max-1 + ellipsis', () => {
19
+ const long = 'a'.repeat(120);
20
+ const out = truncatePreview(long);
21
+ assert.equal(out.length, 80);
22
+ assert.match(out, /…$/);
23
+ });
24
+
25
+ test('truncatePreview returns empty for empty/missing input', () => {
26
+ assert.equal(truncatePreview(''), '');
27
+ assert.equal(truncatePreview(undefined), '');
28
+ assert.equal(truncatePreview(null), '');
29
+ });
30
+
31
+ test('truncatePreview boundary at exactly max', () => {
32
+ const exact = 'b'.repeat(80);
33
+ assert.equal(truncatePreview(exact), exact);
34
+ assert.equal(truncatePreview(exact + 'x'), 'b'.repeat(79) + '…');
35
+ });