gloomberb 0.16.2 → 0.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (527) hide show
  1. package/package.json +4 -1
  2. package/src/api-client/askg.ts +43 -0
  3. package/src/api-client/auth.ts +15 -0
  4. package/src/api-client/beneficial-owners.ts +4 -0
  5. package/src/api-client/chat.ts +19 -0
  6. package/src/api-client/company-kpis.ts +14 -2
  7. package/src/api-client/data.ts +34 -16
  8. package/src/api-client/index.ts +12 -0
  9. package/src/api-client/normalizers.ts +27 -0
  10. package/src/api-client/paths.ts +88 -23
  11. package/src/api-client/request.ts +2 -1
  12. package/src/api-client/research-activity.ts +31 -0
  13. package/src/api-client/socket.ts +39 -8
  14. package/src/api-client/types.ts +57 -1
  15. package/src/api-client/upgrade-experiment.ts +90 -0
  16. package/src/app/global-shortcuts.ts +11 -1
  17. package/src/app/keybindings/actions.ts +10 -0
  18. package/src/app/pane-runtime/index.tsx +8 -0
  19. package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
  20. package/src/app/pane-runtime/plugin-bindings.ts +65 -25
  21. package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
  22. package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
  23. package/src/app/runtime/desktop-deeplink.ts +5 -0
  24. package/src/app/star-prompt/model.ts +98 -0
  25. package/src/app/star-prompt/runtime.ts +41 -0
  26. package/src/app/star-prompt/status-line.tsx +190 -0
  27. package/src/app.tsx +13 -7
  28. package/src/architecture/core-builtin-allowlist.json +70 -0
  29. package/src/brokers/account-cache.ts +45 -0
  30. package/src/brokers/signed-in/adapter.ts +33 -2
  31. package/src/brokers/signed-in/client.ts +10 -0
  32. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  33. package/src/brokers/signed-in/sign-in.ts +62 -5
  34. package/src/capabilities/registry.ts +8 -1
  35. package/src/capabilities/types.ts +2 -0
  36. package/src/cli/command-options.ts +74 -0
  37. package/src/cli/commands/command-utils.ts +29 -0
  38. package/src/cli/commands/crypto-hints.ts +12 -4
  39. package/src/cli/commands/market.ts +524 -148
  40. package/src/cli/commands/options-chain.ts +172 -0
  41. package/src/cli/commands/options-leaps.ts +422 -0
  42. package/src/cli/commands/overview.ts +223 -42
  43. package/src/cli/commands/search.ts +29 -8
  44. package/src/cli/commands/system.ts +99 -19
  45. package/src/cli/commands/themes.ts +64 -0
  46. package/src/cli/commands/ticker.ts +200 -73
  47. package/src/cli/desktop-pane-shot.ts +100 -16
  48. package/src/cli/errors.ts +25 -2
  49. package/src/cli/freshness.ts +7 -4
  50. package/src/cli/fx-pair.ts +57 -0
  51. package/src/cli/help.ts +40 -7
  52. package/src/cli/history-rows.ts +187 -15
  53. package/src/cli/index-roots.ts +51 -0
  54. package/src/cli/index.ts +21 -13
  55. package/src/cli/listing-arg.ts +211 -3
  56. package/src/cli/not-a-ticker.ts +37 -0
  57. package/src/cli/options.ts +55 -7
  58. package/src/cli/pane-functions/capabilities.ts +13 -2
  59. package/src/cli/pane-functions/catalog.ts +13 -2
  60. package/src/cli/pane-functions/data.ts +36 -5
  61. package/src/cli/pane-functions/dom.ts +89 -28
  62. package/src/cli/pane-functions/freshness.ts +205 -26
  63. package/src/cli/pane-functions/function-help.ts +71 -24
  64. package/src/cli/pane-functions/headless.ts +168 -35
  65. package/src/cli/pane-functions/index.ts +12 -2
  66. package/src/cli/pane-functions/market-session.ts +133 -0
  67. package/src/cli/pane-functions/options.ts +15 -4
  68. package/src/cli/pane-functions/report-notices.ts +63 -0
  69. package/src/cli/pane-functions/report.ts +12 -1
  70. package/src/cli/pane-functions/resolver.ts +32 -8
  71. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  72. package/src/cli/pane-functions/screenshot.ts +109 -23
  73. package/src/cli/registry.ts +8 -4
  74. package/src/cli/report-tables.ts +460 -0
  75. package/src/cli/result.ts +63 -16
  76. package/src/cli/row-window.ts +76 -0
  77. package/src/cli/scroll-truncation.ts +34 -0
  78. package/src/components/chart/composite/composite-chart.tsx +43 -4
  79. package/src/components/chart/composite/legend.tsx +39 -6
  80. package/src/components/chart/composite/rasterizer.ts +64 -4
  81. package/src/components/chart/composite/series-cues.ts +71 -0
  82. package/src/components/chart/composite/text-renderer.ts +16 -8
  83. package/src/components/chart/composite/types.ts +3 -0
  84. package/src/components/command-bar/commands/direct/index.ts +9 -1
  85. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  86. package/src/components/command-bar/commands/registry.ts +7 -0
  87. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  88. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  89. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  90. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  91. package/src/components/command-bar/routes/root/results.ts +40 -10
  92. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  93. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  94. package/src/components/command-bar/surface/index.tsx +18 -4
  95. package/src/components/command-bar/theme-picker.tsx +48 -13
  96. package/src/components/command-bar/workflow/ops.ts +5 -3
  97. package/src/components/data-table/export.ts +6 -51
  98. package/src/components/data-table/view.tsx +31 -10
  99. package/src/components/layout/floating-pane.tsx +6 -2
  100. package/src/components/layout/market-summary.ts +23 -9
  101. package/src/components/layout/pane/content.tsx +46 -29
  102. package/src/components/layout/pane/cycle-order.ts +2 -11
  103. package/src/components/layout/pane/header.tsx +2 -1
  104. package/src/components/layout/pane/title.ts +1 -1
  105. package/src/components/layout/presentation.ts +22 -0
  106. package/src/components/layout/shell/active-drag.ts +67 -35
  107. package/src/components/layout/shell/cursor-occlusion.ts +6 -5
  108. package/src/components/layout/shell/drag/index.ts +16 -7
  109. package/src/components/layout/shell/drag/live.ts +193 -0
  110. package/src/components/layout/shell/drag/overlays.tsx +179 -35
  111. package/src/components/layout/shell/drag/runtime.ts +62 -50
  112. package/src/components/layout/shell/drag/slide.ts +10 -0
  113. package/src/components/layout/shell/index.tsx +84 -57
  114. package/src/components/layout/shell/layout-state.ts +5 -11
  115. package/src/components/layout/shell/menu.ts +28 -1
  116. package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
  117. package/src/components/layout/shell/native/surfaces.ts +30 -14
  118. package/src/components/layout/shell/pane/actions.ts +2 -1
  119. package/src/components/layout/shell/pane/layers.tsx +461 -180
  120. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  121. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  122. package/src/components/layout/shell/shortcuts.ts +5 -2
  123. package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
  124. package/src/components/layout/shell/visible-layout.ts +44 -1
  125. package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
  126. package/src/components/layout/status-bar.tsx +4 -1
  127. package/src/components/markdown-text.tsx +9 -5
  128. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  129. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  130. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  131. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  132. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  133. package/src/components/onboarding/pro-step-layout.ts +86 -0
  134. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  135. package/src/components/ui/button.tsx +10 -0
  136. package/src/components/ui/data-table/index.tsx +38 -0
  137. package/src/components/ui/data-table/types.ts +8 -1
  138. package/src/components/ui/data-table/visible-range.ts +2 -0
  139. package/src/components/ui/icon.tsx +11 -2
  140. package/src/components/ui/index.ts +1 -1
  141. package/src/core/connection-health.ts +1 -1
  142. package/src/core/state/app/layout-reducer.ts +89 -3
  143. package/src/core/state/app/state.ts +8 -5
  144. package/src/core/state/app/types.ts +13 -2
  145. package/src/data/config/layout.ts +55 -21
  146. package/src/data/config/store/fold-panes.ts +149 -0
  147. package/src/data/config/store/migrations.ts +7 -0
  148. package/src/data/config/store/node.ts +27 -6
  149. package/src/data/config/store/normalize.ts +37 -3
  150. package/src/data/json-persistence.ts +17 -3
  151. package/src/data/json-storage.ts +2 -0
  152. package/src/data/memory-resource-store.ts +46 -0
  153. package/src/i18n/es.ts +14 -1
  154. package/src/i18n/ja.ts +14 -1
  155. package/src/i18n/ko.ts +14 -1
  156. package/src/i18n/zh-cn.ts +14 -1
  157. package/src/i18n/zh-tw.ts +14 -1
  158. package/src/layout/desks.ts +1 -2
  159. package/src/layout/pane-layout-move.ts +264 -0
  160. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  161. package/src/layout/pane-manager/index.ts +1 -0
  162. package/src/market-data/coordinator/entries.ts +7 -1
  163. package/src/market-data/coordinator/quotes.ts +2 -2
  164. package/src/market-data/market/freshness.ts +156 -11
  165. package/src/market-data/market/status.ts +40 -0
  166. package/src/market-data/options-alternatives.ts +69 -0
  167. package/src/market-data/quotes/contributions.ts +19 -2
  168. package/src/market-data/quotes/freshness.ts +5 -0
  169. package/src/market-data/quotes/metadata.ts +16 -0
  170. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  171. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  172. package/src/plugins/builtin/account-management/model.ts +1 -0
  173. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  174. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  175. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  176. package/src/plugins/builtin/analytics/pane-model.ts +10 -6
  177. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  178. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  179. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  180. package/src/plugins/builtin/attention/pane.tsx +1 -1
  181. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  182. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  183. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  184. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  185. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  186. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  187. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  188. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  189. package/src/plugins/builtin/catalysts/detail.tsx +1 -1
  190. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  191. package/src/plugins/builtin/chat/channels.ts +34 -36
  192. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  193. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  194. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  195. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  196. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  197. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  198. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  199. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  200. package/src/plugins/builtin/chat/conversations.ts +256 -0
  201. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  202. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  203. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  204. package/src/plugins/builtin/chat/index.tsx +40 -2
  205. package/src/plugins/builtin/chat/layout.ts +2 -0
  206. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  207. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  208. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  209. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  210. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  211. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  212. package/src/plugins/builtin/chat/pane.tsx +8 -0
  213. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  214. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  215. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  216. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  217. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  218. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  219. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  220. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  221. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  222. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  223. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  224. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  225. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  226. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  227. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  228. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  229. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  230. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  231. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  232. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  233. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  234. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  235. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  236. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  237. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  238. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  239. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  240. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  241. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  242. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  243. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  244. package/src/plugins/builtin/company-attention/pane.tsx +1 -1
  245. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  246. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  247. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  248. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  249. package/src/plugins/builtin/composite-plugins.ts +107 -64
  250. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  251. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  252. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  253. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  254. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  255. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  256. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  257. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  258. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  259. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  260. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  261. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  262. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  263. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  264. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  265. package/src/plugins/builtin/econ/index.tsx +68 -22
  266. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  267. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  268. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  269. package/src/plugins/builtin/executives/data.ts +17 -6
  270. package/src/plugins/builtin/executives/pane.tsx +16 -7
  271. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  272. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  273. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  274. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  275. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  276. package/src/plugins/builtin/gpu/headless.ts +9 -3
  277. package/src/plugins/builtin/gpu/model.ts +14 -5
  278. package/src/plugins/builtin/help/index.tsx +1 -0
  279. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  280. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  281. package/src/plugins/builtin/holders/client.ts +3 -6
  282. package/src/plugins/builtin/holders/format.ts +31 -1
  283. package/src/plugins/builtin/holders/headless.ts +32 -6
  284. package/src/plugins/builtin/holders/pane.tsx +25 -13
  285. package/src/plugins/builtin/holders/report-header.ts +46 -5
  286. package/src/plugins/builtin/insider/index.tsx +4 -4
  287. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  288. package/src/plugins/builtin/iv-history/client.ts +12 -2
  289. package/src/plugins/builtin/iv-history/format.ts +33 -2
  290. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  291. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  292. package/src/plugins/builtin/iv-history/model.ts +61 -13
  293. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  294. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  295. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  296. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  297. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  298. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  299. package/src/plugins/builtin/kelly-sizer/pane.tsx +121 -45
  300. package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
  301. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  302. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  303. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  304. package/src/plugins/builtin/macro-day/client.ts +24 -0
  305. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  306. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  307. package/src/plugins/builtin/macro-day/model.ts +11 -6
  308. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  309. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  310. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  311. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  312. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  313. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  314. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  315. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  316. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  317. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  318. package/src/plugins/builtin/market-movers/model.ts +71 -17
  319. package/src/plugins/builtin/market-movers/table.ts +50 -23
  320. package/src/plugins/builtin/members/pane.tsx +10 -0
  321. package/src/plugins/builtin/news/headless.ts +9 -2
  322. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  323. package/src/plugins/builtin/openfda/client.ts +1 -1
  324. package/src/plugins/builtin/options/carry.ts +95 -0
  325. package/src/plugins/builtin/options/index.tsx +58 -5
  326. package/src/plugins/builtin/options/strike-window.ts +183 -0
  327. package/src/plugins/builtin/options/table.ts +55 -3
  328. package/src/plugins/builtin/options/types.ts +8 -1
  329. package/src/plugins/builtin/options/view.tsx +161 -12
  330. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  331. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  332. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  333. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  334. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  335. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  336. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  337. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  338. package/src/plugins/builtin/pe-band/model.ts +2 -2
  339. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  340. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  341. package/src/plugins/builtin/perps/model.ts +16 -5
  342. package/src/plugins/builtin/perps/pane.tsx +1 -1
  343. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  344. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  345. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  346. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  347. package/src/plugins/builtin/plugin-module.ts +87 -4
  348. package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
  349. package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
  350. package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
  351. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  352. package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
  353. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  354. package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
  355. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
  356. package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
  357. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
  358. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  359. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  360. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  361. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  362. package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
  363. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  364. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  365. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  366. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  367. package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
  368. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  369. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  370. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  371. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  372. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  373. package/src/plugins/builtin/sec/index.tsx +5 -5
  374. package/src/plugins/builtin/sectors/client.ts +10 -2
  375. package/src/plugins/builtin/sectors/headless.ts +35 -6
  376. package/src/plugins/builtin/sectors/index.tsx +18 -1
  377. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  378. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  379. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  380. package/src/plugins/builtin/shared/members-menu.tsx +260 -0
  381. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  382. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  383. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  384. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  385. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  386. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  387. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  388. package/src/plugins/builtin/short-volume/pane.tsx +1 -1
  389. package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
  390. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  391. package/src/plugins/builtin/themes/pane.tsx +3 -0
  392. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
  393. package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
  394. package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
  395. package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
  396. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
  397. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  398. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  399. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  400. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  401. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  402. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  403. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  404. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  405. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  406. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  407. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  408. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  409. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  410. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  411. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  412. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  413. package/src/plugins/builtin/world-indices/client.ts +22 -4
  414. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  415. package/src/plugins/builtin/world-indices/model.ts +4 -2
  416. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  417. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
  418. package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
  419. package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
  420. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  421. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
  422. package/src/plugins/builtin/world-venue-map/index.ts +6 -6
  423. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
  424. package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
  425. package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
  426. package/src/plugins/builtin/world-venue-map/model.ts +16 -0
  427. package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
  428. package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
  429. package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
  430. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  431. package/src/plugins/builtin-editorial.ts +126 -0
  432. package/src/plugins/catalog-backend.ts +16 -9
  433. package/src/plugins/catalog-browser.ts +127 -63
  434. package/src/plugins/catalog-manifest.ts +73 -40
  435. package/src/plugins/catalog-ui.ts +26 -6
  436. package/src/plugins/extracted-plugins.ts +7 -1
  437. package/src/plugins/ownership.ts +150 -7
  438. package/src/plugins/plugin-functions.ts +54 -0
  439. package/src/plugins/plugin-off.ts +36 -0
  440. package/src/plugins/registry/host-actions.ts +16 -0
  441. package/src/plugins/registry/index.ts +84 -24
  442. package/src/plugins/registry/plugin-state.ts +25 -1
  443. package/src/plugins/runtime/context.tsx +29 -0
  444. package/src/react/input.ts +48 -1
  445. package/src/remote/app-host.tsx +5 -0
  446. package/src/remote/controller.ts +3 -53
  447. package/src/remote/matching.ts +69 -0
  448. package/src/remote/schema.ts +1 -1
  449. package/src/remote/semantic-tree.tsx +14 -0
  450. package/src/remote/types.ts +2 -0
  451. package/src/renderers/browser/app-services.ts +3 -1
  452. package/src/renderers/dom/data-table/dom.ts +31 -0
  453. package/src/renderers/dom/data-table/index.tsx +5 -0
  454. package/src/renderers/dom/desktop/icons.tsx +14 -0
  455. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  456. package/src/renderers/dom/host/box.tsx +113 -17
  457. package/src/renderers/dom/host/drag-shield.ts +35 -0
  458. package/src/renderers/dom/host/live-frame.ts +62 -0
  459. package/src/renderers/dom/host/mouse.ts +8 -1
  460. package/src/renderers/dom/host/scroll-box.tsx +6 -5
  461. package/src/renderers/dom/host/tabs.tsx +3 -2
  462. package/src/renderers/dom/input-host.tsx +6 -1
  463. package/src/renderers/dom/styles.css +26 -5
  464. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
  465. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  466. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  467. package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
  468. package/src/renderers/electrobun/shared/protocol.ts +12 -1
  469. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
  470. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  471. package/src/renderers/electrobun/view/host/native.ts +0 -10
  472. package/src/renderers/electrobun/view/host/window-move.ts +28 -0
  473. package/src/renderers/electrobun/view/main.tsx +4 -1
  474. package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
  475. package/src/renderers/electrobun/view/ui-host.tsx +2 -5
  476. package/src/renderers/opentui/input-host.tsx +6 -1
  477. package/src/renderers/opentui/start.tsx +9 -0
  478. package/src/renderers/opentui/ui-host.tsx +7 -1
  479. package/src/shares/portable-layout.ts +16 -6
  480. package/src/sources/gloomberb-cloud/index.ts +19 -10
  481. package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
  482. package/src/sources/listing-symbols.ts +11 -5
  483. package/src/sources/provider-errors.ts +88 -3
  484. package/src/sources/provider-router/batches.ts +3 -3
  485. package/src/sources/provider-router/cached-routes.ts +8 -4
  486. package/src/sources/provider-router/financial-routes.ts +13 -7
  487. package/src/sources/provider-router/financials.ts +106 -16
  488. package/src/sources/provider-router/history.ts +27 -11
  489. package/src/sources/provider-router/index.ts +1 -1
  490. package/src/sources/provider-router/primary.ts +24 -6
  491. package/src/sources/sec-edgar.ts +68 -8
  492. package/src/state/pane-scroll-registry.tsx +67 -35
  493. package/src/sync/core-contributors.ts +4 -3
  494. package/src/test-support/cli-context.ts +2 -1
  495. package/src/test-support/data-provider.ts +34 -0
  496. package/src/test-support/margin-account.ts +108 -0
  497. package/src/test-support/shell-pane-keys.tsx +1 -0
  498. package/src/theme/series-colors.ts +46 -0
  499. package/src/theme/themes.ts +149 -0
  500. package/src/tickers/search/index.ts +22 -2
  501. package/src/tickers/search/ranking.ts +45 -1
  502. package/src/tickers/search/types.ts +3 -1
  503. package/src/time-series/resolution.ts +15 -0
  504. package/src/time-series/resolve.ts +1 -14
  505. package/src/types/config.ts +61 -1
  506. package/src/types/financials.ts +32 -3
  507. package/src/types/headless.ts +11 -0
  508. package/src/types/instrument.ts +8 -0
  509. package/src/types/plugin.ts +49 -3
  510. package/src/ui/host.tsx +16 -0
  511. package/src/ui/index.tsx +10 -3
  512. package/src/ui/scroll-box-axis.ts +26 -0
  513. package/src/utils/cli-output.ts +14 -1
  514. package/src/utils/currency-units.ts +8 -3
  515. package/src/utils/depositary-receipt.ts +54 -10
  516. package/src/utils/display-number.ts +63 -0
  517. package/src/utils/exchanges.ts +13 -0
  518. package/src/utils/format.ts +2 -1
  519. package/src/utils/fundamentals.ts +17 -1
  520. package/src/utils/fx-market-hours.ts +34 -0
  521. package/src/utils/option-expiry.ts +126 -0
  522. package/src/utils/price-history-integrity.ts +19 -6
  523. package/src/utils/price-history.ts +39 -6
  524. package/src/utils/rpc-timeout-error.ts +6 -2
  525. package/src/utils/sec-eps-basis.ts +159 -30
  526. package/src/utils/sec.ts +38 -34
  527. package/src/utils/throttled-fetch.ts +51 -22
@@ -8,9 +8,10 @@ import type {
8
8
  HeadlessSeriesResult,
9
9
  HeadlessSnapshotResult,
10
10
  } from "../../types/plugin";
11
- import { formatUtcTime, parseReportTime, type ReportTime } from "../../utils/utc-time";
11
+ import { parseReportTime, type ReportTime } from "../../utils/utc-time";
12
12
  import { isRecord } from "../../utils/guards";
13
13
  import { latestRegularSessionClose } from "../../market-data/market/freshness";
14
+ import { reportMarketSession, type ReportMarket, type SessionObservation } from "./market-session";
14
15
 
15
16
  /**
16
17
  * The four states of docs/usage.md#how-current-a-report-is, plus `unreported`
@@ -27,7 +28,10 @@ export interface ReportFreshness {
27
28
  /** Oldest observation, when it is more than a day older than `asOf`. */
28
29
  oldest?: string;
29
30
  status: ReportFreshnessStatus;
31
+ /** How far the data is held back; the most of its rows when they differ... */
30
32
  delayMinutes?: number;
33
+ /** ...and the least, only when it is less. */
34
+ minDelayMinutes?: number;
31
35
  /** How old the stale data is: the newest observation, or the oldest stale row when only some are. */
32
36
  ageMinutes?: number;
33
37
  /** When only some rows are stale: how many, of the rows that say whether they are... */
@@ -36,6 +40,10 @@ export interface ReportFreshness {
36
40
  /** ...and what the rest are. */
37
41
  feed?: "live" | "delayed";
38
42
  basis?: string;
43
+ /** The local date of the session close `asOf` is, when the newest observation is its venue's latest close. */
44
+ asOfClose?: string;
45
+ /** Where the venues of a quote report stand now, from their session calendars. */
46
+ market?: ReportMarket;
39
47
  /** When this report was built. */
40
48
  retrievedAt: string;
41
49
  }
@@ -59,11 +67,14 @@ interface FeedSignals {
59
67
  live: boolean;
60
68
  delayed: boolean;
61
69
  delayMinutes?: number;
70
+ minDelayMinutes?: number;
62
71
  /** A record-wide stale flag (metadata), read only when no row says whether it is stale. */
63
72
  stale: boolean;
64
73
  /** Rows and series that carry a `stale` flag, and the times of those that are stale. */
65
74
  flagged: number;
66
75
  staleTimes: Array<ReportTime | null>;
76
+ /** Rows that name the venue whose sessions they follow (`sessionExchange`), with their time. */
77
+ sessions: SessionObservation[];
67
78
  }
68
79
 
69
80
  interface Observations {
@@ -74,7 +85,7 @@ interface Observations {
74
85
  }
75
86
 
76
87
  function emptySignals(): FeedSignals {
77
- return { live: false, delayed: false, stale: false, flagged: 0, staleTimes: [] };
88
+ return { live: false, delayed: false, stale: false, flagged: 0, staleTimes: [], sessions: [] };
78
89
  }
79
90
 
80
91
  function readFeedSignals(record: Record<string, unknown>, signals: FeedSignals): void {
@@ -84,9 +95,21 @@ function readFeedSignals(record: Record<string, unknown>, signals: FeedSignals):
84
95
  if (typeof delay === "number" && Number.isFinite(delay) && delay > 0) {
85
96
  signals.delayed = true;
86
97
  signals.delayMinutes = Math.max(signals.delayMinutes ?? 0, delay);
98
+ signals.minDelayMinutes = Math.min(signals.minDelayMinutes ?? delay, delay);
87
99
  }
88
100
  }
89
101
 
102
+ /** A quote row's venue and reported session, so the report can say whether its market is open. */
103
+ function readSession(record: Record<string, unknown>, time: ReportTime | null, signals: FeedSignals): void {
104
+ const exchange = record.sessionExchange;
105
+ if (typeof exchange !== "string" || !exchange.trim()) return;
106
+ signals.sessions.push({
107
+ exchange,
108
+ ...(typeof record.marketState === "string" ? { marketState: record.marketState } : {}),
109
+ time,
110
+ });
111
+ }
112
+
90
113
  /** A row's or series' own stale flag; worst of across them, counted so a partly stale board says how much. */
91
114
  function readUnitStale(record: Record<string, unknown>, time: ReportTime | null, signals: FeedSignals): void {
92
115
  if (typeof record.stale !== "boolean") return;
@@ -147,6 +170,7 @@ function collect(
147
170
  const time = observedTime(row, now, rowKeys)
148
171
  ?? (observedKey ? null : observedTime(row, now, UPDATE_KEYS));
149
172
  if (!ignoreStaleFlags) readUnitStale(row, time, signals);
173
+ readSession(row, time, signals);
150
174
  if (time) observations.units.push(time);
151
175
  }
152
176
  if (definition.shape === "series") {
@@ -247,6 +271,7 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
247
271
 
248
272
  let status: ReportFreshnessStatus;
249
273
  let delayMinutes: number | undefined;
274
+ let minDelayMinutes: number | undefined;
250
275
  let staleAt: ReportTime | null = null;
251
276
  let partial: { staleCount: number; observationCount: number; feed?: "live" | "delayed" } | null = null;
252
277
  if (declared.status === "not-a-feed") {
@@ -288,8 +313,16 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
288
313
  status = feed ?? "unreported";
289
314
  }
290
315
  // A partly stale board still says how far its fresh rows are held back.
291
- if (status === "delayed" || partial?.feed === "delayed") delayMinutes = declared.delayMinutes ?? signals.delayMinutes;
316
+ if (status === "delayed" || partial?.feed === "delayed") {
317
+ delayMinutes = declared.delayMinutes ?? signals.delayMinutes;
318
+ if (declared.delayMinutes == null && signals.minDelayMinutes != null && delayMinutes != null && signals.minDelayMinutes < delayMinutes) {
319
+ minDelayMinutes = signals.minDelayMinutes;
320
+ }
321
+ }
292
322
  }
323
+ const { market, closeDate } = reportMarketSession(signals.sessions, now);
324
+ // The close wording dates the newest observation, so only when that is what `asOf` is.
325
+ const asOfClose = closeDate && asOf && !declaredAsOf ? closeDate : null;
293
326
 
294
327
  return {
295
328
  source: declared.source?.trim() || DEFAULT_REPORT_SOURCE,
@@ -297,9 +330,12 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
297
330
  ...(asOf && oldestTime && asOf.time - oldestTime.time > OLDEST_GAP_MS ? { oldest: isoTime(oldestTime) } : {}),
298
331
  status,
299
332
  ...(delayMinutes != null && delayMinutes > 0 ? { delayMinutes } : {}),
333
+ ...(minDelayMinutes != null && minDelayMinutes > 0 ? { minDelayMinutes } : {}),
300
334
  ...(status === "stale" && staleAt ? { ageMinutes: Math.max(0, Math.floor((now - staleAt.time) / 60_000)) } : {}),
301
335
  ...(partial ?? {}),
302
336
  ...(status === "not-a-feed" && declared.basis ? { basis: declared.basis } : {}),
337
+ ...(asOfClose ? { asOfClose } : {}),
338
+ ...(market ? { market } : {}),
303
339
  retrievedAt: new Date(now).toISOString(),
304
340
  };
305
341
  }
@@ -322,14 +358,20 @@ export function deriveHeadlessFreshness(
322
358
  /**
323
359
  * A rendered view has no structured data: the source is the pane's
324
360
  * declaration, the times are the cells that carry an instant, and the state
325
- * is what the pane's own footer says, or not reported.
361
+ * is what the pane's own footer says, or not reported. A pane that dates its
362
+ * figures itself publishes `observed` records, read as a headless report's
363
+ * rows are: quote or rate times, feed, delay, stale flags and venue sessions.
326
364
  */
327
365
  export function deriveRenderedFreshness(
328
366
  declared: HeadlessPaneFreshness | undefined,
329
- input: { footerText: string; cellTimes: unknown[] },
367
+ input: { footerText: string; cellTimes: unknown[]; observed?: readonly Record<string, unknown>[] },
330
368
  now = Date.now(),
331
369
  ): ReportFreshness {
332
- const signals = emptySignals();
370
+ const merged = mergeDeclarations(declared);
371
+ const observed = input.observed?.length
372
+ ? collect({ shape: "rows" }, { rows: input.observed as HeadlessPaneRow[] }, now, merged)
373
+ : null;
374
+ const signals = observed?.signals ?? emptySignals();
333
375
  const footer = input.footerText;
334
376
  const delayed = /\b(\d+)\s*(?:m|min|minutes?)\s+delayed\b/i.exec(footer);
335
377
  if (delayed) {
@@ -344,14 +386,62 @@ export function deriveRenderedFreshness(
344
386
  .map((value) => parseReportTime(value))
345
387
  .filter((time): time is ReportTime => time != null && time.time <= now + FUTURE_TOLERANCE_MS);
346
388
  return resolveFreshness({
347
- declared: mergeDeclarations(declared),
389
+ declared: merged,
348
390
  signals,
349
391
  // Rendered rows are often a history (a news list, a filing feed), so only the newest is cited.
350
- observations: { units: [], extra: units },
392
+ observations: { units: observed?.observations.units ?? [], extra: [...(observed?.observations.extra ?? []), ...units] },
351
393
  now,
352
394
  });
353
395
  }
354
396
 
397
+ const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as const;
398
+ const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] as const;
399
+ const MINUTES_PER_DAY = 24 * 60;
400
+ const DAY_MS = 24 * 60 * 60_000;
401
+ /** A reopen further ahead than this reads with its date, not just its weekday. */
402
+ const WEEKDAY_ONLY_MS = 6 * DAY_MS;
403
+
404
+ /** `Fri 9 Oct` for a `YYYY-MM-DD` date, with the year when it is not the year of `now`. */
405
+ function dayText(date: string, now: number): string {
406
+ const day = new Date(`${date.slice(0, 10)}T00:00:00Z`);
407
+ if (!Number.isFinite(day.getTime())) return date;
408
+ const year = day.getUTCFullYear() === new Date(now).getUTCFullYear() ? "" : ` ${day.getUTCFullYear()}`;
409
+ return `${WEEKDAYS[day.getUTCDay()]} ${day.getUTCDate()} ${MONTHS[day.getUTCMonth()]}${year}`;
410
+ }
411
+
412
+ /** `Fri 9 Oct 23:59 UTC`, or `Fri 9 Oct` for a date. */
413
+ function timeText(value: string, now: number): string {
414
+ const time = parseReportTime(value);
415
+ if (!time) return value;
416
+ const iso = new Date(time.time).toISOString();
417
+ return time.dateOnly ? dayText(iso, now) : `${dayText(iso, now)} ${iso.slice(11, 16)} UTC`;
418
+ }
419
+
420
+ /**
421
+ * How long a delay is, in the unit it is stated in: `15 min`, `12 h`, `3 days`.
422
+ * A delay under two hours that is not a whole hour stays in minutes.
423
+ */
424
+ export function delayLength(minutes: number): string {
425
+ const whole = Math.max(0, Math.round(minutes));
426
+ if (whole >= 2 * MINUTES_PER_DAY && whole % MINUTES_PER_DAY === 0) return `${whole / MINUTES_PER_DAY} days`;
427
+ if (whole >= 60 && whole % 60 === 0) return `${whole / 60} h`;
428
+ if (whole < 120) return `${whole} min`;
429
+ return `${Math.round(whole / 60)} h`;
430
+ }
431
+
432
+ /** `15 min delayed`, `15-20 min delayed` across venues, or `delayed` when the feed does not say. */
433
+ function delayedText(freshness: Pick<ReportFreshness, "delayMinutes" | "minDelayMinutes">): string {
434
+ const most = freshness.delayMinutes;
435
+ if (!most) return "delayed";
436
+ const least = freshness.minDelayMinutes;
437
+ if (least && least < most) {
438
+ const [low, lowUnit] = delayLength(least).split(" ");
439
+ const [high, highUnit] = delayLength(most).split(" ");
440
+ return lowUnit === highUnit ? `${low}-${high} ${highUnit} delayed` : `up to ${delayLength(most)} delayed`;
441
+ }
442
+ return `${delayLength(most)} delayed`;
443
+ }
444
+
355
445
  function ageText(minutes: number): string {
356
446
  if (minutes < 120) return `${minutes} min`;
357
447
  const hours = Math.floor(minutes / 60);
@@ -359,27 +449,25 @@ function ageText(minutes: number): string {
359
449
  return `${Math.floor(hours / 24)} days`;
360
450
  }
361
451
 
362
- function delayedText(minutes: number | undefined): string {
363
- return minutes ? `Delayed ${minutes} min` : "Delayed";
364
- }
365
-
366
- function statusText(freshness: ReportFreshness): string {
452
+ /** `short` counts stale rows without their total (`2 stale`), for a narrow capture. */
453
+ function statusText(freshness: ReportFreshness, short = false): string {
367
454
  switch (freshness.status) {
368
455
  case "live":
369
- return "Live";
456
+ return "live";
370
457
  case "delayed":
371
- return delayedText(freshness.delayMinutes);
458
+ return delayedText(freshness);
372
459
  case "stale": {
373
460
  if (freshness.staleCount != null && freshness.observationCount != null) {
374
- const rest = freshness.feed === "live" ? "Live" : freshness.feed === "delayed" ? delayedText(freshness.delayMinutes) : null;
375
- return `${rest ? `${rest}, ` : ""}${freshness.staleCount} of ${freshness.observationCount} stale`;
461
+ const rest = freshness.feed === "live" ? "live" : freshness.feed === "delayed" ? delayedText(freshness) : null;
462
+ const count = short ? `${freshness.staleCount}` : `${freshness.staleCount} of ${freshness.observationCount}`;
463
+ return `${rest ? `${rest}, ` : ""}${count} stale`;
376
464
  }
377
- return freshness.ageMinutes != null ? `Stale (${ageText(freshness.ageMinutes)} old)` : "Stale";
465
+ return freshness.ageMinutes != null ? `stale (${ageText(freshness.ageMinutes)} old)` : "stale";
378
466
  }
379
467
  case "not-a-feed":
380
- return freshness.basis ? `Not a live feed (${freshness.basis})` : "Not a live feed";
468
+ return freshness.basis ? `not a live feed (${freshness.basis})` : "not a live feed";
381
469
  case "unreported":
382
- return "Status not reported";
470
+ return "status not reported";
383
471
  default: {
384
472
  const _exhaustive: never = freshness.status;
385
473
  return _exhaustive;
@@ -388,13 +476,104 @@ function statusText(freshness: ReportFreshness): string {
388
476
  }
389
477
 
390
478
  /**
391
- * The one line every `fn` text report ends with:
392
- * `Source: Gloom Cloud | As of 2026-10-09 00:08 UTC | Delayed 10 min`.
479
+ * The dated as-of: a session close, a UTC time, a date, or when the report was
480
+ * retrieved; with the oldest observation unless `short`.
481
+ */
482
+ function asOfText(freshness: ReportFreshness, now: number, short = false): string {
483
+ if (!freshness.asOf) return `retrieved ${timeText(freshness.retrievedAt, now)}`;
484
+ const asOf = freshness.asOfClose ? `${dayText(freshness.asOfClose, now)} close` : timeText(freshness.asOf, now);
485
+ return freshness.oldest && !short ? `${asOf} (oldest ${dayText(freshness.oldest, now)})` : asOf;
486
+ }
487
+
488
+ /**
489
+ * When the first venue opens again: its UTC time when that is later the same
490
+ * UTC day, else its weekday at the venue (`Mon`), with the date a week out.
491
+ */
492
+ function reopenText(market: ReportMarket, now: number): string | null {
493
+ if (!market.reopensAt || !market.reopensOn) return null;
494
+ const at = Date.parse(market.reopensAt);
495
+ if (!Number.isFinite(at)) return null;
496
+ const iso = new Date(at).toISOString();
497
+ if (iso.slice(0, 10) === new Date(now).toISOString().slice(0, 10)) return `${iso.slice(11, 16)} UTC`;
498
+ return at - now > WEEKDAY_ONLY_MS ? dayText(market.reopensOn, now) : WEEKDAYS[new Date(`${market.reopensOn}T00:00:00Z`).getUTCDay()]!;
499
+ }
500
+
501
+ /** `markets closed until Mon`; `short` is the compact form a narrow capture falls back to (`reopens Mon`). */
502
+ function marketText(market: ReportMarket, now: number, short = false): string {
503
+ const reopen = reopenText(market, now);
504
+ switch (market.state) {
505
+ case "open":
506
+ return short ? "open" : "markets open";
507
+ case "after-hours":
508
+ return "after hours";
509
+ case "pre-market":
510
+ return reopen ? `pre-market, opens ${reopen}` : "pre-market";
511
+ case "closed":
512
+ if (short) return reopen ? `reopens ${reopen}` : "closed";
513
+ return reopen ? `markets closed until ${reopen}` : "markets closed";
514
+ default: {
515
+ const _exhaustive: never = market.state;
516
+ return _exhaustive;
517
+ }
518
+ }
519
+ }
520
+
521
+ interface StatusLineDetail {
522
+ /** Leave out the oldest observation. */
523
+ asOf?: "short";
524
+ /** Count stale rows without their total. */
525
+ status?: "short";
526
+ /** `reopens Mon` for `markets closed until Mon`; `none` leaves the market out. */
527
+ market?: "short" | "none";
528
+ }
529
+
530
+ /**
531
+ * The parts of a report's status line: the dated as-of, how current the data
532
+ * is (`15 min delayed`, `live`), and where its markets stand, when known.
533
+ */
534
+ function statusLineParts(freshness: ReportFreshness, detail: StatusLineDetail = {}): string[] {
535
+ const now = Date.parse(freshness.retrievedAt);
536
+ return [
537
+ asOfText(freshness, now, detail.asOf === "short"),
538
+ statusText(freshness, detail.status === "short"),
539
+ ...(freshness.market && detail.market !== "none" ? [marketText(freshness.market, now, detail.market === "short")] : []),
540
+ ];
541
+ }
542
+
543
+ function capitalize(text: string): string {
544
+ return text ? `${text[0]!.toUpperCase()}${text.slice(1)}` : text;
545
+ }
546
+
547
+ /**
548
+ * The plain status line of a report or capture, without its source:
549
+ * `Fri 9 Oct close · 15 min delayed · markets closed until Mon`.
550
+ */
551
+ export function formatStatusLine(freshness: ReportFreshness): string {
552
+ return capitalize(statusLineParts(freshness).join(" · "));
553
+ }
554
+
555
+ /**
556
+ * The status line from longest to shortest, for a capture to take the first
557
+ * that fits its width. The date and the delay stay to the last; the market
558
+ * part shortens, then the oldest observation and the stale total go, then
559
+ * the market part.
560
+ */
561
+ export function statusLineVariants(freshness: ReportFreshness): string[] {
562
+ const details: StatusLineDetail[] = [
563
+ {},
564
+ { market: "short" },
565
+ { asOf: "short", market: "short" },
566
+ { asOf: "short", status: "short", market: "short" },
567
+ { asOf: "short", status: "short", market: "none" },
568
+ ];
569
+ return [...new Set(details.map((detail) => capitalize(statusLineParts(freshness, detail).join(" · "))))];
570
+ }
571
+
572
+ /**
573
+ * The one line every text report ends with, its source then its status line:
574
+ * `Source: Gloom Cloud · Fri 9 Oct close · 15 min delayed · markets closed until Mon`.
393
575
  * Without a dated observation it says when the report was retrieved instead.
394
576
  */
395
577
  export function formatFreshnessLine(freshness: ReportFreshness): string {
396
- const time = freshness.asOf
397
- ? `As of ${formatUtcTime(freshness.asOf)}${freshness.oldest ? ` (oldest ${freshness.oldest.slice(0, 10)})` : ""}`
398
- : `Retrieved ${formatUtcTime(freshness.retrievedAt)}`;
399
- return [`Source: ${freshness.source}`, time, statusText(freshness)].join(" | ");
578
+ return [`Source: ${freshness.source}`, ...statusLineParts(freshness)].join(" · ");
400
579
  }