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
@@ -12,6 +12,7 @@ import { useLiveQuoteEntries } from "../../../state/hooks/quote-streaming";
12
12
  import { buildQuoteKey, resolveEntryData } from "../../../market-data/selectors";
13
13
  import type { QuoteSubscriptionTarget } from "../../../types/data-provider";
14
14
  import type { Quote } from "../../../types/financials";
15
+ import type { ExtendedSession } from "../../../market-data/market/status";
15
16
  import { useAutoRefresh, useUpdatedAgo } from "../../../react/auto-refresh";
16
17
  import { useLiveStreamingSetting } from "../../../state/hooks/live-streaming";
17
18
  import { isStreamCarryingQuote, useVisibleBoardSymbols } from "../shared/use-quote-board";
@@ -33,6 +34,7 @@ import {
33
34
  nextSortPreference,
34
35
  normalizeRowsForCollection,
35
36
  overlayLiveSectorQuote,
37
+ sectorExtendedSessions,
36
38
  sectorRowFollowsQuote,
37
39
  sectorRowIssues,
38
40
  sortRows,
@@ -149,7 +151,6 @@ function EtfBoard({ collectionId, focused, width, height, rootBefore }: EtfBoard
149
151
  const fetchGenRef = useRef(0);
150
152
  const [loadError, setLoadError] = useState<string | null>(null);
151
153
 
152
- const columns = useMemo(() => buildSectorColumns(width), [width]);
153
154
  const rows = useMemo(
154
155
  () => normalizeRowsForCollection(rowsByCollection, activeCollection.id, activeItems),
155
156
  [activeCollection.id, activeItems, rowsByCollection],
@@ -203,6 +204,12 @@ function EtfBoard({ collectionId, focused, width, height, rootBefore }: EtfBoard
203
204
  return carried && !!quote && sectorRowFollowsQuote(row, quote);
204
205
  }), [freshnessNow, liveEntries, liveStreaming, rows, subscriptionStartedAt]);
205
206
  const sortedRows = useMemo(() => sortRows(liveRows, sortPreference), [liveRows, sortPreference]);
207
+ // A pre-market or after-hours column only while a fund has such a print.
208
+ const extendedSessionsKey = sectorExtendedSessions(liveRows).join(",");
209
+ const columns = useMemo(
210
+ () => buildSectorColumns(width, extendedSessionsKey ? extendedSessionsKey.split(",") as ExtendedSession[] : []),
211
+ [extendedSessionsKey, width],
212
+ );
206
213
  const lastRefreshMs = lastRefreshByCollection[activeCollection.id] ?? null;
207
214
  const loading = rows.some((row) => row.loading);
208
215
  /**
@@ -296,6 +303,16 @@ function EtfBoard({ collectionId, focused, width, height, rootBefore }: EtfBoard
296
303
  text: row.changePercent !== null ? formatPercentRaw(shownPercent(row.changePercent)) : "—",
297
304
  color: row.changePercent !== null ? priceColor(shownPercent(row.changePercent)) : colors.textDim,
298
305
  };
306
+ case "preMarket":
307
+ case "afterHours": {
308
+ // The extended print's move from the close in LAST.
309
+ const move = row.extendedSession === (column.id === "preMarket" ? "PRE" : "POST") ? row.extendedChangePercent ?? null : null;
310
+ return {
311
+ text: move !== null ? formatPercentRaw(shownPercent(move)) : "—",
312
+ value: move,
313
+ color: move !== null ? priceColor(shownPercent(move)) : colors.textDim,
314
+ };
315
+ }
299
316
  case "return1M":
300
317
  return {
301
318
  text: row.loading && row.return1M === null ? "…" : row.return1M !== null ? formatPercentRaw(shownPercent(row.return1M)) : "—",
@@ -1,8 +1,15 @@
1
1
  import type { DataTableColumn } from "../../../components";
2
+ import { getTableWidth, tableColumnWidth } from "../../../components/ui/table-layout";
2
3
  import type { PricePoint, Quote } from "../../../types/financials";
3
4
  import { compareSortValues, type SortDirection } from "../../../utils/sort-values";
4
5
  import { getPricePointTimestamp } from "../../../utils/price-history";
5
6
  import { mergePriceHistoryIntegrity, pricePointIntegrity, type PriceHistoryIntegrity } from "../../../utils/price-history-integrity";
7
+ import {
8
+ getExtendedSessionDisplay,
9
+ getRegularSessionDisplay,
10
+ marketStateLabel,
11
+ type ExtendedSession,
12
+ } from "../../../market-data/market/status";
6
13
  import {
7
14
  getSectorCollection,
8
15
  type SectorCollectionId,
@@ -12,8 +19,21 @@ import {
12
19
  const DAY_MS = 24 * 60 * 60 * 1000;
13
20
  export const DEFAULT_COLLECTION_ID: SectorCollectionId = "sectors";
14
21
 
15
- export interface SectorRow extends SectorDef {
22
+ /**
23
+ * The pre-market or after-hours print beside a row's regular close, measured
24
+ * from that close (getExtendedSessionDisplay); all null without one.
25
+ */
26
+ export interface SectorExtendedFields {
27
+ extendedSession?: ExtendedSession | null;
28
+ extendedPrice?: number | null;
29
+ extendedChange?: number | null;
30
+ extendedChangePercent?: number | null;
31
+ }
32
+
33
+ export interface SectorRow extends SectorDef, SectorExtendedFields {
34
+ /** The regular session's price: its close once it is over, never an extended print. */
16
35
  price: number | null;
36
+ /** The regular session's move, as `ticker` reports it. */
17
37
  changePercent: number | null;
18
38
  return1M: number | null;
19
39
  return1Y: number | null;
@@ -33,7 +53,7 @@ export interface SectorRow extends SectorDef {
33
53
  return1YStartDate?: string | null;
34
54
  }
35
55
 
36
- type SectorColumnId = "name" | "etf" | "price" | "changePercent" | "return1M" | "return1Y" | "bar";
56
+ type SectorColumnId = "name" | "etf" | "price" | "changePercent" | "preMarket" | "afterHours" | "return1M" | "return1Y" | "bar";
37
57
  export type SectorColumn = DataTableColumn & { id: SectorColumnId };
38
58
  export type SectorRowsByCollection = Record<SectorCollectionId, SectorRow[]>;
39
59
  export type SectorRefreshByCollection = Partial<Record<SectorCollectionId, number>>;
@@ -48,6 +68,45 @@ export const DEFAULT_SORT_PREFERENCE: SectorSortPreference = {
48
68
  direction: "desc",
49
69
  };
50
70
 
71
+ const NO_EXTENDED_PRINT: Required<SectorExtendedFields> = {
72
+ extendedSession: null,
73
+ extendedPrice: null,
74
+ extendedChange: null,
75
+ extendedChangePercent: null,
76
+ };
77
+
78
+ /** A quote's pre-market or after-hours print, from the regular close a row shows, as row fields. */
79
+ export function sectorExtendedFields(quote: Quote | null | undefined): Required<SectorExtendedFields> {
80
+ const extended = getExtendedSessionDisplay(quote);
81
+ if (!extended || !Number.isFinite(extended.price)) return NO_EXTENDED_PRINT;
82
+ return {
83
+ extendedSession: extended.session,
84
+ extendedPrice: extended.price,
85
+ extendedChange: Number.isFinite(extended.change) ? extended.change! : null,
86
+ extendedChangePercent: Number.isFinite(extended.changePercent) ? extended.changePercent! : null,
87
+ };
88
+ }
89
+
90
+ function extendedFieldsOf(row: SectorExtendedFields | undefined): Required<SectorExtendedFields> {
91
+ return {
92
+ extendedSession: row?.extendedSession ?? null,
93
+ extendedPrice: row?.extendedPrice ?? null,
94
+ extendedChange: row?.extendedChange ?? null,
95
+ extendedChangePercent: row?.extendedChangePercent ?? null,
96
+ };
97
+ }
98
+
99
+ function sameExtended(left: SectorExtendedFields, right: SectorExtendedFields): boolean {
100
+ return (left.extendedSession ?? null) === (right.extendedSession ?? null)
101
+ && (left.extendedPrice ?? null) === (right.extendedPrice ?? null)
102
+ && (left.extendedChangePercent ?? null) === (right.extendedChangePercent ?? null);
103
+ }
104
+
105
+ /** The extended sessions some row has a print for, in the order their columns go. */
106
+ export function sectorExtendedSessions(rows: readonly SectorExtendedFields[]): ExtendedSession[] {
107
+ return (["PRE", "POST"] as const).filter((session) => rows.some((row) => row.extendedSession === session));
108
+ }
109
+
51
110
  /** Full length at a 5% session move, which covers all but crash days. */
52
111
  const MOVE_BAR_FULL_SCALE_PERCENT = 5;
53
112
 
@@ -99,6 +158,7 @@ export function normalizeRowsForCollection(
99
158
  quoteIssue: existing?.quoteIssue ?? null,
100
159
  lastReportedPrice: existing?.lastReportedPrice ?? null,
101
160
  quoteUpdatedAt: existing?.quoteUpdatedAt ?? null,
161
+ ...extendedFieldsOf(existing),
102
162
  returnIntegrity: existing?.returnIntegrity ?? {},
103
163
  returnAsOfDate: existing?.returnAsOfDate ?? null,
104
164
  return1MStartDate: existing?.return1MStartDate ?? null,
@@ -138,6 +198,7 @@ const UNAVAILABLE_SECTOR_ROW: Partial<SectorRow> = {
138
198
  lastReportedPrice: null,
139
199
  quoteUpdatedAt: null,
140
200
  returnIntegrity: {},
201
+ ...NO_EXTENDED_PRINT,
141
202
  };
142
203
 
143
204
  function sameSectorRow(left: SectorRow, right: SectorRow): boolean {
@@ -276,33 +337,49 @@ export function sectorRowIssues(row: SectorRow): string[] {
276
337
  return issues;
277
338
  }
278
339
 
279
- export function buildSectorColumns(width: number): SectorColumn[] {
280
- const etfWidth = 4;
281
- const priceWidth = 8;
282
- const changeWidth = 8;
283
- const returnWidth = 8;
284
- const showBar = width >= 67;
285
- const compactBar = width < 82;
286
- const barWidth = showBar
287
- ? compactBar ? 6 : Math.max(8, Math.min(18, Math.floor(width * 0.16)))
288
- : 0;
289
- const columnCount = showBar ? 7 : 6;
290
- const fixedWidth = etfWidth + priceWidth + changeWidth + returnWidth * 2 + barWidth;
291
- const nameWidth = Math.max(10, Math.min(22, width - 2 - columnCount - fixedWidth));
292
-
293
- const columns: SectorColumn[] = [
294
- { id: "name", label: "SECTOR", width: nameWidth, align: "left", flexGrow: 1 },
295
- { id: "etf", label: "ETF", width: etfWidth, align: "left" },
296
- { id: "price", label: "LAST", width: priceWidth, align: "right" },
297
- { id: "changePercent", label: "1D", width: changeWidth, align: "right" },
298
- { id: "return1M", label: "1M", width: returnWidth, align: "right" },
299
- { id: "return1Y", label: "1Y", width: returnWidth, align: "right" },
340
+ const EXTENDED_COLUMN_WIDTH = 9;
341
+ /** The longest name in either collection ("Consumer Staples"), which a narrow pane keeps whole as long as it can. */
342
+ const FULL_NAME_WIDTH = 16;
343
+
344
+ /**
345
+ * The board's columns at `width`. A pre-market or after-hours column follows
346
+ * 1D for each session in `extendedSessions`. A narrow pane gives up the move
347
+ * bar first, then that column, before it cuts a sector's name.
348
+ */
349
+ export function buildSectorColumns(width: number, extendedSessions: readonly ExtendedSession[] = []): SectorColumn[] {
350
+ const lead: SectorColumn[] = [
351
+ { id: "etf", label: "ETF", width: 4, align: "left" },
352
+ { id: "price", label: "LAST", width: 8, align: "right" },
353
+ { id: "changePercent", label: "1D", width: 8, align: "right" },
354
+ ];
355
+ const returns: SectorColumn[] = [
356
+ { id: "return1M", label: "1M", width: 8, align: "right" },
357
+ { id: "return1Y", label: "1Y", width: 8, align: "right" },
358
+ ];
359
+ const extendedColumns = extendedSessions.map((session): SectorColumn => ({
360
+ id: session === "PRE" ? "preMarket" : "afterHours",
361
+ // Named for the session it trades in, as the header labels it.
362
+ label: marketStateLabel(session),
363
+ width: EXTENDED_COLUMN_WIDTH,
364
+ align: "right",
365
+ }));
366
+ // Measured the way the table draws them (a header and its sort mark widen a
367
+ // column, gaps between them); the name adds its own width and a gap.
368
+ const nameRoom = (columns: SectorColumn[]) => width - getTableWidth(columns) - 1;
369
+ const shownExtended = extendedColumns.length > 0 && nameRoom([...lead, ...extendedColumns, ...returns]) >= FULL_NAME_WIDTH
370
+ ? extendedColumns
371
+ : [];
372
+ const extendedWidth = shownExtended.reduce((total, column) => total + tableColumnWidth(column) + 1, 0);
373
+ const barWidth = width - extendedWidth < 82 ? 6 : Math.max(8, Math.min(18, Math.floor(width * 0.16)));
374
+ // Labelled with the window it encodes: it sits after 1Y but tracks 1D.
375
+ const bar: SectorColumn = { id: "bar", label: "1D MOVE", width: barWidth, align: "left" };
376
+ const withBar = [...lead, ...shownExtended, ...returns, bar];
377
+ const showBar = width - extendedWidth >= 67 && nameRoom(withBar) >= FULL_NAME_WIDTH;
378
+ const columns = showBar ? withBar : [...lead, ...shownExtended, ...returns];
379
+ return [
380
+ { id: "name", label: "SECTOR", width: Math.max(10, Math.min(22, nameRoom(columns))), align: "left", flexGrow: 1 },
381
+ ...columns,
300
382
  ];
301
- if (showBar) {
302
- // Labelled with the window it encodes: it sits after 1Y but tracks 1D.
303
- columns.push({ id: "bar", label: "1D MOVE", width: barWidth, align: "left" });
304
- }
305
- return columns;
306
383
  }
307
384
 
308
385
  function getSortValue(columnId: SectorColumnId, row: SectorRow): string | number | null {
@@ -315,6 +392,9 @@ function getSortValue(columnId: SectorColumnId, row: SectorRow): string | number
315
392
  return row.price;
316
393
  case "changePercent":
317
394
  return row.changePercent;
395
+ case "preMarket":
396
+ case "afterHours":
397
+ return row.extendedSession === (columnId === "preMarket" ? "PRE" : "POST") ? row.extendedChangePercent ?? null : null;
318
398
  case "return1M":
319
399
  return row.return1M;
320
400
  case "return1Y":
@@ -338,6 +418,8 @@ export function nextSortPreference(current: SectorSortPreference, columnId: stri
338
418
  return {
339
419
  columnId: typedColumnId,
340
420
  direction: typedColumnId === "changePercent"
421
+ || typedColumnId === "preMarket"
422
+ || typedColumnId === "afterHours"
341
423
  || typedColumnId === "return1M"
342
424
  || typedColumnId === "return1Y"
343
425
  || typedColumnId === "bar"
@@ -373,17 +455,30 @@ export function sectorQuoteSessionDate(quote: Quote): string | null {
373
455
  /**
374
456
  * A live quote extends a loaded row only inside the session its returns are
375
457
  * measured to. Every fund shares that session, so a pre-market print (the
376
- * next session) or a quote older than the snapshot leaves the row alone and
377
- * the board keeps ranking on one completed session. The 1M and 1Y returns
378
- * keep their baselines: the row's own return and price imply the start close.
458
+ * next session) or a quote older than the snapshot leaves the row's price
459
+ * alone and the board keeps ranking on one completed session. The 1M and 1Y
460
+ * returns keep their baselines: the row's own return and price imply the
461
+ * start close.
379
462
  */
380
463
  function isLiveSectorQuote(row: SectorRow, quote: Quote | null | undefined): quote is Quote {
381
- if (!quote || quote.stale === true || !finitePositive(quote.price)) return false;
382
- if (!finitePositive(row.price) || !row.returnAsOfDate) return false;
383
- if (row.quoteUpdatedAt != null && quote.lastUpdated < row.quoteUpdatedAt) return false;
464
+ if (!isCurrentSectorQuote(row, quote) || !finitePositive(getRegularSessionDisplay(quote)?.price)) return false;
384
465
  return sectorQuoteSessionDate(quote) === row.returnAsOfDate;
385
466
  }
386
467
 
468
+ /** A usable quote no older than the snapshot behind a loaded row. */
469
+ function isCurrentSectorQuote(row: SectorRow, quote: Quote | null | undefined): quote is Quote {
470
+ if (!quote || quote.stale === true) return false;
471
+ if (!finitePositive(row.price) || !row.returnAsOfDate) return false;
472
+ return row.quoteUpdatedAt == null || quote.lastUpdated >= row.quoteUpdatedAt;
473
+ }
474
+
475
+ /** A pre-market print of the session after the row's, which moves only the extended column. */
476
+ function isNextPreMarketQuote(row: SectorRow, quote: Quote): boolean {
477
+ if (quote.marketState !== "PRE" && quote.marketState !== "PREPRE") return false;
478
+ const session = sectorQuoteSessionDate(quote);
479
+ return !!session && !!row.returnAsOfDate && session > row.returnAsOfDate;
480
+ }
481
+
387
482
  /**
388
483
  * Whether the feed keeps a loaded row as current as a snapshot reload would,
389
484
  * given a quote the feed is carrying. It does when the quote belongs to the
@@ -402,18 +497,32 @@ export function sectorRowFollowsQuote(row: SectorRow, quote: Quote): boolean {
402
497
  return quote.marketState === "PRE" || quote.marketState === "PREPRE";
403
498
  }
404
499
 
500
+ /**
501
+ * A row with the feed's quote on it. Price and 1D are the regular session,
502
+ * as `ticker` reads them, so they hold at the close; a pre-market or
503
+ * after-hours print moves only the extended column, measured from that close.
504
+ */
405
505
  export function overlayLiveSectorQuote(row: SectorRow, quote: Quote | null | undefined): SectorRow {
406
- if (!isLiveSectorQuote(row, quote) || row.price == null) return row;
407
- const changePercent = Number.isFinite(quote.changePercent) ? quote.changePercent : row.changePercent;
408
- if (quote.price === row.price && changePercent === row.changePercent) return row;
409
- const scale = quote.price / row.price;
410
- const rescale = (value: number | null) => (value == null ? null : ((1 + value / 100) * scale - 1) * 100);
506
+ if (!isCurrentSectorQuote(row, quote) || row.price == null) return row;
507
+ if (!isLiveSectorQuote(row, quote)) {
508
+ if (!isNextPreMarketQuote(row, quote)) return row;
509
+ const extended = sectorExtendedFields(quote);
510
+ return sameExtended(extended, row) ? row : { ...row, ...extended };
511
+ }
512
+ const headline = getRegularSessionDisplay(quote)!;
513
+ const extended = sectorExtendedFields(quote);
514
+ const changePercent = Number.isFinite(headline.changePercent) ? headline.changePercent! : row.changePercent;
515
+ if (headline.price === row.price && changePercent === row.changePercent && sameExtended(extended, row)) return row;
516
+ const scale = headline.price / row.price;
517
+ // A held close leaves the returns exactly as they were.
518
+ const rescale = (value: number | null) => (value == null || scale === 1 ? value : ((1 + value / 100) * scale - 1) * 100);
411
519
  return {
412
520
  ...row,
413
- price: quote.price,
414
- lastReportedPrice: quote.price,
521
+ price: headline.price,
522
+ lastReportedPrice: headline.price,
415
523
  changePercent,
416
524
  return1M: rescale(row.return1M),
417
525
  return1Y: rescale(row.return1Y),
526
+ ...extended,
418
527
  };
419
528
  }
@@ -0,0 +1,51 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { usePluginPaneState } from "../../../public/react";
3
+ import { usePaneInstance } from "../../../state/app/context";
4
+ import type { PaneTemplateDef, PaneTemplateInstanceConfig } from "../../../types/plugin";
5
+
6
+ /** A command's request to show one tab, stamped so a later command can ask again. */
7
+ export function commandTabParams(tab: string, params?: Record<string, string>): Record<string, string> {
8
+ return { ...(params ?? {}), tab, tabAt: String(Date.now()) };
9
+ }
10
+
11
+ /** The template's pane, with the command's tab written onto the instance. */
12
+ function stampCommandTab(instance: PaneTemplateInstanceConfig | null, tab: string): PaneTemplateInstanceConfig | null {
13
+ return instance ? { ...instance, params: commandTabParams(tab, instance.params) } : null;
14
+ }
15
+
16
+ export function withCommandTab(template: PaneTemplateDef, tab: string): PaneTemplateDef {
17
+ const create = template.createInstance;
18
+ return {
19
+ ...template,
20
+ createInstance: (context, options) => {
21
+ const created = create?.(context, options);
22
+ if (!created) return null;
23
+ return created instanceof Promise ? created.then((instance) => stampCommandTab(instance, tab)) : stampCommandTab(created, tab);
24
+ },
25
+ };
26
+ }
27
+
28
+ /**
29
+ * The tab a command asked this pane to show. A stored choice wins until a
30
+ * newer command arrives. Another pane's params, such as Ticker Research
31
+ * around a nested tab, are ignored.
32
+ */
33
+ export function useCommandTab<T extends string>(
34
+ paneId: string,
35
+ stateKey: string,
36
+ tabs: readonly T[],
37
+ fallback: T,
38
+ ): [T, (next: T) => void] {
39
+ const instance = usePaneInstance();
40
+ const params = instance?.paneId === paneId ? instance.params : undefined;
41
+ const requested = params && (tabs as readonly string[]).includes(params.tab ?? "") ? params.tab as T : null;
42
+ const requestAt = params?.tabAt ?? "";
43
+ const [tab, setTab] = usePluginPaneState<T>(stateKey, requested ?? fallback);
44
+ const seen = useRef(requestAt);
45
+ useEffect(() => {
46
+ if (!requested || requestAt === seen.current) return;
47
+ seen.current = requestAt;
48
+ setTab(requested);
49
+ }, [requestAt, requested, setTab]);
50
+ return [tab, setTab];
51
+ }
@@ -1,12 +1,44 @@
1
+ import type { IssuerListingParams } from "../../../api-client/paths";
2
+ import type { DataProvider } from "../../../types/data-provider";
1
3
  import type { HeadlessPaneContext } from "../../../types/headless";
2
4
  import type { TimeRange } from "../../../time-series/range";
3
5
  import { clipPriceHistoryToRange } from "../../../time-series/history-window";
4
- import { parsePublicTickerKey } from "../../../utils/exchanges";
6
+ import { canonicalExchange, parsePublicTickerKey } from "../../../utils/exchanges";
7
+ import { nonUsSecListingVenue } from "../../../utils/sec";
5
8
 
6
9
  export async function resolveHeadlessInstrument(ctx: HeadlessPaneContext, symbol: string) {
7
10
  return ctx.resolveInstrument ? ctx.resolveInstrument(symbol) : parsePublicTickerKey(symbol);
8
11
  }
9
12
 
13
+ /**
14
+ * The venue and company an issuer lookup names for a listing abroad: the
15
+ * venue from the key or the saved listing, the company from the listing's own
16
+ * quote. A US listing or a symbol with no venue names neither.
17
+ */
18
+ export async function resolveIssuerListing(
19
+ key: string,
20
+ exchange: string | undefined,
21
+ marketData: Pick<DataProvider, "getQuote">,
22
+ ): Promise<IssuerListingParams> {
23
+ const venue = nonUsSecListingVenue(key, exchange);
24
+ if (!venue) return {};
25
+ try {
26
+ const quote = await marketData.getQuote(parsePublicTickerKey(key).symbol, venue);
27
+ const quoteVenue = canonicalExchange(quote.listingExchangeName || quote.exchangeName);
28
+ // A quote that prices another venue names another listing's company.
29
+ const name = !quoteVenue || quoteVenue === venue ? quote.name?.trim() : undefined;
30
+ return name ? { exchange: venue, name } : { exchange: venue };
31
+ } catch {
32
+ return { exchange: venue };
33
+ }
34
+ }
35
+
36
+ /** `resolveIssuerListing` for a report's ticker, with the venue of the saved listing when the key has none. */
37
+ export async function resolveHeadlessIssuerListing(ctx: HeadlessPaneContext, key: string): Promise<IssuerListingParams> {
38
+ const { exchange } = await resolveHeadlessInstrument(ctx, key);
39
+ return resolveIssuerListing(key, exchange, ctx.marketData);
40
+ }
41
+
10
42
  export async function loadHeadlessFinancials(ctx: HeadlessPaneContext, key: string) {
11
43
  const { symbol, exchange } = await resolveHeadlessInstrument(ctx, key);
12
44
  return ctx.marketData.getTickerFinancials(symbol, exchange ?? "");
@@ -180,10 +180,10 @@ export function useMembersMenu(registrationId: string, list: MemberList | null)
180
180
  const registry = getSharedRegistry();
181
181
  const symbols = list ? memberSymbols(list.members) : [];
182
182
  if (!list || !symbols.length || !dialog || !registry) return null;
183
- const destinations = MEMBER_DESTINATIONS.filter((destination) => {
184
- const owner = registry.getPaneTemplatePluginId?.(destination.templateId);
185
- return registry.paneTemplates.has(destination.templateId) && (!owner || !disabledPlugins.includes(owner));
186
- });
183
+ const destinations = MEMBER_DESTINATIONS.filter((destination) => (
184
+ registry.paneTemplates.has(destination.templateId)
185
+ && !registry.getDisabledPaneTemplateOwner?.(destination.templateId, disabledPlugins)
186
+ ));
187
187
  const choices = memberDestinationChoices(symbols, destinations);
188
188
  const text = memberListText(list, symbols.length);
189
189
  const openIn = () => {
@@ -1,6 +1,9 @@
1
1
  import type { HeadlessPaneFreshness } from "../../../types/headless";
2
2
  import type { Quote } from "../../../types/financials";
3
3
  import { isQuoteStaleForCurrentSession } from "../../../market-data/quotes/freshness";
4
+ import { delayedFeedLagMs } from "../../../market-data/market/freshness";
5
+ import { apiClient } from "../../../api-client";
6
+ import { CLOUD_NEWS_DELAY_HOURS, hasProAccess } from "../../../api-client/plan-rules";
4
7
 
5
8
  /**
6
9
  * Freshness declarations shared by built-in reports. Each `fn` report ends
@@ -41,17 +44,62 @@ export const REFERENCE_DATA: HeadlessPaneFreshness = { status: "not-a-feed", bas
41
44
  /** Company fundamentals, estimates and calendars: reported figures, not a feed. */
42
45
  export const REPORTED_DATA: HeadlessPaneFreshness = { status: "not-a-feed", basis: "reported data" };
43
46
 
47
+ /** Gloom Cloud's delayed quotes run behind each venue by a lag the app knows (`delayedFeedLagMs`). */
48
+ const CLOUD_QUOTE_PROVIDER_ID = "gloomberb-cloud";
49
+
50
+ interface QuoteFreshnessFields {
51
+ dataSource?: "live" | "delayed";
52
+ stale?: boolean;
53
+ /** How far a delayed Gloom Cloud quote runs behind its venue's trades. */
54
+ delayMinutes?: number;
55
+ /** The venue whose sessions the quote follows, so the report can say whether its market is open. */
56
+ sessionExchange?: string;
57
+ marketState?: Quote["marketState"];
58
+ }
59
+
44
60
  /**
45
61
  * The standard freshness fields of one quote, spread into a report row so the
46
- * report can say whether that row is live, delayed or stale for its session.
62
+ * report can say whether that row is live, delayed (and by how much) or stale
63
+ * for its session, and where its market stands.
47
64
  */
48
65
  export function quoteFreshnessFields(
49
66
  quote: Quote | null | undefined,
50
67
  now = Date.now(),
51
- ): { dataSource?: "live" | "delayed"; stale?: boolean } {
68
+ ): QuoteFreshnessFields {
52
69
  if (!quote) return {};
70
+ const exchange = quote.listingExchangeName || quote.exchangeName;
71
+ const delayed = quote.dataSource === "delayed" && quote.providerId === CLOUD_QUOTE_PROVIDER_ID;
53
72
  return {
54
73
  ...(quote.dataSource === "live" || quote.dataSource === "delayed" ? { dataSource: quote.dataSource } : {}),
55
74
  stale: isQuoteStaleForCurrentSession(quote, now),
75
+ ...(delayed ? { delayMinutes: delayedFeedLagMs(exchange) / 60_000 } : {}),
76
+ ...(exchange ? { sessionExchange: exchange } : {}),
77
+ ...(quote.marketState ? { marketState: quote.marketState } : {}),
56
78
  };
57
79
  }
80
+
81
+ /**
82
+ * Whether the Gloom Cloud session a report reads with has real-time access:
83
+ * no session reads as a free account; a session is asked for its plan. Null
84
+ * when the plan cannot be read, so the report does not guess a delay.
85
+ */
86
+ export async function cloudRealtimeAccess(): Promise<boolean | null> {
87
+ if (!apiClient.isSignedIn()) return false;
88
+ try {
89
+ return hasProAccess(apiClient.getCurrentUser() ?? await apiClient.getSession());
90
+ } catch {
91
+ return null;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Gloom Cloud news stories, dated by the newest: held back
97
+ * `CLOUD_NEWS_DELAY_HOURS` without real-time access, the live wire with it.
98
+ * The list is a history, so its last story is not stale data.
99
+ */
100
+ export function cloudNewsFreshness(realtime: boolean | null): HeadlessPaneFreshness {
101
+ const stories: HeadlessPaneFreshness = { observedKey: "publishedAt", oldest: null };
102
+ if (realtime === true) return { ...stories, status: "live" };
103
+ if (realtime === false) return { ...stories, status: "delayed", delayMinutes: CLOUD_NEWS_DELAY_HOURS * 60 };
104
+ return { ...stories, status: "not-a-feed", basis: "published stories" };
105
+ }
@@ -1,5 +1,7 @@
1
- import { useCallback } from "react";
1
+ import { useCallback, useMemo } from "react";
2
2
  import { ApiRequestError } from "../../../api-client/errors";
3
+ import { listingAbroad, type IssuerListingParams } from "../../../api-client/paths";
4
+ import type { TickerRecord } from "../../../types/ticker";
3
5
  import { useAsyncResource } from "../../../react/async-resource";
4
6
  import { usePaneTickerIdentity } from "../../../state/hooks/pane-ticker";
5
7
  import { parsePublicTickerKey } from "../../../utils/exchanges";
@@ -22,6 +24,28 @@ export function listingIdentity(key: string | null | undefined, savedExchange =
22
24
  return { symbol: parsed.symbol.toUpperCase(), exchange: parsed.exchange ?? savedExchange };
23
25
  }
24
26
 
27
+ /**
28
+ * The venue and company an SEC issuer read names for the pane's listing, from
29
+ * the pane's ticker as the SEC filings pane reads it: the key's venue, else the
30
+ * saved one, and the company the listing was saved as (listingAbroad). Nothing
31
+ * for a US listing or a symbol with no venue. A name that is only the symbol
32
+ * is left out, so the server falls back to the name on a quote it holds.
33
+ */
34
+ function issuerListing(ticker: TickerRecord | null | undefined): IssuerListingParams {
35
+ if (!ticker) return {};
36
+ const key = ticker.metadata.ticker.trim().toUpperCase();
37
+ const name = ticker.metadata.name?.trim();
38
+ const known = !!name && ![key, parsePublicTickerKey(key).symbol].includes(name.toUpperCase());
39
+ const abroad = listingAbroad(key, ticker.metadata.exchange, known ? name : undefined);
40
+ return { exchange: abroad?.exchange, name: abroad?.name };
41
+ }
42
+
43
+ /** `issuerListing` as a stable value, for loader dependencies. */
44
+ export function useIssuerListing(ticker: TickerRecord | null | undefined): IssuerListingParams {
45
+ const { exchange, name } = issuerListing(ticker);
46
+ return useMemo(() => ({ exchange, name }), [exchange, name]);
47
+ }
48
+
25
49
  /**
26
50
  * The pane's symbol for research panes that load their own data. Identity only:
27
51
  * it does not observe the quote, so a price tick does not re-render the pane.
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import type { DataTableCell, DataTableVisibleRange, PaneFooterSegment } from "../../../components";
3
3
  import { useQuoteEntries } from "../../../market-data/hooks";
4
- import { marketStateColor, marketStateLabel } from "../../../market-data/market/status";
4
+ import { getRegularSessionDisplay, marketStateColor, marketStateLabel } from "../../../market-data/market/status";
5
5
  import type { InstrumentRef } from "../../../market-data/request-types";
6
6
  import type { QueryEntry } from "../../../market-data/result-types";
7
7
  import { buildQuoteKey, resolveEntryData } from "../../../market-data/selectors";
@@ -466,12 +466,31 @@ export type QuoteBoardCellKind = "status" | "price" | "change" | "changePercent"
466
466
  export interface QuoteBoardCellFormat {
467
467
  /** Spell out the session instead of a colored dot. */
468
468
  sessionText?: boolean;
469
- /** Called with a finite price. */
469
+ /** Called with the quote at its headline: a finite price. */
470
470
  formatPrice: (quote: Quote) => string;
471
- /** Called with a finite change; the text carries its sign. */
471
+ /** Called with the quote at its headline: a finite change; the text carries its sign. */
472
472
  formatChange: (quote: Quote) => string;
473
473
  }
474
474
 
475
+ /** Each quote's figures as the board shows them, cached so a held quote keeps one object. */
476
+ const boardHeadlines = new WeakMap<Quote, Quote>();
477
+
478
+ /**
479
+ * The quote with its regular session's price and move (getRegularSessionDisplay),
480
+ * as `ticker` reads it: after the close its close and move, never an extended
481
+ * print, which a board of these columns has nowhere to show.
482
+ */
483
+ export function boardHeadlineQuote(quote: Quote): Quote {
484
+ const cached = boardHeadlines.get(quote);
485
+ if (cached) return cached;
486
+ const display = getRegularSessionDisplay(quote);
487
+ const headline = !display || (display.price === quote.price && display.change === quote.change && display.changePercent === quote.changePercent)
488
+ ? quote
489
+ : { ...quote, price: display.price, change: display.change ?? Number.NaN, changePercent: display.changePercent ?? Number.NaN };
490
+ boardHeadlines.set(quote, headline);
491
+ return headline;
492
+ }
493
+
475
494
  /** A row is loading while it has no quote yet, so none of its cells shows a no-data dash. */
476
495
  export function isBoardRowLoading(state: BoardQuoteState | undefined): boolean {
477
496
  return !state?.quote && (state?.loading ?? true);
@@ -482,7 +501,7 @@ export function renderQuoteBoardCell(
482
501
  state: BoardQuoteState | undefined,
483
502
  format: QuoteBoardCellFormat,
484
503
  ): DataTableCell {
485
- const quote = state?.quote;
504
+ const quote = state?.quote ? boardHeadlineQuote(state.quote) : state?.quote;
486
505
  // One row must not mix a loading marker with a no-data marker.
487
506
  if (isBoardRowLoading(state)) return { text: kind === "status" ? "" : "…", color: colors.textDim };
488
507
 
@@ -517,7 +536,8 @@ export function renderQuoteBoardCell(
517
536
  case "time":
518
537
  return {
519
538
  text: formatQuoteTime(quote?.lastUpdated),
520
- value: quote?.lastUpdated ? new Date(quote.lastUpdated).toISOString().replace(".000Z", "Z") : null,
539
+ // A Date, so the export writes it whole and a rendered-view report dates the board by its quotes.
540
+ value: quote?.lastUpdated ? new Date(quote.lastUpdated) : null,
521
541
  color: colors.textDim,
522
542
  };
523
543
  }