@ashlr/hub 3.11.5 → 3.13.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/CHANGELOG.md +153 -0
- package/README.md +29 -16
- package/dist/authority-surface.json +91 -45
- package/dist/build-identity.json +2 -2
- package/dist/cli/authority.d.ts +145 -33
- package/dist/cli/authority.js +1196 -313
- package/dist/cli/authority.js.map +1 -1
- package/dist/cli/cloud.js +12 -3
- package/dist/cli/cloud.js.map +1 -1
- package/dist/core/authority/protected-paths.js +11 -0
- package/dist/core/authority/protected-paths.js.map +1 -1
- package/dist/core/authority/resident.d.ts +101 -0
- package/dist/core/authority/resident.js +246 -0
- package/dist/core/authority/resident.js.map +1 -0
- package/dist/core/authority/trust-roots.js +7 -1
- package/dist/core/authority/trust-roots.js.map +1 -1
- package/dist/core/authority/types.d.ts +43 -0
- package/dist/core/authority/types.js +6 -0
- package/dist/core/authority/types.js.map +1 -1
- package/dist/core/cloud/budget.js +11 -1
- package/dist/core/cloud/budget.js.map +1 -1
- package/dist/core/cloud/cloud-api.d.ts +25 -2
- package/dist/core/cloud/cloud-api.js +139 -5
- package/dist/core/cloud/cloud-api.js.map +1 -1
- package/dist/core/cloud/pr-actions.d.ts +93 -0
- package/dist/core/cloud/pr-actions.js +436 -0
- package/dist/core/cloud/pr-actions.js.map +1 -0
- package/dist/core/cloud/pr-preview.d.ts +128 -0
- package/dist/core/cloud/pr-preview.js +228 -0
- package/dist/core/cloud/pr-preview.js.map +1 -0
- package/dist/core/cloud/store.d.ts +2 -0
- package/dist/core/cloud/store.js +28 -0
- package/dist/core/cloud/store.js.map +1 -1
- package/dist/core/cloud/timeline-api.d.ts +24 -0
- package/dist/core/cloud/timeline-api.js +46 -0
- package/dist/core/cloud/timeline-api.js.map +1 -0
- package/dist/core/cloud/timeline-types.d.ts +62 -0
- package/dist/core/cloud/timeline-types.js +36 -0
- package/dist/core/cloud/timeline-types.js.map +1 -0
- package/dist/core/cloud/timeline.d.ts +102 -0
- package/dist/core/cloud/timeline.js +919 -0
- package/dist/core/cloud/timeline.js.map +1 -0
- package/dist/core/cloud/tracker.js +67 -0
- package/dist/core/cloud/tracker.js.map +1 -1
- package/dist/core/cloud/types.d.ts +38 -0
- package/dist/core/cloud/types.js +1 -1
- package/dist/core/cloud/types.js.map +1 -1
- package/dist/core/daemon/service-install-authority.d.ts +10 -2
- package/dist/core/daemon/service-install-authority.js +14 -5
- package/dist/core/daemon/service-install-authority.js.map +1 -1
- package/dist/core/daemon/service.d.ts +15 -0
- package/dist/core/daemon/service.js +26 -0
- package/dist/core/daemon/service.js.map +1 -1
- package/dist/core/fleet/cloud-intake.d.ts +95 -0
- package/dist/core/fleet/cloud-intake.js +557 -0
- package/dist/core/fleet/cloud-intake.js.map +1 -0
- package/dist/core/fleet/dispatch-router.d.ts +13 -0
- package/dist/core/fleet/dispatch-router.js +32 -8
- package/dist/core/fleet/dispatch-router.js.map +1 -1
- package/dist/core/fleet/fleet-merge-state.d.ts +10 -0
- package/dist/core/fleet/fleet-merge-state.js.map +1 -1
- package/dist/core/fleet/host-merge.d.ts +5 -0
- package/dist/core/fleet/host-merge.js +11 -0
- package/dist/core/fleet/host-merge.js.map +1 -1
- package/dist/core/fleet/merge-gates.d.ts +8 -0
- package/dist/core/fleet/merge-gates.js +25 -5
- package/dist/core/fleet/merge-gates.js.map +1 -1
- package/dist/core/fleet/standing-merge-pass.js +30 -0
- package/dist/core/fleet/standing-merge-pass.js.map +1 -1
- package/dist/core/fleet/tick-hooks-live.d.ts +9 -0
- package/dist/core/fleet/tick-hooks-live.js +44 -5
- package/dist/core/fleet/tick-hooks-live.js.map +1 -1
- package/dist/core/fleet/verify-check-run.d.ts +90 -0
- package/dist/core/fleet/verify-check-run.js +274 -0
- package/dist/core/fleet/verify-check-run.js.map +1 -0
- package/dist/core/routing/budget-api.d.ts +11 -0
- package/dist/core/routing/budget-api.js +64 -8
- package/dist/core/routing/budget-api.js.map +1 -1
- package/dist/core/routing/budget-store.d.ts +22 -1
- package/dist/core/routing/budget-store.js +22 -8
- package/dist/core/routing/budget-store.js.map +1 -1
- package/dist/core/universe/builtins/preparation/manifest.json +1 -1
- package/dist/core/universe/builtins/preparation/preparation-bridge.mjs +8 -2
- package/dist/core/universe/builtins/preparation/preparation-verification-fixtures.mjs +7 -1
- package/dist/core/verse/account-health.js +9 -3
- package/dist/core/verse/account-health.js.map +1 -1
- package/dist/core/verse/activity-api.d.ts +5 -1
- package/dist/core/verse/activity-api.js +62 -14
- package/dist/core/verse/activity-api.js.map +1 -1
- package/dist/core/verse/authority-api.d.ts +9 -1
- package/dist/core/verse/authority-api.js +82 -2
- package/dist/core/verse/authority-api.js.map +1 -1
- package/dist/core/verse/seat-readiness.d.ts +25 -0
- package/dist/core/verse/seat-readiness.js +43 -6
- package/dist/core/verse/seat-readiness.js.map +1 -1
- package/dist/core/verse/verse-api.d.ts +3 -1
- package/dist/core/verse/verse-api.js +6 -0
- package/dist/core/verse/verse-api.js.map +1 -1
- package/dist/core/web/public/next/assets/{ActionMenu-DffwPklw.js → ActionMenu-Dwjk6rUQ.js} +2 -2
- package/dist/core/web/public/next/assets/{ActionMenu-DffwPklw.js.map → ActionMenu-Dwjk6rUQ.js.map} +1 -1
- package/dist/core/web/public/next/assets/Advanced-cVYTAsfB.js +2 -0
- package/dist/core/web/public/next/assets/{Advanced-CxzE6ekM.js.map → Advanced-cVYTAsfB.js.map} +1 -1
- package/dist/core/web/public/next/assets/App-IsaWDsdF.js +7 -0
- package/dist/core/web/public/next/assets/{App-DFlp-vdv.js.map → App-IsaWDsdF.js.map} +1 -1
- package/dist/core/web/public/next/assets/AppsSection-DDqeSrty.js +2 -0
- package/dist/core/web/public/next/assets/{AppsSection-Ct2O605-.js.map → AppsSection-DDqeSrty.js.map} +1 -1
- package/dist/core/web/public/next/assets/{AreaTrend-o-GLl2Is.js → AreaTrend-C32rUaxm.js} +2 -2
- package/dist/core/web/public/next/assets/{AreaTrend-o-GLl2Is.js.map → AreaTrend-C32rUaxm.js.map} +1 -1
- package/dist/core/web/public/next/assets/AutonomyOffState-CmSSTHVe.css +1 -0
- package/dist/core/web/public/next/assets/AutonomyOffState-CpFYS3tQ.js +3 -0
- package/dist/core/web/public/next/assets/AutonomyOffState-CpFYS3tQ.js.map +1 -0
- package/dist/core/web/public/next/assets/AutonomySection-Dwn8UtOy.js +2 -0
- package/dist/core/web/public/next/assets/{AutonomySection-BTg7yVa1.js.map → AutonomySection-Dwn8UtOy.js.map} +1 -1
- package/dist/core/web/public/next/assets/{BranchBar-BjPFtGsj.js → BranchBar-ISZ7xUCj.js} +2 -2
- package/dist/core/web/public/next/assets/{BranchBar-BjPFtGsj.js.map → BranchBar-ISZ7xUCj.js.map} +1 -1
- package/dist/core/web/public/next/assets/BudgetControl-BARVp-En.js +2 -0
- package/dist/core/web/public/next/assets/BudgetControl-BARVp-En.js.map +1 -0
- package/dist/core/web/public/next/assets/{Button-OjVJSrFq.js → Button-DtE0rlWD.js} +2 -2
- package/dist/core/web/public/next/assets/{Button-OjVJSrFq.js.map → Button-DtE0rlWD.js.map} +1 -1
- package/dist/core/web/public/next/assets/{CapacityStrip-Bx4c3dxD.js → CapacityStrip-DKbKA_Aa.js} +2 -2
- package/dist/core/web/public/next/assets/{CapacityStrip-Bx4c3dxD.js.map → CapacityStrip-DKbKA_Aa.js.map} +1 -1
- package/dist/core/web/public/next/assets/{ChartFrame-B_w2Siqg.js → ChartFrame-DK_BDXCt.js} +2 -2
- package/dist/core/web/public/next/assets/{ChartFrame-B_w2Siqg.js.map → ChartFrame-DK_BDXCt.js.map} +1 -1
- package/dist/core/web/public/next/assets/ChatSection-C9myY4ei.js +3 -0
- package/dist/core/web/public/next/assets/ChatSection-C9myY4ei.js.map +1 -0
- package/dist/core/web/public/next/assets/{CloudBudgetForm-5Gf4iAok.js → CloudBudgetForm-CvjK4Oh6.js} +2 -2
- package/dist/core/web/public/next/assets/{CloudBudgetForm-5Gf4iAok.js.map → CloudBudgetForm-CvjK4Oh6.js.map} +1 -1
- package/dist/core/web/public/next/assets/{CommandPalette-iTpk6I8r.js → CommandPalette-DzPsth0u.js} +2 -2
- package/dist/core/web/public/next/assets/{CommandPalette-iTpk6I8r.js.map → CommandPalette-DzPsth0u.js.map} +1 -1
- package/dist/core/web/public/next/assets/CommandSection-Brp73iox.js +3 -0
- package/dist/core/web/public/next/assets/CommandSection-Brp73iox.js.map +1 -0
- package/dist/core/web/public/next/assets/{ConfirmDialog-_S9iE-cJ.js → ConfirmDialog-CLUhzaBj.js} +2 -2
- package/dist/core/web/public/next/assets/{ConfirmDialog-_S9iE-cJ.js.map → ConfirmDialog-CLUhzaBj.js.map} +1 -1
- package/dist/core/web/public/next/assets/{DeleteChatDialog-KUYUygiA.js → DeleteChatDialog-C_eDhV8b.js} +2 -2
- package/dist/core/web/public/next/assets/{DeleteChatDialog-KUYUygiA.js.map → DeleteChatDialog-C_eDhV8b.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Dialog-BuIaG6HF.js → Dialog-BRd_B9OE.js} +2 -2
- package/dist/core/web/public/next/assets/{Dialog-BuIaG6HF.js.map → Dialog-BRd_B9OE.js.map} +1 -1
- package/dist/core/web/public/next/assets/{DiffPane-De4IQt82.js → DiffPane-Cpr9soB2.js} +2 -2
- package/dist/core/web/public/next/assets/{DiffPane-De4IQt82.js.map → DiffPane-Cpr9soB2.js.map} +1 -1
- package/dist/core/web/public/next/assets/{DiffViewer-B75t6Ile.js → DiffViewer-CNwd0W3Q.js} +2 -2
- package/dist/core/web/public/next/assets/{DiffViewer-B75t6Ile.js.map → DiffViewer-CNwd0W3Q.js.map} +1 -1
- package/dist/core/web/public/next/assets/DockHost-BcEq2qaS.js +2 -0
- package/dist/core/web/public/next/assets/{DockHost-BPqyhtCP.js.map → DockHost-BcEq2qaS.js.map} +1 -1
- package/dist/core/web/public/next/assets/{EmptyState-DXkT4vhW.js → EmptyState-DsN1H6MC.js} +2 -2
- package/dist/core/web/public/next/assets/{EmptyState-DXkT4vhW.js.map → EmptyState-DsN1H6MC.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Epistemic-CfqFe_aP.js → Epistemic-fycQTBRO.js} +2 -2
- package/dist/core/web/public/next/assets/{Epistemic-CfqFe_aP.js.map → Epistemic-fycQTBRO.js.map} +1 -1
- package/dist/core/web/public/next/assets/EvidenceTimeline-PBDAiSuG.css +1 -0
- package/dist/core/web/public/next/assets/EvidenceTimeline-sCCAdnBK.js +2 -0
- package/dist/core/web/public/next/assets/EvidenceTimeline-sCCAdnBK.js.map +1 -0
- package/dist/core/web/public/next/assets/{FleetSection-B7f-XBiX.js → FleetSection-BG8iGraG.js} +3 -3
- package/dist/core/web/public/next/assets/{FleetSection-B7f-XBiX.js.map → FleetSection-BG8iGraG.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Funnel-pXYySwXQ.js → Funnel-B88_wpA4.js} +2 -2
- package/dist/core/web/public/next/assets/{Funnel-pXYySwXQ.js.map → Funnel-B88_wpA4.js.map} +1 -1
- package/dist/core/web/public/next/assets/{GearTray-gj6lT7ba.js → GearTray-CpDt0xwj.js} +2 -2
- package/dist/core/web/public/next/assets/{GearTray-gj6lT7ba.js.map → GearTray-CpDt0xwj.js.map} +1 -1
- package/dist/core/web/public/next/assets/{GrowthSection-D8e9m9Wy.js → GrowthSection-BvZezOsX.js} +2 -2
- package/dist/core/web/public/next/assets/{GrowthSection-D8e9m9Wy.js.map → GrowthSection-BvZezOsX.js.map} +1 -1
- package/dist/core/web/public/next/assets/{HandoffDialog-VfKxKhxF.js → HandoffDialog-B1G6gQyk.js} +2 -2
- package/dist/core/web/public/next/assets/{HandoffDialog-VfKxKhxF.js.map → HandoffDialog-B1G6gQyk.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Input-u1YR_Ogl.js → Input-CI0yBHmn.js} +2 -2
- package/dist/core/web/public/next/assets/{Input-u1YR_Ogl.js.map → Input-CI0yBHmn.js.map} +1 -1
- package/dist/core/web/public/next/assets/{LeaderCard-BDELBrnx.js → LeaderCard-BxxiEg4e.js} +2 -2
- package/dist/core/web/public/next/assets/{LeaderCard-BDELBrnx.js.map → LeaderCard-BxxiEg4e.js.map} +1 -1
- package/dist/core/web/public/next/assets/{MatrixHeatmap-NKLF2srR.js → MatrixHeatmap-iPFEU2Tv.js} +2 -2
- package/dist/core/web/public/next/assets/{MatrixHeatmap-NKLF2srR.js.map → MatrixHeatmap-iPFEU2Tv.js.map} +1 -1
- package/dist/core/web/public/next/assets/McpSection-nuXer4K-.js +2 -0
- package/dist/core/web/public/next/assets/{McpSection-D1tDvlRa.js.map → McpSection-nuXer4K-.js.map} +1 -1
- package/dist/core/web/public/next/assets/{MessageMarkdown-DlfLEJxu.js → MessageMarkdown-BCe4TsXk.js} +3 -3
- package/dist/core/web/public/next/assets/{MessageMarkdown-DlfLEJxu.js.map → MessageMarkdown-BCe4TsXk.js.map} +1 -1
- package/dist/core/web/public/next/assets/{MessageMarkdownRenderer-BYZzI115.js → MessageMarkdownRenderer-DL3LU5kL.js} +2 -2
- package/dist/core/web/public/next/assets/{MessageMarkdownRenderer-BYZzI115.js.map → MessageMarkdownRenderer-DL3LU5kL.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Meter-BSVhU43Q.js → Meter-b69LM0mU.js} +2 -2
- package/dist/core/web/public/next/assets/{Meter-BSVhU43Q.js.map → Meter-b69LM0mU.js.map} +1 -1
- package/dist/core/web/public/next/assets/{MindSection-lqV2e_Sq.js → MindSection-Dc5v5rH3.js} +2 -2
- package/dist/core/web/public/next/assets/{MindSection-lqV2e_Sq.js.map → MindSection-Dc5v5rH3.js.map} +1 -1
- package/dist/core/web/public/next/assets/{MutationTokenDialog-CLGP72g6.js → MutationTokenDialog-Bm34BNVV.js} +2 -2
- package/dist/core/web/public/next/assets/{MutationTokenDialog-CLGP72g6.js.map → MutationTokenDialog-Bm34BNVV.js.map} +1 -1
- package/dist/core/web/public/next/assets/NeedsYouDrawer-N1cWJjpz.js +3 -0
- package/dist/core/web/public/next/assets/NeedsYouDrawer-N1cWJjpz.js.map +1 -0
- package/dist/core/web/public/next/assets/NeedsYouList-CH6Mq-E_.css +1 -0
- package/dist/core/web/public/next/assets/NeedsYouList-D5Kk549g.js +2 -0
- package/dist/core/web/public/next/assets/{NeedsYouList-eBOlJr0b.js.map → NeedsYouList-D5Kk549g.js.map} +1 -1
- package/dist/core/web/public/next/assets/{NewChatDialog-DoYTH26M.js → NewChatDialog-BqFTWgHU.js} +2 -2
- package/dist/core/web/public/next/assets/{NewChatDialog-DoYTH26M.js.map → NewChatDialog-BqFTWgHU.js.map} +1 -1
- package/dist/core/web/public/next/assets/OnboardingFlow-DcDsguAM.js +3 -0
- package/dist/core/web/public/next/assets/OnboardingFlow-DcDsguAM.js.map +1 -0
- package/dist/core/web/public/next/assets/OnboardingGate-BZafM1Vf.js +3 -0
- package/dist/core/web/public/next/assets/{OnboardingGate-DrDEVBqw.js.map → OnboardingGate-BZafM1Vf.js.map} +1 -1
- package/dist/core/web/public/next/assets/{PreviewPane-ULN673zm.js → PreviewPane-zT3gK9TD.js} +2 -2
- package/dist/core/web/public/next/assets/{PreviewPane-ULN673zm.js.map → PreviewPane-zT3gK9TD.js.map} +1 -1
- package/dist/core/web/public/next/assets/{ProviderLogo-8WaUjTlN.js → ProviderLogo-AwHn2sKm.js} +2 -2
- package/dist/core/web/public/next/assets/{ProviderLogo-8WaUjTlN.js.map → ProviderLogo-AwHn2sKm.js.map} +1 -1
- package/dist/core/web/public/next/assets/{RailStatus-Ckw9nvYU.js → RailStatus-xHrovWvI.js} +2 -2
- package/dist/core/web/public/next/assets/{RailStatus-Ckw9nvYU.js.map → RailStatus-xHrovWvI.js.map} +1 -1
- package/dist/core/web/public/next/assets/{RefreshIndicator-DGubF4vR.js → RefreshIndicator-SEFZlMB2.js} +2 -2
- package/dist/core/web/public/next/assets/{RefreshIndicator-DGubF4vR.js.map → RefreshIndicator-SEFZlMB2.js.map} +1 -1
- package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-BahEaY4d.js → ResourcePoolConsoleApp-CAxPQ4sZ.js} +3 -3
- package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-BahEaY4d.js.map → ResourcePoolConsoleApp-CAxPQ4sZ.js.map} +1 -1
- package/dist/core/web/public/next/assets/ResourcesChrome-BnhnEdJe.js +3 -0
- package/dist/core/web/public/next/assets/{ResourcesChrome-C2ZrA7la.js.map → ResourcesChrome-BnhnEdJe.js.map} +1 -1
- package/dist/core/web/public/next/assets/{ResourcesDrawer-BC93fzwQ.js → ResourcesDrawer-BAFGodny.js} +2 -2
- package/dist/core/web/public/next/assets/{ResourcesDrawer-BC93fzwQ.js.map → ResourcesDrawer-BAFGodny.js.map} +1 -1
- package/dist/core/web/public/next/assets/{RouteErrorBoundary-BvhHNpE_.js → RouteErrorBoundary-C8WGiixc.js} +2 -2
- package/dist/core/web/public/next/assets/{RouteErrorBoundary-BvhHNpE_.js.map → RouteErrorBoundary-C8WGiixc.js.map} +1 -1
- package/dist/core/web/public/next/assets/{RunInCloudAction-Dywfnw0C.js → RunInCloudAction-1-IPLYhf.js} +2 -2
- package/dist/core/web/public/next/assets/{RunInCloudAction-Dywfnw0C.js.map → RunInCloudAction-1-IPLYhf.js.map} +1 -1
- package/dist/core/web/public/next/assets/{SeatSelector-CH7hTgwy.js → SeatSelector-DpwNiX8F.js} +2 -2
- package/dist/core/web/public/next/assets/{SeatSelector-CH7hTgwy.js.map → SeatSelector-DpwNiX8F.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Segmented-Wynbg0F5.js → Segmented-Wt1ATod9.js} +2 -2
- package/dist/core/web/public/next/assets/{Segmented-Wynbg0F5.js.map → Segmented-Wt1ATod9.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Select-DMyLCkId.js → Select-By-V9lMc.js} +2 -2
- package/dist/core/web/public/next/assets/{Select-DMyLCkId.js.map → Select-By-V9lMc.js.map} +1 -1
- package/dist/core/web/public/next/assets/{SessionGate-8RtQFWeZ.js → SessionGate-WROS02tK.js} +2 -2
- package/dist/core/web/public/next/assets/{SessionGate-8RtQFWeZ.js.map → SessionGate-WROS02tK.js.map} +1 -1
- package/dist/core/web/public/next/assets/{SessionInsightChip-zBJbKv2L.js → SessionInsightChip-DNUcruwp.js} +2 -2
- package/dist/core/web/public/next/assets/{SessionInsightChip-zBJbKv2L.js.map → SessionInsightChip-DNUcruwp.js.map} +1 -1
- package/dist/core/web/public/next/assets/{SessionSearch-DfJIcxxM.js → SessionSearch-UHtt7a8f.js} +2 -2
- package/dist/core/web/public/next/assets/{SessionSearch-DfJIcxxM.js.map → SessionSearch-UHtt7a8f.js.map} +1 -1
- package/dist/core/web/public/next/assets/{SettingsSection-Da9RYyH5.js → SettingsSection-Bf74l0Gj.js} +2 -2
- package/dist/core/web/public/next/assets/{SettingsSection-Da9RYyH5.js.map → SettingsSection-Bf74l0Gj.js.map} +1 -1
- package/dist/core/web/public/next/assets/SetupChecklist-BhZpFLsj.css +1 -0
- package/dist/core/web/public/next/assets/SetupChecklist-CzpEcfOl.js +2 -0
- package/dist/core/web/public/next/assets/SetupChecklist-CzpEcfOl.js.map +1 -0
- package/dist/core/web/public/next/assets/{Sheet-DhXyIDmq.js → Sheet-HkBOBMTT.js} +2 -2
- package/dist/core/web/public/next/assets/{Sheet-DhXyIDmq.js.map → Sheet-HkBOBMTT.js.map} +1 -1
- package/dist/core/web/public/next/assets/{ShortcutsOverlay-Bi5f-wUw.js → ShortcutsOverlay-DiigiT8H.js} +2 -2
- package/dist/core/web/public/next/assets/{ShortcutsOverlay-Bi5f-wUw.js.map → ShortcutsOverlay-DiigiT8H.js.map} +1 -1
- package/dist/core/web/public/next/assets/Sidebar-C9YtBYVZ.js +3 -0
- package/dist/core/web/public/next/assets/Sidebar-C9YtBYVZ.js.map +1 -0
- package/dist/core/web/public/next/assets/{Slider-Bd9YFWEV.js → Slider-DaEDws-b.js} +2 -2
- package/dist/core/web/public/next/assets/{Slider-Bd9YFWEV.js.map → Slider-DaEDws-b.js.map} +1 -1
- package/dist/core/web/public/next/assets/{StatTile-DScTd3c-.js → StatTile-CTX41O6y.js} +2 -2
- package/dist/core/web/public/next/assets/{StatTile-DScTd3c-.js.map → StatTile-CTX41O6y.js.map} +1 -1
- package/dist/core/web/public/next/assets/{StatusBadge-DQ1UtQAJ.js → StatusBadge-CXG3iNL4.js} +2 -2
- package/dist/core/web/public/next/assets/{StatusBadge-DQ1UtQAJ.js.map → StatusBadge-CXG3iNL4.js.map} +1 -1
- package/dist/core/web/public/next/assets/{StepBand-BB3IJxhr.js → StepBand-jJTqYIdX.js} +2 -2
- package/dist/core/web/public/next/assets/{StepBand-BB3IJxhr.js.map → StepBand-jJTqYIdX.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Surface-DWMtQe3Q.js → Surface-5bNA08tK.js} +2 -2
- package/dist/core/web/public/next/assets/{Surface-DWMtQe3Q.js.map → Surface-5bNA08tK.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Swimlane-DcENqtdp.js → Swimlane-LrslsGxw.js} +2 -2
- package/dist/core/web/public/next/assets/{Swimlane-DcENqtdp.js.map → Swimlane-LrslsGxw.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Switch-CenChEYf.js → Switch-BmCzfFnu.js} +2 -2
- package/dist/core/web/public/next/assets/{Switch-CenChEYf.js.map → Switch-BmCzfFnu.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Tag-D4k5E3u0.js → Tag-WSUIaMVy.js} +2 -2
- package/dist/core/web/public/next/assets/{Tag-D4k5E3u0.js.map → Tag-WSUIaMVy.js.map} +1 -1
- package/dist/core/web/public/next/assets/{TerminalPane-DCQTukcE.js → TerminalPane-BVFgYX6K.js} +5 -5
- package/dist/core/web/public/next/assets/{TerminalPane-DCQTukcE.js.map → TerminalPane-BVFgYX6K.js.map} +1 -1
- package/dist/core/web/public/next/assets/{UniverseConsoleApp-DsuWT0pi.js → UniverseConsoleApp-BpphLqoz.js} +2 -2
- package/dist/core/web/public/next/assets/{UniverseConsoleApp-DsuWT0pi.js.map → UniverseConsoleApp-BpphLqoz.js.map} +1 -1
- package/dist/core/web/public/next/assets/{UniverseView-DyI5ApsA.js → UniverseView-DbpbSMwO.js} +4 -4
- package/dist/core/web/public/next/assets/{UniverseView-DyI5ApsA.js.map → UniverseView-DbpbSMwO.js.map} +1 -1
- package/dist/core/web/public/next/assets/{UsageSection-Imr28nwK.js → UsageSection-CUlaKtws.js} +3 -3
- package/dist/core/web/public/next/assets/{UsageSection-Imr28nwK.js.map → UsageSection-CUlaKtws.js.map} +1 -1
- package/dist/core/web/public/next/assets/VerseConsoleApp-DXDv1aOi.js +3 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-DXDv1aOi.js.map +1 -0
- package/dist/core/web/public/next/assets/{Workspace-CpxBO98E.js → Workspace-RXy0pqp6.js} +5 -5
- package/dist/core/web/public/next/assets/{Workspace-CpxBO98E.js.map → Workspace-RXy0pqp6.js.map} +1 -1
- package/dist/core/web/public/next/assets/{actions-DE8NoWV_.js → actions-BZTidCXZ.js} +2 -2
- package/dist/core/web/public/next/assets/{actions-DE8NoWV_.js.map → actions-BZTidCXZ.js.map} +1 -1
- package/dist/core/web/public/next/assets/{apps-model-CFGCUu9p.js → apps-model-CHO133qt.js} +2 -2
- package/dist/core/web/public/next/assets/{apps-model-CFGCUu9p.js.map → apps-model-CHO133qt.js.map} +1 -1
- package/dist/core/web/public/next/assets/{authority-model-CmnfJzmM.js → authority-model-8NeAEwAF.js} +2 -2
- package/dist/core/web/public/next/assets/{authority-model-CmnfJzmM.js.map → authority-model-8NeAEwAF.js.map} +1 -1
- package/dist/core/web/public/next/assets/{budget-queries-DjrPRFwl.js → budget-queries-DFF-3ICg.js} +2 -2
- package/dist/core/web/public/next/assets/{budget-queries-DjrPRFwl.js.map → budget-queries-DFF-3ICg.js.map} +1 -1
- package/dist/core/web/public/next/assets/{capacity-strip-model-C7cdQnuK.js → capacity-strip-model-5ViUgUlP.js} +2 -2
- package/dist/core/web/public/next/assets/{capacity-strip-model-C7cdQnuK.js.map → capacity-strip-model-5ViUgUlP.js.map} +1 -1
- package/dist/core/web/public/next/assets/{charts-DUD_jprE.js → charts-CzxWrZn7.js} +2 -2
- package/dist/core/web/public/next/assets/{charts-DUD_jprE.js.map → charts-CzxWrZn7.js.map} +1 -1
- package/dist/core/web/public/next/assets/cloud-model-CfRydq8N.js +2 -0
- package/dist/core/web/public/next/assets/{cloud-model-DoxBMDvx.js.map → cloud-model-CfRydq8N.js.map} +1 -1
- package/dist/core/web/public/next/assets/{cloud-queries-CTGvO-k-.js → cloud-queries-DmptbjS8.js} +2 -2
- package/dist/core/web/public/next/assets/{cloud-queries-CTGvO-k-.js.map → cloud-queries-DmptbjS8.js.map} +1 -1
- package/dist/core/web/public/next/assets/{command-catalog-CmrHNFzh.js → command-catalog-ffCAijVY.js} +2 -2
- package/dist/core/web/public/next/assets/{command-catalog-CmrHNFzh.js.map → command-catalog-ffCAijVY.js.map} +1 -1
- package/dist/core/web/public/next/assets/{context-model-2Owp2jYF.js → context-model-Bb15chFt.js} +2 -2
- package/dist/core/web/public/next/assets/{context-model-2Owp2jYF.js.map → context-model-Bb15chFt.js.map} +1 -1
- package/dist/core/web/public/next/assets/{context-model-9xywbXxX.js → context-model-CDeyP0cA.js} +2 -2
- package/dist/core/web/public/next/assets/{context-model-9xywbXxX.js.map → context-model-CDeyP0cA.js.map} +1 -1
- package/dist/core/web/public/next/assets/{context-queries-wvcRnV-I.js → context-queries-_9bFWb9v.js} +2 -2
- package/dist/core/web/public/next/assets/{context-queries-wvcRnV-I.js.map → context-queries-_9bFWb9v.js.map} +1 -1
- package/dist/core/web/public/next/assets/{control-queries-DB6Kh7UN.js → control-queries-CXtmZZH6.js} +2 -2
- package/dist/core/web/public/next/assets/{control-queries-DB6Kh7UN.js.map → control-queries-CXtmZZH6.js.map} +1 -1
- package/dist/core/web/public/next/assets/copy-setup-BIp0iaiZ.js +2 -0
- package/dist/core/web/public/next/assets/copy-setup-BIp0iaiZ.js.map +1 -0
- package/dist/core/web/public/next/assets/{dock-icons-BBUGl4tE.js → dock-icons-Cc43W7r4.js} +2 -2
- package/dist/core/web/public/next/assets/{dock-icons-BBUGl4tE.js.map → dock-icons-Cc43W7r4.js.map} +1 -1
- package/dist/core/web/public/next/assets/{fleet-model-BRfRWkZl.js → fleet-model-DOK8ZtEt.js} +2 -2
- package/dist/core/web/public/next/assets/{fleet-model-BRfRWkZl.js.map → fleet-model-DOK8ZtEt.js.map} +1 -1
- package/dist/core/web/public/next/assets/{fleet-queries-tU6UEXN2.js → fleet-queries-BeEAqD6y.js} +2 -2
- package/dist/core/web/public/next/assets/{fleet-queries-tU6UEXN2.js.map → fleet-queries-BeEAqD6y.js.map} +1 -1
- package/dist/core/web/public/next/assets/{focus-trap-BGOceFvj.js → focus-trap-ChZ5EFfM.js} +2 -2
- package/dist/core/web/public/next/assets/{focus-trap-BGOceFvj.js.map → focus-trap-ChZ5EFfM.js.map} +1 -1
- package/dist/core/web/public/next/assets/format-BmH2yWL6.js +2 -0
- package/dist/core/web/public/next/assets/format-BmH2yWL6.js.map +1 -0
- package/dist/core/web/public/next/assets/{git-model-53iRLInY.js → git-model-BFwAVbxF.js} +2 -2
- package/dist/core/web/public/next/assets/{git-model-53iRLInY.js.map → git-model-BFwAVbxF.js.map} +1 -1
- package/dist/core/web/public/next/assets/global-y_exIyAP.js +2 -0
- package/dist/core/web/public/next/assets/{global-CXHHMz9o.js.map → global-y_exIyAP.js.map} +1 -1
- package/dist/core/web/public/next/assets/{guarded-runners-B8zFVBVZ.js → guarded-runners-Cd4POBtI.js} +2 -2
- package/dist/core/web/public/next/assets/{guarded-runners-B8zFVBVZ.js.map → guarded-runners-Cd4POBtI.js.map} +1 -1
- package/dist/core/web/public/next/assets/{icons-D4s-jlxX.js → icons-CygCEFWs.js} +2 -2
- package/dist/core/web/public/next/assets/{icons-D4s-jlxX.js.map → icons-CygCEFWs.js.map} +1 -1
- package/dist/core/web/public/next/assets/{index-BG177cPN.js → index-CyK31YRm.js} +3 -3
- package/dist/core/web/public/next/assets/{index-BG177cPN.js.map → index-CyK31YRm.js.map} +1 -1
- package/dist/core/web/public/next/assets/{live-model-CEf01wrW.js → live-model-D7hilYFk.js} +2 -2
- package/dist/core/web/public/next/assets/live-model-D7hilYFk.js.map +1 -0
- package/dist/core/web/public/next/assets/{mind.module-D_xk6HIQ.js → mind.module-B15k0Y9q.js} +2 -2
- package/dist/core/web/public/next/assets/{mind.module-D_xk6HIQ.js.map → mind.module-B15k0Y9q.js.map} +1 -1
- package/dist/core/web/public/next/assets/mutations-CWlxEMEm.js +2 -0
- package/dist/core/web/public/next/assets/{mutations-CbTSofVH.js.map → mutations-CWlxEMEm.js.map} +1 -1
- package/dist/core/web/public/next/assets/{nav-CmZcbPFJ.js → nav-mimleI0R.js} +2 -2
- package/dist/core/web/public/next/assets/{nav-CmZcbPFJ.js.map → nav-mimleI0R.js.map} +1 -1
- package/dist/core/web/public/next/assets/{overnight-queries-CR0b_j-V.js → overnight-queries-DJ4mZKSj.js} +2 -2
- package/dist/core/web/public/next/assets/{overnight-queries-CR0b_j-V.js.map → overnight-queries-DJ4mZKSj.js.map} +1 -1
- package/dist/core/web/public/next/assets/primitives-DMijqcu0.js +1 -0
- package/dist/core/web/public/next/assets/{queries-rvRsAGW2.js → queries-Bw1McHTj.js} +2 -2
- package/dist/core/web/public/next/assets/{queries-rvRsAGW2.js.map → queries-Bw1McHTj.js.map} +1 -1
- package/dist/core/web/public/next/assets/{reasoning-pref-DMPQkujg.js → reasoning-pref-KkhI_5-j.js} +2 -2
- package/dist/core/web/public/next/assets/{reasoning-pref-DMPQkujg.js.map → reasoning-pref-KkhI_5-j.js.map} +1 -1
- package/dist/core/web/public/next/assets/{reveal-anchor-BKk2wZZZ.js → reveal-anchor-CCCx_kfT.js} +2 -2
- package/dist/core/web/public/next/assets/{reveal-anchor-BKk2wZZZ.js.map → reveal-anchor-CCCx_kfT.js.map} +1 -1
- package/dist/core/web/public/next/assets/{seat-subscription-CLn2ReSZ.js → seat-subscription-BhUUz7VL.js} +2 -2
- package/dist/core/web/public/next/assets/{seat-subscription-CLn2ReSZ.js.map → seat-subscription-BhUUz7VL.js.map} +1 -1
- package/dist/core/web/public/next/assets/{sidebar-model-CjQLvdag.js → sidebar-model-Dcd437cf.js} +2 -2
- package/dist/core/web/public/next/assets/{sidebar-model-CjQLvdag.js.map → sidebar-model-Dcd437cf.js.map} +1 -1
- package/dist/core/web/public/next/assets/{slots-DRxNosZg.js → slots-BL_J5mmV.js} +3 -3
- package/dist/core/web/public/next/assets/{slots-DRxNosZg.js.map → slots-BL_J5mmV.js.map} +1 -1
- package/dist/core/web/public/next/assets/{surface-data-DshUC_TU.js → surface-data-C3UAQ4QX.js} +2 -2
- package/dist/core/web/public/next/assets/{surface-data-DshUC_TU.js.map → surface-data-C3UAQ4QX.js.map} +1 -1
- package/dist/core/web/public/next/assets/surface-prefetch-BXKjPsV5.js +2 -0
- package/dist/core/web/public/next/assets/{surface-prefetch-DO8r4ttQ.js.map → surface-prefetch-BXKjPsV5.js.map} +1 -1
- package/dist/core/web/public/next/assets/{tasks-model-BKPFZAmZ.js → tasks-model-CzHvclVm.js} +2 -2
- package/dist/core/web/public/next/assets/{tasks-model-BKPFZAmZ.js.map → tasks-model-CzHvclVm.js.map} +1 -1
- package/dist/core/web/public/next/assets/{terminal-icons-DoeQkP3y.js → terminal-icons-BwgQTQP0.js} +2 -2
- package/dist/core/web/public/next/assets/{terminal-icons-DoeQkP3y.js.map → terminal-icons-BwgQTQP0.js.map} +1 -1
- package/dist/core/web/public/next/assets/types-IYKrYLku.js +2 -0
- package/dist/core/web/public/next/assets/types-IYKrYLku.js.map +1 -0
- package/dist/core/web/public/next/assets/{usage-queries-MnUeYzwp.js → usage-queries-pXxvy54x.js} +2 -2
- package/dist/core/web/public/next/assets/{usage-queries-MnUeYzwp.js.map → usage-queries-pXxvy54x.js.map} +1 -1
- package/dist/core/web/public/next/assets/{use-guarded-action-P12MfNwU.js → use-guarded-action-EgVWjM5c.js} +2 -2
- package/dist/core/web/public/next/assets/{use-guarded-action-P12MfNwU.js.map → use-guarded-action-EgVWjM5c.js.map} +1 -1
- package/dist/core/web/public/next/assets/{use-ticker-DIvAZ4Ey.js → use-ticker-BoqFxRFG.js} +2 -2
- package/dist/core/web/public/next/assets/{use-ticker-DIvAZ4Ey.js.map → use-ticker-BoqFxRFG.js.map} +1 -1
- package/dist/core/web/public/next/assets/{use-token-gate-DfEja7Wr.js → use-token-gate-DSNIIpI6.js} +2 -2
- package/dist/core/web/public/next/assets/{use-token-gate-DfEja7Wr.js.map → use-token-gate-DSNIIpI6.js.map} +1 -1
- package/dist/core/web/public/next/assets/{useOnboarding-CkOHadqC.js → useOnboarding-Bk0VnkxQ.js} +2 -2
- package/dist/core/web/public/next/assets/{useOnboarding-CkOHadqC.js.map → useOnboarding-Bk0VnkxQ.js.map} +1 -1
- package/dist/core/web/public/next/assets/{useSeatsRefresh-BDlbDlIU.js → useSeatsRefresh-q7Id9RcW.js} +2 -2
- package/dist/core/web/public/next/assets/{useSeatsRefresh-BDlbDlIU.js.map → useSeatsRefresh-q7Id9RcW.js.map} +1 -1
- package/dist/core/web/public/next/assets/{useVerseTranscript-BGtA55k2.js → useVerseTranscript-CqH51rt9.js} +2 -2
- package/dist/core/web/public/next/assets/{useVerseTranscript-BGtA55k2.js.map → useVerseTranscript-CqH51rt9.js.map} +1 -1
- package/dist/core/web/public/next/assets/{verse-icons-wOet9JZy.js → verse-icons-DsG1dOQx.js} +2 -2
- package/dist/core/web/public/next/assets/{verse-icons-wOet9JZy.js.map → verse-icons-DsG1dOQx.js.map} +1 -1
- package/dist/core/web/public/next/assets/{verse-model-CSzzdyu2.js → verse-model-JTuvRBVK.js} +2 -2
- package/dist/core/web/public/next/assets/{verse-model-CSzzdyu2.js.map → verse-model-JTuvRBVK.js.map} +1 -1
- package/dist/core/web/public/next/assets/{warmup-CecO_Nk0.js → warmup-Bv1RumVq.js} +3 -3
- package/dist/core/web/public/next/assets/{warmup-CecO_Nk0.js.map → warmup-Bv1RumVq.js.map} +1 -1
- package/dist/core/web/public/next/assets/why-seat-model-Cau8qG6O.js +2 -0
- package/dist/core/web/public/next/assets/why-seat-model-Cau8qG6O.js.map +1 -0
- package/dist/core/web/public/next/assets/{workspace-model-BdIVybKr.js → workspace-model-D-SLfads.js} +2 -2
- package/dist/core/web/public/next/assets/{workspace-model-BdIVybKr.js.map → workspace-model-D-SLfads.js.map} +1 -1
- package/dist/core/web/public/next/index.html +1 -1
- package/dist/release-dependency-inventory.json +1 -1
- package/docs/AUTONOMY-GAP.md +1 -1
- package/package.json +1 -1
- package/dist/core/web/public/next/assets/Advanced-CxzE6ekM.js +0 -2
- package/dist/core/web/public/next/assets/App-DFlp-vdv.js +0 -7
- package/dist/core/web/public/next/assets/AppsSection-Ct2O605-.js +0 -2
- package/dist/core/web/public/next/assets/AutonomyOffState-CXhXpsim.js +0 -2
- package/dist/core/web/public/next/assets/AutonomyOffState-CXhXpsim.js.map +0 -1
- package/dist/core/web/public/next/assets/AutonomyOffState-DmbE71vI.css +0 -1
- package/dist/core/web/public/next/assets/AutonomySection-BTg7yVa1.js +0 -2
- package/dist/core/web/public/next/assets/BudgetControl-ng6swoZp.js +0 -2
- package/dist/core/web/public/next/assets/BudgetControl-ng6swoZp.js.map +0 -1
- package/dist/core/web/public/next/assets/ChatSection-Bisj9R6E.js +0 -3
- package/dist/core/web/public/next/assets/ChatSection-Bisj9R6E.js.map +0 -1
- package/dist/core/web/public/next/assets/CommandSection-D8ZihzvE.js +0 -2
- package/dist/core/web/public/next/assets/CommandSection-D8ZihzvE.js.map +0 -1
- package/dist/core/web/public/next/assets/DockHost-BPqyhtCP.js +0 -2
- package/dist/core/web/public/next/assets/McpSection-D1tDvlRa.js +0 -2
- package/dist/core/web/public/next/assets/NeedsYouDrawer-D992XTOn.js +0 -2
- package/dist/core/web/public/next/assets/NeedsYouDrawer-D992XTOn.js.map +0 -1
- package/dist/core/web/public/next/assets/NeedsYouList-CruOt4JT.css +0 -1
- package/dist/core/web/public/next/assets/NeedsYouList-eBOlJr0b.js +0 -2
- package/dist/core/web/public/next/assets/OnboardingFlow-D3yfITK1.js +0 -2
- package/dist/core/web/public/next/assets/OnboardingFlow-D3yfITK1.js.map +0 -1
- package/dist/core/web/public/next/assets/OnboardingGate-DrDEVBqw.js +0 -3
- package/dist/core/web/public/next/assets/ResourcesChrome-C2ZrA7la.js +0 -3
- package/dist/core/web/public/next/assets/Sidebar-BuO3sViG.js +0 -3
- package/dist/core/web/public/next/assets/Sidebar-BuO3sViG.js.map +0 -1
- package/dist/core/web/public/next/assets/VerseConsoleApp-DeuBEvAO.js +0 -3
- package/dist/core/web/public/next/assets/VerseConsoleApp-DeuBEvAO.js.map +0 -1
- package/dist/core/web/public/next/assets/cloud-model-DoxBMDvx.js +0 -2
- package/dist/core/web/public/next/assets/copy-setup-CJQhFl-4.js +0 -2
- package/dist/core/web/public/next/assets/copy-setup-CJQhFl-4.js.map +0 -1
- package/dist/core/web/public/next/assets/format-DEAaVF6X.js +0 -2
- package/dist/core/web/public/next/assets/format-DEAaVF6X.js.map +0 -1
- package/dist/core/web/public/next/assets/global-CXHHMz9o.js +0 -2
- package/dist/core/web/public/next/assets/live-model-CEf01wrW.js.map +0 -1
- package/dist/core/web/public/next/assets/mutations-CbTSofVH.js +0 -2
- package/dist/core/web/public/next/assets/primitives-DNI-uJBd.js +0 -1
- package/dist/core/web/public/next/assets/surface-prefetch-DO8r4ttQ.js +0 -2
- package/dist/core/web/public/next/assets/types-D5XOdyw8.js +0 -2
- package/dist/core/web/public/next/assets/types-D5XOdyw8.js.map +0 -1
- package/dist/core/web/public/next/assets/why-seat-model-D68ZA7G8.js +0 -2
- package/dist/core/web/public/next/assets/why-seat-model-D68ZA7G8.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"global-CXHHMz9o.js","names":[],"sources":["../../../../../../src/web-ui/data/cache.ts","../../../../../../src/web-ui/routes/verse/chat/composer-memory.ts","../../../../../../src/web-ui/data/auth-store.ts","../../../../../../src/web-ui/data/sse.ts","../../../../../../src/web-ui/data/theme-store.ts","../../../../../../src/web-ui/data/appearance-store.ts","../../../../../../src/web-ui/data/hooks.ts","../../../../../../src/web-ui/data/client.ts","../../../../../../src/web-ui/components/primitives/Skeleton.module.css","../../../../../../src/web-ui/components/primitives/Skeleton.tsx","../../../../../../src/web-ui/app/SkipToContent.module.css","../../../../../../src/web-ui/app/SkipToContent.tsx"],"sourcesContent":["/**\n * data/cache.ts — the resource cache every view reads through. This is what\n * makes tab switches instant instead of blanking to \"Loading…\": a resource\n * fetched once stays in the cache and renders immediately (possibly stale)\n * on the next mount, with a background refresh kicked off silently behind\n * it. No framework dependency — a plain keyed store consumed via\n * useSyncExternalStore (see hooks.ts), matching the pattern already used\n * for auth-store.ts.\n *\n * Design choices, spelled out because a future agent will extend this:\n * - In-flight de-dupe: two components requesting the same key at once\n * share one fetch, not two.\n * - Never clears `data` on refetch. `status` distinguishes 'loading'\n * (no data yet — show a skeleton) from 'refreshing' (stale data still\n * shown — show a subtle indicator) from 'success'/'error'.\n * - `invalidate(key)` is how the SSE layer (sse.ts) tells the cache \"the\n * server just told us this changed\" without either module importing\n * React.\n * - useSyncExternalStore requires getSnapshot() to return a NEW object\n * reference whenever the observable state actually changes (it detects\n * updates via Object.is, not deep equality) — mutating one long-lived\n * entry object in place and handing back the same reference would fire\n * subscribers that never actually re-render. Every state transition\n * below therefore replaces `bookkeeping.snapshot` wholesale instead of\n * patching fields on it.\n *\n * ---------------------------------------------------------------------------\n * V2.1 — the request burst, and the three defects that lived in this file\n * ---------------------------------------------------------------------------\n *\n * Mounting the Usage section fires SEVEN reads at once (accounts, control,\n * bootstrap, usage-series, local-models, /api/control, /api/usage), several\n * of which spawn short-lived probe processes server-side. That burst starved\n * the 2s local-runtime probe timeout and made a healthy Ollama report as\n * unreachable roughly one burst in ten. The probe timeouts were hardened\n * server-side; the burst itself was still this file's problem. Three changes:\n *\n * 1. CONCURRENCY GATE. Every fetch this cache issues passes through a\n * process-wide gate capped at QUERY_CONCURRENCY_LIMIT. The same seven\n * reads still happen, but at most four are outstanding at once, so the\n * server is never asked to run seven probe-spawning handlers in\n * parallel. Nothing is dropped, nothing is delayed by a timer — a\n * queued task starts the microtask a slot frees. The gate is what makes\n * the probe timeout survivable, and it costs the caller nothing: the\n * browser already caps at ~6 sockets per origin, so this is a cap the\n * requests were going to hit anyway, applied where we can order it.\n *\n * 2. FRESHNESS. `ensureQuery(key, fetcher, maxAgeMs)` is the MOUNT path: it\n * skips the request entirely when the cached value is younger than\n * `maxAgeMs`. Re-entering a section you were just in is now zero\n * requests instead of seven. `runQuery` keeps its old always-fetch\n * semantics because the SSE layer and the prefetchers depend on it.\n *\n * 3. REFRESH THAT ACTUALLY RE-READS. `runQuery`'s in-flight de-dupe is\n * right for two components mounting at once and WRONG for a person\n * pressing Refresh: a click landing while a read is in flight joined\n * the running promise and returned data fetched before the click — a\n * silent no-op, and with local-models taking seconds under load the\n * window was real. `refetchQuery` fixes it in two steps, because the\n * same hook backs both a person's button and a 3s poll loop:\n * - by default it joins an in-flight read ONLY while that read is\n * younger than REFETCH_JOIN_WINDOW_MS. A poll tick landing on a\n * read that started milliseconds ago still coalesces (which is the\n * documented contract UniverseView's poll relies on); a click\n * landing on a read that started seconds ago issues a new request.\n * - with `force` it never joins at all. That is the strict guarantee\n * for an explicit user-initiated refresh (hooks.ts `useRefresh`).\n * Either way a newer request SUPERSEDES an older one by sequence\n * number, so the last read ISSUED is the one whose result is kept even\n * if an earlier one lands later.\n *\n * And the latent one: `evictAll()` used to call `store.clear()`, which\n * dropped the Bookkeeping objects that mounted components had registered\n * their `useSyncExternalStore` listeners on. Those components stayed\n * subscribed to an orphan Set that nothing would ever notify again — they\n * would never re-render for the rest of their life. Evict now RESETS an\n * entry that still has subscribers (and deletes only the unobserved ones),\n * so a subscriber always survives an evict.\n */\n\nexport type QueryStatus = 'idle' | 'loading' | 'refreshing' | 'success' | 'error';\n\nexport interface QueryEntry<T> {\n data: T | undefined;\n error: Error | undefined;\n status: QueryStatus;\n updatedAt: number | null;\n}\n\ninterface Bookkeeping<T = unknown> {\n snapshot: QueryEntry<T>;\n inFlight: Promise<void> | null;\n fetcher: (() => Promise<T>) | null;\n subscribers: Set<() => void>;\n /** Sequence number of the newest request ISSUED for this key. */\n issued: number;\n /** `Date.now()` when that newest request was issued. */\n issuedAt: number;\n /** Highest sequence number whose result has been written to `snapshot`. */\n applied: number;\n}\n\nconst IDLE_SNAPSHOT: QueryEntry<unknown> = { data: undefined, error: undefined, status: 'idle', updatedAt: null };\n\nconst store = new Map<string, Bookkeeping>();\n\n// ---------------------------------------------------------------------------\n// Concurrency gate\n// ---------------------------------------------------------------------------\n\n/**\n * How many cache-issued fetches may be outstanding at once, process-wide.\n *\n * Four, not one: the point is to stop a seven-way burst from spawning seven\n * concurrent server-side probes, not to serialize the app. Four keeps a\n * section's first paint fast while leaving the server two of its six sockets\n * for the SSE stream and any non-cache request.\n */\nexport const QUERY_CONCURRENCY_LIMIT = 4;\n\nlet activeFetches = 0;\nconst gateQueue: (() => void)[] = [];\n/** Peak concurrency observed since the gate was last reset. */\nlet peakConcurrency = 0;\n/**\n * Bumped by `resetQueryGate`. A fetch issued under an older generation no\n * longer owns a slot, so its eventual settlement must not decrement the new\n * generation's count — otherwise a request that never settles (a hung\n * socket, or a test stubbing fetch with a promise that never resolves) would\n * permanently consume capacity and starve every later read.\n */\nlet gateGeneration = 0;\n\nfunction releaseSlot(): void {\n activeFetches -= 1;\n const next = gateQueue.shift();\n if (next) next();\n}\n\n/**\n * Run `task` when a slot is free. Purely promise-driven — no timers, so this\n * is safe under fake timers and adds no latency beyond one microtask.\n */\nfunction schedule<T>(task: () => Promise<T>): Promise<T> {\n const start = (): Promise<T> => {\n const generation = gateGeneration;\n activeFetches += 1;\n if (activeFetches > peakConcurrency) peakConcurrency = activeFetches;\n const done = (): void => {\n if (generation === gateGeneration) releaseSlot();\n };\n let running: Promise<T>;\n try {\n running = task();\n } catch (err) {\n // A fetcher that throws synchronously must still free its slot.\n done();\n return Promise.reject(err instanceof Error ? err : new Error(String(err)));\n }\n return running.then(\n (value) => {\n done();\n return value;\n },\n (err: unknown) => {\n done();\n throw err;\n },\n );\n };\n\n if (activeFetches < QUERY_CONCURRENCY_LIMIT) return start();\n return new Promise<T>((resolve, reject) => {\n gateQueue.push(() => {\n start().then(resolve, reject);\n });\n });\n}\n\n/**\n * Abandon the gate's accounting: forget how many fetches are outstanding and\n * let everything queued run now. Called from `evictAll()`, which is both the\n * logout path and every test's `beforeEach` — a read that outlives the state\n * it was issued for has no claim on the next one's capacity.\n *\n * Nothing is cancelled. Requests already in flight still settle and still\n * write their result unless the key was evicted underneath them.\n */\nexport function resetQueryGate(): void {\n gateGeneration += 1;\n activeFetches = 0;\n peakConcurrency = 0;\n for (const start of gateQueue.splice(0)) start();\n}\n\n/**\n * What the gate is doing right now. The tests read it, and so does the shell's\n * idle warm-up (routes/verse/shell/warmup.ts): it starts nothing while any\n * read is running or queued here.\n */\nexport function queryGateStats(): { active: number; queued: number; peak: number } {\n return { active: activeFetches, queued: gateQueue.length, peak: peakConcurrency };\n}\n\n// ---------------------------------------------------------------------------\n// Entries\n// ---------------------------------------------------------------------------\n\nfunction entryFor<T>(key: string): Bookkeeping<T> {\n let e = store.get(key) as Bookkeeping<T> | undefined;\n if (!e) {\n e = {\n snapshot: IDLE_SNAPSHOT as QueryEntry<T>,\n inFlight: null,\n fetcher: null,\n subscribers: new Set(),\n issued: 0,\n issuedAt: 0,\n applied: 0,\n };\n store.set(key, e);\n }\n return e;\n}\n\nfunction setSnapshot<T>(e: Bookkeeping<T>, patch: Partial<QueryEntry<T>>): void {\n e.snapshot = { ...e.snapshot, ...patch };\n}\n\nfunction notify(key: string): void {\n const e = store.get(key);\n if (!e) return;\n for (const s of e.subscribers) s();\n}\n\n/**\n * Structural equality for API payloads, used to decide whether a refresh\n * actually changed anything.\n *\n * A Refresh that re-reads seven routes and finds six of them byte-identical\n * should re-render nothing for those six. Keeping the PREVIOUS `data`\n * reference when the new body is deep-equal does exactly that: the snapshot\n * object still changes (status and updatedAt did), so useSyncExternalStore\n * still fires, but every `useMemo`/`memo` keyed on `data` downstream skips —\n * and the Usage section's models are all keyed on `data`.\n *\n * Deliberately structural rather than JSON.stringify: key order is not\n * significant in these payloads, and stringify would also throw on the\n * (never-seen, but cheap to survive) cyclic body.\n */\nfunction sameData(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== typeof b) return false;\n if (a === null || b === null || typeof a !== 'object') return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((item, i) => sameData(item, b[i]));\n }\n const left = a as Record<string, unknown>;\n const right = b as Record<string, unknown>;\n const leftKeys = Object.keys(left);\n if (leftKeys.length !== Object.keys(right).length) return false;\n return leftKeys.every((k) => Object.prototype.hasOwnProperty.call(right, k) && sameData(left[k], right[k]));\n}\n\nexport function subscribeQuery(key: string, listener: () => void): () => void {\n const e = entryFor(key);\n e.subscribers.add(listener);\n return () => {\n e.subscribers.delete(listener);\n };\n}\n\nexport function getQuerySnapshot<T>(key: string): QueryEntry<T> {\n return entryFor<T>(key).snapshot;\n}\n\n/**\n * How long an in-flight read is accepted as already satisfying a refetch.\n *\n * A poll tick (UniverseView refreshes every 3–15s and again on\n * visibilitychange) that lands on a read issued milliseconds ago should\n * coalesce with it — that is the behavior its own comment and tests pin\n * down. A person's Refresh landing on a read that has been running for\n * seconds should not: they are asking about the world as of the click.\n */\nexport const REFETCH_JOIN_WINDOW_MS = 250;\n\nexport interface QueryRunOptions {\n /**\n * Skip the request when the cached value is younger than this many ms.\n * Undefined (the default) always fetches.\n */\n maxAgeMs?: number;\n /**\n * Join an in-flight read only while it is younger than this. Undefined\n * joins any in-flight read (the original de-dupe).\n */\n joinWindowMs?: number;\n /**\n * User-initiated. Never joins an in-flight read and never honours\n * `maxAgeMs` — pressing Refresh must produce a request that was issued\n * after the click.\n */\n force?: boolean;\n}\n\nfunction isFresh(e: Bookkeeping, maxAgeMs: number): boolean {\n if (e.snapshot.updatedAt === null) return false;\n if (e.snapshot.status === 'error') return false;\n return Date.now() - e.snapshot.updatedAt < maxAgeMs;\n}\n\nfunction mayJoin(e: Bookkeeping, options: QueryRunOptions): boolean {\n if (e.inFlight === null) return false;\n if (options.joinWindowMs === undefined) return true;\n return Date.now() - e.issuedAt < options.joinWindowMs;\n}\n\nfunction execute<T>(key: string, fetcher: () => Promise<T>, options: QueryRunOptions): Promise<void> {\n const e = entryFor<T>(key);\n e.fetcher = fetcher;\n\n if (!options.force) {\n // Two components mounting at once share one request.\n if (mayJoin(e, options)) return e.inFlight!;\n // Already fresh enough for this caller: no request at all.\n if (options.maxAgeMs !== undefined && isFresh(e, options.maxAgeMs)) return Promise.resolve();\n }\n\n const seq = (e.issued += 1);\n e.issuedAt = Date.now();\n setSnapshot(e, { status: e.snapshot.data !== undefined ? 'refreshing' : 'loading' });\n notify(key);\n\n const run = schedule(fetcher)\n .then((data) => {\n // A superseded request (a forced refresh was issued after this one, or\n // the key was evicted) must never overwrite a newer answer.\n if (seq < e.applied) return;\n e.applied = seq;\n const unchanged = e.snapshot.data !== undefined && sameData(e.snapshot.data, data);\n setSnapshot(e, {\n data: unchanged ? e.snapshot.data : data,\n error: undefined,\n status: 'success',\n updatedAt: Date.now(),\n });\n })\n .catch((err: unknown) => {\n if (seq < e.applied) return;\n e.applied = seq;\n setSnapshot(e, { error: err instanceof Error ? err : new Error(String(err)), status: 'error' });\n })\n .finally(() => {\n // Only the newest request owns `inFlight`; an older, superseded one\n // finishing must not clear the newer one's slot.\n if (e.issued === seq) e.inFlight = null;\n notify(key);\n });\n\n e.inFlight = run;\n return run;\n}\n\n/**\n * Kick off (or join) a fetch for `key`. Safe to call redundantly — repeated\n * calls with an in-flight fetch are no-ops. Registers `fetcher` on the\n * entry so invalidate() can re-run it later without the caller re-supplying\n * it (e.g. from the SSE layer, which doesn't have per-view fetch closures).\n */\nexport function runQuery<T>(key: string, fetcher: () => Promise<T>): Promise<void> {\n return execute(key, fetcher, {});\n}\n\n/**\n * The MOUNT path (hooks.ts `useQuery`). Identical to `runQuery` except that a\n * value fetched less than `maxAgeMs` ago is accepted as-is and no request is\n * made. This is what turns \"switch to Usage and back\" from fourteen requests\n * into seven.\n */\nexport function ensureQuery<T>(key: string, fetcher: () => Promise<T>, maxAgeMs: number): Promise<void> {\n return execute(key, fetcher, { maxAgeMs });\n}\n\n/**\n * The REFRESH path (hooks.ts `useRefetch` / `useRefresh`).\n *\n * Default (`force: false`): joins an in-flight read only while that read is\n * younger than REFETCH_JOIN_WINDOW_MS, so a burst of poll ticks still\n * collapses into one request while a click on a long-running read gets a\n * real re-read.\n *\n * `force: true`: never joins. Always issues a request made after the call,\n * and wins the race against anything already running regardless of which\n * lands first. This is the guarantee an explicit user-initiated refresh\n * needs.\n */\nexport function refetchQuery<T>(key: string, fetcher: () => Promise<T>, force = false): Promise<void> {\n return execute(key, fetcher, force ? { force: true } : { joinWindowMs: REFETCH_JOIN_WINDOW_MS });\n}\n\n/** Re-run the last fetcher registered for `key`, if any (used by sse.ts). */\nexport function invalidate(key: string): void {\n const e = store.get(key);\n if (!e || !e.fetcher) return;\n void runQuery(key, e.fetcher);\n}\n\n/** Re-run the fetcher for every cached key starting with `prefix`. For\n * parameterized resources (per-filter inbox list queries, per-id inbox\n * detail queries) the exact live key isn't known ahead of time — used by\n * sse.ts and data/mutations.ts so those views live-update too. */\nexport function invalidatePrefix(prefix: string): void {\n for (const key of store.keys()) {\n if (key.startsWith(prefix)) invalidate(key);\n }\n}\n\n/**\n * `invalidatePrefix`, but only for keys a mounted component is SUBSCRIBED to.\n *\n * The read-session renewal sweep (auth-store.ts) uses it. An entry nobody is\n * reading — a surface the shell's idle warm-up fetched but the operator never\n * opened, or one they left — is left exactly as it is: whoever mounts it next\n * goes through `ensureQuery`, which re-reads it if it is older than that\n * caller accepts or if it failed (a read that 401ed is 'error', never fresh).\n * Re-running those fetchers on every 15-minute renewal was pure background\n * traffic through the same gate as the chat reads waiting on their retry.\n */\nexport function invalidateObserved(prefix: string): void {\n for (const [key, e] of store) {\n if (e.subscribers.size > 0 && key.startsWith(prefix)) invalidate(key);\n }\n}\n\n/**\n * Reset one entry to idle WITHOUT dropping its subscriber set, and supersede\n * anything still in flight for it so a late result cannot repopulate the key\n * after it was deliberately cleared.\n */\nfunction resetEntry(key: string, e: Bookkeeping): void {\n e.snapshot = IDLE_SNAPSHOT as QueryEntry<unknown>;\n e.fetcher = null;\n e.inFlight = null;\n e.issued += 1;\n e.applied = e.issued;\n notify(key);\n}\n\n/** Drop a key entirely (rare — e.g. on logout, so stale data never leaks\n * into the next session's first paint). An entry that still has mounted\n * subscribers is RESET rather than deleted: deleting it would orphan the\n * listener set those components registered on, leaving them subscribed to\n * something nothing can notify. */\nexport function evict(key: string): void {\n const e = store.get(key);\n if (!e) return;\n if (e.subscribers.size === 0) {\n store.delete(key);\n return;\n }\n resetEntry(key, e);\n}\n\nexport function evictAll(): void {\n resetQueryGate();\n for (const [key, e] of [...store]) {\n if (e.subscribers.size === 0) {\n store.delete(key);\n continue;\n }\n resetEntry(key, e);\n }\n}\n","/**\n * routes/verse/chat/composer-memory.ts — the composer's memory: drafts\n * and sent messages, per chat.\n *\n * Two things a long agentic day needs from a message box and does not get\n * from a bare `useState`:\n *\n * 1. A draft survives switching chats and reloading the app. Half-written\n * instructions are expensive to re-type and are lost today the moment\n * ⌘K lands somewhere else.\n * 2. What was already sent can be recalled with ↑, the way every shell\n * works, because the second attempt at a prompt is usually the first\n * one plus a clause.\n *\n * Both are per-session and stored in `localStorage`, bounded so a long-lived\n * browser cannot accumulate an unbounded map. Every access is wrapped: a\n * private window with storage blocked still runs the app.\n *\n * Split from composer-state.ts (which re-exports all of it) so the modules on\n * the chat first-paint path that only FORGET or CLEAR this memory (the Chat\n * section on delete, auth-store on logout) do not pull the cost estimator and\n * context-math into the chat first-paint critical JS.\n */\n\nconst DRAFT_KEY = 'ashlr.verse.drafts.v1';\nconst SENT_KEY = 'ashlr.verse.sent.v1';\n\n/** Sessions remembered, newest last. */\nconst SESSION_LIMIT = 40;\n/** Messages recalled per session. */\nconst HISTORY_LIMIT = 25;\n/** Matches VERSE_MAX_TURN_TEXT_BYTES; a draft over it cannot be sent anyway. */\nconst TEXT_LIMIT = 64 * 1024;\n\nfunction readMap<T>(key: string, parse: (value: unknown) => T | null): Record<string, T> {\n const out: Record<string, T> = {};\n try {\n const raw = localStorage.getItem(key);\n const parsed = raw ? (JSON.parse(raw) as unknown) : null;\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n for (const [id, value] of Object.entries(parsed as Record<string, unknown>)) {\n const clean = parse(value);\n if (clean !== null) out[id] = clean;\n }\n }\n } catch {\n /* storage unavailable or corrupt — an empty memory is a valid one */\n }\n return out;\n}\n\n/** True when the write landed; false when storage refused it (blocked, over quota). */\nfunction writeMap(key: string, map: Record<string, unknown>): boolean {\n // Insertion order is recency order: every write deletes before re-inserting.\n const ids = Object.keys(map);\n for (const stale of ids.slice(0, Math.max(0, ids.length - SESSION_LIMIT))) delete map[stale];\n try {\n localStorage.setItem(key, JSON.stringify(map));\n return true;\n } catch {\n /* best-effort — the caller decides whether a refusal loses anything */\n return false;\n }\n}\n\nconst asText = (value: unknown): string | null =>\n typeof value === 'string' && value.length > 0 ? value.slice(0, TEXT_LIMIT) : null;\n\nconst asHistory = (value: unknown): string[] | null => {\n if (!Array.isArray(value)) return null;\n const list = value.filter((v): v is string => typeof v === 'string' && v.length > 0)\n .slice(-HISTORY_LIMIT)\n .map((v) => v.slice(0, TEXT_LIMIT));\n return list.length > 0 ? list : null;\n};\n\n/**\n * Drafts whose last write storage REFUSED, held for the page's lifetime ('' =\n * a clear that could not be written) and consulted BEFORE storage.\n *\n * WHY: `localStorage` writes fail silently (blocked storage, a full quota),\n * and one draft is not a convenience but the only copy of something: the\n * handoff note. The dialog hands it over with `saveDraft` and the new chat's\n * Composer restores it with `loadDraft`; with storage refusing, the edited\n * note — possibly written after a paid summary turn — simply vanished. An\n * entry here is always newer than whatever storage holds for that chat, and a\n * later write that lands removes it, so storage is the answer again.\n */\nconst unsavedDrafts = new Map<string, string>();\n\n/** The unsent draft for this chat, or `''`. */\nexport function loadDraft(sessionId: string | null | undefined): string {\n if (!sessionId) return '';\n const unsaved = unsavedDrafts.get(sessionId);\n if (unsaved !== undefined) return unsaved;\n return readMap(DRAFT_KEY, asText)[sessionId] ?? '';\n}\n\n/** Persist (or clear, when empty) the draft for this chat. */\nexport function saveDraft(sessionId: string | null | undefined, text: string): void {\n if (!sessionId) return;\n const clean = text.trim().length > 0 ? text.slice(0, TEXT_LIMIT) : '';\n const map = readMap(DRAFT_KEY, asText);\n delete map[sessionId];\n if (clean) map[sessionId] = clean;\n unsavedDrafts.delete(sessionId);\n if (!writeMap(DRAFT_KEY, map)) {\n unsavedDrafts.set(sessionId, clean);\n // Bounded like storage: oldest refusals go first.\n for (const stale of [...unsavedDrafts.keys()].slice(0, Math.max(0, unsavedDrafts.size - SESSION_LIMIT))) unsavedDrafts.delete(stale);\n }\n}\n\n/** Messages sent in this chat, oldest first. */\nexport function loadHistory(sessionId: string | null | undefined): string[] {\n if (!sessionId) return [];\n return readMap(SENT_KEY, asHistory)[sessionId] ?? [];\n}\n\n/** Record a sent message; consecutive duplicates are not stored twice. */\nexport function pushHistory(sessionId: string | null | undefined, text: string): void {\n if (!sessionId || text.trim().length === 0) return;\n const map = readMap(SENT_KEY, asHistory);\n const current = map[sessionId] ?? [];\n delete map[sessionId];\n const next = current[current.length - 1] === text ? current : [...current, text.slice(0, TEXT_LIMIT)];\n map[sessionId] = next.slice(-HISTORY_LIMIT);\n writeMap(SENT_KEY, map);\n}\n\n/**\n * Forget everything this chat remembered — its unsent draft and its sent\n * messages.\n *\n * Deleting a chat removes its transcript from disk, but the verbatim prompts\n * stayed in browser storage under the dead session id until forty newer\n * sessions rotated them out.\n */\nexport function forgetComposerMemory(sessionId: string | null | undefined): void {\n if (!sessionId) return;\n unsavedDrafts.delete(sessionId);\n for (const [key, parse] of [\n [DRAFT_KEY, asText],\n [SENT_KEY, asHistory],\n ] as const) {\n const map: Record<string, unknown> = readMap(key, parse as (v: unknown) => unknown);\n if (!(sessionId in map)) continue;\n delete map[sessionId];\n writeMap(key, map);\n }\n}\n\n/**\n * Drop every remembered draft and sent message, for every session.\n *\n * Called on disconnect/logout alongside the query cache's `evictAll`, whose\n * contract is that stale data must not leak into the next session's first\n * paint. Prompt text is precisely the content that wipe exists for, and it is\n * the one thing that used to survive it.\n */\nexport function clearComposerMemory(): void {\n unsavedDrafts.clear();\n for (const key of [DRAFT_KEY, SENT_KEY]) {\n try {\n localStorage.removeItem(key);\n } catch {\n /* storage unavailable — nothing to clear */\n }\n }\n}\n\nexport const VERSE_DRAFT_STORAGE_KEY = DRAFT_KEY;\nexport const VERSE_SENT_STORAGE_KEY = SENT_KEY;\n","/**\n * data/auth-store.ts — the read-session + mutation-token authority for the\n * whole app. Mirrors the two-token model in src/core/web/server.ts exactly:\n *\n * READ authority — exchanged via POST /api/session (raw readToken + a\n * per-tab client proof) for an HttpOnly cookie good\n * for 15 minutes. The raw read token is remembered in\n * MEMORY ONLY (never any storage) for the tab's\n * lifetime so the 15-minute ticket can be renewed\n * silently when a GET/SSE comes back 401 — a live\n * chat must not turn into \"paste a 64-hex token every\n * 15 minutes\". The client proof is not a secret by\n * itself (it has no authority without the signed\n * cookie) and is kept in sessionStorage so a page\n * reload can keep using the still-valid cookie instead\n * of forcing re-entry.\n *\n * MUTATION authority — the raw x-ashlr-token header, required on every\n * mutating call, NEVER exchanged for a session and\n * NEVER persisted to any storage. Held in memory only,\n * for a bounded \"hold\" window, so approving a run of\n * proposals isn't one window.prompt() per click. The\n * hold is cleared on tab close, explicit lock, or\n * idle timeout.\n *\n * This module has no React dependency — it is a plain external store\n * consumed via useSyncExternalStore in hooks.ts, per the same pattern used\n * for the query cache.\n */\n\nimport { evictAll, invalidateObserved } from './cache.js';\n// The composer owns its own storage format, so the key names live with it\n// rather than being duplicated here where they could drift.\nimport { clearComposerMemory } from '../routes/verse/chat/composer-memory.js';\n\nconst READ_CLIENT_STORAGE_KEY = 'ashlr.readClientProof.v1';\nconst READ_CLIENT_RE = /^[a-f0-9]{64}$/;\nconst MUTATION_HOLD_MS = 20 * 60 * 1000; // 20 minutes of inactivity clears the hold.\n\nexport type AuthPhase = 'checking' | 'unauthenticated' | 'authenticated';\n\ninterface AuthState {\n phase: AuthPhase;\n /** True once a session check has actually completed (vs. still booting). */\n checked: boolean;\n mutationTokenHeldUntil: number | null;\n}\n\nfunction randomHex64(): string {\n const bytes = new Uint8Array(32);\n crypto.getRandomValues(bytes);\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nfunction loadOrCreateClientProof(): string {\n try {\n const existing = sessionStorage.getItem(READ_CLIENT_STORAGE_KEY);\n if (existing && READ_CLIENT_RE.test(existing)) return existing;\n } catch {\n // sessionStorage unavailable (privacy mode, etc.) — fall through to an\n // in-memory-only proof for this page life.\n }\n const proof = randomHex64();\n try {\n sessionStorage.setItem(READ_CLIENT_STORAGE_KEY, proof);\n } catch {\n /* best-effort persistence only */\n }\n return proof;\n}\n\nlet clientProof = loadOrCreateClientProof();\n\n/** In-memory only. Never touches storage. Cleared by clearMutationToken(). */\nlet mutationToken: string | null = null;\nlet mutationHoldTimer: ReturnType<typeof setTimeout> | null = null;\n/**\n * The mutation token came from the desktop host (window.__ASHLR_TOKENS__).\n * The operator never saw it, so an idle timeout must re-arm the hold rather\n * than clear it — a cleared hold would ask for a token nobody can paste.\n */\nlet mutationHeldByHost = false;\n\n/**\n * Last read token that the server accepted (typed or host-injected). Memory\n * only, same trust boundary as the mutation hold; used to renew the cookie\n * silently on 401. Dropped on explicit disconnect or when renewal fails.\n */\nlet rememberedReadToken: string | null = null;\nlet renewal: Promise<boolean> | null = null;\n/** Desktop-injected tokens kept for re-adoption (see the host-injected section below). */\nlet hostTokens: InjectedTokens | null = null;\n\nlet state: AuthState = { phase: 'checking', checked: false, mutationTokenHeldUntil: null };\nconst listeners = new Set<() => void>();\n\nfunction emit(): void {\n for (const l of listeners) l();\n}\n\nfunction setState(patch: Partial<AuthState>): void {\n state = { ...state, ...patch };\n emit();\n}\n\nexport function subscribeAuth(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\nexport function getAuthSnapshot(): AuthState {\n return state;\n}\n\nexport function getReadClientProof(): string {\n return clientProof;\n}\n\n/** Regenerate the client proof (e.g. after an explicit \"forget this device\"). */\nexport function resetReadClientProof(): string {\n clientProof = randomHex64();\n try {\n sessionStorage.setItem(READ_CLIENT_STORAGE_KEY, clientProof);\n } catch {\n /* best-effort */\n }\n return clientProof;\n}\n\n/**\n * Exchange a raw read token for a session cookie. Throws on failure with a\n * message safe to show the operator. The raw token never leaves this call.\n */\nexport async function establishReadSession(rawReadToken: string): Promise<void> {\n const trimmed = rawReadToken.trim();\n if (!/^[a-f0-9]{64}$/.test(trimmed)) {\n throw new Error('That does not look like a read token — expected 64 hex characters.');\n }\n const res = await fetch('/api/session', {\n method: 'POST',\n credentials: 'same-origin',\n headers: {\n 'x-ashlr-token': trimmed,\n 'x-ashlr-read-client': clientProof,\n },\n });\n if (res.status === 204) {\n rememberedReadToken = trimmed;\n setState({ phase: 'authenticated', checked: true });\n return;\n }\n if (res.status === 401) {\n throw new Error('Server rejected that token. Double-check the read token printed by this server at startup.');\n }\n throw new Error(`Unexpected response establishing session (HTTP ${res.status}).`);\n}\n\nexport async function clearReadSession(): Promise<void> {\n try {\n await fetch('/api/session', {\n method: 'DELETE',\n credentials: 'same-origin',\n headers: { 'x-ashlr-read-client': clientProof },\n });\n } catch {\n /* best-effort */\n }\n rememberedReadToken = null;\n hostTokens = null;\n expireNow();\n}\n\nfunction expireNow(): void {\n clearMutationToken();\n evictAll();\n // `evictAll` clears the query cache so nothing stale leaks into the next\n // session's first paint. The composer's drafts and sent-message history live\n // in localStorage, not in that cache, and they are verbatim prompt text —\n // exactly the kind of content this wipe exists for.\n clearComposerMemory();\n setState({ phase: 'unauthenticated', checked: true });\n}\n\n/**\n * Called by data/client.ts whenever any authenticated GET comes back 401\n * (and by sse.ts on `session-expired`). With a remembered read token the\n * cookie is re-issued silently and the phase never flips; the failed call's\n * own retry/reconnect (SSE backoff, the next refetch) then succeeds. Only\n * when renewal fails (server restarted with fresh tokens) does the gate show.\n */\nexport function reportSessionExpired(): void {\n if (state.phase !== 'authenticated') return;\n if (rememberedReadToken) {\n void renewReadSession();\n return;\n }\n expireNow();\n}\n\n/** Re-exchange the remembered read token for a fresh cookie; one flight at a time. */\nexport function renewReadSession(): Promise<boolean> {\n if (renewal) return renewal;\n const token = rememberedReadToken;\n if (!token) return Promise.resolve(false);\n renewal = (async () => {\n try {\n await establishReadSession(token);\n // Whatever is on screen and 401ed while the ticket was lapsed refetches\n // against the new cookie. Only what is OBSERVED: an entry nobody reads\n // (a surface the idle warm-up fetched but the operator never opened)\n // re-reads when it next mounts, if it is stale or failed — re-running\n // them all here cost ~8 background reads every 15 minutes, all day.\n invalidateObserved('');\n return true;\n } catch {\n rememberedReadToken = null;\n hostTokens = null;\n expireNow();\n return false;\n } finally {\n renewal = null;\n }\n })();\n return renewal;\n}\n\n/** Test hygiene: forget the remembered read token and host tokens without a network call. */\nexport function forgetRememberedTokens(): void {\n rememberedReadToken = null;\n hostTokens = null;\n renewal = null;\n}\n\nexport function markCheckComplete(authenticated: boolean): void {\n setState({ phase: authenticated ? 'authenticated' : 'unauthenticated', checked: true });\n}\n\n// ---------------------------------------------------------------------------\n// Host-injected tokens (desktop wrapper). The Tauri shell starts the server\n// itself and hands both tokens to the page through an initialization script\n// (`window.__ASHLR_TOKENS__`, see desktop/README.md) so the operator never\n// pastes them. The object is removed from `window` before either token is\n// used and kept in this module's memory instead (`hostTokens`), because the\n// desktop operator has no terminal to paste from: when the 15-minute cookie\n// lapses or the gate re-mounts, adoption re-runs from memory. The read token\n// is exchanged for the cookie exactly like a typed one and the mutation token\n// goes into the memory-only hold. Nothing is persisted and nothing is logged.\n// ---------------------------------------------------------------------------\n\nexport interface InjectedTokens {\n readToken: string;\n /** Mutation token; absent or null when the host started the server without dispatch. */\n token?: string | null;\n}\n\ndeclare global {\n interface Window {\n __ASHLR_TOKENS__?: InjectedTokens;\n }\n}\n\n/**\n * Take host-injected tokens: from `window` (cleared from the page on first\n * sight, remembered here) or, on later calls, from memory.\n */\nexport function takeInjectedTokens(): InjectedTokens | null {\n const injected = window.__ASHLR_TOKENS__;\n if (injected && typeof injected === 'object') {\n try {\n delete window.__ASHLR_TOKENS__;\n } catch {\n window.__ASHLR_TOKENS__ = undefined;\n }\n if (typeof injected.readToken === 'string') {\n hostTokens = { readToken: injected.readToken, ...(typeof injected.token === 'string' ? { token: injected.token } : {}) };\n }\n }\n return hostTokens ? { ...hostTokens } : null;\n}\n\n/**\n * Establish the read session (and set the mutation hold) from host-injected\n * tokens. Resolves true when a session was established; false when nothing\n * was injected or the server rejected the read token (the caller then falls\n * back to the normal SessionGate flow — the failure is never surfaced with\n * the token in it).\n */\nexport async function adoptInjectedTokens(): Promise<boolean> {\n const injected = takeInjectedTokens();\n if (!injected) return false;\n try {\n await establishReadSession(injected.readToken);\n } catch {\n // Wrong or stale tokens are not worth retrying from memory.\n hostTokens = null;\n return false;\n }\n if (injected.token && /^[a-f0-9]{64}$/.test(injected.token.trim())) setMutationToken(injected.token.trim(), { host: true });\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Mutation token — session-scoped hold, memory-only.\n// ---------------------------------------------------------------------------\n\nexport function getMutationToken(): string | null {\n return mutationToken;\n}\n\nexport function hasMutationHold(): boolean {\n return mutationToken !== null;\n}\n\nfunction armIdleClear(): void {\n if (mutationHoldTimer) clearTimeout(mutationHoldTimer);\n mutationHoldTimer = setTimeout(() => {\n // A host-held token has no operator to re-paste it: keep the hold alive.\n if (mutationHeldByHost && mutationToken !== null) touchMutationHold();\n else clearMutationToken();\n }, MUTATION_HOLD_MS);\n}\n\n/**\n * Set the mutation token for this tab's session hold. Never persisted.\n * `host` marks a token injected by the desktop wrapper (see adoptInjectedTokens).\n */\nexport function setMutationToken(token: string, opts: { host?: boolean } = {}): void {\n mutationToken = token;\n mutationHeldByHost = opts.host === true;\n const heldUntil = Date.now() + MUTATION_HOLD_MS;\n setState({ mutationTokenHeldUntil: heldUntil });\n armIdleClear();\n}\n\n/** Extend the hold window on real use (called after every mutating call). */\nexport function touchMutationHold(): void {\n if (mutationToken === null) return;\n const heldUntil = Date.now() + MUTATION_HOLD_MS;\n setState({ mutationTokenHeldUntil: heldUntil });\n armIdleClear();\n}\n\nexport function clearMutationToken(): void {\n mutationToken = null;\n mutationHeldByHost = false;\n if (mutationHoldTimer) {\n clearTimeout(mutationHoldTimer);\n mutationHoldTimer = null;\n }\n setState({ mutationTokenHeldUntil: null });\n}\n","/**\n * data/sse.ts — the live channel. One EventSource against /api/events for\n * the whole app, fanning out NAMED events (see api.ts handleSseEvents:\n * 'runs' | 'swarms' | 'inbox' | 'daemon' | 'daemon-observation' |\n * 'fleet-activity-ping' | 'fleet-activity-observation' | 'snapshot') to\n * subscribers, and invalidating the matching cache key so any mounted\n * useQuery(key) re-fetches in the background.\n *\n * The server polls disk at a fixed interval (api.ts SSE_POLL_MS, ~1.5s at\n * time of writing) — that is the real latency floor for \"live\". This layer\n * does not, and should not, try to beat that; it just makes sure the UI\n * never polls MORE often than the server already pushes.\n */\nimport { getReadClientProof, getAuthSnapshot, reportSessionExpired, subscribeAuth } from './auth-store.js';\nimport { invalidate, invalidatePrefix } from './cache.js';\nimport { isScopedConsolePath } from '../app/console-mode.js';\n\nexport const SSE_EVENT_NAMES = [\n 'runs',\n 'swarms',\n 'inbox',\n 'daemon',\n 'daemon-observation',\n 'fleet-activity-ping',\n 'fleet-activity-observation',\n 'snapshot',\n 'session-expired',\n] as const;\nexport type SseEventName = (typeof SSE_EVENT_NAMES)[number];\n\n/** Cache keys (see data/queries.ts) invalidated when a given SSE event fires. */\nconst EVENT_TO_CACHE_KEYS: Record<SseEventName, string[]> = {\n runs: ['runs'],\n swarms: ['swarms'],\n inbox: ['inbox'],\n // 'daemon' was previously mapped but nothing subscribed to a 'daemon'-keyed\n // query (control/daemon didn't exist yet) — now daemonObservationQuery\n // (data/queries.ts) does. Also refreshing 'control-snapshot' here so\n // /control/daemon and /control/security (both read off controlSnapshotQuery)\n // stay live on the same daemon-tick cadence instead of only on page load.\n // `/api/verse/control` and `/api/verse/audit` ARE the daemon projection, so\n // they ride the same signal. Without this the Autonomy cockpit — running\n // state, last tick outcome, today's spend against the cap — was frozen at\n // whatever was true when the section mounted, and DEFAULT_QUERY_FRESH_MS\n // then made a rail switch away and back inside 10s a no-op too.\n //\n // Deliberately NOT a coarse `verse-` prefix sweep: `verse-usage-accounts`\n // and `verse-usage-local-models` spawn provider probes, and firing those on\n // every daemon tick would turn a liveness fix into a process storm. Those\n // two poll on their own visibility-gated 30s cadence in UsageSection,\n // matching the collector's cycle.\n daemon: ['daemon', 'control-snapshot', 'verse-control', 'verse-usage-control', 'verse-caps'],\n 'daemon-observation': ['daemon', 'control-snapshot', 'verse-control', 'verse-usage-control', 'verse-caps'],\n // fleet-activity pings are the closest live signal fleet state changed;\n // 'fleet' (fleetStatusQuery, /control/fleet's queue depth + lease health)\n // has no dedicated SSE event of its own, so it rides these instead of only\n // refreshing on mount.\n 'fleet-activity-ping': ['fleet-activity', 'fleet'],\n 'fleet-activity-observation': ['fleet-activity', 'fleet'],\n // Agent OS snapshots are immutable records but their current authenticated\n // projection advances alongside the server's general snapshot signal.\n snapshot: ['dashboard-snapshot', 'agent-os-snapshot'],\n 'session-expired': [],\n};\n\ntype RawListener = (payload: unknown) => void;\nconst rawListeners = new Map<SseEventName, Set<RawListener>>();\n\nlet source: EventSource | null = null;\nlet reconnectTimer: ReturnType<typeof setTimeout> | null = null;\nlet reconnectAttempt = 0;\n\nfunction backoffMs(): number {\n // 1s, 2s, 4s, 8s, capped at 15s — the server's own poll floor is ~1.5s so\n // there is no benefit reconnecting faster than that even on attempt 0.\n return Math.min(1000 * 2 ** reconnectAttempt, 15_000);\n}\n\nfunction teardown(): void {\n if (source) {\n source.close();\n source = null;\n }\n if (reconnectTimer) {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n }\n}\n\nfunction scheduleReconnect(): void {\n if (reconnectTimer) return;\n reconnectTimer = setTimeout(() => {\n reconnectTimer = null;\n reconnectAttempt += 1;\n connect();\n }, backoffMs());\n}\n\nfunction connect(): void {\n // Shared hooks import this module, but the dedicated console has no global\n // Hub event channel. Check on every reconnect, not just the initial import.\n if (isScopedConsolePath()) { teardown(); return; }\n if (getAuthSnapshot().phase !== 'authenticated') return;\n teardown();\n\n const url = `/api/events?client=${encodeURIComponent(getReadClientProof())}`;\n const es = new EventSource(url, { withCredentials: true });\n source = es;\n\n es.onopen = () => {\n reconnectAttempt = 0;\n };\n\n for (const name of SSE_EVENT_NAMES) {\n es.addEventListener(name, (evt) => {\n const messageEvent = evt as MessageEvent<string>;\n let payload: unknown = undefined;\n try {\n payload = JSON.parse(messageEvent.data);\n } catch {\n payload = undefined;\n }\n for (const key of EVENT_TO_CACHE_KEYS[name]) invalidate(key);\n if (name === 'inbox') {\n // Parameterized inbox queries (one cache key per filter combo / per\n // proposal id) aren't in EVENT_TO_CACHE_KEYS's static map — sweep\n // both prefixes so the list (any filter) and any open detail view\n // (proposalDetailQuery, data/queries.ts) live-update on the same\n // 'inbox' event as the unfiltered badge.\n invalidatePrefix('inbox-list:');\n invalidatePrefix('proposal-detail-');\n }\n if (name === 'session-expired') reportSessionExpired();\n const listeners = rawListeners.get(name);\n if (listeners) for (const l of listeners) l(payload);\n });\n }\n\n es.onerror = () => {\n // EventSource auto-retries on its own, but its built-in retry delay is\n // opaque and can hammer a server that just lost its read session. Close\n // it and drive reconnection ourselves so we stop entirely once\n // auth-store reports the session as expired (see the subscribeAuth\n // wiring at the bottom of this file).\n es.close();\n if (source === es) source = null;\n if (getAuthSnapshot().phase === 'authenticated') scheduleReconnect();\n };\n}\n\n/** Subscribe to one named SSE event's raw payload (rarely needed directly —\n * most views should prefer useQuery + the cache-key mapping above). */\nexport function onSseEvent(name: SseEventName, listener: RawListener): () => void {\n let set = rawListeners.get(name);\n if (!set) {\n set = new Set();\n rawListeners.set(name, set);\n }\n set.add(listener);\n return () => set!.delete(listener);\n}\n\n// Connect once authenticated; disconnect the instant the session drops so a\n// stale connection never masks a 401 the rest of the app needs to react to.\nsubscribeAuth(() => {\n const phase = getAuthSnapshot().phase;\n if (phase === 'authenticated') {\n reconnectAttempt = 0;\n connect();\n } else {\n teardown();\n }\n});\n","/**\n * data/theme-store.ts — the theme preference, extracted out of Topbar.tsx\n * so the command palette's \"Toggle theme\" action can read/write the same\n * source of truth Topbar's own button uses (same module-level state +\n * subscribe pattern as auth-store.ts — commands.ts's `run` closures have no\n * React context to call useState from, so this can't live in a component).\n */\n\nexport type ThemePreference = 'system' | 'light' | 'dark';\n\nconst THEME_STORAGE_KEY = 'ashlr.theme.v1';\n\nfunction applyTheme(pref: ThemePreference): void {\n const root = document.documentElement;\n if (pref === 'system') root.removeAttribute('data-theme');\n else root.setAttribute('data-theme', pref);\n}\n\nfunction loadTheme(): ThemePreference {\n try {\n const v = localStorage.getItem(THEME_STORAGE_KEY);\n if (v === 'light' || v === 'dark' || v === 'system') return v;\n } catch {\n /* ignore */\n }\n return 'system';\n}\n\nlet theme: ThemePreference = loadTheme();\napplyTheme(theme);\n\nconst listeners = new Set<() => void>();\n\nfunction emit(): void {\n for (const l of listeners) l();\n}\n\nexport function subscribeTheme(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\nexport function getTheme(): ThemePreference {\n return theme;\n}\n\nexport function setTheme(next: ThemePreference): void {\n theme = next;\n applyTheme(theme);\n try {\n localStorage.setItem(THEME_STORAGE_KEY, theme);\n } catch {\n /* best-effort persistence only */\n }\n emit();\n}\n\nexport function cycleTheme(): void {\n setTheme(theme === 'system' ? 'light' : theme === 'light' ? 'dark' : 'system');\n}\n","/**\n * data/appearance-store.ts — the operator's appearance preferences: the\n * \"Linux for agentic engineering\" part of the design language\n * (docs/VERSE-DESIGN-V2.md §3). Theme, accent, density, display face,\n * radius, and motion, applied live by writing custom properties and\n * data-attributes on <html>.\n *\n * Same shape as ./theme-store.ts on purpose — module-level state + a\n * listener set, zero React — so it is unit-testable without a DOM renderer\n * and usable from non-component code (the command palette's closures, the\n * rail's quick-toggle). data/hooks.ts binds it into React via\n * `useAppearance()`, exactly as it does for the theme.\n *\n * THEME IS NOT FORKED. `theme-store.ts` remains the single authority for the\n * light/dark/system preference (key `ashlr.theme.v1`, already read by the\n * topbar and the command palette); this module re-exposes it as one field of\n * the appearance snapshot and delegates writes to `setTheme`. Two stores\n * writing `data-theme` would race; one store that owns it does not.\n *\n * Everything is applied at MODULE LOAD (before React's first render, since\n * hooks.ts imports this and every view imports hooks.ts), so the app never\n * paints the default accent for a frame before swapping to the chosen one.\n */\nimport {\n getTheme,\n setTheme,\n subscribeTheme,\n type ThemePreference,\n} from './theme-store.js';\n\nexport type { ThemePreference };\n\nexport type Density = 'comfortable' | 'compact';\n\n/**\n * How large the whole interface is drawn. Scales TYPE AND SPACING TOGETHER\n * (design/tokens.css multiplies its type ramp by --ui-text-scale and its\n * spacing/geometry ramp by --ui-scale), because scaling type alone produces\n * big text in cramped boxes — worse to read than the default, not better.\n *\n * ORTHOGONAL TO `density`, not a replacement for it: display size answers\n * \"how big is this interface\", density answers \"how tightly are its rows\n * packed\". They compose, which is why tokens.css expresses the scale as a\n * multiplier rather than a second table of literal heights.\n *\n * There is no step below 'default' on purpose — see the [data-ui-scale]\n * block in tokens.css for why (the 12px body floor, and Density already\n * being the tightening control).\n */\nexport type UiScale = 'default' | 'large' | 'xlarge';\n\n/**\n * The operator's motion choice. Tri-state ON PURPOSE.\n *\n * A boolean cannot distinguish \"I want full motion\" from \"I never touched\n * this\", and `persist()` writes the whole snapshot on every appearance change\n * — so dragging the accent hue once used to store `reduceMotion: false` and\n * permanently override the OS `prefers-reduced-motion` preference, because\n * `applyAppearance` then wrote `data-motion=\"full\"`, which is exactly the\n * value every `:root:not([data-motion=\"full\"])` guard exists to defer to.\n *\n * 'system' removes the attribute entirely and lets the media queries decide,\n * which is what DESIGN-V2 §3 and §6 ask for by default.\n */\nexport type MotionPreference = 'system' | 'reduce' | 'full';\nexport type DisplayFont = 'grotesk' | 'ui' | 'mono';\nexport type RadiusScale = 'sharp' | 'default' | 'soft';\n\nexport interface Appearance {\n /** Delegated to theme-store.ts — persisted under its own key. */\n theme: ThemePreference;\n /** Accent hue 0-360. */\n accentH: number;\n /** Accent saturation 0-100 (%). */\n accentS: number;\n /** Accent lightness, clamped to a readable band (%). */\n accentL: number;\n density: Density;\n /** How large the whole interface is drawn. Type and spacing together. */\n uiScale: UiScale;\n displayFont: DisplayFont;\n radius: RadiusScale;\n /** The stored choice: 'system' follows `prefers-reduced-motion`. */\n motion: MotionPreference;\n /**\n * DERIVED, never stored — what the Settings switch shows and what the app\n * behaves as right now. `setAppearance({ reduceMotion })` is the only call\n * that turns this back into an explicit 'reduce'/'full'.\n */\n reduceMotion: boolean;\n}\n\n/**\n * The persisted subset. `theme` lives in theme-store, and `reduceMotion` is\n * derived from `motion` — storing it is what created the override bug.\n */\ntype StoredAppearance = Omit<Appearance, 'theme' | 'reduceMotion'>;\n\nconst STORAGE_KEY = 'ashlr.verse.appearance.v1';\n\n/**\n * Accent lightness band. Below ~30% the accent stops reading as a color on a\n * dark canvas; above ~78% it disappears on a light one. The hue and\n * saturation are unconstrained — this is the one axis where a free slider\n * can make the app genuinely unusable.\n */\nconst ACCENT_L_MIN = 30;\nconst ACCENT_L_MAX = 78;\n\n/** Must match the bare `:root` values in design/tokens.css. */\nexport const DEFAULT_ACCENT = { h: 245, s: 72, l: 58 } as const;\n\nfunction prefersReducedMotion(): boolean {\n try {\n return typeof window !== 'undefined' && typeof window.matchMedia === 'function'\n ? window.matchMedia('(prefers-reduced-motion: reduce)').matches\n : false;\n } catch {\n // Some embedded webviews throw on an unsupported media feature.\n return false;\n }\n}\n\nexport function defaultAppearance(): Appearance {\n return {\n theme: 'system',\n accentH: DEFAULT_ACCENT.h,\n accentS: DEFAULT_ACCENT.s,\n accentL: DEFAULT_ACCENT.l,\n density: 'comfortable',\n uiScale: 'default',\n displayFont: 'grotesk',\n radius: 'default',\n motion: 'system',\n reduceMotion: prefersReducedMotion(),\n };\n}\n\nfunction isMotion(v: unknown): v is MotionPreference {\n return v === 'system' || v === 'reduce' || v === 'full';\n}\n\n/** What 'system' resolves to right now; an explicit choice answers itself. */\nfunction resolveReduceMotion(motion: MotionPreference): boolean {\n return motion === 'reduce' ? true : motion === 'full' ? false : prefersReducedMotion();\n}\n\nconst clamp = (n: number, min: number, max: number): number => Math.min(max, Math.max(min, n));\n\nfunction isDensity(v: unknown): v is Density {\n return v === 'comfortable' || v === 'compact';\n}\nfunction isUiScale(v: unknown): v is UiScale {\n return v === 'default' || v === 'large' || v === 'xlarge';\n}\nfunction isDisplayFont(v: unknown): v is DisplayFont {\n return v === 'grotesk' || v === 'ui' || v === 'mono';\n}\nfunction isRadius(v: unknown): v is RadiusScale {\n return v === 'sharp' || v === 'default' || v === 'soft';\n}\n\n/** Coerce anything (stored JSON, a caller's patch) into a valid Appearance. */\nfunction normalize(base: Appearance, patch: Partial<Appearance> | Partial<StoredAppearance>): Appearance {\n const merged = { ...base, ...patch } as Appearance;\n // `reduceMotion` in a PATCH is the Settings switch and is authoritative —\n // it is the one call that converts the derived boolean back into a stored\n // choice. `reduceMotion` in a stored blob is not a choice at all, which is\n // why load() strips it before it ever gets here.\n const asked = (patch as Partial<Appearance>).reduceMotion;\n const motion: MotionPreference = isMotion((patch as Partial<Appearance>).motion)\n ? ((patch as Partial<Appearance>).motion as MotionPreference)\n : typeof asked === 'boolean'\n ? (asked ? 'reduce' : 'full')\n : isMotion(base.motion)\n ? base.motion\n : 'system';\n return {\n theme: merged.theme === 'light' || merged.theme === 'dark' ? merged.theme : 'system',\n accentH: Number.isFinite(merged.accentH) ? ((Math.round(merged.accentH) % 360) + 360) % 360 : DEFAULT_ACCENT.h,\n accentS: Number.isFinite(merged.accentS) ? clamp(Math.round(merged.accentS), 0, 100) : DEFAULT_ACCENT.s,\n accentL: Number.isFinite(merged.accentL)\n ? clamp(Math.round(merged.accentL), ACCENT_L_MIN, ACCENT_L_MAX)\n : DEFAULT_ACCENT.l,\n density: isDensity(merged.density) ? merged.density : 'comfortable',\n uiScale: isUiScale(merged.uiScale) ? merged.uiScale : 'default',\n displayFont: isDisplayFont(merged.displayFont) ? merged.displayFont : 'grotesk',\n radius: isRadius(merged.radius) ? merged.radius : 'default',\n motion,\n reduceMotion: resolveReduceMotion(motion),\n };\n}\n\nfunction load(): Appearance {\n const base = defaultAppearance();\n let stored: Partial<StoredAppearance> = {};\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (raw) {\n const parsed: unknown = JSON.parse(raw);\n if (parsed && typeof parsed === 'object') stored = parsed as Partial<StoredAppearance>;\n }\n } catch {\n // Unavailable storage, quota errors, or corrupt JSON all mean the same\n // thing here: fall back to defaults rather than failing to boot.\n }\n // Drop any `reduceMotion` a pre-tri-state build left behind: it was written\n // by every unrelated appearance change, so it records nothing the operator\n // decided. Without `motion`, the media query decides — as it should.\n const { reduceMotion: _legacy, ...rest } = stored as Partial<StoredAppearance> & { reduceMotion?: unknown };\n return normalize(base, { ...rest, theme: getTheme() });\n}\n\nfunction persist(value: Appearance): void {\n try {\n // theme is deliberately excluded — theme-store.ts owns its own key.\n // reduceMotion is excluded because it is derived: persisting it is what\n // let an accent drag silently override the OS preference.\n const { theme: _theme, reduceMotion: _derived, ...rest } = value;\n localStorage.setItem(STORAGE_KEY, JSON.stringify(rest satisfies StoredAppearance));\n } catch {\n /* best-effort persistence only */\n }\n}\n\nconst FONT_VAR: Record<DisplayFont, string | null> = {\n // null = remove the inline override and fall back to the token default.\n grotesk: null,\n ui: 'var(--font-ui)',\n mono: 'var(--font-mono)',\n};\n\n/**\n * Write the settings onto <html>. The accent is written as the three raw\n * channels tokens.css derives its whole ramp from, so one paint recolors\n * links, focus rings, primary buttons, meters and charts together.\n */\nexport function applyAppearance(value: Appearance): void {\n if (typeof document === 'undefined') return;\n const root = document.documentElement;\n root.style.setProperty('--accent-h', String(value.accentH));\n root.style.setProperty('--accent-s', `${value.accentS}%`);\n root.style.setProperty('--accent-l', `${value.accentL}%`);\n\n const font = FONT_VAR[value.displayFont];\n if (font === null) root.style.removeProperty('--font-display');\n else root.style.setProperty('--font-display', font);\n\n root.setAttribute('data-density', value.density);\n // The display size is an ATTRIBUTE, not an inline custom property, for the\n // same reason the theme is: tokens.css owns the numbers, and a\n // `[data-ui-scale]` block can be read, tested and retuned in the stylesheet\n // that every other token lives in. Writing `--ui-scale` inline here would\n // put half the design system in a TypeScript file.\n root.setAttribute('data-ui-scale', value.uiScale);\n root.setAttribute('data-radius', value.radius);\n // 'system' removes the attribute so the `prefers-reduced-motion` media\n // queries decide on their own. \"full\" is meaningful, not a no-op: it opts an\n // operator explicitly OUT of the OS preference (tokens.css and the four\n // Verse stylesheets guard their media query with :not([data-motion=\"full\"])),\n // which is precisely why it must never be written by default.\n if (value.motion === 'system') root.removeAttribute('data-motion');\n else root.setAttribute('data-motion', value.motion);\n}\n\nlet current: Appearance = load();\napplyAppearance(current);\n\nconst listeners = new Set<() => void>();\n\nfunction emit(): void {\n for (const l of listeners) l();\n}\n\n// The theme can also be changed from the rail's quick-toggle and the command\n// palette (both go through theme-store). Mirror those into the snapshot so a\n// Settings panel rendered at the same time never shows a stale radio.\nsubscribeTheme(() => {\n if (current.theme === getTheme()) return;\n current = { ...current, theme: getTheme() };\n emit();\n});\n\nexport function subscribeAppearance(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\n/** Stable snapshot — a NEW object on every change (useSyncExternalStore uses Object.is). */\nexport function getAppearance(): Appearance {\n return current;\n}\n\n/** Apply and persist a partial change. Unknown/out-of-range values are coerced. */\nexport function setAppearance(patch: Partial<Appearance>): void {\n const next = normalize(current, patch);\n const themeChanged = next.theme !== current.theme;\n current = next;\n applyAppearance(current);\n persist(current);\n // Delegated last so theme-store's own listeners see the final snapshot.\n if (themeChanged) setTheme(current.theme);\n emit();\n}\n\n/** Back to the shipped defaults, including the theme and the stored blob. */\nexport function resetAppearance(): void {\n try {\n localStorage.removeItem(STORAGE_KEY);\n } catch {\n /* best-effort */\n }\n current = defaultAppearance();\n applyAppearance(current);\n setTheme('system');\n emit();\n}\n\nexport const ACCENT_LIGHTNESS_RANGE = { min: ACCENT_L_MIN, max: ACCENT_L_MAX } as const;\nexport const APPEARANCE_STORAGE_KEY = STORAGE_KEY;\n","/**\n * data/hooks.ts — React bindings over the framework-free stores in\n * cache.ts / auth-store.ts / sse.ts. This is the only file in the data\n * layer that imports React; every module underneath it stays plain\n * TypeScript so it's trivially unit-testable without a DOM.\n */\nimport { useCallback, useEffect, useSyncExternalStore } from 'react';\nimport { ensureQuery, getQuerySnapshot, refetchQuery, subscribeQuery, type QueryEntry } from './cache.js';\nimport type { QueryDef } from './queries.js';\nimport {\n getAuthSnapshot,\n subscribeAuth,\n type AuthPhase,\n hasMutationHold,\n getMutationToken,\n setMutationToken as storeSetMutationToken,\n touchMutationHold,\n clearMutationToken,\n} from './auth-store.js';\nimport { onSseEvent, type SseEventName } from './sse.js';\nimport { subscribeTheme, getTheme, setTheme, cycleTheme, type ThemePreference } from './theme-store.js';\nimport {\n getAppearance,\n resetAppearance,\n setAppearance,\n subscribeAppearance,\n type Appearance,\n} from './appearance-store.js';\n\n/**\n * How long a cached value is accepted on MOUNT without re-reading it.\n *\n * A section that re-mounts inside this window (rail switch, a parent\n * re-keying, React 18's double-invoked effects in StrictMode) issues no\n * request at all. Ten seconds is short enough that any surface a human\n * revisits deliberately still re-reads, and long enough that the Usage\n * section's seven probe-spawning reads happen ONCE per visit rather than\n * once per mount.\n *\n * This does not make data stale: keys with an SSE event still refresh the\n * instant the server says they changed (sse.ts → invalidate, which is not\n * freshness-gated), and `useRefetch` always ignores it.\n */\nexport const DEFAULT_QUERY_FRESH_MS = 10_000;\n\nexport interface UseQueryOptions {\n /** Override `DEFAULT_QUERY_FRESH_MS` for this subscription. 0 = always fetch. */\n freshMs?: number;\n}\n\n/**\n * Subscribe to a resource. Fetches on mount (and on `key` change) unless the\n * cached value is still fresh, stays subscribed to background refreshes\n * triggered by sse.ts, and NEVER clears `data` while refetching —\n * `status === 'refreshing'` is the signal to show a subtle indicator instead\n * of a skeleton, `status === 'loading'` (no data yet at all) is the only\n * state that should render a skeleton.\n */\nexport function useQuery<T>(def: QueryDef<T>, options?: UseQueryOptions): QueryEntry<T> {\n const freshMs = options?.freshMs ?? DEFAULT_QUERY_FRESH_MS;\n const snapshot = useSyncExternalStore(\n useCallback((listener) => subscribeQuery(def.key, listener), [def.key]),\n () => getQuerySnapshot<T>(def.key),\n () => getQuerySnapshot<T>(def.key),\n );\n\n useEffect(() => {\n // Cache refreshers outlive any one component. Binding the stored fetcher\n // to this component's AbortSignal poisons later SSE invalidations after\n // unmount, so the shared cache owns the request lifetime.\n void ensureQuery(def.key, () => def.fetch(), freshMs);\n // Re-fetch only when the resource identity changes, not on every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- def.fetch is re-created per render on purpose; only def.key identifies the resource.\n }, [def.key, freshMs]);\n\n return snapshot;\n}\n\n/**\n * Manually trigger a re-fetch without waiting for SSE — a \"Retry\" button, a\n * \"Refresh\" button, or a poll loop.\n *\n * Ignores freshness always, and joins an in-flight read only while that read\n * is younger than `REFETCH_JOIN_WINDOW_MS`. Both halves matter, because this\n * one hook backs two different callers:\n *\n * - A poll (UniverseView ticks every 3–15s and again on visibilitychange)\n * landing on a read that started milliseconds ago SHOULD coalesce with\n * it; issuing a second request there is pure waste.\n * - A person's click landing on a read that has been running for seconds\n * SHOULD NOT. That was the old behavior: the click joined the running\n * promise and returned data fetched before the click — a silent no-op,\n * and with local-model probes taking seconds under load the window was\n * wide enough to hit in ordinary use.\n *\n * For the strict guarantee — a request issued after the click, every time,\n * no matter what else is running — use `useRefresh`.\n */\nexport function useRefetch<T>(def: QueryDef<T>): () => void {\n return useCallback(() => {\n void refetchQuery(def.key, () => def.fetch());\n }, [def]);\n}\n\n/**\n * The explicit user-initiated refresh: ALWAYS issues a new request, never\n * joins one already running, and the read it issues is the one whose result\n * is kept even if an older read lands after it.\n *\n * Use this behind anything a person presses that promises them a fresh\n * reading — a Refresh button on a surface with no SSE invalidation, where\n * the whole reason the button exists is that nothing else will re-read.\n */\nexport function useRefresh<T>(def: QueryDef<T>): () => void {\n return useCallback(() => {\n void refetchQuery(def.key, () => def.fetch(), true);\n }, [def]);\n}\n\nexport function useAuthPhase(): AuthPhase {\n return useSyncExternalStore(subscribeAuth, () => getAuthSnapshot().phase, () => getAuthSnapshot().phase);\n}\n\nexport interface MutationHold {\n hasHold: boolean;\n token: string | null;\n heldUntil: number | null;\n setToken: (token: string) => void;\n touch: () => void;\n clear: () => void;\n}\n\nexport function useMutationHold(): MutationHold {\n const heldUntil = useSyncExternalStore(\n subscribeAuth,\n () => getAuthSnapshot().mutationTokenHeldUntil,\n () => getAuthSnapshot().mutationTokenHeldUntil,\n );\n return {\n hasHold: hasMutationHold(),\n token: getMutationToken(),\n heldUntil,\n setToken: storeSetMutationToken,\n touch: touchMutationHold,\n clear: clearMutationToken,\n };\n}\n\n/** Subscribe to a raw named SSE event for the lifetime of the component. */\nexport function useSseEvent(name: SseEventName, onEvent: (payload: unknown) => void): void {\n useEffect(() => onSseEvent(name, onEvent), [name, onEvent]);\n}\n\nexport interface ThemeControl {\n theme: ThemePreference;\n set: (next: ThemePreference) => void;\n cycle: () => void;\n}\n\n/** Shared with Topbar's theme button and the command palette's \"Toggle\n * theme\" action — both read/write data/theme-store.ts through this hook so\n * neither can drift out of sync with the other. */\nexport function useTheme(): ThemeControl {\n const theme = useSyncExternalStore(subscribeTheme, getTheme, getTheme);\n return { theme, set: setTheme, cycle: cycleTheme };\n}\n\nexport interface AppearanceControl {\n appearance: Appearance;\n /** Partial change; applied to <html> and persisted immediately (no save button). */\n set: (patch: Partial<Appearance>) => void;\n reset: () => void;\n}\n\n/**\n * The Settings section's appearance controls and anything that needs to READ\n * the current density/accent (e.g. a chart sizing rows) share this hook, for\n * the same reason useTheme exists: two readers of one external store, never\n * a second copy of the state.\n *\n * Importing this module is also what applies the stored appearance before\n * the first paint — every view imports hooks.ts, so appearance-store.ts is\n * evaluated (and has written <html>) before React renders anything.\n */\nexport function useAppearance(): AppearanceControl {\n const appearance = useSyncExternalStore(subscribeAppearance, getAppearance, getAppearance);\n return { appearance, set: setAppearance, reset: resetAppearance };\n}\n","/**\n * data/client.ts — thin typed HTTP client for the existing /api/* surface.\n * No redefinition of response shapes here — callers type each call with the\n * interfaces re-exported from ./api-types.ts.\n */\nimport { getReadClientProof, reportSessionExpired } from './auth-store.js';\n\nexport class ApiError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly path: string,\n /**\n * The server's own sentence about the refusal, when it sent one, without\n * the \"POST … failed (HTTP N)\" wrapper. A surface that wants to show the\n * operator why an action was refused should prefer this over `message`:\n * it is the text the route author wrote for a person to read.\n */\n public readonly detail: string | null = null,\n /**\n * The route's machine-readable refusal code (`VERSE_SESSION_NOT_FOUND`,\n * `VERSE_SESSION_BUSY`, …) when the body carried one; null otherwise. Lets a\n * caller branch on WHAT was refused without parsing the sentence.\n */\n public readonly code: string | null = null,\n ) {\n super(message);\n this.name = 'ApiError';\n }\n}\n\n/**\n * Raised for the 404 a mutation route answers when --allow-dispatch is off.\n *\n * That gate answers a bare `{ error: 'not found' }` — deliberately the same\n * body as a route that does not exist, so a read-only server does not\n * advertise its write surface. A route that DOES exist and refuses with a 404\n * of its own (\"session not found\") says so with a `code`; apiPost keeps those\n * as plain ApiErrors, so a deleted chat is never reported as \"this server is\n * read-only\".\n */\nexport class DispatchDisabledError extends ApiError {\n constructor(path: string) {\n super('This server was started without --allow-dispatch, so mutations are disabled.', 404, path);\n this.name = 'DispatchDisabledError';\n }\n}\n\n/** GET an authenticated read route. 401 reports session-expired and throws. */\nexport async function apiGet<T>(path: string, signal?: AbortSignal): Promise<T> {\n const res = await fetch(path, {\n method: 'GET',\n credentials: 'same-origin',\n headers: { 'x-ashlr-read-client': getReadClientProof() },\n signal,\n });\n if (res.status === 401) {\n reportSessionExpired();\n throw new ApiError('Read session expired.', 401, path);\n }\n if (!res.ok) {\n // Keep the route's own sentence and code (e.g. the grant draft's\n // `no-trust-roots`) so a reader can branch on WHAT was refused.\n const { detail, code } = await readRefusal(res);\n throw new ApiError(`GET ${path} failed (HTTP ${res.status}).`, res.status, path, detail || null, code);\n }\n return (await res.json()) as T;\n}\n\n/**\n * POST a mutating route with the raw mutation token. `mutationToken` must\n * come from the caller (data/mutations.ts pulls it from auth-store) — this\n * function does not read auth-store itself so it stays trivially testable.\n */\nexport async function apiPost<T>(\n path: string,\n body: unknown,\n mutationToken: string,\n signal?: AbortSignal,\n): Promise<T> {\n const res = await fetch(path, {\n method: 'POST',\n credentials: 'same-origin',\n headers: {\n 'Content-Type': 'application/json',\n 'x-ashlr-token': mutationToken,\n },\n body: JSON.stringify(body ?? {}),\n signal,\n });\n if (res.status === 401) {\n throw new ApiError('Mutation token was rejected.', 401, path);\n }\n if (!res.ok) {\n const { detail, code } = await readRefusal(res);\n // Only a CODELESS 404 is the dispatch gate (see DispatchDisabledError).\n // An unknown sub-path on an older server is codeless too and lands here\n // as well — the two are indistinguishable by design, so callers that\n // word this error must not assert which one it was.\n if (res.status === 404 && code === null) {\n throw new DispatchDisabledError(path);\n }\n throw new ApiError(\n `POST ${path} failed (HTTP ${res.status})${detail ? `: ${detail}` : ''}.`,\n res.status,\n path,\n detail || null,\n code,\n );\n }\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n}\n\n/**\n * The refusal sentence and code from a failed POST's body, if it had any.\n *\n * `error` is the documented refusal field, but the Verse control plane\n * answers a refused daemon/scope action with a full result body whose\n * plain-language sentence lives in `note` and which has NO `error` key at all\n * (see VerseDaemonActionResult). Reading only `error` threw away sentences\n * like \"no repositories are enrolled, so the loop would do nothing\" and left\n * the caller with a bare status code to guess from.\n */\nasync function readRefusal(res: Response): Promise<{ detail: string; code: string | null }> {\n try {\n const j = (await res.json()) as { error?: unknown; note?: unknown; code?: unknown };\n const detail = typeof j.error === 'string' && j.error ? j.error : typeof j.note === 'string' ? j.note : '';\n const code = typeof j.code === 'string' && j.code ? j.code : null;\n return { detail, code };\n } catch {\n /* body wasn't JSON */\n return { detail: '', code: null };\n }\n}\n\n/** Build the SSE URL, carrying the client proof as the query-string proof\n * EventSource can't attach as a header (see server.ts readSessionClientProof). */\nexport function eventsUrl(): string {\n return `/api/events?client=${encodeURIComponent(getReadClientProof())}`;\n}\n",".grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));\n gap: var(--space-4);\n}\n\n/* No border and no fill: a skeleton stands in for CONTENT, and drawing it as\n a bordered card meant the placeholder had a box the real card does not\n (DESIGN-V2 §1.2) and the layout visibly lost an outline on first paint. */\n.card {\n display: flex;\n flex-direction: column;\n gap: var(--space-3);\n padding: var(--space-4) 0;\n}\n\n/* Each bar matches the LINE BOX of the text it replaces, so nothing reflows\n when the real content lands: a label line, a display-face stat, a caption. */\n.title {\n height: var(--text-xs-line);\n width: 60%;\n border-radius: var(--radius-xs);\n}\n\n.stat {\n height: var(--text-2xl-line);\n width: 40%;\n border-radius: var(--radius-xs);\n}\n\n.caption {\n height: var(--text-2xs-line);\n width: 80%;\n border-radius: var(--radius-xs);\n}\n\n.line {\n height: var(--text-base-line);\n border-radius: var(--radius-xs);\n}\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--space-4);\n min-height: var(--density-row);\n padding: var(--space-3) var(--space-4);\n border-bottom: 1px solid var(--border-subtle);\n}\n\n.cell {\n height: var(--text-sm-line);\n border-radius: var(--radius-xs);\n}\n","/**\n * components/primitives/Skeleton.tsx — loading placeholders. Used ONLY for\n * `status === 'loading'` (no data at all yet). A resource that already has\n * data and is merely refreshing should show <RefreshIndicator/> instead —\n * never re-blank a populated view (see DESIGN.md \"no blank screens\").\n */\nimport styles from './Skeleton.module.css';\n\nexport function SkeletonLine({ width = '100%' }: { width?: string }) {\n return <div className={`skeleton ${styles.line}`} style={{ width }} />;\n}\n\nexport function SkeletonCard() {\n return (\n <div className={styles.card}>\n <div className={`skeleton ${styles.title}`} />\n <div className={`skeleton ${styles.stat}`} />\n <div className={`skeleton ${styles.caption}`} />\n </div>\n );\n}\n\nexport function SkeletonRow() {\n return (\n <div className={styles.row}>\n <div className={`skeleton ${styles.cell}`} style={{ width: '18%' }} />\n <div className={`skeleton ${styles.cell}`} style={{ width: '38%' }} />\n <div className={`skeleton ${styles.cell}`} style={{ width: '14%' }} />\n <div className={`skeleton ${styles.cell}`} style={{ width: '10%' }} />\n </div>\n );\n}\n\nexport function SkeletonCardGrid({ count = 4 }: { count?: number }) {\n return (\n <div className={styles.grid}>\n {Array.from({ length: count }, (_, i) => (\n <SkeletonCard key={i} />\n ))}\n </div>\n );\n}\n",".skipLink {\n position: fixed;\n top: -100px;\n left: var(--space-3);\n z-index: var(--z-toast);\n background: var(--accent-500);\n color: var(--text-on-accent);\n padding: var(--space-2) var(--space-4);\n border-radius: var(--radius-md);\n font-size: var(--text-sm-size);\n font-weight: var(--font-weight-medium);\n transition: top var(--duration-fast) var(--ease-standard);\n}\n\n.skipLink:focus-visible {\n top: var(--space-3);\n box-shadow: var(--shadow-focus-ring);\n}\n","/**\n * app/SkipToContent.tsx — a real skip link, deliberately NOT `<a\n * href=\"#main-content\">`: this app uses HashRouter, so a literal `#`\n * anchor would rewrite location.hash and hijack client-side routing\n * instead of just scrolling. A button that imperatively focuses the\n * `#main-content` element (set on <main> in components/layout/Shell.tsx)\n * gets the same keyboard behavior without touching the route.\n */\nimport styles from './SkipToContent.module.css';\n\nexport function SkipToContent() {\n return (\n <button\n type=\"button\"\n className={styles.skipLink}\n onClick={() => {\n const main = document.getElementById('main-content');\n main?.focus();\n main?.scrollIntoView({ block: 'start' });\n }}\n >\n Skip to content\n </button>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"global-y_exIyAP.js","names":[],"sources":["../../../../../../src/web-ui/data/cache.ts","../../../../../../src/web-ui/routes/verse/chat/composer-memory.ts","../../../../../../src/web-ui/data/auth-store.ts","../../../../../../src/web-ui/data/sse.ts","../../../../../../src/web-ui/data/theme-store.ts","../../../../../../src/web-ui/data/appearance-store.ts","../../../../../../src/web-ui/data/hooks.ts","../../../../../../src/web-ui/data/client.ts","../../../../../../src/web-ui/components/primitives/Skeleton.module.css","../../../../../../src/web-ui/components/primitives/Skeleton.tsx","../../../../../../src/web-ui/app/SkipToContent.module.css","../../../../../../src/web-ui/app/SkipToContent.tsx"],"sourcesContent":["/**\n * data/cache.ts — the resource cache every view reads through. This is what\n * makes tab switches instant instead of blanking to \"Loading…\": a resource\n * fetched once stays in the cache and renders immediately (possibly stale)\n * on the next mount, with a background refresh kicked off silently behind\n * it. No framework dependency — a plain keyed store consumed via\n * useSyncExternalStore (see hooks.ts), matching the pattern already used\n * for auth-store.ts.\n *\n * Design choices, spelled out because a future agent will extend this:\n * - In-flight de-dupe: two components requesting the same key at once\n * share one fetch, not two.\n * - Never clears `data` on refetch. `status` distinguishes 'loading'\n * (no data yet — show a skeleton) from 'refreshing' (stale data still\n * shown — show a subtle indicator) from 'success'/'error'.\n * - `invalidate(key)` is how the SSE layer (sse.ts) tells the cache \"the\n * server just told us this changed\" without either module importing\n * React.\n * - useSyncExternalStore requires getSnapshot() to return a NEW object\n * reference whenever the observable state actually changes (it detects\n * updates via Object.is, not deep equality) — mutating one long-lived\n * entry object in place and handing back the same reference would fire\n * subscribers that never actually re-render. Every state transition\n * below therefore replaces `bookkeeping.snapshot` wholesale instead of\n * patching fields on it.\n *\n * ---------------------------------------------------------------------------\n * V2.1 — the request burst, and the three defects that lived in this file\n * ---------------------------------------------------------------------------\n *\n * Mounting the Usage section fires SEVEN reads at once (accounts, control,\n * bootstrap, usage-series, local-models, /api/control, /api/usage), several\n * of which spawn short-lived probe processes server-side. That burst starved\n * the 2s local-runtime probe timeout and made a healthy Ollama report as\n * unreachable roughly one burst in ten. The probe timeouts were hardened\n * server-side; the burst itself was still this file's problem. Three changes:\n *\n * 1. CONCURRENCY GATE. Every fetch this cache issues passes through a\n * process-wide gate capped at QUERY_CONCURRENCY_LIMIT. The same seven\n * reads still happen, but at most four are outstanding at once, so the\n * server is never asked to run seven probe-spawning handlers in\n * parallel. Nothing is dropped, nothing is delayed by a timer — a\n * queued task starts the microtask a slot frees. The gate is what makes\n * the probe timeout survivable, and it costs the caller nothing: the\n * browser already caps at ~6 sockets per origin, so this is a cap the\n * requests were going to hit anyway, applied where we can order it.\n *\n * 2. FRESHNESS. `ensureQuery(key, fetcher, maxAgeMs)` is the MOUNT path: it\n * skips the request entirely when the cached value is younger than\n * `maxAgeMs`. Re-entering a section you were just in is now zero\n * requests instead of seven. `runQuery` keeps its old always-fetch\n * semantics because the SSE layer and the prefetchers depend on it.\n *\n * 3. REFRESH THAT ACTUALLY RE-READS. `runQuery`'s in-flight de-dupe is\n * right for two components mounting at once and WRONG for a person\n * pressing Refresh: a click landing while a read is in flight joined\n * the running promise and returned data fetched before the click — a\n * silent no-op, and with local-models taking seconds under load the\n * window was real. `refetchQuery` fixes it in two steps, because the\n * same hook backs both a person's button and a 3s poll loop:\n * - by default it joins an in-flight read ONLY while that read is\n * younger than REFETCH_JOIN_WINDOW_MS. A poll tick landing on a\n * read that started milliseconds ago still coalesces (which is the\n * documented contract UniverseView's poll relies on); a click\n * landing on a read that started seconds ago issues a new request.\n * - with `force` it never joins at all. That is the strict guarantee\n * for an explicit user-initiated refresh (hooks.ts `useRefresh`).\n * Either way a newer request SUPERSEDES an older one by sequence\n * number, so the last read ISSUED is the one whose result is kept even\n * if an earlier one lands later.\n *\n * And the latent one: `evictAll()` used to call `store.clear()`, which\n * dropped the Bookkeeping objects that mounted components had registered\n * their `useSyncExternalStore` listeners on. Those components stayed\n * subscribed to an orphan Set that nothing would ever notify again — they\n * would never re-render for the rest of their life. Evict now RESETS an\n * entry that still has subscribers (and deletes only the unobserved ones),\n * so a subscriber always survives an evict.\n */\n\nexport type QueryStatus = 'idle' | 'loading' | 'refreshing' | 'success' | 'error';\n\nexport interface QueryEntry<T> {\n data: T | undefined;\n error: Error | undefined;\n status: QueryStatus;\n updatedAt: number | null;\n}\n\ninterface Bookkeeping<T = unknown> {\n snapshot: QueryEntry<T>;\n inFlight: Promise<void> | null;\n fetcher: (() => Promise<T>) | null;\n subscribers: Set<() => void>;\n /** Sequence number of the newest request ISSUED for this key. */\n issued: number;\n /** `Date.now()` when that newest request was issued. */\n issuedAt: number;\n /** Highest sequence number whose result has been written to `snapshot`. */\n applied: number;\n}\n\nconst IDLE_SNAPSHOT: QueryEntry<unknown> = { data: undefined, error: undefined, status: 'idle', updatedAt: null };\n\nconst store = new Map<string, Bookkeeping>();\n\n// ---------------------------------------------------------------------------\n// Concurrency gate\n// ---------------------------------------------------------------------------\n\n/**\n * How many cache-issued fetches may be outstanding at once, process-wide.\n *\n * Four, not one: the point is to stop a seven-way burst from spawning seven\n * concurrent server-side probes, not to serialize the app. Four keeps a\n * section's first paint fast while leaving the server two of its six sockets\n * for the SSE stream and any non-cache request.\n */\nexport const QUERY_CONCURRENCY_LIMIT = 4;\n\nlet activeFetches = 0;\nconst gateQueue: (() => void)[] = [];\n/** Peak concurrency observed since the gate was last reset. */\nlet peakConcurrency = 0;\n/**\n * Bumped by `resetQueryGate`. A fetch issued under an older generation no\n * longer owns a slot, so its eventual settlement must not decrement the new\n * generation's count — otherwise a request that never settles (a hung\n * socket, or a test stubbing fetch with a promise that never resolves) would\n * permanently consume capacity and starve every later read.\n */\nlet gateGeneration = 0;\n\nfunction releaseSlot(): void {\n activeFetches -= 1;\n const next = gateQueue.shift();\n if (next) next();\n}\n\n/**\n * Run `task` when a slot is free. Purely promise-driven — no timers, so this\n * is safe under fake timers and adds no latency beyond one microtask.\n */\nfunction schedule<T>(task: () => Promise<T>): Promise<T> {\n const start = (): Promise<T> => {\n const generation = gateGeneration;\n activeFetches += 1;\n if (activeFetches > peakConcurrency) peakConcurrency = activeFetches;\n const done = (): void => {\n if (generation === gateGeneration) releaseSlot();\n };\n let running: Promise<T>;\n try {\n running = task();\n } catch (err) {\n // A fetcher that throws synchronously must still free its slot.\n done();\n return Promise.reject(err instanceof Error ? err : new Error(String(err)));\n }\n return running.then(\n (value) => {\n done();\n return value;\n },\n (err: unknown) => {\n done();\n throw err;\n },\n );\n };\n\n if (activeFetches < QUERY_CONCURRENCY_LIMIT) return start();\n return new Promise<T>((resolve, reject) => {\n gateQueue.push(() => {\n start().then(resolve, reject);\n });\n });\n}\n\n/**\n * Abandon the gate's accounting: forget how many fetches are outstanding and\n * let everything queued run now. Called from `evictAll()`, which is both the\n * logout path and every test's `beforeEach` — a read that outlives the state\n * it was issued for has no claim on the next one's capacity.\n *\n * Nothing is cancelled. Requests already in flight still settle and still\n * write their result unless the key was evicted underneath them.\n */\nexport function resetQueryGate(): void {\n gateGeneration += 1;\n activeFetches = 0;\n peakConcurrency = 0;\n for (const start of gateQueue.splice(0)) start();\n}\n\n/**\n * What the gate is doing right now. The tests read it, and so does the shell's\n * idle warm-up (routes/verse/shell/warmup.ts): it starts nothing while any\n * read is running or queued here.\n */\nexport function queryGateStats(): { active: number; queued: number; peak: number } {\n return { active: activeFetches, queued: gateQueue.length, peak: peakConcurrency };\n}\n\n// ---------------------------------------------------------------------------\n// Entries\n// ---------------------------------------------------------------------------\n\nfunction entryFor<T>(key: string): Bookkeeping<T> {\n let e = store.get(key) as Bookkeeping<T> | undefined;\n if (!e) {\n e = {\n snapshot: IDLE_SNAPSHOT as QueryEntry<T>,\n inFlight: null,\n fetcher: null,\n subscribers: new Set(),\n issued: 0,\n issuedAt: 0,\n applied: 0,\n };\n store.set(key, e);\n }\n return e;\n}\n\nfunction setSnapshot<T>(e: Bookkeeping<T>, patch: Partial<QueryEntry<T>>): void {\n e.snapshot = { ...e.snapshot, ...patch };\n}\n\nfunction notify(key: string): void {\n const e = store.get(key);\n if (!e) return;\n for (const s of e.subscribers) s();\n}\n\n/**\n * Structural equality for API payloads, used to decide whether a refresh\n * actually changed anything.\n *\n * A Refresh that re-reads seven routes and finds six of them byte-identical\n * should re-render nothing for those six. Keeping the PREVIOUS `data`\n * reference when the new body is deep-equal does exactly that: the snapshot\n * object still changes (status and updatedAt did), so useSyncExternalStore\n * still fires, but every `useMemo`/`memo` keyed on `data` downstream skips —\n * and the Usage section's models are all keyed on `data`.\n *\n * Deliberately structural rather than JSON.stringify: key order is not\n * significant in these payloads, and stringify would also throw on the\n * (never-seen, but cheap to survive) cyclic body.\n */\nfunction sameData(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== typeof b) return false;\n if (a === null || b === null || typeof a !== 'object') return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((item, i) => sameData(item, b[i]));\n }\n const left = a as Record<string, unknown>;\n const right = b as Record<string, unknown>;\n const leftKeys = Object.keys(left);\n if (leftKeys.length !== Object.keys(right).length) return false;\n return leftKeys.every((k) => Object.prototype.hasOwnProperty.call(right, k) && sameData(left[k], right[k]));\n}\n\nexport function subscribeQuery(key: string, listener: () => void): () => void {\n const e = entryFor(key);\n e.subscribers.add(listener);\n return () => {\n e.subscribers.delete(listener);\n };\n}\n\nexport function getQuerySnapshot<T>(key: string): QueryEntry<T> {\n return entryFor<T>(key).snapshot;\n}\n\n/**\n * How long an in-flight read is accepted as already satisfying a refetch.\n *\n * A poll tick (UniverseView refreshes every 3–15s and again on\n * visibilitychange) that lands on a read issued milliseconds ago should\n * coalesce with it — that is the behavior its own comment and tests pin\n * down. A person's Refresh landing on a read that has been running for\n * seconds should not: they are asking about the world as of the click.\n */\nexport const REFETCH_JOIN_WINDOW_MS = 250;\n\nexport interface QueryRunOptions {\n /**\n * Skip the request when the cached value is younger than this many ms.\n * Undefined (the default) always fetches.\n */\n maxAgeMs?: number;\n /**\n * Join an in-flight read only while it is younger than this. Undefined\n * joins any in-flight read (the original de-dupe).\n */\n joinWindowMs?: number;\n /**\n * User-initiated. Never joins an in-flight read and never honours\n * `maxAgeMs` — pressing Refresh must produce a request that was issued\n * after the click.\n */\n force?: boolean;\n}\n\nfunction isFresh(e: Bookkeeping, maxAgeMs: number): boolean {\n if (e.snapshot.updatedAt === null) return false;\n if (e.snapshot.status === 'error') return false;\n return Date.now() - e.snapshot.updatedAt < maxAgeMs;\n}\n\nfunction mayJoin(e: Bookkeeping, options: QueryRunOptions): boolean {\n if (e.inFlight === null) return false;\n if (options.joinWindowMs === undefined) return true;\n return Date.now() - e.issuedAt < options.joinWindowMs;\n}\n\nfunction execute<T>(key: string, fetcher: () => Promise<T>, options: QueryRunOptions): Promise<void> {\n const e = entryFor<T>(key);\n e.fetcher = fetcher;\n\n if (!options.force) {\n // Two components mounting at once share one request.\n if (mayJoin(e, options)) return e.inFlight!;\n // Already fresh enough for this caller: no request at all.\n if (options.maxAgeMs !== undefined && isFresh(e, options.maxAgeMs)) return Promise.resolve();\n }\n\n const seq = (e.issued += 1);\n e.issuedAt = Date.now();\n setSnapshot(e, { status: e.snapshot.data !== undefined ? 'refreshing' : 'loading' });\n notify(key);\n\n const run = schedule(fetcher)\n .then((data) => {\n // A superseded request (a forced refresh was issued after this one, or\n // the key was evicted) must never overwrite a newer answer.\n if (seq < e.applied) return;\n e.applied = seq;\n const unchanged = e.snapshot.data !== undefined && sameData(e.snapshot.data, data);\n setSnapshot(e, {\n data: unchanged ? e.snapshot.data : data,\n error: undefined,\n status: 'success',\n updatedAt: Date.now(),\n });\n })\n .catch((err: unknown) => {\n if (seq < e.applied) return;\n e.applied = seq;\n setSnapshot(e, { error: err instanceof Error ? err : new Error(String(err)), status: 'error' });\n })\n .finally(() => {\n // Only the newest request owns `inFlight`; an older, superseded one\n // finishing must not clear the newer one's slot.\n if (e.issued === seq) e.inFlight = null;\n notify(key);\n });\n\n e.inFlight = run;\n return run;\n}\n\n/**\n * Kick off (or join) a fetch for `key`. Safe to call redundantly — repeated\n * calls with an in-flight fetch are no-ops. Registers `fetcher` on the\n * entry so invalidate() can re-run it later without the caller re-supplying\n * it (e.g. from the SSE layer, which doesn't have per-view fetch closures).\n */\nexport function runQuery<T>(key: string, fetcher: () => Promise<T>): Promise<void> {\n return execute(key, fetcher, {});\n}\n\n/**\n * The MOUNT path (hooks.ts `useQuery`). Identical to `runQuery` except that a\n * value fetched less than `maxAgeMs` ago is accepted as-is and no request is\n * made. This is what turns \"switch to Usage and back\" from fourteen requests\n * into seven.\n */\nexport function ensureQuery<T>(key: string, fetcher: () => Promise<T>, maxAgeMs: number): Promise<void> {\n return execute(key, fetcher, { maxAgeMs });\n}\n\n/**\n * The REFRESH path (hooks.ts `useRefetch` / `useRefresh`).\n *\n * Default (`force: false`): joins an in-flight read only while that read is\n * younger than REFETCH_JOIN_WINDOW_MS, so a burst of poll ticks still\n * collapses into one request while a click on a long-running read gets a\n * real re-read.\n *\n * `force: true`: never joins. Always issues a request made after the call,\n * and wins the race against anything already running regardless of which\n * lands first. This is the guarantee an explicit user-initiated refresh\n * needs.\n */\nexport function refetchQuery<T>(key: string, fetcher: () => Promise<T>, force = false): Promise<void> {\n return execute(key, fetcher, force ? { force: true } : { joinWindowMs: REFETCH_JOIN_WINDOW_MS });\n}\n\n/** Re-run the last fetcher registered for `key`, if any (used by sse.ts). */\nexport function invalidate(key: string): void {\n const e = store.get(key);\n if (!e || !e.fetcher) return;\n void runQuery(key, e.fetcher);\n}\n\n/** Re-run the fetcher for every cached key starting with `prefix`. For\n * parameterized resources (per-filter inbox list queries, per-id inbox\n * detail queries) the exact live key isn't known ahead of time — used by\n * sse.ts and data/mutations.ts so those views live-update too. */\nexport function invalidatePrefix(prefix: string): void {\n for (const key of store.keys()) {\n if (key.startsWith(prefix)) invalidate(key);\n }\n}\n\n/**\n * `invalidatePrefix`, but only for keys a mounted component is SUBSCRIBED to.\n *\n * The read-session renewal sweep (auth-store.ts) uses it. An entry nobody is\n * reading — a surface the shell's idle warm-up fetched but the operator never\n * opened, or one they left — is left exactly as it is: whoever mounts it next\n * goes through `ensureQuery`, which re-reads it if it is older than that\n * caller accepts or if it failed (a read that 401ed is 'error', never fresh).\n * Re-running those fetchers on every 15-minute renewal was pure background\n * traffic through the same gate as the chat reads waiting on their retry.\n */\nexport function invalidateObserved(prefix: string): void {\n for (const [key, e] of store) {\n if (e.subscribers.size > 0 && key.startsWith(prefix)) invalidate(key);\n }\n}\n\n/**\n * Reset one entry to idle WITHOUT dropping its subscriber set, and supersede\n * anything still in flight for it so a late result cannot repopulate the key\n * after it was deliberately cleared.\n */\nfunction resetEntry(key: string, e: Bookkeeping): void {\n e.snapshot = IDLE_SNAPSHOT as QueryEntry<unknown>;\n e.fetcher = null;\n e.inFlight = null;\n e.issued += 1;\n e.applied = e.issued;\n notify(key);\n}\n\n/** Drop a key entirely (rare — e.g. on logout, so stale data never leaks\n * into the next session's first paint). An entry that still has mounted\n * subscribers is RESET rather than deleted: deleting it would orphan the\n * listener set those components registered on, leaving them subscribed to\n * something nothing can notify. */\nexport function evict(key: string): void {\n const e = store.get(key);\n if (!e) return;\n if (e.subscribers.size === 0) {\n store.delete(key);\n return;\n }\n resetEntry(key, e);\n}\n\nexport function evictAll(): void {\n resetQueryGate();\n for (const [key, e] of [...store]) {\n if (e.subscribers.size === 0) {\n store.delete(key);\n continue;\n }\n resetEntry(key, e);\n }\n}\n","/**\n * routes/verse/chat/composer-memory.ts — the composer's memory: drafts\n * and sent messages, per chat.\n *\n * Two things a long agentic day needs from a message box and does not get\n * from a bare `useState`:\n *\n * 1. A draft survives switching chats and reloading the app. Half-written\n * instructions are expensive to re-type and are lost today the moment\n * ⌘K lands somewhere else.\n * 2. What was already sent can be recalled with ↑, the way every shell\n * works, because the second attempt at a prompt is usually the first\n * one plus a clause.\n *\n * Both are per-session and stored in `localStorage`, bounded so a long-lived\n * browser cannot accumulate an unbounded map. Every access is wrapped: a\n * private window with storage blocked still runs the app.\n *\n * Split from composer-state.ts (which re-exports all of it) so the modules on\n * the chat first-paint path that only FORGET or CLEAR this memory (the Chat\n * section on delete, auth-store on logout) do not pull the cost estimator and\n * context-math into the chat first-paint critical JS.\n */\n\nconst DRAFT_KEY = 'ashlr.verse.drafts.v1';\nconst SENT_KEY = 'ashlr.verse.sent.v1';\n\n/** Sessions remembered, newest last. */\nconst SESSION_LIMIT = 40;\n/** Messages recalled per session. */\nconst HISTORY_LIMIT = 25;\n/** Matches VERSE_MAX_TURN_TEXT_BYTES; a draft over it cannot be sent anyway. */\nconst TEXT_LIMIT = 64 * 1024;\n\nfunction readMap<T>(key: string, parse: (value: unknown) => T | null): Record<string, T> {\n const out: Record<string, T> = {};\n try {\n const raw = localStorage.getItem(key);\n const parsed = raw ? (JSON.parse(raw) as unknown) : null;\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n for (const [id, value] of Object.entries(parsed as Record<string, unknown>)) {\n const clean = parse(value);\n if (clean !== null) out[id] = clean;\n }\n }\n } catch {\n /* storage unavailable or corrupt — an empty memory is a valid one */\n }\n return out;\n}\n\n/** True when the write landed; false when storage refused it (blocked, over quota). */\nfunction writeMap(key: string, map: Record<string, unknown>): boolean {\n // Insertion order is recency order: every write deletes before re-inserting.\n const ids = Object.keys(map);\n for (const stale of ids.slice(0, Math.max(0, ids.length - SESSION_LIMIT))) delete map[stale];\n try {\n localStorage.setItem(key, JSON.stringify(map));\n return true;\n } catch {\n /* best-effort — the caller decides whether a refusal loses anything */\n return false;\n }\n}\n\nconst asText = (value: unknown): string | null =>\n typeof value === 'string' && value.length > 0 ? value.slice(0, TEXT_LIMIT) : null;\n\nconst asHistory = (value: unknown): string[] | null => {\n if (!Array.isArray(value)) return null;\n const list = value.filter((v): v is string => typeof v === 'string' && v.length > 0)\n .slice(-HISTORY_LIMIT)\n .map((v) => v.slice(0, TEXT_LIMIT));\n return list.length > 0 ? list : null;\n};\n\n/**\n * Drafts whose last write storage REFUSED, held for the page's lifetime ('' =\n * a clear that could not be written) and consulted BEFORE storage.\n *\n * WHY: `localStorage` writes fail silently (blocked storage, a full quota),\n * and one draft is not a convenience but the only copy of something: the\n * handoff note. The dialog hands it over with `saveDraft` and the new chat's\n * Composer restores it with `loadDraft`; with storage refusing, the edited\n * note — possibly written after a paid summary turn — simply vanished. An\n * entry here is always newer than whatever storage holds for that chat, and a\n * later write that lands removes it, so storage is the answer again.\n */\nconst unsavedDrafts = new Map<string, string>();\n\n/** The unsent draft for this chat, or `''`. */\nexport function loadDraft(sessionId: string | null | undefined): string {\n if (!sessionId) return '';\n const unsaved = unsavedDrafts.get(sessionId);\n if (unsaved !== undefined) return unsaved;\n return readMap(DRAFT_KEY, asText)[sessionId] ?? '';\n}\n\n/** Persist (or clear, when empty) the draft for this chat. */\nexport function saveDraft(sessionId: string | null | undefined, text: string): void {\n if (!sessionId) return;\n const clean = text.trim().length > 0 ? text.slice(0, TEXT_LIMIT) : '';\n const map = readMap(DRAFT_KEY, asText);\n delete map[sessionId];\n if (clean) map[sessionId] = clean;\n unsavedDrafts.delete(sessionId);\n if (!writeMap(DRAFT_KEY, map)) {\n unsavedDrafts.set(sessionId, clean);\n // Bounded like storage: oldest refusals go first.\n for (const stale of [...unsavedDrafts.keys()].slice(0, Math.max(0, unsavedDrafts.size - SESSION_LIMIT))) unsavedDrafts.delete(stale);\n }\n}\n\n/** Messages sent in this chat, oldest first. */\nexport function loadHistory(sessionId: string | null | undefined): string[] {\n if (!sessionId) return [];\n return readMap(SENT_KEY, asHistory)[sessionId] ?? [];\n}\n\n/** Record a sent message; consecutive duplicates are not stored twice. */\nexport function pushHistory(sessionId: string | null | undefined, text: string): void {\n if (!sessionId || text.trim().length === 0) return;\n const map = readMap(SENT_KEY, asHistory);\n const current = map[sessionId] ?? [];\n delete map[sessionId];\n const next = current[current.length - 1] === text ? current : [...current, text.slice(0, TEXT_LIMIT)];\n map[sessionId] = next.slice(-HISTORY_LIMIT);\n writeMap(SENT_KEY, map);\n}\n\n/**\n * Forget everything this chat remembered — its unsent draft and its sent\n * messages.\n *\n * Deleting a chat removes its transcript from disk, but the verbatim prompts\n * stayed in browser storage under the dead session id until forty newer\n * sessions rotated them out.\n */\nexport function forgetComposerMemory(sessionId: string | null | undefined): void {\n if (!sessionId) return;\n unsavedDrafts.delete(sessionId);\n for (const [key, parse] of [\n [DRAFT_KEY, asText],\n [SENT_KEY, asHistory],\n ] as const) {\n const map: Record<string, unknown> = readMap(key, parse as (v: unknown) => unknown);\n if (!(sessionId in map)) continue;\n delete map[sessionId];\n writeMap(key, map);\n }\n}\n\n/**\n * Drop every remembered draft and sent message, for every session.\n *\n * Called on disconnect/logout alongside the query cache's `evictAll`, whose\n * contract is that stale data must not leak into the next session's first\n * paint. Prompt text is precisely the content that wipe exists for, and it is\n * the one thing that used to survive it.\n */\nexport function clearComposerMemory(): void {\n unsavedDrafts.clear();\n for (const key of [DRAFT_KEY, SENT_KEY]) {\n try {\n localStorage.removeItem(key);\n } catch {\n /* storage unavailable — nothing to clear */\n }\n }\n}\n\nexport const VERSE_DRAFT_STORAGE_KEY = DRAFT_KEY;\nexport const VERSE_SENT_STORAGE_KEY = SENT_KEY;\n","/**\n * data/auth-store.ts — the read-session + mutation-token authority for the\n * whole app. Mirrors the two-token model in src/core/web/server.ts exactly:\n *\n * READ authority — exchanged via POST /api/session (raw readToken + a\n * per-tab client proof) for an HttpOnly cookie good\n * for 15 minutes. The raw read token is remembered in\n * MEMORY ONLY (never any storage) for the tab's\n * lifetime so the 15-minute ticket can be renewed\n * silently when a GET/SSE comes back 401 — a live\n * chat must not turn into \"paste a 64-hex token every\n * 15 minutes\". The client proof is not a secret by\n * itself (it has no authority without the signed\n * cookie) and is kept in sessionStorage so a page\n * reload can keep using the still-valid cookie instead\n * of forcing re-entry.\n *\n * MUTATION authority — the raw x-ashlr-token header, required on every\n * mutating call, NEVER exchanged for a session and\n * NEVER persisted to any storage. Held in memory only,\n * for a bounded \"hold\" window, so approving a run of\n * proposals isn't one window.prompt() per click. The\n * hold is cleared on tab close, explicit lock, or\n * idle timeout.\n *\n * This module has no React dependency — it is a plain external store\n * consumed via useSyncExternalStore in hooks.ts, per the same pattern used\n * for the query cache.\n */\n\nimport { evictAll, invalidateObserved } from './cache.js';\n// The composer owns its own storage format, so the key names live with it\n// rather than being duplicated here where they could drift.\nimport { clearComposerMemory } from '../routes/verse/chat/composer-memory.js';\n\nconst READ_CLIENT_STORAGE_KEY = 'ashlr.readClientProof.v1';\nconst READ_CLIENT_RE = /^[a-f0-9]{64}$/;\nconst MUTATION_HOLD_MS = 20 * 60 * 1000; // 20 minutes of inactivity clears the hold.\n\nexport type AuthPhase = 'checking' | 'unauthenticated' | 'authenticated';\n\ninterface AuthState {\n phase: AuthPhase;\n /** True once a session check has actually completed (vs. still booting). */\n checked: boolean;\n mutationTokenHeldUntil: number | null;\n}\n\nfunction randomHex64(): string {\n const bytes = new Uint8Array(32);\n crypto.getRandomValues(bytes);\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nfunction loadOrCreateClientProof(): string {\n try {\n const existing = sessionStorage.getItem(READ_CLIENT_STORAGE_KEY);\n if (existing && READ_CLIENT_RE.test(existing)) return existing;\n } catch {\n // sessionStorage unavailable (privacy mode, etc.) — fall through to an\n // in-memory-only proof for this page life.\n }\n const proof = randomHex64();\n try {\n sessionStorage.setItem(READ_CLIENT_STORAGE_KEY, proof);\n } catch {\n /* best-effort persistence only */\n }\n return proof;\n}\n\nlet clientProof = loadOrCreateClientProof();\n\n/** In-memory only. Never touches storage. Cleared by clearMutationToken(). */\nlet mutationToken: string | null = null;\nlet mutationHoldTimer: ReturnType<typeof setTimeout> | null = null;\n/**\n * The mutation token came from the desktop host (window.__ASHLR_TOKENS__).\n * The operator never saw it, so an idle timeout must re-arm the hold rather\n * than clear it — a cleared hold would ask for a token nobody can paste.\n */\nlet mutationHeldByHost = false;\n\n/**\n * Last read token that the server accepted (typed or host-injected). Memory\n * only, same trust boundary as the mutation hold; used to renew the cookie\n * silently on 401. Dropped on explicit disconnect or when renewal fails.\n */\nlet rememberedReadToken: string | null = null;\nlet renewal: Promise<boolean> | null = null;\n/** Desktop-injected tokens kept for re-adoption (see the host-injected section below). */\nlet hostTokens: InjectedTokens | null = null;\n\nlet state: AuthState = { phase: 'checking', checked: false, mutationTokenHeldUntil: null };\nconst listeners = new Set<() => void>();\n\nfunction emit(): void {\n for (const l of listeners) l();\n}\n\nfunction setState(patch: Partial<AuthState>): void {\n state = { ...state, ...patch };\n emit();\n}\n\nexport function subscribeAuth(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\nexport function getAuthSnapshot(): AuthState {\n return state;\n}\n\nexport function getReadClientProof(): string {\n return clientProof;\n}\n\n/** Regenerate the client proof (e.g. after an explicit \"forget this device\"). */\nexport function resetReadClientProof(): string {\n clientProof = randomHex64();\n try {\n sessionStorage.setItem(READ_CLIENT_STORAGE_KEY, clientProof);\n } catch {\n /* best-effort */\n }\n return clientProof;\n}\n\n/**\n * Exchange a raw read token for a session cookie. Throws on failure with a\n * message safe to show the operator. The raw token never leaves this call.\n */\nexport async function establishReadSession(rawReadToken: string): Promise<void> {\n const trimmed = rawReadToken.trim();\n if (!/^[a-f0-9]{64}$/.test(trimmed)) {\n throw new Error('That does not look like a read token — expected 64 hex characters.');\n }\n const res = await fetch('/api/session', {\n method: 'POST',\n credentials: 'same-origin',\n headers: {\n 'x-ashlr-token': trimmed,\n 'x-ashlr-read-client': clientProof,\n },\n });\n if (res.status === 204) {\n rememberedReadToken = trimmed;\n setState({ phase: 'authenticated', checked: true });\n return;\n }\n if (res.status === 401) {\n throw new Error('Server rejected that token. Double-check the read token printed by this server at startup.');\n }\n throw new Error(`Unexpected response establishing session (HTTP ${res.status}).`);\n}\n\nexport async function clearReadSession(): Promise<void> {\n try {\n await fetch('/api/session', {\n method: 'DELETE',\n credentials: 'same-origin',\n headers: { 'x-ashlr-read-client': clientProof },\n });\n } catch {\n /* best-effort */\n }\n rememberedReadToken = null;\n hostTokens = null;\n expireNow();\n}\n\nfunction expireNow(): void {\n clearMutationToken();\n evictAll();\n // `evictAll` clears the query cache so nothing stale leaks into the next\n // session's first paint. The composer's drafts and sent-message history live\n // in localStorage, not in that cache, and they are verbatim prompt text —\n // exactly the kind of content this wipe exists for.\n clearComposerMemory();\n setState({ phase: 'unauthenticated', checked: true });\n}\n\n/**\n * Called by data/client.ts whenever any authenticated GET comes back 401\n * (and by sse.ts on `session-expired`). With a remembered read token the\n * cookie is re-issued silently and the phase never flips; the failed call's\n * own retry/reconnect (SSE backoff, the next refetch) then succeeds. Only\n * when renewal fails (server restarted with fresh tokens) does the gate show.\n */\nexport function reportSessionExpired(): void {\n if (state.phase !== 'authenticated') return;\n if (rememberedReadToken) {\n void renewReadSession();\n return;\n }\n expireNow();\n}\n\n/** Re-exchange the remembered read token for a fresh cookie; one flight at a time. */\nexport function renewReadSession(): Promise<boolean> {\n if (renewal) return renewal;\n const token = rememberedReadToken;\n if (!token) return Promise.resolve(false);\n renewal = (async () => {\n try {\n await establishReadSession(token);\n // Whatever is on screen and 401ed while the ticket was lapsed refetches\n // against the new cookie. Only what is OBSERVED: an entry nobody reads\n // (a surface the idle warm-up fetched but the operator never opened)\n // re-reads when it next mounts, if it is stale or failed — re-running\n // them all here cost ~8 background reads every 15 minutes, all day.\n invalidateObserved('');\n return true;\n } catch {\n rememberedReadToken = null;\n hostTokens = null;\n expireNow();\n return false;\n } finally {\n renewal = null;\n }\n })();\n return renewal;\n}\n\n/** Test hygiene: forget the remembered read token and host tokens without a network call. */\nexport function forgetRememberedTokens(): void {\n rememberedReadToken = null;\n hostTokens = null;\n renewal = null;\n}\n\nexport function markCheckComplete(authenticated: boolean): void {\n setState({ phase: authenticated ? 'authenticated' : 'unauthenticated', checked: true });\n}\n\n// ---------------------------------------------------------------------------\n// Host-injected tokens (desktop wrapper). The Tauri shell starts the server\n// itself and hands both tokens to the page through an initialization script\n// (`window.__ASHLR_TOKENS__`, see desktop/README.md) so the operator never\n// pastes them. The object is removed from `window` before either token is\n// used and kept in this module's memory instead (`hostTokens`), because the\n// desktop operator has no terminal to paste from: when the 15-minute cookie\n// lapses or the gate re-mounts, adoption re-runs from memory. The read token\n// is exchanged for the cookie exactly like a typed one and the mutation token\n// goes into the memory-only hold. Nothing is persisted and nothing is logged.\n// ---------------------------------------------------------------------------\n\nexport interface InjectedTokens {\n readToken: string;\n /** Mutation token; absent or null when the host started the server without dispatch. */\n token?: string | null;\n}\n\ndeclare global {\n interface Window {\n __ASHLR_TOKENS__?: InjectedTokens;\n }\n}\n\n/**\n * Take host-injected tokens: from `window` (cleared from the page on first\n * sight, remembered here) or, on later calls, from memory.\n */\nexport function takeInjectedTokens(): InjectedTokens | null {\n const injected = window.__ASHLR_TOKENS__;\n if (injected && typeof injected === 'object') {\n try {\n delete window.__ASHLR_TOKENS__;\n } catch {\n window.__ASHLR_TOKENS__ = undefined;\n }\n if (typeof injected.readToken === 'string') {\n hostTokens = { readToken: injected.readToken, ...(typeof injected.token === 'string' ? { token: injected.token } : {}) };\n }\n }\n return hostTokens ? { ...hostTokens } : null;\n}\n\n/**\n * Establish the read session (and set the mutation hold) from host-injected\n * tokens. Resolves true when a session was established; false when nothing\n * was injected or the server rejected the read token (the caller then falls\n * back to the normal SessionGate flow — the failure is never surfaced with\n * the token in it).\n */\nexport async function adoptInjectedTokens(): Promise<boolean> {\n const injected = takeInjectedTokens();\n if (!injected) return false;\n try {\n await establishReadSession(injected.readToken);\n } catch {\n // Wrong or stale tokens are not worth retrying from memory.\n hostTokens = null;\n return false;\n }\n if (injected.token && /^[a-f0-9]{64}$/.test(injected.token.trim())) setMutationToken(injected.token.trim(), { host: true });\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Mutation token — session-scoped hold, memory-only.\n// ---------------------------------------------------------------------------\n\nexport function getMutationToken(): string | null {\n return mutationToken;\n}\n\nexport function hasMutationHold(): boolean {\n return mutationToken !== null;\n}\n\nfunction armIdleClear(): void {\n if (mutationHoldTimer) clearTimeout(mutationHoldTimer);\n mutationHoldTimer = setTimeout(() => {\n // A host-held token has no operator to re-paste it: keep the hold alive.\n if (mutationHeldByHost && mutationToken !== null) touchMutationHold();\n else clearMutationToken();\n }, MUTATION_HOLD_MS);\n}\n\n/**\n * Set the mutation token for this tab's session hold. Never persisted.\n * `host` marks a token injected by the desktop wrapper (see adoptInjectedTokens).\n */\nexport function setMutationToken(token: string, opts: { host?: boolean } = {}): void {\n mutationToken = token;\n mutationHeldByHost = opts.host === true;\n const heldUntil = Date.now() + MUTATION_HOLD_MS;\n setState({ mutationTokenHeldUntil: heldUntil });\n armIdleClear();\n}\n\n/** Extend the hold window on real use (called after every mutating call). */\nexport function touchMutationHold(): void {\n if (mutationToken === null) return;\n const heldUntil = Date.now() + MUTATION_HOLD_MS;\n setState({ mutationTokenHeldUntil: heldUntil });\n armIdleClear();\n}\n\nexport function clearMutationToken(): void {\n mutationToken = null;\n mutationHeldByHost = false;\n if (mutationHoldTimer) {\n clearTimeout(mutationHoldTimer);\n mutationHoldTimer = null;\n }\n setState({ mutationTokenHeldUntil: null });\n}\n","/**\n * data/sse.ts — the live channel. One EventSource against /api/events for\n * the whole app, fanning out NAMED events (see api.ts handleSseEvents:\n * 'runs' | 'swarms' | 'inbox' | 'daemon' | 'daemon-observation' |\n * 'fleet-activity-ping' | 'fleet-activity-observation' | 'snapshot') to\n * subscribers, and invalidating the matching cache key so any mounted\n * useQuery(key) re-fetches in the background.\n *\n * The server polls disk at a fixed interval (api.ts SSE_POLL_MS, ~1.5s at\n * time of writing) — that is the real latency floor for \"live\". This layer\n * does not, and should not, try to beat that; it just makes sure the UI\n * never polls MORE often than the server already pushes.\n */\nimport { getReadClientProof, getAuthSnapshot, reportSessionExpired, subscribeAuth } from './auth-store.js';\nimport { invalidate, invalidatePrefix } from './cache.js';\nimport { isScopedConsolePath } from '../app/console-mode.js';\n\nexport const SSE_EVENT_NAMES = [\n 'runs',\n 'swarms',\n 'inbox',\n 'daemon',\n 'daemon-observation',\n 'fleet-activity-ping',\n 'fleet-activity-observation',\n 'snapshot',\n 'session-expired',\n] as const;\nexport type SseEventName = (typeof SSE_EVENT_NAMES)[number];\n\n/** Cache keys (see data/queries.ts) invalidated when a given SSE event fires. */\nconst EVENT_TO_CACHE_KEYS: Record<SseEventName, string[]> = {\n runs: ['runs'],\n swarms: ['swarms'],\n inbox: ['inbox'],\n // 'daemon' was previously mapped but nothing subscribed to a 'daemon'-keyed\n // query (control/daemon didn't exist yet) — now daemonObservationQuery\n // (data/queries.ts) does. Also refreshing 'control-snapshot' here so\n // /control/daemon and /control/security (both read off controlSnapshotQuery)\n // stay live on the same daemon-tick cadence instead of only on page load.\n // `/api/verse/control` and `/api/verse/audit` ARE the daemon projection, so\n // they ride the same signal. Without this the Autonomy cockpit — running\n // state, last tick outcome, today's spend against the cap — was frozen at\n // whatever was true when the section mounted, and DEFAULT_QUERY_FRESH_MS\n // then made a rail switch away and back inside 10s a no-op too.\n //\n // Deliberately NOT a coarse `verse-` prefix sweep: `verse-usage-accounts`\n // and `verse-usage-local-models` spawn provider probes, and firing those on\n // every daemon tick would turn a liveness fix into a process storm. Those\n // two poll on their own visibility-gated 30s cadence in UsageSection,\n // matching the collector's cycle.\n daemon: ['daemon', 'control-snapshot', 'verse-control', 'verse-usage-control', 'verse-caps'],\n 'daemon-observation': ['daemon', 'control-snapshot', 'verse-control', 'verse-usage-control', 'verse-caps'],\n // fleet-activity pings are the closest live signal fleet state changed;\n // 'fleet' (fleetStatusQuery, /control/fleet's queue depth + lease health)\n // has no dedicated SSE event of its own, so it rides these instead of only\n // refreshing on mount.\n 'fleet-activity-ping': ['fleet-activity', 'fleet'],\n 'fleet-activity-observation': ['fleet-activity', 'fleet'],\n // Agent OS snapshots are immutable records but their current authenticated\n // projection advances alongside the server's general snapshot signal.\n snapshot: ['dashboard-snapshot', 'agent-os-snapshot'],\n 'session-expired': [],\n};\n\ntype RawListener = (payload: unknown) => void;\nconst rawListeners = new Map<SseEventName, Set<RawListener>>();\n\nlet source: EventSource | null = null;\nlet reconnectTimer: ReturnType<typeof setTimeout> | null = null;\nlet reconnectAttempt = 0;\n\nfunction backoffMs(): number {\n // 1s, 2s, 4s, 8s, capped at 15s — the server's own poll floor is ~1.5s so\n // there is no benefit reconnecting faster than that even on attempt 0.\n return Math.min(1000 * 2 ** reconnectAttempt, 15_000);\n}\n\nfunction teardown(): void {\n if (source) {\n source.close();\n source = null;\n }\n if (reconnectTimer) {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n }\n}\n\nfunction scheduleReconnect(): void {\n if (reconnectTimer) return;\n reconnectTimer = setTimeout(() => {\n reconnectTimer = null;\n reconnectAttempt += 1;\n connect();\n }, backoffMs());\n}\n\nfunction connect(): void {\n // Shared hooks import this module, but the dedicated console has no global\n // Hub event channel. Check on every reconnect, not just the initial import.\n if (isScopedConsolePath()) { teardown(); return; }\n if (getAuthSnapshot().phase !== 'authenticated') return;\n teardown();\n\n const url = `/api/events?client=${encodeURIComponent(getReadClientProof())}`;\n const es = new EventSource(url, { withCredentials: true });\n source = es;\n\n es.onopen = () => {\n reconnectAttempt = 0;\n };\n\n for (const name of SSE_EVENT_NAMES) {\n es.addEventListener(name, (evt) => {\n const messageEvent = evt as MessageEvent<string>;\n let payload: unknown = undefined;\n try {\n payload = JSON.parse(messageEvent.data);\n } catch {\n payload = undefined;\n }\n for (const key of EVENT_TO_CACHE_KEYS[name]) invalidate(key);\n if (name === 'inbox') {\n // Parameterized inbox queries (one cache key per filter combo / per\n // proposal id) aren't in EVENT_TO_CACHE_KEYS's static map — sweep\n // both prefixes so the list (any filter) and any open detail view\n // (proposalDetailQuery, data/queries.ts) live-update on the same\n // 'inbox' event as the unfiltered badge.\n invalidatePrefix('inbox-list:');\n invalidatePrefix('proposal-detail-');\n }\n if (name === 'session-expired') reportSessionExpired();\n const listeners = rawListeners.get(name);\n if (listeners) for (const l of listeners) l(payload);\n });\n }\n\n es.onerror = () => {\n // EventSource auto-retries on its own, but its built-in retry delay is\n // opaque and can hammer a server that just lost its read session. Close\n // it and drive reconnection ourselves so we stop entirely once\n // auth-store reports the session as expired (see the subscribeAuth\n // wiring at the bottom of this file).\n es.close();\n if (source === es) source = null;\n if (getAuthSnapshot().phase === 'authenticated') scheduleReconnect();\n };\n}\n\n/** Subscribe to one named SSE event's raw payload (rarely needed directly —\n * most views should prefer useQuery + the cache-key mapping above). */\nexport function onSseEvent(name: SseEventName, listener: RawListener): () => void {\n let set = rawListeners.get(name);\n if (!set) {\n set = new Set();\n rawListeners.set(name, set);\n }\n set.add(listener);\n return () => set!.delete(listener);\n}\n\n// Connect once authenticated; disconnect the instant the session drops so a\n// stale connection never masks a 401 the rest of the app needs to react to.\nsubscribeAuth(() => {\n const phase = getAuthSnapshot().phase;\n if (phase === 'authenticated') {\n reconnectAttempt = 0;\n connect();\n } else {\n teardown();\n }\n});\n","/**\n * data/theme-store.ts — the theme preference, extracted out of Topbar.tsx\n * so the command palette's \"Toggle theme\" action can read/write the same\n * source of truth Topbar's own button uses (same module-level state +\n * subscribe pattern as auth-store.ts — commands.ts's `run` closures have no\n * React context to call useState from, so this can't live in a component).\n */\n\nexport type ThemePreference = 'system' | 'light' | 'dark';\n\nconst THEME_STORAGE_KEY = 'ashlr.theme.v1';\n\nfunction applyTheme(pref: ThemePreference): void {\n const root = document.documentElement;\n if (pref === 'system') root.removeAttribute('data-theme');\n else root.setAttribute('data-theme', pref);\n}\n\nfunction loadTheme(): ThemePreference {\n try {\n const v = localStorage.getItem(THEME_STORAGE_KEY);\n if (v === 'light' || v === 'dark' || v === 'system') return v;\n } catch {\n /* ignore */\n }\n return 'system';\n}\n\nlet theme: ThemePreference = loadTheme();\napplyTheme(theme);\n\nconst listeners = new Set<() => void>();\n\nfunction emit(): void {\n for (const l of listeners) l();\n}\n\nexport function subscribeTheme(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\nexport function getTheme(): ThemePreference {\n return theme;\n}\n\nexport function setTheme(next: ThemePreference): void {\n theme = next;\n applyTheme(theme);\n try {\n localStorage.setItem(THEME_STORAGE_KEY, theme);\n } catch {\n /* best-effort persistence only */\n }\n emit();\n}\n\nexport function cycleTheme(): void {\n setTheme(theme === 'system' ? 'light' : theme === 'light' ? 'dark' : 'system');\n}\n","/**\n * data/appearance-store.ts — the operator's appearance preferences: the\n * \"Linux for agentic engineering\" part of the design language\n * (docs/VERSE-DESIGN-V2.md §3). Theme, accent, density, display face,\n * radius, and motion, applied live by writing custom properties and\n * data-attributes on <html>.\n *\n * Same shape as ./theme-store.ts on purpose — module-level state + a\n * listener set, zero React — so it is unit-testable without a DOM renderer\n * and usable from non-component code (the command palette's closures, the\n * rail's quick-toggle). data/hooks.ts binds it into React via\n * `useAppearance()`, exactly as it does for the theme.\n *\n * THEME IS NOT FORKED. `theme-store.ts` remains the single authority for the\n * light/dark/system preference (key `ashlr.theme.v1`, already read by the\n * topbar and the command palette); this module re-exposes it as one field of\n * the appearance snapshot and delegates writes to `setTheme`. Two stores\n * writing `data-theme` would race; one store that owns it does not.\n *\n * Everything is applied at MODULE LOAD (before React's first render, since\n * hooks.ts imports this and every view imports hooks.ts), so the app never\n * paints the default accent for a frame before swapping to the chosen one.\n */\nimport {\n getTheme,\n setTheme,\n subscribeTheme,\n type ThemePreference,\n} from './theme-store.js';\n\nexport type { ThemePreference };\n\nexport type Density = 'comfortable' | 'compact';\n\n/**\n * How large the whole interface is drawn. Scales TYPE AND SPACING TOGETHER\n * (design/tokens.css multiplies its type ramp by --ui-text-scale and its\n * spacing/geometry ramp by --ui-scale), because scaling type alone produces\n * big text in cramped boxes — worse to read than the default, not better.\n *\n * ORTHOGONAL TO `density`, not a replacement for it: display size answers\n * \"how big is this interface\", density answers \"how tightly are its rows\n * packed\". They compose, which is why tokens.css expresses the scale as a\n * multiplier rather than a second table of literal heights.\n *\n * There is no step below 'default' on purpose — see the [data-ui-scale]\n * block in tokens.css for why (the 12px body floor, and Density already\n * being the tightening control).\n */\nexport type UiScale = 'default' | 'large' | 'xlarge';\n\n/**\n * The operator's motion choice. Tri-state ON PURPOSE.\n *\n * A boolean cannot distinguish \"I want full motion\" from \"I never touched\n * this\", and `persist()` writes the whole snapshot on every appearance change\n * — so dragging the accent hue once used to store `reduceMotion: false` and\n * permanently override the OS `prefers-reduced-motion` preference, because\n * `applyAppearance` then wrote `data-motion=\"full\"`, which is exactly the\n * value every `:root:not([data-motion=\"full\"])` guard exists to defer to.\n *\n * 'system' removes the attribute entirely and lets the media queries decide,\n * which is what DESIGN-V2 §3 and §6 ask for by default.\n */\nexport type MotionPreference = 'system' | 'reduce' | 'full';\nexport type DisplayFont = 'grotesk' | 'ui' | 'mono';\nexport type RadiusScale = 'sharp' | 'default' | 'soft';\n\nexport interface Appearance {\n /** Delegated to theme-store.ts — persisted under its own key. */\n theme: ThemePreference;\n /** Accent hue 0-360. */\n accentH: number;\n /** Accent saturation 0-100 (%). */\n accentS: number;\n /** Accent lightness, clamped to a readable band (%). */\n accentL: number;\n density: Density;\n /** How large the whole interface is drawn. Type and spacing together. */\n uiScale: UiScale;\n displayFont: DisplayFont;\n radius: RadiusScale;\n /** The stored choice: 'system' follows `prefers-reduced-motion`. */\n motion: MotionPreference;\n /**\n * DERIVED, never stored — what the Settings switch shows and what the app\n * behaves as right now. `setAppearance({ reduceMotion })` is the only call\n * that turns this back into an explicit 'reduce'/'full'.\n */\n reduceMotion: boolean;\n}\n\n/**\n * The persisted subset. `theme` lives in theme-store, and `reduceMotion` is\n * derived from `motion` — storing it is what created the override bug.\n */\ntype StoredAppearance = Omit<Appearance, 'theme' | 'reduceMotion'>;\n\nconst STORAGE_KEY = 'ashlr.verse.appearance.v1';\n\n/**\n * Accent lightness band. Below ~30% the accent stops reading as a color on a\n * dark canvas; above ~78% it disappears on a light one. The hue and\n * saturation are unconstrained — this is the one axis where a free slider\n * can make the app genuinely unusable.\n */\nconst ACCENT_L_MIN = 30;\nconst ACCENT_L_MAX = 78;\n\n/** Must match the bare `:root` values in design/tokens.css. */\nexport const DEFAULT_ACCENT = { h: 245, s: 72, l: 58 } as const;\n\nfunction prefersReducedMotion(): boolean {\n try {\n return typeof window !== 'undefined' && typeof window.matchMedia === 'function'\n ? window.matchMedia('(prefers-reduced-motion: reduce)').matches\n : false;\n } catch {\n // Some embedded webviews throw on an unsupported media feature.\n return false;\n }\n}\n\nexport function defaultAppearance(): Appearance {\n return {\n theme: 'system',\n accentH: DEFAULT_ACCENT.h,\n accentS: DEFAULT_ACCENT.s,\n accentL: DEFAULT_ACCENT.l,\n density: 'comfortable',\n uiScale: 'default',\n displayFont: 'grotesk',\n radius: 'default',\n motion: 'system',\n reduceMotion: prefersReducedMotion(),\n };\n}\n\nfunction isMotion(v: unknown): v is MotionPreference {\n return v === 'system' || v === 'reduce' || v === 'full';\n}\n\n/** What 'system' resolves to right now; an explicit choice answers itself. */\nfunction resolveReduceMotion(motion: MotionPreference): boolean {\n return motion === 'reduce' ? true : motion === 'full' ? false : prefersReducedMotion();\n}\n\nconst clamp = (n: number, min: number, max: number): number => Math.min(max, Math.max(min, n));\n\nfunction isDensity(v: unknown): v is Density {\n return v === 'comfortable' || v === 'compact';\n}\nfunction isUiScale(v: unknown): v is UiScale {\n return v === 'default' || v === 'large' || v === 'xlarge';\n}\nfunction isDisplayFont(v: unknown): v is DisplayFont {\n return v === 'grotesk' || v === 'ui' || v === 'mono';\n}\nfunction isRadius(v: unknown): v is RadiusScale {\n return v === 'sharp' || v === 'default' || v === 'soft';\n}\n\n/** Coerce anything (stored JSON, a caller's patch) into a valid Appearance. */\nfunction normalize(base: Appearance, patch: Partial<Appearance> | Partial<StoredAppearance>): Appearance {\n const merged = { ...base, ...patch } as Appearance;\n // `reduceMotion` in a PATCH is the Settings switch and is authoritative —\n // it is the one call that converts the derived boolean back into a stored\n // choice. `reduceMotion` in a stored blob is not a choice at all, which is\n // why load() strips it before it ever gets here.\n const asked = (patch as Partial<Appearance>).reduceMotion;\n const motion: MotionPreference = isMotion((patch as Partial<Appearance>).motion)\n ? ((patch as Partial<Appearance>).motion as MotionPreference)\n : typeof asked === 'boolean'\n ? (asked ? 'reduce' : 'full')\n : isMotion(base.motion)\n ? base.motion\n : 'system';\n return {\n theme: merged.theme === 'light' || merged.theme === 'dark' ? merged.theme : 'system',\n accentH: Number.isFinite(merged.accentH) ? ((Math.round(merged.accentH) % 360) + 360) % 360 : DEFAULT_ACCENT.h,\n accentS: Number.isFinite(merged.accentS) ? clamp(Math.round(merged.accentS), 0, 100) : DEFAULT_ACCENT.s,\n accentL: Number.isFinite(merged.accentL)\n ? clamp(Math.round(merged.accentL), ACCENT_L_MIN, ACCENT_L_MAX)\n : DEFAULT_ACCENT.l,\n density: isDensity(merged.density) ? merged.density : 'comfortable',\n uiScale: isUiScale(merged.uiScale) ? merged.uiScale : 'default',\n displayFont: isDisplayFont(merged.displayFont) ? merged.displayFont : 'grotesk',\n radius: isRadius(merged.radius) ? merged.radius : 'default',\n motion,\n reduceMotion: resolveReduceMotion(motion),\n };\n}\n\nfunction load(): Appearance {\n const base = defaultAppearance();\n let stored: Partial<StoredAppearance> = {};\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (raw) {\n const parsed: unknown = JSON.parse(raw);\n if (parsed && typeof parsed === 'object') stored = parsed as Partial<StoredAppearance>;\n }\n } catch {\n // Unavailable storage, quota errors, or corrupt JSON all mean the same\n // thing here: fall back to defaults rather than failing to boot.\n }\n // Drop any `reduceMotion` a pre-tri-state build left behind: it was written\n // by every unrelated appearance change, so it records nothing the operator\n // decided. Without `motion`, the media query decides — as it should.\n const { reduceMotion: _legacy, ...rest } = stored as Partial<StoredAppearance> & { reduceMotion?: unknown };\n return normalize(base, { ...rest, theme: getTheme() });\n}\n\nfunction persist(value: Appearance): void {\n try {\n // theme is deliberately excluded — theme-store.ts owns its own key.\n // reduceMotion is excluded because it is derived: persisting it is what\n // let an accent drag silently override the OS preference.\n const { theme: _theme, reduceMotion: _derived, ...rest } = value;\n localStorage.setItem(STORAGE_KEY, JSON.stringify(rest satisfies StoredAppearance));\n } catch {\n /* best-effort persistence only */\n }\n}\n\nconst FONT_VAR: Record<DisplayFont, string | null> = {\n // null = remove the inline override and fall back to the token default.\n grotesk: null,\n ui: 'var(--font-ui)',\n mono: 'var(--font-mono)',\n};\n\n/**\n * Write the settings onto <html>. The accent is written as the three raw\n * channels tokens.css derives its whole ramp from, so one paint recolors\n * links, focus rings, primary buttons, meters and charts together.\n */\nexport function applyAppearance(value: Appearance): void {\n if (typeof document === 'undefined') return;\n const root = document.documentElement;\n root.style.setProperty('--accent-h', String(value.accentH));\n root.style.setProperty('--accent-s', `${value.accentS}%`);\n root.style.setProperty('--accent-l', `${value.accentL}%`);\n\n const font = FONT_VAR[value.displayFont];\n if (font === null) root.style.removeProperty('--font-display');\n else root.style.setProperty('--font-display', font);\n\n root.setAttribute('data-density', value.density);\n // The display size is an ATTRIBUTE, not an inline custom property, for the\n // same reason the theme is: tokens.css owns the numbers, and a\n // `[data-ui-scale]` block can be read, tested and retuned in the stylesheet\n // that every other token lives in. Writing `--ui-scale` inline here would\n // put half the design system in a TypeScript file.\n root.setAttribute('data-ui-scale', value.uiScale);\n root.setAttribute('data-radius', value.radius);\n // 'system' removes the attribute so the `prefers-reduced-motion` media\n // queries decide on their own. \"full\" is meaningful, not a no-op: it opts an\n // operator explicitly OUT of the OS preference (tokens.css and the four\n // Verse stylesheets guard their media query with :not([data-motion=\"full\"])),\n // which is precisely why it must never be written by default.\n if (value.motion === 'system') root.removeAttribute('data-motion');\n else root.setAttribute('data-motion', value.motion);\n}\n\nlet current: Appearance = load();\napplyAppearance(current);\n\nconst listeners = new Set<() => void>();\n\nfunction emit(): void {\n for (const l of listeners) l();\n}\n\n// The theme can also be changed from the rail's quick-toggle and the command\n// palette (both go through theme-store). Mirror those into the snapshot so a\n// Settings panel rendered at the same time never shows a stale radio.\nsubscribeTheme(() => {\n if (current.theme === getTheme()) return;\n current = { ...current, theme: getTheme() };\n emit();\n});\n\nexport function subscribeAppearance(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\n/** Stable snapshot — a NEW object on every change (useSyncExternalStore uses Object.is). */\nexport function getAppearance(): Appearance {\n return current;\n}\n\n/** Apply and persist a partial change. Unknown/out-of-range values are coerced. */\nexport function setAppearance(patch: Partial<Appearance>): void {\n const next = normalize(current, patch);\n const themeChanged = next.theme !== current.theme;\n current = next;\n applyAppearance(current);\n persist(current);\n // Delegated last so theme-store's own listeners see the final snapshot.\n if (themeChanged) setTheme(current.theme);\n emit();\n}\n\n/** Back to the shipped defaults, including the theme and the stored blob. */\nexport function resetAppearance(): void {\n try {\n localStorage.removeItem(STORAGE_KEY);\n } catch {\n /* best-effort */\n }\n current = defaultAppearance();\n applyAppearance(current);\n setTheme('system');\n emit();\n}\n\nexport const ACCENT_LIGHTNESS_RANGE = { min: ACCENT_L_MIN, max: ACCENT_L_MAX } as const;\nexport const APPEARANCE_STORAGE_KEY = STORAGE_KEY;\n","/**\n * data/hooks.ts — React bindings over the framework-free stores in\n * cache.ts / auth-store.ts / sse.ts. This is the only file in the data\n * layer that imports React; every module underneath it stays plain\n * TypeScript so it's trivially unit-testable without a DOM.\n */\nimport { useCallback, useEffect, useSyncExternalStore } from 'react';\nimport { ensureQuery, getQuerySnapshot, refetchQuery, subscribeQuery, type QueryEntry } from './cache.js';\nimport type { QueryDef } from './queries.js';\nimport {\n getAuthSnapshot,\n subscribeAuth,\n type AuthPhase,\n hasMutationHold,\n getMutationToken,\n setMutationToken as storeSetMutationToken,\n touchMutationHold,\n clearMutationToken,\n} from './auth-store.js';\nimport { onSseEvent, type SseEventName } from './sse.js';\nimport { subscribeTheme, getTheme, setTheme, cycleTheme, type ThemePreference } from './theme-store.js';\nimport {\n getAppearance,\n resetAppearance,\n setAppearance,\n subscribeAppearance,\n type Appearance,\n} from './appearance-store.js';\n\n/**\n * How long a cached value is accepted on MOUNT without re-reading it.\n *\n * A section that re-mounts inside this window (rail switch, a parent\n * re-keying, React 18's double-invoked effects in StrictMode) issues no\n * request at all. Ten seconds is short enough that any surface a human\n * revisits deliberately still re-reads, and long enough that the Usage\n * section's seven probe-spawning reads happen ONCE per visit rather than\n * once per mount.\n *\n * This does not make data stale: keys with an SSE event still refresh the\n * instant the server says they changed (sse.ts → invalidate, which is not\n * freshness-gated), and `useRefetch` always ignores it.\n */\nexport const DEFAULT_QUERY_FRESH_MS = 10_000;\n\nexport interface UseQueryOptions {\n /** Override `DEFAULT_QUERY_FRESH_MS` for this subscription. 0 = always fetch. */\n freshMs?: number;\n}\n\n/**\n * Subscribe to a resource. Fetches on mount (and on `key` change) unless the\n * cached value is still fresh, stays subscribed to background refreshes\n * triggered by sse.ts, and NEVER clears `data` while refetching —\n * `status === 'refreshing'` is the signal to show a subtle indicator instead\n * of a skeleton, `status === 'loading'` (no data yet at all) is the only\n * state that should render a skeleton.\n */\nexport function useQuery<T>(def: QueryDef<T>, options?: UseQueryOptions): QueryEntry<T> {\n const freshMs = options?.freshMs ?? DEFAULT_QUERY_FRESH_MS;\n const snapshot = useSyncExternalStore(\n useCallback((listener) => subscribeQuery(def.key, listener), [def.key]),\n () => getQuerySnapshot<T>(def.key),\n () => getQuerySnapshot<T>(def.key),\n );\n\n useEffect(() => {\n // Cache refreshers outlive any one component. Binding the stored fetcher\n // to this component's AbortSignal poisons later SSE invalidations after\n // unmount, so the shared cache owns the request lifetime.\n void ensureQuery(def.key, () => def.fetch(), freshMs);\n // Re-fetch only when the resource identity changes, not on every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- def.fetch is re-created per render on purpose; only def.key identifies the resource.\n }, [def.key, freshMs]);\n\n return snapshot;\n}\n\n/**\n * Manually trigger a re-fetch without waiting for SSE — a \"Retry\" button, a\n * \"Refresh\" button, or a poll loop.\n *\n * Ignores freshness always, and joins an in-flight read only while that read\n * is younger than `REFETCH_JOIN_WINDOW_MS`. Both halves matter, because this\n * one hook backs two different callers:\n *\n * - A poll (UniverseView ticks every 3–15s and again on visibilitychange)\n * landing on a read that started milliseconds ago SHOULD coalesce with\n * it; issuing a second request there is pure waste.\n * - A person's click landing on a read that has been running for seconds\n * SHOULD NOT. That was the old behavior: the click joined the running\n * promise and returned data fetched before the click — a silent no-op,\n * and with local-model probes taking seconds under load the window was\n * wide enough to hit in ordinary use.\n *\n * For the strict guarantee — a request issued after the click, every time,\n * no matter what else is running — use `useRefresh`.\n */\nexport function useRefetch<T>(def: QueryDef<T>): () => void {\n return useCallback(() => {\n void refetchQuery(def.key, () => def.fetch());\n }, [def]);\n}\n\n/**\n * The explicit user-initiated refresh: ALWAYS issues a new request, never\n * joins one already running, and the read it issues is the one whose result\n * is kept even if an older read lands after it.\n *\n * Use this behind anything a person presses that promises them a fresh\n * reading — a Refresh button on a surface with no SSE invalidation, where\n * the whole reason the button exists is that nothing else will re-read.\n */\nexport function useRefresh<T>(def: QueryDef<T>): () => void {\n return useCallback(() => {\n void refetchQuery(def.key, () => def.fetch(), true);\n }, [def]);\n}\n\nexport function useAuthPhase(): AuthPhase {\n return useSyncExternalStore(subscribeAuth, () => getAuthSnapshot().phase, () => getAuthSnapshot().phase);\n}\n\nexport interface MutationHold {\n hasHold: boolean;\n token: string | null;\n heldUntil: number | null;\n setToken: (token: string) => void;\n touch: () => void;\n clear: () => void;\n}\n\nexport function useMutationHold(): MutationHold {\n const heldUntil = useSyncExternalStore(\n subscribeAuth,\n () => getAuthSnapshot().mutationTokenHeldUntil,\n () => getAuthSnapshot().mutationTokenHeldUntil,\n );\n return {\n hasHold: hasMutationHold(),\n token: getMutationToken(),\n heldUntil,\n setToken: storeSetMutationToken,\n touch: touchMutationHold,\n clear: clearMutationToken,\n };\n}\n\n/** Subscribe to a raw named SSE event for the lifetime of the component. */\nexport function useSseEvent(name: SseEventName, onEvent: (payload: unknown) => void): void {\n useEffect(() => onSseEvent(name, onEvent), [name, onEvent]);\n}\n\nexport interface ThemeControl {\n theme: ThemePreference;\n set: (next: ThemePreference) => void;\n cycle: () => void;\n}\n\n/** Shared with Topbar's theme button and the command palette's \"Toggle\n * theme\" action — both read/write data/theme-store.ts through this hook so\n * neither can drift out of sync with the other. */\nexport function useTheme(): ThemeControl {\n const theme = useSyncExternalStore(subscribeTheme, getTheme, getTheme);\n return { theme, set: setTheme, cycle: cycleTheme };\n}\n\nexport interface AppearanceControl {\n appearance: Appearance;\n /** Partial change; applied to <html> and persisted immediately (no save button). */\n set: (patch: Partial<Appearance>) => void;\n reset: () => void;\n}\n\n/**\n * The Settings section's appearance controls and anything that needs to READ\n * the current density/accent (e.g. a chart sizing rows) share this hook, for\n * the same reason useTheme exists: two readers of one external store, never\n * a second copy of the state.\n *\n * Importing this module is also what applies the stored appearance before\n * the first paint — every view imports hooks.ts, so appearance-store.ts is\n * evaluated (and has written <html>) before React renders anything.\n */\nexport function useAppearance(): AppearanceControl {\n const appearance = useSyncExternalStore(subscribeAppearance, getAppearance, getAppearance);\n return { appearance, set: setAppearance, reset: resetAppearance };\n}\n","/**\n * data/client.ts — thin typed HTTP client for the existing /api/* surface.\n * No redefinition of response shapes here — callers type each call with the\n * interfaces re-exported from ./api-types.ts.\n */\nimport { getReadClientProof, reportSessionExpired } from './auth-store.js';\n\nexport class ApiError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly path: string,\n /**\n * The server's own sentence about the refusal, when it sent one, without\n * the \"POST … failed (HTTP N)\" wrapper. A surface that wants to show the\n * operator why an action was refused should prefer this over `message`:\n * it is the text the route author wrote for a person to read.\n */\n public readonly detail: string | null = null,\n /**\n * The route's machine-readable refusal code (`VERSE_SESSION_NOT_FOUND`,\n * `VERSE_SESSION_BUSY`, …) when the body carried one; null otherwise. Lets a\n * caller branch on WHAT was refused without parsing the sentence.\n */\n public readonly code: string | null = null,\n ) {\n super(message);\n this.name = 'ApiError';\n }\n}\n\n/**\n * Raised for the 404 a mutation route answers when --allow-dispatch is off.\n *\n * That gate answers a bare `{ error: 'not found' }` — deliberately the same\n * body as a route that does not exist, so a read-only server does not\n * advertise its write surface. A route that DOES exist and refuses with a 404\n * of its own (\"session not found\") says so with a `code`; apiPost keeps those\n * as plain ApiErrors, so a deleted chat is never reported as \"this server is\n * read-only\".\n */\nexport class DispatchDisabledError extends ApiError {\n constructor(path: string) {\n super('This server was started without --allow-dispatch, so mutations are disabled.', 404, path);\n this.name = 'DispatchDisabledError';\n }\n}\n\n/**\n * The plain reason a READ failed, one or two short sentences for a surface to\n * print after its own \"X unavailable.\" Routes that fail a read answer\n * `{ code, error }` with `error` a sentence written for the operator\n * (budget-api.ts, activity-api.ts), which apiGet keeps as `detail`; prefer it\n * over our own \"GET … failed (HTTP N)\" wrapper, which names a URL instead of\n * the cause. Anything that is not an ApiError gets a fixed sentence: a\n * browser's own exception text (\"Failed to fetch\", a JSON parser's\n * \"Unexpected token <\") is not written for a person. Fits ChartFrame's\n * `{ kind: 'unknown', reason }` and a NoticeSlot line alike.\n */\nexport function readFailureReason(err: unknown): string {\n if (err instanceof ApiError) return err.detail ?? `The server answered HTTP ${err.status}.`;\n // fetch() rejects with a TypeError when the request never got an answer.\n if (err instanceof TypeError) return 'The server did not answer.';\n return 'The request failed.';\n}\n\n/** GET an authenticated read route. 401 reports session-expired and throws. */\nexport async function apiGet<T>(path: string, signal?: AbortSignal): Promise<T> {\n const res = await fetch(path, {\n method: 'GET',\n credentials: 'same-origin',\n headers: { 'x-ashlr-read-client': getReadClientProof() },\n signal,\n });\n if (res.status === 401) {\n reportSessionExpired();\n throw new ApiError('Read session expired.', 401, path);\n }\n if (!res.ok) {\n // Keep the route's own sentence and code (e.g. the grant draft's\n // `no-trust-roots`) so a reader can branch on WHAT was refused.\n const { detail, code } = await readRefusal(res);\n throw new ApiError(`GET ${path} failed (HTTP ${res.status}).`, res.status, path, detail || null, code);\n }\n return (await res.json()) as T;\n}\n\n/**\n * POST a mutating route with the raw mutation token. `mutationToken` must\n * come from the caller (data/mutations.ts pulls it from auth-store) — this\n * function does not read auth-store itself so it stays trivially testable.\n */\nexport async function apiPost<T>(\n path: string,\n body: unknown,\n mutationToken: string,\n signal?: AbortSignal,\n): Promise<T> {\n const res = await fetch(path, {\n method: 'POST',\n credentials: 'same-origin',\n headers: {\n 'Content-Type': 'application/json',\n 'x-ashlr-token': mutationToken,\n },\n body: JSON.stringify(body ?? {}),\n signal,\n });\n if (res.status === 401) {\n throw new ApiError('Mutation token was rejected.', 401, path);\n }\n if (!res.ok) {\n const { detail, code } = await readRefusal(res);\n // Only a CODELESS 404 is the dispatch gate (see DispatchDisabledError).\n // An unknown sub-path on an older server is codeless too and lands here\n // as well — the two are indistinguishable by design, so callers that\n // word this error must not assert which one it was.\n if (res.status === 404 && code === null) {\n throw new DispatchDisabledError(path);\n }\n throw new ApiError(\n `POST ${path} failed (HTTP ${res.status})${detail ? `: ${detail}` : ''}.`,\n res.status,\n path,\n detail || null,\n code,\n );\n }\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n}\n\n/**\n * The refusal sentence and code from a failed POST's body, if it had any.\n *\n * `error` is the documented refusal field, but the Verse control plane\n * answers a refused daemon/scope action with a full result body whose\n * plain-language sentence lives in `note` and which has NO `error` key at all\n * (see VerseDaemonActionResult). Reading only `error` threw away sentences\n * like \"no repositories are enrolled, so the loop would do nothing\" and left\n * the caller with a bare status code to guess from.\n */\nasync function readRefusal(res: Response): Promise<{ detail: string; code: string | null }> {\n try {\n const j = (await res.json()) as { error?: unknown; note?: unknown; code?: unknown };\n const detail = typeof j.error === 'string' && j.error ? j.error : typeof j.note === 'string' ? j.note : '';\n const code = typeof j.code === 'string' && j.code ? j.code : null;\n return { detail, code };\n } catch {\n /* body wasn't JSON */\n return { detail: '', code: null };\n }\n}\n\n/** Build the SSE URL, carrying the client proof as the query-string proof\n * EventSource can't attach as a header (see server.ts readSessionClientProof). */\nexport function eventsUrl(): string {\n return `/api/events?client=${encodeURIComponent(getReadClientProof())}`;\n}\n",".grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));\n gap: var(--space-4);\n}\n\n/* No border and no fill: a skeleton stands in for CONTENT, and drawing it as\n a bordered card meant the placeholder had a box the real card does not\n (DESIGN-V2 §1.2) and the layout visibly lost an outline on first paint. */\n.card {\n display: flex;\n flex-direction: column;\n gap: var(--space-3);\n padding: var(--space-4) 0;\n}\n\n/* Each bar matches the LINE BOX of the text it replaces, so nothing reflows\n when the real content lands: a label line, a display-face stat, a caption. */\n.title {\n height: var(--text-xs-line);\n width: 60%;\n border-radius: var(--radius-xs);\n}\n\n.stat {\n height: var(--text-2xl-line);\n width: 40%;\n border-radius: var(--radius-xs);\n}\n\n.caption {\n height: var(--text-2xs-line);\n width: 80%;\n border-radius: var(--radius-xs);\n}\n\n.line {\n height: var(--text-base-line);\n border-radius: var(--radius-xs);\n}\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--space-4);\n min-height: var(--density-row);\n padding: var(--space-3) var(--space-4);\n border-bottom: 1px solid var(--border-subtle);\n}\n\n.cell {\n height: var(--text-sm-line);\n border-radius: var(--radius-xs);\n}\n","/**\n * components/primitives/Skeleton.tsx — loading placeholders. Used ONLY for\n * `status === 'loading'` (no data at all yet). A resource that already has\n * data and is merely refreshing should show <RefreshIndicator/> instead —\n * never re-blank a populated view (see DESIGN.md \"no blank screens\").\n */\nimport styles from './Skeleton.module.css';\n\nexport function SkeletonLine({ width = '100%' }: { width?: string }) {\n return <div className={`skeleton ${styles.line}`} style={{ width }} />;\n}\n\nexport function SkeletonCard() {\n return (\n <div className={styles.card}>\n <div className={`skeleton ${styles.title}`} />\n <div className={`skeleton ${styles.stat}`} />\n <div className={`skeleton ${styles.caption}`} />\n </div>\n );\n}\n\nexport function SkeletonRow() {\n return (\n <div className={styles.row}>\n <div className={`skeleton ${styles.cell}`} style={{ width: '18%' }} />\n <div className={`skeleton ${styles.cell}`} style={{ width: '38%' }} />\n <div className={`skeleton ${styles.cell}`} style={{ width: '14%' }} />\n <div className={`skeleton ${styles.cell}`} style={{ width: '10%' }} />\n </div>\n );\n}\n\nexport function SkeletonCardGrid({ count = 4 }: { count?: number }) {\n return (\n <div className={styles.grid}>\n {Array.from({ length: count }, (_, i) => (\n <SkeletonCard key={i} />\n ))}\n </div>\n );\n}\n",".skipLink {\n position: fixed;\n top: -100px;\n left: var(--space-3);\n z-index: var(--z-toast);\n background: var(--accent-500);\n color: var(--text-on-accent);\n padding: var(--space-2) var(--space-4);\n border-radius: var(--radius-md);\n font-size: var(--text-sm-size);\n font-weight: var(--font-weight-medium);\n transition: top var(--duration-fast) var(--ease-standard);\n}\n\n.skipLink:focus-visible {\n top: var(--space-3);\n box-shadow: var(--shadow-focus-ring);\n}\n","/**\n * app/SkipToContent.tsx — a real skip link, deliberately NOT `<a\n * href=\"#main-content\">`: this app uses HashRouter, so a literal `#`\n * anchor would rewrite location.hash and hijack client-side routing\n * instead of just scrolling. A button that imperatively focuses the\n * `#main-content` element (set on <main> in components/layout/Shell.tsx)\n * gets the same keyboard behavior without touching the route.\n */\nimport styles from './SkipToContent.module.css';\n\nexport function SkipToContent() {\n return (\n <button\n type=\"button\"\n className={styles.skipLink}\n onClick={() => {\n const main = document.getElementById('main-content');\n main?.focus();\n main?.scrollIntoView({ block: 'start' });\n }}\n >\n Skip to content\n </button>\n );\n}\n"],"mappings":"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"}
|
package/dist/core/web/public/next/assets/{guarded-runners-B8zFVBVZ.js → guarded-runners-Cd4POBtI.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
2
|
-
//# sourceMappingURL=guarded-runners-
|
|
1
|
+
import{D as e,F as t,l as n,o as r}from"./global-y_exIyAP.js";import{B as i,F as a,P as o,a as s}from"./VerseConsoleApp-DXDv1aOi.js";function c(t){let n=e();if(!n)throw new r(`Mutation token was rejected.`,401,t);return n}async function l(){await a();let e=o().data?.running??[];if(e.length===0){i(`Nothing is running.`,`neutral`);return}c(`/api/verse/sessions`);let n=await Promise.allSettled(e.map(e=>s(e.sessionId)));t();let r=n.filter(e=>e.status===`rejected`).length;if(a(),r>0)throw Error(`Stopped ${e.length-r} of ${e.length} chats; ${r} did not answer.`);i(e.length===1?`Stopped 1 chat.`:`Stopped ${e.length} chats.`,`success`)}async function u(){let e=c(`/api/verse/authority`),o=null;try{o=await n(`/api/verse/authority`,{action:`stop`},e)}catch(t){if(!(t instanceof r&&t.status===404&&t.code===null))throw t;await n(`/api/verse/daemon`,{action:`stop`},e)}t(),a(),i(d(o),`success`)}function d(e){let t=`Fleet stopped. It stays stopped until you resume it.`,n=e?.result?.stop;if(typeof n!=`object`||!n)return t;let{quiesced:r,liveExecutionLeases:i,mergeRevokeFailures:a}=n,o=[t];if(r===!1){let e=typeof i==`number`&&Number.isFinite(i)&&i>0?i:null;o.push(e===null?`Agents already running are finishing.`:`${e} agent${e===1?` is`:`s are`} still finishing work started before Stop.`)}if(Array.isArray(a)&&a.length>0){let e=a.length;o.push(`${e} armed merge${e===1?``:`s`} could not be revoked; Stop still blocks ${e===1?`it`:`them`}.`)}return o.join(` `)}var f={"chats.stop-all":l,"fleet.stop":u};function p(e){let t=f[e];return t?t():Promise.reject(Error(`No shell runner for ${e}.`))}export{p as runGuardedShellCommand};
|
|
2
|
+
//# sourceMappingURL=guarded-runners-Cd4POBtI.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guarded-runners-
|
|
1
|
+
{"version":3,"file":"guarded-runners-Cd4POBtI.js","names":[],"sources":["../../../../../../src/web-ui/routes/verse/shell/guarded-runners.ts"],"sourcesContent":["/**\n * routes/verse/shell/guarded-runners.ts — what the shell's own guarded\n * commands DO once the operator has confirmed (and unlocked): stop every\n * running chat, stop the fleet.\n *\n * Split from run-command.ts, which still owns the guard itself (confirm →\n * token → run, through guarded-action.tsx). run-command is on the chat\n * first-paint path — the shell's key handler runs through it — while these\n * run only after a confirmation, so run-command loads this module with\n * import() when a run starts. Keep SHELL_GUARDED_COMMAND_IDS in\n * run-command.ts in step with GUARDED_RUNNERS below.\n */\nimport { getMutationToken, touchMutationHold } from '../../../data/auth-store.js';\nimport { ApiError, apiPost } from '../../../data/client.js';\nimport { cancelVerseTurn } from '../verse-queries.js';\nimport { shellNotify } from './run-command.js';\nimport { getActivityState, refreshActivity } from './useActivity.js';\n\nfunction requireToken(path: string): string {\n const token = getMutationToken();\n if (!token) throw new ApiError('Mutation token was rejected.', 401, path);\n return token;\n}\n\n/** Stop every running chat. Stops what activity says is running NOW, one cancel each. */\nasync function stopRunningChats(): Promise<void> {\n await refreshActivity();\n const running = getActivityState().data?.running ?? [];\n if (running.length === 0) {\n shellNotify('Nothing is running.', 'neutral');\n return;\n }\n requireToken('/api/verse/sessions');\n const results = await Promise.allSettled(running.map((r) => cancelVerseTurn(r.sessionId)));\n touchMutationHold();\n const failed = results.filter((r) => r.status === 'rejected').length;\n void refreshActivity();\n if (failed > 0) throw new Error(`Stopped ${running.length - failed} of ${running.length} chats; ${failed} did not answer.`);\n shellNotify(running.length === 1 ? 'Stopped 1 chat.' : `Stopped ${running.length} chats.`, 'success');\n}\n\n/**\n * Stop the fleet: the authority route's `stop` (B-U1) when this build has it;\n * otherwise the daemon's ordinary stop, which engages the kill switch\n * (control-api.ts `POST /api/verse/daemon {action:'stop'}`). Both stop\n * autonomous work only — chats keep running.\n */\nasync function stopFleet(): Promise<void> {\n const token = requireToken('/api/verse/authority');\n let body: unknown = null;\n try {\n body = await apiPost<unknown>('/api/verse/authority', { action: 'stop' }, token);\n } catch (err) {\n // A CODELESS 404 from the authority route = not in this build (or no\n // dispatch, which the daemon route will then report on its own).\n if (!(err instanceof ApiError && err.status === 404 && err.code === null)) throw err;\n await apiPost<unknown>('/api/verse/daemon', { action: 'stop' }, token);\n }\n touchMutationHold();\n void refreshActivity();\n shellNotify(describeFleetStop(body), 'success');\n}\n\n/**\n * The toast after Stop, from B-U1's StopResult (`{ result: { stop } }` on\n * the authority route). Stop arms KILL at once but does not wait for agents\n * admitted before it (waitMs 0), so \"stopped\" alone would claim more than the\n * server knows (B-U6 request 8): the drain state and any armed merge it could\n * not revoke are said out loud. The daemon fallback has no such fields and\n * gets the plain sentence.\n */\nexport function describeFleetStop(body: unknown): string {\n const base = 'Fleet stopped. It stays stopped until you resume it.';\n const stop = (body as { result?: { stop?: unknown } } | null)?.result?.stop;\n if (typeof stop !== 'object' || stop === null) return base;\n const { quiesced, liveExecutionLeases, mergeRevokeFailures } = stop as {\n quiesced?: unknown;\n liveExecutionLeases?: unknown;\n mergeRevokeFailures?: unknown;\n };\n const parts = [base];\n if (quiesced === false) {\n const n = typeof liveExecutionLeases === 'number' && Number.isFinite(liveExecutionLeases) && liveExecutionLeases > 0 ? liveExecutionLeases : null;\n // Unknown counts as still running (the server fails closed the same way).\n parts.push(n === null ? 'Agents already running are finishing.' : `${n} agent${n === 1 ? ' is' : 's are'} still finishing work started before Stop.`);\n }\n if (Array.isArray(mergeRevokeFailures) && mergeRevokeFailures.length > 0) {\n const m = mergeRevokeFailures.length;\n parts.push(`${m} armed merge${m === 1 ? '' : 's'} could not be revoked; Stop still blocks ${m === 1 ? 'it' : 'them'}.`);\n }\n return parts.join(' ');\n}\n\nexport const GUARDED_RUNNERS: Readonly<Record<string, () => Promise<void>>> = {\n 'chats.stop-all': stopRunningChats,\n 'fleet.stop': stopFleet,\n};\n\n/** Run the shell's runner for a confirmed guarded command. */\nexport function runGuardedShellCommand(id: string): Promise<void> {\n const runner = GUARDED_RUNNERS[id];\n if (!runner) return Promise.reject(new Error(`No shell runner for ${id}.`));\n return runner();\n}\n"],"mappings":"qIAkBA,SAAS,EAAa,EAAsB,CAC1C,IAAM,EAAQ,EAAiB,EAC/B,GAAI,CAAC,EAAO,MAAM,IAAI,EAAS,+BAAgC,IAAK,CAAI,EACxE,OAAO,CACT,CAGA,eAAe,GAAkC,CAC/C,MAAM,EAAgB,EACtB,IAAM,EAAU,EAAiB,CAAC,CAAC,MAAM,SAAW,CAAC,EACrD,GAAI,EAAQ,SAAW,EAAG,CACxB,EAAY,sBAAuB,SAAS,EAC5C,MACF,CACA,EAAa,qBAAqB,EAClC,IAAM,EAAU,MAAM,QAAQ,WAAW,EAAQ,IAAK,GAAM,EAAgB,EAAE,SAAS,CAAC,CAAC,EACzF,EAAkB,EAClB,IAAM,EAAS,EAAQ,OAAQ,GAAM,EAAE,SAAW,UAAU,CAAC,CAAC,OAE9D,GADA,EAAqB,EACjB,EAAS,EAAG,MAAU,MAAM,WAAW,EAAQ,OAAS,EAAO,MAAM,EAAQ,OAAO,UAAU,EAAO,iBAAiB,EAC1H,EAAY,EAAQ,SAAW,EAAI,kBAAoB,WAAW,EAAQ,OAAO,SAAU,SAAS,CACtG,CAQA,eAAe,GAA2B,CACxC,IAAM,EAAQ,EAAa,sBAAsB,EAC7C,EAAgB,KACpB,GAAI,CACF,EAAO,MAAM,EAAiB,uBAAwB,CAAE,OAAQ,MAAO,EAAG,CAAK,CACjF,OAAS,EAAK,CAGZ,GAAI,EAAE,aAAe,GAAY,EAAI,SAAW,KAAO,EAAI,OAAS,MAAO,MAAM,EACjF,MAAM,EAAiB,oBAAqB,CAAE,OAAQ,MAAO,EAAG,CAAK,CACvE,CACA,EAAkB,EAClB,EAAqB,EACrB,EAAY,EAAkB,CAAI,EAAG,SAAS,CAChD,CAUA,SAAgB,EAAkB,EAAuB,CACvD,IAAM,EAAO,uDACP,EAAQ,GAAiD,QAAQ,KACvE,GAAI,OAAO,GAAS,WAAY,EAAe,OAAO,EACtD,GAAM,CAAE,WAAU,sBAAqB,uBAAwB,EAKzD,EAAQ,CAAC,CAAI,EACnB,GAAI,IAAa,GAAO,CACtB,IAAM,EAAI,OAAO,GAAwB,UAAY,OAAO,SAAS,CAAmB,GAAK,EAAsB,EAAI,EAAsB,KAE7I,EAAM,KAAK,IAAM,KAAO,wCAA0C,GAAG,EAAE,QAAQ,IAAM,EAAI,MAAQ,QAAQ,2CAA2C,CACtJ,CACA,GAAI,MAAM,QAAQ,CAAmB,GAAK,EAAoB,OAAS,EAAG,CACxE,IAAM,EAAI,EAAoB,OAC9B,EAAM,KAAK,GAAG,EAAE,cAAc,IAAM,EAAI,GAAK,IAAI,2CAA2C,IAAM,EAAI,KAAO,OAAO,EAAE,CACxH,CACA,OAAO,EAAM,KAAK,GAAG,CACvB,CAEA,IAAa,EAAiE,CAC5E,iBAAkB,EAClB,aAAc,CAChB,EAGA,SAAgB,EAAuB,EAA2B,CAChE,IAAM,EAAS,EAAgB,GAE/B,OADK,EACE,EAAO,EADM,QAAQ,OAAW,MAAM,uBAAuB,EAAG,EAAE,CAAC,CAE5E"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{n as e}from"./index-
|
|
2
|
-
//# sourceMappingURL=icons-
|
|
1
|
+
import{n as e}from"./index-CyK31YRm.js";import{j as t}from"./VerseConsoleApp-DXDv1aOi.js";var n=e(),r=e=>(0,n.jsxs)(t,{...e,children:[(0,n.jsx)(`path`,{d:`M2.2 11.5a6.4 6.4 0 1 1 11.6 0`}),(0,n.jsx)(`path`,{d:`M8 11 10.8 6.6`}),(0,n.jsx)(`circle`,{cx:`8`,cy:`11.6`,r:`0.9`,fill:`currentColor`,stroke:`none`})]}),i=e=>(0,n.jsxs)(t,{...e,children:[(0,n.jsx)(`path`,{d:`M2.5 4.5h4M9.5 4.5h4M2.5 11.5h2M7.5 11.5h6`}),(0,n.jsx)(`circle`,{cx:`8`,cy:`4.5`,r:`1.6`}),(0,n.jsx)(`circle`,{cx:`6`,cy:`11.5`,r:`1.6`})]}),a=e=>(0,n.jsxs)(t,{...e,children:[(0,n.jsx)(`rect`,{x:`4.5`,y:`4.5`,width:`7`,height:`7`,rx:`1.5`}),(0,n.jsx)(`path`,{d:`M6.5 1.5v3M9.5 1.5v3M6.5 11.5v3M9.5 11.5v3M1.5 6.5h3M1.5 9.5h3M11.5 6.5h3M11.5 9.5h3`})]}),o=e=>(0,n.jsx)(t,{...e,children:(0,n.jsx)(`path`,{d:`m3 8.5 3.2 3.2L13 5`})}),s=e=>(0,n.jsxs)(t,{...e,children:[(0,n.jsx)(`circle`,{cx:`8`,cy:`8`,r:`6`}),(0,n.jsx)(`path`,{d:`m5.2 8.2 1.9 1.9 3.7-4`})]}),c=e=>(0,n.jsx)(t,{...e,children:(0,n.jsx)(`path`,{d:`m4 4 8 8M12 4l-8 8`})}),l=e=>(0,n.jsx)(t,{...e,children:(0,n.jsx)(`path`,{d:`M5.5 3.4 12 8l-6.5 4.6V3.4Z`})}),u=e=>(0,n.jsx)(t,{...e,children:(0,n.jsx)(`rect`,{x:`4`,y:`4`,width:`8`,height:`8`,rx:`1.2`})}),d=e=>(0,n.jsxs)(t,{...e,children:[(0,n.jsx)(`path`,{d:`M8 2.6 14.2 13H1.8L8 2.6Z`}),(0,n.jsx)(`path`,{d:`M8 6.6v3`}),(0,n.jsx)(`circle`,{cx:`8`,cy:`11.3`,r:`0.75`,fill:`currentColor`,stroke:`none`})]}),f=e=>(0,n.jsxs)(t,{...e,children:[(0,n.jsx)(`circle`,{cx:`8`,cy:`8`,r:`6`}),(0,n.jsx)(`path`,{d:`M8 7.4v3.4`}),(0,n.jsx)(`circle`,{cx:`8`,cy:`5.4`,r:`0.75`,fill:`currentColor`,stroke:`none`})]}),p=e=>(0,n.jsx)(t,{...e,children:(0,n.jsx)(`path`,{d:`M8 3.2v9.6M3.2 8h9.6`})}),m=e=>(0,n.jsxs)(t,{...e,children:[(0,n.jsx)(`circle`,{cx:`7.2`,cy:`7.2`,r:`4.2`}),(0,n.jsx)(`path`,{d:`m10.4 10.4 3.1 3.1`})]}),h=e=>(0,n.jsxs)(t,{...e,children:[(0,n.jsx)(`rect`,{x:`5.5`,y:`5.5`,width:`8`,height:`8`,rx:`1.5`}),(0,n.jsx)(`path`,{d:`M10.5 3.5a1.5 1.5 0 0 0-1.5-1.5H4A1.5 1.5 0 0 0 2.5 3.5v5A1.5 1.5 0 0 0 4 10`})]}),g=e=>(0,n.jsx)(t,{...e,children:(0,n.jsx)(`path`,{d:`m4 6.2 4 4 4-4`})}),_=e=>(0,n.jsx)(t,{...e,children:(0,n.jsx)(`path`,{d:`m6.2 4 4 4-4 4`})}),v=e=>(0,n.jsx)(t,{...e,children:(0,n.jsx)(`path`,{d:`M14 2 7.2 8.8M14 2l-4.4 12-2.4-5.2L2 6.4 14 2Z`})}),y=e=>(0,n.jsxs)(t,{...e,children:[(0,n.jsx)(`path`,{d:`M9.5 2.5H13.5v4M13.5 2.5 7.8 8.2`}),(0,n.jsx)(`path`,{d:`M12.5 9.6v2.9a1.5 1.5 0 0 1-1.5 1.5H3.5A1.5 1.5 0 0 1 2 12.5V5a1.5 1.5 0 0 1 1.5-1.5h2.9`})]}),b=e=>(0,n.jsxs)(t,{...e,children:[(0,n.jsx)(`path`,{d:`M13.2 7.2a5.2 5.2 0 1 0-.7 3.5`}),(0,n.jsx)(`path`,{d:`M13.5 3.4v3.9h-3.9`})]}),x=e=>(0,n.jsxs)(t,{...e,children:[(0,n.jsx)(`rect`,{x:`3`,y:`7`,width:`10`,height:`6.5`,rx:`1.5`}),(0,n.jsx)(`path`,{d:`M5.5 7V5.2a2.5 2.5 0 0 1 5 0V7`})]}),S=e=>(0,n.jsxs)(t,{...e,children:[(0,n.jsx)(`circle`,{cx:`8`,cy:`8`,r:`3.1`}),(0,n.jsx)(`path`,{d:`M8 1.5v1.6M8 12.9v1.6M1.5 8h1.6M12.9 8h1.6M3.4 3.4l1.1 1.1M11.5 11.5l1.1 1.1M12.6 3.4l-1.1 1.1M4.5 11.5l-1.1 1.1`})]}),C=e=>(0,n.jsx)(t,{...e,children:(0,n.jsx)(`path`,{d:`M13 9.8A5.6 5.6 0 0 1 6.2 3a5.6 5.6 0 1 0 6.8 6.8Z`})}),w=e=>(0,n.jsxs)(t,{...e,children:[(0,n.jsx)(`rect`,{x:`1.8`,y:`2.8`,width:`12.4`,height:`8.4`,rx:`1.5`}),(0,n.jsx)(`path`,{d:`M5.8 14h4.4M8 11.2V14`})]}),T=e=>(0,n.jsxs)(t,{...e,children:[(0,n.jsx)(`rect`,{x:`1.5`,y:`4`,width:`13`,height:`8`,rx:`1.5`}),(0,n.jsx)(`path`,{d:`M4.2 6.6h.01M6.8 6.6h.01M9.4 6.6h.01M12 6.6h.01M4.2 9.4h.01M12 9.4h.01M6.4 9.4h3.2`})]});export{c as C,S,b as _,_ as a,i as b,y as c,T as d,x as f,p as g,l as h,g as i,r as l,C as m,o as n,h as o,w as p,s as r,a as s,d as t,f as u,m as v,u as x,v as y};
|
|
2
|
+
//# sourceMappingURL=icons-CygCEFWs.js.map
|