@polderlabs/bizar 10.1.0 → 10.2.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/README.md +0 -2
- package/bizar-dash/dist/assets/{ActivityFeed-Da-JCrE6.js → ActivityFeed-JjmOY2AQ.js} +1 -1
- package/bizar-dash/dist/assets/{ActivityFeed-Da-JCrE6.js.map → ActivityFeed-JjmOY2AQ.js.map} +1 -1
- package/bizar-dash/dist/assets/{ActivityView-nIRsSETb.js → ActivityView-BIIMvGWo.js} +1 -1
- package/bizar-dash/dist/assets/{ActivityView-nIRsSETb.js.map → ActivityView-BIIMvGWo.js.map} +1 -1
- package/bizar-dash/dist/assets/{AdminView-DP1NMxmG.js → AdminView-DRidJtkq.js} +1 -1
- package/bizar-dash/dist/assets/{AdminView-DP1NMxmG.js.map → AdminView-DRidJtkq.js.map} +1 -1
- package/bizar-dash/dist/assets/{AgentActivity-lSPkOr-O.js → AgentActivity-79l4SBMC.js} +1 -1
- package/bizar-dash/dist/assets/{AgentActivity-lSPkOr-O.js.map → AgentActivity-79l4SBMC.js.map} +1 -1
- package/bizar-dash/dist/assets/{AgentsView-BIWGkz7w.js → AgentsView-0vT4uPSc.js} +1 -1
- package/bizar-dash/dist/assets/{AgentsView-BIWGkz7w.js.map → AgentsView-0vT4uPSc.js.map} +1 -1
- package/bizar-dash/dist/assets/{Alert-DN0UysMg.js → Alert-CG5q_s75.js} +1 -1
- package/bizar-dash/dist/assets/{Alert-DN0UysMg.js.map → Alert-CG5q_s75.js.map} +1 -1
- package/bizar-dash/dist/assets/ArtifactsView-DM8ZmpR9.js +21 -0
- package/bizar-dash/dist/assets/ArtifactsView-DM8ZmpR9.js.map +1 -0
- package/bizar-dash/dist/assets/{AuthView-DW5cs7ma.js → AuthView-BveN94MC.js} +1 -1
- package/bizar-dash/dist/assets/{AuthView-DW5cs7ma.js.map → AuthView-BveN94MC.js.map} +1 -1
- package/bizar-dash/dist/assets/{Avatar-DZPv9pvd.js → Avatar-ulUPIYTd.js} +1 -1
- package/bizar-dash/dist/assets/{Avatar-DZPv9pvd.js.map → Avatar-ulUPIYTd.js.map} +1 -1
- package/bizar-dash/dist/assets/{BackgroundJobsView-D8O-EyMD.js → BackgroundJobsView-BWErhqFo.js} +1 -1
- package/bizar-dash/dist/assets/{BackgroundJobsView-D8O-EyMD.js.map → BackgroundJobsView-BWErhqFo.js.map} +1 -1
- package/bizar-dash/dist/assets/{BackupView-uwXkhxOV.js → BackupView-CnBjzG9N.js} +1 -1
- package/bizar-dash/dist/assets/{BackupView-uwXkhxOV.js.map → BackupView-CnBjzG9N.js.map} +1 -1
- package/bizar-dash/dist/assets/{Card-Tyboihu7.js → Card-B0imhxMk.js} +1 -1
- package/bizar-dash/dist/assets/{Card-Tyboihu7.js.map → Card-B0imhxMk.js.map} +1 -1
- package/bizar-dash/dist/assets/{ChatView-DRBHzgay.js → ChatView-D6zGytzx.js} +1 -1
- package/bizar-dash/dist/assets/{ChatView-DRBHzgay.js.map → ChatView-D6zGytzx.js.map} +1 -1
- package/bizar-dash/dist/assets/{Chip-BrXIlR9w.js → Chip-wwZSGwLE.js} +1 -1
- package/bizar-dash/dist/assets/{Chip-BrXIlR9w.js.map → Chip-wwZSGwLE.js.map} +1 -1
- package/bizar-dash/dist/assets/{ClaudeSessionsView-Ck62jaOr.js → ClaudeSessionsView-DK1sOLhi.js} +1 -1
- package/bizar-dash/dist/assets/{ClaudeSessionsView-Ck62jaOr.js.map → ClaudeSessionsView-DK1sOLhi.js.map} +1 -1
- package/bizar-dash/dist/assets/{ClipboardView-DfND6xL6.js → ClipboardView-CZ2lYPr0.js} +1 -1
- package/bizar-dash/dist/assets/{ClipboardView-DfND6xL6.js.map → ClipboardView-CZ2lYPr0.js.map} +1 -1
- package/bizar-dash/dist/assets/{ConfigView-DjJmSrOJ.js → ConfigView-D-zT9eNA.js} +1 -1
- package/bizar-dash/dist/assets/{ConfigView-DjJmSrOJ.js.map → ConfigView-D-zT9eNA.js.map} +1 -1
- package/bizar-dash/dist/assets/{DiagnosticsView-CKgItf0J.js → DiagnosticsView-BocV68Lb.js} +1 -1
- package/bizar-dash/dist/assets/{DiagnosticsView-CKgItf0J.js.map → DiagnosticsView-BocV68Lb.js.map} +1 -1
- package/bizar-dash/dist/assets/{DialogsView-DHYF8dE1.js → DialogsView-BVjPI5x7.js} +1 -1
- package/bizar-dash/dist/assets/{DialogsView-DHYF8dE1.js.map → DialogsView-BVjPI5x7.js.map} +1 -1
- package/bizar-dash/dist/assets/{DoctorView-DDLHrSKk.js → DoctorView-84gJcZwM.js} +1 -1
- package/bizar-dash/dist/assets/{DoctorView-DDLHrSKk.js.map → DoctorView-84gJcZwM.js.map} +1 -1
- package/bizar-dash/dist/assets/{EmptyState-DZ2ouKwd.js → EmptyState-BjgVOrcO.js} +1 -1
- package/bizar-dash/dist/assets/{EmptyState-DZ2ouKwd.js.map → EmptyState-BjgVOrcO.js.map} +1 -1
- package/bizar-dash/dist/assets/{EnvVarsView-D1cx83mB.js → EnvVarsView-BJ2TEzF7.js} +1 -1
- package/bizar-dash/dist/assets/{EnvVarsView-D1cx83mB.js.map → EnvVarsView-BJ2TEzF7.js.map} +1 -1
- package/bizar-dash/dist/assets/{ErrorState-DwwJ2-Jq.js → ErrorState-BE3WBFqF.js} +1 -1
- package/bizar-dash/dist/assets/{ErrorState-DwwJ2-Jq.js.map → ErrorState-BE3WBFqF.js.map} +1 -1
- package/bizar-dash/dist/assets/{EvalView-C9aYixXW.js → EvalView-BJJ2P-kK.js} +1 -1
- package/bizar-dash/dist/assets/{EvalView-C9aYixXW.js.map → EvalView-BJJ2P-kK.js.map} +1 -1
- package/bizar-dash/dist/assets/{GoalsView-DZa_mGmh.js → GoalsView-pwROHApi.js} +1 -1
- package/bizar-dash/dist/assets/{GoalsView-DZa_mGmh.js.map → GoalsView-pwROHApi.js.map} +1 -1
- package/bizar-dash/dist/assets/{Grid-CiRyhAgR.js → Grid-qcvbhx6e.js} +1 -1
- package/bizar-dash/dist/assets/{Grid-CiRyhAgR.js.map → Grid-qcvbhx6e.js.map} +1 -1
- package/bizar-dash/dist/assets/{HeadroomView-CNc-TI7u.js → HeadroomView-D1UiTYbt.js} +1 -1
- package/bizar-dash/dist/assets/{HeadroomView-CNc-TI7u.js.map → HeadroomView-D1UiTYbt.js.map} +1 -1
- package/bizar-dash/dist/assets/{HistoryView-Dv7aPxJE.js → HistoryView-C7SSOLJA.js} +1 -1
- package/bizar-dash/dist/assets/{HistoryView-Dv7aPxJE.js.map → HistoryView-C7SSOLJA.js.map} +1 -1
- package/bizar-dash/dist/assets/{Input-BbFUDQnS.js → Input-Bgzxf_Hu.js} +1 -1
- package/bizar-dash/dist/assets/{Input-BbFUDQnS.js.map → Input-Bgzxf_Hu.js.map} +1 -1
- package/bizar-dash/dist/assets/{LibrariesView-DBQ83TNa.js → LibrariesView-CbFFksox.js} +1 -1
- package/bizar-dash/dist/assets/{LibrariesView-DBQ83TNa.js.map → LibrariesView-CbFFksox.js.map} +1 -1
- package/bizar-dash/dist/assets/{LightRAGView-JYpE5u9p.js → LightRAGView-BqDrJbRq.js} +1 -1
- package/bizar-dash/dist/assets/{LightRAGView-JYpE5u9p.js.map → LightRAGView-BqDrJbRq.js.map} +1 -1
- package/bizar-dash/dist/assets/{ListHeader-BGjm11Z0.js → ListHeader-TjPJrUfY.js} +1 -1
- package/bizar-dash/dist/assets/{ListHeader-BGjm11Z0.js.map → ListHeader-TjPJrUfY.js.map} +1 -1
- package/bizar-dash/dist/assets/{MemoryView-CnOP2MvM.js → MemoryView-BlTeHLhX.js} +1 -1
- package/bizar-dash/dist/assets/{MemoryView-CnOP2MvM.js.map → MemoryView-BlTeHLhX.js.map} +1 -1
- package/bizar-dash/dist/assets/{MiscView-BBcpUbZZ.js → MiscView-B8QY82Pm.js} +1 -1
- package/bizar-dash/dist/assets/{MiscView-BBcpUbZZ.js.map → MiscView-B8QY82Pm.js.map} +1 -1
- package/bizar-dash/dist/assets/{ModsView-CU4cAmIz.js → ModsView-CvW1OWts.js} +1 -1
- package/bizar-dash/dist/assets/{ModsView-CU4cAmIz.js.map → ModsView-CvW1OWts.js.map} +1 -1
- package/bizar-dash/dist/assets/{NotificationsView-BYffvthq.js → NotificationsView-CsBt5U7x.js} +1 -1
- package/bizar-dash/dist/assets/{NotificationsView-BYffvthq.js.map → NotificationsView-CsBt5U7x.js.map} +1 -1
- package/bizar-dash/dist/assets/{ObsidianView-D3bI0row.js → ObsidianView-BCTejEj2.js} +1 -1
- package/bizar-dash/dist/assets/{ObsidianView-D3bI0row.js.map → ObsidianView-BCTejEj2.js.map} +1 -1
- package/bizar-dash/dist/assets/{OverviewView-0tq8f-Y0.js → OverviewView-BsuPxwnB.js} +1 -1
- package/bizar-dash/dist/assets/{OverviewView-0tq8f-Y0.js.map → OverviewView-BsuPxwnB.js.map} +1 -1
- package/bizar-dash/dist/assets/{ProgressBar-B-h60LKA.js → ProgressBar-DlZTETEn.js} +1 -1
- package/bizar-dash/dist/assets/{ProgressBar-B-h60LKA.js.map → ProgressBar-DlZTETEn.js.map} +1 -1
- package/bizar-dash/dist/assets/{ProjectsView-DhX5lzHS.js → ProjectsView-Soxtmpet.js} +1 -1
- package/bizar-dash/dist/assets/{ProjectsView-DhX5lzHS.js.map → ProjectsView-Soxtmpet.js.map} +1 -1
- package/bizar-dash/dist/assets/{ProvidersView-CJMX7jm9.js → ProvidersView-DaCEV9pM.js} +1 -1
- package/bizar-dash/dist/assets/{ProvidersView-CJMX7jm9.js.map → ProvidersView-DaCEV9pM.js.map} +1 -1
- package/bizar-dash/dist/assets/{SchedulesView-CcwDQ9Nm.js → SchedulesView-DY2ByBj_.js} +1 -1
- package/bizar-dash/dist/assets/{SchedulesView-CcwDQ9Nm.js.map → SchedulesView-DY2ByBj_.js.map} +1 -1
- package/bizar-dash/dist/assets/{Select-Cw7Qtz2M.js → Select-Dbd88_n8.js} +1 -1
- package/bizar-dash/dist/assets/{Select-Cw7Qtz2M.js.map → Select-Dbd88_n8.js.map} +1 -1
- package/bizar-dash/dist/assets/{SettingsRow-DgsgbPSI.js → SettingsRow-CLMNd434.js} +1 -1
- package/bizar-dash/dist/assets/{SettingsRow-DgsgbPSI.js.map → SettingsRow-CLMNd434.js.map} +1 -1
- package/bizar-dash/dist/assets/{SettingsView-DTAFdKyK.js → SettingsView-b24oFq9x.js} +1 -1
- package/bizar-dash/dist/assets/{SettingsView-DTAFdKyK.js.map → SettingsView-b24oFq9x.js.map} +1 -1
- package/bizar-dash/dist/assets/{Sheet-DH-WNJ3Y.js → Sheet-DnDid58j.js} +1 -1
- package/bizar-dash/dist/assets/{Sheet-DH-WNJ3Y.js.map → Sheet-DnDid58j.js.map} +1 -1
- package/bizar-dash/dist/assets/{Slider-DXbJuQ7y.js → Slider-BJCvKI1I.js} +1 -1
- package/bizar-dash/dist/assets/{Slider-DXbJuQ7y.js.map → Slider-BJCvKI1I.js.map} +1 -1
- package/bizar-dash/dist/assets/{Sparkline-CYtAn82j.js → Sparkline-ktqQLdyb.js} +1 -1
- package/bizar-dash/dist/assets/{Sparkline-CYtAn82j.js.map → Sparkline-ktqQLdyb.js.map} +1 -1
- package/bizar-dash/dist/assets/{Switch-BItGrPqr.js → Switch-CwXfsDii.js} +1 -1
- package/bizar-dash/dist/assets/{Switch-BItGrPqr.js.map → Switch-CwXfsDii.js.map} +1 -1
- package/bizar-dash/dist/assets/{TasksView-i5tm6CkX.js → TasksView-BHmpzFCf.js} +1 -1
- package/bizar-dash/dist/assets/{TasksView-i5tm6CkX.js.map → TasksView-BHmpzFCf.js.map} +1 -1
- package/bizar-dash/dist/assets/{Textarea-qYDf3yds.js → Textarea-BzIAwSlp.js} +1 -1
- package/bizar-dash/dist/assets/{Textarea-qYDf3yds.js.map → Textarea-BzIAwSlp.js.map} +1 -1
- package/bizar-dash/dist/assets/{UpdateView-Xln-ej26.js → UpdateView-CSmX9NN0.js} +1 -1
- package/bizar-dash/dist/assets/{UpdateView-Xln-ej26.js.map → UpdateView-CSmX9NN0.js.map} +1 -1
- package/bizar-dash/dist/assets/{UsageView-BGuZSUZ6.js → UsageView-COyr_Gfi.js} +1 -1
- package/bizar-dash/dist/assets/{UsageView-BGuZSUZ6.js.map → UsageView-COyr_Gfi.js.map} +1 -1
- package/bizar-dash/dist/assets/{ViewHeader-BqIPVKuG.js → ViewHeader-BzYZ3m-e.js} +1 -1
- package/bizar-dash/dist/assets/{ViewHeader-BqIPVKuG.js.map → ViewHeader-BzYZ3m-e.js.map} +1 -1
- package/bizar-dash/dist/assets/{VoiceView-CWUOCQs_.js → VoiceView-D33_xG5t.js} +1 -1
- package/bizar-dash/dist/assets/{VoiceView-CWUOCQs_.js.map → VoiceView-D33_xG5t.js.map} +1 -1
- package/bizar-dash/dist/assets/{brain-fsBSDl2g.js → brain-CM-Vls8r.js} +1 -1
- package/bizar-dash/dist/assets/{brain-fsBSDl2g.js.map → brain-CM-Vls8r.js.map} +1 -1
- package/bizar-dash/dist/assets/{calendar-DsHTZoCU.js → calendar-C0lgZk69.js} +1 -1
- package/bizar-dash/dist/assets/{calendar-DsHTZoCU.js.map → calendar-C0lgZk69.js.map} +1 -1
- package/bizar-dash/dist/assets/{circle-CqSxqqeJ.js → circle-Bx_hQEBC.js} +1 -1
- package/bizar-dash/dist/assets/{circle-CqSxqqeJ.js.map → circle-Bx_hQEBC.js.map} +1 -1
- package/bizar-dash/dist/assets/{circle-alert-BsLl001l.js → circle-alert-DHvsqpw5.js} +1 -1
- package/bizar-dash/dist/assets/{circle-alert-BsLl001l.js.map → circle-alert-DHvsqpw5.js.map} +1 -1
- package/bizar-dash/dist/assets/{circle-check-rVKMQmZp.js → circle-check-Bx8wmXR9.js} +1 -1
- package/bizar-dash/dist/assets/{circle-check-rVKMQmZp.js.map → circle-check-Bx8wmXR9.js.map} +1 -1
- package/bizar-dash/dist/assets/{clock-OuficfgH.js → clock-CejGoecX.js} +1 -1
- package/bizar-dash/dist/assets/{clock-OuficfgH.js.map → clock-CejGoecX.js.map} +1 -1
- package/bizar-dash/dist/assets/{download-BdHlstUY.js → download-4cEUWue1.js} +1 -1
- package/bizar-dash/dist/assets/{download-BdHlstUY.js.map → download-4cEUWue1.js.map} +1 -1
- package/bizar-dash/dist/assets/{external-link-DA3_VGm-.js → external-link-0NqUhI4i.js} +1 -1
- package/bizar-dash/dist/assets/{external-link-DA3_VGm-.js.map → external-link-0NqUhI4i.js.map} +1 -1
- package/bizar-dash/dist/assets/{file-text-B-g8H227.js → file-text-yEMkrSoZ.js} +1 -1
- package/bizar-dash/dist/assets/{file-text-B-g8H227.js.map → file-text-yEMkrSoZ.js.map} +1 -1
- package/bizar-dash/dist/assets/{filter-D9B8s5CG.js → filter-BXt60zAS.js} +1 -1
- package/bizar-dash/dist/assets/{filter-D9B8s5CG.js.map → filter-BXt60zAS.js.map} +1 -1
- package/bizar-dash/dist/assets/{git-pull-request-ByH0YuVz.js → git-pull-request-BjG5lNtR.js} +1 -1
- package/bizar-dash/dist/assets/{git-pull-request-ByH0YuVz.js.map → git-pull-request-BjG5lNtR.js.map} +1 -1
- package/bizar-dash/dist/assets/{main-CdOm1Pj9.js → main-CokHAXWM.js} +2 -2
- package/bizar-dash/dist/assets/{main-CdOm1Pj9.js.map → main-CokHAXWM.js.map} +1 -1
- package/bizar-dash/dist/assets/{pause-C9hgd8wX.js → pause-CAmulU0F.js} +1 -1
- package/bizar-dash/dist/assets/{pause-C9hgd8wX.js.map → pause-CAmulU0F.js.map} +1 -1
- package/bizar-dash/dist/assets/{pencil-BanqaMw7.js → pencil-eq5IqBmU.js} +1 -1
- package/bizar-dash/dist/assets/{pencil-BanqaMw7.js.map → pencil-eq5IqBmU.js.map} +1 -1
- package/bizar-dash/dist/assets/{play-CawUZ02_.js → play-BoLrrcBA.js} +1 -1
- package/bizar-dash/dist/assets/{play-CawUZ02_.js.map → play-BoLrrcBA.js.map} +1 -1
- package/bizar-dash/dist/assets/{plug-Cmv0-T_e.js → plug-DBrVM9lK.js} +1 -1
- package/bizar-dash/dist/assets/{plug-Cmv0-T_e.js.map → plug-DBrVM9lK.js.map} +1 -1
- package/bizar-dash/dist/assets/{plus-M6LYiKX8.js → plus-DENm0hqV.js} +1 -1
- package/bizar-dash/dist/assets/{plus-M6LYiKX8.js.map → plus-DENm0hqV.js.map} +1 -1
- package/bizar-dash/dist/assets/{power-DP5x_SRF.js → power-DLjXF7zE.js} +1 -1
- package/bizar-dash/dist/assets/{power-DP5x_SRF.js.map → power-DLjXF7zE.js.map} +1 -1
- package/bizar-dash/dist/assets/{power-off-NOJRLAIs.js → power-off-D47cOnG_.js} +1 -1
- package/bizar-dash/dist/assets/{power-off-NOJRLAIs.js.map → power-off-D47cOnG_.js.map} +1 -1
- package/bizar-dash/dist/assets/{refresh-ccw-CHMprsm5.js → refresh-ccw-DNrXX9gB.js} +1 -1
- package/bizar-dash/dist/assets/{refresh-ccw-CHMprsm5.js.map → refresh-ccw-DNrXX9gB.js.map} +1 -1
- package/bizar-dash/dist/assets/{refresh-cw-1RBqDKgJ.js → refresh-cw-CoAHDpeS.js} +1 -1
- package/bizar-dash/dist/assets/{refresh-cw-1RBqDKgJ.js.map → refresh-cw-CoAHDpeS.js.map} +1 -1
- package/bizar-dash/dist/assets/{rotate-ccw-DJRekBbi.js → rotate-ccw-ObX5awaG.js} +1 -1
- package/bizar-dash/dist/assets/{rotate-ccw-DJRekBbi.js.map → rotate-ccw-ObX5awaG.js.map} +1 -1
- package/bizar-dash/dist/assets/{rotate-cw-DHooc0Tx.js → rotate-cw-xpPJw9LP.js} +1 -1
- package/bizar-dash/dist/assets/{rotate-cw-DHooc0Tx.js.map → rotate-cw-xpPJw9LP.js.map} +1 -1
- package/bizar-dash/dist/assets/{save-DQKCKyZK.js → save-D1iKiJJl.js} +1 -1
- package/bizar-dash/dist/assets/{save-DQKCKyZK.js.map → save-D1iKiJJl.js.map} +1 -1
- package/bizar-dash/dist/assets/{send-xgy3RAi7.js → send-BRcAhZUm.js} +1 -1
- package/bizar-dash/dist/assets/{send-xgy3RAi7.js.map → send-BRcAhZUm.js.map} +1 -1
- package/bizar-dash/dist/assets/{shield-alert-CWV2F5ab.js → shield-alert-C6vEcK-g.js} +1 -1
- package/bizar-dash/dist/assets/{shield-alert-CWV2F5ab.js.map → shield-alert-C6vEcK-g.js.map} +1 -1
- package/bizar-dash/dist/assets/{trash-2-CL4khSRv.js → trash-2-DZDjEnNh.js} +1 -1
- package/bizar-dash/dist/assets/{trash-2-CL4khSRv.js.map → trash-2-DZDjEnNh.js.map} +1 -1
- package/bizar-dash/dist/assets/{trending-up-CSJJhWTS.js → trending-up-DMj-AJVf.js} +1 -1
- package/bizar-dash/dist/assets/{trending-up-CSJJhWTS.js.map → trending-up-DMj-AJVf.js.map} +1 -1
- package/bizar-dash/dist/assets/{triangle-alert-BPLb7uLt.js → triangle-alert-_2xe_tl4.js} +1 -1
- package/bizar-dash/dist/assets/{triangle-alert-BPLb7uLt.js.map → triangle-alert-_2xe_tl4.js.map} +1 -1
- package/bizar-dash/dist/assets/{upload-Cww6MvFq.js → upload-DdDljAeV.js} +1 -1
- package/bizar-dash/dist/assets/{upload-Cww6MvFq.js.map → upload-DdDljAeV.js.map} +1 -1
- package/bizar-dash/dist/assets/{zap-DmhVUzRA.js → zap-mHsgh0FP.js} +1 -1
- package/bizar-dash/dist/assets/{zap-DmhVUzRA.js.map → zap-mHsgh0FP.js.map} +1 -1
- package/bizar-dash/dist/index.html +1 -1
- package/bizar-dash/skills/headroom/SKILL.md +94 -0
- package/bizar-dash/src/server/api.mjs +11 -0
- package/bizar-dash/src/server/artifacts-store.mjs +120 -14
- package/bizar-dash/src/server/claude-artifacts.mjs +281 -0
- package/bizar-dash/src/server/headroom.mjs +649 -0
- package/bizar-dash/src/server/routes/artifacts.mjs +42 -6
- package/bizar-dash/src/server/routes/audit.mjs +78 -0
- package/bizar-dash/src/server/routes/decisions.mjs +78 -0
- package/bizar-dash/src/server/routes/headroom.mjs +126 -0
- package/bizar-dash/src/server/routes/model-router.mjs +55 -0
- package/bizar-dash/src/server/routes/model-router.test.mjs +28 -0
- package/bizar-dash/src/server/routes/sessions.mjs +80 -0
- package/bizar-dash/src/server/sprint.test.mjs +163 -0
- package/bizar-dash/src/web/v8/views/Artifacts/ArtifactsView.tsx +232 -21
- package/bizar-dash/src/web/v8/views/Artifacts/ClaudeArtifactView.tsx +179 -0
- package/bizar-dash/tests/headroom-install.test.mjs +173 -0
- package/bizar-dash/tests/headroom-settings.test.mjs +126 -0
- package/bizar-dash/tests/headroom-status.test.mjs +117 -0
- package/cli/commands/headroom.mjs +204 -0
- package/package.json +1 -1
- package/packages/sdk/dist/agent/cron.d.ts +44 -0
- package/packages/sdk/dist/agent/cron.js +79 -0
- package/packages/sdk/dist/agent/heartbeat.d.ts +43 -0
- package/packages/sdk/dist/agent/heartbeat.js +116 -0
- package/packages/sdk/dist/index.d.ts +3 -0
- package/packages/sdk/dist/index.js +5 -0
- package/packages/sdk/dist/learning/decisions.d.ts +46 -0
- package/packages/sdk/dist/learning/decisions.js +132 -0
- package/packages/sdk/dist/learning/index.d.ts +8 -0
- package/packages/sdk/dist/learning/index.js +8 -0
- package/packages/sdk/dist/learning/instincts.d.ts +38 -0
- package/packages/sdk/dist/learning/instincts.js +139 -0
- package/packages/sdk/dist/mcp/server.js +119 -1
- package/packages/sdk/dist/router/agent-model-registry.d.ts +89 -0
- package/packages/sdk/dist/router/agent-model-registry.js +144 -0
- package/packages/sdk/dist/router/index.d.ts +1 -0
- package/packages/sdk/dist/router/index.js +1 -0
- package/packages/sdk/package.json +3 -2
- package/scripts/audit.mjs +388 -0
- package/scripts/audit.test.mjs +149 -0
- package/scripts/eval-gate.mjs +134 -0
- package/scripts/eval-gate.test.mjs +138 -0
- package/scripts/feature-state-machine.mjs +178 -0
- package/scripts/feature-state-machine.test.mjs +135 -0
- package/scripts/spec-dedup.test.mjs +124 -0
- package/scripts/sprint.mjs +213 -0
- package/scripts/worktree-setup.sh +79 -0
- package/bizar-dash/dist/assets/ArtifactsView-CDIhPBAx.js +0 -6
- package/bizar-dash/dist/assets/ArtifactsView-CDIhPBAx.js.map +0 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bizar-dash/src/server/sprint.test.mjs
|
|
3
|
+
*
|
|
4
|
+
* Pillar B — Tests for scripts/sprint.mjs
|
|
5
|
+
* 3 groups: parser test, fill test, missing-goal tolerance.
|
|
6
|
+
*/
|
|
7
|
+
import { test } from 'node:test';
|
|
8
|
+
import assert from 'node:assert/strict';
|
|
9
|
+
import { execSync } from 'node:child_process';
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { tmpdir } from 'node:os';
|
|
13
|
+
|
|
14
|
+
const SCRIPTS_SPRINT = join(process.cwd(), 'scripts', 'sprint.mjs');
|
|
15
|
+
const PROGRESS_PARSER = join(process.cwd(), 'bizar-dash', 'src', 'server', 'progress-parser.mjs');
|
|
16
|
+
const SPRINT_TEMPLATE = join(process.cwd(), 'templates', 'sprint-contract.md');
|
|
17
|
+
|
|
18
|
+
function runSprintScript(goalId, cwd) {
|
|
19
|
+
try {
|
|
20
|
+
const stdout = execSync(`node "${SCRIPTS_SPRINT}" "${goalId}" "${cwd}" 2>&1`, {
|
|
21
|
+
encoding: 'utf8',
|
|
22
|
+
});
|
|
23
|
+
return { ok: true, stdout, stderr: '', exitCode: 0 };
|
|
24
|
+
} catch (err) {
|
|
25
|
+
return {
|
|
26
|
+
ok: false,
|
|
27
|
+
stdout: err.stdout ?? '',
|
|
28
|
+
stderr: err.stderr ?? '',
|
|
29
|
+
exitCode: err.status ?? 1,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function makeTempProject() {
|
|
35
|
+
const tmp = join(tmpdir(), `bizar-sprint-test-${process.pid}-${Date.now()}`);
|
|
36
|
+
mkdirSync(join(tmp, '.bizar', 'sprints'), { recursive: true });
|
|
37
|
+
mkdirSync(join(tmp, 'bizar-dash', 'src', 'server'), { recursive: true });
|
|
38
|
+
mkdirSync(join(tmp, 'templates'), { recursive: true });
|
|
39
|
+
// Symlink progress-parser.mjs
|
|
40
|
+
try {
|
|
41
|
+
const realParser = PROGRESS_PARSER;
|
|
42
|
+
if (existsSync(realParser)) {
|
|
43
|
+
const content = readFileSync(realParser, 'utf8');
|
|
44
|
+
writeFileSync(join(tmp, 'bizar-dash', 'src', 'server', 'progress-parser.mjs'), content, 'utf8');
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// ignore — test will skip
|
|
48
|
+
}
|
|
49
|
+
// Copy template
|
|
50
|
+
try {
|
|
51
|
+
const realTemplate = SPRINT_TEMPLATE;
|
|
52
|
+
if (existsSync(realTemplate)) {
|
|
53
|
+
const content = readFileSync(realTemplate, 'utf8');
|
|
54
|
+
writeFileSync(join(tmp, 'templates', 'sprint-contract.md'), content, 'utf8');
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
// ignore
|
|
58
|
+
}
|
|
59
|
+
return tmp;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── Parser test ───────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
test('progress-parser correctly extracts id, title, status, keyResults from PROGRESS.md', async () => {
|
|
65
|
+
if (!existsSync(PROGRESS_PARSER)) {
|
|
66
|
+
console.warn('progress-parser.mjs not found — skipping parser test');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const { parseProgress } = await import(PROGRESS_PARSER);
|
|
70
|
+
|
|
71
|
+
const sample = `
|
|
72
|
+
# Current State
|
|
73
|
+
## F-099 — My Feature Title
|
|
74
|
+
Owner: thor · Due: 2026-07-31
|
|
75
|
+
Goal is **active**.
|
|
76
|
+
|
|
77
|
+
Key results:
|
|
78
|
+
- [ ] KR1: First result
|
|
79
|
+
- [x] KR2: Second result
|
|
80
|
+
|
|
81
|
+
## Next Steps
|
|
82
|
+
## F-100 — Another Goal
|
|
83
|
+
Goal is **done**.
|
|
84
|
+
`;
|
|
85
|
+
|
|
86
|
+
const result = parseProgress(sample);
|
|
87
|
+
const goal = result.goals.find((g) => g.id === 'F-099');
|
|
88
|
+
assert.ok(goal, 'F-099 should be found');
|
|
89
|
+
assert.equal(goal.title, 'My Feature Title');
|
|
90
|
+
assert.equal(goal.status, 'active');
|
|
91
|
+
assert.equal(goal.keyResults.length, 2);
|
|
92
|
+
assert.equal(goal.keyResults[0].title, 'KR1: First result');
|
|
93
|
+
assert.equal(goal.keyResults[0].done, false);
|
|
94
|
+
assert.equal(goal.keyResults[1].title, 'KR2: Second result');
|
|
95
|
+
assert.equal(goal.keyResults[1].done, true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ── Fill test ────────────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
test('sprint.mjs writes a file with pre-filled Scope (in) from goal key results', () => {
|
|
101
|
+
const tmp = makeTempProject();
|
|
102
|
+
|
|
103
|
+
// Write a progress file with a goal that has KRs
|
|
104
|
+
const progress = `
|
|
105
|
+
## F-099 — My Feature
|
|
106
|
+
Owner: thor · Due: 2026-07-31
|
|
107
|
+
Goal is **active**.
|
|
108
|
+
|
|
109
|
+
Key results:
|
|
110
|
+
- [ ] Pending KR
|
|
111
|
+
- [x] Done KR
|
|
112
|
+
`;
|
|
113
|
+
writeFileSync(join(tmp, '.bizar', 'PROGRESS.md'), progress, 'utf8');
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const r = runSprintScript('F-099', tmp);
|
|
117
|
+
assert.equal(r.exitCode, 0, `sprint.mjs should exit 0: ${r.stdout}\n${r.stderr}`);
|
|
118
|
+
const destPath = join(tmp, '.bizar', 'sprints', 'F-099-' + new Date().toISOString().slice(0, 10) + '.md');
|
|
119
|
+
assert.ok(
|
|
120
|
+
existsSync(destPath),
|
|
121
|
+
`Sprint file should exist at ${destPath}. stdout: ${r.stdout}`
|
|
122
|
+
);
|
|
123
|
+
const content = readFileSync(destPath, 'utf8');
|
|
124
|
+
assert.ok(content.includes('F-099'), 'Should include feature ID');
|
|
125
|
+
assert.ok(content.includes('My Feature'), 'Should include title');
|
|
126
|
+
assert.ok(content.includes('Pending KR'), 'Should include pending KR');
|
|
127
|
+
assert.ok(content.includes('Done KR'), 'Should include done KR');
|
|
128
|
+
assert.ok(content.includes('[x]'), 'Should have checked items');
|
|
129
|
+
} finally {
|
|
130
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ── Missing-goal tolerance ───────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
test('sprint.mjs exits non-zero and prints a clear error when goal not found', () => {
|
|
137
|
+
const tmp = makeTempProject();
|
|
138
|
+
|
|
139
|
+
// Write a progress file with a different goal
|
|
140
|
+
writeFileSync(join(tmp, '.bizar', 'PROGRESS.md'), '## F-001 — Something\nGoal is **done**.\n', 'utf8');
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const r = runSprintScript('F-999', tmp);
|
|
144
|
+
assert.notEqual(r.exitCode, 0, 'Should exit non-zero for missing goal');
|
|
145
|
+
assert.ok(
|
|
146
|
+
r.stdout.includes('F-999') || r.stderr.includes('F-999') || r.stdout.includes('not found'),
|
|
147
|
+
`Error should mention the goal id: ${r.stdout} ${r.stderr}`
|
|
148
|
+
);
|
|
149
|
+
} finally {
|
|
150
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('sprint.mjs exits non-zero when PROGRESS.md is missing', () => {
|
|
155
|
+
const tmp = makeTempProject();
|
|
156
|
+
// Do NOT write PROGRESS.md
|
|
157
|
+
try {
|
|
158
|
+
const r = runSprintScript('F-001', tmp);
|
|
159
|
+
assert.notEqual(r.exitCode, 0, 'Should exit non-zero when PROGRESS.md missing');
|
|
160
|
+
} finally {
|
|
161
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* v8/views/Artifacts/ArtifactsView.tsx — Sprint S43, v9.3.0.
|
|
2
|
+
* v8/views/Artifacts/ArtifactsView.tsx — Sprint S43, v9.3.0 → v10.1.0
|
|
3
|
+
* (Claude Artifacts swap).
|
|
3
4
|
*
|
|
4
5
|
* Artifacts list (GET /api/artifacts) with per-row open-to-detail
|
|
5
6
|
* (right-hand Sheet using GET /api/artifacts/:slug + /render) and
|
|
6
7
|
* inline-confirm Delete (DELETE /api/artifacts/:slug). Add Sheet
|
|
7
|
-
* posts a new artifact (POST /api/artifacts {slug, ...}).
|
|
8
|
+
* posts a new artifact (POST /api/artifacts {slug, kind, source, ...}).
|
|
9
|
+
*
|
|
10
|
+
* v10.1.0 — Detail panel dispatches by `kind`:
|
|
11
|
+
* - claude-html / claude-svg / claude-react → sandboxed iframe
|
|
12
|
+
* via <ClaudeArtifactView>
|
|
13
|
+
* - mdx → legacy block-renderer fallback
|
|
14
|
+
*
|
|
15
|
+
* The AddForm gains a `kind` dropdown + a textarea bound to `source`;
|
|
16
|
+
* the textarea defaults to the kind-specific starting template.
|
|
8
17
|
*/
|
|
9
18
|
|
|
10
19
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
@@ -21,15 +30,17 @@ import { Skeleton } from '../../ui/feedback/Skeleton.js';
|
|
|
21
30
|
import { EmptyState } from '../../ui/feedback/EmptyState.js';
|
|
22
31
|
import { useFetch } from '../../data/useFetch.js';
|
|
23
32
|
import { fetchJson, FetchError } from '../../data/fetcher.js';
|
|
33
|
+
import { ClaudeArtifactView, type ClaudeRenderPayload } from './ClaudeArtifactView.js';
|
|
24
34
|
|
|
25
35
|
interface Artifact {
|
|
26
36
|
slug: string;
|
|
27
37
|
title?: string;
|
|
28
|
-
kind?:
|
|
38
|
+
kind?: 'mdx' | 'claude-html' | 'claude-svg' | 'claude-react';
|
|
29
39
|
description?: string;
|
|
30
40
|
createdAt?: string | number;
|
|
31
41
|
updatedAt?: string | number;
|
|
32
42
|
planMdx?: string;
|
|
43
|
+
source?: string;
|
|
33
44
|
frontmatter?: Record<string, unknown>;
|
|
34
45
|
}
|
|
35
46
|
|
|
@@ -130,7 +141,7 @@ export function ArtifactsView(): JSX.Element {
|
|
|
130
141
|
</Card>
|
|
131
142
|
|
|
132
143
|
<Sheet open={adding} onOpenChange={setAdding}>
|
|
133
|
-
<SheetContent side="right" title="Add artifact" description="Slug +
|
|
144
|
+
<SheetContent side="right" title="Add artifact" description="Slug + kind + initial source.">
|
|
134
145
|
<AddForm
|
|
135
146
|
onSubmit={async (body) => {
|
|
136
147
|
await fetchJson('/api/artifacts', { method: 'POST', body });
|
|
@@ -144,7 +155,7 @@ export function ArtifactsView(): JSX.Element {
|
|
|
144
155
|
|
|
145
156
|
<Sheet open={activeSlug !== null} onOpenChange={(open) => { if (!open) setActiveSlug(null); }}>
|
|
146
157
|
<SheetContent side="right" title={activeSlug ?? ''} description="Artifact detail">
|
|
147
|
-
{activeSlug !== null && <ArtifactDetail slug={activeSlug} />}
|
|
158
|
+
{activeSlug !== null && <ArtifactDetail slug={activeSlug} refresh={refresh} />}
|
|
148
159
|
</SheetContent>
|
|
149
160
|
</Sheet>
|
|
150
161
|
</Stack>
|
|
@@ -155,16 +166,24 @@ function AddForm({
|
|
|
155
166
|
onSubmit,
|
|
156
167
|
onCancel,
|
|
157
168
|
}: {
|
|
158
|
-
onSubmit: (body: { slug: string; title?: string; description?: string;
|
|
169
|
+
onSubmit: (body: { slug: string; title?: string; description?: string; kind?: string; source?: string; planMdx?: string }) => Promise<void>;
|
|
159
170
|
onCancel: () => void;
|
|
160
171
|
}): JSX.Element {
|
|
161
172
|
const [slug, setSlug] = useState<string>('');
|
|
162
173
|
const [title, setTitle] = useState<string>('');
|
|
163
174
|
const [description, setDescription] = useState<string>('');
|
|
164
|
-
const [
|
|
175
|
+
const [kind, setKind] = useState<'mdx' | 'claude-html' | 'claude-svg' | 'claude-react'>('claude-html');
|
|
176
|
+
const [source, setSource] = useState<string>(KIND_TEMPLATES['claude-html']);
|
|
165
177
|
const [busy, setBusy] = useState<boolean>(false);
|
|
166
178
|
const [error, setError] = useState<string | null>(null);
|
|
167
179
|
|
|
180
|
+
// When the user changes kind, swap the textarea content to a starter
|
|
181
|
+
// template for that kind. They can still edit further.
|
|
182
|
+
const onKindChange = (next: 'mdx' | 'claude-html' | 'claude-svg' | 'claude-react'): void => {
|
|
183
|
+
setKind(next);
|
|
184
|
+
setSource(KIND_TEMPLATES[next]);
|
|
185
|
+
};
|
|
186
|
+
|
|
168
187
|
return (
|
|
169
188
|
<Stack gap={3} style={{ padding: 'var(--space-4)' }}>
|
|
170
189
|
<label style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-1)' }}>
|
|
@@ -180,8 +199,37 @@ function AddForm({
|
|
|
180
199
|
<Input value={description} onChange={(e) => setDescription((e.target as HTMLInputElement).value)} data-testid="artifact-form-description" />
|
|
181
200
|
</label>
|
|
182
201
|
<label style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-1)' }}>
|
|
183
|
-
<span style={{ fontSize: 'var(--fs-12)', color: 'var(--fg-muted)' }}>
|
|
184
|
-
<
|
|
202
|
+
<span style={{ fontSize: 'var(--fs-12)', color: 'var(--fg-muted)' }}>Kind</span>
|
|
203
|
+
<select
|
|
204
|
+
value={kind}
|
|
205
|
+
onChange={(e) => onKindChange(e.target.value as typeof kind)}
|
|
206
|
+
data-testid="artifact-form-kind"
|
|
207
|
+
style={{
|
|
208
|
+
fontFamily: 'inherit',
|
|
209
|
+
fontSize: 'var(--fs-12)',
|
|
210
|
+
padding: 'var(--space-2)',
|
|
211
|
+
background: 'var(--surface-0)',
|
|
212
|
+
color: 'var(--fg)',
|
|
213
|
+
border: '1px solid var(--border)',
|
|
214
|
+
borderRadius: 'var(--radius-sm)',
|
|
215
|
+
}}
|
|
216
|
+
>
|
|
217
|
+
<option value="claude-html">Claude HTML</option>
|
|
218
|
+
<option value="claude-svg">Claude SVG</option>
|
|
219
|
+
<option value="claude-react">Claude React (JSX)</option>
|
|
220
|
+
<option value="mdx">Legacy MDX (glyphs)</option>
|
|
221
|
+
</select>
|
|
222
|
+
</label>
|
|
223
|
+
<label style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-1)' }}>
|
|
224
|
+
<span style={{ fontSize: 'var(--fs-12)', color: 'var(--fg-muted)' }}>
|
|
225
|
+
{kind === 'mdx' ? 'Plan MDX' : 'Source'}
|
|
226
|
+
</span>
|
|
227
|
+
<Textarea
|
|
228
|
+
value={source}
|
|
229
|
+
rows={kind === 'claude-react' ? 10 : 6}
|
|
230
|
+
onChange={(e) => setSource((e.target as HTMLTextAreaElement).value)}
|
|
231
|
+
data-testid={kind === 'mdx' ? 'artifact-form-mdx' : 'artifact-form-source'}
|
|
232
|
+
/>
|
|
185
233
|
</label>
|
|
186
234
|
{error !== null && <span role="alert" style={{ color: 'var(--danger)', fontSize: 'var(--fs-12)' }}>{error}</span>}
|
|
187
235
|
<Inline justify="end" gap={2}>
|
|
@@ -194,7 +242,16 @@ function AddForm({
|
|
|
194
242
|
setBusy(true);
|
|
195
243
|
setError(null);
|
|
196
244
|
try {
|
|
197
|
-
|
|
245
|
+
const body: { slug: string; title?: string; description?: string; kind: typeof kind; source?: string; planMdx?: string } = {
|
|
246
|
+
slug: slug.trim(),
|
|
247
|
+
title: title.trim() || undefined,
|
|
248
|
+
description: description.trim() || undefined,
|
|
249
|
+
kind,
|
|
250
|
+
};
|
|
251
|
+
if (source) {
|
|
252
|
+
if (kind === 'mdx') body.planMdx = source; else body.source = source;
|
|
253
|
+
}
|
|
254
|
+
await onSubmit(body);
|
|
198
255
|
} catch (err) {
|
|
199
256
|
setError(err instanceof FetchError ? err.message : (err as Error).message);
|
|
200
257
|
setBusy(false);
|
|
@@ -208,34 +265,188 @@ function AddForm({
|
|
|
208
265
|
);
|
|
209
266
|
}
|
|
210
267
|
|
|
211
|
-
|
|
268
|
+
const KIND_TEMPLATES: Record<'mdx' | 'claude-html' | 'claude-svg' | 'claude-react', string> = {
|
|
269
|
+
mdx: '# Hello\n\nAdd some MDX here.',
|
|
270
|
+
'claude-html': '<!doctype html>\n<h1 style="font-family:system-ui">Hello artifact</h1>\n<p>Edit me — click <em>Open</em> to preview.</p>',
|
|
271
|
+
'claude-svg': '<svg viewBox="0 0 100 100" width="200" height="200">\n <circle cx="50" cy="50" r="40" fill="#0b66c3"/>\n <text x="50" y="55" text-anchor="middle" fill="white" font-size="14">SVG</text>\n</svg>',
|
|
272
|
+
'claude-react': 'export default function App() {\n const [n, setN] = React.useState(0);\n return (\n <div style={{ fontFamily: "system-ui", padding: 16 }}>\n <h1>Hello</h1>\n <button onClick={() => setN(n + 1)}>clicked {n}</button>\n </div>\n );\n}',
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
function ArtifactDetail({ slug, refresh }: { slug: string; refresh: () => void }): JSX.Element {
|
|
212
276
|
const payload = useFetch<Artifact>(`/api/artifacts/${slug}`);
|
|
213
|
-
const renderPayload = useFetch<
|
|
277
|
+
const renderPayload = useFetch<ClaudeRenderPayload>(`/api/artifacts/${slug}/render`);
|
|
278
|
+
const kind = payload.data?.kind || 'mdx';
|
|
279
|
+
const isClaude = kind === 'claude-html' || kind === 'claude-svg' || kind === 'claude-react';
|
|
280
|
+
|
|
214
281
|
return (
|
|
215
282
|
<Stack gap={2} style={{ padding: 'var(--space-4)' }}>
|
|
216
283
|
{payload.loading && !payload.data ? (
|
|
217
284
|
<Skeleton style={{ height: 80 }} />
|
|
218
285
|
) : payload.data ? (
|
|
219
286
|
<Stack gap={2}>
|
|
220
|
-
<
|
|
287
|
+
<Inline align="center" gap={2}>
|
|
288
|
+
<strong>{payload.data.title || payload.data.slug}</strong>
|
|
289
|
+
<code style={{ fontSize: 'var(--fs-11)', color: 'var(--fg-subtle)' }}>{payload.data.slug}</code>
|
|
290
|
+
<span style={{ fontSize: 'var(--fs-11)', color: 'var(--fg-muted)' }}>· {kind}</span>
|
|
291
|
+
</Inline>
|
|
221
292
|
{payload.data.description && <span style={{ fontSize: 'var(--fs-12)', color: 'var(--fg-muted)' }}>{payload.data.description}</span>}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
<
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
293
|
+
|
|
294
|
+
{/* Claude-* kinds render in a sandboxed iframe via the new
|
|
295
|
+
<ClaudeArtifactView>. The server compiles the source on
|
|
296
|
+
demand and returns iframe-ready HTML (srcdoc). */}
|
|
297
|
+
{isClaude && (
|
|
298
|
+
renderPayload.data ? (
|
|
299
|
+
<ClaudeArtifactView payload={renderPayload.data} slug={slug} />
|
|
300
|
+
) : (
|
|
301
|
+
<Skeleton style={{ height: 200 }} />
|
|
302
|
+
)
|
|
229
303
|
)}
|
|
230
|
-
|
|
304
|
+
|
|
305
|
+
{/* Legacy MDX path keeps the original block-count summary. */}
|
|
306
|
+
{!isClaude && renderPayload.data && (
|
|
231
307
|
<span data-testid={`artifact-render-${slug}`} style={{ fontSize: 'var(--fs-12)', color: 'var(--fg-muted)' }}>
|
|
232
308
|
Render: {Array.isArray(renderPayload.data.blocks) ? `${renderPayload.data.blocks.length} blocks` : 'no blocks'}
|
|
233
309
|
</span>
|
|
234
310
|
)}
|
|
311
|
+
|
|
312
|
+
{/* Inline editor — kind switch + textarea + save. */}
|
|
313
|
+
<Editor payload={payload.data} slug={slug} onSaved={() => { void payload.refetch(); void renderPayload.refetch(); refresh(); }} />
|
|
235
314
|
</Stack>
|
|
236
315
|
) : (
|
|
237
316
|
<span role="alert" style={{ color: 'var(--danger)' }}>Not found</span>
|
|
238
317
|
)}
|
|
239
318
|
</Stack>
|
|
240
319
|
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Inline editor: lets the user change the kind, edit the body, and
|
|
324
|
+
* save via PUT /api/artifacts/:slug. After save we re-fetch both the
|
|
325
|
+
* artifact + the compiled render so the iframe updates with the new
|
|
326
|
+
* source on the next paint.
|
|
327
|
+
*/
|
|
328
|
+
function Editor({
|
|
329
|
+
payload,
|
|
330
|
+
slug,
|
|
331
|
+
onSaved,
|
|
332
|
+
}: {
|
|
333
|
+
payload: Artifact;
|
|
334
|
+
slug: string;
|
|
335
|
+
onSaved: () => void;
|
|
336
|
+
}): JSX.Element {
|
|
337
|
+
const initial = payload.source ?? payload.planMdx ?? '';
|
|
338
|
+
const [editKind, setEditKind] = useState<typeof payload.kind>(payload.kind || 'mdx');
|
|
339
|
+
const [editSource, setEditSource] = useState<string>(initial);
|
|
340
|
+
const [busy, setBusy] = useState<boolean>(false);
|
|
341
|
+
const [error, setError] = useState<string | null>(null);
|
|
342
|
+
const [open, setOpen] = useState<boolean>(false);
|
|
343
|
+
|
|
344
|
+
// Sync the textarea when the underlying artifact changes (e.g. when
|
|
345
|
+
// the user opens a different slug in the same Sheet).
|
|
346
|
+
useEffect(() => {
|
|
347
|
+
setEditKind(payload.kind || 'mdx');
|
|
348
|
+
setEditSource(payload.source ?? payload.planMdx ?? '');
|
|
349
|
+
}, [payload.slug, payload.source, payload.planMdx, payload.kind]);
|
|
350
|
+
|
|
351
|
+
const dirty =
|
|
352
|
+
editKind !== (payload.kind || 'mdx') || editSource !== (payload.source ?? payload.planMdx ?? '');
|
|
353
|
+
|
|
354
|
+
const save = async (): Promise<void> => {
|
|
355
|
+
setBusy(true);
|
|
356
|
+
setError(null);
|
|
357
|
+
try {
|
|
358
|
+
await fetchJson(`/api/artifacts/${slug}`, {
|
|
359
|
+
method: 'PUT',
|
|
360
|
+
body: {
|
|
361
|
+
kind: editKind,
|
|
362
|
+
source: editKind === 'mdx' ? undefined : editSource,
|
|
363
|
+
planMdx: editKind === 'mdx' ? editSource : undefined,
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
onSaved();
|
|
367
|
+
} catch (err) {
|
|
368
|
+
setError(err instanceof FetchError ? err.message : (err as Error).message);
|
|
369
|
+
} finally {
|
|
370
|
+
setBusy(false);
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
return (
|
|
375
|
+
<Stack gap={1} data-testid={`artifact-editor-${slug}`}>
|
|
376
|
+
<Inline align="center" justify="between" gap={2}>
|
|
377
|
+
<Button variant="ghost" onClick={() => setOpen((v) => !v)} data-testid={`artifact-edit-toggle-${slug}`}>
|
|
378
|
+
{open ? 'Close editor' : 'Edit'}
|
|
379
|
+
</Button>
|
|
380
|
+
{dirty && (
|
|
381
|
+
<span style={{ fontSize: 'var(--fs-11)', color: 'var(--callout-warning-fg, #b58900)' }}>
|
|
382
|
+
Unsaved changes
|
|
383
|
+
</span>
|
|
384
|
+
)}
|
|
385
|
+
</Inline>
|
|
386
|
+
{open && (
|
|
387
|
+
<Stack gap={2}>
|
|
388
|
+
<label style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-1)' }}>
|
|
389
|
+
<span style={{ fontSize: 'var(--fs-12)', color: 'var(--fg-muted)' }}>Kind</span>
|
|
390
|
+
<select
|
|
391
|
+
value={editKind}
|
|
392
|
+
onChange={(e) => setEditKind(e.target.value as typeof editKind)}
|
|
393
|
+
data-testid={`artifact-edit-kind-${slug}`}
|
|
394
|
+
style={{
|
|
395
|
+
fontFamily: 'inherit',
|
|
396
|
+
fontSize: 'var(--fs-12)',
|
|
397
|
+
padding: 'var(--space-2)',
|
|
398
|
+
background: 'var(--surface-0)',
|
|
399
|
+
color: 'var(--fg)',
|
|
400
|
+
border: '1px solid var(--border)',
|
|
401
|
+
borderRadius: 'var(--radius-sm)',
|
|
402
|
+
}}
|
|
403
|
+
>
|
|
404
|
+
<option value="claude-html">Claude HTML</option>
|
|
405
|
+
<option value="claude-svg">Claude SVG</option>
|
|
406
|
+
<option value="claude-react">Claude React (JSX)</option>
|
|
407
|
+
<option value="mdx">Legacy MDX</option>
|
|
408
|
+
</select>
|
|
409
|
+
</label>
|
|
410
|
+
<label style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-1)' }}>
|
|
411
|
+
<span style={{ fontSize: 'var(--fs-12)', color: 'var(--fg-muted)' }}>
|
|
412
|
+
{editKind === 'mdx' ? 'Plan MDX' : 'Source'}
|
|
413
|
+
</span>
|
|
414
|
+
<Textarea
|
|
415
|
+
value={editSource}
|
|
416
|
+
rows={editKind === 'claude-react' ? 12 : 8}
|
|
417
|
+
onChange={(e) => setEditSource((e.target as HTMLTextAreaElement).value)}
|
|
418
|
+
data-testid={`artifact-edit-source-${slug}`}
|
|
419
|
+
/>
|
|
420
|
+
</label>
|
|
421
|
+
{error !== null && (
|
|
422
|
+
<span role="alert" data-testid={`artifact-edit-error-${slug}`} style={{ color: 'var(--danger)', fontSize: 'var(--fs-12)' }}>
|
|
423
|
+
{error}
|
|
424
|
+
</span>
|
|
425
|
+
)}
|
|
426
|
+
<Inline gap={1}>
|
|
427
|
+
<Button
|
|
428
|
+
variant="primary"
|
|
429
|
+
disabled={busy || !dirty}
|
|
430
|
+
onClick={() => void save()}
|
|
431
|
+
data-testid={`artifact-edit-save-${slug}`}
|
|
432
|
+
>
|
|
433
|
+
{busy ? 'Saving…' : 'Save'}
|
|
434
|
+
</Button>
|
|
435
|
+
<Button
|
|
436
|
+
variant="ghost"
|
|
437
|
+
disabled={busy || !dirty}
|
|
438
|
+
onClick={() => {
|
|
439
|
+
setEditKind(payload.kind || 'mdx');
|
|
440
|
+
setEditSource(payload.source ?? payload.planMdx ?? '');
|
|
441
|
+
setError(null);
|
|
442
|
+
}}
|
|
443
|
+
data-testid={`artifact-edit-revert-${slug}`}
|
|
444
|
+
>
|
|
445
|
+
Revert
|
|
446
|
+
</Button>
|
|
447
|
+
</Inline>
|
|
448
|
+
</Stack>
|
|
449
|
+
)}
|
|
450
|
+
</Stack>
|
|
451
|
+
);
|
|
241
452
|
}
|