@ashlr/hub 3.11.0 → 3.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (403) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/dist/authority-surface.json +12 -7
  3. package/dist/build-identity.json +2 -2
  4. package/dist/core/universe/builtins/preparation/manifest.json +1 -1
  5. package/dist/core/universe/builtins/preparation/preparation-bridge.mjs +5 -4
  6. package/dist/core/universe/builtins/preparation/preparation-verification-fixtures.mjs +4 -3
  7. package/dist/core/universe/campaign.js +9 -1
  8. package/dist/core/universe/campaign.js.map +1 -1
  9. package/dist/core/universe/runner.d.ts +3 -0
  10. package/dist/core/universe/runner.js +7 -3
  11. package/dist/core/universe/runner.js.map +1 -1
  12. package/dist/core/verse/compaction-point.d.ts +55 -0
  13. package/dist/core/verse/compaction-point.js +75 -0
  14. package/dist/core/verse/compaction-point.js.map +1 -0
  15. package/dist/core/verse/context-math.d.ts +1 -38
  16. package/dist/core/verse/context-math.js +5 -59
  17. package/dist/core/verse/context-math.js.map +1 -1
  18. package/dist/core/web/public/next/assets/{ActionMenu-CQvNJvHY.js → ActionMenu-_DhtX2gO.js} +2 -2
  19. package/dist/core/web/public/next/assets/{ActionMenu-CQvNJvHY.js.map → ActionMenu-_DhtX2gO.js.map} +1 -1
  20. package/dist/core/web/public/next/assets/Advanced-CAAfaBke.js +2 -0
  21. package/dist/core/web/public/next/assets/{Advanced-DEM93sKI.js.map → Advanced-CAAfaBke.js.map} +1 -1
  22. package/dist/core/web/public/next/assets/App-BAywyjE8.js +7 -0
  23. package/dist/core/web/public/next/assets/{App-CimXmK0L.js.map → App-BAywyjE8.js.map} +1 -1
  24. package/dist/core/web/public/next/assets/AppsSection-CUypKeog.js +2 -0
  25. package/dist/core/web/public/next/assets/{AppsSection-D6U-dSj6.js.map → AppsSection-CUypKeog.js.map} +1 -1
  26. package/dist/core/web/public/next/assets/{AreaTrend-LD9lCPkT.js → AreaTrend-CblDqC8Q.js} +2 -2
  27. package/dist/core/web/public/next/assets/{AreaTrend-LD9lCPkT.js.map → AreaTrend-CblDqC8Q.js.map} +1 -1
  28. package/dist/core/web/public/next/assets/AutonomySection-D3d8lYO3.js +2 -0
  29. package/dist/core/web/public/next/assets/{AutonomySection-3V9pUyxm.js.map → AutonomySection-D3d8lYO3.js.map} +1 -1
  30. package/dist/core/web/public/next/assets/BranchBar-DU0jAV1E.js +2 -0
  31. package/dist/core/web/public/next/assets/{BranchBar-U8UdpfEB.js.map → BranchBar-DU0jAV1E.js.map} +1 -1
  32. package/dist/core/web/public/next/assets/{BudgetControl-DOTlMSmW.js → BudgetControl-Mm1ZL4u6.js} +2 -2
  33. package/dist/core/web/public/next/assets/{BudgetControl-DOTlMSmW.js.map → BudgetControl-Mm1ZL4u6.js.map} +1 -1
  34. package/dist/core/web/public/next/assets/{BurnDown-x4PWH9JF.js → BurnDown-C1fgcFqo.js} +2 -2
  35. package/dist/core/web/public/next/assets/{BurnDown-x4PWH9JF.js.map → BurnDown-C1fgcFqo.js.map} +1 -1
  36. package/dist/core/web/public/next/assets/{Button-DYv_KKxr.js → Button-DqgLUZvd.js} +2 -2
  37. package/dist/core/web/public/next/assets/{Button-DYv_KKxr.js.map → Button-DqgLUZvd.js.map} +1 -1
  38. package/dist/core/web/public/next/assets/CapacityStrip-nWFHvqFB.js +2 -0
  39. package/dist/core/web/public/next/assets/CapacityStrip-nWFHvqFB.js.map +1 -0
  40. package/dist/core/web/public/next/assets/ChatSection-DUZNpiVr.js +3 -0
  41. package/dist/core/web/public/next/assets/ChatSection-DUZNpiVr.js.map +1 -0
  42. package/dist/core/web/public/next/assets/{CloudBudgetForm-DJeJvBEu.js → CloudBudgetForm-2OW38o_C.js} +2 -2
  43. package/dist/core/web/public/next/assets/{CloudBudgetForm-DJeJvBEu.js.map → CloudBudgetForm-2OW38o_C.js.map} +1 -1
  44. package/dist/core/web/public/next/assets/CommandPalette-C5w2o1-r.js +2 -0
  45. package/dist/core/web/public/next/assets/{CommandPalette-FiD4N8XD.js.map → CommandPalette-C5w2o1-r.js.map} +1 -1
  46. package/dist/core/web/public/next/assets/CommandSection-Bx2knNqY.js +2 -0
  47. package/dist/core/web/public/next/assets/{CommandSection-DN8KwH-X.js.map → CommandSection-Bx2knNqY.js.map} +1 -1
  48. package/dist/core/web/public/next/assets/ConfirmDialog-B7ZwCQOX.js +2 -0
  49. package/dist/core/web/public/next/assets/ConfirmDialog-B7ZwCQOX.js.map +1 -0
  50. package/dist/core/web/public/next/assets/ConfirmDialog-Dx15x0Ho.css +1 -0
  51. package/dist/core/web/public/next/assets/Dialog-BpgcEW6b.js +2 -0
  52. package/dist/core/web/public/next/assets/Dialog-BpgcEW6b.js.map +1 -0
  53. package/dist/core/web/public/next/assets/Dialog-tQ8kFulD.css +1 -0
  54. package/dist/core/web/public/next/assets/{DiffPane-DLJ3gGGK.js → DiffPane-DpqRUtCx.js} +2 -2
  55. package/dist/core/web/public/next/assets/{DiffPane-DLJ3gGGK.js.map → DiffPane-DpqRUtCx.js.map} +1 -1
  56. package/dist/core/web/public/next/assets/{DiffViewer-B7NWPjIO.js → DiffViewer-CoV5-nUf.js} +2 -2
  57. package/dist/core/web/public/next/assets/{DiffViewer-B7NWPjIO.js.map → DiffViewer-CoV5-nUf.js.map} +1 -1
  58. package/dist/core/web/public/next/assets/DockHost-C0Pq7Jak.js +2 -0
  59. package/dist/core/web/public/next/assets/{DockHost-DvR-hPhZ.js.map → DockHost-C0Pq7Jak.js.map} +1 -1
  60. package/dist/core/web/public/next/assets/{EmptyState-DhNgDDrF.js → EmptyState-DYW29ncw.js} +2 -2
  61. package/dist/core/web/public/next/assets/{EmptyState-DhNgDDrF.js.map → EmptyState-DYW29ncw.js.map} +1 -1
  62. package/dist/core/web/public/next/assets/{Epistemic-uARPnx9x.js → Epistemic-DVx6oHzp.js} +2 -2
  63. package/dist/core/web/public/next/assets/{Epistemic-uARPnx9x.js.map → Epistemic-DVx6oHzp.js.map} +1 -1
  64. package/dist/core/web/public/next/assets/FleetSection-DSETviPK.js +3 -0
  65. package/dist/core/web/public/next/assets/{FleetSection-D_zRBCub.js.map → FleetSection-DSETviPK.js.map} +1 -1
  66. package/dist/core/web/public/next/assets/{Funnel-CL8uHONq.js → Funnel-DxiGqJrt.js} +2 -2
  67. package/dist/core/web/public/next/assets/{Funnel-CL8uHONq.js.map → Funnel-DxiGqJrt.js.map} +1 -1
  68. package/dist/core/web/public/next/assets/GearTray-C3I89sIt.js +2 -0
  69. package/dist/core/web/public/next/assets/{GearTray-C7k4l_BW.js.map → GearTray-C3I89sIt.js.map} +1 -1
  70. package/dist/core/web/public/next/assets/{GrowthSection-DwdNSGj8.js → GrowthSection-C3m2bPEz.js} +2 -2
  71. package/dist/core/web/public/next/assets/{GrowthSection-DwdNSGj8.js.map → GrowthSection-C3m2bPEz.js.map} +1 -1
  72. package/dist/core/web/public/next/assets/HandoffDialog-Bjb96a0N.js +2 -0
  73. package/dist/core/web/public/next/assets/{HandoffDialog-FPVOqOMN.js.map → HandoffDialog-Bjb96a0N.js.map} +1 -1
  74. package/dist/core/web/public/next/assets/{Input-B9-VLcHS.js → Input-ekTehVaH.js} +2 -2
  75. package/dist/core/web/public/next/assets/{Input-B9-VLcHS.js.map → Input-ekTehVaH.js.map} +1 -1
  76. package/dist/core/web/public/next/assets/LeaderCard-85VBbbaZ.js +2 -0
  77. package/dist/core/web/public/next/assets/{LeaderCard-4R0BhDzT.js.map → LeaderCard-85VBbbaZ.js.map} +1 -1
  78. package/dist/core/web/public/next/assets/{MatrixHeatmap-DiuushcS.js → MatrixHeatmap-Bo8j0QPC.js} +2 -2
  79. package/dist/core/web/public/next/assets/{MatrixHeatmap-DiuushcS.js.map → MatrixHeatmap-Bo8j0QPC.js.map} +1 -1
  80. package/dist/core/web/public/next/assets/McpSection-DvHFaVyp.js +2 -0
  81. package/dist/core/web/public/next/assets/{McpSection-CJ__OJKl.js.map → McpSection-DvHFaVyp.js.map} +1 -1
  82. package/dist/core/web/public/next/assets/{MessageMarkdown-qWKyJvAQ.js → MessageMarkdown-fsY_ltVa.js} +3 -3
  83. package/dist/core/web/public/next/assets/{MessageMarkdown-qWKyJvAQ.js.map → MessageMarkdown-fsY_ltVa.js.map} +1 -1
  84. package/dist/core/web/public/next/assets/{MessageMarkdownRenderer-x05TrsrW.js → MessageMarkdownRenderer-DDdtu82l.js} +2 -2
  85. package/dist/core/web/public/next/assets/{MessageMarkdownRenderer-x05TrsrW.js.map → MessageMarkdownRenderer-DDdtu82l.js.map} +1 -1
  86. package/dist/core/web/public/next/assets/{Meter-D-hncpkO.js → Meter-CeGDXdDM.js} +2 -2
  87. package/dist/core/web/public/next/assets/{Meter-D-hncpkO.js.map → Meter-CeGDXdDM.js.map} +1 -1
  88. package/dist/core/web/public/next/assets/MindSection-CDVhHvXl.js +2 -0
  89. package/dist/core/web/public/next/assets/{MindSection-BjXHkQPS.js.map → MindSection-CDVhHvXl.js.map} +1 -1
  90. package/dist/core/web/public/next/assets/MutationTokenDialog-CrfJ0PdD.css +1 -0
  91. package/dist/core/web/public/next/assets/MutationTokenDialog-DTQzEOHR.js +2 -0
  92. package/dist/core/web/public/next/assets/MutationTokenDialog-DTQzEOHR.js.map +1 -0
  93. package/dist/core/web/public/next/assets/NeedsYouDrawer-6AuN0BF3.js +2 -0
  94. package/dist/core/web/public/next/assets/{NeedsYouDrawer-rSd1ZHAR.js.map → NeedsYouDrawer-6AuN0BF3.js.map} +1 -1
  95. package/dist/core/web/public/next/assets/{NeedsYouList-CG-nB1tr.js → NeedsYouList-ChnRmbSk.js} +2 -2
  96. package/dist/core/web/public/next/assets/{NeedsYouList-CG-nB1tr.js.map → NeedsYouList-ChnRmbSk.js.map} +1 -1
  97. package/dist/core/web/public/next/assets/NewChatDialog-B06E7sqg.js +3 -0
  98. package/dist/core/web/public/next/assets/{NewChatDialog-BeDQ1eW3.js.map → NewChatDialog-B06E7sqg.js.map} +1 -1
  99. package/dist/core/web/public/next/assets/OnboardingFlow-C2M3zov9.js +2 -0
  100. package/dist/core/web/public/next/assets/{OnboardingFlow-CkTZtt-m.js.map → OnboardingFlow-C2M3zov9.js.map} +1 -1
  101. package/dist/core/web/public/next/assets/PreviewPane-Bd6HsX1k.js +2 -0
  102. package/dist/core/web/public/next/assets/{PreviewPane-kzbbSK4u.js.map → PreviewPane-Bd6HsX1k.js.map} +1 -1
  103. package/dist/core/web/public/next/assets/ProviderLogo-Bz7KR5j3.js +2 -0
  104. package/dist/core/web/public/next/assets/ProviderLogo-Bz7KR5j3.js.map +1 -0
  105. package/dist/core/web/public/next/assets/{RailStatus-D-t2gPae.js → RailStatus-CctCuuLy.js} +2 -2
  106. package/dist/core/web/public/next/assets/{RailStatus-D-t2gPae.js.map → RailStatus-CctCuuLy.js.map} +1 -1
  107. package/dist/core/web/public/next/assets/{RefreshIndicator-BLnr9IOM.js → RefreshIndicator-CKY5BGdD.js} +2 -2
  108. package/dist/core/web/public/next/assets/{RefreshIndicator-BLnr9IOM.js.map → RefreshIndicator-CKY5BGdD.js.map} +1 -1
  109. package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-CM_8pJs6.css → ResourcePoolConsoleApp-BX9JLkT4.css} +1 -1
  110. package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-BeG0gBif.js → ResourcePoolConsoleApp-CspIsW_G.js} +5 -5
  111. package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-BeG0gBif.js.map → ResourcePoolConsoleApp-CspIsW_G.js.map} +1 -1
  112. package/dist/core/web/public/next/assets/ResourcesChrome-B7fHZ6zX.js +3 -0
  113. package/dist/core/web/public/next/assets/ResourcesChrome-B7fHZ6zX.js.map +1 -0
  114. package/dist/core/web/public/next/assets/ResourcesChrome-uZEqC2EM.css +1 -0
  115. package/dist/core/web/public/next/assets/ResourcesDrawer-B7-N9n7-.js +2 -0
  116. package/dist/core/web/public/next/assets/ResourcesDrawer-B7-N9n7-.js.map +1 -0
  117. package/dist/core/web/public/next/assets/RouteErrorBoundary-C_B11J0s.js +2 -0
  118. package/dist/core/web/public/next/assets/RouteErrorBoundary-C_B11J0s.js.map +1 -0
  119. package/dist/core/web/public/next/assets/RouteErrorBoundary-DdxhTdZ6.css +1 -0
  120. package/dist/core/web/public/next/assets/{RunInCloudAction-C3LBp7C4.js → RunInCloudAction-_juw4EXd.js} +2 -2
  121. package/dist/core/web/public/next/assets/{RunInCloudAction-C3LBp7C4.js.map → RunInCloudAction-_juw4EXd.js.map} +1 -1
  122. package/dist/core/web/public/next/assets/SeatSelector-D_YXvkPy.js +3 -0
  123. package/dist/core/web/public/next/assets/{SeatSelector-DpvvbjNm.js.map → SeatSelector-D_YXvkPy.js.map} +1 -1
  124. package/dist/core/web/public/next/assets/{Segmented-BL40udoO.js → Segmented-DnKP6rr7.js} +2 -2
  125. package/dist/core/web/public/next/assets/{Segmented-BL40udoO.js.map → Segmented-DnKP6rr7.js.map} +1 -1
  126. package/dist/core/web/public/next/assets/{Select-FWLnAw_j.js → Select-DlbUupTX.js} +2 -2
  127. package/dist/core/web/public/next/assets/{Select-FWLnAw_j.js.map → Select-DlbUupTX.js.map} +1 -1
  128. package/dist/core/web/public/next/assets/SessionGate-DnuTZACL.js +2 -0
  129. package/dist/core/web/public/next/assets/SessionGate-DnuTZACL.js.map +1 -0
  130. package/dist/core/web/public/next/assets/SessionGate-DvMpXNpJ.css +1 -0
  131. package/dist/core/web/public/next/assets/SessionInsightChip-C33-k-Q5.js +2 -0
  132. package/dist/core/web/public/next/assets/{SessionInsightChip-CWye87Ju.js.map → SessionInsightChip-C33-k-Q5.js.map} +1 -1
  133. package/dist/core/web/public/next/assets/{SessionSearch-YvCHFUuN.js → SessionSearch-DhhR9Csg.js} +2 -2
  134. package/dist/core/web/public/next/assets/{SessionSearch-YvCHFUuN.js.map → SessionSearch-DhhR9Csg.js.map} +1 -1
  135. package/dist/core/web/public/next/assets/SettingsSection-C0xQvCz3.js +2 -0
  136. package/dist/core/web/public/next/assets/SettingsSection-C0xQvCz3.js.map +1 -0
  137. package/dist/core/web/public/next/assets/Sheet-BvNYOYv5.js +2 -0
  138. package/dist/core/web/public/next/assets/{Sheet-DZKcQXQg.js.map → Sheet-BvNYOYv5.js.map} +1 -1
  139. package/dist/core/web/public/next/assets/ShortcutsOverlay-1nUBVO-Z.js +2 -0
  140. package/dist/core/web/public/next/assets/{ShortcutsOverlay-BpDimdPq.js.map → ShortcutsOverlay-1nUBVO-Z.js.map} +1 -1
  141. package/dist/core/web/public/next/assets/Sidebar-YrVvvBNM.js +3 -0
  142. package/dist/core/web/public/next/assets/{Sidebar-BoBzBEFX.js.map → Sidebar-YrVvvBNM.js.map} +1 -1
  143. package/dist/core/web/public/next/assets/{Slider-DUMo9HDD.js → Slider-B2c-dbQb.js} +2 -2
  144. package/dist/core/web/public/next/assets/{Slider-DUMo9HDD.js.map → Slider-B2c-dbQb.js.map} +1 -1
  145. package/dist/core/web/public/next/assets/{StatusBadge-BwnBNFQr.js → StatusBadge-Q9YYkijx.js} +2 -2
  146. package/dist/core/web/public/next/assets/{StatusBadge-BwnBNFQr.js.map → StatusBadge-Q9YYkijx.js.map} +1 -1
  147. package/dist/core/web/public/next/assets/{StepBand-PDXMIlRk.js → StepBand-Ci-C0GUG.js} +2 -2
  148. package/dist/core/web/public/next/assets/{StepBand-PDXMIlRk.js.map → StepBand-Ci-C0GUG.js.map} +1 -1
  149. package/dist/core/web/public/next/assets/{Surface-B3TZVoER.js → Surface-DBovMq4P.js} +2 -2
  150. package/dist/core/web/public/next/assets/{Surface-B3TZVoER.js.map → Surface-DBovMq4P.js.map} +1 -1
  151. package/dist/core/web/public/next/assets/{Swimlane-DR4OgON_.js → Swimlane-Cdc4TqDe.js} +2 -2
  152. package/dist/core/web/public/next/assets/{Swimlane-DR4OgON_.js.map → Swimlane-Cdc4TqDe.js.map} +1 -1
  153. package/dist/core/web/public/next/assets/{Switch-CcR5Llk5.js → Switch-CUrvzO2k.js} +2 -2
  154. package/dist/core/web/public/next/assets/{Switch-CcR5Llk5.js.map → Switch-CUrvzO2k.js.map} +1 -1
  155. package/dist/core/web/public/next/assets/{Tag-_-K1h7Hk.js → Tag-Bv8u6APt.js} +2 -2
  156. package/dist/core/web/public/next/assets/{Tag-_-K1h7Hk.js.map → Tag-Bv8u6APt.js.map} +1 -1
  157. package/dist/core/web/public/next/assets/TerminalPane-BE4Xlqi5.js +7 -0
  158. package/dist/core/web/public/next/assets/{TerminalPane-_1aH4ndN.js.map → TerminalPane-BE4Xlqi5.js.map} +1 -1
  159. package/dist/core/web/public/next/assets/UniverseConsoleApp-BGwejf0m.js +2 -0
  160. package/dist/core/web/public/next/assets/{UniverseConsoleApp-BUsq-inZ.js.map → UniverseConsoleApp-BGwejf0m.js.map} +1 -1
  161. package/dist/core/web/public/next/assets/{UniverseConsoleApp-Dh7A-hLD.css → UniverseConsoleApp-DfuT-wFw.css} +1 -1
  162. package/dist/core/web/public/next/assets/{UniverseView-Dogy_dsB.js → UniverseView-CgKa-WXC.js} +4 -4
  163. package/dist/core/web/public/next/assets/{UniverseView-Dogy_dsB.js.map → UniverseView-CgKa-WXC.js.map} +1 -1
  164. package/dist/core/web/public/next/assets/UsageSection-CazsHm1f.js +2 -0
  165. package/dist/core/web/public/next/assets/{UsageSection-DiX_dTyo.js.map → UsageSection-CazsHm1f.js.map} +1 -1
  166. package/dist/core/web/public/next/assets/VerseConsoleApp-CTF3Shs0.js +3 -0
  167. package/dist/core/web/public/next/assets/VerseConsoleApp-CTF3Shs0.js.map +1 -0
  168. package/dist/core/web/public/next/assets/Workspace-CgdyXiEV.js +10 -0
  169. package/dist/core/web/public/next/assets/Workspace-CgdyXiEV.js.map +1 -0
  170. package/dist/core/web/public/next/assets/Workspace-Dwri2jFJ.css +1 -0
  171. package/dist/core/web/public/next/assets/actions-MtxBGS5B.js +2 -0
  172. package/dist/core/web/public/next/assets/{actions-s2lfm_5k.js.map → actions-MtxBGS5B.js.map} +1 -1
  173. package/dist/core/web/public/next/assets/budget-queries-CP7hgR-p.js +2 -0
  174. package/dist/core/web/public/next/assets/{budget-queries-wUshcovN.js.map → budget-queries-CP7hgR-p.js.map} +1 -1
  175. package/dist/core/web/public/next/assets/{capacity-strip-model-Bj_9ztzs.js → capacity-strip-model-fHfu5cph.js} +2 -2
  176. package/dist/core/web/public/next/assets/{capacity-strip-model-Bj_9ztzs.js.map → capacity-strip-model-fHfu5cph.js.map} +1 -1
  177. package/dist/core/web/public/next/assets/{charts-CMSgAUKn.js → charts-Bis9WSiS.js} +2 -2
  178. package/dist/core/web/public/next/assets/{charts-CMSgAUKn.js.map → charts-Bis9WSiS.js.map} +1 -1
  179. package/dist/core/web/public/next/assets/cloud-queries-BsOepFz6.js +2 -0
  180. package/dist/core/web/public/next/assets/{cloud-queries-CnV1yaNG.js.map → cloud-queries-BsOepFz6.js.map} +1 -1
  181. package/dist/core/web/public/next/assets/{cloud.module-CiwatLj3.js → cloud.module-DoFnr8ZH.js} +2 -2
  182. package/dist/core/web/public/next/assets/{cloud.module-CiwatLj3.js.map → cloud.module-DoFnr8ZH.js.map} +1 -1
  183. package/dist/core/web/public/next/assets/{command-bus-DqimKCUY.js → command-bus-CMvbYP5K.js} +2 -2
  184. package/dist/core/web/public/next/assets/{command-bus-DqimKCUY.js.map → command-bus-CMvbYP5K.js.map} +1 -1
  185. package/dist/core/web/public/next/assets/command-catalog-Bb_PxC5w.js +2 -0
  186. package/dist/core/web/public/next/assets/command-catalog-Bb_PxC5w.js.map +1 -0
  187. package/dist/core/web/public/next/assets/compaction-point-VL9oegWS.js +2 -0
  188. package/dist/core/web/public/next/assets/compaction-point-VL9oegWS.js.map +1 -0
  189. package/dist/core/web/public/next/assets/{composer-keys-DBRymxQn.js → composer-keys-su0g1h9o.js} +2 -2
  190. package/dist/core/web/public/next/assets/{composer-keys-DBRymxQn.js.map → composer-keys-su0g1h9o.js.map} +1 -1
  191. package/dist/core/web/public/next/assets/context-model-Ci3Ch9t5.js +2 -0
  192. package/dist/core/web/public/next/assets/{context-model-Dwd49-dp.js.map → context-model-Ci3Ch9t5.js.map} +1 -1
  193. package/dist/core/web/public/next/assets/context-model-CjiJTnf1.js +2 -0
  194. package/dist/core/web/public/next/assets/{context-model-MT17V86_.js.map → context-model-CjiJTnf1.js.map} +1 -1
  195. package/dist/core/web/public/next/assets/context-queries-BDXT6LQ3.js +2 -0
  196. package/dist/core/web/public/next/assets/{context-queries-xVKIeynP.js.map → context-queries-BDXT6LQ3.js.map} +1 -1
  197. package/dist/core/web/public/next/assets/control-queries-Dv-h1Clu.js +2 -0
  198. package/dist/core/web/public/next/assets/{control-queries-DOZR-D8G.js.map → control-queries-Dv-h1Clu.js.map} +1 -1
  199. package/dist/core/web/public/next/assets/{dock-icons-8EtARwUm.js → dock-icons-t--Eugrd.js} +2 -2
  200. package/dist/core/web/public/next/assets/{dock-icons-8EtARwUm.js.map → dock-icons-t--Eugrd.js.map} +1 -1
  201. package/dist/core/web/public/next/assets/{dock-store-BA1cHOYA.js → dock-store-qIu7kSQE.js} +2 -2
  202. package/dist/core/web/public/next/assets/{dock-store-BA1cHOYA.js.map → dock-store-qIu7kSQE.js.map} +1 -1
  203. package/dist/core/web/public/next/assets/{fleet-model-CNe8Wh-s.js → fleet-model-IRbwfhkR.js} +2 -2
  204. package/dist/core/web/public/next/assets/{fleet-model-CNe8Wh-s.js.map → fleet-model-IRbwfhkR.js.map} +1 -1
  205. package/dist/core/web/public/next/assets/fleet-queries-7FVNByX1.js +2 -0
  206. package/dist/core/web/public/next/assets/{fleet-queries-BSdRlUwJ.js.map → fleet-queries-7FVNByX1.js.map} +1 -1
  207. package/dist/core/web/public/next/assets/focus-trap-QqQxQdvH.js +2 -0
  208. package/dist/core/web/public/next/assets/focus-trap-QqQxQdvH.js.map +1 -0
  209. package/dist/core/web/public/next/assets/{format-B8DAVcn2.js → format-CaMmVQCn.js} +2 -2
  210. package/dist/core/web/public/next/assets/{format-B8DAVcn2.js.map → format-CaMmVQCn.js.map} +1 -1
  211. package/dist/core/web/public/next/assets/git-model-B4S_GUNA.js +5 -0
  212. package/dist/core/web/public/next/assets/{git-model-CivuBY_D.js.map → git-model-B4S_GUNA.js.map} +1 -1
  213. package/dist/core/web/public/next/assets/global-BkNl4e6E.js +2 -0
  214. package/dist/core/web/public/next/assets/global-BkNl4e6E.js.map +1 -0
  215. package/dist/core/web/public/next/assets/global-BlI2BYa1.css +1 -0
  216. package/dist/core/web/public/next/assets/guarded-action-DHiL8tY5.js +3 -0
  217. package/dist/core/web/public/next/assets/guarded-action-DHiL8tY5.js.map +1 -0
  218. package/dist/core/web/public/next/assets/guarded-runners-DmGEkM2P.js +2 -0
  219. package/dist/core/web/public/next/assets/guarded-runners-DmGEkM2P.js.map +1 -0
  220. package/dist/core/web/public/next/assets/icon-base-NtFmP2yB.js +2 -0
  221. package/dist/core/web/public/next/assets/icon-base-NtFmP2yB.js.map +1 -0
  222. package/dist/core/web/public/next/assets/icons-C5EFt_Ts.js +2 -0
  223. package/dist/core/web/public/next/assets/icons-C5EFt_Ts.js.map +1 -0
  224. package/dist/core/web/public/next/assets/{index-oAAq8XJa.js → index-DbDw9UAs.js} +3 -3
  225. package/dist/core/web/public/next/assets/{index-oAAq8XJa.js.map → index-DbDw9UAs.js.map} +1 -1
  226. package/dist/core/web/public/next/assets/{live-model-DCw_IFRI.js → live-model-DEwrl7_A.js} +2 -2
  227. package/dist/core/web/public/next/assets/{live-model-DCw_IFRI.js.map → live-model-DEwrl7_A.js.map} +1 -1
  228. package/dist/core/web/public/next/assets/{mind.module-vnuJcwvD.js → mind.module-CLhqMQak.js} +2 -2
  229. package/dist/core/web/public/next/assets/{mind.module-vnuJcwvD.js.map → mind.module-CLhqMQak.js.map} +1 -1
  230. package/dist/core/web/public/next/assets/mutations-Kvzsg-iK.js +2 -0
  231. package/dist/core/web/public/next/assets/{mutations-Dpsql5t7.js.map → mutations-Kvzsg-iK.js.map} +1 -1
  232. package/dist/core/web/public/next/assets/nav-C0IunEYF.js +2 -0
  233. package/dist/core/web/public/next/assets/nav-C0IunEYF.js.map +1 -0
  234. package/dist/core/web/public/next/assets/overnight-queries-C_AfcL7q.js +2 -0
  235. package/dist/core/web/public/next/assets/{overnight-queries-X-44Gsw5.js.map → overnight-queries-C_AfcL7q.js.map} +1 -1
  236. package/dist/core/web/public/next/assets/preloaded-CctOitLu.js +2 -0
  237. package/dist/core/web/public/next/assets/preloaded-CctOitLu.js.map +1 -0
  238. package/dist/core/web/public/next/assets/primitives-CbAGCNKt.js +1 -0
  239. package/dist/core/web/public/next/assets/{queries-BA4rNGD-.js → queries-THD05gHP.js} +2 -2
  240. package/dist/core/web/public/next/assets/{queries-BA4rNGD-.js.map → queries-THD05gHP.js.map} +1 -1
  241. package/dist/core/web/public/next/assets/reasoning-pref-BVPu0SME.js +2 -0
  242. package/dist/core/web/public/next/assets/{reasoning-pref-B9-DJUXP.js.map → reasoning-pref-BVPu0SME.js.map} +1 -1
  243. package/dist/core/web/public/next/assets/reveal-anchor-wCT5b4m3.js +2 -0
  244. package/dist/core/web/public/next/assets/reveal-anchor-wCT5b4m3.js.map +1 -0
  245. package/dist/core/web/public/next/assets/seat-subscription-CR_VylsK.js +2 -0
  246. package/dist/core/web/public/next/assets/{seat-subscription-CH6fW4fJ.js.map → seat-subscription-CR_VylsK.js.map} +1 -1
  247. package/dist/core/web/public/next/assets/{section-visibility-qFqc_d4Z.js → section-visibility-B0xXUQJ0.js} +2 -2
  248. package/dist/core/web/public/next/assets/{section-visibility-qFqc_d4Z.js.map → section-visibility-B0xXUQJ0.js.map} +1 -1
  249. package/dist/core/web/public/next/assets/{sidebar-model-M1GaQU5l.js → sidebar-model-C5wVXGpb.js} +2 -2
  250. package/dist/core/web/public/next/assets/{sidebar-model-M1GaQU5l.js.map → sidebar-model-C5wVXGpb.js.map} +1 -1
  251. package/dist/core/web/public/next/assets/slots-DDd8cEoh.js +3 -0
  252. package/dist/core/web/public/next/assets/{slots-Ds9ZIqUL.js.map → slots-DDd8cEoh.js.map} +1 -1
  253. package/dist/core/web/public/next/assets/surface-data-BFxAlefI.js +2 -0
  254. package/dist/core/web/public/next/assets/{surface-data-J52iIGMD.js.map → surface-data-BFxAlefI.js.map} +1 -1
  255. package/dist/core/web/public/next/assets/surface-prefetch-CHusyO9q.js +2 -0
  256. package/dist/core/web/public/next/assets/{surface-prefetch-CgZ-LEJK.js.map → surface-prefetch-CHusyO9q.js.map} +1 -1
  257. package/dist/core/web/public/next/assets/{tasks-model-ent8f0jZ.js → tasks-model-DvkWveXs.js} +2 -2
  258. package/dist/core/web/public/next/assets/{tasks-model-ent8f0jZ.js.map → tasks-model-DvkWveXs.js.map} +1 -1
  259. package/dist/core/web/public/next/assets/{terminal-icons-DJTK7QpR.js → terminal-icons-Dp6pJgV2.js} +2 -2
  260. package/dist/core/web/public/next/assets/{terminal-icons-DJTK7QpR.js.map → terminal-icons-Dp6pJgV2.js.map} +1 -1
  261. package/dist/core/web/public/next/assets/{usage-queries-Bn8ccqyN.js → usage-queries-CUTmnegA.js} +2 -2
  262. package/dist/core/web/public/next/assets/{usage-queries-Bn8ccqyN.js.map → usage-queries-CUTmnegA.js.map} +1 -1
  263. package/dist/core/web/public/next/assets/use-guarded-action-UCX2u1t1.js +2 -0
  264. package/dist/core/web/public/next/assets/{use-guarded-action-C2HlSzkz.js.map → use-guarded-action-UCX2u1t1.js.map} +1 -1
  265. package/dist/core/web/public/next/assets/{use-ticker-BNc1H4ZC.js → use-ticker-OXAjob3y.js} +2 -2
  266. package/dist/core/web/public/next/assets/{use-ticker-BNc1H4ZC.js.map → use-ticker-OXAjob3y.js.map} +1 -1
  267. package/dist/core/web/public/next/assets/use-token-gate-CDDRAoKH.js +2 -0
  268. package/dist/core/web/public/next/assets/{use-token-gate-B3IJL6bf.js.map → use-token-gate-CDDRAoKH.js.map} +1 -1
  269. package/dist/core/web/public/next/assets/useChartWidth-BW6SwYIK.css +1 -0
  270. package/dist/core/web/public/next/assets/useChartWidth-D9M7kUw7.js +2 -0
  271. package/dist/core/web/public/next/assets/{useChartWidth-lZ9PrQn0.js.map → useChartWidth-D9M7kUw7.js.map} +1 -1
  272. package/dist/core/web/public/next/assets/{useSeatsRefresh-CG4jczeL.js → useSeatsRefresh-B2dCVcj9.js} +2 -2
  273. package/dist/core/web/public/next/assets/{useSeatsRefresh-CG4jczeL.js.map → useSeatsRefresh-B2dCVcj9.js.map} +1 -1
  274. package/dist/core/web/public/next/assets/useVerseTranscript-BL3CljSI.js +2 -0
  275. package/dist/core/web/public/next/assets/useVerseTranscript-BL3CljSI.js.map +1 -0
  276. package/dist/core/web/public/next/assets/verse-icons-DASWdYuE.js +2 -0
  277. package/dist/core/web/public/next/assets/verse-icons-DASWdYuE.js.map +1 -0
  278. package/dist/core/web/public/next/assets/verse-model-BQ0Y2CSr.js +2 -0
  279. package/dist/core/web/public/next/assets/verse-model-BQ0Y2CSr.js.map +1 -0
  280. package/dist/core/web/public/next/assets/verse-store-b_Lek_c5.js +3 -0
  281. package/dist/core/web/public/next/assets/verse-store-b_Lek_c5.js.map +1 -0
  282. package/dist/core/web/public/next/assets/verse-ui-store-D7YXi63q.js +3 -0
  283. package/dist/core/web/public/next/assets/{verse-ui-store-zafyzXKD.js.map → verse-ui-store-D7YXi63q.js.map} +1 -1
  284. package/dist/core/web/public/next/assets/{viewport-BEqGUoTA.js → viewport-BYGH5EgA.js} +2 -2
  285. package/dist/core/web/public/next/assets/{viewport-BEqGUoTA.js.map → viewport-BYGH5EgA.js.map} +1 -1
  286. package/dist/core/web/public/next/assets/warmup-AsqwWCUi.js +3 -0
  287. package/dist/core/web/public/next/assets/{warmup-BR6pxAKs.js.map → warmup-AsqwWCUi.js.map} +1 -1
  288. package/dist/core/web/public/next/assets/{workspace-model-D_tWD5Wt.js → workspace-model-CXorJr0G.js} +2 -2
  289. package/dist/core/web/public/next/assets/{workspace-model-D_tWD5Wt.js.map → workspace-model-CXorJr0G.js.map} +1 -1
  290. package/dist/core/web/public/next/index.html +1 -1
  291. package/dist/release-dependency-inventory.json +1 -1
  292. package/package.json +5 -2
  293. package/dist/cli/activation.d.ts +0 -41
  294. package/dist/cli/activation.js +0 -485
  295. package/dist/cli/activation.js.map +0 -1
  296. package/dist/core/fleet/generated-repair-resolution.d.ts +0 -36
  297. package/dist/core/fleet/generated-repair-resolution.js +0 -245
  298. package/dist/core/fleet/generated-repair-resolution.js.map +0 -1
  299. package/dist/core/local-runtime/llama/port.d.ts +0 -17
  300. package/dist/core/local-runtime/llama/port.js +0 -47
  301. package/dist/core/local-runtime/llama/port.js.map +0 -1
  302. package/dist/core/local-runtime/llama/proxy-host.d.ts +0 -54
  303. package/dist/core/local-runtime/llama/proxy-host.js +0 -159
  304. package/dist/core/local-runtime/llama/proxy-host.js.map +0 -1
  305. package/dist/core/local-runtime/llama/proxy-process.d.ts +0 -148
  306. package/dist/core/local-runtime/llama/proxy-process.js +0 -250
  307. package/dist/core/local-runtime/llama/proxy-process.js.map +0 -1
  308. package/dist/core/local-runtime/llama/proxy-record.d.ts +0 -40
  309. package/dist/core/local-runtime/llama/proxy-record.js +0 -133
  310. package/dist/core/local-runtime/llama/proxy-record.js.map +0 -1
  311. package/dist/core/local-runtime/llama/proxy-supervisor.d.ts +0 -118
  312. package/dist/core/local-runtime/llama/proxy-supervisor.js +0 -511
  313. package/dist/core/local-runtime/llama/proxy-supervisor.js.map +0 -1
  314. package/dist/core/web/public/next/assets/Advanced-DEM93sKI.js +0 -2
  315. package/dist/core/web/public/next/assets/App-CimXmK0L.js +0 -7
  316. package/dist/core/web/public/next/assets/AppsSection-D6U-dSj6.js +0 -2
  317. package/dist/core/web/public/next/assets/AutonomySection-3V9pUyxm.js +0 -2
  318. package/dist/core/web/public/next/assets/BranchBar-U8UdpfEB.js +0 -2
  319. package/dist/core/web/public/next/assets/CapacityStrip-C667yQe8.js +0 -2
  320. package/dist/core/web/public/next/assets/CapacityStrip-C667yQe8.js.map +0 -1
  321. package/dist/core/web/public/next/assets/ChatSection-CCcIPpOq.js +0 -3
  322. package/dist/core/web/public/next/assets/ChatSection-CCcIPpOq.js.map +0 -1
  323. package/dist/core/web/public/next/assets/CommandPalette-FiD4N8XD.js +0 -2
  324. package/dist/core/web/public/next/assets/CommandSection-DN8KwH-X.js +0 -2
  325. package/dist/core/web/public/next/assets/DockHost-DvR-hPhZ.js +0 -2
  326. package/dist/core/web/public/next/assets/FleetSection-D_zRBCub.js +0 -3
  327. package/dist/core/web/public/next/assets/GearTray-C7k4l_BW.js +0 -2
  328. package/dist/core/web/public/next/assets/HandoffDialog-FPVOqOMN.js +0 -2
  329. package/dist/core/web/public/next/assets/LeaderCard-4R0BhDzT.js +0 -2
  330. package/dist/core/web/public/next/assets/McpSection-CJ__OJKl.js +0 -2
  331. package/dist/core/web/public/next/assets/MindSection-BjXHkQPS.js +0 -2
  332. package/dist/core/web/public/next/assets/MutationTokenDialog-Cum_DqQZ.css +0 -1
  333. package/dist/core/web/public/next/assets/MutationTokenDialog-SkRxqV_I.js +0 -2
  334. package/dist/core/web/public/next/assets/MutationTokenDialog-SkRxqV_I.js.map +0 -1
  335. package/dist/core/web/public/next/assets/NeedsYouDrawer-rSd1ZHAR.js +0 -2
  336. package/dist/core/web/public/next/assets/NewChatDialog-BeDQ1eW3.js +0 -3
  337. package/dist/core/web/public/next/assets/OnboardingFlow-CkTZtt-m.js +0 -2
  338. package/dist/core/web/public/next/assets/PreviewPane-kzbbSK4u.js +0 -2
  339. package/dist/core/web/public/next/assets/ResourcesChrome-B3Z3O98t.css +0 -1
  340. package/dist/core/web/public/next/assets/ResourcesChrome-BzV6bkIa.js +0 -3
  341. package/dist/core/web/public/next/assets/ResourcesChrome-BzV6bkIa.js.map +0 -1
  342. package/dist/core/web/public/next/assets/ResourcesDrawer-o4ILHdep.js +0 -2
  343. package/dist/core/web/public/next/assets/ResourcesDrawer-o4ILHdep.js.map +0 -1
  344. package/dist/core/web/public/next/assets/RouteErrorBoundary-6RMd_aZg.css +0 -1
  345. package/dist/core/web/public/next/assets/RouteErrorBoundary-C5SxA2Dg.js +0 -2
  346. package/dist/core/web/public/next/assets/RouteErrorBoundary-C5SxA2Dg.js.map +0 -1
  347. package/dist/core/web/public/next/assets/SeatSelector-DpvvbjNm.js +0 -3
  348. package/dist/core/web/public/next/assets/SessionInsightChip-CWye87Ju.js +0 -2
  349. package/dist/core/web/public/next/assets/SettingsSection-Dr18upeE.js +0 -2
  350. package/dist/core/web/public/next/assets/SettingsSection-Dr18upeE.js.map +0 -1
  351. package/dist/core/web/public/next/assets/Sheet-DZKcQXQg.js +0 -2
  352. package/dist/core/web/public/next/assets/ShortcutsOverlay-BpDimdPq.js +0 -2
  353. package/dist/core/web/public/next/assets/Sidebar-BoBzBEFX.js +0 -3
  354. package/dist/core/web/public/next/assets/TerminalPane-_1aH4ndN.js +0 -7
  355. package/dist/core/web/public/next/assets/UniverseConsoleApp-BUsq-inZ.js +0 -2
  356. package/dist/core/web/public/next/assets/UsageSection-DiX_dTyo.js +0 -2
  357. package/dist/core/web/public/next/assets/VerseConsoleApp-WVeyJnW7.js +0 -3
  358. package/dist/core/web/public/next/assets/VerseConsoleApp-WVeyJnW7.js.map +0 -1
  359. package/dist/core/web/public/next/assets/Workspace-BYW9dm90.css +0 -1
  360. package/dist/core/web/public/next/assets/Workspace-BbxZHwQd.js +0 -10
  361. package/dist/core/web/public/next/assets/Workspace-BbxZHwQd.js.map +0 -1
  362. package/dist/core/web/public/next/assets/actions-s2lfm_5k.js +0 -2
  363. package/dist/core/web/public/next/assets/budget-queries-wUshcovN.js +0 -2
  364. package/dist/core/web/public/next/assets/cloud-queries-CnV1yaNG.js +0 -2
  365. package/dist/core/web/public/next/assets/command-catalog-BdtDH93o.js +0 -2
  366. package/dist/core/web/public/next/assets/command-catalog-BdtDH93o.js.map +0 -1
  367. package/dist/core/web/public/next/assets/context-model-Dwd49-dp.js +0 -2
  368. package/dist/core/web/public/next/assets/context-model-MT17V86_.js +0 -2
  369. package/dist/core/web/public/next/assets/context-queries-xVKIeynP.js +0 -2
  370. package/dist/core/web/public/next/assets/control-queries-DOZR-D8G.js +0 -2
  371. package/dist/core/web/public/next/assets/dock-panes-UKS-mXhr.js +0 -2
  372. package/dist/core/web/public/next/assets/dock-panes-UKS-mXhr.js.map +0 -1
  373. package/dist/core/web/public/next/assets/fleet-queries-BSdRlUwJ.js +0 -2
  374. package/dist/core/web/public/next/assets/git-model-CivuBY_D.js +0 -5
  375. package/dist/core/web/public/next/assets/global-Di2PUgj4.js +0 -2
  376. package/dist/core/web/public/next/assets/global-Di2PUgj4.js.map +0 -1
  377. package/dist/core/web/public/next/assets/global-_tOCFt9y.css +0 -1
  378. package/dist/core/web/public/next/assets/guarded-action-CvOHcqyH.js +0 -2
  379. package/dist/core/web/public/next/assets/guarded-action-CvOHcqyH.js.map +0 -1
  380. package/dist/core/web/public/next/assets/icons-B_yuAgdN.js +0 -2
  381. package/dist/core/web/public/next/assets/icons-B_yuAgdN.js.map +0 -1
  382. package/dist/core/web/public/next/assets/mutations-Dpsql5t7.js +0 -2
  383. package/dist/core/web/public/next/assets/overnight-queries-X-44Gsw5.js +0 -2
  384. package/dist/core/web/public/next/assets/primitives-BDIcUvob.js +0 -1
  385. package/dist/core/web/public/next/assets/reasoning-pref-B9-DJUXP.js +0 -2
  386. package/dist/core/web/public/next/assets/seat-subscription-CH6fW4fJ.js +0 -2
  387. package/dist/core/web/public/next/assets/slots-Ds9ZIqUL.js +0 -3
  388. package/dist/core/web/public/next/assets/surface-data-J52iIGMD.js +0 -2
  389. package/dist/core/web/public/next/assets/surface-prefetch-CgZ-LEJK.js +0 -2
  390. package/dist/core/web/public/next/assets/use-guarded-action-C2HlSzkz.js +0 -2
  391. package/dist/core/web/public/next/assets/use-token-gate-B3IJL6bf.js +0 -2
  392. package/dist/core/web/public/next/assets/useActivity-Bk3XgXjL.js +0 -2
  393. package/dist/core/web/public/next/assets/useActivity-Bk3XgXjL.js.map +0 -1
  394. package/dist/core/web/public/next/assets/useChartWidth-BAtVgn2n.css +0 -1
  395. package/dist/core/web/public/next/assets/useChartWidth-lZ9PrQn0.js +0 -2
  396. package/dist/core/web/public/next/assets/verse-model-D7NZQFAR.js +0 -2
  397. package/dist/core/web/public/next/assets/verse-model-D7NZQFAR.js.map +0 -1
  398. package/dist/core/web/public/next/assets/verse-queries-DvIJcn6Z.js +0 -2
  399. package/dist/core/web/public/next/assets/verse-queries-DvIJcn6Z.js.map +0 -1
  400. package/dist/core/web/public/next/assets/verse-store-DjBWj04q.js +0 -3
  401. package/dist/core/web/public/next/assets/verse-store-DjBWj04q.js.map +0 -1
  402. package/dist/core/web/public/next/assets/verse-ui-store-zafyzXKD.js +0 -3
  403. package/dist/core/web/public/next/assets/warmup-BR6pxAKs.js +0 -3
@@ -1 +1 @@
1
- {"version":3,"file":"format-B8DAVcn2.js","names":[],"sources":["../../../../../../src/core/verse/seat-readiness.ts","../../../../../../src/web-ui/routes/verse/autonomy/format.ts"],"sourcesContent":["/**\n * core/verse/seat-readiness.ts — \"can this seat run a turn right now, and if\n * not, where else can the operator go?\" (V3.10, unit A2).\n *\n * PURE and BROWSER-SAFE on purpose: the engine's admission gate\n * (`getSeatReadiness` in core/verse/seats.ts) and the Composer's block\n * (web-ui/routes/verse/health/ComposerSeatBlock.tsx) must give the SAME\n * answer, with the SAME ranked alternatives, or the UI would offer a seat the\n * server then refuses. One implementation, imported by both. Type-only\n * imports and `Intl` only — no node: modules.\n *\n * ── WHAT BLOCKS A SEAT ─────────────────────────────────────────────────────\n * Only POSITIVE evidence that a turn would fail at the provider:\n * - `signed-out` — the CLI said so (status command or the live collector).\n * - `exhausted` — every window that carried a reading is spent and no\n * Codex credit balance remains (`seatUsability` in seats.ts;\n * a spent per-model window beside a healthy account-wide\n * one is `tight`, not exhausted).\n * `unknown`, `expiring` and `binary-skew` are WARNINGS, never refusals: an\n * admission gate that refused on \"we could not read it\" would lock Mason out\n * of his own seats every time a probe hiccuped (docs/VERSE-TELEMETRY-V2.md —\n * no signal is not a verdict).\n *\n * ── HOW ALTERNATIVES ARE RANKED ────────────────────────────────────────────\n * (research r2/accounts.md, recommendation 4): other subscription seats with\n * measured headroom first, then subscription seats whose headroom is tight or\n * unread, then local models (free, always last because they are the weakest).\n * Inside a tier the SAME ENGINE leads (a Codex user blocked on one account most\n * likely wants the other Codex account), then the lower binding-window\n * percentage, then discovery order. A seat with no runnable model, a local\n * seat whose runtime is not answering, or a seat that is itself blocked is\n * never offered.\n */\nimport type { SeatConnection, SeatHealthReport, SeatReadiness } from './health-types.js';\nimport type { VerseSeat } from './types.js';\n\n/** The only connections that refuse a turn. Everything else is a warning. */\nexport const SEAT_BLOCKING_CONNECTIONS: ReadonlySet<SeatConnection> = new Set<SeatConnection>(['signed-out', 'exhausted']);\n\n/** At most this many alternatives are offered — a list, not a directory. */\nexport const SEAT_MAX_ALTERNATIVES = 5;\n\nexport interface SeatBlock {\n connection: 'signed-out' | 'exhausted';\n /** One plain sentence naming the seat and the fact (\"Personal Codex is out of usage — resets Fri 2:25 PM.\"). */\n reason: string;\n /** Machine-readable reset instant, when the provider gave one. */\n resetAt: string | null;\n}\n\nfunction reportFor(reports: readonly SeatHealthReport[] | null | undefined, seatId: string): SeatHealthReport | null {\n if (!reports) return null;\n for (const report of reports) if (report.seatId === seatId) return report;\n return null;\n}\n\n/**\n * A reset instant as a short local phrase (\"Fri 2:25 PM\", \"Sep 30, 2:25 PM\"),\n * or null when `iso` is not a valid instant. Only a MACHINE-READABLE instant\n * is ever formatted — Claude's prose reset never reaches this function.\n */\nexport function describeResetAt(iso: string | null | undefined, now: number = Date.now()): string | null {\n if (typeof iso !== 'string' || iso.length === 0) return null;\n const at = Date.parse(iso);\n if (!Number.isFinite(at)) return null;\n const date = new Date(at);\n const time = date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });\n if (new Date(now).toDateString() === date.toDateString()) return `today ${time}`;\n // Within the coming week the weekday is the clearest name for a day.\n if (at > now && at - now < 6 * 24 * 60 * 60 * 1000) {\n return `${date.toLocaleDateString([], { weekday: 'short' })} ${time}`;\n }\n return `${date.toLocaleDateString([], { month: 'short', day: 'numeric' })}, ${time}`;\n}\n\n/** The provider's own reset wording for the seat's binding window (Claude publishes prose only). */\nfunction bindingResetDescription(seat: VerseSeat): string | null {\n const binding = seat.capacity?.binding ?? null;\n return binding?.resetDescription ?? null;\n}\n\n/**\n * Why `seat` cannot take a turn, or null when nothing positively blocks it.\n *\n * The health report (sweep + live telemetry) wins when present; without one\n * the seat's own `capacity.usability` — which is on every native seat the\n * server serves — is the fallback, so the seat picker can mark a blocked seat\n * before the health endpoint has ever been read.\n */\nexport function seatBlock(\n seat: VerseSeat,\n report: SeatHealthReport | null,\n now: number = Date.now(),\n): SeatBlock | null {\n let connection: SeatBlock['connection'] | null = null;\n let resetAt: string | null = null;\n if (report !== null) {\n if (report.connection === 'signed-out' || report.connection === 'exhausted') {\n connection = report.connection;\n resetAt = report.resetAt;\n }\n } else if (seat.engine !== 'local') {\n const usability = seat.capacity?.usability;\n if (usability === 'signed-out' || usability === 'exhausted') {\n connection = usability;\n resetAt = usability === 'exhausted' ? seat.capacity?.binding?.resetsAt ?? null : null;\n }\n }\n if (connection === null) return null;\n if (connection === 'signed-out') {\n return { connection, resetAt: null, reason: `${seat.label} is signed out — reconnect it to use this seat.` };\n }\n const when = describeResetAt(resetAt, now);\n const prose = when === null ? bindingResetDescription(seat) : null;\n const tail = when !== null ? ` — resets ${when}` : prose !== null ? ` — ${prose}` : '';\n return { connection, resetAt, reason: `${seat.label} is out of usage${tail}.` };\n}\n\nfunction hasRunnableModel(seat: VerseSeat): boolean {\n return seat.models.some((model) => !model.unavailableReason);\n}\n\n/** 0 = subscription seat with measured headroom, 1 = tight/unread subscription seat, 2 = local. */\nfunction tier(seat: VerseSeat): number {\n if (seat.engine === 'local') return 2;\n return seat.capacity?.usability === 'ready' ? 0 : 1;\n}\n\nfunction bindingPercent(seat: VerseSeat): number {\n const used = seat.capacity?.binding?.usedPercent;\n // Unread sorts after every reading: no signal is not \"0% used\".\n return typeof used === 'number' && Number.isFinite(used) ? used : 101;\n}\n\n/** Can `seat` be offered as somewhere else to go? */\nfunction offerable(seat: VerseSeat, report: SeatHealthReport | null, now: number): boolean {\n if (!hasRunnableModel(seat)) return false;\n if (seat.health.state === 'unavailable') return false;\n if (seatBlock(seat, report, now) !== null) return false;\n // A local seat is only worth offering while its runtime answers. `unknown`\n // with reasons is the sweep saying Ollama did not respond.\n if (seat.engine === 'local' && report !== null && report.connection === 'unknown' && report.reasons.length > 0) {\n return false;\n }\n return true;\n}\n\n/** Ranked ids of seats that could take the turn instead of `seatId` (see the file header). */\nexport function rankSeatAlternatives(\n seatId: string,\n seats: readonly VerseSeat[],\n reports: readonly SeatHealthReport[] | null | undefined,\n now: number = Date.now(),\n): string[] {\n const blocked = seats.find((seat) => seat.id === seatId) ?? null;\n const candidates = seats\n .map((seat, index) => ({ seat, index }))\n .filter(({ seat }) => seat.id !== seatId && offerable(seat, reportFor(reports, seat.id), now));\n candidates.sort((a, b) => {\n const byTier = tier(a.seat) - tier(b.seat);\n if (byTier !== 0) return byTier;\n if (blocked !== null) {\n const sameA = a.seat.engine === blocked.engine ? 0 : 1;\n const sameB = b.seat.engine === blocked.engine ? 0 : 1;\n if (sameA !== sameB) return sameA - sameB;\n }\n const byUse = bindingPercent(a.seat) - bindingPercent(b.seat);\n if (byUse !== 0) return byUse;\n return a.index - b.index;\n });\n return candidates.slice(0, SEAT_MAX_ALTERNATIVES).map(({ seat }) => seat.id);\n}\n\n/**\n * The admission answer for one seat. A seat that is not in `seats` at all is\n * reported READY with no reason: the readiness layer has no evidence about\n * it, and \"seat not found\" is the engine's own error to raise.\n */\nexport function seatReadiness(\n seatId: string,\n seats: readonly VerseSeat[],\n reports: readonly SeatHealthReport[] | null | undefined,\n now: number = Date.now(),\n): SeatReadiness {\n const seat = seats.find((candidate) => candidate.id === seatId) ?? null;\n if (seat === null) return { seatId, ready: true, reason: null, alternatives: [] };\n const block = seatBlock(seat, reportFor(reports, seatId), now);\n if (block === null) return { seatId, ready: true, reason: null, alternatives: [] };\n return { seatId, ready: false, reason: block.reason, alternatives: rankSeatAlternatives(seatId, seats, reports, now) };\n}\n","/**\n * routes/verse/autonomy/format.ts — the small pure functions the cockpit's\n * numerals go through. Kept framework-free so they unit-test without a DOM,\n * and kept in ONE place so \"unknown\" never renders as 0 in one panel and as\n * an em-dash in the next.\n *\n * The rule every function here obeys: absent is not zero. A null spend is\n * \"—\", a null budget is \"not configured\", and a budget of exactly 0 is\n * \"stopped\" — which is the single most misread number in the whole product.\n */\nimport { describeResetAt } from '../../../../core/verse/seat-readiness.js';\nimport { formatDayLabel, formatPercent } from '../../../components/charts/format.js';\nimport { isAbsolutePath, projectName } from '../verse-model.js';\nimport { usedPercentText } from '../percent-text.js';\n\n/** What we render when a value genuinely is not known. */\nexport const UNKNOWN = '—';\n\n/**\n * A share as a whole percentage — \"18%\" — except a non-zero share under one\n * percent, which reads \"<1%\". One precision per panel: \"0%\" for a sliver of\n * real spend or progress would state that nothing happened.\n */\nexport function formatWholePercent(fraction: number | null | undefined): string {\n if (typeof fraction !== 'number' || !Number.isFinite(fraction)) return UNKNOWN;\n if (fraction > 0 && fraction < 0.01) return '<1%';\n return formatPercent(fraction);\n}\n\n/**\n * THE ONE PERCENT RULE — a measured share of a window (or of anything capped\n * at 100), printed the same way on every panel: a whole percent, except that a\n * real reading under 1% is \"<1%\" (never \"0%\", which would read as \"untouched\")\n * and one just short of 100 is \"99%\" (never a rounded \"100%\", which would read\n * as \"spent\"). `used` is 0–100 and is clamped; not a number is \"—\".\n *\n * Every place that prints a used percent goes through this — a bar reading\n * \"99%\" beside a summary reading \"100% of 5-hour window used\" is one seat\n * described two ways. Bar WIDTHS stay numeric; only the words use this.\n */\nexport function percentText(used: number): string {\n return usedPercentText(used);\n}\n\n/** An ISO-8601 instant (a date WITH a time) anywhere inside a sentence. */\nconst ISO_INSTANT_IN_TEXT = /\\b\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,9})?)?(?:Z|[+-]\\d{2}:?\\d{2})?(?![\\w:])/g;\n\n/**\n * What may end a sentence right before its full stop: a letter, a digit, a\n * percent sign, an ellipsis, or a closing quote / bracket / backtick. NOT a\n * dot, a slash or a space — so a path segment (`../x`, `cd ..;`) never looks\n * like doubled punctuation.\n */\nconst SENTENCE_END = String.raw`[\\p{L}\\p{N}%…)\\]\"'’”\\x60]`;\n/** \"failed.; next\" / \"failed..; next\" → \"failed; next\". */\nconst STOP_BEFORE_SEMICOLON = new RegExp(`(${SENTENCE_END})\\\\.{1,2}\\\\s*;`, 'gu');\n/**\n * \"failed.. Next\" / \"exhausted..\" → one stop. The pair must be FOLLOWED by\n * whitespace or the end, so a git range (`a1b2c3d..e4f5a6b`, `HEAD~1..HEAD`)\n * and a traversal (`x/../y`) pass through untouched; a third dot (`...`) is\n * an ellipsis, not a doubled stop.\n */\nconst DOUBLED_STOP = new RegExp(`(${SENTENCE_END})\\\\.\\\\.(?=\\\\s|$)`, 'gu');\n\n/**\n * Server prose made fit to print: every ISO instant becomes the viewer's\n * local time (\"today 11:46 PM\", \"Fri 11:46 PM\", \"Sep 26, 11:46 PM\" — the\n * wording `describeResetAt` gives resets everywhere else), and the \".;\" / \"..\"\n * that a sentence joined onto another sentence leaves behind is collapsed.\n *\n * For text the server wrote for a person (`ApiError.detail`, action notes,\n * audit summaries, fleet notes): it is shown verbatim otherwise. That matters\n * on the trust surfaces this runs on (approval summaries, the audit trail):\n * a `../../.ssh/authorized_keys` rewritten to `././.ssh/…` would hide the very\n * traversal the operator is deciding about, so only sentence punctuation is\n * ever touched.\n */\nexport function tidyProse(text: string, now: number = Date.now()): string {\n return text\n .replace(ISO_INSTANT_IN_TEXT, (iso) => describeResetAt(iso, now) ?? iso)\n .replace(STOP_BEFORE_SEMICOLON, '$1;')\n .replace(DOUBLED_STOP, '$1.');\n}\n\n/**\n * A sentence ready to be embedded in another one: trimmed, with its own\n * closing period dropped so \"… unavailable: quota exhausted.\" does not become\n * \"quota exhausted..\". An ellipsis is kept — it is not a full stop.\n */\nexport function asClause(text: string): string {\n const trimmed = text.trim();\n if (trimmed.endsWith('...') || trimmed.endsWith('…')) return trimmed;\n return trimmed.replace(/\\.+$/, '');\n}\n\n/**\n * What a repo cell shows: the folder's own name for an absolute checkout path\n * (the full path belongs in the cell's `title`), and a `owner/name` slug or a\n * bare name as it came — cutting a slug to its last segment would drop the\n * owner, which is half of what identifies it.\n */\nexport function repoDisplayName(repo: string): string {\n return isAbsolutePath(repo) ? projectName(repo) : repo;\n}\n\nexport function formatUsd(value: number | null | undefined): string {\n if (typeof value !== 'number' || !Number.isFinite(value)) return UNKNOWN;\n return `$${value.toFixed(2)}`;\n}\n\nexport function formatCount(value: number | null | undefined): string {\n if (typeof value !== 'number' || !Number.isFinite(value)) return UNKNOWN;\n return String(value);\n}\n\n/** \"4m 12s\" / \"1h 06m\" / \"12s\" — for durations and countdowns alike. */\nexport function formatDuration(ms: number): string {\n const total = Math.max(0, Math.round(ms / 1000));\n if (total < 60) return `${total}s`;\n const minutes = Math.floor(total / 60);\n const seconds = total % 60;\n if (minutes < 60) return `${minutes}m ${String(seconds).padStart(2, '0')}s`;\n const hours = Math.floor(minutes / 60);\n return `${hours}h ${String(minutes % 60).padStart(2, '0')}m`;\n}\n\n/** Compact age for dense table cells: \"42s\", \"7m\", \"3h\", \"5d\". */\nexport function formatAge(iso: string | null | undefined): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n const seconds = Math.max(0, Math.round((Date.now() - ms) / 1000));\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.round(seconds / 60);\n if (minutes < 60) return `${minutes}m`;\n const hours = Math.round(minutes / 60);\n if (hours < 24) return `${hours}h`;\n return `${Math.round(hours / 24)}d`;\n}\n\nexport function formatRelative(iso: string | null | undefined): string {\n const age = formatAge(iso);\n return age === UNKNOWN ? UNKNOWN : `${age} ago`;\n}\n\n/** Local wall-clock time, for the audit table's second column. */\nexport function formatClock(iso: string | null | undefined): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n return new Date(ms).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });\n}\n\n/** The viewer's local calendar day as `YYYY-MM-DD` — NOT the UTC slice. */\nexport function localDateKey(at: Date = new Date()): string {\n const y = at.getFullYear();\n const m = String(at.getMonth() + 1).padStart(2, '0');\n const d = String(at.getDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\n/**\n * Is `todayDate` still the current ledger day?\n *\n * The daemon stamps it in UTC (`resetDayIfNeeded` in core/daemon/state.ts is\n * `new Date().toISOString().slice(0, 10)`) but the operator reads the screen\n * in local time, and the two disagree for part of every day at any offset but\n * zero. Comparing against the local day alone would announce \"nothing has been\n * recorded today\" every evening west of UTC — a false alarm on the meter whose\n * whole purpose is to be trustworthy.\n *\n * So either spelling counts as current. That is deliberately conservative: it\n * can call a day current for a few hours after the UTC rollover, and it will\n * never call a genuinely current day stale. The case this exists for is a\n * ledger day weeks old, which neither spelling matches.\n */\nexport function isCurrentLedgerDay(todayDate: string, now: Date = new Date()): boolean {\n return todayDate === localDateKey(now) || todayDate === now.toISOString().slice(0, 10);\n}\n\n/**\n * Time-of-day for a row from today; date + time for anything older.\n *\n * `formatClock` alone is only honest inside a single-day window, and the\n * tables that use it are not one: the audit trail defaults to 200 rows and\n * offers 500, which on a real machine spans days. Two rows reading \"03:12:44\"\n * could be last night and last week, and the only date was in a hover title\n * that a touch viewport never fires.\n */\nexport function formatStamp(iso: string | null | undefined, now: Date = new Date()): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n const at = new Date(ms);\n const clock = at.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });\n if (localDateKey(at) === localDateKey(now)) return clock;\n const day = at.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n return `${day} ${clock}`;\n}\n\n/** Human tick interval, e.g. \"every 15m\". */\nexport function formatInterval(intervalMs: number | null | undefined): string {\n if (typeof intervalMs !== 'number' || !Number.isFinite(intervalMs) || intervalMs <= 0) return UNKNOWN;\n // A configured interval is a round number far more often than not; \"every\n // 15m\" reads better than \"every 15m 00s\" and loses nothing when it isn't.\n return `every ${formatDuration(intervalMs).replace(/ 00[ms]$/, '')}`;\n}\n\nexport type BudgetState = 'stopped' | 'unknown' | 'ok' | 'warn' | 'over';\n\nexport interface BudgetMeter {\n state: BudgetState;\n /** 0-100, clamped. Null when there is nothing honest to draw. */\n percent: number | null;\n /** One line: \"$4.50 of $25.00 today · 18%\". */\n label: string;\n /** The plain-language verdict shown next to the meter. */\n note: string;\n}\n\nconst BUDGET_WARN_PERCENT = 70;\n\n/**\n * Today's spend against the configured daily cap, as ONE line.\n *\n * A cap of exactly 0 is the contract's guard rail: it means the loop is\n * stopped, and it must never render as an empty (i.e. \"plenty left\") meter.\n *\n * `todayDate` is the ledger day `spend.todayUsd` was counted for. It is NOT\n * optional decoration: the daemon writes the figure once per day and leaves it\n * there, so a machine that last ticked three weeks ago answers\n * `{todayUsd: 0, todayDate: \"2026-09-01\"}`. Rendering that as \"$0.00 of $50.00\n * today\" is a fabricated statement about today on the one number an operator\n * checks before walking away from an autonomous loop. Pass `null` only when\n * the source genuinely has no date to offer (the legacy daemon observation).\n */\nexport function budgetMeter(\n spentUsd: number | null | undefined,\n capUsd: number | null | undefined,\n todayDate?: string | null,\n now: Date = new Date(),\n): BudgetMeter {\n const spend = typeof spentUsd === 'number' && Number.isFinite(spentUsd) ? Math.max(0, spentUsd) : null;\n const cap = typeof capUsd === 'number' && Number.isFinite(capUsd) ? capUsd : null;\n // A cap of 0 is a statement about configuration, not about the ledger, so\n // it still reads true on a stale day and is checked first.\n const stale = typeof todayDate === 'string' && !isCurrentLedgerDay(todayDate, now);\n\n if (cap === 0) {\n return {\n state: 'stopped',\n percent: 100,\n label: `${formatUsd(spend)} spent today · budget $0.00`,\n note: 'Daily budget is 0 — the loop is stopped, not unlimited.',\n };\n }\n if (stale) {\n return {\n state: 'unknown',\n percent: null,\n label: `${UNKNOWN} spent today · budget ${formatUsd(cap)}`,\n // \"Sep 1\", not \"2026-09-01\": the ledger day is a calendar date, so it is\n // read as one (formatDayLabel parses it in UTC and cannot shift a day).\n note: `Nothing has been recorded today — the last ledger day is ${formatDayLabel(todayDate)}.`,\n };\n }\n if (cap === null || spend === null) {\n return {\n state: 'unknown',\n percent: null,\n label: cap === null ? `${formatUsd(spend)} spent today · budget ${UNKNOWN}` : `${UNKNOWN} spent today · budget ${formatUsd(cap)}`,\n note: cap === null ? 'Daily budget is not configured.' : \"Today's spend is not observable right now.\",\n };\n }\n\n const raw = (spend / cap) * 100;\n const percent = Math.min(100, Math.round(raw));\n const state: BudgetState = raw >= 100 ? 'over' : raw >= BUDGET_WARN_PERCENT ? 'warn' : 'ok';\n return {\n state,\n percent,\n // The text follows the one percent rule (\"<1%\", \"99%\" short of the cap); `percent` (the bar) stays numeric.\n label: `${formatUsd(spend)} of ${formatUsd(cap)} today · ${percentText((spend / cap) * 100)}`,\n note:\n state === 'over'\n ? 'Daily budget reached — the loop idles until the date rolls over.'\n : state === 'warn'\n ? `${formatUsd(cap - spend)} left before the loop idles today.`\n : `${formatUsd(cap - spend)} left today.`,\n };\n}\n\n/** Epoch ms of the next expected tick, or null when it cannot be derived. */\nexport function nextTickAt(lastTickAt: string | null | undefined, intervalMs: number | null | undefined): number | null {\n if (!lastTickAt) return null;\n const last = Date.parse(lastTickAt);\n if (Number.isNaN(last)) return null;\n if (typeof intervalMs !== 'number' || !Number.isFinite(intervalMs) || intervalMs <= 0) return null;\n return last + intervalMs;\n}\n\n/** \"in 4m 12s\" / \"due now\" / \"overdue by 2m 03s\". */\nexport function countdownLabel(nextAt: number | null, now: number): string {\n if (nextAt === null) return UNKNOWN;\n const delta = nextAt - now;\n if (delta > 1000) return `in ${formatDuration(delta)}`;\n if (delta > -1000) return 'due now';\n return `overdue by ${formatDuration(-delta)}`;\n}\n\n/** Last tick outcome, phrased the way a person would say it out loud. */\nexport function describeTickOutcome(reason: string | null | undefined): { label: string; tone: 'success' | 'warning' | 'danger' | 'neutral' | 'unknown' } {\n if (!reason) return { label: 'no tick recorded', tone: 'unknown' };\n switch (reason) {\n case 'ok':\n return { label: 'ok', tone: 'success' };\n case 'kill-switch':\n return { label: 'refused — emergency stop engaged', tone: 'danger' };\n case 'budget-exhausted':\n return { label: 'stopped — daily budget spent', tone: 'warning' };\n case 'no-enrolled-repos':\n return { label: 'nothing to do — no repos enrolled', tone: 'warning' };\n case 'no-backlog':\n return { label: 'nothing to do — backlog empty', tone: 'neutral' };\n case 'dry-run':\n return { label: 'dry run', tone: 'neutral' };\n default:\n return { label: reason, tone: 'neutral' };\n }\n}\n"],"mappings":"uJAkDA,SAAS,EAAU,EAAyD,EAAyC,CACnH,GAAI,CAAC,EAAS,OAAO,KACrB,IAAK,IAAM,KAAU,EAAS,GAAI,EAAO,SAAW,EAAQ,OAAO,EACnE,OAAO,IACT,CAOA,SAAgB,EAAgB,EAAgC,EAAc,KAAK,IAAI,EAAkB,CACvG,GAAI,OAAO,GAAQ,UAAY,EAAI,SAAW,EAAG,OAAO,KACxD,IAAM,EAAK,KAAK,MAAM,CAAG,EACzB,GAAI,CAAC,OAAO,SAAS,CAAE,EAAG,OAAO,KACjC,IAAM,EAAO,IAAI,KAAK,CAAE,EAClB,EAAO,EAAK,mBAAmB,CAAC,EAAG,CAAE,KAAM,UAAW,OAAQ,SAAU,CAAC,EAM/E,OALI,IAAI,KAAK,CAAG,CAAC,CAAC,aAAa,IAAM,EAAK,aAAa,EAAU,SAAS,IAEtE,EAAK,GAAO,EAAK,EAAM,OAClB,GAAG,EAAK,mBAAmB,CAAC,EAAG,CAAE,QAAS,OAAQ,CAAC,EAAE,GAAG,IAE1D,GAAG,EAAK,mBAAmB,CAAC,EAAG,CAAE,MAAO,QAAS,IAAK,SAAU,CAAC,EAAE,IAAI,GAChF,CAGA,SAAS,EAAwB,EAAgC,CAE/D,OADgB,EAAK,UAAU,SAAW,KAAA,EAC1B,kBAAoB,IACtC,CAUA,SAAgB,EACd,EACA,EACA,EAAc,KAAK,IAAI,EACL,CAClB,IAAI,EAA6C,KAC7C,EAAyB,KAC7B,GAAI,IAAW,MACT,EAAO,aAAe,cAAgB,EAAO,aAAe,eAC9D,EAAa,EAAO,WACpB,EAAU,EAAO,cAEd,GAAI,EAAK,SAAW,QAAS,CAClC,IAAM,EAAY,EAAK,UAAU,WAC7B,IAAc,cAAgB,IAAc,eAC9C,EAAa,EACb,EAAU,IAAc,YAAc,EAAK,UAAU,SAAS,UAAY,KAAO,KAErF,CACA,GAAI,IAAe,KAAM,OAAO,KAChC,GAAI,IAAe,aACjB,MAAO,CAAE,aAAY,QAAS,KAAM,OAAQ,GAAG,EAAK,MAAM,gDAAiD,EAE7G,IAAM,EAAO,EAAgB,EAAS,CAAG,EACnC,EAAQ,IAAS,KAAO,EAAwB,CAAI,EAAI,KACxD,EAAO,IAAS,KAA6B,IAAU,KAAuB,GAAhB,MAAM,IAA7C,aAAa,IAC1C,MAAO,CAAE,aAAY,UAAS,OAAQ,GAAG,EAAK,MAAM,kBAAkB,EAAK,EAAG,CAChF,CAEA,SAAS,EAAiB,EAA0B,CAClD,OAAO,EAAK,OAAO,KAAM,GAAU,CAAC,EAAM,iBAAiB,CAC7D,CAGA,SAAS,EAAK,EAAyB,CAErC,OADI,EAAK,SAAW,QAAgB,EAC7B,EAAK,UAAU,YAAc,QAAU,EAAI,CACpD,CAEA,SAAS,EAAe,EAAyB,CAC/C,IAAM,EAAO,EAAK,UAAU,SAAS,YAErC,OAAO,OAAO,GAAS,UAAY,OAAO,SAAS,CAAI,EAAI,EAAO,GACpE,CAGA,SAAS,EAAU,EAAiB,EAAiC,EAAsB,CASzF,MAHA,EALI,CAAC,EAAiB,CAAI,GACtB,EAAK,OAAO,QAAU,eACtB,EAAU,EAAM,EAAQ,CAAG,IAAM,MAGjC,EAAK,SAAW,SAAW,IAAW,MAAQ,EAAO,aAAe,WAAa,EAAO,QAAQ,OAAS,EAI/G,CAGA,SAAgB,EACd,EACA,EACA,EACA,EAAc,KAAK,IAAI,EACb,CACV,IAAM,EAAU,EAAM,KAAM,GAAS,EAAK,KAAO,CAAM,GAAK,KACtD,EAAa,EAChB,KAAK,EAAM,KAAW,CAAE,OAAM,OAAM,EAAE,CAAC,CACvC,QAAQ,CAAE,UAAW,EAAK,KAAO,GAAU,EAAU,EAAM,EAAU,EAAS,EAAK,EAAE,EAAG,CAAG,CAAC,EAa/F,OAZA,EAAW,MAAM,EAAG,IAAM,CACxB,IAAM,EAAS,EAAK,EAAE,IAAI,EAAI,EAAK,EAAE,IAAI,EACzC,GAAI,IAAW,EAAG,OAAO,EACzB,GAAI,IAAY,KAAM,CACpB,IAAM,EAAQ,EAAE,KAAK,SAAW,EAAQ,OAAS,EAAI,EAC/C,EAAQ,EAAE,KAAK,SAAW,EAAQ,OAAS,EAAI,EACrD,GAAI,IAAU,EAAO,OAAO,EAAQ,CACtC,CACA,IAAM,EAAQ,EAAe,EAAE,IAAI,EAAI,EAAe,EAAE,IAAI,EAE5D,OADI,IAAU,EACP,EAAE,MAAQ,EAAE,MADK,CAE1B,CAAC,EACM,EAAW,MAAM,EAAA,CAAwB,CAAC,CAAC,KAAK,CAAE,UAAW,EAAK,EAAE,CAC7E,CCpJA,SAAgB,EAAmB,EAA6C,CAG9E,OAFI,OAAO,GAAa,UAAY,CAAC,OAAO,SAAS,CAAQ,EAAG,IAC5D,EAAW,GAAK,EAAW,IAAa,MACrC,EAAc,CAAQ,CAC/B,CAaA,SAAgB,EAAY,EAAsB,CAChD,OAAO,EAAgB,CAAI,CAC7B,CAGA,IAAM,EAAsB,4FAQtB,EAAe,OAAO,GAAG,4BAEzB,EAA4B,OAAO,IAAI,EAAa,gBAAiB,IAAI,EAOzE,EAAmB,OAAO,IAAI,EAAa,kBAAmB,IAAI,EAexE,SAAgB,EAAU,EAAc,EAAc,KAAK,IAAI,EAAW,CACxE,OAAO,EACJ,QAAQ,EAAsB,GAAQ,EAAgB,EAAK,CAAG,GAAK,CAAG,CAAC,CACvE,QAAQ,EAAuB,KAAK,CAAC,CACrC,QAAQ,EAAc,KAAK,CAChC,CAOA,SAAgB,EAAS,EAAsB,CAC7C,IAAM,EAAU,EAAK,KAAK,EAE1B,OADI,EAAQ,SAAS,KAAK,GAAK,EAAQ,SAAS,GAAG,EAAU,EACtD,EAAQ,QAAQ,OAAQ,EAAE,CACnC,CAQA,SAAgB,EAAgB,EAAsB,CACpD,OAAO,EAAe,CAAI,EAAI,EAAY,CAAI,EAAI,CACpD,CAEA,SAAgB,EAAU,EAA0C,CAElE,OADI,OAAO,GAAU,UAAY,CAAC,OAAO,SAAS,CAAK,EAAG,IACnD,IAAI,EAAM,QAAQ,CAAC,GAC5B,CAEA,SAAgB,EAAY,EAA0C,CAEpE,OADI,OAAO,GAAU,UAAY,CAAC,OAAO,SAAS,CAAK,EAAG,IACnD,OAAO,CAAK,CACrB,CAGA,SAAgB,EAAe,EAAoB,CACjD,IAAM,EAAQ,KAAK,IAAI,EAAG,KAAK,MAAM,EAAK,GAAI,CAAC,EAC/C,GAAI,EAAQ,GAAI,MAAO,GAAG,EAAM,GAChC,IAAM,EAAU,KAAK,MAAM,EAAQ,EAAE,EAC/B,EAAU,EAAQ,GAGxB,OAFI,EAAU,GAAW,GAAG,EAAQ,IAAI,OAAO,CAAO,CAAC,CAAC,SAAS,EAAG,GAAG,EAAE,GAElE,GADO,KAAK,MAAM,EAAU,EACzB,EAAM,IAAI,OAAO,EAAU,EAAE,CAAC,CAAC,SAAS,EAAG,GAAG,EAAE,EAC5D,CAGA,SAAgB,EAAU,EAAwC,CAChE,GAAI,CAAC,EAAK,MAAA,IACV,IAAM,EAAK,KAAK,MAAM,CAAG,EACzB,GAAI,OAAO,MAAM,CAAE,EAAG,MAAA,IACtB,IAAM,EAAU,KAAK,IAAI,EAAG,KAAK,OAAO,KAAK,IAAI,EAAI,GAAM,GAAI,CAAC,EAChE,GAAI,EAAU,GAAI,MAAO,GAAG,EAAQ,GACpC,IAAM,EAAU,KAAK,MAAM,EAAU,EAAE,EACvC,GAAI,EAAU,GAAI,MAAO,GAAG,EAAQ,GACpC,IAAM,EAAQ,KAAK,MAAM,EAAU,EAAE,EAErC,OADI,EAAQ,GAAW,GAAG,EAAM,GACzB,GAAG,KAAK,MAAM,EAAQ,EAAE,EAAE,EACnC,CAEA,SAAgB,EAAe,EAAwC,CACrE,IAAM,EAAM,EAAU,CAAG,EACzB,OAAO,IAAA,IAAA,IAA4B,GAAG,EAAI,KAC5C,CAWA,SAAgB,EAAa,EAAW,IAAI,KAAgB,CAI1D,MAAO,GAHG,EAAG,YAGH,EAAE,GAFF,OAAO,EAAG,SAAS,EAAI,CAAC,CAAC,CAAC,SAAS,EAAG,GAEjC,EAAE,GADP,OAAO,EAAG,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAG,GACvB,GACtB,CAiBA,SAAgB,EAAmB,EAAmB,EAAY,IAAI,KAAiB,CACrF,OAAO,IAAc,EAAa,CAAG,GAAK,IAAc,EAAI,YAAY,CAAC,CAAC,MAAM,EAAG,EAAE,CACvF,CAWA,SAAgB,EAAY,EAAgC,EAAY,IAAI,KAAgB,CAC1F,GAAI,CAAC,EAAK,MAAA,IACV,IAAM,EAAK,KAAK,MAAM,CAAG,EACzB,GAAI,OAAO,MAAM,CAAE,EAAG,MAAA,IACtB,IAAM,EAAK,IAAI,KAAK,CAAE,EAChB,EAAQ,EAAG,mBAAmB,IAAA,GAAW,CAAE,KAAM,UAAW,OAAQ,UAAW,OAAQ,SAAU,CAAC,EAGxG,OAFI,EAAa,CAAE,IAAM,EAAa,CAAG,EAAU,EAE5C,GADK,EAAG,mBAAmB,IAAA,GAAW,CAAE,MAAO,QAAS,IAAK,SAAU,CACpE,EAAI,GAAG,GACnB,CAGA,SAAgB,EAAe,EAA+C,CAI5E,OAHI,OAAO,GAAe,UAAY,CAAC,OAAO,SAAS,CAAU,GAAK,GAAc,EAAG,IAGhF,SAAS,EAAe,CAAU,CAAC,CAAC,QAAQ,WAAY,EAAE,GACnE,CAcA,IAAM,EAAsB,GAgB5B,SAAgB,EACd,EACA,EACA,EACA,EAAY,IAAI,KACH,CACb,IAAM,EAAQ,OAAO,GAAa,UAAY,OAAO,SAAS,CAAQ,EAAI,KAAK,IAAI,EAAG,CAAQ,EAAI,KAC5F,EAAM,OAAO,GAAW,UAAY,OAAO,SAAS,CAAM,EAAI,EAAS,KAGvE,EAAQ,OAAO,GAAc,UAAY,CAAC,EAAmB,EAAW,CAAG,EAEjF,GAAI,IAAQ,EACV,MAAO,CACL,MAAO,UACP,QAAS,IACT,MAAO,GAAG,EAAU,CAAK,EAAE,6BAC3B,KAAM,yDACR,EAEF,GAAI,EACF,MAAO,CACL,MAAO,UACP,QAAS,KACT,MAAO,0BAAmC,EAAU,CAAG,IAGvD,KAAM,4DAA4D,EAAe,CAAS,EAAE,EAC9F,EAEF,GAAI,IAAQ,MAAQ,IAAU,KAC5B,MAAO,CACL,MAAO,UACP,QAAS,KACT,MAAO,IAAQ,KAAO,GAAG,EAAU,CAAK,EAAE,yBAAoC,0BAAmC,EAAU,CAAG,IAC9H,KAAM,IAAQ,KAAO,kCAAoC,4CAC3D,EAGF,IAAM,EAAO,EAAQ,EAAO,IACtB,EAAU,KAAK,IAAI,IAAK,KAAK,MAAM,CAAG,CAAC,EACvC,EAAqB,GAAO,IAAM,OAAS,GAAO,EAAsB,OAAS,KACvF,MAAO,CACL,QACA,UAEA,MAAO,GAAG,EAAU,CAAK,EAAE,MAAM,EAAU,CAAG,EAAE,WAAW,EAAa,EAAQ,EAAO,GAAG,IAC1F,KACE,IAAU,OACN,mEACA,IAAU,OACR,GAAG,EAAU,EAAM,CAAK,EAAE,oCAC1B,GAAG,EAAU,EAAM,CAAK,EAAE,aACpC,CACF,CAGA,SAAgB,EAAW,EAAuC,EAAsD,CACtH,GAAI,CAAC,EAAY,OAAO,KACxB,IAAM,EAAO,KAAK,MAAM,CAAU,EAGlC,OAFI,OAAO,MAAM,CAAI,GACjB,OAAO,GAAe,UAAY,CAAC,OAAO,SAAS,CAAU,GAAK,GAAc,EAAU,KACvF,EAAO,CAChB,CAGA,SAAgB,EAAe,EAAuB,EAAqB,CACzE,GAAI,IAAW,KAAM,MAAA,IACrB,IAAM,EAAQ,EAAS,EAGvB,OAFI,EAAQ,IAAa,MAAM,EAAe,CAAK,IAC/C,EAAQ,KAAc,UACnB,cAAc,EAAe,CAAC,CAAK,GAC5C,CAGA,SAAgB,EAAoB,EAAsH,CACxJ,GAAI,CAAC,EAAQ,MAAO,CAAE,MAAO,mBAAoB,KAAM,SAAU,EACjE,OAAQ,EAAR,CACE,IAAK,KACH,MAAO,CAAE,MAAO,KAAM,KAAM,SAAU,EACxC,IAAK,cACH,MAAO,CAAE,MAAO,mCAAoC,KAAM,QAAS,EACrE,IAAK,mBACH,MAAO,CAAE,MAAO,+BAAgC,KAAM,SAAU,EAClE,IAAK,oBACH,MAAO,CAAE,MAAO,oCAAqC,KAAM,SAAU,EACvE,IAAK,aACH,MAAO,CAAE,MAAO,gCAAiC,KAAM,SAAU,EACnE,IAAK,UACH,MAAO,CAAE,MAAO,UAAW,KAAM,SAAU,EAC7C,QACE,MAAO,CAAE,MAAO,EAAQ,KAAM,SAAU,CAC5C,CACF"}
1
+ {"version":3,"file":"format-CaMmVQCn.js","names":[],"sources":["../../../../../../src/core/verse/seat-readiness.ts","../../../../../../src/web-ui/routes/verse/autonomy/format.ts"],"sourcesContent":["/**\n * core/verse/seat-readiness.ts — \"can this seat run a turn right now, and if\n * not, where else can the operator go?\" (V3.10, unit A2).\n *\n * PURE and BROWSER-SAFE on purpose: the engine's admission gate\n * (`getSeatReadiness` in core/verse/seats.ts) and the Composer's block\n * (web-ui/routes/verse/health/ComposerSeatBlock.tsx) must give the SAME\n * answer, with the SAME ranked alternatives, or the UI would offer a seat the\n * server then refuses. One implementation, imported by both. Type-only\n * imports and `Intl` only — no node: modules.\n *\n * ── WHAT BLOCKS A SEAT ─────────────────────────────────────────────────────\n * Only POSITIVE evidence that a turn would fail at the provider:\n * - `signed-out` — the CLI said so (status command or the live collector).\n * - `exhausted` — every window that carried a reading is spent and no\n * Codex credit balance remains (`seatUsability` in seats.ts;\n * a spent per-model window beside a healthy account-wide\n * one is `tight`, not exhausted).\n * `unknown`, `expiring` and `binary-skew` are WARNINGS, never refusals: an\n * admission gate that refused on \"we could not read it\" would lock Mason out\n * of his own seats every time a probe hiccuped (docs/VERSE-TELEMETRY-V2.md —\n * no signal is not a verdict).\n *\n * ── HOW ALTERNATIVES ARE RANKED ────────────────────────────────────────────\n * (research r2/accounts.md, recommendation 4): other subscription seats with\n * measured headroom first, then subscription seats whose headroom is tight or\n * unread, then local models (free, always last because they are the weakest).\n * Inside a tier the SAME ENGINE leads (a Codex user blocked on one account most\n * likely wants the other Codex account), then the lower binding-window\n * percentage, then discovery order. A seat with no runnable model, a local\n * seat whose runtime is not answering, or a seat that is itself blocked is\n * never offered.\n */\nimport type { SeatConnection, SeatHealthReport, SeatReadiness } from './health-types.js';\nimport type { VerseSeat } from './types.js';\n\n/** The only connections that refuse a turn. Everything else is a warning. */\nexport const SEAT_BLOCKING_CONNECTIONS: ReadonlySet<SeatConnection> = new Set<SeatConnection>(['signed-out', 'exhausted']);\n\n/** At most this many alternatives are offered — a list, not a directory. */\nexport const SEAT_MAX_ALTERNATIVES = 5;\n\nexport interface SeatBlock {\n connection: 'signed-out' | 'exhausted';\n /** One plain sentence naming the seat and the fact (\"Personal Codex is out of usage — resets Fri 2:25 PM.\"). */\n reason: string;\n /** Machine-readable reset instant, when the provider gave one. */\n resetAt: string | null;\n}\n\nfunction reportFor(reports: readonly SeatHealthReport[] | null | undefined, seatId: string): SeatHealthReport | null {\n if (!reports) return null;\n for (const report of reports) if (report.seatId === seatId) return report;\n return null;\n}\n\n/**\n * A reset instant as a short local phrase (\"Fri 2:25 PM\", \"Sep 30, 2:25 PM\"),\n * or null when `iso` is not a valid instant. Only a MACHINE-READABLE instant\n * is ever formatted — Claude's prose reset never reaches this function.\n */\nexport function describeResetAt(iso: string | null | undefined, now: number = Date.now()): string | null {\n if (typeof iso !== 'string' || iso.length === 0) return null;\n const at = Date.parse(iso);\n if (!Number.isFinite(at)) return null;\n const date = new Date(at);\n const time = date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });\n if (new Date(now).toDateString() === date.toDateString()) return `today ${time}`;\n // Within the coming week the weekday is the clearest name for a day.\n if (at > now && at - now < 6 * 24 * 60 * 60 * 1000) {\n return `${date.toLocaleDateString([], { weekday: 'short' })} ${time}`;\n }\n return `${date.toLocaleDateString([], { month: 'short', day: 'numeric' })}, ${time}`;\n}\n\n/** The provider's own reset wording for the seat's binding window (Claude publishes prose only). */\nfunction bindingResetDescription(seat: VerseSeat): string | null {\n const binding = seat.capacity?.binding ?? null;\n return binding?.resetDescription ?? null;\n}\n\n/**\n * Why `seat` cannot take a turn, or null when nothing positively blocks it.\n *\n * The health report (sweep + live telemetry) wins when present; without one\n * the seat's own `capacity.usability` — which is on every native seat the\n * server serves — is the fallback, so the seat picker can mark a blocked seat\n * before the health endpoint has ever been read.\n */\nexport function seatBlock(\n seat: VerseSeat,\n report: SeatHealthReport | null,\n now: number = Date.now(),\n): SeatBlock | null {\n let connection: SeatBlock['connection'] | null = null;\n let resetAt: string | null = null;\n if (report !== null) {\n if (report.connection === 'signed-out' || report.connection === 'exhausted') {\n connection = report.connection;\n resetAt = report.resetAt;\n }\n } else if (seat.engine !== 'local') {\n const usability = seat.capacity?.usability;\n if (usability === 'signed-out' || usability === 'exhausted') {\n connection = usability;\n resetAt = usability === 'exhausted' ? seat.capacity?.binding?.resetsAt ?? null : null;\n }\n }\n if (connection === null) return null;\n if (connection === 'signed-out') {\n return { connection, resetAt: null, reason: `${seat.label} is signed out — reconnect it to use this seat.` };\n }\n const when = describeResetAt(resetAt, now);\n const prose = when === null ? bindingResetDescription(seat) : null;\n const tail = when !== null ? ` — resets ${when}` : prose !== null ? ` — ${prose}` : '';\n return { connection, resetAt, reason: `${seat.label} is out of usage${tail}.` };\n}\n\nfunction hasRunnableModel(seat: VerseSeat): boolean {\n return seat.models.some((model) => !model.unavailableReason);\n}\n\n/** 0 = subscription seat with measured headroom, 1 = tight/unread subscription seat, 2 = local. */\nfunction tier(seat: VerseSeat): number {\n if (seat.engine === 'local') return 2;\n return seat.capacity?.usability === 'ready' ? 0 : 1;\n}\n\nfunction bindingPercent(seat: VerseSeat): number {\n const used = seat.capacity?.binding?.usedPercent;\n // Unread sorts after every reading: no signal is not \"0% used\".\n return typeof used === 'number' && Number.isFinite(used) ? used : 101;\n}\n\n/** Can `seat` be offered as somewhere else to go? */\nfunction offerable(seat: VerseSeat, report: SeatHealthReport | null, now: number): boolean {\n if (!hasRunnableModel(seat)) return false;\n if (seat.health.state === 'unavailable') return false;\n if (seatBlock(seat, report, now) !== null) return false;\n // A local seat is only worth offering while its runtime answers. `unknown`\n // with reasons is the sweep saying Ollama did not respond.\n if (seat.engine === 'local' && report !== null && report.connection === 'unknown' && report.reasons.length > 0) {\n return false;\n }\n return true;\n}\n\n/** Ranked ids of seats that could take the turn instead of `seatId` (see the file header). */\nexport function rankSeatAlternatives(\n seatId: string,\n seats: readonly VerseSeat[],\n reports: readonly SeatHealthReport[] | null | undefined,\n now: number = Date.now(),\n): string[] {\n const blocked = seats.find((seat) => seat.id === seatId) ?? null;\n const candidates = seats\n .map((seat, index) => ({ seat, index }))\n .filter(({ seat }) => seat.id !== seatId && offerable(seat, reportFor(reports, seat.id), now));\n candidates.sort((a, b) => {\n const byTier = tier(a.seat) - tier(b.seat);\n if (byTier !== 0) return byTier;\n if (blocked !== null) {\n const sameA = a.seat.engine === blocked.engine ? 0 : 1;\n const sameB = b.seat.engine === blocked.engine ? 0 : 1;\n if (sameA !== sameB) return sameA - sameB;\n }\n const byUse = bindingPercent(a.seat) - bindingPercent(b.seat);\n if (byUse !== 0) return byUse;\n return a.index - b.index;\n });\n return candidates.slice(0, SEAT_MAX_ALTERNATIVES).map(({ seat }) => seat.id);\n}\n\n/**\n * The admission answer for one seat. A seat that is not in `seats` at all is\n * reported READY with no reason: the readiness layer has no evidence about\n * it, and \"seat not found\" is the engine's own error to raise.\n */\nexport function seatReadiness(\n seatId: string,\n seats: readonly VerseSeat[],\n reports: readonly SeatHealthReport[] | null | undefined,\n now: number = Date.now(),\n): SeatReadiness {\n const seat = seats.find((candidate) => candidate.id === seatId) ?? null;\n if (seat === null) return { seatId, ready: true, reason: null, alternatives: [] };\n const block = seatBlock(seat, reportFor(reports, seatId), now);\n if (block === null) return { seatId, ready: true, reason: null, alternatives: [] };\n return { seatId, ready: false, reason: block.reason, alternatives: rankSeatAlternatives(seatId, seats, reports, now) };\n}\n","/**\n * routes/verse/autonomy/format.ts — the small pure functions the cockpit's\n * numerals go through. Kept framework-free so they unit-test without a DOM,\n * and kept in ONE place so \"unknown\" never renders as 0 in one panel and as\n * an em-dash in the next.\n *\n * The rule every function here obeys: absent is not zero. A null spend is\n * \"—\", a null budget is \"not configured\", and a budget of exactly 0 is\n * \"stopped\" — which is the single most misread number in the whole product.\n */\nimport { describeResetAt } from '../../../../core/verse/seat-readiness.js';\nimport { formatDayLabel, formatPercent } from '../../../components/charts/format.js';\nimport { isAbsolutePath, projectName } from '../verse-model.js';\nimport { usedPercentText } from '../percent-text.js';\n\n/** What we render when a value genuinely is not known. */\nexport const UNKNOWN = '—';\n\n/**\n * A share as a whole percentage — \"18%\" — except a non-zero share under one\n * percent, which reads \"<1%\". One precision per panel: \"0%\" for a sliver of\n * real spend or progress would state that nothing happened.\n */\nexport function formatWholePercent(fraction: number | null | undefined): string {\n if (typeof fraction !== 'number' || !Number.isFinite(fraction)) return UNKNOWN;\n if (fraction > 0 && fraction < 0.01) return '<1%';\n return formatPercent(fraction);\n}\n\n/**\n * THE ONE PERCENT RULE — a measured share of a window (or of anything capped\n * at 100), printed the same way on every panel: a whole percent, except that a\n * real reading under 1% is \"<1%\" (never \"0%\", which would read as \"untouched\")\n * and one just short of 100 is \"99%\" (never a rounded \"100%\", which would read\n * as \"spent\"). `used` is 0–100 and is clamped; not a number is \"—\".\n *\n * Every place that prints a used percent goes through this — a bar reading\n * \"99%\" beside a summary reading \"100% of 5-hour window used\" is one seat\n * described two ways. Bar WIDTHS stay numeric; only the words use this.\n */\nexport function percentText(used: number): string {\n return usedPercentText(used);\n}\n\n/** An ISO-8601 instant (a date WITH a time) anywhere inside a sentence. */\nconst ISO_INSTANT_IN_TEXT = /\\b\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,9})?)?(?:Z|[+-]\\d{2}:?\\d{2})?(?![\\w:])/g;\n\n/**\n * What may end a sentence right before its full stop: a letter, a digit, a\n * percent sign, an ellipsis, or a closing quote / bracket / backtick. NOT a\n * dot, a slash or a space — so a path segment (`../x`, `cd ..;`) never looks\n * like doubled punctuation.\n */\nconst SENTENCE_END = String.raw`[\\p{L}\\p{N}%…)\\]\"'’”\\x60]`;\n/** \"failed.; next\" / \"failed..; next\" → \"failed; next\". */\nconst STOP_BEFORE_SEMICOLON = new RegExp(`(${SENTENCE_END})\\\\.{1,2}\\\\s*;`, 'gu');\n/**\n * \"failed.. Next\" / \"exhausted..\" → one stop. The pair must be FOLLOWED by\n * whitespace or the end, so a git range (`a1b2c3d..e4f5a6b`, `HEAD~1..HEAD`)\n * and a traversal (`x/../y`) pass through untouched; a third dot (`...`) is\n * an ellipsis, not a doubled stop.\n */\nconst DOUBLED_STOP = new RegExp(`(${SENTENCE_END})\\\\.\\\\.(?=\\\\s|$)`, 'gu');\n\n/**\n * Server prose made fit to print: every ISO instant becomes the viewer's\n * local time (\"today 11:46 PM\", \"Fri 11:46 PM\", \"Sep 26, 11:46 PM\" — the\n * wording `describeResetAt` gives resets everywhere else), and the \".;\" / \"..\"\n * that a sentence joined onto another sentence leaves behind is collapsed.\n *\n * For text the server wrote for a person (`ApiError.detail`, action notes,\n * audit summaries, fleet notes): it is shown verbatim otherwise. That matters\n * on the trust surfaces this runs on (approval summaries, the audit trail):\n * a `../../.ssh/authorized_keys` rewritten to `././.ssh/…` would hide the very\n * traversal the operator is deciding about, so only sentence punctuation is\n * ever touched.\n */\nexport function tidyProse(text: string, now: number = Date.now()): string {\n return text\n .replace(ISO_INSTANT_IN_TEXT, (iso) => describeResetAt(iso, now) ?? iso)\n .replace(STOP_BEFORE_SEMICOLON, '$1;')\n .replace(DOUBLED_STOP, '$1.');\n}\n\n/**\n * A sentence ready to be embedded in another one: trimmed, with its own\n * closing period dropped so \"… unavailable: quota exhausted.\" does not become\n * \"quota exhausted..\". An ellipsis is kept — it is not a full stop.\n */\nexport function asClause(text: string): string {\n const trimmed = text.trim();\n if (trimmed.endsWith('...') || trimmed.endsWith('…')) return trimmed;\n return trimmed.replace(/\\.+$/, '');\n}\n\n/**\n * What a repo cell shows: the folder's own name for an absolute checkout path\n * (the full path belongs in the cell's `title`), and a `owner/name` slug or a\n * bare name as it came — cutting a slug to its last segment would drop the\n * owner, which is half of what identifies it.\n */\nexport function repoDisplayName(repo: string): string {\n return isAbsolutePath(repo) ? projectName(repo) : repo;\n}\n\nexport function formatUsd(value: number | null | undefined): string {\n if (typeof value !== 'number' || !Number.isFinite(value)) return UNKNOWN;\n return `$${value.toFixed(2)}`;\n}\n\nexport function formatCount(value: number | null | undefined): string {\n if (typeof value !== 'number' || !Number.isFinite(value)) return UNKNOWN;\n return String(value);\n}\n\n/** \"4m 12s\" / \"1h 06m\" / \"12s\" — for durations and countdowns alike. */\nexport function formatDuration(ms: number): string {\n const total = Math.max(0, Math.round(ms / 1000));\n if (total < 60) return `${total}s`;\n const minutes = Math.floor(total / 60);\n const seconds = total % 60;\n if (minutes < 60) return `${minutes}m ${String(seconds).padStart(2, '0')}s`;\n const hours = Math.floor(minutes / 60);\n return `${hours}h ${String(minutes % 60).padStart(2, '0')}m`;\n}\n\n/** Compact age for dense table cells: \"42s\", \"7m\", \"3h\", \"5d\". */\nexport function formatAge(iso: string | null | undefined): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n const seconds = Math.max(0, Math.round((Date.now() - ms) / 1000));\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.round(seconds / 60);\n if (minutes < 60) return `${minutes}m`;\n const hours = Math.round(minutes / 60);\n if (hours < 24) return `${hours}h`;\n return `${Math.round(hours / 24)}d`;\n}\n\nexport function formatRelative(iso: string | null | undefined): string {\n const age = formatAge(iso);\n return age === UNKNOWN ? UNKNOWN : `${age} ago`;\n}\n\n/** Local wall-clock time, for the audit table's second column. */\nexport function formatClock(iso: string | null | undefined): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n return new Date(ms).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });\n}\n\n/** The viewer's local calendar day as `YYYY-MM-DD` — NOT the UTC slice. */\nexport function localDateKey(at: Date = new Date()): string {\n const y = at.getFullYear();\n const m = String(at.getMonth() + 1).padStart(2, '0');\n const d = String(at.getDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\n/**\n * Is `todayDate` still the current ledger day?\n *\n * The daemon stamps it in UTC (`resetDayIfNeeded` in core/daemon/state.ts is\n * `new Date().toISOString().slice(0, 10)`) but the operator reads the screen\n * in local time, and the two disagree for part of every day at any offset but\n * zero. Comparing against the local day alone would announce \"nothing has been\n * recorded today\" every evening west of UTC — a false alarm on the meter whose\n * whole purpose is to be trustworthy.\n *\n * So either spelling counts as current. That is deliberately conservative: it\n * can call a day current for a few hours after the UTC rollover, and it will\n * never call a genuinely current day stale. The case this exists for is a\n * ledger day weeks old, which neither spelling matches.\n */\nexport function isCurrentLedgerDay(todayDate: string, now: Date = new Date()): boolean {\n return todayDate === localDateKey(now) || todayDate === now.toISOString().slice(0, 10);\n}\n\n/**\n * Time-of-day for a row from today; date + time for anything older.\n *\n * `formatClock` alone is only honest inside a single-day window, and the\n * tables that use it are not one: the audit trail defaults to 200 rows and\n * offers 500, which on a real machine spans days. Two rows reading \"03:12:44\"\n * could be last night and last week, and the only date was in a hover title\n * that a touch viewport never fires.\n */\nexport function formatStamp(iso: string | null | undefined, now: Date = new Date()): string {\n if (!iso) return UNKNOWN;\n const ms = Date.parse(iso);\n if (Number.isNaN(ms)) return UNKNOWN;\n const at = new Date(ms);\n const clock = at.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });\n if (localDateKey(at) === localDateKey(now)) return clock;\n const day = at.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n return `${day} ${clock}`;\n}\n\n/** Human tick interval, e.g. \"every 15m\". */\nexport function formatInterval(intervalMs: number | null | undefined): string {\n if (typeof intervalMs !== 'number' || !Number.isFinite(intervalMs) || intervalMs <= 0) return UNKNOWN;\n // A configured interval is a round number far more often than not; \"every\n // 15m\" reads better than \"every 15m 00s\" and loses nothing when it isn't.\n return `every ${formatDuration(intervalMs).replace(/ 00[ms]$/, '')}`;\n}\n\nexport type BudgetState = 'stopped' | 'unknown' | 'ok' | 'warn' | 'over';\n\nexport interface BudgetMeter {\n state: BudgetState;\n /** 0-100, clamped. Null when there is nothing honest to draw. */\n percent: number | null;\n /** One line: \"$4.50 of $25.00 today · 18%\". */\n label: string;\n /** The plain-language verdict shown next to the meter. */\n note: string;\n}\n\nconst BUDGET_WARN_PERCENT = 70;\n\n/**\n * Today's spend against the configured daily cap, as ONE line.\n *\n * A cap of exactly 0 is the contract's guard rail: it means the loop is\n * stopped, and it must never render as an empty (i.e. \"plenty left\") meter.\n *\n * `todayDate` is the ledger day `spend.todayUsd` was counted for. It is NOT\n * optional decoration: the daemon writes the figure once per day and leaves it\n * there, so a machine that last ticked three weeks ago answers\n * `{todayUsd: 0, todayDate: \"2026-09-01\"}`. Rendering that as \"$0.00 of $50.00\n * today\" is a fabricated statement about today on the one number an operator\n * checks before walking away from an autonomous loop. Pass `null` only when\n * the source genuinely has no date to offer (the legacy daemon observation).\n */\nexport function budgetMeter(\n spentUsd: number | null | undefined,\n capUsd: number | null | undefined,\n todayDate?: string | null,\n now: Date = new Date(),\n): BudgetMeter {\n const spend = typeof spentUsd === 'number' && Number.isFinite(spentUsd) ? Math.max(0, spentUsd) : null;\n const cap = typeof capUsd === 'number' && Number.isFinite(capUsd) ? capUsd : null;\n // A cap of 0 is a statement about configuration, not about the ledger, so\n // it still reads true on a stale day and is checked first.\n const stale = typeof todayDate === 'string' && !isCurrentLedgerDay(todayDate, now);\n\n if (cap === 0) {\n return {\n state: 'stopped',\n percent: 100,\n label: `${formatUsd(spend)} spent today · budget $0.00`,\n note: 'Daily budget is 0 — the loop is stopped, not unlimited.',\n };\n }\n if (stale) {\n return {\n state: 'unknown',\n percent: null,\n label: `${UNKNOWN} spent today · budget ${formatUsd(cap)}`,\n // \"Sep 1\", not \"2026-09-01\": the ledger day is a calendar date, so it is\n // read as one (formatDayLabel parses it in UTC and cannot shift a day).\n note: `Nothing has been recorded today — the last ledger day is ${formatDayLabel(todayDate)}.`,\n };\n }\n if (cap === null || spend === null) {\n return {\n state: 'unknown',\n percent: null,\n label: cap === null ? `${formatUsd(spend)} spent today · budget ${UNKNOWN}` : `${UNKNOWN} spent today · budget ${formatUsd(cap)}`,\n note: cap === null ? 'Daily budget is not configured.' : \"Today's spend is not observable right now.\",\n };\n }\n\n const raw = (spend / cap) * 100;\n const percent = Math.min(100, Math.round(raw));\n const state: BudgetState = raw >= 100 ? 'over' : raw >= BUDGET_WARN_PERCENT ? 'warn' : 'ok';\n return {\n state,\n percent,\n // The text follows the one percent rule (\"<1%\", \"99%\" short of the cap); `percent` (the bar) stays numeric.\n label: `${formatUsd(spend)} of ${formatUsd(cap)} today · ${percentText((spend / cap) * 100)}`,\n note:\n state === 'over'\n ? 'Daily budget reached — the loop idles until the date rolls over.'\n : state === 'warn'\n ? `${formatUsd(cap - spend)} left before the loop idles today.`\n : `${formatUsd(cap - spend)} left today.`,\n };\n}\n\n/** Epoch ms of the next expected tick, or null when it cannot be derived. */\nexport function nextTickAt(lastTickAt: string | null | undefined, intervalMs: number | null | undefined): number | null {\n if (!lastTickAt) return null;\n const last = Date.parse(lastTickAt);\n if (Number.isNaN(last)) return null;\n if (typeof intervalMs !== 'number' || !Number.isFinite(intervalMs) || intervalMs <= 0) return null;\n return last + intervalMs;\n}\n\n/** \"in 4m 12s\" / \"due now\" / \"overdue by 2m 03s\". */\nexport function countdownLabel(nextAt: number | null, now: number): string {\n if (nextAt === null) return UNKNOWN;\n const delta = nextAt - now;\n if (delta > 1000) return `in ${formatDuration(delta)}`;\n if (delta > -1000) return 'due now';\n return `overdue by ${formatDuration(-delta)}`;\n}\n\n/** Last tick outcome, phrased the way a person would say it out loud. */\nexport function describeTickOutcome(reason: string | null | undefined): { label: string; tone: 'success' | 'warning' | 'danger' | 'neutral' | 'unknown' } {\n if (!reason) return { label: 'no tick recorded', tone: 'unknown' };\n switch (reason) {\n case 'ok':\n return { label: 'ok', tone: 'success' };\n case 'kill-switch':\n return { label: 'refused — emergency stop engaged', tone: 'danger' };\n case 'budget-exhausted':\n return { label: 'stopped — daily budget spent', tone: 'warning' };\n case 'no-enrolled-repos':\n return { label: 'nothing to do — no repos enrolled', tone: 'warning' };\n case 'no-backlog':\n return { label: 'nothing to do — backlog empty', tone: 'neutral' };\n case 'dry-run':\n return { label: 'dry run', tone: 'neutral' };\n default:\n return { label: reason, tone: 'neutral' };\n }\n}\n"],"mappings":"uJAkDA,SAAS,EAAU,EAAyD,EAAyC,CACnH,GAAI,CAAC,EAAS,OAAO,KACrB,IAAK,IAAM,KAAU,EAAS,GAAI,EAAO,SAAW,EAAQ,OAAO,EACnE,OAAO,IACT,CAOA,SAAgB,EAAgB,EAAgC,EAAc,KAAK,IAAI,EAAkB,CACvG,GAAI,OAAO,GAAQ,UAAY,EAAI,SAAW,EAAG,OAAO,KACxD,IAAM,EAAK,KAAK,MAAM,CAAG,EACzB,GAAI,CAAC,OAAO,SAAS,CAAE,EAAG,OAAO,KACjC,IAAM,EAAO,IAAI,KAAK,CAAE,EAClB,EAAO,EAAK,mBAAmB,CAAC,EAAG,CAAE,KAAM,UAAW,OAAQ,SAAU,CAAC,EAM/E,OALI,IAAI,KAAK,CAAG,CAAC,CAAC,aAAa,IAAM,EAAK,aAAa,EAAU,SAAS,IAEtE,EAAK,GAAO,EAAK,EAAM,OAClB,GAAG,EAAK,mBAAmB,CAAC,EAAG,CAAE,QAAS,OAAQ,CAAC,EAAE,GAAG,IAE1D,GAAG,EAAK,mBAAmB,CAAC,EAAG,CAAE,MAAO,QAAS,IAAK,SAAU,CAAC,EAAE,IAAI,GAChF,CAGA,SAAS,EAAwB,EAAgC,CAE/D,OADgB,EAAK,UAAU,SAAW,KAAA,EAC1B,kBAAoB,IACtC,CAUA,SAAgB,EACd,EACA,EACA,EAAc,KAAK,IAAI,EACL,CAClB,IAAI,EAA6C,KAC7C,EAAyB,KAC7B,GAAI,IAAW,MACT,EAAO,aAAe,cAAgB,EAAO,aAAe,eAC9D,EAAa,EAAO,WACpB,EAAU,EAAO,cAEd,GAAI,EAAK,SAAW,QAAS,CAClC,IAAM,EAAY,EAAK,UAAU,WAC7B,IAAc,cAAgB,IAAc,eAC9C,EAAa,EACb,EAAU,IAAc,YAAc,EAAK,UAAU,SAAS,UAAY,KAAO,KAErF,CACA,GAAI,IAAe,KAAM,OAAO,KAChC,GAAI,IAAe,aACjB,MAAO,CAAE,aAAY,QAAS,KAAM,OAAQ,GAAG,EAAK,MAAM,gDAAiD,EAE7G,IAAM,EAAO,EAAgB,EAAS,CAAG,EACnC,EAAQ,IAAS,KAAO,EAAwB,CAAI,EAAI,KACxD,EAAO,IAAS,KAA6B,IAAU,KAAuB,GAAhB,MAAM,IAA7C,aAAa,IAC1C,MAAO,CAAE,aAAY,UAAS,OAAQ,GAAG,EAAK,MAAM,kBAAkB,EAAK,EAAG,CAChF,CAEA,SAAS,EAAiB,EAA0B,CAClD,OAAO,EAAK,OAAO,KAAM,GAAU,CAAC,EAAM,iBAAiB,CAC7D,CAGA,SAAS,EAAK,EAAyB,CAErC,OADI,EAAK,SAAW,QAAgB,EAC7B,EAAK,UAAU,YAAc,QAAU,EAAI,CACpD,CAEA,SAAS,EAAe,EAAyB,CAC/C,IAAM,EAAO,EAAK,UAAU,SAAS,YAErC,OAAO,OAAO,GAAS,UAAY,OAAO,SAAS,CAAI,EAAI,EAAO,GACpE,CAGA,SAAS,EAAU,EAAiB,EAAiC,EAAsB,CASzF,MAHA,EALI,CAAC,EAAiB,CAAI,GACtB,EAAK,OAAO,QAAU,eACtB,EAAU,EAAM,EAAQ,CAAG,IAAM,MAGjC,EAAK,SAAW,SAAW,IAAW,MAAQ,EAAO,aAAe,WAAa,EAAO,QAAQ,OAAS,EAI/G,CAGA,SAAgB,EACd,EACA,EACA,EACA,EAAc,KAAK,IAAI,EACb,CACV,IAAM,EAAU,EAAM,KAAM,GAAS,EAAK,KAAO,CAAM,GAAK,KACtD,EAAa,EAChB,KAAK,EAAM,KAAW,CAAE,OAAM,OAAM,EAAE,CAAC,CACvC,QAAQ,CAAE,UAAW,EAAK,KAAO,GAAU,EAAU,EAAM,EAAU,EAAS,EAAK,EAAE,EAAG,CAAG,CAAC,EAa/F,OAZA,EAAW,MAAM,EAAG,IAAM,CACxB,IAAM,EAAS,EAAK,EAAE,IAAI,EAAI,EAAK,EAAE,IAAI,EACzC,GAAI,IAAW,EAAG,OAAO,EACzB,GAAI,IAAY,KAAM,CACpB,IAAM,EAAQ,EAAE,KAAK,SAAW,EAAQ,OAAS,EAAI,EAC/C,EAAQ,EAAE,KAAK,SAAW,EAAQ,OAAS,EAAI,EACrD,GAAI,IAAU,EAAO,OAAO,EAAQ,CACtC,CACA,IAAM,EAAQ,EAAe,EAAE,IAAI,EAAI,EAAe,EAAE,IAAI,EAE5D,OADI,IAAU,EACP,EAAE,MAAQ,EAAE,MADK,CAE1B,CAAC,EACM,EAAW,MAAM,EAAA,CAAwB,CAAC,CAAC,KAAK,CAAE,UAAW,EAAK,EAAE,CAC7E,CCpJA,SAAgB,EAAmB,EAA6C,CAG9E,OAFI,OAAO,GAAa,UAAY,CAAC,OAAO,SAAS,CAAQ,EAAG,IAC5D,EAAW,GAAK,EAAW,IAAa,MACrC,EAAc,CAAQ,CAC/B,CAaA,SAAgB,EAAY,EAAsB,CAChD,OAAO,EAAgB,CAAI,CAC7B,CAGA,IAAM,EAAsB,4FAQtB,EAAe,OAAO,GAAG,4BAEzB,EAA4B,OAAO,IAAI,EAAa,gBAAiB,IAAI,EAOzE,EAAmB,OAAO,IAAI,EAAa,kBAAmB,IAAI,EAexE,SAAgB,EAAU,EAAc,EAAc,KAAK,IAAI,EAAW,CACxE,OAAO,EACJ,QAAQ,EAAsB,GAAQ,EAAgB,EAAK,CAAG,GAAK,CAAG,CAAC,CACvE,QAAQ,EAAuB,KAAK,CAAC,CACrC,QAAQ,EAAc,KAAK,CAChC,CAOA,SAAgB,EAAS,EAAsB,CAC7C,IAAM,EAAU,EAAK,KAAK,EAE1B,OADI,EAAQ,SAAS,KAAK,GAAK,EAAQ,SAAS,GAAG,EAAU,EACtD,EAAQ,QAAQ,OAAQ,EAAE,CACnC,CAQA,SAAgB,EAAgB,EAAsB,CACpD,OAAO,EAAe,CAAI,EAAI,EAAY,CAAI,EAAI,CACpD,CAEA,SAAgB,EAAU,EAA0C,CAElE,OADI,OAAO,GAAU,UAAY,CAAC,OAAO,SAAS,CAAK,EAAG,IACnD,IAAI,EAAM,QAAQ,CAAC,GAC5B,CAEA,SAAgB,EAAY,EAA0C,CAEpE,OADI,OAAO,GAAU,UAAY,CAAC,OAAO,SAAS,CAAK,EAAG,IACnD,OAAO,CAAK,CACrB,CAGA,SAAgB,EAAe,EAAoB,CACjD,IAAM,EAAQ,KAAK,IAAI,EAAG,KAAK,MAAM,EAAK,GAAI,CAAC,EAC/C,GAAI,EAAQ,GAAI,MAAO,GAAG,EAAM,GAChC,IAAM,EAAU,KAAK,MAAM,EAAQ,EAAE,EAC/B,EAAU,EAAQ,GAGxB,OAFI,EAAU,GAAW,GAAG,EAAQ,IAAI,OAAO,CAAO,CAAC,CAAC,SAAS,EAAG,GAAG,EAAE,GAElE,GADO,KAAK,MAAM,EAAU,EACzB,EAAM,IAAI,OAAO,EAAU,EAAE,CAAC,CAAC,SAAS,EAAG,GAAG,EAAE,EAC5D,CAGA,SAAgB,EAAU,EAAwC,CAChE,GAAI,CAAC,EAAK,MAAA,IACV,IAAM,EAAK,KAAK,MAAM,CAAG,EACzB,GAAI,OAAO,MAAM,CAAE,EAAG,MAAA,IACtB,IAAM,EAAU,KAAK,IAAI,EAAG,KAAK,OAAO,KAAK,IAAI,EAAI,GAAM,GAAI,CAAC,EAChE,GAAI,EAAU,GAAI,MAAO,GAAG,EAAQ,GACpC,IAAM,EAAU,KAAK,MAAM,EAAU,EAAE,EACvC,GAAI,EAAU,GAAI,MAAO,GAAG,EAAQ,GACpC,IAAM,EAAQ,KAAK,MAAM,EAAU,EAAE,EAErC,OADI,EAAQ,GAAW,GAAG,EAAM,GACzB,GAAG,KAAK,MAAM,EAAQ,EAAE,EAAE,EACnC,CAEA,SAAgB,EAAe,EAAwC,CACrE,IAAM,EAAM,EAAU,CAAG,EACzB,OAAO,IAAA,IAAA,IAA4B,GAAG,EAAI,KAC5C,CAWA,SAAgB,EAAa,EAAW,IAAI,KAAgB,CAI1D,MAAO,GAHG,EAAG,YAGH,EAAE,GAFF,OAAO,EAAG,SAAS,EAAI,CAAC,CAAC,CAAC,SAAS,EAAG,GAEjC,EAAE,GADP,OAAO,EAAG,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAG,GACvB,GACtB,CAiBA,SAAgB,EAAmB,EAAmB,EAAY,IAAI,KAAiB,CACrF,OAAO,IAAc,EAAa,CAAG,GAAK,IAAc,EAAI,YAAY,CAAC,CAAC,MAAM,EAAG,EAAE,CACvF,CAWA,SAAgB,EAAY,EAAgC,EAAY,IAAI,KAAgB,CAC1F,GAAI,CAAC,EAAK,MAAA,IACV,IAAM,EAAK,KAAK,MAAM,CAAG,EACzB,GAAI,OAAO,MAAM,CAAE,EAAG,MAAA,IACtB,IAAM,EAAK,IAAI,KAAK,CAAE,EAChB,EAAQ,EAAG,mBAAmB,IAAA,GAAW,CAAE,KAAM,UAAW,OAAQ,UAAW,OAAQ,SAAU,CAAC,EAGxG,OAFI,EAAa,CAAE,IAAM,EAAa,CAAG,EAAU,EAE5C,GADK,EAAG,mBAAmB,IAAA,GAAW,CAAE,MAAO,QAAS,IAAK,SAAU,CACpE,EAAI,GAAG,GACnB,CAGA,SAAgB,EAAe,EAA+C,CAI5E,OAHI,OAAO,GAAe,UAAY,CAAC,OAAO,SAAS,CAAU,GAAK,GAAc,EAAG,IAGhF,SAAS,EAAe,CAAU,CAAC,CAAC,QAAQ,WAAY,EAAE,GACnE,CAcA,IAAM,EAAsB,GAgB5B,SAAgB,EACd,EACA,EACA,EACA,EAAY,IAAI,KACH,CACb,IAAM,EAAQ,OAAO,GAAa,UAAY,OAAO,SAAS,CAAQ,EAAI,KAAK,IAAI,EAAG,CAAQ,EAAI,KAC5F,EAAM,OAAO,GAAW,UAAY,OAAO,SAAS,CAAM,EAAI,EAAS,KAGvE,EAAQ,OAAO,GAAc,UAAY,CAAC,EAAmB,EAAW,CAAG,EAEjF,GAAI,IAAQ,EACV,MAAO,CACL,MAAO,UACP,QAAS,IACT,MAAO,GAAG,EAAU,CAAK,EAAE,6BAC3B,KAAM,yDACR,EAEF,GAAI,EACF,MAAO,CACL,MAAO,UACP,QAAS,KACT,MAAO,0BAAmC,EAAU,CAAG,IAGvD,KAAM,4DAA4D,EAAe,CAAS,EAAE,EAC9F,EAEF,GAAI,IAAQ,MAAQ,IAAU,KAC5B,MAAO,CACL,MAAO,UACP,QAAS,KACT,MAAO,IAAQ,KAAO,GAAG,EAAU,CAAK,EAAE,yBAAoC,0BAAmC,EAAU,CAAG,IAC9H,KAAM,IAAQ,KAAO,kCAAoC,4CAC3D,EAGF,IAAM,EAAO,EAAQ,EAAO,IACtB,EAAU,KAAK,IAAI,IAAK,KAAK,MAAM,CAAG,CAAC,EACvC,EAAqB,GAAO,IAAM,OAAS,GAAO,EAAsB,OAAS,KACvF,MAAO,CACL,QACA,UAEA,MAAO,GAAG,EAAU,CAAK,EAAE,MAAM,EAAU,CAAG,EAAE,WAAW,EAAa,EAAQ,EAAO,GAAG,IAC1F,KACE,IAAU,OACN,mEACA,IAAU,OACR,GAAG,EAAU,EAAM,CAAK,EAAE,oCAC1B,GAAG,EAAU,EAAM,CAAK,EAAE,aACpC,CACF,CAGA,SAAgB,EAAW,EAAuC,EAAsD,CACtH,GAAI,CAAC,EAAY,OAAO,KACxB,IAAM,EAAO,KAAK,MAAM,CAAU,EAGlC,OAFI,OAAO,MAAM,CAAI,GACjB,OAAO,GAAe,UAAY,CAAC,OAAO,SAAS,CAAU,GAAK,GAAc,EAAU,KACvF,EAAO,CAChB,CAGA,SAAgB,EAAe,EAAuB,EAAqB,CACzE,GAAI,IAAW,KAAM,MAAA,IACrB,IAAM,EAAQ,EAAS,EAGvB,OAFI,EAAQ,IAAa,MAAM,EAAe,CAAK,IAC/C,EAAQ,KAAc,UACnB,cAAc,EAAe,CAAC,CAAK,GAC5C,CAGA,SAAgB,EAAoB,EAAsH,CACxJ,GAAI,CAAC,EAAQ,MAAO,CAAE,MAAO,mBAAoB,KAAM,SAAU,EACjE,OAAQ,EAAR,CACE,IAAK,KACH,MAAO,CAAE,MAAO,KAAM,KAAM,SAAU,EACxC,IAAK,cACH,MAAO,CAAE,MAAO,mCAAoC,KAAM,QAAS,EACrE,IAAK,mBACH,MAAO,CAAE,MAAO,+BAAgC,KAAM,SAAU,EAClE,IAAK,oBACH,MAAO,CAAE,MAAO,oCAAqC,KAAM,SAAU,EACvE,IAAK,aACH,MAAO,CAAE,MAAO,gCAAiC,KAAM,SAAU,EACnE,IAAK,UACH,MAAO,CAAE,MAAO,UAAW,KAAM,SAAU,EAC7C,QACE,MAAO,CAAE,MAAO,EAAQ,KAAM,SAAU,CAC5C,CACF"}
@@ -0,0 +1,5 @@
1
+ import{D as e,F as t,M as n,O as r,l as i,o as a}from"./global-BkNl4e6E.js";import{r as o}from"./VerseConsoleApp-CTF3Shs0.js";var s=`/api/verse/git`,c=new Set([`VERSE_GIT_NOT_A_REPO`,`VERSE_GIT_REFUSED`]);async function l(e,t){let i=await fetch(e,{method:`GET`,credentials:`same-origin`,headers:{"x-ashlr-read-client":r()},signal:t});if(i.status===401)throw n(),new a(`Read session expired.`,401,e);if(!i.ok){let t=null,n=null;try{let e=await i.json();t=typeof e.error==`string`&&e.error?e.error:null,n=typeof e.code==`string`&&e.code?e.code:null}catch{}throw new a(`GET ${e} failed (HTTP ${i.status}).`,i.status,e,t,n)}return await i.json()}function u(e,t){return l(`${s}/status?root=${encodeURIComponent(e)}`,t)}function d(e,t,n,r){let i=new URLSearchParams({root:e,scope:t});return n!==null&&i.set(`file`,n),l(`${s}/diff?${i.toString()}`,r)}async function f(n,r){let a=e();if(!a)throw new o;let s=await i(n,r,a);return t(),s}function p(e){return f(`${s}/commit`,e)}function m(e){return f(`${s}/push`,{root:e})}function h(e){return f(`${s}/pr`,e)}function g(e){return f(`${s}/pr/merge`,e)}function _(e,t){return f(`${s}/worktree`,{root:e,name:t})}function v(e){return e instanceof a&&e.code!==null&&c.has(e.code)}var y=new Intl.NumberFormat(`en-US`);function b(e){return y.format(Math.max(0,Math.round(e)))}function x(e,t){return`+${b(e)} −${b(t)}`}function S(e,t,n){return`${b(e)} ${e===1?`file`:`files`} changed, ${b(t)} added, ${b(n)} removed`}function C(e){return!!(e.dirty>0||e.ahead>0||e.diffstat.files>0||e.pr&&(e.pr.state===`open`||e.pr.state===`draft`))}function w(e){let t=new Set,n=[];for(let r of e){let e=r.gitRoot??r.root;t.has(e)||(t.add(e),n.push(r))}return n}function T(e){let t=e.branch??`HEAD`,n=e.base??`the default branch`;switch(e.suggested){case`commit`:return{action:`commit`,label:`Commit`,disclosure:`Commit ${b(e.dirty)} changed ${e.dirty===1?`file`:`files`} on ${t}.`};case`push`:return{action:`push`,label:`Push`,disclosure:e.upstream?`Push ${b(e.ahead)} ${e.ahead===1?`commit`:`commits`} on ${t} to ${e.upstream}.`:`Publish ${t} to origin.`};case`create-pr`:return{action:`create-pr`,label:`Create PR`,disclosure:`Open a pull request from ${t} into ${n}${e.upstream===null||e.ahead>0?`, pushing it first`:``}.`};case`merge`:return e.pr?{action:`merge`,label:`Merge`,disclosure:`Squash-merge #${e.pr.number} into ${e.pr.baseRef||n}. Checks passed.`}:null;case`view-pr`:return e.pr?{action:`view-pr`,label:`View PR`,disclosure:`Open #${e.pr.number} on GitHub.`}:null;default:return null}}function E(e){let t=e.branch===null,n=e.branch!==null&&e.base!==null&&e.branch===e.base,r=(e.conflicts??0)>0,i=e.pr!==null&&(e.pr.state===`open`||e.pr.state===`draft`);return[{id:`draft-pr`,label:`Create draft PR…`,disabledReason:t?`HEAD is detached`:n?`You are on ${e.base}`:i?`#${e.pr.number} is already open`:e.prLookup===`unavailable`?"GitHub is not reachable (is `gh` signed in?)":null},{id:`commit`,label:`Commit…`,disabledReason:t?`HEAD is detached`:r?`Resolve the conflicts first`:e.dirty===0?`Nothing to commit`:null},{id:`push`,label:e.upstream?`Push`:`Publish branch`,disabledReason:t?`HEAD is detached`:e.upstream!==null&&e.ahead===0?`Already pushed`:e.headSha===null?`No commits yet`:null},{id:`open-github`,label:`Open on GitHub`,disabledReason:e.pr?null:`No pull request for this branch`},{id:`copy-branch`,label:`Copy branch name`,disabledReason:t?`HEAD is detached`:null},{id:`review`,label:`Review changes`,disabledReason:e.diffstat.files===0&&e.dirty===0?`No changes`:null}]}function D(e,t){let n=e.state===`open`?`Open`:e.state===`draft`?`Draft`:e.state===`merged`?`Merged`:`Closed`,r=null,i=`neutral`;if(e.state===`merged`)i=`merged`;else if(e.state===`closed`)i=`neutral`;else{switch(e.checks){case`passing`:r=t&&t.total>0?`${t.passed}/${t.total} checks passed`:`checks passed`,i=e.mergeable===!1?`danger`:`success`;break;case`failing`:r=t?`${t.failed} of ${t.total} checks failing`:`checks failing`,i=`danger`;break;case`pending`:r=t?`${t.pending} of ${t.total} checks running`:`checks running`,i=`running`;break;case`none`:r=`no checks`;break;default:r=`checks —`}e.mergeable===!1&&(r=r?`${r}, conflicts`:`conflicts`)}let a=[`Pull request #${e.number}`,e.title,n,r].filter(Boolean).join(`, `);return{number:`#${e.number}`,title:e.title,state:n,checks:r,tone:i,label:a}}function O(e){let t=e.headSubject?.trim();if(t)return t;let n=((e.branch??``).split(`/`).pop()??``).replace(/[-_]+/g,` `).trim();return n?n[0].toUpperCase()+n.slice(1):``}function k(e){let t=e.pr;return t?{number:t.number,title:t.title,headSha:t.headSha,headRef:t.headRef||e.branch||``,baseRef:t.baseRef||e.base||``,checks:t.checks,mergeable:t.mergeable,counts:e.prCheckCounts??null}:null}function A(e){let t=e.headSha?e.headSha.slice(0,7):`—`,n=e.counts,r;switch(e.checks){case`passing`:r=n&&n.total>0?`${n.passed}/${n.total} checks passed`:`checks passed`;break;case`failing`:r=n&&n.total>0?`${n.failed} of ${n.total} checks failing`:`checks failing`;break;case`pending`:r=n&&n.total>0?`${n.pending} of ${n.total} checks still running`:`checks still running`;break;case`none`:r=`no checks reported`;break;default:r=`checks not known`}let i=e.mergeable===!0?`no conflicts`:e.mergeable===!1?`has conflicts`:`GitHub has not finished checking for conflicts`,a=`${r}, ${i}.`;return{sha:t,detail:a,text:`Head ${t}, ${a}`,verified:e.checks===`passing`&&e.mergeable===!0&&e.headSha!==null}}function j(e,t){let n=t?.pr??null;return!n||n.number!==e.number||n.state!==`open`?`#${e.number} is no longer an open, ready pull request — close this and check the branch bar.`:n.headSha===e.headSha?n.checks===`passing`?n.mergeable===!0?null:`GitHub no longer reports this head as mergeable — close and reopen to review.`:`Checks on this head are no longer passing — close and reopen once they are.`:`The PR changed since you opened this — new commits landed. Close and reopen to review the new head.`}function M(e){let t=new Map,n=[...e].sort((e,t)=>e.path<t.path?-1:+(e.path>t.path));for(let e of n){let n=e.path.lastIndexOf(`/`),r=n===-1?``:e.path.slice(0,n),i=n===-1?e.path:e.path.slice(n+1),a=t.get(r);a||(a={dir:r,files:[]},t.set(r,a)),a.files.push({...e,name:i})}return[...t.values()]}var N={M:`modified`,A:`added`,D:`deleted`,R:`renamed`,U:`conflicted`};function P(e){return[...e].sort((e,t)=>e.path===t.path?e.line-t.line:e.path<t.path?-1:1).map(e=>{let[t,...n]=e.note.trim().split(`
2
+ `);return[`${`${e.path}:${e.line}${e.side===`old`?` (removed line)`:``}`}: ${t??``}`,...n.map(e=>` ${e}`)].join(`
3
+ `)}).join(`
4
+ `)}function F(e){if(e&&typeof e==`object`){let t=e;if(t.name===`DispatchDisabledError`)return`This server is read-only (started without dispatch), so git actions are off.`;if(t.name===`VerseMutationLockedError`)return`Unlock actions with the mutation token first.`;if(t.status===401)return"The mutation token was rejected. Unlock again with the token `ashlr verse` printed.";if(t.detail)return t.detail;if(t.status===404)return"This server has no git routes yet. Update Ashlr and restart `ashlr verse`.";if(t.status===409)return`Git is busy in this repository. Try again in a moment.`}return`The git action failed. The terminal will show why.`}export{h as C,g as S,p as _,F as a,u as b,b as c,E as d,j as f,T as g,D as h,O as i,M as l,k as m,P as n,S as o,A as p,w as r,x as s,N as t,C as u,_ as v,m as w,v as x,d as y};
5
+ //# sourceMappingURL=git-model-B4S_GUNA.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-model-CivuBY_D.js","names":[],"sources":["../../../../../../src/web-ui/routes/verse/git/git-queries.ts","../../../../../../src/web-ui/routes/verse/git/git-model.ts"],"sourcesContent":["/**\n * routes/verse/git/git-queries.ts — the page's side of `/api/verse/git*`\n * (unit C5; server: core/verse/git-api.ts).\n *\n * Reads are plain fetches, not cache entries: a branch's state is stale the\n * moment a turn commits, and the bar and pane each own their own freshness\n * (poll while visible; re-read after every action).\n *\n * WHY a GET helper of its own instead of data/client.ts `apiGet`: the git\n * routes answer \"not a repository\" (404) and \"not a folder Verse knows\"\n * (409) with a CODE the bar acts on — it drops that root instead of showing\n * an error — and apiGet discards the body of a failed read.\n *\n * Mutations go through the held mutation token exactly like every other\n * Verse write (verse-queries.ts): no token → VerseMutationLockedError, which\n * the caller's token gate turns into the unlock dialog.\n */\nimport { ApiError, apiPost } from '../../../data/client.js';\nimport { getMutationToken, getReadClientProof, reportSessionExpired, touchMutationHold } from '../../../data/auth-store.js';\nimport type {\n VerseGitActionResponse,\n VerseGitCommitRequest,\n VerseGitDiffResponse,\n VerseGitDiffScope,\n VerseGitMergeRequest,\n VerseGitPrRequest,\n VerseGitWorktreeResponse,\n} from '../../../data/api-types.js';\nimport { VerseMutationLockedError } from '../verse-queries.js';\nimport type { GitStatusView } from './git-model.js';\n\nexport const GIT_API = '/api/verse/git';\n\n/** Codes that mean \"this root has no bar\", not \"something broke\". */\nexport const GIT_ROOT_UNAVAILABLE_CODES: ReadonlySet<string> = new Set(['VERSE_GIT_NOT_A_REPO', 'VERSE_GIT_REFUSED']);\n\nasync function gitGet<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 let detail: string | null = null;\n let code: string | null = null;\n try {\n const body = (await res.json()) as { error?: unknown; code?: unknown };\n detail = typeof body.error === 'string' && body.error ? body.error : null;\n code = typeof body.code === 'string' && body.code ? body.code : null;\n } catch {\n /* not JSON */\n }\n throw new ApiError(`GET ${path} failed (HTTP ${res.status}).`, res.status, path, detail, code);\n }\n return (await res.json()) as T;\n}\n\nexport function fetchGitStatus(root: string, signal?: AbortSignal): Promise<GitStatusView> {\n return gitGet<GitStatusView>(`${GIT_API}/status?root=${encodeURIComponent(root)}`, signal);\n}\n\nexport interface GitDiffView extends VerseGitDiffResponse {\n /** Size of the whole patch before the 256 KB cut; null when no file was asked. */\n patchBytes?: number | null;\n}\n\nexport function fetchGitDiff(root: string, scope: VerseGitDiffScope, file: string | null, signal?: AbortSignal): Promise<GitDiffView> {\n const params = new URLSearchParams({ root, scope });\n if (file !== null) params.set('file', file);\n return gitGet<GitDiffView>(`${GIT_API}/diff?${params.toString()}`, signal);\n}\n\nasync function post<T>(path: string, body: unknown): Promise<T> {\n const token = getMutationToken();\n if (!token) throw new VerseMutationLockedError();\n const result = await apiPost<T>(path, body, token);\n touchMutationHold();\n return result;\n}\n\nexport function commitGit(req: VerseGitCommitRequest): Promise<VerseGitActionResponse> {\n return post(`${GIT_API}/commit`, req);\n}\n\nexport function pushGit(root: string): Promise<VerseGitActionResponse> {\n return post(`${GIT_API}/push`, { root });\n}\n\nexport function openGitPr(req: VerseGitPrRequest): Promise<VerseGitActionResponse> {\n return post(`${GIT_API}/pr`, req);\n}\n\nexport function mergeGitPr(req: VerseGitMergeRequest): Promise<VerseGitActionResponse> {\n return post(`${GIT_API}/pr/merge`, req);\n}\n\nexport function createGitWorktree(root: string, name: string): Promise<VerseGitWorktreeResponse> {\n return post(`${GIT_API}/worktree`, { root, name });\n}\n\n/** True for a failed read that means \"no bar for this root\" rather than an error to show. */\nexport function isRootUnavailable(err: unknown): boolean {\n return err instanceof ApiError && err.code !== null && GIT_ROOT_UNAVAILABLE_CODES.has(err.code);\n}\n","/**\n * routes/verse/git/git-model.ts — the branch bar's and Review pane's words\n * and decisions, as pure functions (unit C5; SPEC-310C §2, §3).\n *\n * Kept apart from the components so the copy, the suggested-action labels and\n * the menu's disabled reasons are tested as a table, and so the bar's chunk\n * stays small (no React here, no DOM).\n *\n * Copy rules (DESIGN §13.8): operator language with units; \"—\" for a value\n * that was not measured; a state is always a WORD beside any colour or icon.\n */\nimport type {\n VerseGitDiffFile,\n VerseGitPr,\n VerseGitStatus,\n VerseGitSuggestedAction,\n} from '../../../data/api-types.js';\nimport type { VerseGitCheckCounts, VerseGitPrLookup, VerseGitStatusDetail } from '../../../../core/verse/workbench-types.js';\n\n// ---------------------------------------------------------------------------\n// Wire additions the server sends beyond the frozen VerseGitStatus — contract\n// now (workbench-types.ts §7 VerseGitStatusDetail). Optional HERE, so an\n// older server that omits them still renders; the names alias the contract\n// so the two can never drift.\n// ---------------------------------------------------------------------------\n\nexport type GitPrLookup = VerseGitPrLookup;\nexport type GitCheckCounts = VerseGitCheckCounts;\n\nexport interface GitStatusView extends VerseGitStatus, Partial<Omit<VerseGitStatusDetail, keyof VerseGitStatus>> {}\n\n// ---------------------------------------------------------------------------\n// Numbers\n// ---------------------------------------------------------------------------\n\nconst NUMBER = new Intl.NumberFormat('en-US');\n\n/** 35079 → \"35,079\". */\nexport function formatCount(n: number): string {\n return NUMBER.format(Math.max(0, Math.round(n)));\n}\n\n/** \"+35,079 −1,074\" — a real minus sign, grouped digits. */\nexport function diffstatText(additions: number, deletions: number): string {\n return `+${formatCount(additions)} −${formatCount(deletions)}`;\n}\n\n/** The ± counts as a screen reader should hear them. */\nexport function diffstatLabel(files: number, additions: number, deletions: number): string {\n return `${formatCount(files)} ${files === 1 ? 'file' : 'files'} changed, ${formatCount(additions)} added, ${formatCount(deletions)} removed`;\n}\n\n// ---------------------------------------------------------------------------\n// Rows\n// ---------------------------------------------------------------------------\n\n/** A root earns a bar row when there is something to do or to see. */\nexport function hasBranchActivity(s: GitStatusView): boolean {\n if (s.dirty > 0 || s.ahead > 0 || s.diffstat.files > 0) return true;\n if (s.pr && (s.pr.state === 'open' || s.pr.state === 'draft')) return true;\n return false;\n}\n\n/**\n * One row per repository: two chat roots inside the same repo would show the\n * same branch twice. The first root (the chat's priority order) keeps the row.\n */\nexport function dedupeByRepo<T extends GitStatusView>(statuses: readonly T[]): T[] {\n const seen = new Set<string>();\n const out: T[] = [];\n for (const s of statuses) {\n const key = s.gitRoot ?? s.root;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(s);\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// The primary button\n// ---------------------------------------------------------------------------\n\nexport interface PrimaryAction {\n action: Exclude<VerseGitSuggestedAction, 'none'>;\n label: string;\n /** What the click will do, in one sentence (tooltip + accessible description). */\n disclosure: string;\n}\n\nexport function primaryAction(s: GitStatusView): PrimaryAction | null {\n const branch = s.branch ?? 'HEAD';\n const base = s.base ?? 'the default branch';\n switch (s.suggested) {\n case 'commit':\n return { action: 'commit', label: 'Commit', disclosure: `Commit ${formatCount(s.dirty)} changed ${s.dirty === 1 ? 'file' : 'files'} on ${branch}.` };\n case 'push':\n return {\n action: 'push',\n label: 'Push',\n disclosure: s.upstream\n ? `Push ${formatCount(s.ahead)} ${s.ahead === 1 ? 'commit' : 'commits'} on ${branch} to ${s.upstream}.`\n : `Publish ${branch} to origin.`,\n };\n case 'create-pr':\n return {\n action: 'create-pr',\n label: 'Create PR',\n disclosure: `Open a pull request from ${branch} into ${base}${s.upstream === null || s.ahead > 0 ? ', pushing it first' : ''}.`,\n };\n case 'merge':\n return s.pr\n ? { action: 'merge', label: 'Merge', disclosure: `Squash-merge #${s.pr.number} into ${s.pr.baseRef || base}. Checks passed.` }\n : null;\n case 'view-pr':\n return s.pr ? { action: 'view-pr', label: 'View PR', disclosure: `Open #${s.pr.number} on GitHub.` } : null;\n default:\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// The ▾ menu (every item present; disabled WITH a reason, never hidden)\n// ---------------------------------------------------------------------------\n\nexport type MenuActionId = 'draft-pr' | 'commit' | 'push' | 'open-github' | 'copy-branch' | 'review';\n\nexport interface MenuItem {\n id: MenuActionId;\n label: string;\n disabledReason: string | null;\n}\n\nexport function menuItems(s: GitStatusView): MenuItem[] {\n const detached = s.branch === null;\n const onBase = s.branch !== null && s.base !== null && s.branch === s.base;\n const conflicts = (s.conflicts ?? 0) > 0;\n const openPr = s.pr !== null && (s.pr.state === 'open' || s.pr.state === 'draft');\n const draftReason = detached\n ? 'HEAD is detached'\n : onBase\n ? `You are on ${s.base}`\n : openPr\n ? `#${s.pr!.number} is already open`\n : s.prLookup === 'unavailable'\n ? 'GitHub is not reachable (is `gh` signed in?)'\n : null;\n return [\n { id: 'draft-pr', label: 'Create draft PR…', disabledReason: draftReason },\n {\n id: 'commit',\n label: 'Commit…',\n disabledReason: detached ? 'HEAD is detached' : conflicts ? 'Resolve the conflicts first' : s.dirty === 0 ? 'Nothing to commit' : null,\n },\n {\n id: 'push',\n label: s.upstream ? 'Push' : 'Publish branch',\n disabledReason: detached ? 'HEAD is detached' : s.upstream !== null && s.ahead === 0 ? 'Already pushed' : s.headSha === null ? 'No commits yet' : null,\n },\n { id: 'open-github', label: 'Open on GitHub', disabledReason: s.pr ? null : 'No pull request for this branch' },\n { id: 'copy-branch', label: 'Copy branch name', disabledReason: detached ? 'HEAD is detached' : null },\n { id: 'review', label: 'Review changes', disabledReason: s.diffstat.files === 0 && s.dirty === 0 ? 'No changes' : null },\n ];\n}\n\n// ---------------------------------------------------------------------------\n// PR chip\n// ---------------------------------------------------------------------------\n\nexport type PrTone = 'success' | 'danger' | 'warning' | 'running' | 'neutral' | 'merged';\n\nexport interface PrChipModel {\n /** \"#463\" */\n number: string;\n title: string;\n /** \"Open\", \"Draft\", \"Merged\", \"Closed\". */\n state: string;\n /** \"checks passing\", \"2 of 9 checks failing\", \"checks running\", \"no checks\", null. */\n checks: string | null;\n tone: PrTone;\n /** Everything above in one sentence, for the accessible name. */\n label: string;\n}\n\nexport function prChip(pr: VerseGitPr, counts: GitCheckCounts | null | undefined): PrChipModel {\n const state = pr.state === 'open' ? 'Open' : pr.state === 'draft' ? 'Draft' : pr.state === 'merged' ? 'Merged' : 'Closed';\n let checks: string | null = null;\n let tone: PrTone = 'neutral';\n if (pr.state === 'merged') {\n tone = 'merged';\n } else if (pr.state === 'closed') {\n tone = 'neutral';\n } else {\n switch (pr.checks) {\n case 'passing':\n checks = counts && counts.total > 0 ? `${counts.passed}/${counts.total} checks passed` : 'checks passed';\n tone = pr.mergeable === false ? 'danger' : 'success';\n break;\n case 'failing':\n checks = counts ? `${counts.failed} of ${counts.total} checks failing` : 'checks failing';\n tone = 'danger';\n break;\n case 'pending':\n checks = counts ? `${counts.pending} of ${counts.total} checks running` : 'checks running';\n tone = 'running';\n break;\n case 'none':\n checks = 'no checks';\n break;\n default:\n checks = 'checks —';\n }\n if (pr.mergeable === false) checks = checks ? `${checks}, conflicts` : 'conflicts';\n }\n const label = [`Pull request #${pr.number}`, pr.title, state, checks].filter(Boolean).join(', ');\n return { number: `#${pr.number}`, title: pr.title, state, checks, tone, label };\n}\n\n// ---------------------------------------------------------------------------\n// PR dialog defaults\n// ---------------------------------------------------------------------------\n\n/**\n * A PR title from the branch: the last commit's subject when there is one\n * (it is usually the best one-line summary), else the branch name made\n * readable (`feat/branch-bar` → \"Branch bar\").\n */\nexport function defaultPrTitle(s: GitStatusView): string {\n const subject = s.headSubject?.trim();\n if (subject) return subject;\n const leaf = (s.branch ?? '').split('/').pop() ?? '';\n const words = leaf.replace(/[-_]+/g, ' ').trim();\n return words ? words[0]!.toUpperCase() + words.slice(1) : '';\n}\n\n// ---------------------------------------------------------------------------\n// Merge: the head the operator confirmed\n// ---------------------------------------------------------------------------\n\n/**\n * What the Merge dialog showed when it opened. WHY a snapshot: the bar\n * re-polls git status every 10 s, and the server's `--match-head-commit`\n * guard only protects the SHA the CLIENT sends. If the dialog followed the\n * live poll, a commit pushed while it was open (and whose CI went green\n * before the click) would be merged under a confirmation the operator gave\n * for a different head. So the dialog pins the PR at open, asserts only what\n * that pin verified, and submits the pinned SHA — never the latest poll's.\n */\nexport interface PinnedMerge {\n number: number;\n title: string;\n headSha: string | null;\n headRef: string;\n baseRef: string;\n checks: VerseGitPr['checks'];\n mergeable: boolean | null;\n counts: GitCheckCounts | null;\n}\n\nexport function pinMerge(s: GitStatusView): PinnedMerge | null {\n const pr = s.pr;\n if (!pr) return null;\n return {\n number: pr.number,\n title: pr.title,\n headSha: pr.headSha,\n headRef: pr.headRef || s.branch || '',\n baseRef: pr.baseRef || s.base || '',\n checks: pr.checks,\n mergeable: pr.mergeable,\n counts: s.prCheckCounts ?? null,\n };\n}\n\n/**\n * The \"Head abc1234, 9/9 checks passed, no conflicts.\" fact, built ONLY from\n * what the pinned read verified for that SHA: \"checks passed\" needs\n * `checks === 'passing'`, \"no conflicts\" needs `mergeable === true`. Anything\n * else says what is actually known (running, failing, not reported, GitHub\n * still computing mergeability) instead of the reassuring default.\n */\nexport function mergeHeadFact(pin: PinnedMerge): { sha: string; detail: string; text: string; verified: boolean } {\n const sha = pin.headSha ? pin.headSha.slice(0, 7) : '—';\n const c = pin.counts;\n let checks: string;\n switch (pin.checks) {\n case 'passing':\n checks = c && c.total > 0 ? `${c.passed}/${c.total} checks passed` : 'checks passed';\n break;\n case 'failing':\n checks = c && c.total > 0 ? `${c.failed} of ${c.total} checks failing` : 'checks failing';\n break;\n case 'pending':\n checks = c && c.total > 0 ? `${c.pending} of ${c.total} checks still running` : 'checks still running';\n break;\n case 'none':\n checks = 'no checks reported';\n break;\n default:\n checks = 'checks not known';\n }\n const conflicts = pin.mergeable === true ? 'no conflicts' : pin.mergeable === false ? 'has conflicts' : 'GitHub has not finished checking for conflicts';\n const detail = `${checks}, ${conflicts}.`;\n return { sha, detail, text: `Head ${sha}, ${detail}`, verified: pin.checks === 'passing' && pin.mergeable === true && pin.headSha !== null };\n}\n\n/**\n * Why Merge must stay disabled for this pin against the latest poll, or null\n * when the live PR is still exactly what the dialog showed. A different head\n * (or PR, or a PR that is gone/closed) means the operator has not reviewed\n * what would merge; a regression in checks or mergeability on the SAME head\n * means the server's own suggestion no longer stands.\n */\nexport function mergeDrift(pin: PinnedMerge, live: GitStatusView | null): string | null {\n const pr = live?.pr ?? null;\n // A draft cannot be merged (the server refuses it), so \"open\" is the only live state that keeps the pin valid.\n if (!pr || pr.number !== pin.number || pr.state !== 'open') {\n return `#${pin.number} is no longer an open, ready pull request — close this and check the branch bar.`;\n }\n if (pr.headSha !== pin.headSha) return 'The PR changed since you opened this — new commits landed. Close and reopen to review the new head.';\n if (pr.checks !== 'passing') return 'Checks on this head are no longer passing — close and reopen once they are.';\n if (pr.mergeable !== true) return 'GitHub no longer reports this head as mergeable — close and reopen to review.';\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Review pane: file tree\n// ---------------------------------------------------------------------------\n\nexport interface FileGroup {\n /** \"src/core/verse\", or \"\" for the repository root. */\n dir: string;\n files: Array<VerseGitDiffFile & { name: string }>;\n}\n\n/** Files grouped by directory, in path order — a flat, scannable tree. */\nexport function groupFiles(files: readonly VerseGitDiffFile[]): FileGroup[] {\n const groups = new Map<string, FileGroup>();\n const sorted = [...files].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n for (const f of sorted) {\n const cut = f.path.lastIndexOf('/');\n const dir = cut === -1 ? '' : f.path.slice(0, cut);\n const name = cut === -1 ? f.path : f.path.slice(cut + 1);\n let g = groups.get(dir);\n if (!g) {\n g = { dir, files: [] };\n groups.set(dir, g);\n }\n g.files.push({ ...f, name });\n }\n return [...groups.values()];\n}\n\nexport const FILE_STATUS_WORD: Record<VerseGitDiffFile['status'], string> = {\n M: 'modified',\n A: 'added',\n D: 'deleted',\n R: 'renamed',\n U: 'conflicted',\n};\n\n// ---------------------------------------------------------------------------\n// Review comments → a message draft\n// ---------------------------------------------------------------------------\n\nexport interface ReviewComment {\n id: string;\n path: string;\n /** The file's line number (new side; old side for a deleted line). */\n line: number;\n side: 'new' | 'old';\n note: string;\n}\n\n/**\n * `path:line: note`, one per line (SPEC-310C §3), in file then line order so\n * the agent reads them top to bottom. A multi-line note keeps its line breaks,\n * indented under its anchor so each comment stays one visual block. A comment\n * on a deleted line says so — its number is the OLD file's.\n */\nexport function commentsToMessage(comments: readonly ReviewComment[]): string {\n const sorted = [...comments].sort((a, b) => (a.path === b.path ? a.line - b.line : a.path < b.path ? -1 : 1));\n return sorted\n .map((c) => {\n const [first, ...rest] = c.note.trim().split('\\n');\n const anchor = `${c.path}:${c.line}${c.side === 'old' ? ' (removed line)' : ''}`;\n return [`${anchor}: ${first ?? ''}`, ...rest.map((l) => ` ${l}`)].join('\\n');\n })\n .join('\\n');\n}\n\n// ---------------------------------------------------------------------------\n// Errors → one sentence\n// ---------------------------------------------------------------------------\n\n/** What the page says when a git action fails. The server's sentence when it wrote one. */\nexport function describeGitError(err: unknown): string {\n if (err && typeof err === 'object') {\n const e = err as { name?: string; status?: number; detail?: string | null; code?: string | null; message?: string };\n if (e.name === 'DispatchDisabledError') return 'This server is read-only (started without dispatch), so git actions are off.';\n if (e.name === 'VerseMutationLockedError') return 'Unlock actions with the mutation token first.';\n if (e.status === 401) return 'The mutation token was rejected. Unlock again with the token `ashlr verse` printed.';\n if (e.detail) return e.detail;\n if (e.status === 404) return 'This server has no git routes yet. Update Ashlr and restart `ashlr verse`.';\n if (e.status === 409) return 'Git is busy in this repository. Try again in a moment.';\n }\n return 'The git action failed. The terminal will show why.';\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"git-model-B4S_GUNA.js","names":[],"sources":["../../../../../../src/web-ui/routes/verse/git/git-queries.ts","../../../../../../src/web-ui/routes/verse/git/git-model.ts"],"sourcesContent":["/**\n * routes/verse/git/git-queries.ts — the page's side of `/api/verse/git*`\n * (unit C5; server: core/verse/git-api.ts).\n *\n * Reads are plain fetches, not cache entries: a branch's state is stale the\n * moment a turn commits, and the bar and pane each own their own freshness\n * (poll while visible; re-read after every action).\n *\n * WHY a GET helper of its own instead of data/client.ts `apiGet`: the git\n * routes answer \"not a repository\" (404) and \"not a folder Verse knows\"\n * (409) with a CODE the bar acts on — it drops that root instead of showing\n * an error — and apiGet discards the body of a failed read.\n *\n * Mutations go through the held mutation token exactly like every other\n * Verse write (verse-queries.ts): no token → VerseMutationLockedError, which\n * the caller's token gate turns into the unlock dialog.\n */\nimport { ApiError, apiPost } from '../../../data/client.js';\nimport { getMutationToken, getReadClientProof, reportSessionExpired, touchMutationHold } from '../../../data/auth-store.js';\nimport type {\n VerseGitActionResponse,\n VerseGitCommitRequest,\n VerseGitDiffResponse,\n VerseGitDiffScope,\n VerseGitMergeRequest,\n VerseGitPrRequest,\n VerseGitWorktreeResponse,\n} from '../../../data/api-types.js';\nimport { VerseMutationLockedError } from '../verse-queries.js';\nimport type { GitStatusView } from './git-model.js';\n\nexport const GIT_API = '/api/verse/git';\n\n/** Codes that mean \"this root has no bar\", not \"something broke\". */\nexport const GIT_ROOT_UNAVAILABLE_CODES: ReadonlySet<string> = new Set(['VERSE_GIT_NOT_A_REPO', 'VERSE_GIT_REFUSED']);\n\nasync function gitGet<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 let detail: string | null = null;\n let code: string | null = null;\n try {\n const body = (await res.json()) as { error?: unknown; code?: unknown };\n detail = typeof body.error === 'string' && body.error ? body.error : null;\n code = typeof body.code === 'string' && body.code ? body.code : null;\n } catch {\n /* not JSON */\n }\n throw new ApiError(`GET ${path} failed (HTTP ${res.status}).`, res.status, path, detail, code);\n }\n return (await res.json()) as T;\n}\n\nexport function fetchGitStatus(root: string, signal?: AbortSignal): Promise<GitStatusView> {\n return gitGet<GitStatusView>(`${GIT_API}/status?root=${encodeURIComponent(root)}`, signal);\n}\n\nexport interface GitDiffView extends VerseGitDiffResponse {\n /** Size of the whole patch before the 256 KB cut; null when no file was asked. */\n patchBytes?: number | null;\n}\n\nexport function fetchGitDiff(root: string, scope: VerseGitDiffScope, file: string | null, signal?: AbortSignal): Promise<GitDiffView> {\n const params = new URLSearchParams({ root, scope });\n if (file !== null) params.set('file', file);\n return gitGet<GitDiffView>(`${GIT_API}/diff?${params.toString()}`, signal);\n}\n\nasync function post<T>(path: string, body: unknown): Promise<T> {\n const token = getMutationToken();\n if (!token) throw new VerseMutationLockedError();\n const result = await apiPost<T>(path, body, token);\n touchMutationHold();\n return result;\n}\n\nexport function commitGit(req: VerseGitCommitRequest): Promise<VerseGitActionResponse> {\n return post(`${GIT_API}/commit`, req);\n}\n\nexport function pushGit(root: string): Promise<VerseGitActionResponse> {\n return post(`${GIT_API}/push`, { root });\n}\n\nexport function openGitPr(req: VerseGitPrRequest): Promise<VerseGitActionResponse> {\n return post(`${GIT_API}/pr`, req);\n}\n\nexport function mergeGitPr(req: VerseGitMergeRequest): Promise<VerseGitActionResponse> {\n return post(`${GIT_API}/pr/merge`, req);\n}\n\nexport function createGitWorktree(root: string, name: string): Promise<VerseGitWorktreeResponse> {\n return post(`${GIT_API}/worktree`, { root, name });\n}\n\n/** True for a failed read that means \"no bar for this root\" rather than an error to show. */\nexport function isRootUnavailable(err: unknown): boolean {\n return err instanceof ApiError && err.code !== null && GIT_ROOT_UNAVAILABLE_CODES.has(err.code);\n}\n","/**\n * routes/verse/git/git-model.ts — the branch bar's and Review pane's words\n * and decisions, as pure functions (unit C5; SPEC-310C §2, §3).\n *\n * Kept apart from the components so the copy, the suggested-action labels and\n * the menu's disabled reasons are tested as a table, and so the bar's chunk\n * stays small (no React here, no DOM).\n *\n * Copy rules (DESIGN §13.8): operator language with units; \"—\" for a value\n * that was not measured; a state is always a WORD beside any colour or icon.\n */\nimport type {\n VerseGitDiffFile,\n VerseGitPr,\n VerseGitStatus,\n VerseGitSuggestedAction,\n} from '../../../data/api-types.js';\nimport type { VerseGitCheckCounts, VerseGitPrLookup, VerseGitStatusDetail } from '../../../../core/verse/workbench-types.js';\n\n// ---------------------------------------------------------------------------\n// Wire additions the server sends beyond the frozen VerseGitStatus — contract\n// now (workbench-types.ts §7 VerseGitStatusDetail). Optional HERE, so an\n// older server that omits them still renders; the names alias the contract\n// so the two can never drift.\n// ---------------------------------------------------------------------------\n\nexport type GitPrLookup = VerseGitPrLookup;\nexport type GitCheckCounts = VerseGitCheckCounts;\n\nexport interface GitStatusView extends VerseGitStatus, Partial<Omit<VerseGitStatusDetail, keyof VerseGitStatus>> {}\n\n// ---------------------------------------------------------------------------\n// Numbers\n// ---------------------------------------------------------------------------\n\nconst NUMBER = new Intl.NumberFormat('en-US');\n\n/** 35079 → \"35,079\". */\nexport function formatCount(n: number): string {\n return NUMBER.format(Math.max(0, Math.round(n)));\n}\n\n/** \"+35,079 −1,074\" — a real minus sign, grouped digits. */\nexport function diffstatText(additions: number, deletions: number): string {\n return `+${formatCount(additions)} −${formatCount(deletions)}`;\n}\n\n/** The ± counts as a screen reader should hear them. */\nexport function diffstatLabel(files: number, additions: number, deletions: number): string {\n return `${formatCount(files)} ${files === 1 ? 'file' : 'files'} changed, ${formatCount(additions)} added, ${formatCount(deletions)} removed`;\n}\n\n// ---------------------------------------------------------------------------\n// Rows\n// ---------------------------------------------------------------------------\n\n/** A root earns a bar row when there is something to do or to see. */\nexport function hasBranchActivity(s: GitStatusView): boolean {\n if (s.dirty > 0 || s.ahead > 0 || s.diffstat.files > 0) return true;\n if (s.pr && (s.pr.state === 'open' || s.pr.state === 'draft')) return true;\n return false;\n}\n\n/**\n * One row per repository: two chat roots inside the same repo would show the\n * same branch twice. The first root (the chat's priority order) keeps the row.\n */\nexport function dedupeByRepo<T extends GitStatusView>(statuses: readonly T[]): T[] {\n const seen = new Set<string>();\n const out: T[] = [];\n for (const s of statuses) {\n const key = s.gitRoot ?? s.root;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(s);\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// The primary button\n// ---------------------------------------------------------------------------\n\nexport interface PrimaryAction {\n action: Exclude<VerseGitSuggestedAction, 'none'>;\n label: string;\n /** What the click will do, in one sentence (tooltip + accessible description). */\n disclosure: string;\n}\n\nexport function primaryAction(s: GitStatusView): PrimaryAction | null {\n const branch = s.branch ?? 'HEAD';\n const base = s.base ?? 'the default branch';\n switch (s.suggested) {\n case 'commit':\n return { action: 'commit', label: 'Commit', disclosure: `Commit ${formatCount(s.dirty)} changed ${s.dirty === 1 ? 'file' : 'files'} on ${branch}.` };\n case 'push':\n return {\n action: 'push',\n label: 'Push',\n disclosure: s.upstream\n ? `Push ${formatCount(s.ahead)} ${s.ahead === 1 ? 'commit' : 'commits'} on ${branch} to ${s.upstream}.`\n : `Publish ${branch} to origin.`,\n };\n case 'create-pr':\n return {\n action: 'create-pr',\n label: 'Create PR',\n disclosure: `Open a pull request from ${branch} into ${base}${s.upstream === null || s.ahead > 0 ? ', pushing it first' : ''}.`,\n };\n case 'merge':\n return s.pr\n ? { action: 'merge', label: 'Merge', disclosure: `Squash-merge #${s.pr.number} into ${s.pr.baseRef || base}. Checks passed.` }\n : null;\n case 'view-pr':\n return s.pr ? { action: 'view-pr', label: 'View PR', disclosure: `Open #${s.pr.number} on GitHub.` } : null;\n default:\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// The ▾ menu (every item present; disabled WITH a reason, never hidden)\n// ---------------------------------------------------------------------------\n\nexport type MenuActionId = 'draft-pr' | 'commit' | 'push' | 'open-github' | 'copy-branch' | 'review';\n\nexport interface MenuItem {\n id: MenuActionId;\n label: string;\n disabledReason: string | null;\n}\n\nexport function menuItems(s: GitStatusView): MenuItem[] {\n const detached = s.branch === null;\n const onBase = s.branch !== null && s.base !== null && s.branch === s.base;\n const conflicts = (s.conflicts ?? 0) > 0;\n const openPr = s.pr !== null && (s.pr.state === 'open' || s.pr.state === 'draft');\n const draftReason = detached\n ? 'HEAD is detached'\n : onBase\n ? `You are on ${s.base}`\n : openPr\n ? `#${s.pr!.number} is already open`\n : s.prLookup === 'unavailable'\n ? 'GitHub is not reachable (is `gh` signed in?)'\n : null;\n return [\n { id: 'draft-pr', label: 'Create draft PR…', disabledReason: draftReason },\n {\n id: 'commit',\n label: 'Commit…',\n disabledReason: detached ? 'HEAD is detached' : conflicts ? 'Resolve the conflicts first' : s.dirty === 0 ? 'Nothing to commit' : null,\n },\n {\n id: 'push',\n label: s.upstream ? 'Push' : 'Publish branch',\n disabledReason: detached ? 'HEAD is detached' : s.upstream !== null && s.ahead === 0 ? 'Already pushed' : s.headSha === null ? 'No commits yet' : null,\n },\n { id: 'open-github', label: 'Open on GitHub', disabledReason: s.pr ? null : 'No pull request for this branch' },\n { id: 'copy-branch', label: 'Copy branch name', disabledReason: detached ? 'HEAD is detached' : null },\n { id: 'review', label: 'Review changes', disabledReason: s.diffstat.files === 0 && s.dirty === 0 ? 'No changes' : null },\n ];\n}\n\n// ---------------------------------------------------------------------------\n// PR chip\n// ---------------------------------------------------------------------------\n\nexport type PrTone = 'success' | 'danger' | 'warning' | 'running' | 'neutral' | 'merged';\n\nexport interface PrChipModel {\n /** \"#463\" */\n number: string;\n title: string;\n /** \"Open\", \"Draft\", \"Merged\", \"Closed\". */\n state: string;\n /** \"checks passing\", \"2 of 9 checks failing\", \"checks running\", \"no checks\", null. */\n checks: string | null;\n tone: PrTone;\n /** Everything above in one sentence, for the accessible name. */\n label: string;\n}\n\nexport function prChip(pr: VerseGitPr, counts: GitCheckCounts | null | undefined): PrChipModel {\n const state = pr.state === 'open' ? 'Open' : pr.state === 'draft' ? 'Draft' : pr.state === 'merged' ? 'Merged' : 'Closed';\n let checks: string | null = null;\n let tone: PrTone = 'neutral';\n if (pr.state === 'merged') {\n tone = 'merged';\n } else if (pr.state === 'closed') {\n tone = 'neutral';\n } else {\n switch (pr.checks) {\n case 'passing':\n checks = counts && counts.total > 0 ? `${counts.passed}/${counts.total} checks passed` : 'checks passed';\n tone = pr.mergeable === false ? 'danger' : 'success';\n break;\n case 'failing':\n checks = counts ? `${counts.failed} of ${counts.total} checks failing` : 'checks failing';\n tone = 'danger';\n break;\n case 'pending':\n checks = counts ? `${counts.pending} of ${counts.total} checks running` : 'checks running';\n tone = 'running';\n break;\n case 'none':\n checks = 'no checks';\n break;\n default:\n checks = 'checks —';\n }\n if (pr.mergeable === false) checks = checks ? `${checks}, conflicts` : 'conflicts';\n }\n const label = [`Pull request #${pr.number}`, pr.title, state, checks].filter(Boolean).join(', ');\n return { number: `#${pr.number}`, title: pr.title, state, checks, tone, label };\n}\n\n// ---------------------------------------------------------------------------\n// PR dialog defaults\n// ---------------------------------------------------------------------------\n\n/**\n * A PR title from the branch: the last commit's subject when there is one\n * (it is usually the best one-line summary), else the branch name made\n * readable (`feat/branch-bar` → \"Branch bar\").\n */\nexport function defaultPrTitle(s: GitStatusView): string {\n const subject = s.headSubject?.trim();\n if (subject) return subject;\n const leaf = (s.branch ?? '').split('/').pop() ?? '';\n const words = leaf.replace(/[-_]+/g, ' ').trim();\n return words ? words[0]!.toUpperCase() + words.slice(1) : '';\n}\n\n// ---------------------------------------------------------------------------\n// Merge: the head the operator confirmed\n// ---------------------------------------------------------------------------\n\n/**\n * What the Merge dialog showed when it opened. WHY a snapshot: the bar\n * re-polls git status every 10 s, and the server's `--match-head-commit`\n * guard only protects the SHA the CLIENT sends. If the dialog followed the\n * live poll, a commit pushed while it was open (and whose CI went green\n * before the click) would be merged under a confirmation the operator gave\n * for a different head. So the dialog pins the PR at open, asserts only what\n * that pin verified, and submits the pinned SHA — never the latest poll's.\n */\nexport interface PinnedMerge {\n number: number;\n title: string;\n headSha: string | null;\n headRef: string;\n baseRef: string;\n checks: VerseGitPr['checks'];\n mergeable: boolean | null;\n counts: GitCheckCounts | null;\n}\n\nexport function pinMerge(s: GitStatusView): PinnedMerge | null {\n const pr = s.pr;\n if (!pr) return null;\n return {\n number: pr.number,\n title: pr.title,\n headSha: pr.headSha,\n headRef: pr.headRef || s.branch || '',\n baseRef: pr.baseRef || s.base || '',\n checks: pr.checks,\n mergeable: pr.mergeable,\n counts: s.prCheckCounts ?? null,\n };\n}\n\n/**\n * The \"Head abc1234, 9/9 checks passed, no conflicts.\" fact, built ONLY from\n * what the pinned read verified for that SHA: \"checks passed\" needs\n * `checks === 'passing'`, \"no conflicts\" needs `mergeable === true`. Anything\n * else says what is actually known (running, failing, not reported, GitHub\n * still computing mergeability) instead of the reassuring default.\n */\nexport function mergeHeadFact(pin: PinnedMerge): { sha: string; detail: string; text: string; verified: boolean } {\n const sha = pin.headSha ? pin.headSha.slice(0, 7) : '—';\n const c = pin.counts;\n let checks: string;\n switch (pin.checks) {\n case 'passing':\n checks = c && c.total > 0 ? `${c.passed}/${c.total} checks passed` : 'checks passed';\n break;\n case 'failing':\n checks = c && c.total > 0 ? `${c.failed} of ${c.total} checks failing` : 'checks failing';\n break;\n case 'pending':\n checks = c && c.total > 0 ? `${c.pending} of ${c.total} checks still running` : 'checks still running';\n break;\n case 'none':\n checks = 'no checks reported';\n break;\n default:\n checks = 'checks not known';\n }\n const conflicts = pin.mergeable === true ? 'no conflicts' : pin.mergeable === false ? 'has conflicts' : 'GitHub has not finished checking for conflicts';\n const detail = `${checks}, ${conflicts}.`;\n return { sha, detail, text: `Head ${sha}, ${detail}`, verified: pin.checks === 'passing' && pin.mergeable === true && pin.headSha !== null };\n}\n\n/**\n * Why Merge must stay disabled for this pin against the latest poll, or null\n * when the live PR is still exactly what the dialog showed. A different head\n * (or PR, or a PR that is gone/closed) means the operator has not reviewed\n * what would merge; a regression in checks or mergeability on the SAME head\n * means the server's own suggestion no longer stands.\n */\nexport function mergeDrift(pin: PinnedMerge, live: GitStatusView | null): string | null {\n const pr = live?.pr ?? null;\n // A draft cannot be merged (the server refuses it), so \"open\" is the only live state that keeps the pin valid.\n if (!pr || pr.number !== pin.number || pr.state !== 'open') {\n return `#${pin.number} is no longer an open, ready pull request — close this and check the branch bar.`;\n }\n if (pr.headSha !== pin.headSha) return 'The PR changed since you opened this — new commits landed. Close and reopen to review the new head.';\n if (pr.checks !== 'passing') return 'Checks on this head are no longer passing — close and reopen once they are.';\n if (pr.mergeable !== true) return 'GitHub no longer reports this head as mergeable — close and reopen to review.';\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Review pane: file tree\n// ---------------------------------------------------------------------------\n\nexport interface FileGroup {\n /** \"src/core/verse\", or \"\" for the repository root. */\n dir: string;\n files: Array<VerseGitDiffFile & { name: string }>;\n}\n\n/** Files grouped by directory, in path order — a flat, scannable tree. */\nexport function groupFiles(files: readonly VerseGitDiffFile[]): FileGroup[] {\n const groups = new Map<string, FileGroup>();\n const sorted = [...files].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n for (const f of sorted) {\n const cut = f.path.lastIndexOf('/');\n const dir = cut === -1 ? '' : f.path.slice(0, cut);\n const name = cut === -1 ? f.path : f.path.slice(cut + 1);\n let g = groups.get(dir);\n if (!g) {\n g = { dir, files: [] };\n groups.set(dir, g);\n }\n g.files.push({ ...f, name });\n }\n return [...groups.values()];\n}\n\nexport const FILE_STATUS_WORD: Record<VerseGitDiffFile['status'], string> = {\n M: 'modified',\n A: 'added',\n D: 'deleted',\n R: 'renamed',\n U: 'conflicted',\n};\n\n// ---------------------------------------------------------------------------\n// Review comments → a message draft\n// ---------------------------------------------------------------------------\n\nexport interface ReviewComment {\n id: string;\n path: string;\n /** The file's line number (new side; old side for a deleted line). */\n line: number;\n side: 'new' | 'old';\n note: string;\n}\n\n/**\n * `path:line: note`, one per line (SPEC-310C §3), in file then line order so\n * the agent reads them top to bottom. A multi-line note keeps its line breaks,\n * indented under its anchor so each comment stays one visual block. A comment\n * on a deleted line says so — its number is the OLD file's.\n */\nexport function commentsToMessage(comments: readonly ReviewComment[]): string {\n const sorted = [...comments].sort((a, b) => (a.path === b.path ? a.line - b.line : a.path < b.path ? -1 : 1));\n return sorted\n .map((c) => {\n const [first, ...rest] = c.note.trim().split('\\n');\n const anchor = `${c.path}:${c.line}${c.side === 'old' ? ' (removed line)' : ''}`;\n return [`${anchor}: ${first ?? ''}`, ...rest.map((l) => ` ${l}`)].join('\\n');\n })\n .join('\\n');\n}\n\n// ---------------------------------------------------------------------------\n// Errors → one sentence\n// ---------------------------------------------------------------------------\n\n/** What the page says when a git action fails. The server's sentence when it wrote one. */\nexport function describeGitError(err: unknown): string {\n if (err && typeof err === 'object') {\n const e = err as { name?: string; status?: number; detail?: string | null; code?: string | null; message?: string };\n if (e.name === 'DispatchDisabledError') return 'This server is read-only (started without dispatch), so git actions are off.';\n if (e.name === 'VerseMutationLockedError') return 'Unlock actions with the mutation token first.';\n if (e.status === 401) return 'The mutation token was rejected. Unlock again with the token `ashlr verse` printed.';\n if (e.detail) return e.detail;\n if (e.status === 404) return 'This server has no git routes yet. Update Ashlr and restart `ashlr verse`.';\n if (e.status === 409) return 'Git is busy in this repository. Try again in a moment.';\n }\n return 'The git action failed. The terminal will show why.';\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{a as e,n as t,r as n,s as r}from"./index-DbDw9UAs.js";var i=r(e(),1),a={data:void 0,error:void 0,status:`idle`,updatedAt:null},o=new Map,s=0,c=[],l=0,u=0;function ee(){--s;let e=c.shift();e&&e()}function te(e){let t=()=>{let t=u;s+=1,s>l&&(l=s);let n=()=>{t===u&&ee()},r;try{r=e()}catch(e){return n(),Promise.reject(e instanceof Error?e:Error(String(e)))}return r.then(e=>(n(),e),e=>{throw n(),e})};return s<4?t():new Promise((e,n)=>{c.push(()=>{t().then(e,n)})})}function ne(){u+=1,s=0,l=0;for(let e of c.splice(0))e()}function re(){return{active:s,queued:c.length,peak:l}}function d(e){let t=o.get(e);return t||(t={snapshot:a,inFlight:null,fetcher:null,subscribers:new Set,issued:0,issuedAt:0,applied:0},o.set(e,t)),t}function f(e,t){e.snapshot={...e.snapshot,...t}}function p(e){let t=o.get(e);if(t)for(let e of t.subscribers)e()}function m(e,t){if(Object.is(e,t))return!0;if(typeof e!=typeof t||e===null||t===null||typeof e!=`object`||Array.isArray(e)!==Array.isArray(t))return!1;if(Array.isArray(e)&&Array.isArray(t))return e.length===t.length&&e.every((e,n)=>m(e,t[n]));let n=e,r=t,i=Object.keys(n);return i.length===Object.keys(r).length&&i.every(e=>Object.prototype.hasOwnProperty.call(r,e)&&m(n[e],r[e]))}function ie(e,t){let n=d(e);return n.subscribers.add(t),()=>{n.subscribers.delete(t)}}function ae(e){return d(e).snapshot}function oe(e,t){return e.snapshot.updatedAt===null||e.snapshot.status===`error`?!1:Date.now()-e.snapshot.updatedAt<t}function se(e,t){return e.inFlight===null?!1:t.joinWindowMs===void 0||Date.now()-e.issuedAt<t.joinWindowMs}function ce(e,t,n){let r=d(e);if(r.fetcher=t,!n.force){if(se(r,n))return r.inFlight;if(n.maxAgeMs!==void 0&&oe(r,n.maxAgeMs))return Promise.resolve()}let i=r.issued+=1;r.issuedAt=Date.now(),f(r,{status:r.snapshot.data===void 0?`loading`:`refreshing`}),p(e);let a=te(t).then(e=>{if(i<r.applied)return;r.applied=i;let t=r.snapshot.data!==void 0&&m(r.snapshot.data,e);f(r,{data:t?r.snapshot.data:e,error:void 0,status:`success`,updatedAt:Date.now()})}).catch(e=>{i<r.applied||(r.applied=i,f(r,{error:e instanceof Error?e:Error(String(e)),status:`error`}))}).finally(()=>{r.issued===i&&(r.inFlight=null),p(e)});return r.inFlight=a,a}function le(e,t){return ce(e,t,{})}function ue(e,t,n){return ce(e,t,{maxAgeMs:n})}function h(e,t,n=!1){return ce(e,t,n?{force:!0}:{joinWindowMs:250})}function g(e){let t=o.get(e);t&&t.fetcher&&le(e,t.fetcher)}function _(e){for(let t of o.keys())t.startsWith(e)&&g(t)}function de(e){for(let[t,n]of o)n.subscribers.size>0&&t.startsWith(e)&&g(t)}function fe(e,t){t.snapshot=a,t.fetcher=null,t.inFlight=null,t.issued+=1,t.applied=t.issued,p(e)}function pe(){ne();for(let[e,t]of[...o]){if(t.subscribers.size===0){o.delete(e);continue}fe(e,t)}}var v=`ashlr.verse.drafts.v1`,y=`ashlr.verse.sent.v1`,me=40,b=65536;function x(e,t){let n={};try{let r=localStorage.getItem(e),i=r?JSON.parse(r):null;if(i&&typeof i==`object`&&!Array.isArray(i))for(let[e,r]of Object.entries(i)){let i=t(r);i!==null&&(n[e]=i)}}catch{}return n}function S(e,t){let n=Object.keys(t);for(let e of n.slice(0,Math.max(0,n.length-me)))delete t[e];try{return localStorage.setItem(e,JSON.stringify(t)),!0}catch{return!1}}var he=e=>typeof e==`string`&&e.length>0?e.slice(0,b):null,ge=e=>{if(!Array.isArray(e))return null;let t=e.filter(e=>typeof e==`string`&&e.length>0).slice(-25).map(e=>e.slice(0,b));return t.length>0?t:null},C=new Map;function _e(e){if(!e)return``;let t=C.get(e);return t===void 0?x(v,he)[e]??``:t}function ve(e,t){if(!e)return;let n=t.trim().length>0?t.slice(0,b):``,r=x(v,he);if(delete r[e],n&&(r[e]=n),C.delete(e),!S(v,r)){C.set(e,n);for(let e of[...C.keys()].slice(0,Math.max(0,C.size-me)))C.delete(e)}}function ye(e){return e?x(y,ge)[e]??[]:[]}function be(e,t){if(!e||t.trim().length===0)return;let n=x(y,ge),r=n[e]??[];delete n[e],n[e]=(r[r.length-1]===t?r:[...r,t.slice(0,b)]).slice(-25),S(y,n)}function xe(e){if(e){C.delete(e);for(let[t,n]of[[v,he],[y,ge]]){let r=x(t,n);e in r&&(delete r[e],S(t,r))}}}function Se(){C.clear();for(let e of[v,y])try{localStorage.removeItem(e)}catch{}}var Ce=`ashlr.readClientProof.v1`,we=/^[a-f0-9]{64}$/,Te=12e5;function Ee(){let e=new Uint8Array(32);return crypto.getRandomValues(e),Array.from(e,e=>e.toString(16).padStart(2,`0`)).join(``)}function De(){try{let e=sessionStorage.getItem(Ce);if(e&&we.test(e))return e}catch{}let e=Ee();try{sessionStorage.setItem(Ce,e)}catch{}return e}var Oe=De(),w=null,T=null,ke=!1,E=null,D=null,O=null,k={phase:`checking`,checked:!1,mutationTokenHeldUntil:null},A=new Set;function Ae(){for(let e of A)e()}function j(e){k={...k,...e},Ae()}function M(e){return A.add(e),()=>A.delete(e)}function N(){return k}function P(){return Oe}async function F(e){let t=e.trim();if(!/^[a-f0-9]{64}$/.test(t))throw Error(`That does not look like a read token — expected 64 hex characters.`);let n=await fetch(`/api/session`,{method:`POST`,credentials:`same-origin`,headers:{"x-ashlr-token":t,"x-ashlr-read-client":Oe}});if(n.status===204){E=t,j({phase:`authenticated`,checked:!0});return}throw n.status===401?Error(`Server rejected that token. Double-check the read token printed by this server at startup.`):Error(`Unexpected response establishing session (HTTP ${n.status}).`)}async function je(){try{await fetch(`/api/session`,{method:`DELETE`,credentials:`same-origin`,headers:{"x-ashlr-read-client":Oe}})}catch{}E=null,O=null,I()}function I(){B(),pe(),Se(),j({phase:`unauthenticated`,checked:!0})}function L(){if(k.phase===`authenticated`){if(E){Me();return}I()}}function Me(){if(D)return D;let e=E;return e?(D=(async()=>{try{return await F(e),de(``),!0}catch{return E=null,O=null,I(),!1}finally{D=null}})(),D):Promise.resolve(!1)}function Ne(e){j({phase:e?`authenticated`:`unauthenticated`,checked:!0})}function Pe(){let e=window.__ASHLR_TOKENS__;if(e&&typeof e==`object`){try{delete window.__ASHLR_TOKENS__}catch{window.__ASHLR_TOKENS__=void 0}typeof e.readToken==`string`&&(O={readToken:e.readToken,...typeof e.token==`string`?{token:e.token}:{}})}return O?{...O}:null}async function Fe(){let e=Pe();if(!e)return!1;try{await F(e.readToken)}catch{return O=null,!1}return e.token&&/^[a-f0-9]{64}$/.test(e.token.trim())&&R(e.token.trim(),{host:!0}),!0}function Ie(){return w}function Le(){return w!==null}function Re(){T&&clearTimeout(T),T=setTimeout(()=>{ke&&w!==null?z():B()},Te)}function R(e,t={}){w=e,ke=t.host===!0,j({mutationTokenHeldUntil:Date.now()+Te}),Re()}function z(){w!==null&&(j({mutationTokenHeldUntil:Date.now()+Te}),Re())}function B(){w=null,ke=!1,T&&=(clearTimeout(T),null),j({mutationTokenHeldUntil:null})}var ze=[`runs`,`swarms`,`inbox`,`daemon`,`daemon-observation`,`fleet-activity-ping`,`fleet-activity-observation`,`snapshot`,`session-expired`],Be={runs:[`runs`],swarms:[`swarms`],inbox:[`inbox`],daemon:[`daemon`,`control-snapshot`,`verse-control`,`verse-usage-control`,`verse-caps`],"daemon-observation":[`daemon`,`control-snapshot`,`verse-control`,`verse-usage-control`,`verse-caps`],"fleet-activity-ping":[`fleet-activity`,`fleet`],"fleet-activity-observation":[`fleet-activity`,`fleet`],snapshot:[`dashboard-snapshot`,`agent-os-snapshot`],"session-expired":[]},Ve=new Map,V=null,H=null,U=0;function He(){return Math.min(1e3*2**U,15e3)}function W(){V&&=(V.close(),null),H&&=(clearTimeout(H),null)}function Ue(){H||=setTimeout(()=>{H=null,U+=1,We()},He())}function We(){if(n()){W();return}if(N().phase!==`authenticated`)return;W();let e=`/api/events?client=${encodeURIComponent(P())}`,t=new EventSource(e,{withCredentials:!0});V=t,t.onopen=()=>{U=0};for(let e of ze)t.addEventListener(e,t=>{let n=t,r;try{r=JSON.parse(n.data)}catch{r=void 0}for(let t of Be[e])g(t);e===`inbox`&&(_(`inbox-list:`),_(`proposal-detail-`)),e===`session-expired`&&L();let i=Ve.get(e);if(i)for(let e of i)e(r)});t.onerror=()=>{t.close(),V===t&&(V=null),N().phase===`authenticated`&&Ue()}}M(()=>{N().phase===`authenticated`?(U=0,We()):W()});var Ge=`ashlr.theme.v1`;function Ke(e){let t=document.documentElement;e===`system`?t.removeAttribute(`data-theme`):t.setAttribute(`data-theme`,e)}function qe(){try{let e=localStorage.getItem(Ge);if(e===`light`||e===`dark`||e===`system`)return e}catch{}return`system`}var G=qe();Ke(G);var Je=new Set;function Ye(){for(let e of Je)e()}function Xe(e){return Je.add(e),()=>Je.delete(e)}function K(){return G}function q(e){G=e,Ke(G);try{localStorage.setItem(Ge,G)}catch{}Ye()}function Ze(){q(G===`system`?`light`:G===`light`?`dark`:`system`)}var Qe=`ashlr.verse.appearance.v1`,$e=30,et=78,J={h:245,s:72,l:58},tt=[{id:`indigo`,label:`Indigo`,h:245,s:72,l:58},{id:`blue`,label:`Blue`,h:212,s:80,l:50},{id:`cyan`,label:`Cyan`,h:190,s:80,l:40},{id:`teal`,label:`Teal`,h:168,s:70,l:38},{id:`green`,label:`Green`,h:145,s:60,l:38},{id:`amber`,label:`Amber`,h:38,s:80,l:42},{id:`rose`,label:`Rose`,h:348,s:72,l:50},{id:`violet`,label:`Violet`,h:278,s:66,l:55}];function nt(){try{return typeof window<`u`&&typeof window.matchMedia==`function`&&window.matchMedia(`(prefers-reduced-motion: reduce)`).matches}catch{return!1}}function rt(){return{theme:`system`,accentH:J.h,accentS:J.s,accentL:J.l,density:`comfortable`,uiScale:`default`,displayFont:`grotesk`,radius:`default`,motion:`system`,reduceMotion:nt()}}function it(e){return e===`system`||e===`reduce`||e===`full`}function at(e){return e===`reduce`||e!==`full`&&nt()}var ot=(e,t,n)=>Math.min(n,Math.max(t,e));function st(e){return e===`comfortable`||e===`compact`}function ct(e){return e==="default"||e===`large`||e===`xlarge`}function lt(e){return e===`grotesk`||e===`ui`||e===`mono`}function ut(e){return e===`sharp`||e==="default"||e===`soft`}function dt(e,t){let n={...e,...t},r=t.reduceMotion,i=it(t.motion)?t.motion:typeof r==`boolean`?r?`reduce`:`full`:it(e.motion)?e.motion:`system`;return{theme:n.theme===`light`||n.theme===`dark`?n.theme:`system`,accentH:Number.isFinite(n.accentH)?(Math.round(n.accentH)%360+360)%360:J.h,accentS:Number.isFinite(n.accentS)?ot(Math.round(n.accentS),0,100):J.s,accentL:Number.isFinite(n.accentL)?ot(Math.round(n.accentL),$e,et):J.l,density:st(n.density)?n.density:`comfortable`,uiScale:ct(n.uiScale)?n.uiScale:`default`,displayFont:lt(n.displayFont)?n.displayFont:`grotesk`,radius:ut(n.radius)?n.radius:`default`,motion:i,reduceMotion:at(i)}}function ft(){let e=rt(),t={};try{let e=localStorage.getItem(Qe);if(e){let n=JSON.parse(e);n&&typeof n==`object`&&(t=n)}}catch{}let{reduceMotion:n,...r}=t;return dt(e,{...r,theme:K()})}function pt(e){try{let{theme:t,reduceMotion:n,...r}=e;localStorage.setItem(Qe,JSON.stringify(r))}catch{}}var mt={grotesk:null,ui:`var(--font-ui)`,mono:`var(--font-mono)`};function ht(e){if(typeof document>`u`)return;let t=document.documentElement;t.style.setProperty(`--accent-h`,String(e.accentH)),t.style.setProperty(`--accent-s`,`${e.accentS}%`),t.style.setProperty(`--accent-l`,`${e.accentL}%`);let n=mt[e.displayFont];n===null?t.style.removeProperty(`--font-display`):t.style.setProperty(`--font-display`,n),t.setAttribute(`data-density`,e.density),t.setAttribute(`data-ui-scale`,e.uiScale),t.setAttribute(`data-radius`,e.radius),e.motion===`system`?t.removeAttribute(`data-motion`):t.setAttribute(`data-motion`,e.motion)}var Y=ft();ht(Y);var X=new Set;function gt(){for(let e of X)e()}Xe(()=>{Y.theme!==K()&&(Y={...Y,theme:K()},gt())});function _t(e){return X.add(e),()=>X.delete(e)}function vt(){return Y}function yt(e){let t=dt(Y,e),n=t.theme!==Y.theme;Y=t,ht(Y),pt(Y),n&&q(Y.theme),gt()}function bt(){try{localStorage.removeItem(Qe)}catch{}Y=rt(),ht(Y),q(`system`),gt()}function xt(e=Y){let t=rt();return e.theme===t.theme&&e.accentH===t.accentH&&e.accentS===t.accentS&&e.accentL===t.accentL&&e.density===t.density&&e.uiScale===t.uiScale&&e.displayFont===t.displayFont&&e.radius===t.radius&&e.motion===t.motion}function St(e=Y){return tt.find(t=>t.h===e.accentH&&t.s===e.accentS&&t.l===e.accentL)??null}var Ct={min:$e,max:et};function wt(e,t){let n=t?.freshMs??1e4,r=(0,i.useSyncExternalStore)((0,i.useCallback)(t=>ie(e.key,t),[e.key]),()=>ae(e.key),()=>ae(e.key));return(0,i.useEffect)(()=>{ue(e.key,()=>e.fetch(),n)},[e.key,n]),r}function Tt(e){return(0,i.useCallback)(()=>{h(e.key,()=>e.fetch())},[e])}function Et(e){return(0,i.useCallback)(()=>{h(e.key,()=>e.fetch(),!0)},[e])}function Dt(){return(0,i.useSyncExternalStore)(M,()=>N().phase,()=>N().phase)}function Ot(){let e=(0,i.useSyncExternalStore)(M,()=>N().mutationTokenHeldUntil,()=>N().mutationTokenHeldUntil);return{hasHold:Le(),token:Ie(),heldUntil:e,setToken:R,touch:z,clear:B}}function kt(){return{theme:(0,i.useSyncExternalStore)(Xe,K,K),set:q,cycle:Ze}}function At(){return{appearance:(0,i.useSyncExternalStore)(_t,vt,vt),set:yt,reset:bt}}var Z=class extends Error{status;path;detail;code;constructor(e,t,n,r=null,i=null){super(e),this.status=t,this.path=n,this.detail=r,this.code=i,this.name=`ApiError`}},jt=class extends Z{constructor(e){super(`This server was started without --allow-dispatch, so mutations are disabled.`,404,e),this.name=`DispatchDisabledError`}};async function Mt(e,t){let n=await fetch(e,{method:`GET`,credentials:`same-origin`,headers:{"x-ashlr-read-client":P()},signal:t});if(n.status===401)throw L(),new Z(`Read session expired.`,401,e);if(!n.ok)throw new Z(`GET ${e} failed (HTTP ${n.status}).`,n.status,e);return await n.json()}async function Nt(e,t,n,r){let i=await fetch(e,{method:`POST`,credentials:`same-origin`,headers:{"Content-Type":`application/json`,"x-ashlr-token":n},body:JSON.stringify(t??{}),signal:r});if(i.status===401)throw new Z(`Mutation token was rejected.`,401,e);if(!i.ok){let{detail:t,code:n}=await Pt(i);throw i.status===404&&n===null?new jt(e):new Z(`POST ${e} failed (HTTP ${i.status})${t?`: ${t}`:``}.`,i.status,e,t||null,n)}if(i.status!==204)return await i.json()}async function Pt(e){try{let t=await e.json();return{detail:typeof t.error==`string`&&t.error?t.error:typeof t.note==`string`?t.note:``,code:typeof t.code==`string`&&t.code?t.code:null}}catch{return{detail:``,code:null}}}var Q={grid:`_grid_x7p2y_1`,card:`_card_x7p2y_10`,title:`_title_x7p2y_19`,stat:`_stat_x7p2y_25`,caption:`_caption_x7p2y_31`,line:`_line_x7p2y_37`,row:`_row_x7p2y_42`,cell:`_cell_x7p2y_51`},$=t();function Ft({width:e=`100%`}){return(0,$.jsx)(`div`,{className:`skeleton ${Q.line}`,style:{width:e}})}function It(){return(0,$.jsxs)(`div`,{className:Q.card,children:[(0,$.jsx)(`div`,{className:`skeleton ${Q.title}`}),(0,$.jsx)(`div`,{className:`skeleton ${Q.stat}`}),(0,$.jsx)(`div`,{className:`skeleton ${Q.caption}`})]})}function Lt(){return(0,$.jsxs)(`div`,{className:Q.row,children:[(0,$.jsx)(`div`,{className:`skeleton ${Q.cell}`,style:{width:`18%`}}),(0,$.jsx)(`div`,{className:`skeleton ${Q.cell}`,style:{width:`38%`}}),(0,$.jsx)(`div`,{className:`skeleton ${Q.cell}`,style:{width:`14%`}}),(0,$.jsx)(`div`,{className:`skeleton ${Q.cell}`,style:{width:`10%`}})]})}function Rt({count:e=4}){return(0,$.jsx)(`div`,{className:Q.grid,children:Array.from({length:e},(e,t)=>(0,$.jsx)(It,{},t))})}var zt={skipLink:`_skipLink_1uply_1`};function Bt(){return(0,$.jsx)(`button`,{type:`button`,className:zt.skipLink,onClick:()=>{let e=document.getElementById(`main-content`);e?.focus(),e?.scrollIntoView({block:`start`})},children:`Skip to content`})}export{Ne as A,ve as B,B as C,Ie as D,N as E,z as F,re as G,ae as H,xe as I,ie as J,h as K,_e as L,L as M,R as N,P as O,M as P,ye as R,Fe as S,F as T,g as U,ue as V,_ as W,Ct as _,Lt as a,St as b,Mt as c,Dt as d,Ot as f,kt as g,Et as h,Ft as i,Me as j,Le as k,Nt as l,Tt as m,It as n,Z as o,wt as p,le as q,Rt as r,jt as s,Bt as t,At as u,tt as v,je as w,Ze as x,xt as y,be as z};
2
+ //# sourceMappingURL=global-BkNl4e6E.js.map