gloomberb 0.19.1 → 0.19.3

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 (554) hide show
  1. package/README.md +7 -1
  2. package/package.json +2 -1
  3. package/patches/electrobun@1.18.1.patch +108 -1
  4. package/src/account/upgrade-dialog.tsx +4 -23
  5. package/src/api-client/coalesce.ts +118 -0
  6. package/src/api-client/credit-boards.ts +13 -0
  7. package/src/api-client/crypto-etf-flows.ts +6 -0
  8. package/src/api-client/crypto-markets.ts +40 -0
  9. package/src/api-client/data.ts +201 -14
  10. package/src/api-client/equity-screener.ts +10 -0
  11. package/src/api-client/errors.ts +58 -0
  12. package/src/api-client/gpu.ts +29 -0
  13. package/src/api-client/index.ts +5 -2
  14. package/src/api-client/plan-access.ts +10 -1
  15. package/src/api-client/plan-rules.ts +35 -0
  16. package/src/api-client/research-activity.ts +1 -32
  17. package/src/api-client/types.ts +21 -1
  18. package/src/app/attention-notice.tsx +94 -0
  19. package/src/app/runtime/startup.ts +3 -1
  20. package/src/app.tsx +23 -1
  21. package/src/architecture/core-builtin-allowlist.json +2 -1
  22. package/src/capabilities/chart-series.ts +2 -1
  23. package/src/capabilities/factories.ts +1 -0
  24. package/src/cli/ascii-output.ts +42 -0
  25. package/src/cli/base-converter.ts +10 -2
  26. package/src/cli/command-options.ts +16 -1
  27. package/src/cli/commands/crypto-hints.ts +23 -4
  28. package/src/cli/commands/market.ts +101 -27
  29. package/src/cli/commands/news-search.ts +24 -5
  30. package/src/cli/commands/overview.ts +14 -10
  31. package/src/cli/commands/search.ts +84 -25
  32. package/src/cli/commands/system.ts +22 -30
  33. package/src/cli/commands/ticker.ts +49 -9
  34. package/src/cli/commands/version.ts +33 -0
  35. package/src/cli/context.ts +6 -9
  36. package/src/cli/data-dir.ts +7 -0
  37. package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
  38. package/src/cli/desktop-pane-shot-browser.ts +320 -0
  39. package/src/cli/desktop-pane-shot-routes.ts +26 -1
  40. package/src/cli/desktop-pane-shot.ts +196 -256
  41. package/src/cli/early-command.ts +42 -0
  42. package/src/cli/earnings-calendar.ts +9 -0
  43. package/src/cli/entry.ts +19 -13
  44. package/src/cli/errors.ts +12 -2
  45. package/src/cli/glossary.ts +2 -1
  46. package/src/cli/help.ts +1 -1
  47. package/src/cli/history-export.ts +22 -2
  48. package/src/cli/history-rows.ts +189 -11
  49. package/src/cli/index.ts +18 -1
  50. package/src/cli/known-gaps.ts +51 -5
  51. package/src/cli/listing-arg.ts +63 -10
  52. package/src/cli/options.ts +1 -1
  53. package/src/cli/output-file.ts +60 -13
  54. package/src/cli/pane-functions/catalog.ts +14 -3
  55. package/src/cli/pane-functions/data.ts +1 -1
  56. package/src/cli/pane-functions/dom.ts +45 -0
  57. package/src/cli/pane-functions/freshness.ts +46 -9
  58. package/src/cli/pane-functions/function-help.ts +17 -9
  59. package/src/cli/pane-functions/headless.ts +22 -14
  60. package/src/cli/pane-functions/index.ts +6 -2
  61. package/src/cli/pane-functions/options.ts +6 -2
  62. package/src/cli/pane-functions/resolver.ts +27 -5
  63. package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
  64. package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
  65. package/src/cli/pane-functions/screenshot-http.ts +70 -0
  66. package/src/cli/pane-functions/screenshot-render.ts +19 -5
  67. package/src/cli/quote-answer.ts +49 -6
  68. package/src/cli/registry.ts +41 -13
  69. package/src/cli/result.ts +12 -6
  70. package/src/cli/ticker-resolution.ts +2 -1
  71. package/src/cli/types.ts +2 -0
  72. package/src/components/chart/composite/format.ts +3 -0
  73. package/src/components/chart-table/header.tsx +1 -1
  74. package/src/components/chart-table/layout.ts +28 -11
  75. package/src/components/command-bar/action-runtime.ts +4 -0
  76. package/src/components/command-bar/commands/direct/index.ts +18 -33
  77. package/src/components/command-bar/commands/direct/runtime.ts +5 -0
  78. package/src/components/command-bar/commands/registry.ts +10 -1
  79. package/src/components/command-bar/routes/root/command-items.ts +9 -4
  80. package/src/components/command-bar/routes/root/runtime.ts +51 -6
  81. package/src/components/command-bar/selection-runtime.ts +12 -29
  82. package/src/components/command-bar/surface/index.tsx +9 -3
  83. package/src/components/company-logo.tsx +2 -1
  84. package/src/components/layout/detached-pane-shell.tsx +2 -2
  85. package/src/components/layout/header.tsx +10 -5
  86. package/src/components/layout/market-summary.ts +25 -7
  87. package/src/components/layout/pane/footer/index.tsx +8 -2
  88. package/src/components/layout/pane/footer/model.ts +73 -6
  89. package/src/components/layout/pane/header.tsx +2 -2
  90. package/src/components/layout/pane/status-footer.ts +2 -1
  91. package/src/components/market-board/index.tsx +3 -2
  92. package/src/components/onboarding/desks-step.tsx +12 -4
  93. package/src/components/onboarding/first-run-workspace.ts +81 -4
  94. package/src/components/onboarding/onboarding-frame.tsx +4 -2
  95. package/src/components/onboarding/onboarding-keyboard.ts +8 -5
  96. package/src/components/onboarding/onboarding-wizard.tsx +60 -26
  97. package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
  98. package/src/components/onboarding/run-setup-again.ts +24 -0
  99. package/src/components/onboarding/use-onboarding-progress.ts +33 -15
  100. package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
  101. package/src/components/onboarding/wizard-model.ts +57 -3
  102. package/src/components/onboarding/wizard-positions.ts +1 -1
  103. package/src/components/ui/delayed-mark.tsx +56 -0
  104. package/src/components/ui/display.tsx +16 -5
  105. package/src/components/ui/external-link.tsx +3 -6
  106. package/src/components/ui/icon.tsx +16 -3
  107. package/src/components/ui/logo-mark.tsx +8 -5
  108. package/src/components/ui/stat-grid.tsx +263 -19
  109. package/src/core/app-services.ts +11 -2
  110. package/src/data/app-persistence.ts +9 -7
  111. package/src/data/config/store/node.ts +11 -5
  112. package/src/data/config/store/normalize.ts +16 -3
  113. package/src/data/plugin-cache.ts +15 -0
  114. package/src/data/plugin-state-store.ts +81 -24
  115. package/src/data/profile-permissions.ts +47 -0
  116. package/src/data/resource-store.ts +165 -90
  117. package/src/data/session-store.ts +31 -8
  118. package/src/data/sqlite/access.ts +324 -0
  119. package/src/data/sqlite/checkpointer.ts +285 -0
  120. package/src/data/sqlite/database.ts +80 -4
  121. package/src/data/sqlite/retry.ts +50 -9
  122. package/src/data/ticker-store.ts +57 -23
  123. package/src/docs/snapshot.json +191 -78
  124. package/src/i18n/es.ts +18 -2
  125. package/src/i18n/ja.ts +18 -2
  126. package/src/i18n/ko.ts +18 -2
  127. package/src/i18n/zh-cn.ts +18 -2
  128. package/src/i18n/zh-tw.ts +18 -2
  129. package/src/layout/desk-keys.ts +4 -1
  130. package/src/layout/desks.ts +38 -5
  131. package/src/layout-marketplace/gallery-desktop.tsx +45 -16
  132. package/src/market-data/batched-reads.ts +16 -0
  133. package/src/market-data/coordinator/auxiliary.ts +4 -2
  134. package/src/market-data/coordinator/index.ts +11 -2
  135. package/src/market-data/futures-board.ts +1 -1
  136. package/src/market-data/futures-contract.ts +3 -1
  137. package/src/market-data/market/index-level.ts +6 -4
  138. package/src/market-data/published-apac-sessions.ts +6 -0
  139. package/src/market-data/realized-volatility.ts +4 -2
  140. package/src/news/aggregator.ts +7 -4
  141. package/src/news/news-model.ts +11 -0
  142. package/src/news/story-id.ts +22 -0
  143. package/src/news/ticker-symbols.ts +17 -1
  144. package/src/notifications/app-notifier.ts +35 -14
  145. package/src/plugins/auto-update-node.ts +10 -6
  146. package/src/plugins/builtin/account-management/form-components.tsx +1 -1
  147. package/src/plugins/builtin/account-management/index.tsx +2 -3
  148. package/src/plugins/builtin/account-management/model.ts +1 -1
  149. package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
  150. package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
  151. package/src/plugins/builtin/alerts/events.ts +10 -2
  152. package/src/plugins/builtin/alerts/index.tsx +5 -5
  153. package/src/plugins/builtin/alerts/research-builder.ts +5 -1
  154. package/src/plugins/builtin/alerts/research-rules.ts +18 -0
  155. package/src/plugins/builtin/alerts/targets.ts +4 -1
  156. package/src/plugins/builtin/attention/headless.ts +1 -1
  157. package/src/plugins/builtin/attention/index.tsx +2 -4
  158. package/src/plugins/builtin/attention/listing.ts +1 -1
  159. package/src/plugins/builtin/attention/series.ts +1 -1
  160. package/src/plugins/builtin/awards/index.tsx +1 -2
  161. package/src/plugins/builtin/backtest/client.ts +1 -0
  162. package/src/plugins/builtin/backtest/index.tsx +1 -2
  163. package/src/plugins/builtin/backtest/rules.ts +3 -1
  164. package/src/plugins/builtin/backtest/window.ts +8 -7
  165. package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
  166. package/src/plugins/builtin/broker-manager/index.tsx +1 -1
  167. package/src/plugins/builtin/buildout/cells.tsx +5 -0
  168. package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
  169. package/src/plugins/builtin/buildout/company/id.ts +2 -0
  170. package/src/plugins/builtin/buildout/company/model.ts +247 -0
  171. package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
  172. package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
  173. package/src/plugins/builtin/buildout/facilities.ts +309 -0
  174. package/src/plugins/builtin/buildout/format.ts +9 -0
  175. package/src/plugins/builtin/buildout/index.tsx +51 -5
  176. package/src/plugins/builtin/buildout/model/index.ts +62 -10
  177. package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
  178. package/src/plugins/builtin/buildout/model/types.ts +11 -2
  179. package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
  180. package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
  181. package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
  182. package/src/plugins/builtin/buildout/table-model.ts +26 -2
  183. package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
  184. package/src/plugins/builtin/cds/headless.ts +10 -2
  185. package/src/plugins/builtin/cds/model.ts +7 -3
  186. package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
  187. package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
  188. package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
  189. package/src/plugins/builtin/changelog/index.tsx +3 -2
  190. package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
  191. package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
  192. package/src/plugins/builtin/chart-composer/headless.ts +88 -21
  193. package/src/plugins/builtin/chart-composer/index.tsx +2 -3
  194. package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
  195. package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
  196. package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
  197. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  198. package/src/plugins/builtin/chat/controller/index.ts +1 -1
  199. package/src/plugins/builtin/chat/controller/messages.ts +15 -4
  200. package/src/plugins/builtin/chat/ghost-user.ts +8 -0
  201. package/src/plugins/builtin/chat/index.tsx +5 -5
  202. package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
  203. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  204. package/src/plugins/builtin/cloud/index.tsx +1 -2
  205. package/src/plugins/builtin/cloud/plugin.tsx +6 -4
  206. package/src/plugins/builtin/cloud/team/module.tsx +1 -1
  207. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
  208. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
  209. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
  210. package/src/plugins/builtin/comment-letters/index.tsx +1 -2
  211. package/src/plugins/builtin/commodity-balances/client.ts +4 -1
  212. package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
  213. package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
  214. package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
  215. package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
  216. package/src/plugins/builtin/congress-trades/headless.ts +2 -0
  217. package/src/plugins/builtin/congress-trades/model.ts +92 -64
  218. package/src/plugins/builtin/congress-trades/table.tsx +14 -2
  219. package/src/plugins/builtin/connections/index.ts +2 -3
  220. package/src/plugins/builtin/cot/client.ts +28 -2
  221. package/src/plugins/builtin/cot/headless.ts +4 -1
  222. package/src/plugins/builtin/cot/index.tsx +5 -4
  223. package/src/plugins/builtin/cot/model.ts +58 -7
  224. package/src/plugins/builtin/cot/pane.tsx +6 -2
  225. package/src/plugins/builtin/cpi/client.ts +6 -5
  226. package/src/plugins/builtin/cpi/index.tsx +1 -2
  227. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  228. package/src/plugins/builtin/credit-boards/client.ts +7 -2
  229. package/src/plugins/builtin/credit-boards/headless.ts +25 -12
  230. package/src/plugins/builtin/credit-boards/model.ts +29 -0
  231. package/src/plugins/builtin/credit-boards/series.ts +3 -4
  232. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
  233. package/src/plugins/builtin/credit-conditions/client.ts +22 -14
  234. package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
  235. package/src/plugins/builtin/crypto-board/client.ts +5 -5
  236. package/src/plugins/builtin/crypto-board/headless.ts +4 -3
  237. package/src/plugins/builtin/crypto-board/index.tsx +4 -2
  238. package/src/plugins/builtin/crypto-board/series.ts +6 -4
  239. package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
  240. package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
  241. package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
  242. package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
  243. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
  244. package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
  245. package/src/plugins/builtin/debt-maturities/model.ts +17 -1
  246. package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
  247. package/src/plugins/builtin/debt-maturities/series.ts +3 -3
  248. package/src/plugins/builtin/debug/index.tsx +1 -1
  249. package/src/plugins/builtin/dividend-yield/client.ts +32 -2
  250. package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
  251. package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
  252. package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
  253. package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
  254. package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
  255. package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
  256. package/src/plugins/builtin/dividend-yield/view.ts +21 -0
  257. package/src/plugins/builtin/docs/index.tsx +2 -3
  258. package/src/plugins/builtin/doe/client.ts +6 -5
  259. package/src/plugins/builtin/doe/headless.ts +16 -3
  260. package/src/plugins/builtin/doe/index.tsx +5 -4
  261. package/src/plugins/builtin/doe/prices.ts +2 -3
  262. package/src/plugins/builtin/doe/series.ts +4 -4
  263. package/src/plugins/builtin/earnings/board.tsx +2 -1
  264. package/src/plugins/builtin/earnings/history.tsx +2 -1
  265. package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
  266. package/src/plugins/builtin/earnings-calls/data.ts +19 -11
  267. package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
  268. package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
  269. package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
  270. package/src/plugins/builtin/econ/calendar-model.ts +29 -6
  271. package/src/plugins/builtin/econ/detail-view.tsx +16 -2
  272. package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
  273. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  274. package/src/plugins/builtin/equity-screener/headless.ts +1 -0
  275. package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
  276. package/src/plugins/builtin/equity-screener/index.tsx +1 -2
  277. package/src/plugins/builtin/equity-screener/model.ts +28 -4
  278. package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
  279. package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
  280. package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
  281. package/src/plugins/builtin/executives/data.ts +6 -1
  282. package/src/plugins/builtin/executives/pane.tsx +9 -21
  283. package/src/plugins/builtin/exposure/holdings.ts +35 -13
  284. package/src/plugins/builtin/exposure/index.tsx +1 -2
  285. package/src/plugins/builtin/fear-greed/cache.ts +4 -0
  286. package/src/plugins/builtin/fear-greed/cli.ts +44 -0
  287. package/src/plugins/builtin/fear-greed/index.tsx +5 -53
  288. package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
  289. package/src/plugins/builtin/fear-greed/series.ts +6 -4
  290. package/src/plugins/builtin/fixed-income/index.tsx +1 -2
  291. package/src/plugins/builtin/futures/board-reads.ts +25 -0
  292. package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
  293. package/src/plugins/builtin/futures/index.tsx +15 -0
  294. package/src/plugins/builtin/futures/model.ts +14 -0
  295. package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
  296. package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
  297. package/src/plugins/builtin/futures-curve/headless.ts +6 -1
  298. package/src/plugins/builtin/futures-curve/index.tsx +1 -2
  299. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  300. package/src/plugins/builtin/gpu/board.tsx +51 -12
  301. package/src/plugins/builtin/gpu/client.ts +19 -3
  302. package/src/plugins/builtin/gpu/equities.tsx +5 -2
  303. package/src/plugins/builtin/gpu/headless.ts +14 -2
  304. package/src/plugins/builtin/gpu/index.tsx +1 -2
  305. package/src/plugins/builtin/gpu/model.ts +20 -2
  306. package/src/plugins/builtin/gpu/pane.tsx +1 -1
  307. package/src/plugins/builtin/gpu/series.ts +2 -3
  308. package/src/plugins/builtin/help/index.tsx +1 -1
  309. package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
  310. package/src/plugins/builtin/holders/index-holders.ts +24 -12
  311. package/src/plugins/builtin/holders/index.tsx +2 -3
  312. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
  313. package/src/plugins/builtin/iv-history/index.tsx +1 -2
  314. package/src/plugins/builtin/jodi/client.ts +4 -1
  315. package/src/plugins/builtin/jodi/index.tsx +1 -2
  316. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
  317. package/src/plugins/builtin/layout-manager/index.tsx +9 -8
  318. package/src/plugins/builtin/macro-day/pane.tsx +4 -2
  319. package/src/plugins/builtin/market-halts/index.tsx +1 -2
  320. package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
  321. package/src/plugins/builtin/market-movers/client.ts +1 -0
  322. package/src/plugins/builtin/market-movers/index.tsx +16 -5
  323. package/src/plugins/builtin/market-movers/screener.ts +23 -6
  324. package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
  325. package/src/plugins/builtin/market-valuation/index.tsx +1 -2
  326. package/src/plugins/builtin/members/index.tsx +1 -2
  327. package/src/plugins/builtin/money-markets/index.tsx +1 -2
  328. package/src/plugins/builtin/money-markets/pane.tsx +2 -1
  329. package/src/plugins/builtin/money-markets/series.ts +3 -5
  330. package/src/plugins/builtin/news/headless.ts +7 -0
  331. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  332. package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
  333. package/src/plugins/builtin/notes/index.tsx +3 -6
  334. package/src/plugins/builtin/openfda/index.tsx +1 -2
  335. package/src/plugins/builtin/options/view.tsx +6 -5
  336. package/src/plugins/builtin/options-calculator/index.tsx +1 -2
  337. package/src/plugins/builtin/options-positioning/index.tsx +1 -2
  338. package/src/plugins/builtin/options-scenario/index.tsx +2 -4
  339. package/src/plugins/builtin/perps/index.tsx +1 -2
  340. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
  341. package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
  342. package/src/plugins/builtin/plugin-module.ts +16 -2
  343. package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
  344. package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
  345. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
  346. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
  347. package/src/plugins/builtin/power/gas-client.ts +2 -1
  348. package/src/plugins/builtin/power/index.tsx +1 -2
  349. package/src/plugins/builtin/power/prices-client.ts +3 -2
  350. package/src/plugins/builtin/rate-path/index.tsx +1 -2
  351. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  352. package/src/plugins/builtin/realized-vol/index.tsx +1 -2
  353. package/src/plugins/builtin/realized-vol/model.ts +16 -2
  354. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  355. package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
  356. package/src/plugins/builtin/research/analyst-headless.ts +33 -6
  357. package/src/plugins/builtin/research/analyst-model.ts +37 -2
  358. package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
  359. package/src/plugins/builtin/research/index.tsx +5 -8
  360. package/src/plugins/builtin/research-search/index.tsx +1 -2
  361. package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
  362. package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
  363. package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
  364. package/src/plugins/builtin/risk-factors/data.ts +8 -4
  365. package/src/plugins/builtin/risk-factors/index.tsx +3 -1
  366. package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
  367. package/src/plugins/builtin/roadmap/index.tsx +2 -3
  368. package/src/plugins/builtin/roadmap/pane.tsx +49 -2
  369. package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
  370. package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
  371. package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
  372. package/src/plugins/builtin/scanner/lock-note.ts +26 -3
  373. package/src/plugins/builtin/sec/filing-content.ts +38 -6
  374. package/src/plugins/builtin/sec/headless.ts +5 -1
  375. package/src/plugins/builtin/sec/index.tsx +4 -2
  376. package/src/plugins/builtin/sec/model.ts +2 -0
  377. package/src/plugins/builtin/sectors/index.tsx +11 -5
  378. package/src/plugins/builtin/series-inventory.ts +2 -0
  379. package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
  380. package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
  381. package/src/plugins/builtin/shared/forming-bars.ts +29 -0
  382. package/src/plugins/builtin/shared/fred-points.ts +6 -1
  383. package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
  384. package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
  385. package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
  386. package/src/plugins/builtin/shared/series-warning.ts +13 -4
  387. package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
  388. package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
  389. package/src/plugins/builtin/short-volume/pane.tsx +2 -1
  390. package/src/plugins/builtin/short-volume/series.ts +2 -3
  391. package/src/plugins/builtin/social-mentions/client.ts +15 -10
  392. package/src/plugins/builtin/social-mentions/index.tsx +1 -2
  393. package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
  394. package/src/plugins/builtin/social-mentions/series.ts +2 -3
  395. package/src/plugins/builtin/supply-chain/client.ts +22 -1
  396. package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
  397. package/src/plugins/builtin/supply-chain/index.tsx +1 -2
  398. package/src/plugins/builtin/thirteenf/api.ts +127 -29
  399. package/src/plugins/builtin/thirteenf/data.ts +165 -35
  400. package/src/plugins/builtin/thirteenf/index.tsx +2 -4
  401. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  402. package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
  403. package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
  404. package/src/plugins/builtin/thirteenf/types.ts +14 -0
  405. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
  406. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
  407. package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
  408. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
  409. package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
  410. package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
  411. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
  412. package/src/plugins/builtin/time-sales/index.tsx +1 -2
  413. package/src/plugins/builtin/time-sales/pane.tsx +3 -1
  414. package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
  415. package/src/plugins/builtin/trace-bonds/model.ts +44 -10
  416. package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
  417. package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
  418. package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
  419. package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
  420. package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
  421. package/src/plugins/builtin/vol-surface/client.ts +18 -3
  422. package/src/plugins/builtin/vol-surface/index.tsx +1 -2
  423. package/src/plugins/builtin/volatility/client.ts +43 -16
  424. package/src/plugins/builtin/volatility/headless.ts +5 -2
  425. package/src/plugins/builtin/volatility/index.tsx +1 -2
  426. package/src/plugins/builtin/volatility/model.ts +17 -6
  427. package/src/plugins/builtin/volatility/pane.tsx +6 -2
  428. package/src/plugins/builtin/volatility/series.ts +2 -1
  429. package/src/plugins/builtin/world-indices/footer.ts +6 -1
  430. package/src/plugins/builtin/world-venue-map/client.ts +10 -1
  431. package/src/plugins/builtin/world-venue-map/index.ts +6 -2
  432. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
  433. package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
  434. package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
  435. package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
  436. package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
  437. package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
  438. package/src/plugins/builtin/yield-curve/client.ts +4 -6
  439. package/src/plugins/builtin/yield-curve/curves.ts +16 -2
  440. package/src/plugins/builtin/yield-curve/headless.ts +3 -3
  441. package/src/plugins/bundle.ts +52 -2
  442. package/src/plugins/catalog-browser.ts +2 -2
  443. package/src/plugins/catalog-cli.ts +20 -0
  444. package/src/plugins/host-link.ts +13 -1
  445. package/src/plugins/installer.ts +64 -42
  446. package/src/plugins/loader.ts +43 -11
  447. package/src/plugins/peer-imports.ts +201 -0
  448. package/src/plugins/peer-retry.ts +26 -29
  449. package/src/plugins/plugin-process.ts +128 -0
  450. package/src/plugins/redirection-guard.ts +45 -0
  451. package/src/remote/endpoint.ts +16 -3
  452. package/src/renderers/browser/main.tsx +3 -2
  453. package/src/renderers/cloudflare/http-proxy.ts +7 -123
  454. package/src/renderers/dom/desktop/icons.tsx +5 -2
  455. package/src/renderers/dom/host/image-surface.tsx +46 -21
  456. package/src/renderers/dom/styles.css +7 -0
  457. package/src/renderers/dom/toast-host.tsx +2 -0
  458. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  459. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
  460. package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
  461. package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
  462. package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
  463. package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
  464. package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
  465. package/src/renderers/electrobun/bun/window/attention.ts +54 -0
  466. package/src/renderers/electrobun/bun/window/create.ts +6 -2
  467. package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
  468. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
  469. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
  470. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
  471. package/src/renderers/electrobun/view/main.tsx +7 -2
  472. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
  473. package/src/renderers/electrobun/view/ui-host.tsx +9 -2
  474. package/src/renderers/opentui/browser-open.ts +10 -4
  475. package/src/renderers/opentui/image/surface.tsx +1 -1
  476. package/src/renderers/opentui/start.tsx +12 -6
  477. package/src/renderers/opentui/toast-host.tsx +2 -1
  478. package/src/series/registry.ts +18 -0
  479. package/src/series/resolve.ts +2 -1
  480. package/src/series/standalone.ts +10 -1
  481. package/src/series/types.ts +12 -0
  482. package/src/series/validate.ts +11 -2
  483. package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
  484. package/src/sources/gloomberb-cloud/index.ts +28 -5
  485. package/src/sources/gloomberb-cloud/news.ts +24 -3
  486. package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
  487. package/src/sources/provider-errors.ts +26 -3
  488. package/src/sources/provider-router/history.ts +5 -0
  489. package/src/sources/provider-router/index.ts +13 -0
  490. package/src/sync/core-contributors.ts +32 -0
  491. package/src/telemetry/attention-counts.ts +64 -6
  492. package/src/telemetry/attention-setting.ts +33 -0
  493. package/src/telemetry/crash-reports-node.ts +3 -1
  494. package/src/telemetry/stall-clocks.ts +93 -0
  495. package/src/telemetry/stall-reports-node.ts +25 -9
  496. package/src/telemetry/stall-reports.ts +218 -13
  497. package/src/test-support/outage.ts +8 -0
  498. package/src/test-support/preload.ts +9 -0
  499. package/src/tickers/company-name-listing.ts +59 -29
  500. package/src/tickers/company-name.ts +84 -1
  501. package/src/tickers/listing-fate.ts +14 -3
  502. package/src/tickers/saved-instrument.ts +17 -0
  503. package/src/tickers/search/home-listing.ts +104 -8
  504. package/src/tickers/search/index.ts +5 -2
  505. package/src/tickers/search/types.ts +6 -1
  506. package/src/time-series/bar-aggregation.ts +7 -1
  507. package/src/time-series/base-series.ts +3 -3
  508. package/src/time-series/chart-bounds.ts +1 -1
  509. package/src/time-series/chart-data.ts +5 -2
  510. package/src/time-series/chart-seed.ts +1 -1
  511. package/src/time-series/coin-session-close.ts +211 -0
  512. package/src/time-series/live-bars.ts +13 -2
  513. package/src/time-series/live-quotes.ts +1 -1
  514. package/src/time-series/market.ts +5 -3
  515. package/src/time-series/official-close.ts +159 -0
  516. package/src/time-series/price-comparison.ts +75 -8
  517. package/src/time-series/resolve.ts +32 -4
  518. package/src/time-series/series-presentation.ts +1 -1
  519. package/src/time-series/studies/formula.ts +205 -0
  520. package/src/time-series/studies/indicators.ts +299 -0
  521. package/src/time-series/studies/leverage.ts +75 -0
  522. package/src/time-series/studies/pair.ts +138 -0
  523. package/src/time-series/studies/resolve.ts +333 -0
  524. package/src/time-series/studies/shared.ts +141 -0
  525. package/src/time-series/studies/units.ts +243 -0
  526. package/src/time-series/studies/vwap.ts +274 -0
  527. package/src/time-series/types.ts +7 -0
  528. package/src/types/config.ts +18 -2
  529. package/src/types/data-provider.ts +23 -0
  530. package/src/types/financials.ts +40 -3
  531. package/src/types/headless.ts +42 -3
  532. package/src/types/plugin.ts +32 -3
  533. package/src/ui/host.tsx +17 -1
  534. package/src/ui/toast.tsx +26 -1
  535. package/src/utils/ascii-text.ts +140 -0
  536. package/src/utils/cli-output.ts +91 -18
  537. package/src/utils/concurrency.ts +24 -0
  538. package/src/utils/exchanges.ts +6 -0
  539. package/src/utils/external-url.ts +20 -7
  540. package/src/utils/financial-statements.ts +73 -16
  541. package/src/utils/fundamentals.ts +17 -5
  542. package/src/utils/futures-generic.ts +4 -4
  543. package/src/utils/http-proxy-upstream.ts +124 -0
  544. package/src/utils/lease-liabilities.ts +54 -0
  545. package/src/utils/main-thread-stall.ts +122 -5
  546. package/src/utils/perf-marks.ts +58 -12
  547. package/src/utils/price-history-integrity.ts +47 -1
  548. package/src/utils/private-files.ts +141 -0
  549. package/src/utils/public-address.ts +99 -0
  550. package/src/account/upgrade-personalization.ts +0 -57
  551. package/src/account/upgrade-tickers.ts +0 -124
  552. package/src/api-client/upgrade-experiment.ts +0 -91
  553. package/src/plugins/builtin/cloud/browser.tsx +0 -3
  554. package/src/time-series/studies.ts +0 -1628
@@ -10,6 +10,7 @@ import type { PerpBoardPayload, PerpEquitiesPayload, PerpEquityListing, PerpHist
10
10
  PerpMarketPayload, PerpLiquidationsPayload, PerpDepthPayload } from "./perps";
11
11
  import type { ExposureRequest, ExposurePayload, ExposureScenario } from "./exposure";
12
12
  import type { SupplyChainPayload, SupplyOptions } from "./supply-chain";
13
+ import { createReadCoalescer } from "./coalesce";
13
14
  import { supplyGraphQuery, type GraphOptions, type GraphPayload } from "./supply-chain-graph";
14
15
  import type { AwardDetailPayload, AwardFilter, AwardsPayload } from "./awards";
15
16
  import type { EarningsEvent } from "../types/data-provider";
@@ -30,7 +31,7 @@ import type { IpoCalendarParams, IpoCalendarPayload } from "./ipo";
30
31
  import type { TickerChanges } from "../types/ticker-changes";
31
32
  import type { IssuerListings } from "../types/issuer-listings";
32
33
  import type { OrganizationTickersResponse } from "./organization-tickers";
33
- import type { CryptoMarketsPayload, DefiTvlPayload, StablecoinSupplyHistoryPayload, StablecoinSupplyPayload } from "./crypto-markets";
34
+ import type { BitcoinNetworkPayload, CryptoMarketsPayload, DefiTvlPayload, StablecoinSupplyHistoryPayload, StablecoinSupplyPayload } from "./crypto-markets";
34
35
  import type { CryptoEtfFlowAsset, CryptoEtfFlowsPayload } from "./crypto-etf-flows";
35
36
  import type { CentralBankRatesPayload } from "./central-bank-rates";
36
37
  import type { EstimateRevisionsPayload } from "./estimate-revisions";
@@ -53,7 +54,7 @@ import type { CpiBoardPayload } from "./cpi";
53
54
  import type { TapeRange, TapeSnapshot } from "./tape";
54
55
  import { LIVE_SCAN_PATH, type LiveScanPayload, type LiveScanWireDefinition } from "./live-scan";
55
56
  import { signalWithTimeout } from "../utils/async-deadline";
56
- import { ApiRequestError } from "./errors";
57
+ import { ApiRequestError, isRetryableApiError, markAskedTwice } from "./errors";
57
58
  import type { ExchangeRateSnapshot } from "../types/exchange-rate";
58
59
  import type { RatePathPayload } from "./rates";
59
60
  import type { EarningsCalendarPayload, EarningsCalendarQuery, EarningsHistoryPayload } from "./earnings";
@@ -204,8 +205,161 @@ function batchAnswers<K extends string, T>(response: CloudMarketResponse<CloudBa
204
205
  return answers;
205
206
  }
206
207
 
208
+ /** How long a listing read waits before asking again after the service said its source failed. */
209
+ const LISTING_RETRY_DELAY_MS = 400;
210
+
211
+ /**
212
+ * Whether a failed listing read is asked again: the service answered that its
213
+ * own source failed (`retryable_error`, or the catch-all `fatal_error`, with no
214
+ * data), or failed with a 5xx or a timeout. A rate limit is never asked again,
215
+ * whatever wait it names: asking again is more of what it refused. An answer of
216
+ * no data, a refusal, and a request that never reached the service
217
+ * (`DataServiceUnreachableError`) are not asked again either.
218
+ */
219
+ function isListingReadRetried(outcome: { response: CloudMarketResponse<unknown> } | { error: unknown }): boolean {
220
+ if ("response" in outcome) {
221
+ const { status, data } = outcome.response ?? {};
222
+ return (status === "retryable_error" || status === "fatal_error") && data == null;
223
+ }
224
+ return isRetryableApiError(outcome.error) && (outcome.error as ApiRequestError).status !== 429;
225
+ }
226
+
227
+ /**
228
+ * `batch`: the caller asks many reads like this one in the same moment (a
229
+ * board's rows), so the read may wait a few milliseconds to go out in a batch
230
+ * request with them. Any other read goes out on its own at once.
231
+ */
232
+ export interface BatchableRead {
233
+ batch?: boolean;
234
+ }
235
+
207
236
  export class CloudDataApi {
208
- constructor(private readonly request: CloudApiRequest) {}
237
+ constructor(
238
+ private readonly request: CloudApiRequest,
239
+ /** The wait before a failed listing read is asked again (`readListing`); tests set 0. */
240
+ private readonly listingRetryDelayMs = LISTING_RETRY_DELAY_MS,
241
+ ) {}
242
+
243
+ /**
244
+ * Histories asked with others (`batch`) in the same moment with the same
245
+ * bars (a futures board's contracts, SOVR's currencies) go out as
246
+ * `/market/history/batch` requests, each answered as its own
247
+ * `/market/history` would be. Sixteen a request, sent side by side: a
248
+ * board's seventy-two rows are five requests, and the first rows land as
249
+ * soon as sixteen reads have.
250
+ */
251
+ private readonly histories = createReadCoalescer<
252
+ { symbol: string; exchange: string; params: Pick<CloudHistoryParams, "interval" | "outputsize" | "rangeKey"> },
253
+ CloudMarketResponse<CloudPricePointPayload[]>
254
+ >({
255
+ max: 16,
256
+ // Reads asked alone after a refused batch stay within what the panes kept in flight before: SOVR four, the board twelve with its terms.
257
+ fallbackConcurrency: 4,
258
+ group: ({ params }) => `${params.interval ?? ""}|${params.outputsize ?? ""}|${params.rangeKey ?? ""}`,
259
+ single: ({ symbol, exchange, params }) => this.request(cloudHistoryPath(symbol, exchange, params)),
260
+ batch: async (keys) => {
261
+ const { interval, outputsize, rangeKey } = keys[0]!.params;
262
+ // The server answers each target in the order asked, so a listing asked twice is asked once.
263
+ const targetKey = ({ symbol, exchange }: { symbol: string; exchange: string }) => `${symbol}\u001f${exchange}`;
264
+ const targets = [...new Map(keys.map((key) => [targetKey(key), { symbol: key.symbol, exchange: key.exchange }])).values()];
265
+ const response = await this.request<CloudMarketResponse<{ items: Array<CloudMarketResponse<CloudPricePointPayload[]>> }>>("/market/history/batch", {
266
+ method: "POST",
267
+ body: JSON.stringify({
268
+ targets,
269
+ interval: interval ?? "1day",
270
+ ...(outputsize != null ? { outputsize: String(outputsize) } : {}),
271
+ ...(rangeKey ? { rangeKey } : {}),
272
+ }),
273
+ });
274
+ const items = response.data?.items;
275
+ // An answer that does not line up with the targets asked is not matched by place: each read is asked on its own.
276
+ if (!Array.isArray(items) || items.length !== targets.length) return keys.map(() => undefined);
277
+ const byTarget = new Map(targets.map((target, index) => [targetKey(target), items[index]]));
278
+ return keys.map((key) => byTarget.get(targetKey(key)));
279
+ },
280
+ });
281
+
282
+ /**
283
+ * Futures terms asked with others in the same moment (a futures board's rows) go out as
284
+ * `/cloud/futures/contracts` requests, sixteen a request like the histories.
285
+ * A symbol without known terms answers null there, which reads as the 404
286
+ * its own request answers.
287
+ */
288
+ private readonly futuresTerms = createReadCoalescer<string, FuturesContractTermsPayload | null>({
289
+ max: 16,
290
+ // With the board's histories' four, the twelve reads it kept in flight before.
291
+ fallbackConcurrency: 8,
292
+ group: () => "",
293
+ single: (symbol) => this.request<FuturesContractTermsPayload>(`/cloud/futures/contract/${encodeURIComponent(symbol)}`, { signal: AbortSignal.timeout(20_000) }),
294
+ batch: async (symbols) => {
295
+ // The server answers each symbol once, by the symbol asked.
296
+ const { items } = await this.getCloudFuturesContracts([...new Set(symbols)]);
297
+ const bySymbol = new Map(items.map((item) => [item.symbol, item]));
298
+ // A failed read is asked again on its own.
299
+ return symbols.map((symbol) => bySymbol.get(symbol)?.error ? undefined : bySymbol.get(symbol)?.terms);
300
+ },
301
+ });
302
+
303
+ /**
304
+ * Expiries of one listing's chain asked with others in the same moment (a
305
+ * volatility surface's) go out as `/market/options/batch` requests, each answered as
306
+ * its own `/market/options` would be. Six a request, the server's one round,
307
+ * sent side by side: a surface's eighteen expiries are three requests.
308
+ */
309
+ private readonly optionsChains = createReadCoalescer<
310
+ { symbol: string; exchange?: string; expirationDate: number },
311
+ CloudMarketResponse<CloudOptionsChainPayload>
312
+ >({
313
+ max: 6,
314
+ fallbackConcurrency: 4,
315
+ group: ({ symbol, exchange }) => `${symbol}|${exchange ?? ""}`,
316
+ single: ({ symbol, exchange, expirationDate }) => this.request(cloudOptionsChainPath(symbol, exchange, expirationDate)),
317
+ batch: async (keys) => {
318
+ const { symbol, exchange } = keys[0]!;
319
+ // The server answers each expiry once, naming it.
320
+ const expiries = [...new Set(keys.map((key) => key.expirationDate))];
321
+ const search = new URLSearchParams({ symbol, expirationDates: expiries.join(",") });
322
+ if (exchange) search.set("exchange", exchange);
323
+ const response = await this.request<CloudMarketResponse<{ items: Array<CloudMarketResponse<CloudOptionsChainPayload> & { expirationDate?: number }> }>>(
324
+ `/market/options/batch?${search}`,
325
+ );
326
+ const byExpiry = new Map((response.data?.items ?? []).map(({ expirationDate, ...item }) => [expirationDate, item]));
327
+ return keys.map((key) => byExpiry.get(key.expirationDate));
328
+ },
329
+ });
330
+
331
+ /**
332
+ * Whether reads of `kind` asked together go out in batch requests now:
333
+ * false while the service answers that batch route 404, until it is asked
334
+ * again. A caller limiting its reads in flight asks them all at once only
335
+ * when they do.
336
+ */
337
+ batchesReads(kind: "history" | "options" | "futures-terms"): boolean {
338
+ const coalescer = kind === "history" ? this.histories : kind === "options" ? this.optionsChains : this.futuresTerms;
339
+ return coalescer.batching();
340
+ }
341
+
342
+ /**
343
+ * A listing's quote, history or financials, asked again once when the
344
+ * service failed to answer it (`isListingReadRetried`): its source blinks for a
345
+ * few minutes at a time, and a known listing then read as one no source
346
+ * covers. A caller's abort stops the second ask.
347
+ */
348
+ private async readListing<T>(read: () => Promise<CloudMarketResponse<T>>, signal?: AbortSignal): Promise<CloudMarketResponse<T>> {
349
+ const first = await read().then((response) => ({ response }), (error: unknown) => ({ error }));
350
+ const settle = () => {
351
+ if ("error" in first) throw first.error;
352
+ return first.response;
353
+ };
354
+ if (!isListingReadRetried(first) || signal?.aborted) return settle();
355
+ if (this.listingRetryDelayMs > 0) await new Promise((resolve) => setTimeout(resolve, this.listingRetryDelayMs));
356
+ if (signal?.aborted) return settle();
357
+ const second = await read().then((response) => ({ response }), (error: unknown) => ({ error }));
358
+ // Failing the same way twice, the service did not answer it (`markAskedTwice`).
359
+ if (isListingReadRetried(second)) markAskedTwice("error" in second ? second.error : second.response);
360
+ if ("error" in second) throw second.error;
361
+ return second.response;
362
+ }
209
363
 
210
364
  private requestMarketSymbol<T>(
211
365
  path: string,
@@ -315,7 +469,7 @@ export class CloudDataApi {
315
469
  symbol: string,
316
470
  exchange?: string,
317
471
  ): Promise<CloudMarketResponse<CloudQuotePayload>> {
318
- return this.requestMarketSymbol("/market/quote", symbol, exchange);
472
+ return this.readListing(() => this.requestMarketSymbol("/market/quote", symbol, exchange));
319
473
  }
320
474
 
321
475
  async getCloudQuotesBatch(
@@ -323,7 +477,7 @@ export class CloudDataApi {
323
477
  mode: "cache-first" | "refresh" = "cache-first",
324
478
  options: { signal?: AbortSignal } = {},
325
479
  ): Promise<CloudMarketResponse<CloudMarketBatchPayload<CloudQuotePayload>>> {
326
- return this.postMarketBatch("/market/quotes/batch", targets, mode, options.signal);
480
+ return this.readListing(() => this.postMarketBatch("/market/quotes/batch", targets, mode, options.signal), options.signal);
327
481
  }
328
482
 
329
483
  async getCloudWorldVenues(): Promise<
@@ -388,7 +542,10 @@ export class CloudDataApi {
388
542
  symbol: string,
389
543
  exchange?: string,
390
544
  expirationDate?: number,
545
+ options: BatchableRead = {},
391
546
  ): Promise<CloudMarketResponse<CloudOptionsChainPayload>> {
547
+ // A dated expiry asked with others may go out with the listing's other expiries asked in the same moment.
548
+ if (expirationDate != null && options.batch) return this.optionsChains.read({ symbol, exchange, expirationDate });
392
549
  return this.request<CloudMarketResponse<CloudOptionsChainPayload>>(
393
550
  cloudOptionsChainPath(symbol, exchange, expirationDate),
394
551
  );
@@ -400,14 +557,14 @@ export class CloudDataApi {
400
557
  statementHistory?: "extended",
401
558
  ): Promise<CloudMarketResponse<CloudFinancialsPayload>> {
402
559
  const path = cloudMarketSymbolPath("/market/financials", symbol, exchange);
403
- return this.request(statementHistory === "extended" ? `${path}&statementHistory=extended` : path);
560
+ return this.readListing(() => this.request(statementHistory === "extended" ? `${path}&statementHistory=extended` : path));
404
561
  }
405
562
 
406
563
  async getCloudFinancialsBatch(
407
564
  targets: CloudMarketBatchTarget[],
408
565
  mode: "cache-first" | "refresh" = "cache-first",
409
566
  ): Promise<CloudMarketResponse<CloudMarketBatchPayload<CloudFinancialsPayload>>> {
410
- return this.postMarketBatch("/market/financials/batch", targets, mode);
567
+ return this.readListing(() => this.postMarketBatch("/market/financials/batch", targets, mode));
411
568
  }
412
569
 
413
570
  async getCloudHolders(
@@ -518,12 +675,16 @@ export class CloudDataApi {
518
675
  symbol: string,
519
676
  exchange: string,
520
677
  params: CloudHistoryParams = {},
521
- options: { signal?: AbortSignal } = {},
678
+ options: { signal?: AbortSignal } & BatchableRead = {},
522
679
  ): Promise<CloudMarketResponse<CloudPricePointPayload[]>> {
523
- return this.request<CloudMarketResponse<CloudPricePointPayload[]>>(
524
- cloudHistoryPath(symbol, exchange, params),
525
- options.signal ? { signal: options.signal } : undefined,
526
- );
680
+ // Trailing bars asked with others, not a session, window or recovery of their own, may go out with the others asked in the same moment.
681
+ const shared = options.batch && !options.signal && !params.startDate && !params.endDate && !params.historyRecovery && !params.session;
682
+ return this.readListing(() => shared
683
+ ? this.histories.read({ symbol, exchange, params: { interval: params.interval, outputsize: params.outputsize, rangeKey: params.rangeKey } })
684
+ : this.request<CloudMarketResponse<CloudPricePointPayload[]>>(
685
+ cloudHistoryPath(symbol, exchange, params),
686
+ options.signal ? { signal: options.signal } : undefined,
687
+ ), options.signal);
527
688
  }
528
689
 
529
690
  async getCloudExchangeRate(
@@ -732,6 +893,19 @@ export class CloudDataApi {
732
893
  return this.request<AwardDetailPayload>(`/cloud/awards/detail/${encodeURIComponent(id)}${query}`, { signal });
733
894
  }
734
895
 
896
+ /** Many symbols' disclosures, each as getCloudSupplyChain reads it; an item that failed has the status its own read would have had. */
897
+ async getCloudSupplyChains(symbols: string[], options: SupplyOptions = {}): Promise<{ items: Array<{ symbol: string; chain?: SupplyChainPayload; status?: number }> }> {
898
+ const query = new URLSearchParams({ symbols: symbols.join(",") });
899
+ if (options.tiers?.length) query.set("tiers", options.tiers.join(","));
900
+ if (options.includeLeads) query.set("includeLeads", "1");
901
+ return this.request(`/cloud/supply-chain/batch?${query}`);
902
+ }
903
+
904
+ /** Many symbols' graphs on the same options, each as getCloudSupplyGraph reads it. */
905
+ async getCloudSupplyGraphs(symbols: string[], options: Partial<GraphOptions> = {}): Promise<{ items: Array<{ symbol: string; graph?: GraphPayload; status?: number }> }> {
906
+ return this.request(`/cloud/supply-chain/graphs?symbols=${encodeURIComponent(symbols.join(","))}&${supplyGraphQuery(options)}`);
907
+ }
908
+
735
909
  async getCloudSupplyGraph(symbol: string, options: Partial<GraphOptions> = {}): Promise<GraphPayload> {
736
910
  return this.request<GraphPayload>(`/cloud/supply-chain/${encodeURIComponent(symbol)}/graph?${supplyGraphQuery(options)}`);
737
911
  }
@@ -896,6 +1070,11 @@ export class CloudDataApi {
896
1070
  return this.request<DefiTvlPayload>("/cloud/crypto/defi/tvl", { signal: AbortSignal.timeout(30_000) });
897
1071
  }
898
1072
 
1073
+ /** Bitcoin's daily hash rate, the difficulty at each retarget, the next retarget's estimate and derived hashprice. */
1074
+ async getCloudBitcoinNetwork(): Promise<BitcoinNetworkPayload> {
1075
+ return this.request<BitcoinNetworkPayload>("/cloud/crypto/bitcoin/network", { signal: AbortSignal.timeout(30_000) });
1076
+ }
1077
+
899
1078
  /** Daily net flows of the US spot bitcoin or ether ETFs, per fund, in US$ millions. */
900
1079
  async getCloudCryptoEtfFlows(asset: CryptoEtfFlowAsset): Promise<CryptoEtfFlowsPayload> {
901
1080
  return this.request<CryptoEtfFlowsPayload>(
@@ -1023,8 +1202,16 @@ export class CloudDataApi {
1023
1202
  }
1024
1203
 
1025
1204
  /** What a futures symbol (GC=F, GCZ26, CL1) trades: its contract, terms and latest confirmed settlement. */
1026
- async getCloudFuturesContract(symbol: string): Promise<FuturesContractTermsPayload> {
1027
- return this.request<FuturesContractTermsPayload>(`/cloud/futures/contract/${encodeURIComponent(symbol)}`, { signal: AbortSignal.timeout(20_000) });
1205
+ async getCloudFuturesContract(symbol: string, options: BatchableRead = {}): Promise<FuturesContractTermsPayload> {
1206
+ if (!options.batch) return this.request<FuturesContractTermsPayload>(`/cloud/futures/contract/${encodeURIComponent(symbol)}`, { signal: AbortSignal.timeout(20_000) });
1207
+ const terms = await this.futuresTerms.read(symbol);
1208
+ if (!terms) throw new ApiRequestError("Not a futures symbol with known contract terms", 404);
1209
+ return terms;
1210
+ }
1211
+
1212
+ /** Many symbols' terms, each as getCloudFuturesContract reads it: null for one without known terms, an error for a failed read. */
1213
+ async getCloudFuturesContracts(symbols: string[]): Promise<{ items: Array<{ symbol: string; terms: FuturesContractTermsPayload | null; error?: string }> }> {
1214
+ return this.request(`/cloud/futures/contracts?${new URLSearchParams({ symbols: symbols.join(",") })}`, { signal: AbortSignal.timeout(30_000) });
1028
1215
  }
1029
1216
 
1030
1217
  async getCloudFuturesCurveAsOf(root: string, date: string): Promise<FuturesCurveAsOfPayload> {
@@ -23,6 +23,15 @@ export const OPTION_VOLATILITY_FIELDS = ["iv30Percent", "ivPercentile", "iv30Cha
23
23
  * listed only when asked (`include=ownership`), and omitted by a server from before them.
24
24
  */
25
25
  export const OWNERSHIP_FIELDS = ["sharesOutstanding", "floatShares", "shortPercentOfFloat", "institutionalOwnershipPercent"] as const;
26
+ /**
27
+ * ROIC, FCF yield, leverage, revenue and EPS CAGRs and consensus growth, on the bases `FA` and `EE` use; listed only
28
+ * when asked (`include=quality`), and omitted by a server from before them.
29
+ */
30
+ export const QUALITY_FIELDS = [
31
+ "roicPercent", "fcfYieldPercent", "netDebtToEbitda", "debtToEquity",
32
+ "revenueCagr3YPercent", "revenueCagr5YPercent", "epsCagr3YPercent", "epsCagr5YPercent",
33
+ "forwardRevenueGrowthPercent", "forwardEpsGrowthPercent",
34
+ ] as const;
26
35
  export const NUMERIC_FIELDS = [
27
36
  ...KPI_FIELDS,
28
37
  "price",
@@ -73,6 +82,7 @@ export const NUMERIC_FIELDS = [
73
82
  "ivToHv",
74
83
  ...OPTION_VOLATILITY_FIELDS,
75
84
  ...OWNERSHIP_FIELDS,
85
+ ...QUALITY_FIELDS,
76
86
  "hiringOpenRoles", "hiringNetAddsWeek", "hiringRemotePercent", "hiringZScore",
77
87
  "appAttentionScore", "appRankVelocity7d", "appRatingDrift7d", "appRatingCountGrowth7d",
78
88
  ] as const;
@@ -1,3 +1,5 @@
1
+ import { isNetworkFailure } from "../utils/network-failure";
2
+
1
3
  const HARD_SESSION_INVALID_PATTERNS = [
2
4
  /\b(user|account)\b.*\b(not found|deleted|removed|disabled|deactivated|suspended)\b/i,
3
5
  /\b(user|account)\b.*\bdoes(?:\s+not|n't)\s+exist\b/i,
@@ -61,6 +63,62 @@ export function isRetryableApiError(error: unknown): boolean {
61
63
  return error.code === "retryable_error" || status === 408 || status === 429 || (status !== undefined && status >= 500);
62
64
  }
63
65
 
66
+ /**
67
+ * A 200 reply saying its data is unavailable this time (a board's `unavailable`
68
+ * status, a market answer's `retryable_error`): the server's own source did
69
+ * not answer. Like a 5xx it may pass, so a saved copy answers it with its note.
70
+ * A permanent reason (a route not served yet, an unsupported symbol or series,
71
+ * sign-in or entitlement) comes as its status, or as a plain error, and stays
72
+ * the error.
73
+ */
74
+ export function sourceDidNotAnswer(message: string): ApiRequestError {
75
+ return new ApiRequestError(message, undefined, undefined, "retryable_error");
76
+ }
77
+
78
+ // Registered, so a mark set by another copy of this module is still read.
79
+ const ASKED_TWICE = Symbol.for("gloomberb.listingReadAskedTwice");
80
+
81
+ /**
82
+ * Marks a listing read's failure, or its failed answer, as the second of two
83
+ * asks (`CloudDataApi.readListing`). Only such a failure is said to be the data
84
+ * service not answering; any other 5xx keeps the server's own words.
85
+ */
86
+ export function markAskedTwice<T>(value: T): T {
87
+ if (value && typeof value === "object" && Object.isExtensible(value) && !wasAskedTwice(value)) {
88
+ Object.defineProperty(value, ASKED_TWICE, { value: true });
89
+ }
90
+ return value;
91
+ }
92
+
93
+ /** Whether a failure, or a failed answer, came from a listing read asked twice (`markAskedTwice`). */
94
+ export function wasAskedTwice(value: unknown): boolean {
95
+ return !!value && typeof value === "object" && (value as Record<symbol, unknown>)[ASKED_TWICE] === true;
96
+ }
97
+
98
+ /** Codes of a connection that dropped or stalled after it opened: Node's and the system's, then Bun's. */
99
+ const INTERRUPTED_CODES = new Set(["ECONNRESET", "ECONNABORTED", "EPIPE", "ETIMEDOUT", "ConnectionClosed"]);
100
+
101
+ /**
102
+ * A failure asking again later may get past, which a copy saved earlier may
103
+ * answer for: no connection, a connection that dropped, a timeout, or a server
104
+ * that was rate limited or overloaded (408, 429, 5xx). Anything else the
105
+ * server answered (another 4xx) and an answer that failed validation are
106
+ * refusals to show as errors, saved copy or not. A bare throw with nothing to
107
+ * classify is not taken for a refusal.
108
+ */
109
+ export function isTransientFailure(error: unknown): boolean {
110
+ if (!error || typeof error !== "object") return typeof error !== "string" || isNetworkFailure(error);
111
+ if (isNetworkFailure(error) || isRetryableApiError(error)) return true;
112
+ for (let current: unknown = error, depth = 0; current && typeof current === "object" && depth < 4; depth += 1) {
113
+ if (current instanceof ApiRequestError) return false;
114
+ const { name, code } = current as { name?: unknown; code?: unknown };
115
+ if (name === "AbortError" || name === "TimeoutError") return true;
116
+ if (typeof code === "string" && INTERRUPTED_CODES.has(code)) return true;
117
+ current = (current as { cause?: unknown }).cause;
118
+ }
119
+ return false;
120
+ }
121
+
64
122
  /** Reads `Retry-After` as milliseconds, accepting seconds or an HTTP date. */
65
123
  export function parseRetryAfterMs(
66
124
  header: string | null,
@@ -45,6 +45,33 @@ export interface GpuBoardRow extends GpuObservation {
45
45
  lastError: string | null;
46
46
  }
47
47
 
48
+ /**
49
+ * Gloom's daily settle: the official price of one GPU and form factor for a
50
+ * day, set at 20:00 UTC and never revised. A day with too few prices is
51
+ * "no-settle" with no price. A correction is a new entry naming the one it
52
+ * replaces.
53
+ */
54
+ export interface GpuSettle {
55
+ id: string;
56
+ /** The settle day, YYYY-MM-DD. */
57
+ date: string;
58
+ /** The moment the price stands for: the day at 20:00 UTC. */
59
+ settleAt: string;
60
+ gpuModel: string;
61
+ formFactor: string;
62
+ memoryGb: number | null;
63
+ label: string;
64
+ status: "settled" | "no-settle";
65
+ pricePerGpuHr: number | null;
66
+ currency: "USD";
67
+ kind: "settle" | "correction";
68
+ corrects: string | null;
69
+ publishedAt: string;
70
+ change1d: number | null;
71
+ change7d: number | null;
72
+ change30d: number | null;
73
+ }
74
+
48
75
  export interface GpuBoardPayload {
49
76
  access?: GpuAccess;
50
77
  generatedAt: string;
@@ -53,6 +80,8 @@ export interface GpuBoardPayload {
53
80
  stale: boolean;
54
81
  gaps: string[];
55
82
  rows: GpuBoardRow[];
83
+ /** Each GPU's latest daily settle, the same on every plan. Older servers send none. */
84
+ settles?: GpuSettle[];
56
85
  }
57
86
 
58
87
  export interface GpuHistoryPayload {
@@ -601,8 +601,6 @@ class GloomApiClient {
601
601
  async recordExperimentExposure(payload: {
602
602
  eventId: string; surface: "web" | "desktop" | "tui" | "cli"; anonymousId?: string;
603
603
  attribution?: Record<string, string>; experiment: string; variant?: string;
604
- /** Where the experiment showed, by a content-free id like an upgrade placement. */
605
- placement?: string;
606
604
  }, signal?: AbortSignal): Promise<import("./web-experiments").ExperimentAnswer> {
607
605
  return this.request("/activity/research", {
608
606
  method: "POST",
@@ -955,6 +953,7 @@ class GloomApiClient {
955
953
  getCloudStablecoinSupply = this.data.getCloudStablecoinSupply.bind(this.data);
956
954
  getCloudStablecoinSupplyHistory = this.data.getCloudStablecoinSupplyHistory.bind(this.data);
957
955
  getCloudDefiTvl = this.data.getCloudDefiTvl.bind(this.data);
956
+ getCloudBitcoinNetwork = this.data.getCloudBitcoinNetwork.bind(this.data);
958
957
  getCloudCentralBankRates = this.data.getCloudCentralBankRates.bind(this.data);
959
958
  getMobileAlertHistory = this.data.getMobileAlertHistory.bind(this.data);
960
959
  getScannerFlowHistory = this.data.getScannerFlowHistory.bind(this.data);
@@ -993,6 +992,8 @@ class GloomApiClient {
993
992
  getCloudFuturesCurve = this.data.getCloudFuturesCurve.bind(this.data);
994
993
  getCloudFuturesCurveAsOf = this.data.getCloudFuturesCurveAsOf.bind(this.data);
995
994
  getCloudFuturesContract = this.data.getCloudFuturesContract.bind(this.data);
995
+ getCloudFuturesContracts = this.data.getCloudFuturesContracts.bind(this.data);
996
+ batchesReads = this.data.batchesReads.bind(this.data);
996
997
  getCloudRatePath = this.data.getCloudRatePath.bind(this.data);
997
998
  getCloudShiller = this.data.getCloudShiller.bind(this.data);
998
999
  getCloudCotBoard = this.data.getCloudCotBoard.bind(this.data);
@@ -1025,6 +1026,8 @@ class GloomApiClient {
1025
1026
  analyzeCloudExposure = this.data.analyzeCloudExposure.bind(this.data);
1026
1027
  getCloudExposureScenarios = this.data.getCloudExposureScenarios.bind(this.data);
1027
1028
  getCloudSupplyGraph = this.data.getCloudSupplyGraph.bind(this.data);
1029
+ getCloudSupplyChains = this.data.getCloudSupplyChains.bind(this.data);
1030
+ getCloudSupplyGraphs = this.data.getCloudSupplyGraphs.bind(this.data);
1028
1031
  getCloudSupplyPaths = this.data.getCloudSupplyPaths.bind(this.data);
1029
1032
  getCloudSupplyChain = this.data.getCloudSupplyChain.bind(this.data);
1030
1033
  getCloudAwards = this.data.getCloudAwards.bind(this.data);
@@ -1,6 +1,6 @@
1
1
  import { useSyncExternalStore } from "react";
2
2
  import { apiClient } from "./index";
3
- import { resolvePlanAccess, type PlanAccess } from "./plan-rules";
3
+ import { freeQuoteDelayMinutes, resolvePlanAccess, type PlanAccess, type QuoteDelayFacts } from "./plan-rules";
4
4
 
5
5
  export * from "./plan-rules";
6
6
 
@@ -25,3 +25,12 @@ export function usePlanAccess(): PlanAccess {
25
25
  );
26
26
  return resolvePlanAccess(apiClient.getCurrentUser());
27
27
  }
28
+
29
+ /**
30
+ * The delay this account sees on a price (see {@link freeQuoteDelayMinutes}):
31
+ * the free tier's on a price that is not live, null with Pro or a trial.
32
+ * Follows sign-in and plan changes.
33
+ */
34
+ export function useFreeQuoteDelay(quote: QuoteDelayFacts | null | undefined): number | null {
35
+ return freeQuoteDelayMinutes(usePlanAccess(), quote);
36
+ }
@@ -1,6 +1,8 @@
1
1
  // The plan rules alone, free of React and the API client, so the provider
2
2
  // router can gate real-time routing on the same entitlement the panes show.
3
3
 
4
+ import type { Quote } from "../types/financials";
5
+
4
6
  /**
5
7
  * Minimum shape the plan helpers need. Both `AuthUser` (from `/auth/get-session`)
6
8
  * and `AccountProfile` (from `/account/profile`) satisfy it.
@@ -70,6 +72,39 @@ export function hasProAccess(user: PlanAccessUser | null | undefined, now = Date
70
72
  return user.effectivePlan === "pro" && !user.trialEndsAt;
71
73
  }
72
74
 
75
+ /** What of a price decides whether a free account sees it late. */
76
+ export type QuoteDelayFacts = Pick<Quote, "dataSource" | "delisting" | "priceObservation">;
77
+
78
+ /**
79
+ * The delay in minutes an account sees on a price: the free tier's quote
80
+ * delay when the account has no real-time access (signed out, free, a trial
81
+ * that ended) and the price is not live, null otherwise. A live price (a
82
+ * coin, a broker's own feed) has none, and neither does a price with no delay
83
+ * to speak of (a delisted line's last trade, a fund's NAV). Pro and trial
84
+ * accounts never see one.
85
+ */
86
+ export function freeQuoteDelayMinutes(
87
+ access: Pick<PlanAccess, "hasProAccess">,
88
+ quote: QuoteDelayFacts | null | undefined,
89
+ ): number | null {
90
+ if (access.hasProAccess || !quote) return null;
91
+ if (quote.dataSource === "live" || quote.delisting || quote.priceObservation === "nav") return null;
92
+ return CLOUD_QUOTE_DELAY_MINUTES;
93
+ }
94
+
95
+ /** The delay of the first of `quotes` an account sees late (see {@link freeQuoteDelayMinutes}), or null when none is. */
96
+ export function freeQuotesDelayMinutes(
97
+ access: Pick<PlanAccess, "hasProAccess">,
98
+ quotes: Iterable<QuoteDelayFacts | null | undefined>,
99
+ ): number | null {
100
+ if (access.hasProAccess) return null;
101
+ for (const quote of quotes) {
102
+ const minutes = freeQuoteDelayMinutes(access, quote);
103
+ if (minutes != null) return minutes;
104
+ }
105
+ return null;
106
+ }
107
+
73
108
  export function resolvePlanAccess(
74
109
  user: PlanAccessUser | null | undefined,
75
110
  now = Date.now(),
@@ -4,7 +4,6 @@ import { getCurrentPluginTarget } from "../plugins/current-target";
4
4
  import type { DesktopDeepLinkBridge } from "../types/desktop-deeplink";
5
5
  import { exposeExperiment, storedExperimentAssignments } from "./web-experiments";
6
6
  import { createWallExperimentSession, type WallExperiment, type WallExperimentContext, type WallTeaserVariant } from "./wall-experiments";
7
- import { createUpgradeExperimentSession, UPGRADE_PERSONALIZED, type UpgradeExperimentContext, type UpgradePersonalizedVariant } from "./upgrade-experiment";
8
7
  import { hasProAccess } from "./plan-rules";
9
8
  import { withDeadline } from "../utils/async-deadline";
10
9
  import { browserDoNotTrack } from "../telemetry/crash-reports-dom";
@@ -43,7 +42,6 @@ let anonymousId: string | undefined;
43
42
  let attribution: Record<string, string> = {};
44
43
  const wallExperiment = createWallExperimentSession();
45
44
  const signinWallExperiment = createWallExperimentSession("wall_teaser_signin");
46
- const upgradeExperiment = createUpgradeExperimentSession();
47
45
  const WALL_VIEWS_SESSION_KEY = "gloomberb.wall-teaser.views";
48
46
 
49
47
  // The desktop app also keeps a copy of these two on disk (electrobun/view/view-ids.ts).
@@ -350,7 +348,7 @@ function attributionPayload(): Record<string, string> | undefined {
350
348
  }
351
349
 
352
350
  export interface ResearchActivityDetails {
353
- /** The desks picked at "What do you trade?", on the milestone that ends the first run. */
351
+ /** The desks picked at "What do you trade most?", on the milestone that ends the first run. */
354
352
  desks?: readonly string[];
355
353
  /**
356
354
  * Which prompt raised an `upgrade_intent` (a pane footer, a wall, the
@@ -538,35 +536,6 @@ export function exposeWallTeaser(experiment: WallExperiment = "wall_teaser"): Pr
538
536
  });
539
537
  }
540
538
 
541
- function upgradeExperimentContext(): UpgradeExperimentContext {
542
- const { accountId, pro, optedOut, automated, session } = wallExperimentContext();
543
- return { accountId, pro, optedOut, automated, session };
544
- }
545
-
546
- /** The `upgrade_personalized` arm this session already has, without asking; see upgrade-experiment.ts. */
547
- export function knownUpgradePersonalized(): UpgradePersonalizedVariant | null | undefined {
548
- return upgradeExperiment.known(upgradeExperimentContext());
549
- }
550
-
551
- /**
552
- * Asks for the `upgrade_personalized` arm when an upgrade surface is shown to
553
- * a free account that has tickers to name, once per account and session. The
554
- * request says where it showed and nothing about the holdings.
555
- */
556
- export function exposeUpgradePersonalized(placement: string): Promise<UpgradePersonalizedVariant | null> {
557
- const context = upgradeExperimentContext();
558
- const surface = getCurrentPluginTarget();
559
- return upgradeExperiment.expose(context, () => {
560
- const controller = new AbortController();
561
- return withDeadline(apiClient.recordExperimentExposure({
562
- eventId: crypto.randomUUID(),
563
- surface,
564
- experiment: UPGRADE_PERSONALIZED,
565
- ...(ACTIVITY_ID.test(placement) ? { placement } : {}),
566
- }, controller.signal), 3_000, "Upgrade experiment request timed out", (error) => controller.abort(error));
567
- });
568
- }
569
-
570
539
  /** Records the visible wall once per placement, including visitors outside the experiment. */
571
540
  export function recordWallViewed(placement: string, teaserKind?: "summary" | "sample" | "none"): void {
572
541
  recordResearchActivity("wall_viewed", undefined, undefined, { placement, teaser_kind: teaserKind });