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
@@ -5,6 +5,8 @@ import {
5
5
  type CloudEarningsTranscriptPayload,
6
6
  } from "../../../api-client";
7
7
  import { ApiRequestError } from "../../../api-client/errors";
8
+ import { listingAbroad, type IssuerListingParams, type ListingAbroad } from "../../../api-client/paths";
9
+ import { areDifferentCompanies } from "../../../sources/sec-registrant";
8
10
  import type { HeadlessPaneApiClient, PluginPersistence } from "../../../types/plugin";
9
11
 
10
12
  type EarningsCallsApiClient = Pick<
@@ -16,7 +18,8 @@ const LIST_KIND = "calls";
16
18
  const TRANSCRIPT_KIND = "transcript";
17
19
  const CACHE_SOURCE = "earnings-calls";
18
20
  const CACHE_SCHEMA_VERSION = 1;
19
- const LIST_CACHE_SCHEMA_VERSION = 2;
21
+ // Version 3: a listing abroad is keyed by its venue and company, not its symbol alone.
22
+ const LIST_CACHE_SCHEMA_VERSION = 3;
20
23
 
21
24
  /** The call list changes as new transcripts publish. */
22
25
  const LIST_CACHE_POLICY = {
@@ -94,19 +97,36 @@ function listOffset(value: number | undefined): number {
94
97
  return value !== undefined && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
95
98
  }
96
99
 
97
- function listKey(ticker: string | null, limit: number, offset: number): string {
98
- return offset > 0 ? JSON.stringify([ticker, limit, offset]) : JSON.stringify([ticker, limit]);
100
+ /** A listing abroad is its venue and company, so it never shares an entry with a US company of the same symbol. */
101
+ function listKey(ticker: string | null, abroad: ListingAbroad | null, limit: number, offset: number): string {
102
+ const identity = abroad ? [abroad.symbol, abroad.exchange, abroad.name ?? null] : ticker;
103
+ return offset > 0 ? JSON.stringify([identity, limit, offset]) : JSON.stringify([identity, limit]);
99
104
  }
100
105
 
106
+ /**
107
+ * A server that predates venue-aware lookups ignores the venue and lists the
108
+ * calls of the US company with the same symbol: AI in Paris would show C3.ai.
109
+ * Those calls are another company's, so they are dropped, and without the
110
+ * listing's company they cannot be told apart.
111
+ */
112
+ function listingCalls(calls: CloudEarningsCallPayload[], abroad: ListingAbroad | null): CloudEarningsCallPayload[] {
113
+ if (!abroad) return calls;
114
+ const name = abroad.name;
115
+ return name ? calls.filter((call) => !areDifferentCompanies(name, call.companyName)) : [];
116
+ }
117
+
118
+ type EarningsCallsLoadOptions = IssuerListingParams & { force?: boolean; limit?: number; offset?: number };
119
+
101
120
  export async function loadEarningsCallsWithClient(
102
121
  client: EarningsCallsApiClient,
103
122
  ticker: string | null,
104
- options?: { force?: boolean; limit?: number; offset?: number },
123
+ options?: EarningsCallsLoadOptions,
105
124
  ): Promise<EarningsCallsResult> {
106
125
  const normalizedTicker = ticker?.trim().toUpperCase() || null;
126
+ const abroad = normalizedTicker ? listingAbroad(normalizedTicker, options?.exchange, options?.name) : null;
107
127
  const limit = listLimit(options?.limit);
108
128
  const offset = listOffset(options?.offset);
109
- const key = listKey(normalizedTicker, limit, offset);
129
+ const key = listKey(normalizedTicker, abroad, limit, offset);
110
130
  const force = options?.force ?? false;
111
131
  // Only the first page is worth keeping. A later page is an artefact of how
112
132
  // far somebody scrolled, over a shelf that reorders as transcripts publish,
@@ -131,11 +151,12 @@ export async function loadEarningsCallsWithClient(
131
151
  const request = client
132
152
  .getCloudEarningsCalls({
133
153
  ticker: normalizedTicker ?? undefined,
154
+ ...(abroad ? { exchange: abroad.exchange, ...(abroad.name ? { name: abroad.name } : {}) } : {}),
134
155
  limit,
135
156
  ...(offset > 0 ? { offset } : {}),
136
157
  })
137
158
  .then((payload) => {
138
- const calls = payload.calls ?? [];
159
+ const calls = listingCalls(payload.calls ?? [], abroad);
139
160
  const value = { calls, pending: payload.pending === true, unknownTicker: payload.unknownTicker === true };
140
161
  // A list with calls still being produced changes by the minute, so it
141
162
  // is not worth keeping; a list of finished transcripts is.
@@ -190,7 +211,7 @@ export async function loadEarningsCallsWithClient(
190
211
 
191
212
  export function loadEarningsCalls(
192
213
  ticker: string | null,
193
- options?: { force?: boolean; limit?: number; offset?: number },
214
+ options?: EarningsCallsLoadOptions,
194
215
  ): Promise<EarningsCallsResult> {
195
216
  return loadEarningsCallsWithClient(apiClient, ticker, options);
196
217
  }
@@ -8,6 +8,7 @@ import type {
8
8
  HeadlessPaneLoadArgs,
9
9
  HeadlessRowsResult,
10
10
  } from "../../../types/plugin";
11
+ import { resolveHeadlessIssuerListing } from "../shared/headless-market-data";
11
12
  import {
12
13
  callStatusLabel,
13
14
  isPendingTranscript,
@@ -57,10 +58,10 @@ export interface EarningsCallsHeadlessDependencies {
57
58
  }
58
59
 
59
60
  const defaultDependencies: EarningsCallsHeadlessDependencies = {
60
- loadCalls: (ticker, limit, context) => loadEarningsCallsWithClient(
61
+ loadCalls: async (ticker, limit, context) => loadEarningsCallsWithClient(
61
62
  context.apiClient,
62
63
  ticker,
63
- { limit },
64
+ { limit, ...(ticker ? await resolveHeadlessIssuerListing(context, ticker) : {}) },
64
65
  ),
65
66
  loadTranscript: (callId, context) => loadTranscriptWithClient(context.apiClient, callId),
66
67
  };
@@ -33,6 +33,7 @@ import { nextHeaderSort, type SortDirection } from "../../../utils/sort-values";
33
33
  import { usePluginPaneState } from "../../runtime";
34
34
  import { ProWall, SignInWall } from "../cloud/auth-actions";
35
35
  import { usePlanAccess } from "../../../api-client/plan-access";
36
+ import { listingAbroad } from "../../../api-client/paths";
36
37
  import { useBoundTicker as useSymbolBinding } from "../shared/ticker-request";
37
38
  import {
38
39
  callStatusLabel,
@@ -177,7 +178,7 @@ interface EarningsCallsViewProps {
177
178
  }
178
179
 
179
180
  export function EarningsCallsPane({ focused, width, height }: EarningsCallsViewProps) {
180
- const { symbol, exchange } = useSymbolBinding();
181
+ const { symbol, exchange, ticker: boundTicker } = useSymbolBinding();
181
182
  const access = usePlanAccess();
182
183
 
183
184
  const [calls, setCalls] = useState<CloudEarningsCallPayload[]>([]);
@@ -227,6 +228,10 @@ export function EarningsCallsPane({ focused, width, height }: EarningsCallsViewP
227
228
  const [listPending, setListPending] = useState(false);
228
229
 
229
230
  const ticker = symbol ? symbol.toUpperCase() : null;
231
+ // A listing abroad asks for its own calls by venue and company, never a US namesake's.
232
+ const abroad = ticker ? listingAbroad(ticker, exchange, boundTicker?.metadata.name) : null;
233
+ const listingVenue = abroad?.exchange;
234
+ const listingName = abroad?.name;
230
235
 
231
236
  const fetchCalls = useCallback(
232
237
  (force: boolean) => {
@@ -234,7 +239,7 @@ export function EarningsCallsPane({ focused, width, height }: EarningsCallsViewP
234
239
  const request = ++listRequestVersion.current;
235
240
  setListStatus((current) => (current === "loaded" ? current : "loading"));
236
241
  setLoadingMore(false);
237
- loadEarningsCalls(ticker, { force, limit: CALL_PAGE_SIZE })
242
+ loadEarningsCalls(ticker, { force, limit: CALL_PAGE_SIZE, exchange: listingVenue, name: listingName })
238
243
  .then((result) => {
239
244
  if (request !== listRequestVersion.current) return;
240
245
  setCalls(result.calls);
@@ -261,7 +266,7 @@ export function EarningsCallsPane({ focused, width, height }: EarningsCallsViewP
261
266
  setListStatus("error");
262
267
  });
263
268
  },
264
- [ticker, access.emailVerified, access.hasProAccess],
269
+ [ticker, listingVenue, listingName, access.emailVerified, access.hasProAccess],
265
270
  );
266
271
 
267
272
  // Scrolling to the end of the shelf asks the server for the next page. A
@@ -271,7 +276,7 @@ export function EarningsCallsPane({ focused, width, height }: EarningsCallsViewP
271
276
  if (!access.emailVerified || !access.hasProAccess) return;
272
277
  const request = listRequestVersion.current;
273
278
  setLoadingMore(true);
274
- loadEarningsCalls(ticker, { limit: CALL_PAGE_SIZE, offset: nextOffset })
279
+ loadEarningsCalls(ticker, { limit: CALL_PAGE_SIZE, offset: nextOffset, exchange: listingVenue, name: listingName })
275
280
  .then((result) => {
276
281
  if (request !== listRequestVersion.current) return;
277
282
  setCalls((current) => appendNewCalls(current, result.calls));
@@ -289,7 +294,7 @@ export function EarningsCallsPane({ focused, width, height }: EarningsCallsViewP
289
294
  status: statusOf(error),
290
295
  });
291
296
  });
292
- }, [ticker, nextOffset, access.emailVerified, access.hasProAccess]);
297
+ }, [ticker, listingVenue, listingName, nextOffset, access.emailVerified, access.hasProAccess]);
293
298
 
294
299
  const loadMoreFromScroll = useTableLoadMore(
295
300
  tableScrollRef,
@@ -62,41 +62,66 @@ export function impactIndicator(impact: EconImpact): { text: string; color: stri
62
62
  }
63
63
  }
64
64
 
65
- function dateKey(d: Date): string {
66
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
65
+ /** The UTC day of an instant, so a release sits under the same day on every machine. */
66
+ export function dateKey(d: Date): string {
67
+ return d.toISOString().slice(0, 10);
67
68
  }
68
69
 
69
70
  /**
70
- * Rows are grouped by local day, so the time beside them is local too. The
71
- * payload's `time` is the UTC clock time and would put a 23:00 UTC release
72
- * under the next local day at "23:00".
71
+ * The release's UTC clock time. Rows group by UTC day and the column header
72
+ * names the zone, as `gloomberb econ` prints it; local time would move a 23:00
73
+ * UTC release under another day and read differently on every machine.
73
74
  */
74
75
  export function timeLabel(d: Date): string {
75
- return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
76
+ return d.toISOString().slice(11, 16);
77
+ }
78
+
79
+ /** The zone the calendar's times and days are in, for the column header. */
80
+ export const CALENDAR_TIME_ZONE_LABEL = "UTC";
81
+
82
+ /** "Fri Oct 9", the UTC day the calendar groups an instant under. */
83
+ export function shortDayLabel(d: Date): string {
84
+ return `${DAY_NAMES[d.getUTCDay()]!} ${MONTH_NAMES[d.getUTCMonth()]!} ${d.getUTCDate()}`;
76
85
  }
77
86
 
78
87
  export function dayLabel(d: Date, today: Date): string {
79
88
  const dk = dateKey(d);
80
89
  const todayKey = dateKey(today);
81
- const yesterday = new Date(today);
82
- yesterday.setDate(yesterday.getDate() - 1);
83
- const tomorrow = new Date(today);
84
- tomorrow.setDate(tomorrow.getDate() + 1);
90
+ const dayMs = 24 * 60 * 60 * 1000;
91
+ const yesterday = dateKey(new Date(today.getTime() - dayMs));
92
+ const tomorrow = dateKey(new Date(today.getTime() + dayMs));
85
93
 
86
- const dayName = DAY_NAMES[d.getDay()]!;
87
- const monthName = MONTH_NAMES[d.getMonth()]!;
88
- const dateNum = d.getDate();
89
- const suffix = `${dayName} ${monthName} ${dateNum}`;
94
+ const suffix = shortDayLabel(d);
90
95
 
91
96
  if (dk === todayKey) return `TODAY · ${suffix}`;
92
- if (dk === dateKey(tomorrow)) return `TOMORROW · ${suffix}`;
93
- if (dk === dateKey(yesterday)) return `YESTERDAY · ${suffix}`;
97
+ if (dk === tomorrow) return `TOMORROW · ${suffix}`;
98
+ if (dk === yesterday) return `YESTERDAY · ${suffix}`;
94
99
  return suffix;
95
100
  }
96
101
 
102
+ /**
103
+ * The calendar runs two weeks ahead. One that lists nothing a week past the
104
+ * start of the window (today, or a later first day asked for) stops short,
105
+ * as a feed that ends with its own week does, and says where it stops rather
106
+ * than reading as a quiet week.
107
+ */
108
+ const MIN_DAYS_LISTED_AHEAD = 7;
109
+
110
+ /**
111
+ * When the calendar stops short of a week past `windowStart`, the time of its
112
+ * last event; null when it reaches that far or lists nothing.
113
+ */
114
+ export function shortCalendarEnd(events: readonly EconEvent[], windowStart: number): Date | null {
115
+ let last: Date | null = null;
116
+ for (const event of events) {
117
+ if (!last || event.date.getTime() > last.getTime()) last = event.date;
118
+ }
119
+ return last && last.getTime() < windowStart + MIN_DAYS_LISTED_AHEAD * 86_400_000 ? last : null;
120
+ }
121
+
97
122
  /**
98
123
  * Events (already in time order; `eventIdx` indexes them) under a header per
99
- * local day, with NOW before the first event still to come. When that event is
124
+ * UTC day, with NOW before the first event still to come. When that event is
100
125
  * on a later day, NOW sits above that day's header, since the present is still
101
126
  * on the earlier day.
102
127
  */
@@ -29,7 +29,7 @@ import { Box, Text } from "../../../ui";
29
29
  import { displayWidth } from "../../../utils/format";
30
30
  import { useOpenTickerChoice } from "../shared/ticker-choice";
31
31
  import { resolveFredMapping, projectFredHistory, fredHistoryUnits } from "./fred-series-map";
32
- import { actualColor, timeLabel } from "./calendar-model";
32
+ import { actualColor, CALENDAR_TIME_ZONE_LABEL, timeLabel } from "./calendar-model";
33
33
  import type { EconEvent } from "./types";
34
34
 
35
35
  interface EconDetailViewProps {
@@ -195,7 +195,7 @@ export function EconDetailView({ event, width, height, focused }: EconDetailView
195
195
  ...(event.actual ? [{ id: "actual", label: "Actual", value: event.actual, color: actualColor(event.actual, event.forecast) }] : []),
196
196
  ...(event.forecast ? [{ id: "forecast", label: "Forecast", value: event.forecast }] : []),
197
197
  ...(event.prior ? [{ id: "prior", label: "Prior", value: event.prior }] : []),
198
- { id: "time", label: "Release", value: timeLabel(event.date) },
198
+ { id: "time", label: "Release", value: `${timeLabel(event.date)} ${CALENDAR_TIME_ZONE_LABEL}` },
199
199
  ];
200
200
 
201
201
  const projected = useMemo(
@@ -24,6 +24,8 @@ import {
24
24
  attachEconCalendarPersistence,
25
25
  actualColor,
26
26
  calendarDisplayRows,
27
+ CALENDAR_TIME_ZONE_LABEL,
28
+ dateKey,
27
29
  dayLabel,
28
30
  formatCountdown,
29
31
  formatStaleness,
@@ -33,6 +35,8 @@ import {
33
35
  matchesCountry,
34
36
  matchesImpact,
35
37
  resetEconCalendarPersistence,
38
+ shortCalendarEnd,
39
+ shortDayLabel,
36
40
  timeLabel,
37
41
  type CountryFilter,
38
42
  type DisplayRow,
@@ -42,6 +46,8 @@ import {
42
46
  } from "./calendar-model";
43
47
  import { usePaneStatusFooter } from "../../../components/layout/pane/status-footer";
44
48
  import { handleRefreshKey } from "../../../components/data-table/table-pane";
49
+ import { tableColumnWidth } from "../../../components/ui/table-layout";
50
+ import { useRemoteUiNode } from "../../../remote/semantic-tree";
45
51
 
46
52
  const IMPACT_LABELS: Record<ImpactFilter, string> = {
47
53
  all: "All",
@@ -100,17 +106,28 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
100
106
  // Day headers and the NOW marker between the past and the upcoming events.
101
107
  const today = new Date(now);
102
108
  const rows = calendarDisplayRows(filtered, now);
109
+ // Rows group by UTC day, so today starts at UTC midnight.
110
+ const todayStart = Math.floor(now / 86_400_000) * 86_400_000;
111
+ // A calendar that stops short of a week ahead says where it stops.
112
+ const listedEnd = shortCalendarEnd(events, todayStart);
113
+ const listedAfter = listedEnd ? `No events listed after ${shortDayLabel(listedEnd)}` : null;
103
114
 
104
115
  // Map from eventIdx to flat row index (for scroll tracking)
105
116
  const eventIdxToRowIdx = new Map<number, number>();
106
117
  let nowRowIdx = -1;
107
118
  let nextUpcomingEventIdx = -1;
108
119
  let nextUpcomingTime = Number.POSITIVE_INFINITY;
120
+ // The first row of today or later, with the day header and NOW band above it.
121
+ let todayRowIdx = -1;
109
122
  for (let r = 0; r < rows.length; r++) {
110
123
  const row = rows[r]!;
111
124
  if (row.kind === "event") {
112
125
  eventIdxToRowIdx.set(row.eventIdx, r);
113
126
  const eventTime = row.event.date.getTime();
127
+ if (todayRowIdx < 0 && eventTime >= todayStart) {
128
+ todayRowIdx = r;
129
+ while (todayRowIdx > 0 && rows[todayRowIdx - 1]!.kind !== "event") todayRowIdx -= 1;
130
+ }
114
131
  if (eventTime > now && eventTime < nextUpcomingTime) {
115
132
  nextUpcomingEventIdx = row.eventIdx;
116
133
  nextUpcomingTime = eventTime;
@@ -125,51 +142,63 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
125
142
  const selectedIdx = rememberedIdx >= 0 ? rememberedIdx
126
143
  : nextUpcomingEventIdx >= 0 ? nextUpcomingEventIdx : Math.max(0, filtered.length - 1);
127
144
 
128
- // On initial load, scroll to NOW and select the first upcoming event
129
- const initialScrollDone = useRef(false);
145
+ // The present at the top of the table: NOW a few rows down for context, but
146
+ // never above today, so a Saturday opens on NOW and Monday rather than on
147
+ // Friday's last releases. Asked for on open, on a filter change and on a
148
+ // forced reload; it holds until the user scrolls or moves the selection.
149
+ const presentRowIdx = nowRowIdx >= 0 ? Math.max(0, todayRowIdx, nowRowIdx - 3) : -1;
150
+ const [presentRequest, setPresentRequest] = useState(1);
151
+ const [presentAnchor, setPresentAnchor] = useState<{ index: number; version: number } | null>(null);
152
+ const servedPresentRequest = useRef(0);
130
153
  useEffect(() => {
131
- if (initialScrollDone.current || filtered.length === 0) return;
132
- if (nextUpcomingEventIdx >= 0) {
154
+ if (servedPresentRequest.current === presentRequest || filtered.length === 0) return;
155
+ // On open the selection moves to the next release as well.
156
+ if (servedPresentRequest.current === 0 && nextUpcomingEventIdx >= 0) {
133
157
  setSelectedKey(filtered[nextUpcomingEventIdx]?.id ?? null);
134
158
  }
135
- const sb = scrollRef.current;
136
- if (sb?.viewport && nowRowIdx >= 0) {
137
- // Position NOW a few rows from the top so you can see context
138
- const scrollTarget = Math.max(0, nowRowIdx - 3);
139
- sb.scrollTo(scrollTarget);
140
- }
141
- initialScrollDone.current = true;
142
- }, [filtered.length]);
159
+ servedPresentRequest.current = presentRequest;
160
+ setPresentAnchor(presentRowIdx >= 0 ? { index: presentRowIdx, version: presentRequest } : null);
161
+ }, [filtered.length, presentRequest]);
162
+ const returnToPresent = useCallback(() => setPresentRequest((request) => request + 1), []);
163
+ const leavePresent = useCallback(() => setPresentAnchor(null), []);
143
164
 
144
165
  const selectImpactFilter = useCallback((value: ImpactFilter) => {
145
166
  setImpactFilter(value);
146
167
  setSelectedKey(null);
147
- }, [setImpactFilter, setSelectedKey]);
168
+ returnToPresent();
169
+ }, [returnToPresent, setImpactFilter, setSelectedKey]);
148
170
  const selectCountryFilter = useCallback((value: CountryFilter) => {
149
171
  setCountryFilter(value);
150
172
  setSelectedKey(null);
151
- }, [setCountryFilter, setSelectedKey]);
173
+ returnToPresent();
174
+ }, [returnToPresent, setCountryFilter, setSelectedKey]);
152
175
  const cycleImpactFilter = useCallback((step: 1 | -1) => {
153
176
  setImpactFilter((prev) => FILTER_CYCLE[(FILTER_CYCLE.indexOf(prev) + step + FILTER_CYCLE.length) % FILTER_CYCLE.length]!);
154
177
  setSelectedKey(null);
155
- }, [setImpactFilter, setSelectedKey]);
178
+ returnToPresent();
179
+ }, [returnToPresent, setImpactFilter, setSelectedKey]);
156
180
  const cycleCountryFilter = useCallback((step: 1 | -1) => {
157
181
  setCountryFilter((prev) => COUNTRY_CYCLE[(COUNTRY_CYCLE.indexOf(prev) + step + COUNTRY_CYCLE.length) % COUNTRY_CYCLE.length]!);
158
182
  setSelectedKey(null);
159
- }, [setCountryFilter, setSelectedKey]);
183
+ returnToPresent();
184
+ }, [returnToPresent, setCountryFilter, setSelectedKey]);
160
185
 
161
186
  // A forced reload goes back to the present once it answers.
162
187
  const forcedResult = calendar.data?.forced ? calendar.data : null;
163
188
  useEffect(() => {
164
- if (forcedResult) setSelectedKey(null);
165
- }, [forcedResult, setSelectedKey]);
189
+ if (!forcedResult) return;
190
+ setSelectedKey(null);
191
+ returnToPresent();
192
+ }, [forcedResult, returnToPresent, setSelectedKey]);
166
193
 
167
194
  const handleRootKeyDown = useCallback((event: DataTableKeyEvent) => (
168
195
  handleRefreshKey(event, () => void calendar.reload(), { stopPropagation: true })
169
196
  ), [calendar.reload]);
170
197
 
171
198
  const columns = useMemo<EconCalendarColumn[]>(() => {
172
- const timeWidth = 6;
199
+ // The header names the zone, so the column is as wide as the header needs.
200
+ const timeLabelText = `TIME (${CALENDAR_TIME_ZONE_LABEL})`;
201
+ const timeWidth = tableColumnWidth({ width: 6, label: timeLabelText });
173
202
  const impactWidth = 4;
174
203
  const flagWidth = 3;
175
204
  const actualWidth = 9;
@@ -183,7 +212,7 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
183
212
  const eventWidth = Math.max(minEventWidth, width - 3 - columnCount - fixedWidth);
184
213
 
185
214
  return [
186
- { id: "time", label: "TIME", width: timeWidth, align: "left" },
215
+ { id: "time", label: timeLabelText, width: timeWidth, align: "left" },
187
216
  { id: "impact", label: "IMP", width: impactWidth, align: "left" },
188
217
  { id: "country", label: "CTY", width: flagWidth, align: "left" },
189
218
  { id: "event", label: "EVENT", width: eventWidth, align: "left" },
@@ -209,11 +238,21 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
209
238
  const nextText = nextEvent && nextCountdown ? `next ${nextEvent.event} ${nextCountdown}` : null;
210
239
  // The cached first paint is being replaced; its age only counts once a load has answered.
211
240
  const showStale = stale && (!loading || calendar.updatedAt !== null);
241
+ // An empty list says it in its empty state instead.
242
+ const listedAfterStatus = listedAfter && !detailEvent && filtered.length > 0 ? listedAfter : null;
212
243
  const calendarStatus = useMemo<PaneFooterSegment[]>(() => [
213
244
  ...(nextText && !detailEvent ? [{ id: "next", parts: [{ text: nextText, tone: "muted" as const }] }] : []),
245
+ ...(listedAfterStatus ? [{ id: "listed-after", parts: [{ text: listedAfterStatus, tone: "warning" as const }] }] : []),
214
246
  ...(showStale ? [{ id: "stale", parts: [{ text: "STALE", tone: "warning" as const }] }] : []),
215
247
  ...(staleness ? [{ id: "updated", parts: [{ text: staleness, tone: "muted" as const }] }] : []),
216
- ], [detailEvent, nextText, showStale, staleness]);
248
+ ], [detailEvent, listedAfterStatus, nextText, showStale, staleness]);
249
+ // A report of the pane (`gloomberb fn ECO`) says it too.
250
+ const listedThrough = listedEnd ? dateKey(listedEnd) : null;
251
+ useRemoteUiNode(listedAfter && listedThrough ? {
252
+ role: "report-notice",
253
+ label: "Listed through",
254
+ getMetadata: () => ({ text: listedAfter, key: "listedThrough", value: listedThrough }),
255
+ } : null);
217
256
  usePaneStatusFooter({
218
257
  registrationId: "econ-calendar",
219
258
  loading,
@@ -336,9 +375,16 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
336
375
  kind: "index",
337
376
  selectedIndex: eventIdxToRowIdx.get(selectedIdx) ?? selectedIdx,
338
377
  onChange: (_index, row) => {
378
+ leavePresent();
339
379
  if (row.kind === "event") setSelectedKey(row.event.id);
340
380
  },
341
381
  }}
382
+ scrollToIndex={presentAnchor?.index}
383
+ scrollToIndexAlign={presentAnchor ? "start" : "nearest"}
384
+ scrollToIndexVersion={presentAnchor?.version ?? 0}
385
+ onBodyScrollActivity={(source) => {
386
+ if (source === "user") leavePresent();
387
+ }}
342
388
  columns={columns}
343
389
  items={rows}
344
390
  isNavigable={(row) => row.kind === "event"}
@@ -351,7 +397,7 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
351
397
  renderSectionHeader={renderSectionHeader}
352
398
  renderCell={renderCell}
353
399
  selectedTextOverridesCellColor
354
- emptyStateTitle={loading ? "Loading economic events..." : "No events"}
400
+ emptyStateTitle={loading ? "Loading economic events..." : listedAfter ?? "No events"}
355
401
  emptyStateHint={emptyStateHint}
356
402
  showHorizontalScrollbar={false}
357
403
  />
@@ -49,11 +49,19 @@ function detailEntries(view: StatViewModel): HeadlessPaneEntry[] {
49
49
  value: view.low,
50
50
  formatted: `${format(view.low.value)} ${view.low.date}`,
51
51
  },
52
- { label: "As of", value: view.latest.date },
52
+ // FRED dates a monthly reading at the month's first day; the text says which period it is.
53
+ { label: "Period", value: view.latest.date, formatted: view.reading },
53
54
  { label: "Source", value: `FRED ${view.stat.seriesId}` },
54
55
  ];
55
56
  }
56
57
 
58
+ /** The window the %ile column counts over, in words. */
59
+ const PERCENTILE_WINDOW: Record<StatRangeId, string> = {
60
+ "5Y": "the last 5 years",
61
+ "20Y": "the last 20 years",
62
+ ALL: "the full history",
63
+ };
64
+
57
65
  export function projectStatsHeadlessBundle(
58
66
  bundle: StatsBundle,
59
67
  range: StatRangeId,
@@ -86,7 +94,7 @@ export function projectStatsHeadlessBundle(
86
94
  align: "right" as const,
87
95
  format: (value: unknown) => formatNumber(Number(value), 0),
88
96
  },
89
- { key: "asOf", header: "As of" },
97
+ { key: "asOf", header: "Period", format: (_value: unknown, row: Record<string, unknown>) => String(row.reading) },
90
98
  ],
91
99
  rows: categoryViews.map((view) => ({
92
100
  id: view.stat.id,
@@ -97,6 +105,7 @@ export function projectStatsHeadlessBundle(
97
105
  formattedPrevious: view.previous ? view.stat.formatValue(view.previous.value) : "-",
98
106
  percentile: view.percentile,
99
107
  asOf: view.latest.date,
108
+ reading: view.reading,
100
109
  stale: view.observationStale || view.cacheStale,
101
110
  fetchedAt: view.fetchedAt,
102
111
  cacheStale: view.cacheStale,
@@ -112,6 +121,7 @@ export function projectStatsHeadlessBundle(
112
121
  ...(selected ? [{ title: selected.stat.label, entries: detailEntries(selected) }] : []),
113
122
  ],
114
123
  errors: bundle.errors,
124
+ notes: [`%ile is the share of past readings at or below the latest one, over ${PERCENTILE_WINDOW[range]}.`],
115
125
  metadata: {
116
126
  fetchedAt: bundle.fetchedAt,
117
127
  fetchedAtComplete: bundle.fetchedAtComplete,
@@ -148,7 +148,7 @@ export function EconStatisticsPane({ focused, width, height }: PaneProps) {
148
148
  const footerInfo = useMemo<PaneFooterSegment[]>(() => {
149
149
  if (!selected) return [];
150
150
  const info: PaneFooterSegment[] = [
151
- { id: "as-of", parts: [{ text: `as of ${selected.latest.date}`, tone: "muted" }] },
151
+ { id: "as-of", parts: [{ text: `reading for ${selected.reading}`, tone: "muted" }] },
152
152
  ];
153
153
  // The cached first paint is being replaced; its age only counts once that load fails.
154
154
  const seeding = resource.loading && resource.updatedAt === null && !selected.refreshError;
@@ -19,6 +19,8 @@ export interface StatViewModel {
19
19
  latest: StatPoint;
20
20
  /** The latest print's period at the series' own cadence: "Sep 21", "Aug", "Q2". */
21
21
  period: string;
22
+ /** What the latest print is a reading of, in full: "Aug 2026", "Q2 2026", "week ending 2026-10-03", "2026-10-09". */
23
+ reading: string;
22
24
  /** The print before this one, for the direction of the last move. */
23
25
  previous: StatPoint | null;
24
26
  yearAgo: StatPoint | null;
@@ -60,6 +62,20 @@ function formatStatPeriod(date: string, perYear: number): string {
60
62
  return `${name} ${Number(date.slice(8, 10))}`;
61
63
  }
62
64
 
65
+ /**
66
+ * What the latest reading is a reading of. FRED dates a monthly or quarterly print at the start of
67
+ * its period, so 2026-08-01 is August 2026, not the first of the month; a weekly print is dated by
68
+ * the week's last day, a daily one by its day.
69
+ */
70
+ function formatStatReading(date: string, perYear: number): string {
71
+ const month = Number(date.slice(5, 7));
72
+ const name = MONTHS[month - 1];
73
+ if (!name) return date;
74
+ if (perYear <= 4) return `Q${Math.ceil(month / 3)} ${date.slice(0, 4)}`;
75
+ if (perYear <= 12) return `${name} ${date.slice(0, 4)}`;
76
+ return perYear <= 52 ? `week ending ${date}` : date;
77
+ }
78
+
63
79
  function extreme(points: readonly StatPoint[], pick: "high" | "low"): StatPoint {
64
80
  let best = points[0]!;
65
81
  for (const point of points) {
@@ -99,6 +115,7 @@ export function projectStat(
99
115
  range,
100
116
  latest,
101
117
  period: formatStatPeriod(latest.date, perYear),
118
+ reading: formatStatReading(latest.date, perYear),
102
119
  previous,
103
120
  yearAgo,
104
121
  changeOnPrevious: previous ? latest.value - previous.value : null,
@@ -4,7 +4,9 @@ import {
4
4
  type CloudProxyStatementPayload,
5
5
  } from "../../../api-client";
6
6
  import { ApiRequestError, isPermanentClientError } from "../../../api-client/errors";
7
+ import type { IssuerListingParams } from "../../../api-client/paths";
7
8
  import type { PluginPersistence } from "../../../types/plugin";
9
+ import { secListingKey } from "../../../utils/sec";
8
10
 
9
11
  /**
10
12
  * Executive compensation comes from Gloom Cloud's open proxy-statement
@@ -101,21 +103,30 @@ function loadCached<T>(
101
103
  return request.promise;
102
104
  }
103
105
 
106
+ interface ProxyLoadOptions {
107
+ force?: boolean;
108
+ /** The listing's venue and company, for a listing outside the US. */
109
+ listing?: IssuerListingParams;
110
+ }
111
+
104
112
  export async function loadProxyStatements(
105
113
  ticker: string,
106
- options?: { force?: boolean },
114
+ options?: ProxyLoadOptions,
107
115
  ): Promise<ProxyResult<CloudProxyStatementListPayload>> {
108
- const key = ticker.toUpperCase();
116
+ const symbol = ticker.toUpperCase();
117
+ // SAN in Paris is not SAN in New York: a listing abroad is cached under its venue.
118
+ const key = secListingKey(symbol, options?.listing?.exchange);
109
119
  return loadCached(LIST_KIND, key, activeListFetches, LIST_CACHE_POLICY, options?.force ?? false,
110
- () => apiClient.getProxyStatements(key));
120
+ () => apiClient.getProxyStatements(symbol, options?.listing));
111
121
  }
112
122
 
113
123
  export async function loadProxyStatement(
114
124
  ticker: string,
115
125
  year: number,
116
- options?: { force?: boolean },
126
+ options?: ProxyLoadOptions,
117
127
  ): Promise<ProxyResult<CloudProxyStatementPayload>> {
118
- const key = `${ticker.toUpperCase()}:${year}`;
128
+ const symbol = ticker.toUpperCase();
129
+ const key = `${secListingKey(symbol, options?.listing?.exchange)}:${year}`;
119
130
  return loadCached(STATEMENT_KIND, key, activeStatementFetches, STATEMENT_CACHE_POLICY, options?.force ?? false,
120
- () => apiClient.getProxyStatement(ticker.toUpperCase(), year));
131
+ () => apiClient.getProxyStatement(symbol, year, options?.listing));
121
132
  }