@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.
- package/bizar-dash/dist/assets/{ActivityFeed-DC0JiK49.js → ActivityFeed-RQeyFifx.js} +1 -1
- package/bizar-dash/dist/assets/{ActivityFeed-DC0JiK49.js.map → ActivityFeed-RQeyFifx.js.map} +1 -1
- package/bizar-dash/dist/assets/{ActivityView-ubEDAWMY.js → ActivityView-CibyssPM.js} +1 -1
- package/bizar-dash/dist/assets/{ActivityView-ubEDAWMY.js.map → ActivityView-CibyssPM.js.map} +1 -1
- package/bizar-dash/dist/assets/AdminView-BjczABeB.js +11 -0
- package/bizar-dash/dist/assets/AdminView-BjczABeB.js.map +1 -0
- package/bizar-dash/dist/assets/{AgentActivity-BLcqqkt2.js → AgentActivity-Bywt1twv.js} +1 -1
- package/bizar-dash/dist/assets/{AgentActivity-BLcqqkt2.js.map → AgentActivity-Bywt1twv.js.map} +1 -1
- package/bizar-dash/dist/assets/{AgentsView-CIQkZH8_.js → AgentsView-BnOjjM74.js} +4 -4
- package/bizar-dash/dist/assets/AgentsView-BnOjjM74.js.map +1 -0
- package/bizar-dash/dist/assets/{Alert-MwVEymjy.js → Alert-BZGk39mB.js} +1 -1
- package/bizar-dash/dist/assets/{Alert-MwVEymjy.js.map → Alert-BZGk39mB.js.map} +1 -1
- package/bizar-dash/dist/assets/{ArtifactsView-B-YUwZyb.js → ArtifactsView-CfYB1le1.js} +3 -3
- package/bizar-dash/dist/assets/ArtifactsView-CfYB1le1.js.map +1 -0
- package/bizar-dash/dist/assets/{AuthView-LaQuGo4u.js → AuthView-D4frSShw.js} +1 -1
- package/bizar-dash/dist/assets/{AuthView-LaQuGo4u.js.map → AuthView-D4frSShw.js.map} +1 -1
- package/bizar-dash/dist/assets/Avatar-BYg0rkPA.js +6 -0
- package/bizar-dash/dist/assets/Avatar-BYg0rkPA.js.map +1 -0
- package/bizar-dash/dist/assets/{BackgroundJobsView-CO3DEG8R.js → BackgroundJobsView-DEStvILz.js} +1 -1
- package/bizar-dash/dist/assets/{BackgroundJobsView-CO3DEG8R.js.map → BackgroundJobsView-DEStvILz.js.map} +1 -1
- package/bizar-dash/dist/assets/{BackupView-Cjf23ZWc.js → BackupView-B875KQa4.js} +1 -1
- package/bizar-dash/dist/assets/{BackupView-Cjf23ZWc.js.map → BackupView-B875KQa4.js.map} +1 -1
- package/bizar-dash/dist/assets/Banner-C8HEcT5l.js +1 -0
- package/bizar-dash/dist/assets/Banner-C8HEcT5l.js.map +1 -0
- package/bizar-dash/dist/assets/{Card-BmXiT4c6.js → Card-BtsmkhJa.js} +1 -1
- package/bizar-dash/dist/assets/{Card-BmXiT4c6.js.map → Card-BtsmkhJa.js.map} +1 -1
- package/bizar-dash/dist/assets/{ChatView-DXKFwvgp.js → ChatView-CkNS0SD8.js} +3 -3
- package/bizar-dash/dist/assets/{ChatView-DXKFwvgp.js.map → ChatView-CkNS0SD8.js.map} +1 -1
- package/bizar-dash/dist/assets/{Chip-Dei66BLx.js → Chip-XPOk0d_E.js} +1 -1
- package/bizar-dash/dist/assets/{Chip-Dei66BLx.js.map → Chip-XPOk0d_E.js.map} +1 -1
- package/bizar-dash/dist/assets/{ClaudeSessionsView-D-YNmzKi.js → ClaudeSessionsView-BprGo67K.js} +1 -1
- package/bizar-dash/dist/assets/{ClaudeSessionsView-D-YNmzKi.js.map → ClaudeSessionsView-BprGo67K.js.map} +1 -1
- package/bizar-dash/dist/assets/ClipboardView-CPlTU5VG.js +1 -0
- package/bizar-dash/dist/assets/ClipboardView-CPlTU5VG.js.map +1 -0
- package/bizar-dash/dist/assets/{ConfigView-BMBgifw5.js → ConfigView-BntUrW7s.js} +1 -1
- package/bizar-dash/dist/assets/{ConfigView-BMBgifw5.js.map → ConfigView-BntUrW7s.js.map} +1 -1
- package/bizar-dash/dist/assets/{DiagnosticsView-C0sdqhEl.js → DiagnosticsView-4x0Zu3yP.js} +1 -1
- package/bizar-dash/dist/assets/{DiagnosticsView-C0sdqhEl.js.map → DiagnosticsView-4x0Zu3yP.js.map} +1 -1
- package/bizar-dash/dist/assets/DialogsView-Xd02dVAX.js +6 -0
- package/bizar-dash/dist/assets/DialogsView-Xd02dVAX.js.map +1 -0
- package/bizar-dash/dist/assets/{DoctorView-CiFwRMW0.js → DoctorView-DepBdQGR.js} +2 -2
- package/bizar-dash/dist/assets/{DoctorView-CiFwRMW0.js.map → DoctorView-DepBdQGR.js.map} +1 -1
- package/bizar-dash/dist/assets/{EmptyState-CdDQmfEL.js → EmptyState-DjKtY1Qi.js} +1 -1
- package/bizar-dash/dist/assets/{EmptyState-CdDQmfEL.js.map → EmptyState-DjKtY1Qi.js.map} +1 -1
- package/bizar-dash/dist/assets/{EnvVarsView-CNO03NDJ.js → EnvVarsView-S4Pbm128.js} +3 -3
- package/bizar-dash/dist/assets/{EnvVarsView-CNO03NDJ.js.map → EnvVarsView-S4Pbm128.js.map} +1 -1
- package/bizar-dash/dist/assets/{ErrorState-BRvj8qZZ.js → ErrorState-CBJbEQ8B.js} +1 -1
- package/bizar-dash/dist/assets/{ErrorState-BRvj8qZZ.js.map → ErrorState-CBJbEQ8B.js.map} +1 -1
- package/bizar-dash/dist/assets/EvalView-BeAyMCD1.js +1 -0
- package/bizar-dash/dist/assets/{EvalView-lgUhlGJh.js.map → EvalView-BeAyMCD1.js.map} +1 -1
- package/bizar-dash/dist/assets/{GoalsView-DmkCCs9i.js → GoalsView-BZr8In4c.js} +1 -1
- package/bizar-dash/dist/assets/{GoalsView-DmkCCs9i.js.map → GoalsView-BZr8In4c.js.map} +1 -1
- package/bizar-dash/dist/assets/{Grid-BThCPJaV.js → Grid--WKvqu5h.js} +1 -1
- package/bizar-dash/dist/assets/{Grid-BThCPJaV.js.map → Grid--WKvqu5h.js.map} +1 -1
- package/bizar-dash/dist/assets/{HistoryView-Cn2cPiHZ.js → HistoryView-djEbzqtk.js} +1 -1
- package/bizar-dash/dist/assets/{HistoryView-Cn2cPiHZ.js.map → HistoryView-djEbzqtk.js.map} +1 -1
- package/bizar-dash/dist/assets/{Input-cAQOvR8U.js → Input-h_nSOtwm.js} +1 -1
- package/bizar-dash/dist/assets/{Input-cAQOvR8U.js.map → Input-h_nSOtwm.js.map} +1 -1
- package/bizar-dash/dist/assets/LibrariesView-Vez8CvNq.js +6 -0
- package/bizar-dash/dist/assets/{LibrariesView-Ca_PRjUm.js.map → LibrariesView-Vez8CvNq.js.map} +1 -1
- package/bizar-dash/dist/assets/{LightRAGView-CMk9wOae.js → LightRAGView-DvqzQoBp.js} +1 -1
- package/bizar-dash/dist/assets/{LightRAGView-CMk9wOae.js.map → LightRAGView-DvqzQoBp.js.map} +1 -1
- package/bizar-dash/dist/assets/{ListHeader-BLAeshe0.js → ListHeader-CM8QoIWG.js} +1 -1
- package/bizar-dash/dist/assets/{ListHeader-BLAeshe0.js.map → ListHeader-CM8QoIWG.js.map} +1 -1
- package/bizar-dash/dist/assets/{MemoryView-wo2Pu7xQ.js → MemoryView-DnL8E2vI.js} +1 -1
- package/bizar-dash/dist/assets/{MemoryView-wo2Pu7xQ.js.map → MemoryView-DnL8E2vI.js.map} +1 -1
- package/bizar-dash/dist/assets/MiscView-B2YQXhZ_.js +1 -0
- package/bizar-dash/dist/assets/MiscView-B2YQXhZ_.js.map +1 -0
- package/bizar-dash/dist/assets/{ModsView-CV_RxdzJ.js → ModsView-BVM3X5xc.js} +1 -1
- package/bizar-dash/dist/assets/{ModsView-CV_RxdzJ.js.map → ModsView-BVM3X5xc.js.map} +1 -1
- package/bizar-dash/dist/assets/NotificationsView-A-Xs8BL1.js +6 -0
- package/bizar-dash/dist/assets/NotificationsView-A-Xs8BL1.js.map +1 -0
- package/bizar-dash/dist/assets/ObsidianView-mI3qW9OP.js +1 -0
- package/bizar-dash/dist/assets/{ObsidianView-C-AvHfpT.js.map → ObsidianView-mI3qW9OP.js.map} +1 -1
- package/bizar-dash/dist/assets/OverviewView-By69Cr5H.js +1 -0
- package/bizar-dash/dist/assets/OverviewView-By69Cr5H.js.map +1 -0
- package/bizar-dash/dist/assets/{ProgressBar-ya2PCj8L.js → ProgressBar-BnaBqv-W.js} +1 -1
- package/bizar-dash/dist/assets/{ProgressBar-ya2PCj8L.js.map → ProgressBar-BnaBqv-W.js.map} +1 -1
- package/bizar-dash/dist/assets/{ProjectsView-N9zqFZ_O.js → ProjectsView-C81WTFka.js} +1 -1
- package/bizar-dash/dist/assets/{ProjectsView-N9zqFZ_O.js.map → ProjectsView-C81WTFka.js.map} +1 -1
- package/bizar-dash/dist/assets/{ProvidersView-BcL8jjwK.js → ProvidersView-2cn-TALE.js} +2 -2
- package/bizar-dash/dist/assets/{ProvidersView-BcL8jjwK.js.map → ProvidersView-2cn-TALE.js.map} +1 -1
- package/bizar-dash/dist/assets/{SchedulesView-8qxH3bg7.js → SchedulesView-o9AtIsH9.js} +1 -1
- package/bizar-dash/dist/assets/{SchedulesView-8qxH3bg7.js.map → SchedulesView-o9AtIsH9.js.map} +1 -1
- package/bizar-dash/dist/assets/{Select-D5AuVxxV.js → Select-CiGWEN8I.js} +1 -1
- package/bizar-dash/dist/assets/{Select-D5AuVxxV.js.map → Select-CiGWEN8I.js.map} +1 -1
- package/bizar-dash/dist/assets/{SettingsRow-BBALf9dA.js → SettingsRow-CflGJymA.js} +1 -1
- package/bizar-dash/dist/assets/{SettingsRow-BBALf9dA.js.map → SettingsRow-CflGJymA.js.map} +1 -1
- package/bizar-dash/dist/assets/{SettingsView-UD403neY.js → SettingsView-2HSFJPPK.js} +1 -1
- package/bizar-dash/dist/assets/{SettingsView-UD403neY.js.map → SettingsView-2HSFJPPK.js.map} +1 -1
- package/bizar-dash/dist/assets/{Sheet-CxXf6gvC.js → Sheet-B-CMP9-_.js} +1 -1
- package/bizar-dash/dist/assets/{Sheet-CxXf6gvC.js.map → Sheet-B-CMP9-_.js.map} +1 -1
- package/bizar-dash/dist/assets/{Slider-hvaVD_SW.js → Slider-Z4TP_CXY.js} +1 -1
- package/bizar-dash/dist/assets/{Slider-hvaVD_SW.js.map → Slider-Z4TP_CXY.js.map} +1 -1
- package/bizar-dash/dist/assets/{Sparkline-DqmQzdJH.js → Sparkline-cvkv2HbU.js} +1 -1
- package/bizar-dash/dist/assets/{Sparkline-DqmQzdJH.js.map → Sparkline-cvkv2HbU.js.map} +1 -1
- package/bizar-dash/dist/assets/{Switch-C-_zcNdl.js → Switch-jcYo4lmp.js} +1 -1
- package/bizar-dash/dist/assets/{Switch-C-_zcNdl.js.map → Switch-jcYo4lmp.js.map} +1 -1
- package/bizar-dash/dist/assets/{TasksView-DseftDlx.js → TasksView-CQaSFzWY.js} +2 -2
- package/bizar-dash/dist/assets/{TasksView-DseftDlx.js.map → TasksView-CQaSFzWY.js.map} +1 -1
- package/bizar-dash/dist/assets/{Textarea-CcQGSgSH.js → Textarea-xnAybFGd.js} +1 -1
- package/bizar-dash/dist/assets/{Textarea-CcQGSgSH.js.map → Textarea-xnAybFGd.js.map} +1 -1
- package/bizar-dash/dist/assets/UpdateView-BUAEcRCX.js +1 -0
- package/bizar-dash/dist/assets/UpdateView-BUAEcRCX.js.map +1 -0
- package/bizar-dash/dist/assets/{UsageView-P5_pQ68d.js → UsageView-DMFbBOfH.js} +1 -1
- package/bizar-dash/dist/assets/{UsageView-P5_pQ68d.js.map → UsageView-DMFbBOfH.js.map} +1 -1
- package/bizar-dash/dist/assets/{ViewHeader-BaCWNuVX.js → ViewHeader-C-t1U2cQ.js} +1 -1
- package/bizar-dash/dist/assets/{ViewHeader-BaCWNuVX.js.map → ViewHeader-C-t1U2cQ.js.map} +1 -1
- package/bizar-dash/dist/assets/VoiceView-aoPVT6LJ.js +1 -0
- package/bizar-dash/dist/assets/VoiceView-aoPVT6LJ.js.map +1 -0
- package/bizar-dash/dist/assets/{brain-D6tAeUDE.js → brain-DhAzcHUk.js} +1 -1
- package/bizar-dash/dist/assets/{brain-D6tAeUDE.js.map → brain-DhAzcHUk.js.map} +1 -1
- package/bizar-dash/dist/assets/{calendar-BrMJf3vl.js → calendar-B9Wbemh7.js} +1 -1
- package/bizar-dash/dist/assets/{calendar-BrMJf3vl.js.map → calendar-B9Wbemh7.js.map} +1 -1
- package/bizar-dash/dist/assets/{circle-fPTATiOs.js → circle-6NJF_Xl6.js} +1 -1
- package/bizar-dash/dist/assets/{circle-fPTATiOs.js.map → circle-6NJF_Xl6.js.map} +1 -1
- package/bizar-dash/dist/assets/{circle-alert-D7jn06kH.js → circle-alert-Dz8lGfCZ.js} +1 -1
- package/bizar-dash/dist/assets/{circle-alert-D7jn06kH.js.map → circle-alert-Dz8lGfCZ.js.map} +1 -1
- package/bizar-dash/dist/assets/{circle-check-DHs37Sot.js → circle-check-DFB50ojt.js} +1 -1
- package/bizar-dash/dist/assets/{circle-check-DHs37Sot.js.map → circle-check-DFB50ojt.js.map} +1 -1
- package/bizar-dash/dist/assets/{clock-E-G5aGua.js → clock-ClVA6-hQ.js} +1 -1
- package/bizar-dash/dist/assets/{clock-E-G5aGua.js.map → clock-ClVA6-hQ.js.map} +1 -1
- package/bizar-dash/dist/assets/{download-DBkNb6oI.js → download-BvJddD8D.js} +1 -1
- package/bizar-dash/dist/assets/{download-DBkNb6oI.js.map → download-BvJddD8D.js.map} +1 -1
- package/bizar-dash/dist/assets/{external-link-C4pHGbz2.js → external-link-DvL89gaZ.js} +1 -1
- package/bizar-dash/dist/assets/{external-link-C4pHGbz2.js.map → external-link-DvL89gaZ.js.map} +1 -1
- package/bizar-dash/dist/assets/{file-text-CEhDgLq2.js → file-text-BOroDtZE.js} +1 -1
- package/bizar-dash/dist/assets/{file-text-CEhDgLq2.js.map → file-text-BOroDtZE.js.map} +1 -1
- package/bizar-dash/dist/assets/{filter-CEYjeWFX.js → filter-DwPjMRQt.js} +1 -1
- package/bizar-dash/dist/assets/{filter-CEYjeWFX.js.map → filter-DwPjMRQt.js.map} +1 -1
- package/bizar-dash/dist/assets/{git-pull-request-Bo_wBZZw.js → git-pull-request-TDb1inux.js} +1 -1
- package/bizar-dash/dist/assets/{git-pull-request-Bo_wBZZw.js.map → git-pull-request-TDb1inux.js.map} +1 -1
- package/bizar-dash/dist/assets/{main-DDAMz_H5.css → main-B7D_IMTh.css} +1 -1
- package/bizar-dash/dist/assets/main-BURAlktW.js +277 -0
- package/bizar-dash/dist/assets/main-BURAlktW.js.map +1 -0
- package/bizar-dash/dist/assets/{network-BsfwrUA-.js → network-BcCoCzIY.js} +1 -1
- package/bizar-dash/dist/assets/{network-BsfwrUA-.js.map → network-BcCoCzIY.js.map} +1 -1
- package/bizar-dash/dist/assets/{pause-C8PN1_yO.js → pause-BllItT2_.js} +1 -1
- package/bizar-dash/dist/assets/{pause-C8PN1_yO.js.map → pause-BllItT2_.js.map} +1 -1
- package/bizar-dash/dist/assets/{pencil---XuvKlz.js → pencil-DAwM-CC3.js} +1 -1
- package/bizar-dash/dist/assets/{pencil---XuvKlz.js.map → pencil-DAwM-CC3.js.map} +1 -1
- package/bizar-dash/dist/assets/{play-fMLPTihK.js → play-mJh67AXo.js} +2 -2
- package/bizar-dash/dist/assets/{play-fMLPTihK.js.map → play-mJh67AXo.js.map} +1 -1
- package/bizar-dash/dist/assets/{plug-CG8al2Kq.js → plug-BjfntCMC.js} +1 -1
- package/bizar-dash/dist/assets/{plug-CG8al2Kq.js.map → plug-BjfntCMC.js.map} +1 -1
- package/bizar-dash/dist/assets/{plus-uA5Ecuch.js → plus-Cm6-fo14.js} +1 -1
- package/bizar-dash/dist/assets/{plus-uA5Ecuch.js.map → plus-Cm6-fo14.js.map} +1 -1
- package/bizar-dash/dist/assets/{power-DSUlNIuR.js → power-CXN9y2KV.js} +1 -1
- package/bizar-dash/dist/assets/{power-DSUlNIuR.js.map → power-CXN9y2KV.js.map} +1 -1
- package/bizar-dash/dist/assets/{power-off-1CAQsHHy.js → power-off-DUZqwMf9.js} +1 -1
- package/bizar-dash/dist/assets/{power-off-1CAQsHHy.js.map → power-off-DUZqwMf9.js.map} +1 -1
- package/bizar-dash/dist/assets/{refresh-ccw-BL7B0NO1.js → refresh-ccw-DA8_IB9l.js} +1 -1
- package/bizar-dash/dist/assets/{refresh-ccw-BL7B0NO1.js.map → refresh-ccw-DA8_IB9l.js.map} +1 -1
- package/bizar-dash/dist/assets/{refresh-cw-CR9BzJik.js → refresh-cw-DwlP59en.js} +1 -1
- package/bizar-dash/dist/assets/{refresh-cw-CR9BzJik.js.map → refresh-cw-DwlP59en.js.map} +1 -1
- package/bizar-dash/dist/assets/{rotate-ccw-BtnitLfb.js → rotate-ccw-DMTASfAn.js} +1 -1
- package/bizar-dash/dist/assets/{rotate-ccw-BtnitLfb.js.map → rotate-ccw-DMTASfAn.js.map} +1 -1
- package/bizar-dash/dist/assets/{rotate-cw-CHgXCO7O.js → rotate-cw-BDX5SE2s.js} +1 -1
- package/bizar-dash/dist/assets/{rotate-cw-CHgXCO7O.js.map → rotate-cw-BDX5SE2s.js.map} +1 -1
- package/bizar-dash/dist/assets/{save-RcZOXR0U.js → save-Y0jPK5D6.js} +1 -1
- package/bizar-dash/dist/assets/{save-RcZOXR0U.js.map → save-Y0jPK5D6.js.map} +1 -1
- package/bizar-dash/dist/assets/{send-Bg2X_ibk.js → send-C8wwPHhR.js} +2 -2
- package/bizar-dash/dist/assets/{send-Bg2X_ibk.js.map → send-C8wwPHhR.js.map} +1 -1
- package/bizar-dash/dist/assets/{shield-alert-Dm48D034.js → shield-alert-DEvq9UG0.js} +1 -1
- package/bizar-dash/dist/assets/{shield-alert-Dm48D034.js.map → shield-alert-DEvq9UG0.js.map} +1 -1
- package/bizar-dash/dist/assets/{trash-2-B885GY46.js → trash-2-D6qePV_q.js} +2 -2
- package/bizar-dash/dist/assets/{trash-2-B885GY46.js.map → trash-2-D6qePV_q.js.map} +1 -1
- package/bizar-dash/dist/assets/{trending-up-CqUBvVxw.js → trending-up-BZzo_1ic.js} +1 -1
- package/bizar-dash/dist/assets/{trending-up-CqUBvVxw.js.map → trending-up-BZzo_1ic.js.map} +1 -1
- package/bizar-dash/dist/assets/{triangle-alert-DLmUhymD.js → triangle-alert-B2oHbmoh.js} +1 -1
- package/bizar-dash/dist/assets/{triangle-alert-DLmUhymD.js.map → triangle-alert-B2oHbmoh.js.map} +1 -1
- package/bizar-dash/dist/assets/{upload-C-oafpyE.js → upload-Dy59aZn6.js} +1 -1
- package/bizar-dash/dist/assets/{upload-C-oafpyE.js.map → upload-Dy59aZn6.js.map} +1 -1
- package/bizar-dash/dist/assets/{zap-O_4Ys_hh.js → zap-DENVhJnO.js} +2 -2
- package/bizar-dash/dist/assets/{zap-O_4Ys_hh.js.map → zap-DENVhJnO.js.map} +1 -1
- package/bizar-dash/dist/index.html +2 -2
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/bizar-dash/src/web/v8/App.tsx +25 -4
- package/bizar-dash/src/web/v8/shell/Sidebar.tsx +8 -5
- package/bizar-dash/src/web/v8/shell/Topbar.tsx +57 -1
- package/bizar-dash/src/web/v8/ui/styles/globals.css +14 -0
- package/bizar-dash/src/web/v8/ui/theme/ThemeToggle.tsx +11 -6
- package/bizar-dash/src/web/v8/views/Admin/AdminView.tsx +32 -5
- package/bizar-dash/src/web/v8/views/Agents/AgentsView.tsx +11 -4
- package/bizar-dash/src/web/v8/views/Artifacts/ArtifactsView.tsx +1 -1
- package/bizar-dash/src/web/v8/views/Clipboard/ClipboardView.tsx +1 -1
- package/bizar-dash/src/web/v8/views/Dialogs/DialogsView.tsx +1 -1
- package/bizar-dash/src/web/v8/views/Misc/MiscView.tsx +1 -1
- package/bizar-dash/src/web/v8/views/Notifications/NotificationsView.tsx +82 -13
- package/bizar-dash/src/web/v8/views/Overview/OverviewView.tsx +19 -0
- package/bizar-dash/src/web/v8/views/Voice/VoiceView.tsx +1 -1
- package/bizar-dash/tests/admin-run-grouping.test.mjs +42 -0
- package/bizar-dash/tests/agents-preview-truncate.test.mjs +35 -0
- package/bizar-dash/tests/empty-state-docs.test.mjs +55 -0
- package/bizar-dash/tests/focus-visible.test.mjs +30 -0
- package/bizar-dash/tests/notifications-dismiss-restore.test.mjs +85 -0
- package/bizar-dash/tests/palette-shortcut-hint.test.mjs +58 -0
- package/bizar-dash/tests/sidebar-distinct-icons.test.mjs +28 -0
- package/bizar-dash/tests/theme-toggle-icon.test.mjs +28 -0
- package/bizar-dash/tests/topbar-design-system.test.mjs +56 -0
- package/bizar-dash/vite.config.ts +8 -0
- package/package.json +1 -1
- package/packages/sdk/package.json +1 -1
- package/bizar-dash/dist/assets/AdminView-CI4zM2Qg.js +0 -11
- package/bizar-dash/dist/assets/AdminView-CI4zM2Qg.js.map +0 -1
- package/bizar-dash/dist/assets/AgentsView-CIQkZH8_.js.map +0 -1
- package/bizar-dash/dist/assets/ArtifactsView-B-YUwZyb.js.map +0 -1
- package/bizar-dash/dist/assets/Avatar-Dg4OF4RD.js +0 -6
- package/bizar-dash/dist/assets/Avatar-Dg4OF4RD.js.map +0 -1
- package/bizar-dash/dist/assets/ClipboardView-BA323V2J.js +0 -1
- package/bizar-dash/dist/assets/ClipboardView-BA323V2J.js.map +0 -1
- package/bizar-dash/dist/assets/DialogsView-DzTnyX8K.js +0 -6
- package/bizar-dash/dist/assets/DialogsView-DzTnyX8K.js.map +0 -1
- package/bizar-dash/dist/assets/EvalView-lgUhlGJh.js +0 -1
- package/bizar-dash/dist/assets/LibrariesView-Ca_PRjUm.js +0 -6
- package/bizar-dash/dist/assets/MiscView-fBT1TOXC.js +0 -1
- package/bizar-dash/dist/assets/MiscView-fBT1TOXC.js.map +0 -1
- package/bizar-dash/dist/assets/NotificationsView-BKAZLxM2.js +0 -6
- package/bizar-dash/dist/assets/NotificationsView-BKAZLxM2.js.map +0 -1
- package/bizar-dash/dist/assets/ObsidianView-C-AvHfpT.js +0 -1
- package/bizar-dash/dist/assets/OverviewView-CKYzZJe3.js +0 -1
- package/bizar-dash/dist/assets/OverviewView-CKYzZJe3.js.map +0 -1
- package/bizar-dash/dist/assets/UpdateView-B9Lh4svM.js +0 -6
- package/bizar-dash/dist/assets/UpdateView-B9Lh4svM.js.map +0 -1
- package/bizar-dash/dist/assets/VoiceView-BwR9gm5e.js +0 -1
- package/bizar-dash/dist/assets/VoiceView-BwR9gm5e.js.map +0 -1
- package/bizar-dash/dist/assets/main-BZMmD6a8.js +0 -249
- 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
|
-
|
|
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 ${
|
|
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
|
|
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
|
-
{
|
|
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
|
-
<
|
|
116
|
-
{
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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:
|
|
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
|
-
|
|
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="
|
|
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="
|
|
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
|
|
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
|
|
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
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
161
|
-
<Button variant="ghost" onClick={() =>
|
|
162
|
-
|
|
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
|
|
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
|
+
});
|