gloomberb 0.16.3 → 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 (451) 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/signed-in/adapter.ts +33 -2
  30. package/src/brokers/signed-in/client.ts +10 -0
  31. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  32. package/src/brokers/signed-in/sign-in.ts +62 -5
  33. package/src/capabilities/registry.ts +8 -1
  34. package/src/capabilities/types.ts +2 -0
  35. package/src/cli/commands/crypto-hints.ts +12 -4
  36. package/src/cli/commands/market.ts +359 -99
  37. package/src/cli/commands/options-chain.ts +172 -0
  38. package/src/cli/commands/options-leaps.ts +422 -0
  39. package/src/cli/commands/overview.ts +164 -26
  40. package/src/cli/commands/search.ts +29 -8
  41. package/src/cli/commands/system.ts +99 -19
  42. package/src/cli/commands/themes.ts +64 -0
  43. package/src/cli/commands/ticker.ts +62 -30
  44. package/src/cli/desktop-pane-shot.ts +97 -16
  45. package/src/cli/errors.ts +25 -2
  46. package/src/cli/freshness.ts +7 -4
  47. package/src/cli/fx-pair.ts +57 -0
  48. package/src/cli/help.ts +14 -1
  49. package/src/cli/index-roots.ts +51 -0
  50. package/src/cli/index.ts +9 -6
  51. package/src/cli/listing-arg.ts +211 -3
  52. package/src/cli/not-a-ticker.ts +37 -0
  53. package/src/cli/options.ts +50 -6
  54. package/src/cli/pane-functions/capabilities.ts +8 -2
  55. package/src/cli/pane-functions/catalog.ts +13 -2
  56. package/src/cli/pane-functions/data.ts +36 -5
  57. package/src/cli/pane-functions/dom.ts +21 -13
  58. package/src/cli/pane-functions/freshness.ts +205 -26
  59. package/src/cli/pane-functions/function-help.ts +67 -22
  60. package/src/cli/pane-functions/headless.ts +5 -4
  61. package/src/cli/pane-functions/index.ts +3 -0
  62. package/src/cli/pane-functions/market-session.ts +133 -0
  63. package/src/cli/pane-functions/options.ts +15 -4
  64. package/src/cli/pane-functions/report-notices.ts +63 -0
  65. package/src/cli/pane-functions/report.ts +4 -0
  66. package/src/cli/pane-functions/resolver.ts +4 -6
  67. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  68. package/src/cli/pane-functions/screenshot.ts +105 -23
  69. package/src/cli/registry.ts +2 -1
  70. package/src/cli/result.ts +50 -13
  71. package/src/cli/row-window.ts +76 -0
  72. package/src/cli/scroll-truncation.ts +34 -0
  73. package/src/components/chart/composite/composite-chart.tsx +43 -4
  74. package/src/components/chart/composite/legend.tsx +39 -6
  75. package/src/components/chart/composite/rasterizer.ts +64 -4
  76. package/src/components/chart/composite/series-cues.ts +71 -0
  77. package/src/components/chart/composite/text-renderer.ts +16 -8
  78. package/src/components/chart/composite/types.ts +3 -0
  79. package/src/components/command-bar/commands/direct/index.ts +9 -1
  80. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  81. package/src/components/command-bar/commands/registry.ts +7 -0
  82. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  83. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  84. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  85. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  86. package/src/components/command-bar/routes/root/results.ts +40 -10
  87. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  88. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  89. package/src/components/command-bar/surface/index.tsx +18 -4
  90. package/src/components/command-bar/theme-picker.tsx +48 -13
  91. package/src/components/command-bar/workflow/ops.ts +5 -3
  92. package/src/components/data-table/view.tsx +28 -9
  93. package/src/components/layout/floating-pane.tsx +6 -2
  94. package/src/components/layout/market-summary.ts +23 -9
  95. package/src/components/layout/pane/content.tsx +46 -29
  96. package/src/components/layout/pane/cycle-order.ts +2 -11
  97. package/src/components/layout/pane/header.tsx +2 -1
  98. package/src/components/layout/pane/title.ts +1 -1
  99. package/src/components/layout/presentation.ts +22 -0
  100. package/src/components/layout/shell/active-drag.ts +1 -1
  101. package/src/components/layout/shell/drag/index.ts +4 -0
  102. package/src/components/layout/shell/drag/live.ts +76 -5
  103. package/src/components/layout/shell/drag/runtime.ts +8 -8
  104. package/src/components/layout/shell/index.tsx +47 -6
  105. package/src/components/layout/shell/layout-state.ts +5 -11
  106. package/src/components/layout/shell/menu.ts +28 -1
  107. package/src/components/layout/shell/pane/actions.ts +2 -1
  108. package/src/components/layout/shell/pane/layers.tsx +51 -10
  109. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  110. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  111. package/src/components/layout/shell/shortcuts.ts +5 -2
  112. package/src/components/layout/shell/visible-layout.ts +44 -1
  113. package/src/components/layout/status-bar.tsx +4 -1
  114. package/src/components/markdown-text.tsx +9 -5
  115. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  116. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  117. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  118. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  119. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  120. package/src/components/onboarding/pro-step-layout.ts +86 -0
  121. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  122. package/src/components/ui/button.tsx +10 -0
  123. package/src/components/ui/data-table/index.tsx +38 -0
  124. package/src/components/ui/data-table/types.ts +8 -1
  125. package/src/components/ui/data-table/visible-range.ts +2 -0
  126. package/src/components/ui/icon.tsx +11 -2
  127. package/src/components/ui/index.ts +1 -1
  128. package/src/core/connection-health.ts +1 -1
  129. package/src/core/state/app/layout-reducer.ts +89 -3
  130. package/src/core/state/app/state.ts +8 -5
  131. package/src/core/state/app/types.ts +13 -2
  132. package/src/data/config/layout.ts +55 -21
  133. package/src/data/config/store/fold-panes.ts +149 -0
  134. package/src/data/config/store/migrations.ts +7 -0
  135. package/src/data/config/store/node.ts +27 -6
  136. package/src/data/config/store/normalize.ts +37 -3
  137. package/src/i18n/es.ts +14 -1
  138. package/src/i18n/ja.ts +14 -1
  139. package/src/i18n/ko.ts +14 -1
  140. package/src/i18n/zh-cn.ts +14 -1
  141. package/src/i18n/zh-tw.ts +14 -1
  142. package/src/layout/desks.ts +1 -2
  143. package/src/layout/pane-layout-move.ts +264 -0
  144. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  145. package/src/layout/pane-manager/index.ts +1 -0
  146. package/src/market-data/coordinator/entries.ts +7 -1
  147. package/src/market-data/coordinator/quotes.ts +2 -2
  148. package/src/market-data/market/freshness.ts +118 -18
  149. package/src/market-data/market/status.ts +40 -0
  150. package/src/market-data/options-alternatives.ts +69 -0
  151. package/src/market-data/quotes/contributions.ts +19 -2
  152. package/src/market-data/quotes/metadata.ts +16 -0
  153. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  154. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  155. package/src/plugins/builtin/account-management/model.ts +1 -0
  156. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  157. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  158. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  159. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  160. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  161. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  162. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  163. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  164. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  165. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  166. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  167. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  168. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  169. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  170. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  171. package/src/plugins/builtin/chat/channels.ts +34 -36
  172. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  173. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  174. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  175. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  176. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  177. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  178. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  179. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  180. package/src/plugins/builtin/chat/conversations.ts +256 -0
  181. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  182. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  183. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  184. package/src/plugins/builtin/chat/index.tsx +40 -2
  185. package/src/plugins/builtin/chat/layout.ts +2 -0
  186. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  187. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  188. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  189. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  190. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  191. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  192. package/src/plugins/builtin/chat/pane.tsx +8 -0
  193. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  194. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  195. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  196. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  197. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  198. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  199. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  200. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  201. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  202. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  203. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  204. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  205. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  206. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  207. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  208. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  209. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  210. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  211. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  212. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  213. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  214. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  215. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  216. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  217. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  218. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  219. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  220. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  221. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  222. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  223. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  224. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  225. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  226. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  227. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  228. package/src/plugins/builtin/composite-plugins.ts +107 -64
  229. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  230. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  231. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  232. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  233. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  234. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  235. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  236. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  237. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  238. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  239. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  240. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  241. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  242. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  243. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  244. package/src/plugins/builtin/econ/index.tsx +68 -22
  245. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  246. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  247. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  248. package/src/plugins/builtin/executives/data.ts +17 -6
  249. package/src/plugins/builtin/executives/pane.tsx +16 -7
  250. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  251. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  252. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  253. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  254. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  255. package/src/plugins/builtin/gpu/headless.ts +9 -3
  256. package/src/plugins/builtin/gpu/model.ts +14 -5
  257. package/src/plugins/builtin/help/index.tsx +1 -0
  258. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  259. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  260. package/src/plugins/builtin/holders/headless.ts +6 -1
  261. package/src/plugins/builtin/holders/pane.tsx +3 -0
  262. package/src/plugins/builtin/insider/index.tsx +4 -4
  263. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  264. package/src/plugins/builtin/iv-history/client.ts +12 -2
  265. package/src/plugins/builtin/iv-history/format.ts +33 -2
  266. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  267. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  268. package/src/plugins/builtin/iv-history/model.ts +61 -13
  269. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  270. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  271. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  272. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  273. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  274. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  275. package/src/plugins/builtin/kelly-sizer/pane.tsx +109 -39
  276. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  277. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  278. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  279. package/src/plugins/builtin/macro-day/client.ts +24 -0
  280. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  281. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  282. package/src/plugins/builtin/macro-day/model.ts +11 -6
  283. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  284. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  285. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  286. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  287. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  288. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  289. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  290. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  291. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  292. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  293. package/src/plugins/builtin/market-movers/model.ts +71 -17
  294. package/src/plugins/builtin/market-movers/table.ts +50 -23
  295. package/src/plugins/builtin/news/headless.ts +9 -2
  296. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  297. package/src/plugins/builtin/openfda/client.ts +1 -1
  298. package/src/plugins/builtin/options/carry.ts +95 -0
  299. package/src/plugins/builtin/options/index.tsx +58 -5
  300. package/src/plugins/builtin/options/strike-window.ts +183 -0
  301. package/src/plugins/builtin/options/table.ts +55 -3
  302. package/src/plugins/builtin/options/types.ts +8 -1
  303. package/src/plugins/builtin/options/view.tsx +161 -12
  304. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  305. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  306. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  307. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  308. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  309. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  310. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  311. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  312. package/src/plugins/builtin/pe-band/model.ts +2 -2
  313. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  314. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  315. package/src/plugins/builtin/perps/model.ts +16 -5
  316. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  317. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  318. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  319. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  320. package/src/plugins/builtin/plugin-module.ts +87 -4
  321. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  322. package/src/plugins/builtin/portfolio-list/cli/render.ts +87 -18
  323. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  324. package/src/plugins/builtin/portfolio-list/headless.ts +38 -7
  325. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  326. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  327. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  328. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  329. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  330. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  331. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  332. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  333. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  334. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  335. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  336. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  337. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  338. package/src/plugins/builtin/sec/index.tsx +5 -5
  339. package/src/plugins/builtin/sectors/client.ts +10 -2
  340. package/src/plugins/builtin/sectors/headless.ts +35 -6
  341. package/src/plugins/builtin/sectors/index.tsx +18 -1
  342. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  343. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  344. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  345. package/src/plugins/builtin/shared/members-menu.tsx +4 -4
  346. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  347. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  348. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  349. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  350. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  351. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  352. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  353. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  354. package/src/plugins/builtin/ticker-detail/headless.ts +43 -21
  355. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  356. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  357. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  358. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  359. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  360. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  361. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  362. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  363. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  364. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  365. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  366. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  367. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  368. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  369. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  370. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  371. package/src/plugins/builtin/world-indices/client.ts +22 -4
  372. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  373. package/src/plugins/builtin/world-indices/model.ts +4 -2
  374. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  375. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  376. package/src/plugins/builtin/world-venue-map/index.ts +1 -1
  377. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  378. package/src/plugins/builtin-editorial.ts +126 -0
  379. package/src/plugins/catalog-backend.ts +16 -9
  380. package/src/plugins/catalog-browser.ts +127 -63
  381. package/src/plugins/catalog-manifest.ts +73 -40
  382. package/src/plugins/catalog-ui.ts +26 -6
  383. package/src/plugins/extracted-plugins.ts +7 -1
  384. package/src/plugins/ownership.ts +150 -7
  385. package/src/plugins/plugin-functions.ts +54 -0
  386. package/src/plugins/plugin-off.ts +36 -0
  387. package/src/plugins/registry/host-actions.ts +16 -0
  388. package/src/plugins/registry/index.ts +84 -24
  389. package/src/plugins/registry/plugin-state.ts +25 -1
  390. package/src/plugins/runtime/context.tsx +29 -0
  391. package/src/react/input.ts +48 -1
  392. package/src/remote/app-host.tsx +5 -0
  393. package/src/remote/controller.ts +3 -53
  394. package/src/remote/matching.ts +69 -0
  395. package/src/remote/schema.ts +1 -1
  396. package/src/remote/semantic-tree.tsx +14 -0
  397. package/src/remote/types.ts +2 -0
  398. package/src/renderers/dom/desktop/icons.tsx +14 -0
  399. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  400. package/src/renderers/dom/host/box.tsx +1 -1
  401. package/src/renderers/dom/host/drag-shield.ts +17 -6
  402. package/src/renderers/dom/host/live-frame.ts +10 -2
  403. package/src/renderers/dom/input-host.tsx +6 -1
  404. package/src/renderers/dom/styles.css +13 -0
  405. package/src/renderers/electrobun/bun/desktop/backend.ts +5 -1
  406. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  407. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
  408. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  409. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  410. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  411. package/src/renderers/electrobun/view/main.tsx +4 -1
  412. package/src/renderers/opentui/input-host.tsx +6 -1
  413. package/src/renderers/opentui/start.tsx +9 -0
  414. package/src/shares/portable-layout.ts +16 -6
  415. package/src/sources/gloomberb-cloud/index.ts +19 -10
  416. package/src/sources/gloomberb-cloud/normalizers.ts +4 -1
  417. package/src/sources/listing-symbols.ts +11 -5
  418. package/src/sources/provider-errors.ts +88 -3
  419. package/src/sources/provider-router/cached-routes.ts +8 -4
  420. package/src/sources/provider-router/financial-routes.ts +11 -5
  421. package/src/sources/provider-router/financials.ts +42 -15
  422. package/src/sources/provider-router/history.ts +27 -11
  423. package/src/sources/provider-router/index.ts +1 -1
  424. package/src/sources/provider-router/primary.ts +23 -5
  425. package/src/sources/sec-edgar.ts +68 -8
  426. package/src/sync/core-contributors.ts +4 -3
  427. package/src/test-support/cli-context.ts +2 -1
  428. package/src/test-support/data-provider.ts +34 -0
  429. package/src/test-support/shell-pane-keys.tsx +1 -0
  430. package/src/theme/series-colors.ts +46 -0
  431. package/src/theme/themes.ts +149 -0
  432. package/src/tickers/search/index.ts +22 -2
  433. package/src/tickers/search/ranking.ts +45 -1
  434. package/src/tickers/search/types.ts +3 -1
  435. package/src/time-series/resolve.ts +1 -14
  436. package/src/types/config.ts +61 -1
  437. package/src/types/headless.ts +11 -0
  438. package/src/types/instrument.ts +8 -0
  439. package/src/types/plugin.ts +49 -3
  440. package/src/ui/host.tsx +8 -4
  441. package/src/ui/index.tsx +2 -0
  442. package/src/utils/cli-output.ts +14 -1
  443. package/src/utils/exchanges.ts +13 -0
  444. package/src/utils/format.ts +2 -1
  445. package/src/utils/fx-market-hours.ts +34 -0
  446. package/src/utils/option-expiry.ts +126 -0
  447. package/src/utils/price-history.ts +39 -6
  448. package/src/utils/rpc-timeout-error.ts +6 -2
  449. package/src/utils/sec-eps-basis.ts +159 -30
  450. package/src/utils/sec.ts +38 -34
  451. package/src/utils/throttled-fetch.ts +51 -22
@@ -1,8 +1,8 @@
1
1
  import type { HeadlessPaneDefinition } from "../../../types/plugin";
2
2
  import { resolveHeadlessInstrument } from "../shared/headless-market-data";
3
- import { loadMacroDayHistory } from "./client";
3
+ import { loadMacroDayHistory, loadMacroReleases } from "./client";
4
4
  import { projectMacroDays, type MacroDayStats } from "./model";
5
- import { MACRO_EVENT_KINDS, MACRO_EVENT_LABELS, MACRO_RELEASE_BASIS } from "./releases";
5
+ import { MACRO_EVENT_KINDS, MACRO_EVENT_LABELS, MACRO_RELEASE_SOURCES, staleMacroReleasesNotice } from "./releases";
6
6
 
7
7
  // A mean that rounds to zero reads 0.00%, never -0.00%.
8
8
  const percent = (value: unknown) => typeof value !== "number" ? "--" : /[1-9]/.test((value * 100).toFixed(2)) ? `${(value * 100).toFixed(2)}%` : "0.00%";
@@ -11,12 +11,12 @@ const share = (value: unknown) => typeof value === "number" ? `${Math.round(valu
11
11
 
12
12
  export const macroDayHeadless: HeadlessPaneDefinition<"bundle"> = {
13
13
  shape: "bundle", argument: { kind: "ticker", description: "Ticker" },
14
- // Dated by the maintained release list as much as by the closes, so no session schedule applies.
14
+ // Dated by the release list (releasesThrough) as much as by the closes, so no session schedule applies.
15
15
  freshness: { status: "not-a-feed", basis: "release-day study on daily closes" },
16
16
  describe: (args) => `MDAY ${args.symbols[0] ?? ""}`,
17
17
  discovery: { screenshotReadiness: "live-dom", limitations: [
18
18
  "US listings; close-to-close on the release day.",
19
- `Published CPI, jobs and FOMC days through ${MACRO_RELEASE_BASIS.coveredThrough}.`,
19
+ "Published CPI, jobs and FOMC days; sessions after the list's last covered day (releasesThrough) are left out.",
20
20
  ] },
21
21
  options: [
22
22
  { key: "release", type: "enum", values: [{ value: "all" }, ...MACRO_EVENT_KINDS.map((value) => ({ value }))], defaultValue: "all",
@@ -25,9 +25,11 @@ export const macroDayHeadless: HeadlessPaneDefinition<"bundle"> = {
25
25
  ],
26
26
  async load(args, ctx) {
27
27
  const instrument = await resolveHeadlessInstrument(ctx, args.symbols[0]!);
28
- const history = await loadMacroDayHistory({ instrument, signal: ctx.signal }, ctx.marketData);
28
+ const [history, releases] = await Promise.all([loadMacroDayHistory({ instrument, signal: ctx.signal }, ctx.marketData), loadMacroReleases()]);
29
29
  ctx.signal.throwIfAborted();
30
- const model = projectMacroDays(history.history, { symbol: instrument.symbol, lookbackYears: Number(args.options.lookbackYears) || 5 });
30
+ const model = projectMacroDays(history.history, { symbol: instrument.symbol, lookbackYears: Number(args.options.lookbackYears) || 5,
31
+ releases: releases.releases, coveredThrough: releases.coveredThrough });
32
+ const releasesNotice = staleMacroReleasesNotice(releases);
31
33
  const row = (label: string, stats: MacroDayStats) => ({ label, ...stats });
32
34
  const release = String(args.options.release ?? "all");
33
35
  const events = release === "all" ? model.events : model.events.filter((event) => event.kind === release);
@@ -42,11 +44,12 @@ export const macroDayHeadless: HeadlessPaneDefinition<"bundle"> = {
42
44
  { key: "session", header: "Session" }, { key: "move", header: "Move", format: percent }, { key: "multiple", header: "vs normal", format: multiple }],
43
45
  rows: events.map((event) => ({ ...event, event: MACRO_EVENT_LABELS[event.kind] })) },
44
46
  ],
45
- complete: !history.stale && !history.error && model.events.length > 0,
47
+ complete: !history.stale && !history.error && !releasesNotice && model.events.length > 0,
46
48
  unavailableSymbols: model.events.length ? [] : [instrument.symbol],
47
49
  errors: history.error ? [history.error] : [],
50
+ ...(releasesNotice ? { notes: [releasesNotice] } : {}),
48
51
  metadata: { unit: "decimal return, close to close on the release day", start: model.start, asOf: model.asOf,
49
- releasesThrough: MACRO_RELEASE_BASIS.coveredThrough, releaseSources: MACRO_RELEASE_BASIS.sources,
52
+ releasesThrough: releases.coveredThrough, releaseSources: Object.values(MACRO_RELEASE_SOURCES),
50
53
  stale: history.stale, fetchedAt: history.fetchedAt, methodology: "docs/research-data.md#macro-day-reaction" },
51
54
  };
52
55
  },
@@ -1,5 +1,6 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
2
  import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
3
+ import { macroReleaseCache } from "./client";
3
4
  import { macroDayHeadless } from "./headless";
4
5
  import { LOOKBACK_OPTIONS, MacroDayPane } from "./pane";
5
6
 
@@ -18,4 +19,6 @@ export const macroDayModule: PluginModule = {
18
19
  keywords: ["mday", "macro day", "cpi day", "fomc day", "jobs day", "nfp", "payrolls", "event study", "release reaction"],
19
20
  shortcut: "MDAY", publicShare: true,
20
21
  }), headless: macroDayHeadless }],
22
+ setup(ctx) { macroReleaseCache.attach(ctx.persistence); },
23
+ dispose() { macroReleaseCache.reset(); },
21
24
  };
@@ -1,6 +1,6 @@
1
1
  import type { PricePoint } from "../../../types/financials";
2
2
  import { zonedDateTimeParts } from "../../../utils/zoned-date-time";
3
- import { MACRO_EVENT_KINDS, MACRO_RELEASE_BASIS, macroReleases, type MacroEventKind, type MacroRelease } from "./releases";
3
+ import { MACRO_EVENT_KINDS, type MacroEventKind, type MacroRelease } from "./releases";
4
4
 
5
5
  const DAY_MS = 86_400_000;
6
6
  /** Longest calendar gap between two closes that is still one session apart: Thursday to Monday over a Friday holiday. */
@@ -71,10 +71,11 @@ function stats(moves: number[], normalAbs: number | null): MacroDayStats {
71
71
  export function projectMacroDays(history: readonly PricePoint[], options: {
72
72
  symbol: string;
73
73
  lookbackYears: number;
74
- releases?: readonly MacroRelease[];
75
- coveredThrough?: string;
74
+ releases: readonly MacroRelease[];
75
+ /** The release list's last covered day. */
76
+ coveredThrough: string;
76
77
  }): MacroDayModel {
77
- const coveredThrough = options.coveredThrough ?? MACRO_RELEASE_BASIS.coveredThrough;
78
+ const { coveredThrough } = options;
78
79
  const byDate = new Map<string, number>();
79
80
  for (const point of history) {
80
81
  const time = new Date(point.date).getTime();
@@ -90,7 +91,11 @@ export function projectMacroDays(history: readonly PricePoint[], options: {
90
91
  // Sessions after the published lists end may be unlisted release days, so they are left out of both sides.
91
92
  const end = last < coveredThrough ? last : coveredThrough;
92
93
  const startYear = Number(end.slice(0, 4)) - Math.max(1, options.lookbackYears);
93
- const start = `${startYear}${end.slice(4)}`;
94
+ const nominalStart = `${startYear}${end.slice(4)}`;
95
+ // The history can begin after the nominal start (daily bars reach back five years from today, the list's end is older).
96
+ // The first bar has no close before it, so the window opens there: a release before it falls outside the stated start.
97
+ const firstSession = sessions[0]!;
98
+ const start = firstSession > nominalStart ? firstSession : nominalStart;
94
99
  /** Session date -> close-to-close move from the session before, when the two are one session apart. */
95
100
  const moves = new Map<string, number>();
96
101
  for (let index = 1; index < sessions.length; index++) {
@@ -101,7 +106,7 @@ export function projectMacroDays(history: readonly PricePoint[], options: {
101
106
 
102
107
  const reactionSessions = new Set<string>();
103
108
  const found: Omit<MacroDayEvent, "multiple">[] = [];
104
- for (const release of options.releases ?? macroReleases()) {
109
+ for (const release of options.releases) {
105
110
  if (release.date <= start || release.date > end || !isWeekday(release.date)) continue;
106
111
  const at = sessions.findIndex((date) => date >= release.date);
107
112
  if (at < 1) continue;
@@ -9,9 +9,9 @@ import { useAutoRefresh } from "../../../react/auto-refresh";
9
9
  import { useThemeColors } from "../../../theme/theme-context";
10
10
  import type { PaneProps } from "../../../types/plugin";
11
11
  import { Box } from "../../../ui";
12
- import { loadMacroDayHistory } from "./client";
12
+ import { loadMacroDayHistory, loadMacroReleases } from "./client";
13
13
  import { projectMacroDays, type MacroDayEvent, type MacroDayModel } from "./model";
14
- import { MACRO_EVENT_KINDS, MACRO_EVENT_LABELS, type MacroEventKind } from "./releases";
14
+ import { MACRO_EVENT_KINDS, MACRO_EVENT_LABELS, staleMacroReleasesNotice, type MacroEventKind } from "./releases";
15
15
 
16
16
  export const LOOKBACK_OPTIONS = [{ value: "1", label: "1Y" }, { value: "2", label: "2Y" }, { value: "3", label: "3Y" }, { value: "5", label: "5Y" }];
17
17
  const RELEASE_OPTIONS = [{ value: "all", label: "All" }, ...MACRO_EVENT_KINDS.map((kind) => ({ value: kind, label: MACRO_EVENT_LABELS[kind] }))];
@@ -64,22 +64,26 @@ export function MacroDayPane({ width, height, focused }: PaneProps) {
64
64
  const loader = useCallback(async (force: boolean) => {
65
65
  controller.current?.abort();
66
66
  controller.current = new AbortController();
67
- const snapshot = await loadMacroDayHistory({ instrument: instrument!, forceRefresh: force, signal: controller.current.signal });
67
+ const [snapshot, releases] = await Promise.all([
68
+ loadMacroDayHistory({ instrument: instrument!, forceRefresh: force, signal: controller.current.signal }),
69
+ loadMacroReleases({ force }),
70
+ ]);
68
71
  if (!snapshot.history.length && snapshot.error) throw new Error(snapshot.error);
69
- return snapshot;
72
+ return { ...snapshot, releases };
70
73
  }, [instrumentKey]);
71
74
  const history = useAsyncResource(instrument ? loader : null);
72
75
  useEffect(() => () => controller.current?.abort(), [loader]);
73
76
  useAutoRefresh(history.updatedAt, history.load);
74
77
  usePaneRefreshKey(() => { void history.reload(); }, { focused });
75
78
 
76
- const model = useMemo(() => history.data ? projectMacroDays(history.data.history, { symbol: symbol ?? "", lookbackYears: Number(lookback) || 5 }) : null,
77
- [history.data, lookback, symbol]);
79
+ const model = useMemo(() => history.data ? projectMacroDays(history.data.history, { symbol: symbol ?? "", lookbackYears: Number(lookback) || 5,
80
+ releases: history.data.releases.releases, coveredThrough: history.data.releases.coveredThrough }) : null, [history.data, lookback, symbol]);
81
+ const releasesNotice = history.data ? staleMacroReleasesNotice(history.data.releases) : null;
78
82
  const rows = useMemo(() => model ? model.events.filter((event) => release === "all" || event.kind === release) : [], [model, release]);
79
83
  const stats = useMemo(() => model?.events.length ? figures(model, release) : [], [model, release]);
80
84
 
81
85
  usePaneNoticeFooter({ registrationId: "macro-day-notices", focused,
82
- notices: [...new Set([identityError, history.error, history.data?.error].filter((value): value is string => !!value))] });
86
+ notices: [...new Set([identityError, history.error, history.data?.error, releasesNotice].filter((value): value is string => !!value))] });
83
87
  usePaneFooter("macro-day", () => ({ info: [
84
88
  ...(history.loading ? [{ id: "loading", parts: [{ text: "loading history", tone: "muted" as const }] }] : []),
85
89
  ...(history.data?.stale ? [{ id: "stale", parts: [{ text: "stale history", tone: "warning" as const }] }] : []),
@@ -102,7 +106,7 @@ export function MacroDayPane({ width, height, focused }: PaneProps) {
102
106
  { id: "release", label: "Release", value: release, options: RELEASE_OPTIONS, onChange: setRelease, inline: true },
103
107
  { id: "lookback", label: "Lookback", value: String(lookback), options: LOOKBACK_OPTIONS, onChange: setLookback },
104
108
  ]} />
105
- {!symbol ? <EmptyState title="Choose a ticker." /> : <PaneStatusBody subject="macro-day moves" loading={history.loading && !model}
109
+ {!symbol ? <EmptyState title="Select a ticker." /> : <PaneStatusBody subject="macro-day moves" loading={history.loading && !model}
106
110
  error={!model ? history.error ?? identityError ?? null : null} empty={!!model && !model.events.length}>
107
111
  {model ? <>
108
112
  <StatGrid items={stats} width={width} />
@@ -0,0 +1,40 @@
1
+ // Generated by scripts/update-macro-releases.ts from the publishers' archives
2
+ // (bun run macro-releases:update). Dates are New York calendar days.
3
+ export const MACRO_RELEASE_TABLE: {
4
+ checkedAt: string;
5
+ coveredThrough: string;
6
+ days: Record<"cpi" | "jobs" | "fomc", Record<number, readonly string[]>>;
7
+ } = {
8
+ checkedAt: "2026-10-10",
9
+ /** The last day the lists are complete for: later sessions are left out, release day or not. */
10
+ coveredThrough: "2026-10-09",
11
+ days: {
12
+ // BLS Consumer Price Index, 8:30 ET.
13
+ cpi: {
14
+ 2021: ["01-13", "02-10", "03-10", "04-13", "05-12", "06-10", "07-13", "08-11", "09-14", "10-13", "11-10", "12-10"],
15
+ 2022: ["01-12", "02-10", "03-10", "04-12", "05-11", "06-10", "07-13", "08-10", "09-13", "10-13", "11-10", "12-13"],
16
+ 2023: ["01-12", "02-14", "03-14", "04-12", "05-10", "06-13", "07-12", "08-10", "09-13", "10-12", "11-14", "12-12"],
17
+ 2024: ["01-11", "02-13", "03-12", "04-10", "05-15", "06-12", "07-11", "08-14", "09-11", "10-10", "11-13", "12-11"],
18
+ 2025: ["01-15", "02-12", "03-12", "04-10", "05-13", "06-11", "07-15", "08-12", "09-11", "10-24", "12-18"],
19
+ 2026: ["01-13", "02-13", "03-11", "04-10", "05-12", "06-10", "07-14", "08-12", "09-11"],
20
+ },
21
+ // BLS Employment Situation (nonfarm payrolls), 8:30 ET.
22
+ jobs: {
23
+ 2021: ["01-08", "02-05", "03-05", "04-02", "05-07", "06-04", "07-02", "08-06", "09-03", "10-08", "11-05", "12-03"],
24
+ 2022: ["01-07", "02-04", "03-04", "04-01", "05-06", "06-03", "07-08", "08-05", "09-02", "10-07", "11-04", "12-02"],
25
+ 2023: ["01-06", "02-03", "03-10", "04-07", "05-05", "06-02", "07-07", "08-04", "09-01", "10-06", "11-03", "12-08"],
26
+ 2024: ["01-05", "02-02", "03-08", "04-05", "05-03", "06-07", "07-05", "08-02", "09-06", "10-04", "11-01", "12-06"],
27
+ 2025: ["01-10", "02-07", "03-07", "04-04", "05-02", "06-06", "07-03", "08-01", "09-05", "11-20", "12-16"],
28
+ 2026: ["01-09", "02-11", "03-06", "04-03", "05-08", "06-05", "07-02", "08-07", "09-04", "10-02"],
29
+ },
30
+ // FOMC statement days (the meeting's last day), 14:00 ET. The August 2025 notation vote is not a meeting.
31
+ fomc: {
32
+ 2021: ["01-27", "03-17", "04-28", "06-16", "07-28", "09-22", "11-03", "12-15"],
33
+ 2022: ["01-26", "03-16", "05-04", "06-15", "07-27", "09-21", "11-02", "12-14"],
34
+ 2023: ["02-01", "03-22", "05-03", "06-14", "07-26", "09-20", "11-01", "12-13"],
35
+ 2024: ["01-31", "03-20", "05-01", "06-12", "07-31", "09-18", "11-07", "12-18"],
36
+ 2025: ["01-29", "03-19", "05-07", "06-18", "07-30", "09-17", "10-29", "12-10"],
37
+ 2026: ["01-28", "03-18", "04-29", "06-17", "07-29", "09-16"],
38
+ },
39
+ },
40
+ };
@@ -1,58 +1,76 @@
1
1
  // Published US release days, not a rule engine: the dates each release came
2
2
  // out, read from the publishers' own archives. A release cancelled or moved (the
3
- // 2025 shutdown) is as published. Dates are New York calendar days.
3
+ // 2025 shutdown) is as published, and a scheduled date is not a release day
4
+ // until it is out. Dates are New York calendar days.
5
+ import { zonedDateKey } from "../../../utils/zoned-date-time";
6
+ import { MACRO_RELEASE_TABLE } from "./release-table";
7
+
4
8
  export type MacroEventKind = "cpi" | "jobs" | "fomc";
5
9
 
6
10
  export const MACRO_EVENT_KINDS: readonly MacroEventKind[] = ["cpi", "jobs", "fomc"];
7
11
 
8
12
  export const MACRO_EVENT_LABELS: Record<MacroEventKind, string> = { cpi: "CPI", jobs: "Jobs", fomc: "FOMC" };
9
13
 
10
- const RELEASES: Record<MacroEventKind, Record<number, readonly string[]>> = {
11
- // BLS Consumer Price Index, 8:30 ET.
12
- cpi: {
13
- 2021: ["01-13", "02-10", "03-10", "04-13", "05-12", "06-10", "07-13", "08-11", "09-14", "10-13", "11-10", "12-10"],
14
- 2022: ["01-12", "02-10", "03-10", "04-12", "05-11", "06-10", "07-13", "08-10", "09-13", "10-13", "11-10", "12-13"],
15
- 2023: ["01-12", "02-14", "03-14", "04-12", "05-10", "06-13", "07-12", "08-10", "09-13", "10-12", "11-14", "12-12"],
16
- 2024: ["01-11", "02-13", "03-12", "04-10", "05-15", "06-12", "07-11", "08-14", "09-11", "10-10", "11-13", "12-11"],
17
- 2025: ["01-15", "02-12", "03-12", "04-10", "05-13", "06-11", "07-15", "08-12", "09-11", "10-24", "12-18"],
18
- 2026: ["01-13", "02-13", "03-11", "04-10", "05-12", "06-10", "07-14", "08-12", "09-11"],
19
- },
20
- // BLS Employment Situation (nonfarm payrolls), 8:30 ET.
21
- jobs: {
22
- 2021: ["01-08", "02-05", "03-05", "04-02", "05-07", "06-04", "07-02", "08-06", "09-03", "10-08", "11-05", "12-03"],
23
- 2022: ["01-07", "02-04", "03-04", "04-01", "05-06", "06-03", "07-08", "08-05", "09-02", "10-07", "11-04", "12-02"],
24
- 2023: ["01-06", "02-03", "03-10", "04-07", "05-05", "06-02", "07-07", "08-04", "09-01", "10-06", "11-03", "12-08"],
25
- 2024: ["01-05", "02-02", "03-08", "04-05", "05-03", "06-07", "07-05", "08-02", "09-06", "10-04", "11-01", "12-06"],
26
- 2025: ["01-10", "02-07", "03-07", "04-04", "05-02", "06-06", "07-03", "08-01", "09-05", "11-20", "12-16"],
27
- 2026: ["01-09", "02-11", "03-06", "04-03", "05-08", "06-05", "07-02", "08-07", "09-04"],
28
- },
29
- // FOMC statement days (the meeting's last day), 14:00 ET. The August 2025 notation vote is not a meeting.
30
- fomc: {
31
- 2021: ["01-27", "03-17", "04-28", "06-16", "07-28", "09-22", "11-03", "12-15"],
32
- 2022: ["01-26", "03-16", "05-04", "06-15", "07-27", "09-21", "11-02", "12-14"],
33
- 2023: ["02-01", "03-22", "05-03", "06-14", "07-26", "09-20", "11-01", "12-13"],
34
- 2024: ["01-31", "03-20", "05-01", "06-12", "07-31", "09-18", "11-07", "12-18"],
35
- 2025: ["01-29", "03-19", "05-07", "06-18", "07-30", "09-17", "10-29", "12-10"],
36
- 2026: ["01-28", "03-18", "04-29", "06-17", "07-29", "09-16"],
37
- },
14
+ /** BLS news release archives for CPI and the Employment Situation, and the Federal Reserve's meeting calendar. */
15
+ export const MACRO_RELEASE_SOURCES: Record<MacroEventKind, string> = {
16
+ cpi: "https://www.bls.gov/bls/news-release/cpi.htm",
17
+ jobs: "https://www.bls.gov/bls/news-release/empsit.htm",
18
+ fomc: "https://www.federalreserve.gov/monetarypolicy/fomccalendars.htm",
38
19
  };
39
20
 
40
- export const MACRO_RELEASE_BASIS = {
41
- checkedAt: "2026-10-04",
42
- /** The last day the lists are complete for: later sessions are left out, release day or not. */
43
- coveredThrough: "2026-10-01",
44
- sources: [
45
- "https://www.bls.gov/bls/news-release/cpi.htm",
46
- "https://www.bls.gov/bls/news-release/empsit.htm",
47
- "https://www.federalreserve.gov/monetarypolicy/fomccalendars.htm",
48
- ],
49
- } as const;
21
+ /** A list more than this many days behind today says so: a release day may be missing from its end. */
22
+ export const MACRO_RELEASES_STALE_DAYS = 14;
50
23
 
51
24
  export interface MacroRelease { kind: MacroEventKind; date: string }
52
25
 
53
- /** Every published release day, oldest first. */
54
- export function macroReleases(): MacroRelease[] {
55
- return MACRO_EVENT_KINDS.flatMap((kind) => Object.entries(RELEASES[kind]).flatMap(([year, days]) =>
56
- days.map((day) => ({ kind, date: `${year}-${day}` }))))
57
- .sort((left, right) => left.date.localeCompare(right.date) || left.kind.localeCompare(right.kind));
26
+ export interface MacroReleaseList {
27
+ /** Oldest first. */
28
+ releases: MacroRelease[];
29
+ /** The last day the list is complete for: later sessions are left out, release day or not. */
30
+ coveredThrough: string;
31
+ }
32
+
33
+ const DAY_MS = 86_400_000;
34
+ const isDate = (value: unknown): value is string => typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value)
35
+ && new Date(`${value}T00:00:00Z`).toISOString().startsWith(value);
36
+ const byDate = (left: MacroRelease, right: MacroRelease) => left.date.localeCompare(right.date) || left.kind.localeCompare(right.kind);
37
+
38
+ /** The list the app ships with: the floor whenever the server's list is missing or older. */
39
+ export function bundledMacroReleases(): MacroReleaseList {
40
+ const releases = MACRO_EVENT_KINDS.flatMap((kind) => Object.entries(MACRO_RELEASE_TABLE.days[kind]).flatMap(([year, days]) =>
41
+ days.map((day) => ({ kind, date: `${year}-${day}` }))));
42
+ return { releases: releases.sort(byDate), coveredThrough: MACRO_RELEASE_TABLE.coveredThrough };
43
+ }
44
+
45
+ /**
46
+ * The served list extends the floor past its end; the floor keeps its own
47
+ * days up to there. A served list that breaks the contract, or ends no later
48
+ * than the floor, adds nothing.
49
+ */
50
+ export function mergeMacroReleases(floor: MacroReleaseList, served: unknown): MacroReleaseList {
51
+ const payload = served as { coveredThrough?: unknown; releases?: Partial<Record<MacroEventKind, unknown>> } | null;
52
+ const coveredThrough = payload?.coveredThrough;
53
+ if (!isDate(coveredThrough) || coveredThrough <= floor.coveredThrough) return floor;
54
+ const later: MacroRelease[] = [];
55
+ for (const kind of MACRO_EVENT_KINDS) {
56
+ const dates = payload?.releases?.[kind];
57
+ if (!Array.isArray(dates) || !dates.every(isDate)) return floor;
58
+ for (const date of new Set(dates)) {
59
+ if (date > floor.coveredThrough && date <= coveredThrough) later.push({ kind, date });
60
+ }
61
+ }
62
+ return { releases: [...floor.releases, ...later].sort(byDate), coveredThrough };
63
+ }
64
+
65
+ /** Whole days from the list's last covered day to today in New York. */
66
+ export function macroReleasesBehind(coveredThrough: string, now = Date.now()): number {
67
+ const today = zonedDateKey(now, "America/New_York");
68
+ return Math.round((Date.parse(`${today}T00:00:00Z`) - Date.parse(`${coveredThrough}T00:00:00Z`)) / DAY_MS);
69
+ }
70
+
71
+ /** The pane footer and the report notes say so once the list is old enough to miss a release day. */
72
+ export function staleMacroReleasesNotice(list: MacroReleaseList, now = Date.now()): string | null {
73
+ return macroReleasesBehind(list.coveredThrough, now) > MACRO_RELEASES_STALE_DAYS
74
+ ? `Release days are listed through ${list.coveredThrough}; later sessions are left out.`
75
+ : null;
58
76
  }
@@ -1,6 +1,6 @@
1
1
  import { apiClient } from "../../../api-client";
2
2
  import type { MarketHeatmapResult, MarketHeatmapUniverseId } from "../../../api-client/market-discovery";
3
- export type { MarketHeatmapResult, MarketHeatmapUniverseId } from "../../../api-client/market-discovery";
3
+ export type { MarketHeatmapUniverseId } from "../../../api-client/market-discovery";
4
4
 
5
5
  export const MARKET_HEATMAP_UNIVERSES = [
6
6
  { id: "us-equity", label: "US Stocks" },
@@ -10,12 +10,30 @@ export const MARKET_HEATMAP_UNIVERSES = [
10
10
  export const MARKET_HEATMAP_REQUEST_COUNT: Record<MarketHeatmapUniverseId, number> = { "us-equity": 500, "us-etf": 160 };
11
11
  export interface MarketHeatmapFetchOptions { count?: number; forceRefresh?: boolean; cache?: boolean; }
12
12
  export interface MarketHeatmapSources { client?: Pick<typeof apiClient, "getMarketHeatmap">; }
13
+
14
+ /** A board as this client holds it: the server's snapshot, and what the request behind it was. */
15
+ export interface MarketHeatmapBoard extends MarketHeatmapResult {
16
+ /**
17
+ * When the response arrived and passed validation. `fetchedAt` is when the
18
+ * server put the snapshot together, which a check a minute later can
19
+ * return unchanged. A board served from the cache keeps the time of the
20
+ * response it came from.
21
+ */
22
+ checkedAt: number;
23
+ /**
24
+ * How many of the largest listings the board covers: the count asked for,
25
+ * or fewer when the server serves fewer. A company listed twice is one
26
+ * listing of them but one tile, so `assets` can be shorter.
27
+ */
28
+ topCount: number;
29
+ }
30
+
13
31
  const DEFAULT_COUNT = 80;
14
32
  /** The most names one board asks for; a server that serves fewer answers with its own maximum. */
15
33
  const MAX_MARKET_HEATMAP_COUNT = 500;
16
34
  const CACHE_TTL_MS = 60_000;
17
- const activeFetches = new Map<string, Promise<MarketHeatmapResult>>();
18
- const memoryCache = new Map<string, { expiresAt: number; result: MarketHeatmapResult }>();
35
+ const activeFetches = new Map<string, Promise<MarketHeatmapBoard>>();
36
+ const memoryCache = new Map<string, { expiresAt: number; result: MarketHeatmapBoard }>();
19
37
 
20
38
  /**
21
39
  * One tile per symbol. A company listed on two exchanges (KHC on NASDAQ and
@@ -37,7 +55,7 @@ export function dedupeHeatmapAssets<T extends { symbol: string; size: number | n
37
55
  return result;
38
56
  }
39
57
 
40
- async function loadMarketHeatmap(universe: MarketHeatmapUniverseId, count: number, sources?: MarketHeatmapSources): Promise<MarketHeatmapResult> {
58
+ async function loadMarketHeatmap(universe: MarketHeatmapUniverseId, count: number, sources?: MarketHeatmapSources): Promise<MarketHeatmapBoard> {
41
59
  const response = await (sources?.client ?? apiClient).getMarketHeatmap(universe, count);
42
60
  if (!response.data || !Array.isArray(response.data.assets) || response.data.universe !== universe
43
61
  || (response.status !== "success" && response.status !== "partial")) throw new Error("Market heatmap unavailable");
@@ -45,6 +63,8 @@ async function loadMarketHeatmap(universe: MarketHeatmapUniverseId, count: numbe
45
63
  ...response.data,
46
64
  stale: response.stale === true || response.data.stale === true,
47
65
  assets: dedupeHeatmapAssets(response.data.assets).slice(0, count),
66
+ checkedAt: Date.now(),
67
+ topCount: Math.min(count, response.data.assets.length),
48
68
  };
49
69
  }
50
70
 
@@ -52,7 +72,7 @@ export async function fetchMarketHeatmap(
52
72
  universe: MarketHeatmapUniverseId,
53
73
  options?: MarketHeatmapFetchOptions,
54
74
  sources?: MarketHeatmapSources,
55
- ): Promise<MarketHeatmapResult> {
75
+ ): Promise<MarketHeatmapBoard> {
56
76
  const count = Math.max(1, Math.min(MAX_MARKET_HEATMAP_COUNT, Math.round(options?.count ?? DEFAULT_COUNT)));
57
77
  const cacheKey = `${universe}:${count}`;
58
78
  const useCache = options?.cache !== false && !sources?.client;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * What `gloomberb shot HM` certifies: the tab the heat map drew, how many tiles
3
+ * and sectors are plotted, and whether its snapshot had loaded. The heat map has
4
+ * no table rows or labeled values, so this is how a capture counts as data.
5
+ */
6
+ import type { PaneScreenshotEvidenceHook } from "../../../cli/pane-functions/screenshot-evidence";
7
+ import { useRemoteUiNode } from "../../../remote/semantic-tree";
8
+ import { isRecord } from "../../../utils/guards";
9
+ import { heatmapMove } from "./model";
10
+ import { heatmapTabId, type HeatmapBoardAsset, type HeatmapTabId } from "./portfolio";
11
+
12
+ interface MarketHeatmapEvidence {
13
+ kind: "market-heatmap";
14
+ version: 1;
15
+ complete: boolean;
16
+ plottedValueCount: number;
17
+ tab: HeatmapTabId;
18
+ tiles: number;
19
+ sectors: number;
20
+ /** Tiles colored by a move, not left grey for want of one. */
21
+ moved: number;
22
+ }
23
+
24
+ interface HeatmapEvidenceInput {
25
+ tab: HeatmapTabId;
26
+ tiles: readonly { item: { id: string; data: Pick<HeatmapBoardAsset, "sector"> } }[];
27
+ /** The assets the tiles are colored by. */
28
+ assets: readonly HeatmapBoardAsset[];
29
+ /** Whether the tiles are grouped into sectors, rather than laid out flat. */
30
+ grouped: boolean;
31
+ /** A snapshot is on its way. */
32
+ loading: boolean;
33
+ /** The tiles are a retained board: its refresh failed, or the server served a stale snapshot. */
34
+ retained: boolean;
35
+ }
36
+
37
+ export function marketHeatmapEvidence({ tab, tiles, assets, grouped, loading, retained }: HeatmapEvidenceInput): MarketHeatmapEvidence {
38
+ const drawn = new Set(tiles.map((tile) => tile.item.id));
39
+ const sectors = grouped
40
+ ? new Set(tiles.flatMap((tile) => (tile.item.data.sector ? [tile.item.data.sector] : []))).size
41
+ : 0;
42
+ const moved = assets.filter((asset) => drawn.has(asset.symbol) && heatmapMove(asset) != null).length;
43
+ return {
44
+ kind: "market-heatmap",
45
+ version: 1,
46
+ complete: !loading && !retained && moved > 0,
47
+ plottedValueCount: tiles.length,
48
+ tab,
49
+ tiles: tiles.length,
50
+ sectors,
51
+ moved,
52
+ };
53
+ }
54
+
55
+ export function useMarketHeatmapEvidence(input: HeatmapEvidenceInput) {
56
+ useRemoteUiNode({
57
+ role: "chart-data",
58
+ label: "Rendered market heatmap",
59
+ // The capture waits for a board that is still loading.
60
+ getMetadata: () => ({ ...marketHeatmapEvidence(input), ready: !input.loading }),
61
+ });
62
+ }
63
+
64
+ const isCount = (value: unknown): value is number => Number.isSafeInteger(value) && (value as number) >= 0;
65
+
66
+ export const marketHeatmapScreenshotEvidence: PaneScreenshotEvidenceHook<MarketHeatmapEvidence> = {
67
+ paneId: "market-heatmap",
68
+ kind: "market-heatmap",
69
+ label: "market heatmap",
70
+ read(value) {
71
+ if (!isRecord(value) || value.kind !== "market-heatmap" || value.version !== 1 || typeof value.complete !== "boolean"
72
+ || !isCount(value.plottedValueCount) || value.tiles !== value.plottedValueCount
73
+ || value.tab !== heatmapTabId(typeof value.tab === "string" ? value.tab : null)
74
+ || !isCount(value.sectors) || value.sectors > value.tiles
75
+ || !isCount(value.moved) || value.moved > value.tiles
76
+ || (value.complete && value.moved === 0)) return null;
77
+ return value as unknown as MarketHeatmapEvidence;
78
+ },
79
+ mismatches(evidence, { resolved, payload }) {
80
+ const settings = payload.config.layout.instances.find((entry) => entry.instanceId === payload.paneId)?.settings;
81
+ const asked = settings?.universe ?? resolved.options.universe;
82
+ return evidence.tab === heatmapTabId(typeof asked === "string" ? asked : null) ? [] : ["Heat map tab differs from the request"];
83
+ },
84
+ unavailable(evidence) {
85
+ return evidence?.complete ? [] : ["market heatmap"];
86
+ },
87
+ symbols() {
88
+ return [];
89
+ },
90
+ };