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
@@ -1,8 +1,6 @@
1
1
  import type { AppConfig } from "../../../../types/config";
2
2
  import type { CliCommandContext } from "../../../../types/plugin";
3
- import type { TickerRecord } from "../../../../types/ticker";
4
3
  import { parseListingArg, type SavedListingName } from "../../../../cli/listing-arg";
5
- import { isUsListingExchange } from "../../../../utils/exchanges";
6
4
  import { findPortfolio, isManualPortfolio } from "../mutations";
7
5
  import { CASH_SYMBOL } from "../allocation";
8
6
 
@@ -23,8 +21,8 @@ export function failPortfolioCommand(ctx: CliCommandContext, error: unknown, fal
23
21
  return ctx.fail(fallback);
24
22
  }
25
23
 
26
- /** What add and remove report about the listing they changed, as JSON and as the sentence's subject. */
27
- export function listingFields(saved: SavedListingName) {
24
+ /** What every collection command reports about the listing it changed, as JSON and as the sentence's subject. */
25
+ export function listingFields(saved: Pick<SavedListingName, "key" | "symbol" | "exchange" | "name">) {
28
26
  return { listing: saved.key, symbol: saved.symbol, exchange: saved.exchange || null, name: saved.name };
29
27
  }
30
28
 
@@ -72,15 +70,3 @@ export function rejectCashSymbol(symbol: string, exchange: string | undefined, p
72
70
  `Record cash with: gloomberb portfolio cash set "${portfolioName}" <amount> [currency]. For the stock that trades as CASH, name its exchange: CASH:NASDAQ.`,
73
71
  );
74
72
  }
75
-
76
- /** `VTI (Vanguard Total Stock Market ETF)`, with the venue and currency for a listing outside the US. */
77
- export function describeTicker(ticker: TickerRecord): string {
78
- const { ticker: symbol, name, exchange, currency } = ticker.metadata;
79
- const foreign = !!exchange && !isUsListingExchange(exchange);
80
- const details = [
81
- name?.trim(),
82
- foreign ? exchange : undefined,
83
- foreign && currency && currency.toUpperCase() !== "USD" ? currency.toUpperCase() : undefined,
84
- ].filter((part): part is string => !!part);
85
- return details.length > 0 ? `${symbol} (${details.join(", ")})` : symbol;
86
- }
@@ -9,13 +9,18 @@ import type {
9
9
  import type { TickerRecord } from "../../../types/ticker";
10
10
  import { formatNumber, formatPercentRaw } from "../../../utils/format";
11
11
  import {
12
+ collectionQuoteHeadline,
13
+ extendedQuoteFields,
14
+ extendedSessionsOf,
12
15
  findCollection,
13
16
  loadCollectionQuotes,
14
17
  valuePortfolioAllocation,
15
18
  valuePortfolioPositions,
16
19
  type CollectionMatch,
17
20
  } from "./cli/render";
21
+ import { EXTENDED_SESSION_LABELS, type ExtendedSession } from "../../../market-data/market/status";
18
22
  import {
23
+ BROKER_ACCOUNT_MISSING_NOTE,
19
24
  CASH_SYMBOL,
20
25
  describeTargetSum,
21
26
  formatAllocationDrift,
@@ -60,6 +65,7 @@ function textOf(value: unknown): string | undefined {
60
65
  const POSITION_COLUMNS: HeadlessPaneColumn[] = [
61
66
  { key: "symbol", header: "Ticker" },
62
67
  { key: "name", header: "Name" },
68
+ { key: "exchange", header: "Exchange" },
63
69
  { key: "shares", header: "Qty", align: "right", format: quantity },
64
70
  { key: "avgCost", header: "Avg Cost", align: "right", format: unitMoney("positionCurrency"), description: "Per unit, in the position's currency." },
65
71
  { key: "price", header: "Last", align: "right", format: unitMoney("priceCurrency"), description: "In the quote's currency." },
@@ -78,6 +84,26 @@ const TARGET_COLUMNS: HeadlessPaneColumn[] = [
78
84
  { key: "tradeValue", header: "Trade Value", align: "right", format: signedAmount, description: "The trade in the portfolio's currency." },
79
85
  ];
80
86
 
87
+ /** The pre-market or after-hours print and its move from the regular close, for the rows that have one. */
88
+ function extendedColumn(session: ExtendedSession): HeadlessPaneColumn {
89
+ return {
90
+ key: "extendedPrice",
91
+ header: EXTENDED_SESSION_LABELS[session],
92
+ align: "right",
93
+ format: (value, row) => row.extendedSession === session && typeof value === "number"
94
+ ? `${unitMoney("priceCurrency")(value, row)} ${percent(row.extendedChangePercent)}`
95
+ : formatNumber(undefined),
96
+ description: "Measured from the regular close in Last.",
97
+ };
98
+ }
99
+
100
+ /** `columns` with an extended column after Chg for each session a row has a print for. */
101
+ function withExtendedColumns(columns: HeadlessPaneColumn[], sessions: readonly ExtendedSession[]): HeadlessPaneColumn[] {
102
+ if (sessions.length === 0) return columns;
103
+ const at = columns.findIndex((column) => column.key === "changePercent") + 1;
104
+ return [...columns.slice(0, at), ...sessions.map(extendedColumn), ...columns.slice(at)];
105
+ }
106
+
81
107
  const WATCHLIST_COLUMNS: HeadlessPaneColumn[] = [
82
108
  { key: "symbol", header: "Ticker" },
83
109
  { key: "name", header: "Name" },
@@ -153,8 +179,9 @@ async function portfolioHoldings(
153
179
  const gross = held.reduce((sum, entry) => sum + Math.abs(finite(entry.row.marketValue) ?? 0), 0);
154
180
  const sum = (key: string) => held.reduce((total, entry) => total + (finite(entry.row[key]) ?? 0), 0);
155
181
  const seen = new Set<string>();
182
+ const extendedSessions = extendedSessionsOf(held.map(({ extended }) => extended?.session));
156
183
  const rows = held
157
- .map(({ ticker, activeQuote, row }) => {
184
+ .map(({ ticker, headline, extended, row }) => {
158
185
  const marketValue = finite(row.marketValue);
159
186
  // A ticker held in several lots carries its allocation on its first row.
160
187
  const figures = seen.has(ticker.metadata.ticker) ? undefined : allocationBySymbol.get(ticker.metadata.ticker);
@@ -169,9 +196,11 @@ async function portfolioHoldings(
169
196
  shares: finite(row.shares),
170
197
  avgCost: finite(row.avgCost),
171
198
  positionCurrency: row.positionCurrency ?? null,
199
+ // Last and Chg are the regular session; market value and P&L are at the live price.
172
200
  price: finite(row.quotePrice),
173
201
  priceCurrency: row.quoteCurrency ?? null,
174
- changePercent: finite(activeQuote?.changePercent),
202
+ changePercent: finite(headline?.changePercent),
203
+ ...extendedQuoteFields(extended),
175
204
  marketValue,
176
205
  unrealizedPnl: finite(row.unrealizedPnl),
177
206
  weight: figures?.weight ?? null,
@@ -210,11 +239,12 @@ async function portfolioHoldings(
210
239
  ? [`${rows.length - limit} more position${rows.length - limit === 1 ? "" : "s"} not shown; totals include every position.`]
211
240
  : []),
212
241
  ...(targetNote ? [targetNote] : []),
242
+ ...(target.portfolio.brokerInstanceId && !resolved.account ? [BROKER_ACCOUNT_MISSING_NOTE] : []),
213
243
  ];
214
244
 
215
245
  return {
216
246
  freshness: { source: broker ? "Your broker account and Gloom Cloud" : "Local portfolio and Gloom Cloud" },
217
- columns: showTargets ? [...POSITION_COLUMNS, ...TARGET_COLUMNS] : POSITION_COLUMNS,
247
+ columns: withExtendedColumns(showTargets ? [...POSITION_COLUMNS, ...TARGET_COLUMNS] : POSITION_COLUMNS, extendedSessions),
218
248
  rows: shown,
219
249
  complete: unavailable.length === 0,
220
250
  ...(unavailable.length
@@ -252,21 +282,24 @@ async function watchlistHoldings(
252
282
  ctx.signal.throwIfAborted();
253
283
  const rows = tickers.map((ticker) => {
254
284
  const quote = quotes.get(ticker.metadata.ticker);
285
+ // As the watchlist pane shows it: the regular session, then any extended print from its close.
286
+ const { headline, extended } = collectionQuoteHeadline(quote);
255
287
  return {
256
288
  ...quoteFreshnessFields(quote),
257
289
  updatedAt: quote?.lastUpdated ?? null,
258
290
  symbol: ticker.metadata.ticker,
259
291
  name: ticker.metadata.name ?? null,
260
292
  exchange: ticker.metadata.exchange || null,
261
- price: finite(quote?.price),
293
+ price: finite(headline?.price ?? quote?.price),
262
294
  priceCurrency: quote?.currency ?? null,
263
- changePercent: finite(quote?.changePercent),
295
+ changePercent: finite(headline ? headline.changePercent : quote?.changePercent),
296
+ ...extendedQuoteFields(extended),
264
297
  };
265
298
  });
266
299
  const shown = rows.slice(0, limit);
267
300
  return {
268
301
  freshness: { source: "Local watchlist and Gloom Cloud" },
269
- columns: WATCHLIST_COLUMNS,
302
+ columns: withExtendedColumns(WATCHLIST_COLUMNS, extendedSessionsOf(shown.map((row) => row.extendedSession))),
270
303
  rows: shown,
271
304
  metadata: {
272
305
  collection: { kind: "watchlist", id: target.id, name: target.name },
@@ -286,7 +319,7 @@ async function watchlistHoldings(
286
319
  export const collectionHoldingsHeadless: HeadlessPaneDefinition<"rows"> = {
287
320
  shape: "rows",
288
321
  description:
289
- "Positions held in a portfolio, broker or manual: symbol, quantity, average cost, last price, market value, unrealized P&L and weight of the total with cash, largest first, then the cash line and totals. With target weights set, each row adds its target, drift and the trade to reach it. For a watchlist, its tickers with quotes. Takes a portfolio or watchlist ID; the first portfolio when omitted.",
322
+ "Positions held in a portfolio, broker or manual: symbol, exchange, shares, average cost, last price, market value, unrealized P&L and weight of the total with cash, largest first, then the cash line and totals. With target weights set, each row adds its target, drift and the trade to reach it. For a watchlist, its tickers with quotes. Takes a portfolio or watchlist ID; the first portfolio when omitted.",
290
323
  discovery: {
291
324
  dataRequirements: ["Local portfolios, watchlists and synced broker positions; current quotes"],
292
325
  limitations: ["Unrealized P&L on current positions; excludes realized trades, distributions and cash flows"],
@@ -172,7 +172,8 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
172
172
  const flashSymbols = useQuoteFlashMap(financialsMap, valueFlashingEnabled);
173
173
 
174
174
  const accountStateInput = useMemo(() => ({ brokerAccounts, config }), [brokerAccounts, config]);
175
- const { accountState, accountsError } = usePortfolioAccountState(currentPortfolio, accountStateInput);
175
+ const { accountState, accountsError, brokerError } = usePortfolioAccountState(currentPortfolio, accountStateInput);
176
+ const brokerPortfolio = isPortfolioTab && !!currentPortfolio?.brokerInstanceId;
176
177
  const portfolioTargets = currentPortfolio?.targetWeights;
177
178
  const columns = useMemo(
178
179
  () => resolveVisibleColumns(paneSettings.columnIds, isPortfolioTab, { targetWeights: hasTargetWeights(currentPortfolio) }),
@@ -338,7 +339,8 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
338
339
  currency: totalsCurrency,
339
340
  baseCurrency: config.baseCurrency,
340
341
  manualCash,
341
- }), [config.baseCurrency, convertAccountValue, isPortfolioTab, manualCash, portfolioSummaryTotals, summaryAccountState, totalsCurrency]);
342
+ brokerPortfolio,
343
+ }), [brokerPortfolio, config.baseCurrency, convertAccountValue, isPortfolioTab, manualCash, portfolioSummaryTotals, summaryAccountState, totalsCurrency]);
342
344
  // The header row sits in the pane's one-cell side padding, like the table.
343
345
  const summaryWidth = Math.max(0, width - 2);
344
346
  const summaryLayout = useMemo(() => layoutPortfolioSummaryHeader(summarySegments, summaryWidth, {
@@ -476,7 +478,7 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
476
478
  accountState: summaryAccountState,
477
479
  accountStatusText: accountsError
478
480
  ? `Accounts unavailable: ${accountsError}`
479
- : isPortfolioTab && currentPortfolio?.brokerInstanceId && !accountState ? "Acct missing" : undefined,
481
+ : brokerPortfolio ? brokerError ?? (accountState ? undefined : "Acct missing") : undefined,
480
482
  financialsMap,
481
483
  isPortfolioTab,
482
484
  refreshingSize,
@@ -485,7 +487,8 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
485
487
  }), [financialsMap, sortedTickers], [
486
488
  accountState,
487
489
  accountsError,
488
- currentPortfolio?.brokerInstanceId,
490
+ brokerError,
491
+ brokerPortfolio,
489
492
  isPortfolioTab,
490
493
  portfolioSummaryTotals,
491
494
  refreshingSize,
@@ -12,6 +12,7 @@ import { displayWidth, formatPercentRaw } from "../../../../utils/format";
12
12
  import { getBrokerInstance } from "../../../../utils/broker-instances";
13
13
  import {
14
14
  resolvePortfolioAccountMetrics,
15
+ resolvePortfolioLeverage,
15
16
  resolvePortfolioMarketValue,
16
17
  resolvePortfolioNetLiquidation,
17
18
  type BrokerSnapshotBasis,
@@ -223,10 +224,13 @@ export function buildPortfolioSummarySegments({
223
224
  currency = "USD",
224
225
  baseCurrency = "USD",
225
226
  manualCash = null,
227
+ brokerPortfolio = false,
226
228
  }: {
227
229
  totals: PortfolioSummaryTotals;
228
230
  accountState: PortfolioSummaryAccountState | null;
229
231
  isPortfolioTab?: boolean;
232
+ /** Synced from a broker, so it is worth its account's equity even while the account is missing. */
233
+ brokerPortfolio?: boolean;
230
234
  /** Into the totals currency. */
231
235
  convertAccountValue?: (value: number) => number;
232
236
  /** The totals currency. */
@@ -249,6 +253,9 @@ export function buildPortfolioSummarySegments({
249
253
 
250
254
  const candidates: PortfolioSummarySegment[] = [];
251
255
  const account = accountState?.account;
256
+ // A broker account is worth its equity. Its positions alone are its gross,
257
+ // which margin or shorts put above it, so that sum never reads as its value.
258
+ const broker = brokerPortfolio || !!account;
252
259
  const basis = accountState?.snapshotBasis;
253
260
  const accountMetrics = resolvePortfolioAccountMetrics(totals, account, convertAccountValue, basis);
254
261
  const totalMarketValue = resolvePortfolioMarketValue(totals, account, convertAccountValue, basis);
@@ -269,15 +276,17 @@ export function buildPortfolioSummarySegments({
269
276
  ]));
270
277
  }
271
278
 
272
- if (netLiquidation != null) {
279
+ // Unknown without the account, so it reads as missing rather than disappearing,
280
+ // unless cash entered by hand gives the Total above in its place.
281
+ if (broker && (netLiquidation != null || !manualCash)) {
273
282
  candidates.push(createSummarySegment("netliq", [
274
283
  { text: "Net Liq", tone: "label" },
275
- { text: money(netLiquidation), tone: "value", bold: true },
284
+ { text: money(netLiquidation ?? Number.NaN), tone: "value", bold: true },
276
285
  ]));
277
286
  }
278
287
 
279
288
  candidates.push(createSummarySegment("val", [
280
- { text: totals.hasShorts ? "Gross" : "Val", tone: "label" },
289
+ { text: broker || totals.hasShorts ? "Gross" : "Val", tone: "label" },
281
290
  { text: money(totalMarketValue), tone: "value", bold: true },
282
291
  ]));
283
292
 
@@ -289,7 +298,7 @@ export function buildPortfolioSummarySegments({
289
298
  }
290
299
 
291
300
  // A broker account always states its cash, so a missing balance reads as unknown rather than zero.
292
- if (account) candidates.push(accountValue("cash", "Cash", account.totalCashValue ?? Number.NaN)!);
301
+ if (broker && !manualCash) candidates.push(accountValue("cash", "Cash", account?.totalCashValue ?? Number.NaN)!);
293
302
  if (manualCash) {
294
303
  const drift = formatAllocationDrift(manualCash.drift);
295
304
  candidates.push(createSummarySegment("cash", [
@@ -318,6 +327,14 @@ export function buildPortfolioSummarySegments({
318
327
 
319
328
  if (!account) return candidates;
320
329
 
330
+ const leverage = resolvePortfolioLeverage(totalMarketValue, netLiquidation);
331
+ // Only while the account borrows or is short enough to read above 1.0x, as the analytics pane rounds it.
332
+ const lev = leverage != null && leverage >= 1.05
333
+ ? createSummarySegment("lev", [
334
+ { text: "Lev", tone: "label" },
335
+ { text: `${leverage.toFixed(1)}x`, tone: "value", bold: true },
336
+ ])
337
+ : null;
321
338
  const realized = accountMetrics.realizedPnl != null
322
339
  ? createSummarySegment("realized", [
323
340
  { text: "Realized", tone: "label" },
@@ -327,6 +344,7 @@ export function buildPortfolioSummarySegments({
327
344
  return [
328
345
  ...candidates,
329
346
  ...[
347
+ lev,
330
348
  realized,
331
349
  accountValue("settled", "Settled", account.settledCash),
332
350
  accountValue("avail", "Avail", account.availableFunds),
@@ -84,6 +84,12 @@ export interface PortfolioAccountStateResult {
84
84
  accountState: ResolvedPortfolioAccountState | null;
85
85
  /** Set when the broker refused to list accounts, so "no cash" is not mistaken for a clean empty. */
86
86
  accountsError: string | null;
87
+ /**
88
+ * The broker's last call failed, such as the sync at startup after its
89
+ * sign-in lapsed. Accounts are listed only while it is connected, so this
90
+ * is the only word of why the account is missing or stale.
91
+ */
92
+ brokerError: string | null;
87
93
  }
88
94
 
89
95
  /**
@@ -111,5 +117,9 @@ export function usePortfolioAccountState(
111
117
  () => resolvePortfolioAccountState(portfolio, state, snapshot),
112
118
  [portfolio, snapshot, state.brokerAccounts, state.config],
113
119
  );
114
- return useMemo(() => ({ accountState, accountsError: live.error }), [accountState, live.error]);
120
+ const brokerError = live.status?.state === "error" ? live.status.message?.trim() || "Broker unavailable" : null;
121
+ return useMemo(
122
+ () => ({ accountState, accountsError: live.error, brokerError }),
123
+ [accountState, brokerError, live.error],
124
+ );
115
125
  }
@@ -46,7 +46,10 @@ export function realizedVolHeadless(initialView: "graph" | "cone"): HeadlessPane
46
46
  estimator: ESTIMATOR_OPTIONS.find(({ value }) => value === args.options.estimator)?.value,
47
47
  windows, lookbackYears: Number(args.options.lookbackYears) === 2 ? 2 : 1 });
48
48
  const errors = [history.error, iv?.error].filter((value): value is string => !!value);
49
+ // The cone rows are as of the last daily bar, not of when the report ran.
50
+ const lastBar = model.asOf && Number.isFinite(model.asOf.getTime()) ? model.asOf.toISOString().slice(0, 10) : null;
49
51
  return {
52
+ freshness: { asOf: lastBar },
50
53
  sections: [
51
54
  { title: "Volatility cone", columns: [
52
55
  { key: "window", header: "Sessions" },
@@ -164,7 +164,7 @@ export function RealizedVolPane({ width, height, focused }: PaneProps) {
164
164
  { id: "estimator", label: "Estimator", value: estimator, options: ESTIMATOR_OPTIONS, onChange: (value: string) => setEstimator(value as RealizedVolatilityEstimator) },
165
165
  { id: "lookback", label: "Lookback", value: String(lookback), options: [{ value: "1", label: "1Y" }, { value: "2", label: "2Y" }], onChange: setLookback },
166
166
  ]} meta={reference ? `ATM IV observed ${reference.date.toISOString().slice(0, 16).replace("T", " ")} UTC` : "Annualized %"} />
167
- {!symbol ? <EmptyState title="Choose a ticker." /> : <PaneStatusBody subject="realized volatility" loading={history.loading && !model}
167
+ {!symbol ? <EmptyState title="Select a ticker." /> : <PaneStatusBody subject="realized volatility" loading={history.loading && !model}
168
168
  error={!model ? history.error ?? identityError ?? null : null} empty={!!model && !model.history.length}>
169
169
  {model && view === "cone" ? <>
170
170
  <StatGrid items={coneStats} width={width} />
@@ -25,7 +25,7 @@ import { formatPercent } from "../../../utils/format";
25
25
  import { useResolvedEntryValue, useSecFilingDocuments, useSecFilingsQuery } from "../../../market-data/hooks";
26
26
  import { instrumentFromTicker } from "../../../market-data/request-types";
27
27
  import { usePaneTicker } from "../../../state/app/context";
28
- import { isUsEquityTicker } from "../../../utils/sec";
28
+ import { mayBeUsEquityTicker } from "../../../utils/sec";
29
29
  import { useAssetData, usePluginPaneState } from "../../runtime";
30
30
  import { handleRefreshKey } from "../../../components/data-table/table-pane";
31
31
  import { SignInWall } from "../cloud/auth-actions";
@@ -496,7 +496,7 @@ export function CorporateActionsView({
496
496
  : null;
497
497
  const instrument = useMemo(() => instrumentFromTicker(ticker, symbol), [symbol, ticker]);
498
498
  const secFilingsEntry = useSecFilingsQuery(
499
- openRow?.status === "Earnings" && instrument && isUsEquityTicker(ticker)
499
+ openRow?.status === "Earnings" && instrument && mayBeUsEquityTicker(ticker)
500
500
  ? { instrument, count: SEC_EVENT_FILING_LIMIT }
501
501
  : null,
502
502
  );
@@ -558,7 +558,7 @@ export function EquityDiagnosticView({ focused, width, height }: {
558
558
  // Walls and empty states are drawn straight into the pane, like every other
559
559
  // pane's; only a report sits in the padded scroll area.
560
560
  if (!symbol) {
561
- return <PaneStatusBody empty emptyTitle="No ticker selected." emptyMessage="Move the cursor in a list pane to populate this view." />;
561
+ return <PaneStatusBody empty emptyTitle="Select a ticker." emptyMessage="Move the cursor in a list pane to populate this view." />;
562
562
  }
563
563
  if (signInRequired || verificationRequired) {
564
564
  return <SignInWall placement="diag-signin" width={width} height={height} symbol={symbol} exchange={exchange} action="run the Equity Diagnostic" needsVerification={verificationRequired} />;
@@ -1,5 +1,6 @@
1
1
  import { comparablePriceEarnings, notMeaningfulMultiples } from "../../../utils/price-earnings";
2
2
  import { selectMarketCapitalization } from "../../../utils/market-capitalization";
3
+ import { reportedEnterpriseValue } from "../../../utils/fundamentals";
3
4
  import type { Quote, TickerFinancials } from "../../../types/financials";
4
5
 
5
6
  export const RELATIVE_VALUATION_STALE_QUOTE_NOTICE = "Quote stale: quote-based values unavailable";
@@ -10,6 +11,9 @@ export function relativeValuationValues(financials: TickerFinancials | null) {
10
11
  const fundamentals = financials?.fundamentals;
11
12
  const quoteStale = quote?.stale === true;
12
13
  const capitalization = selectMarketCapitalization(quoteStale ? undefined : quote, fundamentals);
14
+ const enterpriseValue = reportedEnterpriseValue(fundamentals);
15
+ // A zero or non-finite enterprise value is the source's gap, and the ratio it serves beside it comes from the same observation.
16
+ const enterpriseValueGap = fundamentals?.enterpriseValue != null && enterpriseValue == null;
13
17
  const compatibleCurrency = !!fundamentals?.financialCurrency && !!quote?.currency
14
18
  && fundamentals.financialCurrency === quote.currency;
15
19
  const reportedMultiples = {
@@ -47,11 +51,11 @@ export function relativeValuationValues(financials: TickerFinancials | null) {
47
51
  /** Why a multiple is N/M: its earnings base is zero or a loss. */
48
52
  notMeaningful,
49
53
  // Vendor ADR ratios can mix unverified valuation and reporting units too.
50
- evSales: !compatibleCurrency ? null
54
+ evSales: !compatibleCurrency || enterpriseValueGap ? null
51
55
  : fundamentals?.enterpriseToRevenue != null && Number.isFinite(fundamentals.enterpriseToRevenue)
52
56
  ? fundamentals.enterpriseToRevenue
53
- : fundamentals?.enterpriseValue != null && fundamentals.revenue != null && fundamentals.revenue > 0
54
- ? fundamentals.enterpriseValue / fundamentals.revenue : null,
57
+ : enterpriseValue != null && fundamentals?.revenue != null && fundamentals.revenue > 0
58
+ ? enterpriseValue / fundamentals.revenue : null,
55
59
  fcfYield: capitalization && fundamentals?.financialCurrency === capitalization.currency
56
60
  && fundamentals.freeCashFlow != null && Number.isFinite(fundamentals.freeCashFlow) && capitalization.value > 0
57
61
  ? fundamentals.freeCashFlow / capitalization.value : null,
@@ -4,7 +4,9 @@ import type {
4
4
  RevenueBreakdownPayload,
5
5
  RevenueBreakdownView,
6
6
  } from "../../../api-client/revenue-breakdown";
7
+ import type { IssuerListingParams } from "../../../api-client/paths";
7
8
  import { createPluginCache } from "../../../data/plugin-cache";
9
+ import { secListingKey } from "../../../utils/sec";
8
10
 
9
11
  /**
10
12
  * Revenue by product, segment or region comes from Gloom Cloud, which reads
@@ -59,10 +61,12 @@ export async function fetchRevenueBreakdown(
59
61
  symbol: string,
60
62
  view: RevenueBreakdownView,
61
63
  client: Pick<typeof apiClient, "getCloudRevenueBreakdown"> = apiClient,
64
+ listing?: IssuerListingParams,
62
65
  ): Promise<RevenueBreakdownPayload> {
63
66
  try {
64
- return validateRevenueBreakdown(await client.getCloudRevenueBreakdown(symbol, view));
67
+ return validateRevenueBreakdown(await client.getCloudRevenueBreakdown(symbol, view, listing));
65
68
  } catch (error) {
69
+ // 404 is also a listing abroad whose company the SEC does not know.
66
70
  if (error instanceof ApiRequestError && error.status === 404) throw new Error(NO_BREAKDOWN);
67
71
  throw error;
68
72
  }
@@ -74,8 +78,9 @@ export interface RevenueResource {
74
78
  refreshError: string | null;
75
79
  }
76
80
 
77
- const cacheKey = (symbol: string, view: RevenueBreakdownView, pro: boolean) =>
78
- `${symbol.toUpperCase()}:${view}:${pro ? "full" : "preview"}`;
81
+ // SAN in Paris is not SAN in New York: a listing abroad is cached under its venue.
82
+ const cacheKey = (symbol: string, view: RevenueBreakdownView, pro: boolean, listing?: IssuerListingParams) =>
83
+ `${secListingKey(symbol, listing?.exchange)}:${view}:${pro ? "full" : "preview"}`;
79
84
 
80
85
  // Seeds the first paint while the mount load runs; cache age is not staleness,
81
86
  // and a failed refresh reports its own.
@@ -83,8 +88,9 @@ export function cachedRevenueBreakdown(
83
88
  symbol: string,
84
89
  view: RevenueBreakdownView,
85
90
  pro: boolean,
91
+ listing?: IssuerListingParams,
86
92
  ): RevenueResource | null {
87
- const cached = revenueBreakdownCache.get(cacheKey(symbol, view, pro), { allowExpired: true });
93
+ const cached = revenueBreakdownCache.get(cacheKey(symbol, view, pro, listing), { allowExpired: true });
88
94
  if (!cached) return null;
89
95
  try {
90
96
  return { payload: validateRevenueBreakdown(cached.data), stale: false, refreshError: null };
@@ -98,10 +104,11 @@ export async function loadRevenueBreakdown(
98
104
  view: RevenueBreakdownView,
99
105
  pro: boolean,
100
106
  force = false,
107
+ listing?: IssuerListingParams,
101
108
  ): Promise<RevenueResource> {
102
109
  const result = await revenueBreakdownCache.load(
103
- cacheKey(symbol, view, pro),
104
- () => fetchRevenueBreakdown(symbol, view),
110
+ cacheKey(symbol, view, pro, listing),
111
+ () => fetchRevenueBreakdown(symbol, view, apiClient, listing),
105
112
  { force },
106
113
  );
107
114
  // A company that stopped reporting a breakdown must not keep showing an old one.
@@ -3,6 +3,7 @@ import type { HeadlessPaneDefinition } from "../../../types/plugin";
3
3
  import { fetchRevenueBreakdown } from "./client";
4
4
  import { quarterLabel, reportedSpan } from "./model";
5
5
  import { SEC_FILINGS } from "../shared/report-freshness";
6
+ import { resolveHeadlessIssuerListing } from "../shared/headless-market-data";
6
7
 
7
8
  export const revenueBreakdownHeadless: HeadlessPaneDefinition<"bundle"> = {
8
9
  shape: "bundle",
@@ -34,10 +35,12 @@ export const revenueBreakdownHeadless: HeadlessPaneDefinition<"bundle"> = {
34
35
  ],
35
36
  describe: (args) => `Revenue breakdown | ${args.symbols[0]}`,
36
37
  async load(args, ctx) {
38
+ const symbol = args.symbols[0]!;
37
39
  const data = reportedSpan(await fetchRevenueBreakdown(
38
- args.symbols[0]!,
40
+ symbol,
39
41
  (args.options.view as RevenueBreakdownView | undefined) ?? "product",
40
42
  ctx.apiClient,
43
+ await resolveHeadlessIssuerListing(ctx, symbol),
41
44
  ));
42
45
  return {
43
46
  complete: data.access === "full",
@@ -30,7 +30,7 @@ import { Blurred, LockedOverlay, UpgradeLabel } from "../shared/locked-rows";
30
30
  import { usePaneStatusFooter } from "../../../components/layout/pane/status-footer";
31
31
  import { usePlanAccess } from "../../../api-client/plan-access";
32
32
  import { useResearchCloudSession } from "../shared/research-cloud-session";
33
- import { listingIdentity } from "../shared/ticker-request";
33
+ import { listingIdentity, useIssuerListing } from "../shared/ticker-request";
34
34
  import { QuarterBars, type BarHover } from "./bars";
35
35
  import {
36
36
  cachedRevenueBreakdown,
@@ -82,17 +82,20 @@ function RevenueBreakdownView({ width, height, focused }: { width: number; heigh
82
82
  const openUpgrade = useCloudUpgradeAction("seg");
83
83
  const identity = listingIdentity(boundSymbol, ticker?.metadata.exchange ?? "");
84
84
  const symbol = identity?.symbol ?? null;
85
+ const { name } = useIssuerListing(ticker);
86
+ const exchange = identity?.exchange ?? "";
87
+ const listing = useMemo(() => ({ exchange, name }), [exchange, name]);
85
88
  const [requestedView, setView] = usePaneSettingValue<RevenueBreakdownView>("view", "product");
86
89
  const [mode, setMode] = usePluginPaneState<RevenueMode>("revenue:mode", "trend");
87
90
  const [sort, setSort] = usePluginPaneState<RevenueSort>("revenue:sort", { column: "revenue", direction: "desc" });
88
91
  const view = REVENUE_VIEWS.includes(requestedView) ? requestedView : "product";
89
92
 
90
93
  const loader = useCallback(
91
- (force: boolean) => loadRevenueBreakdown(symbol!, view, pro, force),
92
- [symbol, view, pro, session.requestKey],
94
+ (force: boolean) => loadRevenueBreakdown(symbol!, view, pro, force, listing),
95
+ [symbol, listing, view, pro, session.requestKey],
93
96
  );
94
97
  const resource = useAsyncResource(symbol ? loader : null, {
95
- initialData: () => (symbol ? cachedRevenueBreakdown(symbol, view, pro) : null),
98
+ initialData: () => (symbol ? cachedRevenueBreakdown(symbol, view, pro, listing) : null),
96
99
  clearOnError: (error) => error instanceof Error && error.message === NO_BREAKDOWN,
97
100
  });
98
101
  useAutoRefresh(resource.updatedAt, resource.load);
@@ -36,7 +36,11 @@ export const reverseDcfHeadless: HeadlessPaneDefinition<"bundle"> = {
36
36
  ...Object.fromEntries(row.implied.map((value, index) => [`t${index}`, implied(value)])) })) },
37
37
  ],
38
38
  complete: !inputs.stale && !inputs.error && !model.error,
39
- ...(inputs.stale ? { freshness: { status: "stale" as const } } : {}),
39
+ // Dated by the price the enterprise value is marked at, not by when the report ran.
40
+ freshness: {
41
+ asOf: inputs.financials?.quote?.lastUpdated ?? null,
42
+ ...(inputs.stale ? { status: "stale" as const } : {}),
43
+ },
40
44
  unavailableSymbols: model.implied ? [] : [instrument.symbol],
41
45
  errors: [inputs.error, model.error].filter((value): value is string => !!value),
42
46
  metadata: { currency: model.currency, discountRate, terminalGrowth: TERMINAL_GROWTH, discountRates: DISCOUNT_RATES,
@@ -1,4 +1,5 @@
1
1
  import type { TickerFinancials } from "../../../types/financials";
2
+ import { reportedEnterpriseValue } from "../../../utils/fundamentals";
2
3
 
3
4
  /** Years of explicit growth before the terminal value. */
4
5
  export const FORECAST_YEARS = 10;
@@ -65,7 +66,7 @@ function pastFcfGrowth(financials: TickerFinancials): ReverseDcfModel["pastGrowt
65
66
  export function projectReverseDcf(financials: TickerFinancials | null, options: { symbol: string; discountRate: number }): ReverseDcfModel {
66
67
  const fundamentals = financials?.fundamentals;
67
68
  const currency = fundamentals?.financialCurrency ?? financials?.financialCurrency ?? null;
68
- const enterpriseValue = fundamentals?.enterpriseValue ?? null;
69
+ const enterpriseValue = reportedEnterpriseValue(fundamentals) ?? null;
69
70
  const freeCashFlow = fundamentals?.freeCashFlow ?? null;
70
71
  const empty: ReverseDcfModel = { symbol: options.symbol, currency, enterpriseValue, freeCashFlow, fcfYield: null, implied: null,
71
72
  pastGrowth: financials ? pastFcfGrowth(financials) : null, sensitivity: [], error: null };
@@ -91,7 +91,7 @@ export function ReverseDcfPane({ width, height, focused }: PaneProps) {
91
91
 
92
92
  return <Box width={width} height={height} flexDirection="column" overflow="hidden">
93
93
  <QueryBar width={width} filters={[{ id: "discount", label: "Discount", value: String(discount), options: DISCOUNT_OPTIONS, onChange: setDiscount }]} />
94
- {!symbol ? <EmptyState title="Choose a ticker." /> : <PaneStatusBody subject="reverse DCF" loading={inputs.loading && !model}
94
+ {!symbol ? <EmptyState title="Select a ticker." /> : <PaneStatusBody subject="reverse DCF" loading={inputs.loading && !model}
95
95
  error={!model ? inputs.error ?? identityError ?? null : model.error} empty={false}>
96
96
  {model && !model.error ? <>
97
97
  <StatGrid items={stats} width={width} />
@@ -4,7 +4,9 @@ import {
4
4
  type CloudRiskReportPayload,
5
5
  } from "../../../api-client";
6
6
  import { ApiRequestError, isPermanentClientError } from "../../../api-client/errors";
7
+ import { listingAbroad, type IssuerListingParams } from "../../../api-client/paths";
7
8
  import type { HeadlessPaneApiClient, PluginPersistence } from "../../../types/plugin";
9
+ import { secListingKey } from "../../../utils/sec";
8
10
 
9
11
  /** Annual reports are immutable; discovery of newly produced reports is not. */
10
12
  const LIST_KIND = "reports";
@@ -88,12 +90,20 @@ function loadCached<T extends object>(
88
90
  return request.promise;
89
91
  }
90
92
 
91
- export function loadRiskReportsWithClient(client: RiskApiClient, ticker: string, options?: { force?: boolean }): Promise<RiskReportsResult> {
92
- const key = ticker.trim().toUpperCase();
93
+ interface RiskLoadOptions {
94
+ force?: boolean;
95
+ /** The listing's venue and company, for a listing outside the US. */
96
+ listing?: IssuerListingParams;
97
+ }
98
+
99
+ export function loadRiskReportsWithClient(client: RiskApiClient, ticker: string, options?: RiskLoadOptions): Promise<RiskReportsResult> {
100
+ const symbol = ticker.trim().toUpperCase();
101
+ // SAN in Paris is not SAN in New York: a listing abroad is cached under its venue.
102
+ const key = secListingKey(symbol, options?.listing?.exchange);
93
103
  return loadCached(client, LIST_KIND, key, activeListFetches, LIST_CACHE_POLICY, options?.force ?? false, async () => {
94
104
  let payload: CloudRiskReportListPayload;
95
105
  try {
96
- payload = await client.getRiskReports(key);
106
+ payload = await client.getRiskReports(symbol, options?.listing);
97
107
  } catch (error) {
98
108
  // The list answers 404 when no 10-K risk report is on file (20-F filers, funds).
99
109
  if (error instanceof ApiRequestError && error.status === 404) return { company: null, reports: [] };
@@ -104,12 +114,16 @@ export function loadRiskReportsWithClient(client: RiskApiClient, ticker: string,
104
114
  });
105
115
  }
106
116
 
107
- export function loadRiskReportWithClient(client: RiskApiClient, ticker: string, year: number, options?: { force?: boolean }): Promise<RiskReportResult> {
117
+ export function loadRiskReportWithClient(client: RiskApiClient, ticker: string, year: number, options?: RiskLoadOptions): Promise<RiskReportResult> {
108
118
  const symbol = ticker.trim().toUpperCase();
109
- return loadCached(client, REPORT_KIND, `${symbol}:${year}`, activeReportFetches, REPORT_CACHE_POLICY, options?.force ?? false, async () => {
110
- const payload = await client.getRiskReport(symbol, year);
119
+ const abroad = listingAbroad(symbol, options?.listing?.exchange) !== null;
120
+ const key = `${secListingKey(symbol, options?.listing?.exchange)}:${year}`;
121
+ return loadCached(client, REPORT_KIND, key, activeReportFetches, REPORT_CACHE_POLICY, options?.force ?? false, async () => {
122
+ const payload = await client.getRiskReport(symbol, year, options?.listing);
111
123
  // SEC rows spell share classes BRK-B; a BRK.B request is the same company.
112
- const sameTicker = (value: string | undefined) => value?.toUpperCase().replace(/\./g, "-") === symbol.replace(/\./g, "-");
124
+ // A listing abroad reads its registrant's rows, filed under that
125
+ // registrant's US ticker (Sanofi's SNY for SAN in Paris).
126
+ const sameTicker = (value: string | undefined) => abroad || value?.toUpperCase().replace(/\./g, "-") === symbol.replace(/\./g, "-");
113
127
  if (!payload || !sameTicker(payload.ticker) || payload.reportYear !== year) {
114
128
  throw new Error(`Risk report response does not match ${symbol} ${year}.`);
115
129
  }
@@ -117,5 +131,5 @@ export function loadRiskReportWithClient(client: RiskApiClient, ticker: string,
117
131
  });
118
132
  }
119
133
 
120
- export const loadRiskReports = (ticker: string, options?: { force?: boolean }) => loadRiskReportsWithClient(apiClient, ticker, options);
121
- export const loadRiskReport = (ticker: string, year: number, options?: { force?: boolean }) => loadRiskReportWithClient(apiClient, ticker, year, options);
134
+ export const loadRiskReports = (ticker: string, options?: RiskLoadOptions) => loadRiskReportsWithClient(apiClient, ticker, options);
135
+ export const loadRiskReport = (ticker: string, year: number, options?: RiskLoadOptions) => loadRiskReportWithClient(apiClient, ticker, year, options);