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