gloomberb 0.19.2 → 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 (472) hide show
  1. package/README.md +7 -1
  2. package/package.json +1 -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 +20 -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.tsx +23 -1
  20. package/src/architecture/core-builtin-allowlist.json +2 -1
  21. package/src/capabilities/factories.ts +1 -0
  22. package/src/cli/ascii-output.ts +42 -0
  23. package/src/cli/base-converter.ts +10 -2
  24. package/src/cli/command-options.ts +16 -1
  25. package/src/cli/commands/crypto-hints.ts +23 -4
  26. package/src/cli/commands/market.ts +92 -23
  27. package/src/cli/commands/news-search.ts +24 -5
  28. package/src/cli/commands/overview.ts +9 -4
  29. package/src/cli/commands/search.ts +71 -12
  30. package/src/cli/commands/system.ts +22 -30
  31. package/src/cli/commands/ticker.ts +49 -9
  32. package/src/cli/commands/version.ts +33 -0
  33. package/src/cli/context.ts +5 -9
  34. package/src/cli/data-dir.ts +7 -0
  35. package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
  36. package/src/cli/desktop-pane-shot-browser.ts +320 -0
  37. package/src/cli/desktop-pane-shot-routes.ts +26 -1
  38. package/src/cli/desktop-pane-shot.ts +196 -256
  39. package/src/cli/early-command.ts +42 -0
  40. package/src/cli/earnings-calendar.ts +9 -0
  41. package/src/cli/entry.ts +19 -13
  42. package/src/cli/errors.ts +12 -2
  43. package/src/cli/help.ts +1 -1
  44. package/src/cli/history-export.ts +22 -2
  45. package/src/cli/history-rows.ts +154 -4
  46. package/src/cli/index.ts +18 -1
  47. package/src/cli/known-gaps.ts +40 -5
  48. package/src/cli/listing-arg.ts +46 -9
  49. package/src/cli/options.ts +1 -1
  50. package/src/cli/output-file.ts +60 -13
  51. package/src/cli/pane-functions/catalog.ts +14 -3
  52. package/src/cli/pane-functions/data.ts +1 -1
  53. package/src/cli/pane-functions/dom.ts +45 -0
  54. package/src/cli/pane-functions/freshness.ts +46 -9
  55. package/src/cli/pane-functions/function-help.ts +17 -9
  56. package/src/cli/pane-functions/headless.ts +22 -14
  57. package/src/cli/pane-functions/index.ts +6 -2
  58. package/src/cli/pane-functions/options.ts +6 -2
  59. package/src/cli/pane-functions/resolver.ts +27 -5
  60. package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
  61. package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
  62. package/src/cli/pane-functions/screenshot-http.ts +70 -0
  63. package/src/cli/pane-functions/screenshot-render.ts +19 -5
  64. package/src/cli/quote-answer.ts +20 -0
  65. package/src/cli/registry.ts +41 -13
  66. package/src/cli/result.ts +12 -6
  67. package/src/cli/ticker-resolution.ts +2 -1
  68. package/src/cli/types.ts +2 -0
  69. package/src/components/chart/composite/format.ts +3 -0
  70. package/src/components/chart-table/header.tsx +1 -1
  71. package/src/components/chart-table/layout.ts +28 -11
  72. package/src/components/command-bar/action-runtime.ts +4 -0
  73. package/src/components/command-bar/commands/direct/index.ts +18 -33
  74. package/src/components/command-bar/commands/direct/runtime.ts +5 -0
  75. package/src/components/command-bar/commands/registry.ts +10 -1
  76. package/src/components/command-bar/routes/root/command-items.ts +9 -4
  77. package/src/components/command-bar/routes/root/runtime.ts +24 -1
  78. package/src/components/command-bar/surface/index.tsx +9 -3
  79. package/src/components/company-logo.tsx +2 -1
  80. package/src/components/layout/header.tsx +8 -3
  81. package/src/components/layout/market-summary.ts +25 -7
  82. package/src/components/layout/pane/footer/index.tsx +7 -2
  83. package/src/components/layout/pane/footer/model.ts +40 -5
  84. package/src/components/market-board/index.tsx +3 -2
  85. package/src/components/onboarding/desks-step.tsx +12 -4
  86. package/src/components/onboarding/first-run-workspace.ts +81 -4
  87. package/src/components/onboarding/onboarding-frame.tsx +2 -1
  88. package/src/components/onboarding/onboarding-keyboard.ts +8 -5
  89. package/src/components/onboarding/onboarding-wizard.tsx +54 -23
  90. package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
  91. package/src/components/onboarding/run-setup-again.ts +24 -0
  92. package/src/components/onboarding/use-onboarding-progress.ts +33 -15
  93. package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
  94. package/src/components/onboarding/wizard-model.ts +57 -3
  95. package/src/components/onboarding/wizard-positions.ts +1 -1
  96. package/src/components/ui/delayed-mark.tsx +56 -0
  97. package/src/components/ui/external-link.tsx +3 -6
  98. package/src/components/ui/icon.tsx +8 -1
  99. package/src/components/ui/logo-mark.tsx +8 -5
  100. package/src/components/ui/stat-grid.tsx +263 -19
  101. package/src/data/config/store/node.ts +11 -5
  102. package/src/data/config/store/normalize.ts +16 -3
  103. package/src/data/profile-permissions.ts +47 -0
  104. package/src/data/resource-store.ts +34 -31
  105. package/src/data/sqlite/access.ts +19 -8
  106. package/src/data/sqlite/checkpointer.ts +95 -8
  107. package/src/data/sqlite/database.ts +40 -7
  108. package/src/docs/snapshot.json +191 -78
  109. package/src/i18n/es.ts +18 -2
  110. package/src/i18n/ja.ts +18 -2
  111. package/src/i18n/ko.ts +18 -2
  112. package/src/i18n/zh-cn.ts +18 -2
  113. package/src/i18n/zh-tw.ts +18 -2
  114. package/src/layout/desk-keys.ts +4 -1
  115. package/src/layout/desks.ts +38 -5
  116. package/src/layout-marketplace/gallery-desktop.tsx +45 -16
  117. package/src/market-data/batched-reads.ts +16 -0
  118. package/src/market-data/coordinator/auxiliary.ts +4 -2
  119. package/src/market-data/coordinator/index.ts +11 -2
  120. package/src/market-data/futures-board.ts +1 -1
  121. package/src/market-data/futures-contract.ts +3 -1
  122. package/src/market-data/market/index-level.ts +6 -4
  123. package/src/market-data/published-apac-sessions.ts +6 -0
  124. package/src/news/aggregator.ts +7 -4
  125. package/src/news/news-model.ts +11 -0
  126. package/src/news/story-id.ts +22 -0
  127. package/src/news/ticker-symbols.ts +17 -1
  128. package/src/notifications/app-notifier.ts +35 -14
  129. package/src/plugins/builtin/account-management/form-components.tsx +1 -1
  130. package/src/plugins/builtin/account-management/index.tsx +2 -3
  131. package/src/plugins/builtin/account-management/model.ts +1 -1
  132. package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
  133. package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
  134. package/src/plugins/builtin/alerts/events.ts +10 -2
  135. package/src/plugins/builtin/alerts/index.tsx +5 -5
  136. package/src/plugins/builtin/alerts/research-builder.ts +5 -1
  137. package/src/plugins/builtin/alerts/research-rules.ts +18 -0
  138. package/src/plugins/builtin/alerts/targets.ts +4 -1
  139. package/src/plugins/builtin/attention/headless.ts +1 -1
  140. package/src/plugins/builtin/attention/index.tsx +2 -4
  141. package/src/plugins/builtin/attention/listing.ts +1 -1
  142. package/src/plugins/builtin/attention/series.ts +1 -1
  143. package/src/plugins/builtin/awards/index.tsx +1 -2
  144. package/src/plugins/builtin/backtest/client.ts +1 -0
  145. package/src/plugins/builtin/backtest/index.tsx +1 -2
  146. package/src/plugins/builtin/backtest/rules.ts +3 -1
  147. package/src/plugins/builtin/backtest/window.ts +8 -7
  148. package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
  149. package/src/plugins/builtin/broker-manager/index.tsx +1 -1
  150. package/src/plugins/builtin/buildout/cells.tsx +5 -0
  151. package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
  152. package/src/plugins/builtin/buildout/company/id.ts +2 -0
  153. package/src/plugins/builtin/buildout/company/model.ts +247 -0
  154. package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
  155. package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
  156. package/src/plugins/builtin/buildout/facilities.ts +309 -0
  157. package/src/plugins/builtin/buildout/format.ts +9 -0
  158. package/src/plugins/builtin/buildout/index.tsx +51 -5
  159. package/src/plugins/builtin/buildout/model/index.ts +62 -10
  160. package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
  161. package/src/plugins/builtin/buildout/model/types.ts +11 -2
  162. package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
  163. package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
  164. package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
  165. package/src/plugins/builtin/buildout/table-model.ts +26 -2
  166. package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
  167. package/src/plugins/builtin/cds/headless.ts +10 -2
  168. package/src/plugins/builtin/cds/model.ts +7 -3
  169. package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
  170. package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
  171. package/src/plugins/builtin/changelog/index.tsx +3 -2
  172. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -2
  173. package/src/plugins/builtin/chart-composer/headless.ts +88 -21
  174. package/src/plugins/builtin/chart-composer/index.tsx +2 -3
  175. package/src/plugins/builtin/chart-composer/pane.tsx +26 -2
  176. package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
  177. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  178. package/src/plugins/builtin/chat/controller/index.ts +1 -1
  179. package/src/plugins/builtin/chat/controller/messages.ts +15 -4
  180. package/src/plugins/builtin/chat/ghost-user.ts +8 -0
  181. package/src/plugins/builtin/chat/index.tsx +5 -5
  182. package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
  183. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  184. package/src/plugins/builtin/cloud/index.tsx +1 -2
  185. package/src/plugins/builtin/cloud/plugin.tsx +6 -4
  186. package/src/plugins/builtin/cloud/team/module.tsx +1 -1
  187. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
  188. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
  189. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
  190. package/src/plugins/builtin/comment-letters/index.tsx +1 -2
  191. package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
  192. package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
  193. package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
  194. package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
  195. package/src/plugins/builtin/congress-trades/headless.ts +2 -0
  196. package/src/plugins/builtin/congress-trades/model.ts +92 -64
  197. package/src/plugins/builtin/congress-trades/table.tsx +14 -2
  198. package/src/plugins/builtin/connections/index.ts +2 -3
  199. package/src/plugins/builtin/cot/index.tsx +1 -2
  200. package/src/plugins/builtin/cpi/index.tsx +1 -2
  201. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  202. package/src/plugins/builtin/credit-boards/client.ts +7 -2
  203. package/src/plugins/builtin/credit-boards/headless.ts +25 -12
  204. package/src/plugins/builtin/credit-boards/model.ts +29 -0
  205. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
  206. package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
  207. package/src/plugins/builtin/crypto-board/headless.ts +4 -3
  208. package/src/plugins/builtin/crypto-board/index.tsx +4 -2
  209. package/src/plugins/builtin/crypto-board/series.ts +2 -0
  210. package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
  211. package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
  212. package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
  213. package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
  214. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
  215. package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
  216. package/src/plugins/builtin/debt-maturities/model.ts +17 -1
  217. package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
  218. package/src/plugins/builtin/debug/index.tsx +1 -1
  219. package/src/plugins/builtin/dividend-yield/client.ts +32 -2
  220. package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
  221. package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
  222. package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
  223. package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
  224. package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
  225. package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
  226. package/src/plugins/builtin/dividend-yield/view.ts +21 -0
  227. package/src/plugins/builtin/docs/index.tsx +2 -3
  228. package/src/plugins/builtin/doe/headless.ts +16 -3
  229. package/src/plugins/builtin/doe/index.tsx +5 -4
  230. package/src/plugins/builtin/earnings/board.tsx +2 -1
  231. package/src/plugins/builtin/earnings/history.tsx +2 -1
  232. package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
  233. package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
  234. package/src/plugins/builtin/econ/calendar-model.ts +29 -6
  235. package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
  236. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  237. package/src/plugins/builtin/equity-screener/headless.ts +1 -0
  238. package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
  239. package/src/plugins/builtin/equity-screener/index.tsx +1 -2
  240. package/src/plugins/builtin/equity-screener/model.ts +28 -4
  241. package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
  242. package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
  243. package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
  244. package/src/plugins/builtin/exposure/holdings.ts +35 -13
  245. package/src/plugins/builtin/exposure/index.tsx +1 -2
  246. package/src/plugins/builtin/fear-greed/cli.ts +44 -0
  247. package/src/plugins/builtin/fear-greed/index.tsx +5 -53
  248. package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
  249. package/src/plugins/builtin/fixed-income/index.tsx +1 -2
  250. package/src/plugins/builtin/futures/board-reads.ts +25 -0
  251. package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
  252. package/src/plugins/builtin/futures/index.tsx +15 -0
  253. package/src/plugins/builtin/futures/model.ts +14 -0
  254. package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
  255. package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
  256. package/src/plugins/builtin/futures-curve/headless.ts +6 -1
  257. package/src/plugins/builtin/futures-curve/index.tsx +1 -2
  258. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  259. package/src/plugins/builtin/gpu/board.tsx +51 -12
  260. package/src/plugins/builtin/gpu/client.ts +15 -2
  261. package/src/plugins/builtin/gpu/equities.tsx +5 -2
  262. package/src/plugins/builtin/gpu/headless.ts +14 -2
  263. package/src/plugins/builtin/gpu/index.tsx +1 -2
  264. package/src/plugins/builtin/gpu/model.ts +20 -2
  265. package/src/plugins/builtin/gpu/pane.tsx +1 -1
  266. package/src/plugins/builtin/help/index.tsx +1 -1
  267. package/src/plugins/builtin/holders/index-holders.ts +24 -12
  268. package/src/plugins/builtin/holders/index.tsx +2 -3
  269. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
  270. package/src/plugins/builtin/iv-history/index.tsx +1 -2
  271. package/src/plugins/builtin/jodi/index.tsx +1 -2
  272. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
  273. package/src/plugins/builtin/layout-manager/index.tsx +9 -8
  274. package/src/plugins/builtin/macro-day/pane.tsx +4 -2
  275. package/src/plugins/builtin/market-halts/index.tsx +1 -2
  276. package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
  277. package/src/plugins/builtin/market-movers/index.tsx +7 -1
  278. package/src/plugins/builtin/market-movers/session-body.tsx +4 -1
  279. package/src/plugins/builtin/market-valuation/index.tsx +1 -2
  280. package/src/plugins/builtin/members/index.tsx +1 -2
  281. package/src/plugins/builtin/money-markets/index.tsx +1 -2
  282. package/src/plugins/builtin/money-markets/pane.tsx +2 -1
  283. package/src/plugins/builtin/news/headless.ts +7 -0
  284. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  285. package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
  286. package/src/plugins/builtin/notes/index.tsx +3 -6
  287. package/src/plugins/builtin/openfda/index.tsx +1 -2
  288. package/src/plugins/builtin/options/view.tsx +6 -5
  289. package/src/plugins/builtin/options-calculator/index.tsx +1 -2
  290. package/src/plugins/builtin/options-positioning/index.tsx +1 -2
  291. package/src/plugins/builtin/options-scenario/index.tsx +2 -4
  292. package/src/plugins/builtin/perps/index.tsx +1 -2
  293. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
  294. package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
  295. package/src/plugins/builtin/plugin-module.ts +16 -2
  296. package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
  297. package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
  298. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
  299. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
  300. package/src/plugins/builtin/power/index.tsx +1 -2
  301. package/src/plugins/builtin/rate-path/index.tsx +1 -2
  302. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  303. package/src/plugins/builtin/realized-vol/index.tsx +1 -2
  304. package/src/plugins/builtin/realized-vol/model.ts +16 -2
  305. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  306. package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
  307. package/src/plugins/builtin/research/analyst-headless.ts +33 -6
  308. package/src/plugins/builtin/research/analyst-model.ts +37 -2
  309. package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
  310. package/src/plugins/builtin/research/index.tsx +5 -8
  311. package/src/plugins/builtin/research-search/index.tsx +1 -2
  312. package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
  313. package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
  314. package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
  315. package/src/plugins/builtin/roadmap/index.tsx +2 -3
  316. package/src/plugins/builtin/roadmap/pane.tsx +49 -2
  317. package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
  318. package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
  319. package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
  320. package/src/plugins/builtin/sec/filing-content.ts +38 -6
  321. package/src/plugins/builtin/sec/headless.ts +5 -1
  322. package/src/plugins/builtin/sec/index.tsx +4 -2
  323. package/src/plugins/builtin/sec/model.ts +2 -0
  324. package/src/plugins/builtin/sectors/index.tsx +11 -5
  325. package/src/plugins/builtin/series-inventory.ts +2 -0
  326. package/src/plugins/builtin/shared/cloud-resource.ts +60 -0
  327. package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
  328. package/src/plugins/builtin/shared/forming-bars.ts +29 -0
  329. package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
  330. package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
  331. package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
  332. package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
  333. package/src/plugins/builtin/short-volume/pane.tsx +2 -1
  334. package/src/plugins/builtin/social-mentions/index.tsx +1 -2
  335. package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
  336. package/src/plugins/builtin/supply-chain/client.ts +22 -1
  337. package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
  338. package/src/plugins/builtin/supply-chain/index.tsx +1 -2
  339. package/src/plugins/builtin/thirteenf/api.ts +116 -13
  340. package/src/plugins/builtin/thirteenf/data.ts +132 -30
  341. package/src/plugins/builtin/thirteenf/index.tsx +2 -4
  342. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  343. package/src/plugins/builtin/thirteenf/pane.tsx +20 -11
  344. package/src/plugins/builtin/thirteenf/types.ts +12 -0
  345. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +17 -4
  346. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
  347. package/src/plugins/builtin/ticker-detail/headless.ts +11 -3
  348. package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
  349. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
  350. package/src/plugins/builtin/time-sales/index.tsx +1 -2
  351. package/src/plugins/builtin/time-sales/pane.tsx +3 -1
  352. package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
  353. package/src/plugins/builtin/trace-bonds/model.ts +44 -10
  354. package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
  355. package/src/plugins/builtin/treasury-auctions/pane.tsx +2 -2
  356. package/src/plugins/builtin/vol-surface/client.ts +18 -3
  357. package/src/plugins/builtin/vol-surface/index.tsx +1 -2
  358. package/src/plugins/builtin/volatility/index.tsx +1 -2
  359. package/src/plugins/builtin/world-indices/footer.ts +6 -1
  360. package/src/plugins/builtin/world-venue-map/client.ts +10 -1
  361. package/src/plugins/builtin/world-venue-map/index.ts +6 -2
  362. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
  363. package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
  364. package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
  365. package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
  366. package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
  367. package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
  368. package/src/plugins/builtin/yield-curve/curves.ts +16 -2
  369. package/src/plugins/builtin/yield-curve/headless.ts +3 -3
  370. package/src/plugins/bundle.ts +52 -2
  371. package/src/plugins/catalog-browser.ts +2 -2
  372. package/src/plugins/catalog-cli.ts +20 -0
  373. package/src/plugins/host-link.ts +13 -1
  374. package/src/plugins/installer.ts +15 -4
  375. package/src/plugins/loader.ts +43 -11
  376. package/src/plugins/peer-imports.ts +201 -0
  377. package/src/plugins/peer-retry.ts +26 -29
  378. package/src/plugins/plugin-process.ts +128 -0
  379. package/src/plugins/redirection-guard.ts +45 -0
  380. package/src/remote/endpoint.ts +16 -3
  381. package/src/renderers/browser/main.tsx +3 -2
  382. package/src/renderers/cloudflare/http-proxy.ts +7 -123
  383. package/src/renderers/dom/desktop/icons.tsx +2 -0
  384. package/src/renderers/dom/host/image-surface.tsx +46 -21
  385. package/src/renderers/dom/styles.css +7 -0
  386. package/src/renderers/dom/toast-host.tsx +2 -0
  387. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  388. package/src/renderers/electrobun/bun/desktop/initialization.ts +3 -2
  389. package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
  390. package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
  391. package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
  392. package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
  393. package/src/renderers/electrobun/bun/window/create.ts +4 -2
  394. package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
  395. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
  396. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
  397. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
  398. package/src/renderers/electrobun/view/main.tsx +7 -2
  399. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
  400. package/src/renderers/opentui/browser-open.ts +10 -4
  401. package/src/renderers/opentui/image/surface.tsx +1 -1
  402. package/src/renderers/opentui/start.tsx +12 -6
  403. package/src/renderers/opentui/toast-host.tsx +2 -1
  404. package/src/series/registry.ts +18 -0
  405. package/src/series/standalone.ts +10 -1
  406. package/src/series/types.ts +7 -0
  407. package/src/series/validate.ts +11 -2
  408. package/src/sources/gloomberb-cloud/index.ts +28 -5
  409. package/src/sources/gloomberb-cloud/news.ts +24 -3
  410. package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
  411. package/src/sources/provider-errors.ts +26 -3
  412. package/src/sources/provider-router/history.ts +5 -0
  413. package/src/sources/provider-router/index.ts +13 -0
  414. package/src/sync/core-contributors.ts +32 -0
  415. package/src/telemetry/attention-counts.ts +64 -6
  416. package/src/telemetry/attention-setting.ts +33 -0
  417. package/src/telemetry/crash-reports-node.ts +3 -1
  418. package/src/telemetry/stall-clocks.ts +93 -0
  419. package/src/telemetry/stall-reports-node.ts +12 -7
  420. package/src/telemetry/stall-reports.ts +34 -5
  421. package/src/test-support/preload.ts +9 -0
  422. package/src/tickers/company-name.ts +19 -0
  423. package/src/tickers/listing-fate.ts +14 -3
  424. package/src/tickers/saved-instrument.ts +17 -0
  425. package/src/tickers/search/home-listing.ts +13 -2
  426. package/src/tickers/search/index.ts +4 -1
  427. package/src/tickers/search/types.ts +6 -1
  428. package/src/time-series/bar-aggregation.ts +7 -1
  429. package/src/time-series/base-series.ts +3 -3
  430. package/src/time-series/chart-bounds.ts +1 -1
  431. package/src/time-series/chart-data.ts +5 -2
  432. package/src/time-series/chart-seed.ts +1 -1
  433. package/src/time-series/coin-session-close.ts +211 -0
  434. package/src/time-series/live-bars.ts +13 -2
  435. package/src/time-series/live-quotes.ts +1 -1
  436. package/src/time-series/official-close.ts +159 -0
  437. package/src/time-series/price-comparison.ts +55 -6
  438. package/src/time-series/resolve.ts +12 -3
  439. package/src/time-series/series-presentation.ts +1 -1
  440. package/src/time-series/studies/formula.ts +205 -0
  441. package/src/time-series/studies/indicators.ts +299 -0
  442. package/src/time-series/studies/leverage.ts +75 -0
  443. package/src/time-series/studies/pair.ts +138 -0
  444. package/src/time-series/studies/resolve.ts +333 -0
  445. package/src/time-series/studies/shared.ts +141 -0
  446. package/src/time-series/studies/units.ts +243 -0
  447. package/src/time-series/studies/vwap.ts +274 -0
  448. package/src/types/config.ts +18 -2
  449. package/src/types/data-provider.ts +23 -0
  450. package/src/types/financials.ts +22 -0
  451. package/src/types/headless.ts +42 -3
  452. package/src/types/plugin.ts +32 -3
  453. package/src/ui/host.tsx +11 -0
  454. package/src/ui/toast.tsx +26 -1
  455. package/src/utils/ascii-text.ts +140 -0
  456. package/src/utils/cli-output.ts +91 -18
  457. package/src/utils/concurrency.ts +24 -0
  458. package/src/utils/exchanges.ts +6 -0
  459. package/src/utils/external-url.ts +20 -7
  460. package/src/utils/futures-generic.ts +4 -4
  461. package/src/utils/http-proxy-upstream.ts +124 -0
  462. package/src/utils/main-thread-stall.ts +75 -3
  463. package/src/utils/perf-marks.ts +36 -2
  464. package/src/utils/private-files.ts +141 -0
  465. package/src/utils/public-address.ts +99 -0
  466. package/src/account/upgrade-personalization.ts +0 -57
  467. package/src/account/upgrade-tickers.ts +0 -124
  468. package/src/api-client/upgrade-experiment.ts +0 -91
  469. package/src/plugins/builtin/cloud/browser.tsx +0 -3
  470. package/src/renderers/electrobun/launcher/preload.ts +0 -13
  471. package/src/renderers/electrobun/launcher/windows-emulation.ts +0 -50
  472. package/src/time-series/studies.ts +0 -1628
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The step a daily study takes before it reads closes (BT's run, HVG's
3
+ * estimates): the newest session is left out while it is not final, and the
4
+ * report says so. Not final is a session still trading (`partial`: its close
5
+ * is the latest price) or a future's closed session whose settlement is not
6
+ * in yet (`unsettled`: its close is the last trade). Either close moves
7
+ * before the day is done, so a study that kept it would give another answer
8
+ * read again.
9
+ */
10
+ interface DatedBar {
11
+ partial?: boolean;
12
+ unsettled?: boolean;
13
+ }
14
+
15
+ export function isFormingBar(bar: DatedBar): boolean {
16
+ return bar.partial === true || bar.unsettled === true;
17
+ }
18
+
19
+ /**
20
+ * What the bars left out are, after their dates: "is still forming
21
+ * (partial)", "is not settled yet (unsettled)", or for several of both kinds
22
+ * "are still forming or not settled yet".
23
+ */
24
+ export function formingBarsState(bars: readonly DatedBar[]): string {
25
+ const verb = bars.length === 1 ? "is" : "are";
26
+ if (bars.every((bar) => bar.partial === true)) return `${verb} still forming (partial)`;
27
+ if (bars.every((bar) => bar.unsettled === true && bar.partial !== true)) return `${verb} not settled yet (unsettled)`;
28
+ return `${verb} still forming or not settled yet`;
29
+ }
@@ -2,6 +2,7 @@ import { intradayBarText } from "../../../market-data/quotes/intraday-bar";
2
2
  import type { Quote, QuoteIntradayBar } from "../../../types/financials";
3
3
  import { isFxMarketOpen } from "../../../utils/fx-market-hours";
4
4
  import { quoteFreshnessFields } from "./report-freshness";
5
+ import { formatUtcDayTime } from "../../../utils/utc-time";
5
6
 
6
7
  /** The venue FX pairs trade on, so a report can place the FX market. */
7
8
  const FX_VENUE = "CCY";
@@ -56,6 +57,22 @@ export function fxRateNotes(legs: ReadonlyMap<string, FxRateTiming>, now = Date.
56
57
  });
57
58
  }
58
59
 
60
+ /**
61
+ * What `fx --refresh` says of a rate it read again and found still stale with
62
+ * no reason given (`ZAR/USD: --refresh found no newer rate than Tue 27 Oct
63
+ * 14:00 UTC.`): the refresh did run, and the data service has nothing newer.
64
+ * A rate whose refresh failed says that instead, and one with a stated reason
65
+ * says the reason (fxRateNotes).
66
+ */
67
+ export function refreshedFxRateNotes(legs: ReadonlyMap<string, FxRateTiming>, now = Date.now()): string[] {
68
+ return [...legs].flatMap(([currency, timing]) => {
69
+ if (currency === "USD" || timing.error != null || timing.staleReason || timing.staleAt == null || timing.staleAt > now) return [];
70
+ const since = timing.asOf != null && Number.isFinite(timing.asOf)
71
+ ? ` than ${formatUtcDayTime({ time: timing.asOf, dateOnly: false }, now)} UTC` : "";
72
+ return [`${currency}/USD: --refresh found no newer rate${since}.`];
73
+ });
74
+ }
75
+
59
76
  function isoTime(time: number | undefined | null): string | undefined {
60
77
  return time != null && Number.isFinite(time) ? new Date(time).toISOString() : undefined;
61
78
  }
@@ -111,7 +111,9 @@ export function SeriesDetailChart({
111
111
  }) {
112
112
  const [userViewport, setUserViewport] = useState<{ start: Date; end: Date } | null>(null);
113
113
  const chartWidth = Math.max(24, width);
114
- const statRows = statGridRows(stats, width);
114
+ // A figure wraps only into rows the chart's least leaves, so the pane never spills.
115
+ const statMaxLines = height - MIN_CHART_ROWS - aboveChartRows - belowChartRows;
116
+ const statRows = statGridRows(stats, width, undefined, { maxLines: statMaxLines });
115
117
  const chartHeight = Math.max(MIN_CHART_ROWS, height - statRows - aboveChartRows - belowChartRows);
116
118
  const { unit, unitGroup, nativeFrequency } = axis;
117
119
 
@@ -139,7 +141,7 @@ export function SeriesDetailChart({
139
141
 
140
142
  return (
141
143
  <Box flexDirection="column" width={width}>
142
- <StatGrid items={stats} width={width} />
144
+ <StatGrid items={stats} width={width} maxLines={statMaxLines} />
143
145
  {drawable ? (
144
146
  <>
145
147
  {aboveChart?.(chartWidth)}
@@ -520,6 +520,11 @@ export function quoteBoardStatus(quotes: BoardQuoteMap): QuoteBoardStatus {
520
520
  return { loading, stale, unavailable, latestTs, savedAt: oldestSavedCopyAt(Array.from(quotes.values(), (state) => state.quote)) };
521
521
  }
522
522
 
523
+ /** The quotes a board shows, for the footer's delayed clock (`useQuoteDelayFooter`). */
524
+ export function* boardQuotes(quotes: BoardQuoteMap): Generator<Quote | null> {
525
+ for (const state of quotes.values()) yield state.quote;
526
+ }
527
+
523
528
  /** Board footer status: everything here changes as loads succeed or fail. */
524
529
  export function quoteBoardFooterInfo(status: QuoteBoardStatus): PaneFooterSegment[] {
525
530
  const info: PaneFooterSegment[] = [];
@@ -1,5 +1,6 @@
1
1
  import { useCallback, useMemo } from "react";
2
2
  import { DataTableStackView, EmptyState, PaneStatusBody, usePaneNoticeFooter, usePaneStatusLinkFooter, type DataTableCell } from "../../../components";
3
+ import { ageFooterSegment } from "../../../components/layout/pane/footer";
3
4
  import { DetailReader } from "../../../components/reader/detail-reader";
4
5
  import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
5
6
  import { useAsyncResource, useAutoRefresh, usePluginPaneState, useUpdatedAgo } from "../../../public/react";
@@ -40,7 +41,7 @@ export function ShortSidePublicationPane({ kind, width, height, focused }: Pick<
40
41
  notices: [...(data?.warnings ?? []), ...(resource.data?.refreshError ? [resource.data.refreshError] : [])] });
41
42
  usePaneStatusLinkFooter({ registrationId: kind, focused, loading: resource.loading, error: resource.error,
42
43
  url: selected?.sourceUrl ?? data?.source.url ?? null, showOpenHint: true,
43
- info: data && updatedAgo ? [{ id: "updated", parts: [{ text: updatedAgo, tone: "muted" as const }] }] : [],
44
+ info: data && updatedAgo ? [ageFooterSegment(updatedAgo)] : [],
44
45
  stale: !!data && resource.data?.stale,
45
46
  });
46
47
  if (!symbol) return <EmptyState title="No ticker selected." message={`Select a US stock to view ${subject}.`} />;
@@ -3,6 +3,7 @@ import { listingIdentity } from "../shared/ticker-request";
3
3
  import { Box, ScrollBox, useUiCapabilities } from "../../../ui";
4
4
  import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginPaneState, useUpdatedAgo } from "../../../public/react";
5
5
  import { ChartTableHeader, DataTableStackView, EmptyState, formatPercentAxis, KeyValueRow, PaneStatusBody, useChartTableSelection, usePaneNoticeFooter, usePaneStatusLinkFooter, type DataTableCell, type StatItem } from "../../../components";
6
+ import { ageFooterSegment } from "../../../components/layout/pane/footer";
6
7
  import { colors } from "../../../theme/colors";
7
8
  import type { ShortVolumeObservation } from "../../../api-client/short-volume";
8
9
  import { isAccessDenied } from "../../../api-client/errors";
@@ -83,7 +84,7 @@ export function ShortVolumePane({ width, height, focused }: Pick<PaneProps, "wid
83
84
  notices: [...(data?.warnings ?? []), ...(resource.data?.refreshError ? [resource.data.refreshError] : [])] });
84
85
  usePaneStatusLinkFooter({ registrationId: "short-volume", focused, loading: resource.loading, error: resource.error,
85
86
  url: selected?.sourceUrl ?? data?.source.url ?? null, showOpenHint: true,
86
- info: data && updatedAgo ? [{ id: "updated", parts: [{ text: updatedAgo, tone: "muted" as const }] }] : [],
87
+ info: data && updatedAgo ? [ageFooterSegment(updatedAgo)] : [],
87
88
  stale: !!data && resource.data?.stale,
88
89
  });
89
90
  if (!data && isCloudSessionRequired(resource.error)) return <SignInWall placement="short-volume-signin" action="view daily short volume" needsVerification={session.needsVerification} />;
@@ -4,7 +4,6 @@ import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
4
4
  import { socialMentionPostsCache, socialMentionsCache } from "./client";
5
5
  import { socialMentionsHeadless } from "./headless";
6
6
  import { socialMentionsLockNote } from "./lock-note";
7
- import { SocialMentionsPane } from "./pane";
8
7
 
9
8
  const socialMentionsSettings = [
10
9
  { key: "socialRange", label: "History", type: "select" as const,
@@ -12,7 +11,7 @@ const socialMentionsSettings = [
12
11
  ];
13
12
  export const socialMentionsModule: PluginModule = {
14
13
  series: SOCIAL_MENTION_SERIES,
15
- panes: [{ id: "social-mentions", reportLockNote: socialMentionsLockNote, name: "Social Mentions", icon: "B", component: SocialMentionsPane,
14
+ panes: [{ id: "social-mentions", reportLockNote: socialMentionsLockNote, name: "Social Mentions", icon: "B", get component(): typeof import("./pane").SocialMentionsPane { const { SocialMentionsPane } = require("./pane"); return SocialMentionsPane; },
16
15
  defaultPosition: "right", tickerFollower: true, defaultMode: "floating", defaultFloatingSize: { width: 96, height: 30 },
17
16
  tableExport: true, headless: socialMentionsHeadless, settings: { title: "Social Mentions", fields: socialMentionsSettings } }],
18
17
  paneTemplates: [{ ...createTickerSurfacePaneTemplate({ id: "social-mentions-pane", paneId: "social-mentions", label: "Social Mentions",
@@ -3,6 +3,7 @@ import { listingIdentity } from "../shared/ticker-request";
3
3
  import { Box, ScrollBox, Text, useUiCapabilities } from "../../../ui";
4
4
  import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginPaneState, useShortcut, useUpdatedAgo } from "../../../public/react";
5
5
  import { CompositeChart, DataTableStackView, EmptyState, PaneStatusBody, usePaneNoticeFooter, usePaneStatusLinkFooter, type DataTableCell, StatGrid } from "../../../components";
6
+ import { ageFooterSegment } from "../../../components/layout/pane/footer";
6
7
  import { colors } from "../../../theme/colors";
7
8
  import type { SocialMentionPost, SocialMentionsRange } from "../../../api-client/social-mentions";
8
9
  import { staticSeries } from "../../../components/chart/static/series";
@@ -117,7 +118,7 @@ export function SocialMentionsPane({ width, height, focused }: Pick<PaneProps, "
117
118
  usePaneStatusLinkFooter({ registrationId: "social-mentions", focused, loading: resource.loading || pending, error: signInWall ? null : resource.error,
118
119
  url: selected?.topPost?.url ?? (symbol ? `https://x.com/search?q=%24${symbol}` : null), showOpenHint: true,
119
120
  info: data ? [
120
- ...(updatedAgo ? [{ id: "updated", parts: [{ text: updatedAgo, tone: "muted" as const }] }] : []),
121
+ ...(updatedAgo ? [ageFooterSegment(updatedAgo)] : []),
121
122
  ...(resource.data?.stale ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []),
122
123
  ] : [],
123
124
  });
@@ -1,7 +1,7 @@
1
1
  import { apiClient } from "../../../api-client";
2
2
  import type { SupplyChainPayload, SupplyEntity, SupplyEvidenceItem, SupplyOptions, SupplyRow } from "../../../api-client/supply-chain";
3
3
  import { createPluginCache } from "../../../data/plugin-cache";
4
- import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
4
+ import { batchReplyItems, cachedCloudResource, createCloudBatchAhead, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
5
5
  import { activeRelationship, canShowQuote, isUnconfirmed, publicEvidence, supplyOptions, TIER_OPTIONS, trustTier } from "./trust";
6
6
 
7
7
  export const SUPPLY_UNAVAILABLE = "Supply chain disclosures are not available yet.";
@@ -111,3 +111,24 @@ export async function fetchSupplyChain(symbol: string, client: Pick<typeof apiCl
111
111
  const cacheKey = (symbol: string, access: string, options: SupplyOptions) => `${symbol.toUpperCase()}:${access}:${JSON.stringify(supplyOptions(options.tiers))}:${!!options.includeLeads}`;
112
112
  export const cachedSupplyChain = (symbol: string, access: string, options: SupplyOptions = {}) => cachedCloudResource(supplyChainCache, cacheKey(symbol, access, options), validateSupplyChain);
113
113
  export const loadSupplyChain = (symbol: string, access: string, force = false, options: SupplyOptions = {}) => loadCloudResource(supplyChainCache, cacheKey(symbol, access, options), () => fetchSupplyChain(symbol, apiClient, options), { force, validate: validateSupplyChain });
114
+ /** Symbols per disclosures batch request: a ripple's sixty holdings are two, read sixteen at a time on the server. */
115
+ const SUPPLY_BATCH_MAX = 30;
116
+ const supplyChainsAheadByOptions = new Map<string, ReturnType<typeof createCloudBatchAhead<SupplyChainPayload>>>();
117
+ /**
118
+ * Reads many symbols' disclosures ahead in a request per 30 (EARN ripple's
119
+ * holdings), into the cache loadSupplyChain reads. Answers the symbols stored.
120
+ */
121
+ export function loadSupplyChainsAhead(symbols: readonly string[], access: string, force = false, options: SupplyOptions = {}): Promise<Set<string>> {
122
+ const optionsKey = JSON.stringify([supplyOptions(options.tiers), !!options.includeLeads]);
123
+ let ahead = supplyChainsAheadByOptions.get(optionsKey);
124
+ if (!ahead) {
125
+ ahead = createCloudBatchAhead(supplyChainCache, {
126
+ max: SUPPLY_BATCH_MAX,
127
+ validate: validateSupplyChain,
128
+ read: async (ids) => batchReplyItems(await apiClient.getCloudSupplyChains(ids, options))
129
+ .flatMap((item) => item.chain ? [{ id: item.symbol.toUpperCase(), payload: item.chain }] : []),
130
+ });
131
+ supplyChainsAheadByOptions.set(optionsKey, ahead);
132
+ }
133
+ return ahead(symbols.map((symbol) => ({ id: symbol.toUpperCase(), key: cacheKey(symbol, access, options) })), force);
134
+ }
@@ -2,7 +2,7 @@ import { apiClient } from "../../../api-client";
2
2
  import { DEFAULT_GRAPH_OPTIONS, supplyGraphQuery, type GraphOptions, type GraphPayload, type GraphPath } from "../../../api-client/supply-chain-graph";
3
3
  import { createPluginCache } from "../../../data/plugin-cache";
4
4
  import { dateOptionError, readCalendarDate } from "../../../utils/calendar-date";
5
- import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
5
+ import { batchReplyItems, cachedCloudResource, createCloudBatchAhead, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
6
6
  import { normalizeSupplyRow, validPeriodMonths, validSupplyEntity, validSupplyRow } from "./client";
7
7
  import { graphEvidenceRow } from "./graph-evidence";
8
8
  import { activeRelationship, isUnconfirmed } from "./trust";
@@ -84,3 +84,24 @@ export function graphOptions(input: Record<string, unknown>): GraphOptions {
84
84
  || options.tiers.some(value => !["structured", "primary", "secondary", "imported"].includes(value))) throw new Error("Invalid supply chain graph filters");
85
85
  return options;
86
86
  }
87
+ /** Symbols per graphs batch request: a ripple's sixty holdings are two, read sixteen at a time on the server. */
88
+ const GRAPH_BATCH_MAX = 30;
89
+ const graphsAheadByOptions = new Map<string, ReturnType<typeof createCloudBatchAhead<GraphPayload>>>();
90
+ /**
91
+ * Reads many symbols' graphs on one query ahead in a request per 30 (EARN
92
+ * ripple's two hops), into the cache loadGraph reads. Answers the symbols stored.
93
+ */
94
+ export function loadGraphsAhead(symbols: readonly string[], options: GraphOptions, access: string, force = false): Promise<Set<string>> {
95
+ const query = supplyGraphQuery(options);
96
+ let ahead = graphsAheadByOptions.get(query);
97
+ if (!ahead) {
98
+ ahead = createCloudBatchAhead(graphCache, {
99
+ max: GRAPH_BATCH_MAX,
100
+ validate: validateGraph,
101
+ read: async (ids) => batchReplyItems(await apiClient.getCloudSupplyGraphs(ids, options))
102
+ .flatMap((item) => item.graph ? [{ id: item.symbol.toUpperCase(), payload: item.graph }] : []),
103
+ });
104
+ graphsAheadByOptions.set(query, ahead);
105
+ }
106
+ return ahead(symbols.map((symbol) => ({ id: symbol.toUpperCase(), key: key(symbol, "", options, access) })), force);
107
+ }
@@ -3,7 +3,6 @@ import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
3
3
  import { graphCache } from "./graph-client";
4
4
  import { supplyChainCache } from "./client";
5
5
  import { supplyChainHeadless } from "./headless";
6
- import { SupplyChainPane } from "./pane";
7
6
 
8
7
  /** What another pane can open SPLC on: a Graph or Path view, its target and its query, as `graphOptions` reads them. */
9
8
  const OPENING_SETTINGS = ["tab", "to", "depth", "direction", "roles", "tiers"];
@@ -13,7 +12,7 @@ const openingSettings = (values: Record<string, string> = {}) =>
13
12
  export const supplyChainModule: PluginModule = {
14
13
  setup(ctx) { supplyChainCache.attach(ctx.persistence); graphCache.attach(ctx.persistence); },
15
14
  dispose() { supplyChainCache.reset(); graphCache.reset(); },
16
- panes: [{ id: "supply-chain", name: "Supply Chain", icon: "S", component: SupplyChainPane,
15
+ panes: [{ id: "supply-chain", name: "Supply Chain", icon: "S", get component(): typeof import("./pane").SupplyChainPane { const { SupplyChainPane } = require("./pane"); return SupplyChainPane; },
17
16
  defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 130, height: 30 },
18
17
  tickerFollower: true, instruments: ["equity"], tableExport: true, headless: supplyChainHeadless }],
19
18
  paneTemplates: [{ ...createTickerSurfacePaneTemplate({ id: "supply-chain-pane", paneId: "supply-chain", label: "Supply Chain",
@@ -12,15 +12,23 @@ import type { PluginPersistence } from "../../../types/plugin";
12
12
  import { apiClient } from "../../../api-client";
13
13
  import { ApiRequestError, isPermanentClientError, isTransientFailure } from "../../../api-client/errors";
14
14
  import { httpFetch } from "../../../utils/http-transport";
15
+ import { isNetworkFailure } from "../../../utils/network-failure";
15
16
  import { asRecord, finiteOrNull } from "../../../utils/guards";
16
17
  import { logFailure } from "../../../utils/log-failure";
17
18
 
18
19
  const FORMS_13F_BASE_URL = "https://forms13f.com/api/v1";
19
20
  const FORM_PAGE_LIMIT = 100;
20
- // Large funds file well past 2,000 positions, and a silently clipped
21
- // list makes every weight, total, and buy/sell action wrong. Paging further is
22
- // slower but truthful; page in parallel if the wait becomes the problem.
23
- const MAX_FORM_ROWS = 20_000;
21
+ /** The most rows the 13F source gives in one page. */
22
+ const FILING_PAGE_ROWS = 250;
23
+ /** Pages of one filing read at once where the service cannot send it whole. */
24
+ const FILING_PAGE_CONCURRENCY = 4;
25
+ // Large funds file tens of thousands of positions (BlackRock about 50,000), and
26
+ // a silently clipped list makes every weight, total, and buy/sell action wrong.
27
+ const MAX_FORM_ROWS = 100_000;
28
+ /** Rows asked first of a large filing the service is still reading. */
29
+ const FIRST_ROWS = 2_000;
30
+ /** How long a large filing is waited for whole before its first rows are asked. */
31
+ const FIRST_ROWS_AFTER_MS = 300;
24
32
  const CACHE_KIND = "forms13f-api";
25
33
  const CACHE_SOURCE = "forms13f";
26
34
  const CACHE_SCHEMA_VERSION = 1;
@@ -52,6 +60,8 @@ let forms13FPersistence: PluginPersistence | null = null;
52
60
  const failedRefreshes = new Set<string>();
53
61
  // The promise is the generation. A newer download replaces it, and the replaced one must not write.
54
62
  const activeRequests = new Map<string, Promise<unknown>>();
63
+ /** The service answered a whole filing with 404: it is an older one, so filings are read a page at a time. */
64
+ let wholeFilingsMissing = false;
55
65
 
56
66
  export function attachThirteenFApiPersistence(persistence: PluginPersistence) {
57
67
  if (forms13FPersistence !== persistence) resetThirteenFApiPersistence();
@@ -62,6 +72,7 @@ export function resetThirteenFApiPersistence() {
62
72
  forms13FPersistence = null;
63
73
  failedRefreshes.clear();
64
74
  activeRequests.clear();
75
+ wholeFilingsMissing = false;
65
76
  }
66
77
 
67
78
  function padCik(value: string): string {
@@ -122,7 +133,15 @@ async function fetchForms13F<T>(
122
133
 
123
134
  if (!options.forceRefresh) {
124
135
  const pending = activeRequests.get(key) as Promise<T> | undefined;
125
- if (pending) return cached != null ? cached.value : pending;
136
+ if (pending) {
137
+ if (cached != null) return cached.value;
138
+ // Another reader's request, which that reader may stop (a fund read ahead
139
+ // while the cursor rested on it): this reader then asks again on its own.
140
+ return pending.catch((error: unknown) => {
141
+ if (options.signal?.aborted || !(error instanceof Error && error.name === "AbortError")) throw error;
142
+ return fetchForms13F<T>(path, params, options);
143
+ });
144
+ }
126
145
  }
127
146
 
128
147
  const slot: { promise?: Promise<T> } = {};
@@ -343,24 +362,108 @@ export async function listThirteenFFormHoldingsPage(
343
362
  return { rows, hasMore: rows.length >= limit && offset + rows.length < MAX_FORM_ROWS };
344
363
  }
345
364
 
365
+ function mapHoldings(raw: unknown): ThirteenFHoldingRecord[] {
366
+ return arrayResponse(raw).map(mapHolding).filter((holding): holding is ThirteenFHoldingRecord => !!holding);
367
+ }
368
+
369
+ /** A filing's whole information table as Gloom Cloud sends it in one answer. */
370
+ function filingRows(raw: unknown): ThirteenFHoldingRecord[] {
371
+ const rows = asRecord(raw).rows;
372
+ if (!Array.isArray(rows)) throw new Error("13F filing answer has no rows");
373
+ return mapHoldings(rows);
374
+ }
375
+
376
+ export interface FilingHoldingsOptions extends Forms13FReadOptions {
377
+ /** The rows the filing's summary counts, when known: a large filing's first rows come first. */
378
+ entries?: number | null;
379
+ /** The filing's first rows, while a large filing the service has not read yet is still loading. */
380
+ onFirstRows?: (rows: ThirteenFHoldingRecord[]) => void;
381
+ }
382
+
383
+ /**
384
+ * A filing's whole information table: one request to Gloom Cloud, which
385
+ * reads the source's pages itself and keeps the filing. An older service
386
+ * without that answer (a 404, remembered for the session) or one that cannot
387
+ * be reached is read a page at a time, a few pages at once, as before; a page
388
+ * Gloom Cloud cannot send comes from the public source. A busy or failing
389
+ * service (429, 5xx) is the error: paging it would only ask it more.
390
+ */
346
391
  export async function listThirteenFFormHoldings(
347
392
  cik: string,
348
393
  accessionNumber: string,
349
394
  signal?: AbortSignal,
350
- options: Forms13FReadOptions = {},
395
+ options: FilingHoldingsOptions = {},
396
+ ): Promise<ThirteenFHoldingRecord[]> {
397
+ const { entries, onFirstRows, ...readOptions } = options;
398
+ if (wholeFilingsMissing) return listPagedHoldings(cik, accessionNumber, signal, readOptions);
399
+ // The count lets a filing the service has not stored yet be read in fewer requests.
400
+ const params = {
401
+ cik: normalizeCik(cik),
402
+ accession_number: accessionNumber,
403
+ ...(entries != null && entries > 0 ? { entries } : {}),
404
+ };
405
+ const whole = fetchForms13F<unknown>("/holdings", params, { ...readOptions, signal, cloudOnly: true });
406
+ let answered = false;
407
+ let timer: ReturnType<typeof setTimeout> | undefined;
408
+ if (onFirstRows && entries != null && entries > FIRST_ROWS) {
409
+ timer = setTimeout(() => {
410
+ if (answered) return;
411
+ void fetchForms13F<unknown>("/holdings", { ...params, limit: FIRST_ROWS }, { signal, cloudOnly: true, cache: false })
412
+ .then((raw) => { if (!answered) onFirstRows(filingRows(raw)); })
413
+ .catch(() => {
414
+ // Intentional ignore: the first rows only come ahead of the whole filing, which reports its own failure.
415
+ });
416
+ }, FIRST_ROWS_AFTER_MS);
417
+ }
418
+ try {
419
+ return filingRows(await whole);
420
+ } catch (error) {
421
+ signal?.throwIfAborted();
422
+ const missing = error instanceof ApiRequestError && error.status === 404;
423
+ if (!missing && !isNetworkFailure(error)) throw error;
424
+ if (missing) wholeFilingsMissing = true;
425
+ return listPagedHoldings(cik, accessionNumber, signal, readOptions);
426
+ } finally {
427
+ answered = true;
428
+ clearTimeout(timer);
429
+ }
430
+ }
431
+
432
+ /** A filing read a page at a time, a few pages at once, until a page comes back short. */
433
+ async function listPagedHoldings(
434
+ cik: string,
435
+ accessionNumber: string,
436
+ signal: AbortSignal | undefined,
437
+ options: Forms13FReadOptions,
351
438
  ): Promise<ThirteenFHoldingRecord[]> {
352
- const rows: ThirteenFHoldingRecord[] = [];
353
- for (let offset = 0; offset < MAX_FORM_ROWS; offset += FORM_PAGE_LIMIT) {
439
+ const pages: ThirteenFHoldingRecord[][] = [];
440
+ const maxPages = Math.ceil(MAX_FORM_ROWS / FILING_PAGE_ROWS);
441
+ let next = 0;
442
+ let last: number | null = null;
443
+ let failed = false;
444
+ async function readPage() {
354
445
  signal?.throwIfAborted();
446
+ const index = next++;
355
447
  const page = await listThirteenFFormHoldingsPage(cik, accessionNumber, signal, {
356
448
  ...options,
357
- offset,
358
- limit: FORM_PAGE_LIMIT,
449
+ offset: index * FILING_PAGE_ROWS,
450
+ limit: FILING_PAGE_ROWS,
359
451
  });
360
- rows.push(...page.rows);
361
- if (!page.hasMore) break;
452
+ pages[index] = page.rows;
453
+ if (page.rows.length < FILING_PAGE_ROWS && (last === null || index < last)) last = index;
454
+ }
455
+ async function worker() {
456
+ try {
457
+ while (!failed && next < maxPages && (last === null || next <= last)) await readPage();
458
+ } catch (error) {
459
+ failed = true;
460
+ throw error;
461
+ }
362
462
  }
363
- return rows;
463
+ // The first page alone says whether there is more: most filings fit in it.
464
+ await readPage();
465
+ await Promise.all(Array.from({ length: FILING_PAGE_CONCURRENCY }, worker));
466
+ return pages.slice(0, (last ?? maxPages - 1) + 1).flat();
364
467
  }
365
468
 
366
469
  export async function lookupThirteenFTickers(