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
@@ -15,6 +15,7 @@ import { isoDateDaysAgo, isoDateToday, isoDateYearsAgo } from "../../../utils/ca
15
15
  import { abortable, withDeadline } from "../../../utils/async-deadline";
16
16
  import { logFailure } from "../../../utils/log-failure";
17
17
  import {
18
+ amendmentKind,
18
19
  buildBrowserRows,
19
20
  filingGapFor,
20
21
  estimateFilingQuarterReturn,
@@ -33,6 +34,7 @@ import type {
33
34
  FundBrowserRow,
34
35
  FundDetailData,
35
36
  ThirteenFBrowserTab,
37
+ ThirteenFDetailLoading,
36
38
  ThirteenFFilingGap,
37
39
  ThirteenFFund,
38
40
  ThirteenFHoldingRecord,
@@ -162,9 +164,12 @@ async function loadBrowserPage(
162
164
  const pageApiOptions = { ...apiOptions, offset };
163
165
 
164
166
  if (tab === "performance") {
165
- const topFunds = await listTopThirteenFFunds(quarter, browserLimit, signal, pageApiOptions);
166
167
  const quarters = priorQuarters(quarter);
167
- const history = await loadRankingHistory(quarters, signal, apiOptions);
168
+ // The prior quarters' rankings do not depend on this page, so they are read beside it.
169
+ const [topFunds, history] = await Promise.all([
170
+ listTopThirteenFFunds(quarter, browserLimit, signal, pageApiOptions),
171
+ loadRankingHistory(quarters, signal, apiOptions),
172
+ ]);
168
173
  const funds: ThirteenFFund[] = topFunds.map((fund) => ({ cik: fund.cik, name: fund.name }));
169
174
  const rows = buildBrowserRows({ topFunds, source: "performance" }).map(row => ({ ...row, priorReturns: quarters.map((previous, index) => ({ quarter: previous, value: history[index]?.get(row.cik) ?? null })) }));
170
175
  const result: BrowserLoadResult = {
@@ -183,7 +188,10 @@ async function loadBrowserPage(
183
188
 
184
189
  if (tab === "latest") {
185
190
  const filings = await listThirteenFFilings(isoDateDaysAgo(21, now), to, latestLimit, signal, pageApiOptions);
186
- const selected = dedupeLatestForms(filings);
191
+ // An original filing or a restatement is its period's whole report, and an
192
+ // amendment of unknown kind shows no totals either way: only one that adds
193
+ // NEW HOLDINGS needs the fund's earlier filing for its quarter's totals.
194
+ const selected = dedupeLatestForms(filings).filter((form) => amendmentKind(form) === "new-holdings");
187
195
  const rows = buildBrowserRows({ latestFilings: filings, source: "latest" });
188
196
  const result: BrowserLoadResult = {
189
197
  rows,
@@ -516,12 +524,66 @@ export async function loadFundPerformance(
516
524
  };
517
525
  }
518
526
 
527
+ /** Rows a fund's two newest reports may hold together for their holdings to be read ahead. */
528
+ const PREFETCH_HOLDINGS_MAX = 2_000;
529
+
530
+ /**
531
+ * Reads ahead what opening a fund asks first, while the cursor rests on its
532
+ * row: its filing list, which every other request waits on, and the holdings
533
+ * of its two newest reports when they hold PREFETCH_HOLDINGS_MAX rows or fewer
534
+ * together. A large filer's holdings wait for Enter. These are loadFundDetail's
535
+ * own requests, so opening the fund reads them from cache or joins them.
536
+ */
537
+ export async function prefetchFundDetail(cik: string, signal: AbortSignal): Promise<void> {
538
+ const now = new Date();
539
+ const forms = await listThirteenFForms(cik, isoDateYearsAgo(4, now), isoDateToday(now), 100, signal);
540
+ const filings = buildPeriodReports(forms).slice(0, 2).flatMap((report) => report.filings);
541
+ const rows = filings.reduce((sum, form) => sum + (form.tableEntryTotal ?? Infinity), 0);
542
+ if (!filings.length || rows > PREFETCH_HOLDINGS_MAX) return;
543
+ await Promise.all(filings.map((form) => listThirteenFFormHoldings(cik, form.accessionNumber, signal, { entries: form.tableEntryTotal })));
544
+ }
545
+
546
+ /**
547
+ * One fund read ahead at a time (prefetchFundDetail): resting on another row
548
+ * stops the last one's requests, and `cancel` stops it when the list goes.
549
+ */
550
+ export function createFundPrefetcher(prefetch: (cik: string, signal: AbortSignal) => Promise<void> = prefetchFundDetail) {
551
+ let current: { cik: string; controller: AbortController } | null = null;
552
+ return {
553
+ prefetch(cik: string) {
554
+ if (current?.cik === cik) return;
555
+ current?.controller.abort();
556
+ const entry = { cik, controller: new AbortController() };
557
+ current = entry;
558
+ void prefetch(cik, entry.controller.signal).catch(() => {
559
+ // Intentional ignore: a fund read ahead that fails is read again, and the failure shown, when it opens.
560
+ }).finally(() => {
561
+ if (current === entry) current = null;
562
+ });
563
+ },
564
+ cancel() {
565
+ current?.controller.abort();
566
+ current = null;
567
+ },
568
+ };
569
+ }
570
+
571
+ /**
572
+ * A fund's latest report and the one before it, with their holdings. With
573
+ * `onProgress`, the fund as it fills in: the latest report's holdings as
574
+ * soon as they are read (a large filing's first rows before the rest), then
575
+ * the prior quarter they are compared with and EDGAR's filing list, each
576
+ * part still loading named in `loading`. The answer is the whole fund.
577
+ */
519
578
  export async function loadFundDetail(
520
579
  cik: string,
521
580
  fallbackName: string,
522
581
  signal?: AbortSignal,
523
582
  /** `filingGap: false` skips EDGAR's filing list, for a load that never shows it (an overlap). */
524
- options: Pick<Forms13FReadOptions, "forceRefresh" | "onSavedCopy"> & { filingGap?: boolean } = {},
583
+ options: Pick<Forms13FReadOptions, "forceRefresh" | "onSavedCopy"> & {
584
+ filingGap?: boolean;
585
+ onProgress?: (detail: FundDetailData) => void;
586
+ } = {},
525
587
  ): Promise<FundDetailData> {
526
588
  const now = new Date();
527
589
  const warnings: string[] = [];
@@ -541,47 +603,87 @@ export async function loadFundDetail(
541
603
  const previousReport = reports[1];
542
604
  const latestForm = latestReport?.filings.at(-1) ?? null;
543
605
  const previousForm = previousReport?.filings.at(-1) ?? null;
544
- async function loadReport(report: ThirteenFPeriodReport | undefined): Promise<ThirteenFHoldingRecord[]> {
606
+ const checkGap = options.filingGap !== false;
607
+ const parts: Pick<FundDetailData, "latestHoldings" | "previousHoldings" | "filingGap"> & { loading: ThirteenFDetailLoading } = {
608
+ latestHoldings: [],
609
+ previousHoldings: [],
610
+ filingGap: null,
611
+ loading: { positions: true, ...(previousReport ? { comparison: true } : {}), ...(checkGap ? { filingGap: true } : {}) },
612
+ };
613
+ const detail = (): FundDetailData => {
614
+ const loading = Object.fromEntries(Object.entries(parts.loading).filter(([, value]) => value)) as ThirteenFDetailLoading;
615
+ return {
616
+ cik: normalizeCik(cik),
617
+ name: latestForm?.companyName || fallbackName,
618
+ forms,
619
+ latestForm,
620
+ previousForm,
621
+ latestHoldings: parts.latestHoldings,
622
+ previousHoldings: parts.previousHoldings,
623
+ latestReport,
624
+ previousReport,
625
+ warnings: [...warnings],
626
+ filingGap: parts.filingGap,
627
+ ...(saved.savedAt != null ? { savedAt: saved.savedAt } : {}),
628
+ ...(Object.keys(loading).length ? { loading } : {}),
629
+ };
630
+ };
631
+ let failed = false;
632
+ // The fund is shown once its latest holdings, or a large filing's first rows, are in.
633
+ const progress = () => {
634
+ if (!options.onProgress || failed || signal?.aborted || (parts.loading.positions && !parts.latestHoldings.length)) return;
635
+ options.onProgress(detail());
636
+ };
637
+ async function loadReport(report: ThirteenFPeriodReport | undefined, onFirstRows?: (rows: ThirteenFHoldingRecord[]) => void): Promise<ThirteenFHoldingRecord[]> {
545
638
  if (!report) return [];
546
- const holdings: ThirteenFHoldingRecord[] = [];
547
- for (const form of report.filings) {
548
- const rows = await listThirteenFFormHoldings(cik, form.accessionNumber, signal, apiOptions);
549
- holdings.push(...rows);
639
+ // An amended period's filings are read at once: the original and each of its additions.
640
+ const filings = await Promise.all(report.filings.map((form, index) => listThirteenFFormHoldings(cik, form.accessionNumber, signal, {
641
+ ...apiOptions,
642
+ entries: form.tableEntryTotal,
643
+ ...(index === 0 && onFirstRows ? { onFirstRows } : {}),
644
+ })));
645
+ report.filings.forEach((form, index) => {
646
+ const rows = filings[index]!;
550
647
  if (form.tableEntryTotal != null && rows.length !== form.tableEntryTotal) {
551
648
  report.complete = false;
552
649
  warnings.push(`${report.periodOfReport}: loaded ${rows.length} of ${form.tableEntryTotal} disclosed entries for ${form.accessionNumber}.`);
553
650
  }
554
- }
651
+ });
555
652
  if (!report.complete && !warnings.some((warning) => warning.startsWith(report.periodOfReport))) {
556
653
  warnings.push(`${report.periodOfReport}: amendment type or original report unavailable; disclosed entries cannot be reconciled into a full report.`);
557
654
  }
558
- return holdings;
655
+ return filings.flat();
559
656
  }
560
- const [latestHoldings, previousHoldings, filingGap] = await Promise.all([
561
- loadReport(latestReport),
657
+ await Promise.all([
658
+ loadReport(latestReport, options.onProgress ? (rows) => {
659
+ if (!parts.loading.positions) return;
660
+ parts.latestHoldings = rows;
661
+ progress();
662
+ } : undefined).then((rows) => {
663
+ parts.latestHoldings = rows;
664
+ parts.loading.positions = false;
665
+ progress();
666
+ }),
562
667
  loadReport(previousReport).catch((error) => {
563
668
  if (signal?.aborted) throw error;
564
669
  if (previousReport) previousReport.complete = false;
565
670
  warnings.push(`${previousReport?.periodOfReport ?? "Prior quarter"}: holdings unavailable; comparison unavailable.`);
566
671
  return [];
672
+ }).then((rows) => {
673
+ parts.previousHoldings = rows;
674
+ parts.loading.comparison = false;
675
+ progress();
567
676
  }),
568
- options.filingGap === false ? null
569
- : loadFilingGap(cik, latestForm?.periodOfReport, signal, now, { forceRefresh: options.forceRefresh }),
570
- ]);
571
- return {
572
- cik: normalizeCik(cik),
573
- name: latestForm?.companyName || fallbackName,
574
- forms,
575
- latestForm,
576
- previousForm,
577
- latestHoldings,
578
- previousHoldings,
579
- latestReport,
580
- previousReport,
581
- warnings,
582
- filingGap,
583
- ...(saved.savedAt != null ? { savedAt: saved.savedAt } : {}),
584
- };
677
+ (checkGap ? loadFilingGap(cik, latestForm?.periodOfReport, signal, now, { forceRefresh: options.forceRefresh }) : Promise.resolve(null)).then((gap) => {
678
+ parts.filingGap = gap;
679
+ parts.loading.filingGap = false;
680
+ progress();
681
+ }),
682
+ ]).catch((error: unknown) => {
683
+ failed = true;
684
+ throw error;
685
+ });
686
+ return detail();
585
687
  }
586
688
 
587
689
  export async function loadFilingPositions(
@@ -6,13 +6,11 @@ import {
6
6
  inferBrowserTabFromQuery,
7
7
  isCikQuery,
8
8
  } from "./model";
9
- import { ThirteenFPane } from "./pane";
10
9
  import {
11
10
  attachThirteenFApiPersistence,
12
11
  resetThirteenFApiPersistence,
13
12
  } from "./api";
14
13
  import { thirteenFHeadless } from "./headless";
15
- import { ThirteenFTickerPane } from "./signals-pane";
16
14
  import { isKnownNonUsListing } from "../../../utils/sec";
17
15
  import { isKnownMutualFund } from "../../../tickers/instrument-kind";
18
16
 
@@ -32,7 +30,7 @@ export const thirteenFModule: PluginModule = {
32
30
  id: "thirteenf",
33
31
  name: "13F",
34
32
  order: 39,
35
- component: ThirteenFTickerPane,
33
+ get component(): typeof import("./signals-pane").ThirteenFTickerPane { const { ThirteenFTickerPane } = require("./signals-pane"); return ThirteenFTickerPane; },
36
34
  instruments: ["equity", "fund"],
37
35
  isVisible: ({ ticker, financials }) => !isKnownNonUsListing(ticker) && !isKnownMutualFund(ticker, financials),
38
36
  });
@@ -47,7 +45,7 @@ export const thirteenFModule: PluginModule = {
47
45
  id: THIRTEENF_PANE_ID,
48
46
  name: "13F Funds",
49
47
  icon: "13F",
50
- component: ThirteenFPane,
48
+ get component(): typeof import("./pane").ThirteenFPane { const { ThirteenFPane } = require("./pane"); return ThirteenFPane; },
51
49
  defaultPosition: "right",
52
50
  defaultMode: "floating",
53
51
  defaultFloatingSize: { width: 112, height: 36 },
@@ -547,6 +547,8 @@ export function estimateFilingQuarterReturn(data: FundDetailData, splits: SplitF
547
547
 
548
548
  export function hasComparable13FQuarter(data: FundDetailData): boolean {
549
549
  if (data.latestReport?.complete === false || data.previousReport?.complete === false) return false;
550
+ // Until both quarters are in whole, a line one of them lacks is not a buy or a sale.
551
+ if (data.loading?.positions || data.loading?.comparison) return false;
550
552
  const current = data.latestForm?.periodOfReport;
551
553
  const previous = data.previousForm?.periodOfReport;
552
554
  return adjacentQuarterPeriods(current, previous);
@@ -23,7 +23,7 @@ import { nextHeaderSort } from "../../../utils/sort-values";
23
23
  import { useAssetData, usePluginPaneState, usePluginTickerActions } from "../../runtime";
24
24
  import { useMineTickers } from "../shared/mine-tickers";
25
25
  import { usePaneStatusFooter, usePaneStatusLinkFooter } from "../../../components/layout/pane/status-footer";
26
- import { corporateSplitFactor, loadBrowserRows, loadFilingPositions, loadFundDetail, loadSplitFactors } from "./data";
26
+ import { corporateSplitFactor, createFundPrefetcher, loadBrowserRows, loadFilingPositions, loadFundDetail, loadSplitFactors } from "./data";
27
27
  import { FundOverlapView } from "./overlap-pane";
28
28
  import { ThirteenFCrowdingPane, ThirteenFTickerHoldingsView } from "./signals-pane";
29
29
  import { PaneFooterScope, savedCopyFooterInfo } from "../../../components/layout/pane/footer";
@@ -196,13 +196,11 @@ function ThirteenFBrowserPane({ focused, width, height, onDetailChange }: PanePr
196
196
  blurSearch();
197
197
  setDetailSeed({ cik: row.cik, name: row.name });
198
198
  }, [blurSearch]);
199
- // Every request behind a fund detail is cached per path, so warming it
200
- // while the cursor rests on the row makes Enter read from cache.
201
- const prefetchDetail = useCallback((row: FundBrowserRow) => {
202
- void loadFundDetail(row.cik, row.name).catch(() => {
203
- // Intentional ignore: speculative prefetch is retried and failures are shown when the fund opens.
204
- });
205
- }, []);
199
+ // The fund's first requests are read ahead while the cursor rests on its
200
+ // row, one fund at a time, and stopped when the cursor moves on.
201
+ const prefetcher = useMemo(() => createFundPrefetcher(), []);
202
+ useEffect(() => () => prefetcher.cancel(), [prefetcher]);
203
+ const prefetchDetail = useCallback((row: FundBrowserRow) => prefetcher.prefetch(row.cik), [prefetcher]);
206
204
 
207
205
  const browserStatusInfo = useMemo<PaneFooterSegment[]>(() => [
208
206
  ...(loadingMore ? [{ id: "loading-more", parts: [{ text: "loading more", tone: "muted" as const }] }] : []),
@@ -372,7 +370,11 @@ export function FundDetailView({
372
370
  abortRef.current = controller;
373
371
  setStatus("loading");
374
372
  setError(null);
375
- void loadFundDetail(seed.cik, seed.name, controller.signal, { forceRefresh: refresh })
373
+ void loadFundDetail(seed.cik, seed.name, controller.signal, {
374
+ forceRefresh: refresh,
375
+ // A first load shows the fund as it fills in; a refresh keeps what is on screen until it answers whole.
376
+ ...(refresh ? {} : { onProgress: (partial: FundDetailData) => { if (abortRef.current === controller) setData(partial); } }),
377
+ })
376
378
  .then((nextData) => {
377
379
  if (abortRef.current !== controller) return;
378
380
  setData(nextData);
@@ -392,6 +394,8 @@ export function FundDetailView({
392
394
  .catch((loadError) => {
393
395
  if (abortRef.current !== controller) return;
394
396
  if (loadError instanceof Error && loadError.name === "AbortError") return;
397
+ // A first load that failed after its first rows shows the error, not a part of the filing as the fund.
398
+ setData((current) => current?.loading ? null : current);
395
399
  setError(loadError instanceof Error ? loadError.message : String(loadError));
396
400
  setStatus("error");
397
401
  });
@@ -522,7 +526,10 @@ export function FundDetailView({
522
526
  ...(staleFiling ? { tone: "warning" as const } : {}) },
523
527
  data && hasComparable13FQuarter(data)
524
528
  ? { id: "compared", label: "Compared with", value: data.previousForm!.periodOfReport }
525
- : { id: "compared", label: "Compared with", value: "No prior quarter", tone: "muted" },
529
+ // The quarter the changes will compare with, dim until both are in.
530
+ : data?.loading && hasComparable13FQuarter({ ...data, loading: undefined })
531
+ ? { id: "compared", label: "Compared with", value: data.previousForm!.periodOfReport, tone: "muted" }
532
+ : { id: "compared", label: "Compared with", value: "No prior quarter", tone: "muted" },
526
533
  ...(data?.latestReport && data.latestReport.filings.length > 1
527
534
  ? [{ id: "combined", label: "Combined", value: `${data.latestReport.filings.length} filings` }]
528
535
  : []),
@@ -593,7 +600,9 @@ export function FundDetailView({
593
600
  ]}
594
601
  meta={data?.name && data.name !== seed.name ? data.name : undefined}
595
602
  />
596
- {activeTab === "overlap" && data ? <FundOverlapView data={data} focused={focused} width={width} /> : activeTab === "filings" ? (
603
+ {activeTab === "overlap" && data ? data.loading?.positions || data.loading?.comparison ? (
604
+ <PaneStatusBody loading align="center" loadingLabel="Loading 13F filing..." />
605
+ ) : <FundOverlapView data={data} focused={focused} width={width} /> : activeTab === "filings" ? (
597
606
  <DataTableStackView<FundTimelineRow, FundTimelineColumn>
598
607
  focused={focused}
599
608
  detailOpen={!!openFiling}
@@ -101,6 +101,18 @@ export interface FundDetailData {
101
101
  filingGap?: ThirteenFFilingGap | null;
102
102
  /** A refresh failed and the oldest copy that answered instead was saved at this time (ms). */
103
103
  savedAt?: number;
104
+ /** The parts still loading, while the fund fills in (loadFundDetail's `onProgress`); absent once whole. */
105
+ loading?: ThirteenFDetailLoading;
106
+ }
107
+
108
+ /** Parts of a fund's detail still loading. */
109
+ export interface ThirteenFDetailLoading {
110
+ /** The latest report's holdings are a large filing's first rows. */
111
+ positions?: boolean;
112
+ /** The prior quarter's holdings, which the latest are compared with. */
113
+ comparison?: boolean;
114
+ /** EDGAR's filing list, for a fund behind on its filings. */
115
+ filingGap?: boolean;
104
116
  }
105
117
 
106
118
  /** A manager a 13F-NT names as reporting its holdings. */
@@ -131,7 +131,9 @@ function buildHistoricalPriceRows(points: PricePoint[], venue: string | undefine
131
131
  const sorted = points
132
132
  .flatMap((point, sourceIndex) => {
133
133
  const date = pricePointDate(point);
134
- return date ? [{ point: pricePointValues(point), date, sourceIndex, partial: point.partial === true, filled: point.filled === true }] : [];
134
+ return date ? [{
135
+ point: pricePointValues(point), date, sourceIndex, partial: point.partial === true, filled: point.filled === true, contract: point.contract,
136
+ }] : [];
135
137
  })
136
138
  .sort((left, right) => left.date.getTime() - right.date.getTime());
137
139
  const size = historyBarSize(sorted.map((entry) => entry.date.getTime()));
@@ -142,10 +144,21 @@ function buildHistoricalPriceRows(points: PricePoint[], venue: string | undefine
142
144
  const times = sorted.map((entry) => entry.date.getTime());
143
145
  const interval = size === "intraday" ? null : size;
144
146
  const afterGap = historyBarsAfterHole(times, interval);
145
- const gapNotices = [historyMissingPeriodsNote(times, interval), historyChangeBaseNote(times, interval)]
146
- .filter((notice): notice is string => notice != null);
147
+ // A continuous future's bar on another contract than the bar before has no change: two months' prices are no move.
148
+ let held: string | undefined;
149
+ const rolled = sorted.map((entry) => {
150
+ const roll = !!entry.contract && !!held && held !== entry.contract;
151
+ held = entry.contract ?? held;
152
+ return roll;
153
+ });
154
+ const rolls = sorted.flatMap((entry, index) => rolled[index] ? [`${entry.date.toISOString().slice(0, 10)} (${entry.contract})`] : []);
155
+ const gapNotices = [
156
+ historyMissingPeriodsNote(times, interval),
157
+ historyChangeBaseNote(times, interval),
158
+ rolls.length > 0 ? `No CHG at ${rolls.length === 1 ? "the roll" : "each roll"} to a new contract: ${rolls.join(", ")}` : null,
159
+ ].filter((notice): notice is string => notice != null);
147
160
  const rows = sorted.map((entry, index) => {
148
- const previous = sorted[index - 1]?.point;
161
+ const previous = rolled[index] ? undefined : sorted[index - 1]?.point;
149
162
  const { point, date, sourceIndex, filled } = entry;
150
163
  const newest = index === sorted.length - 1;
151
164
  const partial = newest && (!!forming || dayToDate);
@@ -15,6 +15,10 @@ import type { DataProvider } from "../../../../types/data-provider";
15
15
  import type { PricePoint } from "../../../../types/financials";
16
16
  import { isRoundTheClockCoin } from "../../../../utils/crypto-pair";
17
17
  import { isCurrencyPairListing } from "../../../../utils/fx-market-hours";
18
+ import { latestRegularSessionClose } from "../../../../market-data/market/freshness";
19
+ import { futuresSessionOpenMinute } from "../../../../market-data/market/trading-sessions";
20
+ import { calendarBarStart } from "../../../../time-series/chart-data";
21
+ import { newestClosedSession, newestSessionClose, withOfficialCloses } from "../../../../time-series/official-close";
18
22
  import {
19
23
  effectiveReturnResolution,
20
24
  endsAtLatestBar,
@@ -39,6 +43,8 @@ export interface ReturnSeries {
39
43
  lastSession?: string;
40
44
  /** The venue whose sessions the window follows; empty when none is known. */
41
45
  venue: string;
46
+ /** The local date of the newest session, closed but without its official close yet: its last bar ends on the last trade. */
47
+ closingPrintPending?: string;
42
48
  }
43
49
 
44
50
  export interface ReturnSeriesRequest {
@@ -66,20 +72,76 @@ function historyErrorMessage(error: unknown, resolution: ManualChartResolution):
66
72
  /**
67
73
  * The venue whose sessions set where the window ends: the one the ticker
68
74
  * names, else the listing its quote names, so a bare VTI ends at the NYSE
69
- * Arca close on a weekend. Coins and currency pairs need none.
75
+ * Arca close on a weekend, with the listing's currency when that was read.
76
+ * Coins and currency pairs need none.
70
77
  */
71
- async function sessionVenue(provider: DataProvider, symbol: string, exchange: string): Promise<string> {
72
- if (exchange || isRoundTheClockCoin(symbol) || isCurrencyPairListing(symbol, exchange)) return exchange;
78
+ async function sessionVenue(provider: DataProvider, symbol: string, exchange: string): Promise<{ venue: string; currency?: string }> {
79
+ if (exchange || isRoundTheClockCoin(symbol) || isCurrencyPairListing(symbol, exchange)) return { venue: exchange };
73
80
  try {
74
81
  const metadata = provider.getQuoteMetadata
75
82
  ? await provider.getQuoteMetadata(symbol, exchange)
76
83
  : await provider.getQuote(symbol, exchange);
77
- return metadata?.listingExchangeName ?? "";
84
+ return { venue: metadata?.listingExchangeName ?? "", ...(metadata?.currency ? { currency: metadata.currency } : {}) };
78
85
  } catch (error) {
79
- return logFailure("ticker-detail.data-panes.returns-history", "Read return session venue failed", "")(error);
86
+ return { venue: logFailure("ticker-detail.data-panes.returns-history", "Read return session venue failed", "")(error) };
80
87
  }
81
88
  }
82
89
 
90
+ const DAY_MS = 86_400_000;
91
+
92
+ /**
93
+ * Intraday bars with each closed session's last bar on its official close
94
+ * (withOfficialCloses), so every total and the base they are measured from
95
+ * are the closes `history` and CMP give, not the last trade before the
96
+ * closing auction. Earlier sessions take their daily bar's close, dated by
97
+ * the venue's local date; the newest closed session the close
98
+ * newestSessionClose picks from the quote and its daily bar, or the note
99
+ * that its print is to come. Daily and longer bars, futures (which settle
100
+ * rather than close on a print) and listings without sessions are returned
101
+ * as loaded.
102
+ */
103
+ async function withSessionCloses(
104
+ provider: DataProvider,
105
+ target: { symbol: string; exchange: string; venue: string; currency?: string },
106
+ interval: ManualChartResolution,
107
+ points: PricePoint[],
108
+ now: number,
109
+ context: { cacheMode: "refresh" } | undefined,
110
+ ): Promise<{ points: PricePoint[]; closingPrintPending?: string }> {
111
+ const venue = target.venue || undefined;
112
+ if (!isIntradayResolution(interval) || !points.length || futuresSessionOpenMinute(target.symbol, target.venue || target.exchange) != null
113
+ || !latestRegularSessionClose(venue, now)) return { points };
114
+ const newest = newestClosedSession(points, { venue, now });
115
+ // Whole UTC days, so the same window asks the same read (and its cached copy) all day.
116
+ const first = Math.floor(new Date(points[0]!.date).getTime() / DAY_MS) * DAY_MS - 2 * DAY_MS;
117
+ const end = Math.floor(Math.min(now, new Date(points.at(-1)!.date).getTime() + DAY_MS) / DAY_MS) * DAY_MS + DAY_MS - 1;
118
+ const [daily, quote] = await Promise.all([
119
+ fetchHistoryResult(provider, target.symbol, target.exchange, { kind: "detail", start: new Date(first), end: new Date(end), interval: "1d" }, context)
120
+ .catch(logFailure("ticker-detail.data-panes.returns-history", "Read the daily closes of a return window failed", null)),
121
+ // Only the newest closed session needs the quote's word on its close.
122
+ newest
123
+ ? Promise.resolve(provider.getQuote(target.symbol, target.exchange, context))
124
+ .catch(logFailure("ticker-detail.data-panes.returns-history", "Read the official close of a return window failed", null))
125
+ : null,
126
+ ]);
127
+ const dailyCloses = new Map<string, number>();
128
+ for (const bar of daily?.points ?? []) {
129
+ const time = new Date(bar.date).getTime();
130
+ if (!bar.partial && Number.isFinite(time) && Number.isFinite(bar.close) && bar.close > 0) {
131
+ dailyCloses.set(calendarBarStart(time, "1d", venue), bar.close);
132
+ }
133
+ }
134
+ const closes = new Map([...dailyCloses].filter(([date]) => date !== newest?.date));
135
+ const decided = newest
136
+ ? newestSessionClose(quote, { ...newest, venue, currency: target.currency ?? null, dailyClose: dailyCloses.get(newest.date) ?? null })
137
+ : null;
138
+ if (newest && decided && "close" in decided) closes.set(newest.date, decided.close);
139
+ return {
140
+ points: withOfficialCloses(points, closes, { venue, now }),
141
+ ...(newest && decided && "pending" in decided ? { closingPrintPending: newest.date } : {}),
142
+ };
143
+ }
144
+
83
145
  /**
84
146
  * The bars of a return window and where it starts and ends, as RETURN, its
85
147
  * report and its capture all load them: the window ends now while the
@@ -91,7 +153,7 @@ export async function loadReturnSeries(provider: DataProvider, request: ReturnSe
91
153
  if (request.window) return loadReturnWindow(provider, request, request.window);
92
154
  const { symbol, exchange, range, support, now } = request;
93
155
  const interval = effectiveReturnResolution(range, request.resolution, support);
94
- const venue = await sessionVenue(provider, symbol, exchange);
156
+ const { venue, currency } = await sessionVenue(provider, symbol, exchange);
95
157
  const windowEnd = returnWindowEnd({ symbol, exchange: venue }, now);
96
158
  const visibleFrom = returnVisibleFrom(range, interval, windowEnd.end, venue || undefined);
97
159
  const start = new Date(visibleFrom - returnLookbackMs(range, interval, support));
@@ -100,18 +162,24 @@ export async function loadReturnSeries(provider: DataProvider, request: ReturnSe
100
162
  let result;
101
163
  try {
102
164
  // Exact bounds first, then the interval's range history, as charts do
103
- // when a source keeps no bars for the exact window.
104
- result = await fetchHistoryResult(provider, symbol, exchange, { kind: "detail", start, end, interval }, context);
165
+ // when a source keeps no bars for the exact window or failed to load it
166
+ // (the range history may answer from a saved copy); the failure stands when that has none either.
167
+ let detailFailure: unknown = null;
168
+ result = await fetchHistoryResult(provider, symbol, exchange, { kind: "detail", start, end, interval }, context)
169
+ .catch((error: unknown) => { detailFailure = error; return null; });
105
170
  if (!result?.points.length) {
106
171
  result = await fetchHistoryResult(provider, symbol, exchange, { kind: "resolution", range: returnSupportRange(range), resolution: interval }, context) ?? result;
107
172
  }
173
+ if (!result?.points.length && detailFailure) throw detailFailure;
108
174
  } catch (error) {
109
175
  throw historyErrorMessage(error, interval);
110
176
  }
111
177
  if (!result) throw new Error("Price history unavailable");
112
178
  const latest = latestBarTime(result.points);
179
+ const closed = await withSessionCloses(provider, { symbol, exchange, venue, currency }, interval, result.points, now, context);
113
180
  return {
114
- points: result.points,
181
+ points: closed.points,
182
+ ...(closed.closingPrintPending ? { closingPrintPending: closed.closingPrintPending } : {}),
115
183
  // A 1W to 5Y window's rows start after its base close, found among the bars by their session days.
116
184
  visibleFrom: endsAtLatestBar(range) && latest !== null && latest < windowEnd.end
117
185
  ? returnVisibleFrom(range, interval, latest, venue || undefined)
@@ -142,7 +210,7 @@ async function loadReturnWindow(provider: DataProvider, request: ReturnSeriesReq
142
210
  const step = CHART_RESOLUTION_STEP_MS[request.resolution];
143
211
  let interval = available.includes(request.resolution) ? request.resolution
144
212
  : available.find((candidate) => CHART_RESOLUTION_STEP_MS[candidate] >= step) ?? available.at(-1) ?? "1d";
145
- const venue = await sessionVenue(provider, symbol, exchange);
213
+ const { venue, currency } = await sessionVenue(provider, symbol, exchange);
146
214
  const context = request.forceRefresh ? { cacheMode: "refresh" as const } : undefined;
147
215
  const load = async (resolution: ManualChartResolution) => {
148
216
  // A daily or longer bar is stamped at midnight UTC, so the window takes every bar from its first date's day, week or month on.
@@ -167,8 +235,10 @@ async function loadReturnWindow(provider: DataProvider, request: ReturnSeriesReq
167
235
  }
168
236
  }
169
237
  if (!loaded?.result) throw new Error("Price history unavailable");
238
+ const closed = await withSessionCloses(provider, { symbol, exchange, venue, currency }, interval, loaded.result.points, now, context);
170
239
  return {
171
- points: loaded.result.points,
240
+ points: closed.points,
241
+ ...(closed.closingPrintPending ? { closingPrintPending: closed.closingPrintPending } : {}),
172
242
  visibleFrom: loaded.visibleFrom,
173
243
  visibleTo: Math.min(last, now),
174
244
  ...(loaded.result.coverageStart ? { coverageStart: loaded.result.coverageStart } : {}),
@@ -52,6 +52,7 @@ import { DAY_SO_FAR_TEXT, withSessionBar } from "../../../time-series/session-ba
52
52
  import { buildReturnRows, closedMarketNote, isTodayReturnRow, returnBaseCloseDay, type ReturnRange } from "./data-panes/returns-model";
53
53
  import { periodBaseText } from "../../../market-data/market/period-return";
54
54
  import { loadReturnSeries } from "./data-panes/returns-history";
55
+ import { CLOSING_PRINT_PENDING_TEXT } from "../../../time-series/official-close";
55
56
  import {
56
57
  closedHistoryBarDay, formingHistoryBar, HISTORY_DEFAULT_RANGE, historyBarDate, historyBarSize, historyBarSizeWord, historyDateHeader, isDailyOrLongerBar,
57
58
  type HistoryBarSize,
@@ -596,7 +597,10 @@ export const returnsHeadless: HeadlessPaneDefinition<"rows"> = {
596
597
  ...figures,
597
598
  ...forming || dayToDate ? [{ key: "partial", header: "Partial", format: (value: unknown) => value === true ? "true" : "" }] : [],
598
599
  ];
599
- const textColumns: HeadlessPaneColumn[] = [timeColumn(true), ...figures];
600
+ // A narrow table drops the dollar moves, the interval's first, rather than putting Total % on a line under every row.
601
+ const textFigures = figures.map((column) => column.key === "intervalChange" ? { ...column, optional: true, dropPriority: 2 }
602
+ : column.key === "totalChange" ? { ...column, optional: true, dropPriority: 1 } : column);
603
+ const textColumns: HeadlessPaneColumn[] = [timeColumn(true), ...textFigures];
600
604
  // An interval the range is too long for is served in the nearest longer bar; say so rather than switch silently.
601
605
  const notices = [
602
606
  maxDrawdownLine,
@@ -619,8 +623,12 @@ export const returnsHeadless: HeadlessPaneDefinition<"rows"> = {
619
623
  oldest: null,
620
624
  ...(forming ? { periodToDate: forming } : closedDay ? { asOf: closedDay } : {}),
621
625
  ...(series.venue ? { cadenceExchange: series.venue } : {}),
622
- // The window ends at the last session while the market is shut, which the status line says.
623
- ...(series.lastSession ? { note: closedMarketNote(series.lastSession, series.visibleTo) } : {}),
626
+ // The window ends at the last session while the market is shut, which the status line says, and whether its
627
+ // last bar ends on the official close yet.
628
+ ...(series.lastSession || series.closingPrintPending ? { note: [
629
+ series.lastSession ? closedMarketNote(series.lastSession, series.visibleTo) : null,
630
+ series.closingPrintPending ? CLOSING_PRINT_PENDING_TEXT : null,
631
+ ].filter(Boolean).join(", ") } : {}),
624
632
  ...(dayToDate ? { basis: "daily bars", note: `the newest bar is ${DAY_SO_FAR_TEXT}` } : {}),
625
633
  },
626
634
  metadata: {