@gloom-sh/terminal 0.19.1 → 0.19.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (554) hide show
  1. package/README.md +7 -1
  2. package/package.json +2 -1
  3. package/patches/electrobun@1.18.1.patch +108 -1
  4. package/src/account/upgrade-dialog.tsx +4 -23
  5. package/src/api-client/coalesce.ts +118 -0
  6. package/src/api-client/credit-boards.ts +13 -0
  7. package/src/api-client/crypto-etf-flows.ts +6 -0
  8. package/src/api-client/crypto-markets.ts +40 -0
  9. package/src/api-client/data.ts +201 -14
  10. package/src/api-client/equity-screener.ts +10 -0
  11. package/src/api-client/errors.ts +58 -0
  12. package/src/api-client/gpu.ts +29 -0
  13. package/src/api-client/index.ts +5 -2
  14. package/src/api-client/plan-access.ts +10 -1
  15. package/src/api-client/plan-rules.ts +35 -0
  16. package/src/api-client/research-activity.ts +1 -32
  17. package/src/api-client/types.ts +21 -1
  18. package/src/app/attention-notice.tsx +94 -0
  19. package/src/app/runtime/startup.ts +3 -1
  20. package/src/app.tsx +23 -1
  21. package/src/architecture/core-builtin-allowlist.json +2 -1
  22. package/src/capabilities/chart-series.ts +2 -1
  23. package/src/capabilities/factories.ts +1 -0
  24. package/src/cli/ascii-output.ts +42 -0
  25. package/src/cli/base-converter.ts +10 -2
  26. package/src/cli/command-options.ts +16 -1
  27. package/src/cli/commands/crypto-hints.ts +23 -4
  28. package/src/cli/commands/market.ts +101 -27
  29. package/src/cli/commands/news-search.ts +24 -5
  30. package/src/cli/commands/overview.ts +14 -10
  31. package/src/cli/commands/search.ts +84 -25
  32. package/src/cli/commands/system.ts +22 -30
  33. package/src/cli/commands/ticker.ts +49 -9
  34. package/src/cli/commands/version.ts +33 -0
  35. package/src/cli/context.ts +6 -9
  36. package/src/cli/data-dir.ts +7 -0
  37. package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
  38. package/src/cli/desktop-pane-shot-browser.ts +320 -0
  39. package/src/cli/desktop-pane-shot-routes.ts +26 -1
  40. package/src/cli/desktop-pane-shot.ts +196 -256
  41. package/src/cli/early-command.ts +42 -0
  42. package/src/cli/earnings-calendar.ts +9 -0
  43. package/src/cli/entry.ts +19 -13
  44. package/src/cli/errors.ts +12 -2
  45. package/src/cli/glossary.ts +2 -1
  46. package/src/cli/help.ts +1 -1
  47. package/src/cli/history-export.ts +22 -2
  48. package/src/cli/history-rows.ts +189 -11
  49. package/src/cli/index.ts +18 -1
  50. package/src/cli/known-gaps.ts +51 -5
  51. package/src/cli/listing-arg.ts +63 -10
  52. package/src/cli/options.ts +1 -1
  53. package/src/cli/output-file.ts +60 -13
  54. package/src/cli/pane-functions/catalog.ts +14 -3
  55. package/src/cli/pane-functions/data.ts +1 -1
  56. package/src/cli/pane-functions/dom.ts +45 -0
  57. package/src/cli/pane-functions/freshness.ts +46 -9
  58. package/src/cli/pane-functions/function-help.ts +17 -9
  59. package/src/cli/pane-functions/headless.ts +22 -14
  60. package/src/cli/pane-functions/index.ts +6 -2
  61. package/src/cli/pane-functions/options.ts +6 -2
  62. package/src/cli/pane-functions/resolver.ts +27 -5
  63. package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
  64. package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
  65. package/src/cli/pane-functions/screenshot-http.ts +70 -0
  66. package/src/cli/pane-functions/screenshot-render.ts +19 -5
  67. package/src/cli/quote-answer.ts +49 -6
  68. package/src/cli/registry.ts +41 -13
  69. package/src/cli/result.ts +12 -6
  70. package/src/cli/ticker-resolution.ts +2 -1
  71. package/src/cli/types.ts +2 -0
  72. package/src/components/chart/composite/format.ts +3 -0
  73. package/src/components/chart-table/header.tsx +1 -1
  74. package/src/components/chart-table/layout.ts +28 -11
  75. package/src/components/command-bar/action-runtime.ts +4 -0
  76. package/src/components/command-bar/commands/direct/index.ts +18 -33
  77. package/src/components/command-bar/commands/direct/runtime.ts +5 -0
  78. package/src/components/command-bar/commands/registry.ts +10 -1
  79. package/src/components/command-bar/routes/root/command-items.ts +9 -4
  80. package/src/components/command-bar/routes/root/runtime.ts +51 -6
  81. package/src/components/command-bar/selection-runtime.ts +12 -29
  82. package/src/components/command-bar/surface/index.tsx +9 -3
  83. package/src/components/company-logo.tsx +2 -1
  84. package/src/components/layout/detached-pane-shell.tsx +2 -2
  85. package/src/components/layout/header.tsx +10 -5
  86. package/src/components/layout/market-summary.ts +25 -7
  87. package/src/components/layout/pane/footer/index.tsx +8 -2
  88. package/src/components/layout/pane/footer/model.ts +73 -6
  89. package/src/components/layout/pane/header.tsx +2 -2
  90. package/src/components/layout/pane/status-footer.ts +2 -1
  91. package/src/components/market-board/index.tsx +3 -2
  92. package/src/components/onboarding/desks-step.tsx +12 -4
  93. package/src/components/onboarding/first-run-workspace.ts +81 -4
  94. package/src/components/onboarding/onboarding-frame.tsx +4 -2
  95. package/src/components/onboarding/onboarding-keyboard.ts +8 -5
  96. package/src/components/onboarding/onboarding-wizard.tsx +60 -26
  97. package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
  98. package/src/components/onboarding/run-setup-again.ts +24 -0
  99. package/src/components/onboarding/use-onboarding-progress.ts +33 -15
  100. package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
  101. package/src/components/onboarding/wizard-model.ts +57 -3
  102. package/src/components/onboarding/wizard-positions.ts +1 -1
  103. package/src/components/ui/delayed-mark.tsx +56 -0
  104. package/src/components/ui/display.tsx +16 -5
  105. package/src/components/ui/external-link.tsx +3 -6
  106. package/src/components/ui/icon.tsx +16 -3
  107. package/src/components/ui/logo-mark.tsx +8 -5
  108. package/src/components/ui/stat-grid.tsx +263 -19
  109. package/src/core/app-services.ts +11 -2
  110. package/src/data/app-persistence.ts +9 -7
  111. package/src/data/config/store/node.ts +11 -5
  112. package/src/data/config/store/normalize.ts +16 -3
  113. package/src/data/plugin-cache.ts +15 -0
  114. package/src/data/plugin-state-store.ts +81 -24
  115. package/src/data/profile-permissions.ts +47 -0
  116. package/src/data/resource-store.ts +165 -90
  117. package/src/data/session-store.ts +31 -8
  118. package/src/data/sqlite/access.ts +324 -0
  119. package/src/data/sqlite/checkpointer.ts +285 -0
  120. package/src/data/sqlite/database.ts +80 -4
  121. package/src/data/sqlite/retry.ts +50 -9
  122. package/src/data/ticker-store.ts +57 -23
  123. package/src/docs/snapshot.json +191 -78
  124. package/src/i18n/es.ts +18 -2
  125. package/src/i18n/ja.ts +18 -2
  126. package/src/i18n/ko.ts +18 -2
  127. package/src/i18n/zh-cn.ts +18 -2
  128. package/src/i18n/zh-tw.ts +18 -2
  129. package/src/layout/desk-keys.ts +4 -1
  130. package/src/layout/desks.ts +38 -5
  131. package/src/layout-marketplace/gallery-desktop.tsx +45 -16
  132. package/src/market-data/batched-reads.ts +16 -0
  133. package/src/market-data/coordinator/auxiliary.ts +4 -2
  134. package/src/market-data/coordinator/index.ts +11 -2
  135. package/src/market-data/futures-board.ts +1 -1
  136. package/src/market-data/futures-contract.ts +3 -1
  137. package/src/market-data/market/index-level.ts +6 -4
  138. package/src/market-data/published-apac-sessions.ts +6 -0
  139. package/src/market-data/realized-volatility.ts +4 -2
  140. package/src/news/aggregator.ts +7 -4
  141. package/src/news/news-model.ts +11 -0
  142. package/src/news/story-id.ts +22 -0
  143. package/src/news/ticker-symbols.ts +17 -1
  144. package/src/notifications/app-notifier.ts +35 -14
  145. package/src/plugins/auto-update-node.ts +10 -6
  146. package/src/plugins/builtin/account-management/form-components.tsx +1 -1
  147. package/src/plugins/builtin/account-management/index.tsx +2 -3
  148. package/src/plugins/builtin/account-management/model.ts +1 -1
  149. package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
  150. package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
  151. package/src/plugins/builtin/alerts/events.ts +10 -2
  152. package/src/plugins/builtin/alerts/index.tsx +5 -5
  153. package/src/plugins/builtin/alerts/research-builder.ts +5 -1
  154. package/src/plugins/builtin/alerts/research-rules.ts +18 -0
  155. package/src/plugins/builtin/alerts/targets.ts +4 -1
  156. package/src/plugins/builtin/attention/headless.ts +1 -1
  157. package/src/plugins/builtin/attention/index.tsx +2 -4
  158. package/src/plugins/builtin/attention/listing.ts +1 -1
  159. package/src/plugins/builtin/attention/series.ts +1 -1
  160. package/src/plugins/builtin/awards/index.tsx +1 -2
  161. package/src/plugins/builtin/backtest/client.ts +1 -0
  162. package/src/plugins/builtin/backtest/index.tsx +1 -2
  163. package/src/plugins/builtin/backtest/rules.ts +3 -1
  164. package/src/plugins/builtin/backtest/window.ts +8 -7
  165. package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
  166. package/src/plugins/builtin/broker-manager/index.tsx +1 -1
  167. package/src/plugins/builtin/buildout/cells.tsx +5 -0
  168. package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
  169. package/src/plugins/builtin/buildout/company/id.ts +2 -0
  170. package/src/plugins/builtin/buildout/company/model.ts +247 -0
  171. package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
  172. package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
  173. package/src/plugins/builtin/buildout/facilities.ts +309 -0
  174. package/src/plugins/builtin/buildout/format.ts +9 -0
  175. package/src/plugins/builtin/buildout/index.tsx +51 -5
  176. package/src/plugins/builtin/buildout/model/index.ts +62 -10
  177. package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
  178. package/src/plugins/builtin/buildout/model/types.ts +11 -2
  179. package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
  180. package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
  181. package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
  182. package/src/plugins/builtin/buildout/table-model.ts +26 -2
  183. package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
  184. package/src/plugins/builtin/cds/headless.ts +10 -2
  185. package/src/plugins/builtin/cds/model.ts +7 -3
  186. package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
  187. package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
  188. package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
  189. package/src/plugins/builtin/changelog/index.tsx +3 -2
  190. package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
  191. package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
  192. package/src/plugins/builtin/chart-composer/headless.ts +88 -21
  193. package/src/plugins/builtin/chart-composer/index.tsx +2 -3
  194. package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
  195. package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
  196. package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
  197. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  198. package/src/plugins/builtin/chat/controller/index.ts +1 -1
  199. package/src/plugins/builtin/chat/controller/messages.ts +15 -4
  200. package/src/plugins/builtin/chat/ghost-user.ts +8 -0
  201. package/src/plugins/builtin/chat/index.tsx +5 -5
  202. package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
  203. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  204. package/src/plugins/builtin/cloud/index.tsx +1 -2
  205. package/src/plugins/builtin/cloud/plugin.tsx +6 -4
  206. package/src/plugins/builtin/cloud/team/module.tsx +1 -1
  207. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
  208. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
  209. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
  210. package/src/plugins/builtin/comment-letters/index.tsx +1 -2
  211. package/src/plugins/builtin/commodity-balances/client.ts +4 -1
  212. package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
  213. package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
  214. package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
  215. package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
  216. package/src/plugins/builtin/congress-trades/headless.ts +2 -0
  217. package/src/plugins/builtin/congress-trades/model.ts +92 -64
  218. package/src/plugins/builtin/congress-trades/table.tsx +14 -2
  219. package/src/plugins/builtin/connections/index.ts +2 -3
  220. package/src/plugins/builtin/cot/client.ts +28 -2
  221. package/src/plugins/builtin/cot/headless.ts +4 -1
  222. package/src/plugins/builtin/cot/index.tsx +5 -4
  223. package/src/plugins/builtin/cot/model.ts +58 -7
  224. package/src/plugins/builtin/cot/pane.tsx +6 -2
  225. package/src/plugins/builtin/cpi/client.ts +6 -5
  226. package/src/plugins/builtin/cpi/index.tsx +1 -2
  227. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  228. package/src/plugins/builtin/credit-boards/client.ts +7 -2
  229. package/src/plugins/builtin/credit-boards/headless.ts +25 -12
  230. package/src/plugins/builtin/credit-boards/model.ts +29 -0
  231. package/src/plugins/builtin/credit-boards/series.ts +3 -4
  232. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
  233. package/src/plugins/builtin/credit-conditions/client.ts +22 -14
  234. package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
  235. package/src/plugins/builtin/crypto-board/client.ts +5 -5
  236. package/src/plugins/builtin/crypto-board/headless.ts +4 -3
  237. package/src/plugins/builtin/crypto-board/index.tsx +4 -2
  238. package/src/plugins/builtin/crypto-board/series.ts +6 -4
  239. package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
  240. package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
  241. package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
  242. package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
  243. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
  244. package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
  245. package/src/plugins/builtin/debt-maturities/model.ts +17 -1
  246. package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
  247. package/src/plugins/builtin/debt-maturities/series.ts +3 -3
  248. package/src/plugins/builtin/debug/index.tsx +1 -1
  249. package/src/plugins/builtin/dividend-yield/client.ts +32 -2
  250. package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
  251. package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
  252. package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
  253. package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
  254. package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
  255. package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
  256. package/src/plugins/builtin/dividend-yield/view.ts +21 -0
  257. package/src/plugins/builtin/docs/index.tsx +2 -3
  258. package/src/plugins/builtin/doe/client.ts +6 -5
  259. package/src/plugins/builtin/doe/headless.ts +16 -3
  260. package/src/plugins/builtin/doe/index.tsx +5 -4
  261. package/src/plugins/builtin/doe/prices.ts +2 -3
  262. package/src/plugins/builtin/doe/series.ts +4 -4
  263. package/src/plugins/builtin/earnings/board.tsx +2 -1
  264. package/src/plugins/builtin/earnings/history.tsx +2 -1
  265. package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
  266. package/src/plugins/builtin/earnings-calls/data.ts +19 -11
  267. package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
  268. package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
  269. package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
  270. package/src/plugins/builtin/econ/calendar-model.ts +29 -6
  271. package/src/plugins/builtin/econ/detail-view.tsx +16 -2
  272. package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
  273. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  274. package/src/plugins/builtin/equity-screener/headless.ts +1 -0
  275. package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
  276. package/src/plugins/builtin/equity-screener/index.tsx +1 -2
  277. package/src/plugins/builtin/equity-screener/model.ts +28 -4
  278. package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
  279. package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
  280. package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
  281. package/src/plugins/builtin/executives/data.ts +6 -1
  282. package/src/plugins/builtin/executives/pane.tsx +9 -21
  283. package/src/plugins/builtin/exposure/holdings.ts +35 -13
  284. package/src/plugins/builtin/exposure/index.tsx +1 -2
  285. package/src/plugins/builtin/fear-greed/cache.ts +4 -0
  286. package/src/plugins/builtin/fear-greed/cli.ts +44 -0
  287. package/src/plugins/builtin/fear-greed/index.tsx +5 -53
  288. package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
  289. package/src/plugins/builtin/fear-greed/series.ts +6 -4
  290. package/src/plugins/builtin/fixed-income/index.tsx +1 -2
  291. package/src/plugins/builtin/futures/board-reads.ts +25 -0
  292. package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
  293. package/src/plugins/builtin/futures/index.tsx +15 -0
  294. package/src/plugins/builtin/futures/model.ts +14 -0
  295. package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
  296. package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
  297. package/src/plugins/builtin/futures-curve/headless.ts +6 -1
  298. package/src/plugins/builtin/futures-curve/index.tsx +1 -2
  299. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  300. package/src/plugins/builtin/gpu/board.tsx +51 -12
  301. package/src/plugins/builtin/gpu/client.ts +19 -3
  302. package/src/plugins/builtin/gpu/equities.tsx +5 -2
  303. package/src/plugins/builtin/gpu/headless.ts +14 -2
  304. package/src/plugins/builtin/gpu/index.tsx +1 -2
  305. package/src/plugins/builtin/gpu/model.ts +20 -2
  306. package/src/plugins/builtin/gpu/pane.tsx +1 -1
  307. package/src/plugins/builtin/gpu/series.ts +2 -3
  308. package/src/plugins/builtin/help/index.tsx +1 -1
  309. package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
  310. package/src/plugins/builtin/holders/index-holders.ts +24 -12
  311. package/src/plugins/builtin/holders/index.tsx +2 -3
  312. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
  313. package/src/plugins/builtin/iv-history/index.tsx +1 -2
  314. package/src/plugins/builtin/jodi/client.ts +4 -1
  315. package/src/plugins/builtin/jodi/index.tsx +1 -2
  316. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
  317. package/src/plugins/builtin/layout-manager/index.tsx +9 -8
  318. package/src/plugins/builtin/macro-day/pane.tsx +4 -2
  319. package/src/plugins/builtin/market-halts/index.tsx +1 -2
  320. package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
  321. package/src/plugins/builtin/market-movers/client.ts +1 -0
  322. package/src/plugins/builtin/market-movers/index.tsx +16 -5
  323. package/src/plugins/builtin/market-movers/screener.ts +23 -6
  324. package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
  325. package/src/plugins/builtin/market-valuation/index.tsx +1 -2
  326. package/src/plugins/builtin/members/index.tsx +1 -2
  327. package/src/plugins/builtin/money-markets/index.tsx +1 -2
  328. package/src/plugins/builtin/money-markets/pane.tsx +2 -1
  329. package/src/plugins/builtin/money-markets/series.ts +3 -5
  330. package/src/plugins/builtin/news/headless.ts +7 -0
  331. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  332. package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
  333. package/src/plugins/builtin/notes/index.tsx +3 -6
  334. package/src/plugins/builtin/openfda/index.tsx +1 -2
  335. package/src/plugins/builtin/options/view.tsx +6 -5
  336. package/src/plugins/builtin/options-calculator/index.tsx +1 -2
  337. package/src/plugins/builtin/options-positioning/index.tsx +1 -2
  338. package/src/plugins/builtin/options-scenario/index.tsx +2 -4
  339. package/src/plugins/builtin/perps/index.tsx +1 -2
  340. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
  341. package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
  342. package/src/plugins/builtin/plugin-module.ts +16 -2
  343. package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
  344. package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
  345. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
  346. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
  347. package/src/plugins/builtin/power/gas-client.ts +2 -1
  348. package/src/plugins/builtin/power/index.tsx +1 -2
  349. package/src/plugins/builtin/power/prices-client.ts +3 -2
  350. package/src/plugins/builtin/rate-path/index.tsx +1 -2
  351. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  352. package/src/plugins/builtin/realized-vol/index.tsx +1 -2
  353. package/src/plugins/builtin/realized-vol/model.ts +16 -2
  354. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  355. package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
  356. package/src/plugins/builtin/research/analyst-headless.ts +33 -6
  357. package/src/plugins/builtin/research/analyst-model.ts +37 -2
  358. package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
  359. package/src/plugins/builtin/research/index.tsx +5 -8
  360. package/src/plugins/builtin/research-search/index.tsx +1 -2
  361. package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
  362. package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
  363. package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
  364. package/src/plugins/builtin/risk-factors/data.ts +8 -4
  365. package/src/plugins/builtin/risk-factors/index.tsx +3 -1
  366. package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
  367. package/src/plugins/builtin/roadmap/index.tsx +2 -3
  368. package/src/plugins/builtin/roadmap/pane.tsx +49 -2
  369. package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
  370. package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
  371. package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
  372. package/src/plugins/builtin/scanner/lock-note.ts +26 -3
  373. package/src/plugins/builtin/sec/filing-content.ts +38 -6
  374. package/src/plugins/builtin/sec/headless.ts +5 -1
  375. package/src/plugins/builtin/sec/index.tsx +4 -2
  376. package/src/plugins/builtin/sec/model.ts +2 -0
  377. package/src/plugins/builtin/sectors/index.tsx +11 -5
  378. package/src/plugins/builtin/series-inventory.ts +2 -0
  379. package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
  380. package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
  381. package/src/plugins/builtin/shared/forming-bars.ts +29 -0
  382. package/src/plugins/builtin/shared/fred-points.ts +6 -1
  383. package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
  384. package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
  385. package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
  386. package/src/plugins/builtin/shared/series-warning.ts +13 -4
  387. package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
  388. package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
  389. package/src/plugins/builtin/short-volume/pane.tsx +2 -1
  390. package/src/plugins/builtin/short-volume/series.ts +2 -3
  391. package/src/plugins/builtin/social-mentions/client.ts +15 -10
  392. package/src/plugins/builtin/social-mentions/index.tsx +1 -2
  393. package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
  394. package/src/plugins/builtin/social-mentions/series.ts +2 -3
  395. package/src/plugins/builtin/supply-chain/client.ts +22 -1
  396. package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
  397. package/src/plugins/builtin/supply-chain/index.tsx +1 -2
  398. package/src/plugins/builtin/thirteenf/api.ts +127 -29
  399. package/src/plugins/builtin/thirteenf/data.ts +165 -35
  400. package/src/plugins/builtin/thirteenf/index.tsx +2 -4
  401. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  402. package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
  403. package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
  404. package/src/plugins/builtin/thirteenf/types.ts +14 -0
  405. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
  406. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
  407. package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
  408. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
  409. package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
  410. package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
  411. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
  412. package/src/plugins/builtin/time-sales/index.tsx +1 -2
  413. package/src/plugins/builtin/time-sales/pane.tsx +3 -1
  414. package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
  415. package/src/plugins/builtin/trace-bonds/model.ts +44 -10
  416. package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
  417. package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
  418. package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
  419. package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
  420. package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
  421. package/src/plugins/builtin/vol-surface/client.ts +18 -3
  422. package/src/plugins/builtin/vol-surface/index.tsx +1 -2
  423. package/src/plugins/builtin/volatility/client.ts +43 -16
  424. package/src/plugins/builtin/volatility/headless.ts +5 -2
  425. package/src/plugins/builtin/volatility/index.tsx +1 -2
  426. package/src/plugins/builtin/volatility/model.ts +17 -6
  427. package/src/plugins/builtin/volatility/pane.tsx +6 -2
  428. package/src/plugins/builtin/volatility/series.ts +2 -1
  429. package/src/plugins/builtin/world-indices/footer.ts +6 -1
  430. package/src/plugins/builtin/world-venue-map/client.ts +10 -1
  431. package/src/plugins/builtin/world-venue-map/index.ts +6 -2
  432. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
  433. package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
  434. package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
  435. package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
  436. package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
  437. package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
  438. package/src/plugins/builtin/yield-curve/client.ts +4 -6
  439. package/src/plugins/builtin/yield-curve/curves.ts +16 -2
  440. package/src/plugins/builtin/yield-curve/headless.ts +3 -3
  441. package/src/plugins/bundle.ts +52 -2
  442. package/src/plugins/catalog-browser.ts +2 -2
  443. package/src/plugins/catalog-cli.ts +20 -0
  444. package/src/plugins/host-link.ts +13 -1
  445. package/src/plugins/installer.ts +64 -42
  446. package/src/plugins/loader.ts +43 -11
  447. package/src/plugins/peer-imports.ts +201 -0
  448. package/src/plugins/peer-retry.ts +26 -29
  449. package/src/plugins/plugin-process.ts +128 -0
  450. package/src/plugins/redirection-guard.ts +45 -0
  451. package/src/remote/endpoint.ts +16 -3
  452. package/src/renderers/browser/main.tsx +3 -2
  453. package/src/renderers/cloudflare/http-proxy.ts +7 -123
  454. package/src/renderers/dom/desktop/icons.tsx +5 -2
  455. package/src/renderers/dom/host/image-surface.tsx +46 -21
  456. package/src/renderers/dom/styles.css +7 -0
  457. package/src/renderers/dom/toast-host.tsx +2 -0
  458. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  459. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
  460. package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
  461. package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
  462. package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
  463. package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
  464. package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
  465. package/src/renderers/electrobun/bun/window/attention.ts +54 -0
  466. package/src/renderers/electrobun/bun/window/create.ts +6 -2
  467. package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
  468. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
  469. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
  470. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
  471. package/src/renderers/electrobun/view/main.tsx +7 -2
  472. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
  473. package/src/renderers/electrobun/view/ui-host.tsx +9 -2
  474. package/src/renderers/opentui/browser-open.ts +10 -4
  475. package/src/renderers/opentui/image/surface.tsx +1 -1
  476. package/src/renderers/opentui/start.tsx +12 -6
  477. package/src/renderers/opentui/toast-host.tsx +2 -1
  478. package/src/series/registry.ts +18 -0
  479. package/src/series/resolve.ts +2 -1
  480. package/src/series/standalone.ts +10 -1
  481. package/src/series/types.ts +12 -0
  482. package/src/series/validate.ts +11 -2
  483. package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
  484. package/src/sources/gloomberb-cloud/index.ts +28 -5
  485. package/src/sources/gloomberb-cloud/news.ts +24 -3
  486. package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
  487. package/src/sources/provider-errors.ts +26 -3
  488. package/src/sources/provider-router/history.ts +5 -0
  489. package/src/sources/provider-router/index.ts +13 -0
  490. package/src/sync/core-contributors.ts +32 -0
  491. package/src/telemetry/attention-counts.ts +64 -6
  492. package/src/telemetry/attention-setting.ts +33 -0
  493. package/src/telemetry/crash-reports-node.ts +3 -1
  494. package/src/telemetry/stall-clocks.ts +93 -0
  495. package/src/telemetry/stall-reports-node.ts +25 -9
  496. package/src/telemetry/stall-reports.ts +218 -13
  497. package/src/test-support/outage.ts +8 -0
  498. package/src/test-support/preload.ts +9 -0
  499. package/src/tickers/company-name-listing.ts +59 -29
  500. package/src/tickers/company-name.ts +84 -1
  501. package/src/tickers/listing-fate.ts +14 -3
  502. package/src/tickers/saved-instrument.ts +17 -0
  503. package/src/tickers/search/home-listing.ts +104 -8
  504. package/src/tickers/search/index.ts +5 -2
  505. package/src/tickers/search/types.ts +6 -1
  506. package/src/time-series/bar-aggregation.ts +7 -1
  507. package/src/time-series/base-series.ts +3 -3
  508. package/src/time-series/chart-bounds.ts +1 -1
  509. package/src/time-series/chart-data.ts +5 -2
  510. package/src/time-series/chart-seed.ts +1 -1
  511. package/src/time-series/coin-session-close.ts +211 -0
  512. package/src/time-series/live-bars.ts +13 -2
  513. package/src/time-series/live-quotes.ts +1 -1
  514. package/src/time-series/market.ts +5 -3
  515. package/src/time-series/official-close.ts +159 -0
  516. package/src/time-series/price-comparison.ts +75 -8
  517. package/src/time-series/resolve.ts +32 -4
  518. package/src/time-series/series-presentation.ts +1 -1
  519. package/src/time-series/studies/formula.ts +205 -0
  520. package/src/time-series/studies/indicators.ts +299 -0
  521. package/src/time-series/studies/leverage.ts +75 -0
  522. package/src/time-series/studies/pair.ts +138 -0
  523. package/src/time-series/studies/resolve.ts +333 -0
  524. package/src/time-series/studies/shared.ts +141 -0
  525. package/src/time-series/studies/units.ts +243 -0
  526. package/src/time-series/studies/vwap.ts +274 -0
  527. package/src/time-series/types.ts +7 -0
  528. package/src/types/config.ts +18 -2
  529. package/src/types/data-provider.ts +23 -0
  530. package/src/types/financials.ts +40 -3
  531. package/src/types/headless.ts +42 -3
  532. package/src/types/plugin.ts +32 -3
  533. package/src/ui/host.tsx +17 -1
  534. package/src/ui/toast.tsx +26 -1
  535. package/src/utils/ascii-text.ts +140 -0
  536. package/src/utils/cli-output.ts +91 -18
  537. package/src/utils/concurrency.ts +24 -0
  538. package/src/utils/exchanges.ts +6 -0
  539. package/src/utils/external-url.ts +20 -7
  540. package/src/utils/financial-statements.ts +73 -16
  541. package/src/utils/fundamentals.ts +17 -5
  542. package/src/utils/futures-generic.ts +4 -4
  543. package/src/utils/http-proxy-upstream.ts +124 -0
  544. package/src/utils/lease-liabilities.ts +54 -0
  545. package/src/utils/main-thread-stall.ts +122 -5
  546. package/src/utils/perf-marks.ts +58 -12
  547. package/src/utils/price-history-integrity.ts +47 -1
  548. package/src/utils/private-files.ts +141 -0
  549. package/src/utils/public-address.ts +99 -0
  550. package/src/account/upgrade-personalization.ts +0 -57
  551. package/src/account/upgrade-tickers.ts +0 -124
  552. package/src/api-client/upgrade-experiment.ts +0 -91
  553. package/src/plugins/builtin/cloud/browser.tsx +0 -3
  554. package/src/time-series/studies.ts +0 -1628
@@ -4,6 +4,9 @@ import type { DataProvider } from "../../../types/data-provider";
4
4
  import { isNoSourceAnswer } from "../../../sources/provider-errors";
5
5
  import type { PricePoint } from "../../../types/financials";
6
6
  import { logFailure } from "../../../utils/log-failure";
7
+ import { coalescedBump } from "./coalesced-bump";
8
+ import { readsInBatches } from "../../../market-data/batched-reads";
9
+ import { boardReadGate } from "./board-reads";
7
10
  import type { BoardQuoteMap } from "../shared/use-quote-board";
8
11
  import type { FuturesContract } from "../../../market-data/futures-board";
9
12
  import {
@@ -33,8 +36,6 @@ const HISTORY_TTL_MS = 30 * 60_000;
33
36
  * once a minute, not once a tick.
34
37
  */
35
38
  const HISTORY_RETRY_MS = 60_000;
36
- /** Forty-odd contracts open at once; a few requests at a time keep the board's quotes first. */
37
- const HISTORY_CONCURRENCY = 4;
38
39
 
39
40
  /**
40
41
  * Null when the source has no history for the contract (delisted, unknown),
@@ -43,16 +44,17 @@ const HISTORY_CONCURRENCY = 4;
43
44
  */
44
45
  const historyCache = createTtlCache<readonly PricePoint[] | null>({ ttlMs: HISTORY_TTL_MS, rejectionMs: HISTORY_RETRY_MS });
45
46
 
46
- function readHistory(provider: DataProvider, symbol: string): Promise<readonly PricePoint[] | null> {
47
- return provider.getPriceHistory(symbol, "", "1Y").then((points) => points.length > 0 ? points : null, (error: unknown) => {
47
+ /** `batch`: asked with every other row's, from a source that batches them (`DataProvider.batchesReads`). */
48
+ function readHistory(provider: DataProvider, symbol: string, batch: boolean): Promise<readonly PricePoint[] | null> {
49
+ return provider.getPriceHistory(symbol, "", "1Y", batch ? { batch } : undefined).then((points) => points.length > 0 ? points : null, (error: unknown) => {
48
50
  if (isNoSourceAnswer(error)) return null;
49
51
  throw error;
50
52
  });
51
53
  }
52
54
 
53
55
  /** Whether the contract has an answer; false when the read failed. */
54
- function loadHistory(provider: DataProvider, symbol: string): Promise<boolean> {
55
- return historyCache.load(symbol, () => readHistory(provider, symbol))
56
+ function loadHistory(provider: DataProvider, symbol: string, batch: boolean): Promise<boolean> {
57
+ return historyCache.load(symbol, () => readHistory(provider, symbol, batch))
56
58
  .then(() => true, logFailure("futures", "Read a futures contract's history failed", false));
57
59
  }
58
60
 
@@ -89,18 +91,22 @@ export function useFrontContractReturns(
89
91
  let cancelled = false;
90
92
  let failed = false;
91
93
  let retryTimer: ReturnType<typeof setTimeout> | undefined;
92
- const worker = async () => {
93
- for (let symbol = queue.shift(); symbol; symbol = queue.shift()) {
94
- if (!await loadHistory(provider, symbol)) failed = true;
95
- if (!cancelled) setVersion((current) => current + 1);
96
- }
97
- };
98
- void Promise.all(Array.from({ length: Math.min(HISTORY_CONCURRENCY, queue.length) }, worker)).then(() => {
94
+ const landed = coalescedBump(() => {
95
+ if (!cancelled) setVersion((current) => current + 1);
96
+ });
97
+ // Twelve reads at a time, shared with the rows' terms (`boardRead`), or all at once from a source that
98
+ // batches them (`DataProvider.batchesReads`); a board that moved on skips its turn.
99
+ void readsInBatches(provider, "getPriceHistory").then((batch) => Promise.all(queue.map((symbol) => boardReadGate(batch)(async () => {
100
+ if (cancelled) return;
101
+ if (!await loadHistory(provider, symbol, batch)) failed = true;
102
+ landed.bump();
103
+ })))).then(() => {
104
+ landed.flush();
99
105
  if (failed && !cancelled) retryTimer = setTimeout(() => setRetry((current) => current + 1), HISTORY_RETRY_MS);
100
106
  });
101
107
  return () => {
102
108
  cancelled = true;
103
- queue.length = 0;
109
+ landed.cancel();
104
110
  clearTimeout(retryTimer);
105
111
  };
106
112
  }, [candidates, enabled, provider, today, retry]);
@@ -7,6 +7,7 @@ import { newestReportTime, oldestReportTime } from "../../../utils/utc-time";
7
7
  import { fetchFuturesCurve, loadCurveSpot, loadCurveSpotBars, loadFuturesCurveAsOf } from "./client";
8
8
  import { basisSpotSymbol, BASIS_CONVENTION, blankBasisNote, catalogueCurveUnit, curveAsOfDate, curveBasisPercent, curveBasisRows, curveChangeText, curveContractCode, curvePrice, curveRoundedPrice, curveSpotSource, curveSpreadLabel, curveTimestamp, curveNoTradeSince, curveUnitLabel, noTradeChangeNote, normalizeCurveRoot, shortDatedBasisNote, thinSpreadNotice, unpairedContractCount, unsupportedCurveRootMessage, type ContractBasis } from "./model";
9
9
  import { COMMODITIES_VIEW_STATE } from "../commodities-energy/view-state";
10
+ import { knownGapMessage } from "../../../cli/known-gaps";
10
11
 
11
12
  /** The root a report shows when none is given. */
12
13
  const DEFAULT_ROOT = "ES";
@@ -120,7 +121,11 @@ export const futuresCurveHeadless: HeadlessPaneDefinition<"bundle"> = {
120
121
  async load(args, ctx) {
121
122
  const input = args.argument || DEFAULT_ROOT;
122
123
  const root = normalizeCurveRoot(input);
123
- if (!root) throw new Error(unsupportedCurveRootMessage(input));
124
+ if (!root) {
125
+ // A subject no source carries (`fn CTM BDI`, the Baltic Dry Index) is said with its proxies, as search says it.
126
+ const gap = typeof input === "string" ? knownGapMessage(input) : null;
127
+ throw Object.assign(new Error(unsupportedCurveRootMessage(input)), gap ? { details: gap } : {});
128
+ }
124
129
  const date = curveAsOfDate(args.options.date, new Date(), "--date");
125
130
  // The archive keeps no spot history, so a past curve has no basis.
126
131
  const spotSymbol = date ? null : basisSpotSymbol(root);
@@ -2,10 +2,9 @@ import type { PluginModule } from "../plugin-module";
2
2
  import { futuresCurveCache } from "./client";
3
3
  import { futuresCurveHeadless } from "./headless";
4
4
  import { BASIS_FORMULA, CURVE_HORIZONS, CURVE_ROOTS, curveRootForTicker, curveTitle, DEFAULT_CURVE_HORIZON, normalizeCurveRoot } from "./model";
5
- import { FuturesCurvePane } from "./pane";
6
5
 
7
6
  export const futuresCurveModule: PluginModule = {
8
- panes: [{ id: "futures-curve", name: "Futures Curve", icon: "F", component: FuturesCurvePane,
7
+ panes: [{ id: "futures-curve", name: "Futures Curve", icon: "F", get component(): typeof import("./pane").FuturesCurvePane { const { FuturesCurvePane } = require("./pane"); return FuturesCurvePane; },
9
8
  defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 98, height: 30 },
10
9
  tableExport: true, headless: futuresCurveHeadless,
11
10
  settings: (context) => ({ title: "Futures Curve Settings",
@@ -9,7 +9,7 @@ import {
9
9
  type PaneFooterSegment,
10
10
  } from "../../../components";
11
11
  import { handleRefreshKey } from "../../../components/data-table/table-pane";
12
- import { savedCopyFooterSegment } from "../../../components/layout/pane/footer";
12
+ import { ageFooterSegment, savedCopyFooterSegment } from "../../../components/layout/pane/footer";
13
13
  import { CountryFlag } from "../../../components/ui/country-flag";
14
14
  import { getSharedMarketDataCoordinator } from "../../../market-data/coordinator";
15
15
  import { useFxRatesMap } from "../../../market-data/hooks";
@@ -244,7 +244,7 @@ function FxMatrixPane({ focused, width, height }: PaneProps) {
244
244
  if (status.loading > 0) info.push({ id: "loading", parts: [{ text: "loading", tone: "muted" }] });
245
245
  if (statusText) info.push({ id: "rates", parts: [{ text: statusText, tone: status.stale || status.unknownTime || status.unavailable ? "warning" : "muted" }] });
246
246
  if (savedAt != null) info.push(savedCopyFooterSegment("updated", savedAt));
247
- else if (updatedAgo) info.push({ id: "updated", parts: [{ text: `fetched ${updatedAgo}`, tone: "muted" }] });
247
+ else if (updatedAgo) info.push(ageFooterSegment(updatedAgo, { verb: "fetched" }));
248
248
  return { info };
249
249
  }, [savedAt, status.loading, statusText, updatedAgo]);
250
250
 
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useMemo } from "react";
2
- import type { GpuBoardRow } from "../../../api-client/gpu";
2
+ import type { GpuBoardRow, GpuSettle } from "../../../api-client/gpu";
3
3
  import {
4
4
  Badge, buildSectionedRows, ChartTableHeader, DataTableView, EMPTY_TABLE_CELL, isSectionedItemRow, QueryBar,
5
5
  renderSectionedRowHeader, type DataTableCell, type DataTableColumn, type SectionedRow,
@@ -12,11 +12,11 @@ import { Box, Text, TextAttributes, useUiCapabilities } from "../../../ui";
12
12
  import { loadGpuHistory } from "./client";
13
13
  import { GpuPriceLadder } from "./ladder";
14
14
  import {
15
- gpuAvailability, gpuBoardSections, gpuChange, gpuChangeColor, gpuChangesLocked, gpuChangeWindows, gpuHeadline, gpuPrice, gpuPriceLadder, gpuShortSource, gpuSparklineSeries, gpuTime, gpuVariant,
16
- type GpuChangeKey,
15
+ GPU_SETTLE_SECTION, gpuAvailability, gpuBoardSections, gpuChange, gpuChangeColor, gpuChangesLocked, gpuChangeWindows, gpuHeadline, gpuPrice, gpuPriceLadder,
16
+ gpuSettleRows, gpuShortSource, gpuSparklineSeries, gpuTime, gpuVariant, isGpuSettleRow, type GpuBoardItem, type GpuChangeKey,
17
17
  } from "./model";
18
18
 
19
- type BoardRow = SectionedRow<GpuBoardRow>;
19
+ type BoardRow = SectionedRow<GpuBoardItem>;
20
20
  const rowKey = (row: BoardRow) => row.key;
21
21
  const EMPTY_HISTORY = new Map<string, PricePoint[]>();
22
22
  const CHANGE_LABELS: Record<GpuChangeKey, string> = { change1d: "1D", change7d: "7D", change30d: "30D" };
@@ -79,21 +79,29 @@ function boardColumns(width: number, options: { windows: GpuChangeKey[]; trend:
79
79
  ];
80
80
  }
81
81
 
82
- export function GpuBoard({ rows, models, asOf, model, setModel, selectedId, select, emptyTitle, width, height, focused, pending = false }: {
83
- rows: GpuBoardRow[]; models: readonly string[]; asOf: string | null; model: string; setModel: (value: string) => void; selectedId: string; select: (row: GpuBoardRow, open?: boolean) => void;
82
+ export function GpuBoard({ rows, settles, models, asOf, model, setModel, selectedId, select, emptyTitle, width, height, focused, pending = false }: {
83
+ rows: GpuBoardRow[]; settles?: readonly GpuSettle[]; models: readonly string[]; asOf: string | null; model: string; setModel: (value: string) => void; selectedId: string;
84
+ /** Opening a settle row selects it: its history is the GPU pages', not the History tab's. */
85
+ select: (row: GpuBoardItem, open?: boolean) => void;
84
86
  emptyTitle: string; width: number; height: number; focused: boolean;
85
87
  /** The picked GPU's free preview is still loading. */
86
88
  pending?: boolean;
87
89
  }) {
88
90
  const colors = useThemeColors();
89
91
  const sections = useMemo(() => gpuBoardSections(rows, model), [rows, model]);
90
- const items = useMemo(() => buildSectionedRows(sections.map((section) => ({ label: section.label, items: section.rows })), (row) => row.id), [sections]);
92
+ // A GPU nobody quotes has no board to lead.
93
+ const settleRows = useMemo(() => sections.length ? gpuSettleRows(settles, model) : [], [sections, settles, model]);
94
+ const items = useMemo(() => buildSectionedRows<GpuBoardItem>([
95
+ ...(settleRows.length ? [{ label: GPU_SETTLE_SECTION, items: settleRows }] : []),
96
+ ...sections.map((section) => ({ label: section.label, items: section.rows })),
97
+ ], (row) => row.id), [settleRows, sections]);
91
98
  const visible = useMemo(() => sections.flatMap((section) => section.rows), [sections]);
92
- const windows = useMemo(() => gpuChangeWindows(visible), [visible]);
99
+ const windows = useMemo(() => gpuChangeWindows([...visible, ...settleRows.map((row) => row.settle)]), [visible, settleRows]);
93
100
  const history = useHeadlineHistory(rows, model);
94
101
  const trend = [...history.values()].some((points) => points.length >= MIN_TREND_POINTS);
95
102
  const ladder = useMemo(() => model ? gpuPriceLadder(rows, model) : [], [rows, model]);
96
- const selected = visible.find((row) => row.id === selectedId) ?? visible[0] ?? null;
103
+ const selectedSettle = settleRows.find((row) => row.id === selectedId) ?? null;
104
+ const selected = selectedSettle ? null : visible.find((row) => row.id === selectedId) ?? visible[0] ?? null;
97
105
  const medium = width >= 100;
98
106
  const asOfDay = asOf?.slice(0, 10) ?? null;
99
107
  const columns = boardColumns(width, { windows, trend, range: visible.some((row) => row.stats), availability: visible.some((row) => row.availability) });
@@ -102,9 +110,10 @@ export function GpuBoard({ rows, models, asOf, model, setModel, selectedId, sele
102
110
 
103
111
  const renderCell = (item: BoardRow, column: DataTableColumn, _index: number, state: { selected: boolean }): DataTableCell => {
104
112
  if (!isSectionedItemRow(item)) return EMPTY_TABLE_CELL;
113
+ const ink = state.selected ? colors.selectedText : undefined;
114
+ if (isGpuSettleRow(item.item)) return settleCell(item.item.settle, column, ink);
105
115
  const row = item.item;
106
116
  const headline = gpuHeadline(row);
107
- const ink = state.selected ? colors.selectedText : undefined;
108
117
  switch (column.id) {
109
118
  case "source": {
110
119
  const name = gpuShortSource(row);
@@ -153,6 +162,36 @@ export function GpuBoard({ rows, models, asOf, model, setModel, selectedId, sele
153
162
  }
154
163
  };
155
164
 
165
+ /**
166
+ * A settle draws as the board's lead rows do (bold name and price), on the
167
+ * same columns: its moves are against earlier settles, and its time is the
168
+ * settle time.
169
+ */
170
+ const settleCell = (settle: GpuSettle, column: DataTableColumn, ink: string | undefined): DataTableCell => {
171
+ const settled = settle.status === "settled" && settle.pricePerGpuHr != null;
172
+ switch (column.id) {
173
+ case "source": {
174
+ const name = `Gloom ${settle.gpuModel}`;
175
+ const note = settle.kind === "correction" ? "corrected" : settled ? "settled" : "no settle";
176
+ return { text: `${name} ${note}`, content: <Box flexDirection="row" height={1} overflow="hidden">
177
+ <Text fg={ink ?? colors.textBright} attributes={TextAttributes.BOLD}>{name}</Text>
178
+ <Text fg={ink ?? colors.textDim}>{` ${note}`}</Text>
179
+ </Box> };
180
+ }
181
+ case "variant": return { text: gpuVariant(settle).join(" "), content: <GpuVariantChips row={settle} width={column.width} selected={!!ink} /> };
182
+ case "price": return settled ? { text: gpuPrice(settle.pricePerGpuHr), value: settle.pricePerGpuHr, color: colors.textBright, attributes: TextAttributes.BOLD }
183
+ : { text: "-", value: null, color: colors.textMuted };
184
+ case "unavailableChange": return { text: "-", value: null, color: colors.textMuted };
185
+ case "change1d": case "change7d": case "change30d": {
186
+ const value = settle[column.id];
187
+ return value == null ? { text: "-", value: null, color: colors.textMuted } : { text: gpuChange(value), value, color: gpuChangeColor(value, colors) };
188
+ }
189
+ case "asof": return { text: asOfDay && settle.settleAt.startsWith(asOfDay) ? settle.settleAt.slice(11, 16) : gpuTime(settle.settleAt, true),
190
+ value: settle.settleAt, color: colors.textDim };
191
+ default: return EMPTY_TABLE_CELL;
192
+ }
193
+ };
194
+
156
195
  const query = <GpuModelQuery models={models} model={model} setModel={setModel} width={width} />;
157
196
  return <DataTableView<BoardRow> columns={columns} items={items} rootWidth={width} rootHeight={height} focused={focused}
158
197
  status={pending && !items.length ? { loading: true, subject: "GPU rental prices" } : undefined}
@@ -161,9 +200,9 @@ export function GpuBoard({ rows, models, asOf, model, setModel, selectedId, sele
161
200
  render: (size) => <GpuPriceLadder rows={ladder} width={size.width} height={size.height}
162
201
  selected={selected && selected.gpuModel === model && selected.providerClass !== "aggregate" ? { basis: selected.basis, price: selected.pricePerGpuHr } : null} /> }} />
163
202
  : query}
164
- selection={{ kind: "id", selectedId: selected?.id ?? null, getId: rowKey,
203
+ selection={{ kind: "id", selectedId: selectedSettle?.id ?? selected?.id ?? null, getId: rowKey,
165
204
  onChange: (_id, row) => { if (row && isSectionedItemRow(row)) select(row.item); } }}
166
205
  isNavigable={isSectionedItemRow} renderSectionHeader={renderSectionedRowHeader}
167
- getItemKey={rowKey} onActivate={(row) => { if (isSectionedItemRow(row)) select(row.item, true); }} sortColumnId={null} sortDirection="asc"
206
+ getItemKey={rowKey} onActivate={(row) => { if (isSectionedItemRow(row)) select(row.item, !isGpuSettleRow(row.item)); }} sortColumnId={null} sortDirection="asc"
168
207
  selectedTextOverridesCellColor renderCell={renderCell} emptyStateTitle={emptyTitle} />;
169
208
  }
@@ -1,6 +1,7 @@
1
1
  import { resolvePlanAccess } from "../../../api-client/plan-rules";
2
+ import { sourceDidNotAnswer } from "../../../api-client/errors";
2
3
  import { apiClient } from "../../../api-client";
3
- import type { GpuBoardPayload, GpuEventsPayload, GpuHistoryPayload, GpuHistoryQuery, GpuObservation } from "../../../api-client/gpu";
4
+ import type { GpuBoardPayload, GpuEventsPayload, GpuHistoryPayload, GpuHistoryQuery, GpuObservation, GpuSettle } from "../../../api-client/gpu";
4
5
  import { createPluginCache } from "../../../data/plugin-cache";
5
6
  import type { DataProvider } from "../../../types/data-provider";
6
7
  import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
@@ -58,6 +59,17 @@ function validObservation(row: unknown): row is GpuObservation {
58
59
  && (stats.constituentPrices === undefined || record(stats.constituentPrices) && Object.values(stats.constituentPrices).every((value) => finite(value) && value > 0)));
59
60
  }
60
61
 
62
+ function validSettle(row: unknown): row is GpuSettle {
63
+ if (!record(row)) return false;
64
+ const settled = row.status === "settled";
65
+ return [row.id, row.gpuModel, row.formFactor, row.label].every((value) => typeof value === "string" && value.length > 0)
66
+ && typeof row.date === "string" && /^\d{4}-\d{2}-\d{2}$/.test(row.date) && instant(row.settleAt) && instant(row.publishedAt)
67
+ && (row.memoryGb === null || finite(row.memoryGb) && row.memoryGb > 0)
68
+ && (settled ? finite(row.pricePerGpuHr) && row.pricePerGpuHr > 0 : row.status === "no-settle" && row.pricePerGpuHr === null)
69
+ && row.currency === "USD" && ["settle", "correction"].includes(row.kind) && nullableText(row.corrects)
70
+ && [row.change1d, row.change7d, row.change30d].every(nullableNumber);
71
+ }
72
+
61
73
  function validateBoard(payload: GpuBoardPayload): GpuBoardPayload {
62
74
  if (!record(payload) || !validAccess(payload.access) || !instant(payload.generatedAt) || !nullableInstant(payload.asOf) || typeof payload.stale !== "boolean"
63
75
  || !["available", "partial", "unavailable"].includes(payload.status) || !strings(payload.gaps)
@@ -65,7 +77,9 @@ function validateBoard(payload: GpuBoardPayload): GpuBoardPayload {
65
77
  && typeof row.id === "string" && row.id === `${row.source}:${row.skuKey}` && typeof row.label === "string" && typeof row.sourceLabel === "string"
66
78
  && [row.change1d, row.change7d, row.change30d].every(nullableNumber) && typeof row.stale === "boolean" && nullableText(row.lastError))
67
79
  || new Set(payload.rows.map((row) => row.id)).size !== payload.rows.length) throw new Error("The server returned invalid GPU rental prices.");
68
- return payload;
80
+ // The settles ride along with the board: one that does not read right is left out, never the board with it.
81
+ return payload.settles === undefined ? payload
82
+ : { ...payload, settles: Array.isArray(payload.settles) ? payload.settles.filter(validSettle) : [] };
69
83
  }
70
84
 
71
85
  function validateHistory(payload: GpuHistoryPayload): GpuHistoryPayload {
@@ -94,7 +108,9 @@ export async function fetchGpuBoard(client: Pick<GpuApi, "getCloudGpuBoard"> = a
94
108
  try { payload = await client.getCloudGpuBoard(); }
95
109
  catch (error) { throw unavailableOnServer(error, GPU_NOT_AVAILABLE, [404, 503]); }
96
110
  const board = validateBoard(payload);
97
- if (board.status === "unavailable" || !board.rows.length) throw new Error(GPU_NOT_AVAILABLE);
111
+ // The server's source did not answer this time: a saved copy answers, as it does a 5xx.
112
+ if (board.status === "unavailable") throw sourceDidNotAnswer(GPU_NOT_AVAILABLE);
113
+ if (!board.rows.length) throw new Error(GPU_NOT_AVAILABLE);
98
114
  return board;
99
115
  }
100
116
 
@@ -14,7 +14,8 @@ import { Box, TextAttributes, useUiCapabilities } from "../../../ui";
14
14
  import { ClippedText } from "../../../components/ui/display";
15
15
  import { publicTickerKey } from "../../../utils/exchanges";
16
16
  import { displayWidth } from "../../../utils/format";
17
- import { quoteBoardFooterInfo, quoteBoardStatus, renderQuoteBoardCell, useQuoteBoard } from "../shared/use-quote-board";
17
+ import { boardQuotes, quoteBoardFooterInfo, quoteBoardStatus, renderQuoteBoardCell, useQuoteBoard } from "../shared/use-quote-board";
18
+ import { useQuoteDelayFooter } from "../shared/cloud-upgrade";
18
19
  import { GpuModelQuery } from "./board";
19
20
  import { loadGpuEquityHistory } from "./client";
20
21
  import { GPU_EQUITIES, GPU_EQUITY_GROUPS, gpuChange, gpuChangeColor, gpuChangesLocked, gpuEquityGap, gpuEquityRows, gpuPrice, gpuShortSource, gpuVariant, type GpuEquityRow } from "./model";
@@ -62,8 +63,10 @@ export function GpuEquities({ board, models, locked, model, setModel, reloadBoar
62
63
  items: rows.filter((row) => (group.roles as readonly string[]).includes(row.role)) })), (row) => row.symbol), [rows]);
63
64
  const historyMap = useMemo(() => new Map(history.data?.map((entry) => [entry.symbol, entry]) ?? []), [history.data]);
64
65
  const ends = [...new Set(history.data?.flatMap((entry) => entry.asOf ? [entry.asOf] : []) ?? [])].sort();
66
+ const quoteDelay = useQuoteDelayFooter(boardQuotes(quotes), "gpu-equities-delay");
65
67
  usePaneFooter("gpu:equity-status", () => ({ info: [...quoteBoardFooterInfo(quoteBoardStatus(quotes)),
66
- ...(ends.length === 1 ? [{ id: "five-asof", parts: [{ text: `5D closes through ${ends[0]}`, tone: "muted" as const }] }] : [])] }), [quotes, ends.join(",")]);
68
+ ...(ends.length === 1 ? [{ id: "five-asof", parts: [{ text: `5D closes through ${ends[0]}`, tone: "muted" as const }] }] : []),
69
+ ...(quoteDelay ? [quoteDelay] : [])] }), [quotes, quoteDelay, ends.join(",")]);
67
70
  usePaneNoticeFooter({ registrationId: "gpu:equity-notices", focused,
68
71
  notices: [...(history.error ? [history.error] : []), ...(history.data?.flatMap((entry) => entry.error ? [entry.error] : []) ?? [])] });
69
72
  const dataRef = useRef({ quotes, historyMap });
@@ -4,7 +4,7 @@ import { formatUtcTime, parseReportTime } from "../../../utils/utc-time";
4
4
  import { fetchGpuBoard, fetchGpuEvents, fetchGpuHistory, fetchGpuModelPreviews, gpuPreviewModels, loadGpuEquityHistory } from "./client";
5
5
  import {
6
6
  GPU_TABS, gpuBasisLabel, gpuChange, gpuChangesLocked, gpuEquityGap, gpuEquityModels, gpuEquityRows, gpuEventDate, gpuFamilies, gpuFamily,
7
- gpuLabel, gpuNoMatch, gpuPrice, gpuProvenanceLabel, gpuRows, gpuSource, gpuTab,
7
+ gpuLabel, gpuNoMatch, gpuPrice, gpuProvenanceLabel, gpuRows, gpuSettleRows, gpuSource, gpuTab, GPU_SETTLE_SECTION,
8
8
  } from "./model";
9
9
  import { quoteFreshnessFields } from "../shared/report-freshness";
10
10
  import { countOf, freePreviewNote } from "../shared/free-preview";
@@ -33,6 +33,13 @@ const columns = [
33
33
  { key: "observedAt", header: "As of", format: minute },
34
34
  ];
35
35
 
36
+ const settleColumns = [
37
+ { key: "gpu", header: "GPU" }, { key: "status", header: "Settle" },
38
+ { key: "price", header: "$/GPU-hr", align: "right" as const, format: price },
39
+ { key: "change1d", header: "1D", align: "right" as const, format: percent }, { key: "change7d", header: "7D", align: "right" as const, format: percent },
40
+ { key: "change30d", header: "30D", align: "right" as const, format: percent }, { key: "settleAt", header: "Settled", format: minute },
41
+ ];
42
+
36
43
  export const gpuHeadless: HeadlessPaneDefinition<"bundle"> = {
37
44
  // Observed rental prices; the Equities tab's quotes keep their market line.
38
45
  shape: "bundle", freshness: { dataset: true }, argument: { kind: "free-text", optional: true, placeholder: "GPU", description: "GPU model, such as H100, h-100 or B200." },
@@ -41,6 +48,7 @@ export const gpuHeadless: HeadlessPaneDefinition<"bundle"> = {
41
48
  limitations: ["List prices, provider-declared spot and asks are distinct bases.",
42
49
  "Hyperscaler and Neocloud rows are list indexes: the median of the providers' list prices linked over time, so a provider joining or leaving causes no jump; sample.rawMedian is the raw median of today's providers and can differ slightly.",
43
50
  "1D/7D/30D need sufficient observation history; unavailable changes remain null.",
51
+ "The Gloom daily settle is Gloom's official price per GPU and form factor, set at 20:00 UTC and never revised; a day with too few prices has no settle.",
44
52
  "Free preview: three board rows, or the latest rows of the GPU asked for; Pro unlocks every provider, the moves and the history."] },
45
53
  describe: "GPU rental prices in USD per GPU-hour",
46
54
  async load(args, ctx) {
@@ -113,7 +121,11 @@ export const gpuHeadless: HeadlessPaneDefinition<"bundle"> = {
113
121
  }
114
122
  const boardRows = gpuRows(rows, model).map((row) => ({ ...observationRow(row), change1d: row.change1d, change7d: row.change7d,
115
123
  change30d: row.change30d, stale: row.stale, sample: row.stats ?? null, ...(gpuChangesLocked(row) ? { changesLocked: true } : {}) }));
116
- return { sections: [{ title: "GPU rental prices", columns, rows: boardRows, empty: `No ${model ? `${model} ` : ""}rental prices have been observed yet.` }],
124
+ const settles = gpuSettleRows(board.settles, model).map(({ settle }) => ({ gpu: gpuLabel(settle), status: settle.status === "settled" ? "settled" : "no settle",
125
+ price: settle.pricePerGpuHr, change1d: settle.change1d, change7d: settle.change7d, change30d: settle.change30d, settleAt: settle.settleAt,
126
+ date: settle.date, ...(settle.kind === "correction" ? { corrected: true } : {}) }));
127
+ return { sections: [...(settles.length ? [{ title: GPU_SETTLE_SECTION, columns: settleColumns, rows: settles }] : []),
128
+ { title: "GPU rental prices", columns, rows: boardRows, empty: `No ${model ? `${model} ` : ""}rental prices have been observed yet.` }],
117
129
  freshness, complete: !board.access?.locked, metadata, errors: [...locks, ...board.gaps] };
118
130
  },
119
131
  };
@@ -2,11 +2,10 @@ import type { PluginModule } from "../plugin-module";
2
2
  import { gpuBoardCache, gpuEventsCache, gpuHistoryCache } from "./client";
3
3
  import { gpuHeadless } from "./headless";
4
4
  import { GPU_MODELS, gpuArgument } from "./model";
5
- import { GpuPane } from "./pane";
6
5
  import { GPU_SERIES } from "./series";
7
6
 
8
7
  export const gpuModule: PluginModule = {
9
- panes: [{ id: "gpu", name: "GPU Rental Prices", icon: "G", component: GpuPane, defaultPosition: "right", defaultMode: "floating",
8
+ panes: [{ id: "gpu", name: "GPU Rental Prices", icon: "G", get component(): typeof import("./pane").GpuPane { const { GpuPane } = require("./pane"); return GpuPane; }, defaultPosition: "right", defaultMode: "floating",
10
9
  defaultFloatingSize: { width: 120, height: 34 }, tableExport: true, headless: gpuHeadless }],
11
10
  paneTemplates: [{ id: "gpu-pane", paneId: "gpu", label: "GPU Rental Prices",
12
11
  description: "Pro GPU rental list prices, provider-declared spot and marketplace asks, with sourced price history and related equities. Free preview available.",
@@ -1,4 +1,4 @@
1
- import type { GpuBasis, GpuBoardRow, GpuEvent, GpuObservation } from "../../../api-client/gpu";
1
+ import type { GpuBasis, GpuBoardRow, GpuEvent, GpuObservation, GpuSettle } from "../../../api-client/gpu";
2
2
  import { staticSeries } from "../../../components/chart/static/series";
3
3
  import { SERIES_COLORS } from "../../../theme/series-colors";
4
4
  import type { ResolvedSeries } from "../../../time-series/types";
@@ -175,9 +175,27 @@ export function gpuBoardSections(rows: readonly GpuBoardRow[], model = ""): GpuB
175
175
  return sections;
176
176
  }
177
177
 
178
+ /** Gloom's daily settle as a board row: it leads the board, above every quote. */
179
+ export interface GpuSettleRow { kind: "settle"; id: string; settle: GpuSettle }
180
+ export type GpuBoardItem = GpuBoardRow | GpuSettleRow;
181
+ export const isGpuSettleRow = (row: GpuBoardItem): row is GpuSettleRow => (row as Partial<GpuSettleRow>).kind === "settle";
182
+ export const GPU_SETTLE_SECTION = "Gloom daily settle";
183
+
184
+ /**
185
+ * The settle rows of the board: every form factor of the picked GPU, settled
186
+ * or not, so a day with too few prices says so; across all GPUs only the ones
187
+ * settled. GPUs in board order, SXM first.
188
+ */
189
+ export function gpuSettleRows(settles: readonly GpuSettle[] | undefined, model = ""): GpuSettleRow[] {
190
+ return (settles ?? []).filter((settle) => model ? settle.gpuModel === model : settle.status === "settled")
191
+ .sort((a, b) => gpuModelOrder(a.gpuModel, b.gpuModel) || Number(b.formFactor === "SXM") - Number(a.formFactor === "SXM")
192
+ || a.formFactor.localeCompare(b.formFactor))
193
+ .map((settle) => ({ kind: "settle", id: `settle:${settle.gpuModel}:${settle.formFactor}`, settle }));
194
+ }
195
+
178
196
  export type GpuChangeKey = "change1d" | "change7d" | "change30d";
179
197
  /** Change windows with at least one figure; missing windows remain unavailable. */
180
- export const gpuChangeWindows = (rows: readonly GpuBoardRow[]): GpuChangeKey[] =>
198
+ export const gpuChangeWindows = (rows: readonly Pick<GpuBoardRow, GpuChangeKey>[]): GpuChangeKey[] =>
181
199
  (["change1d", "change7d", "change30d"] as const).filter((key) => rows.some((row) => row[key] != null));
182
200
 
183
201
  /**
@@ -71,7 +71,7 @@ function GpuView({ width, height, focused }: PaneProps) {
71
71
  {strip}
72
72
  <PaneStatusBody loading={resource.loading && !data} error={!data && !notAvailable ? resource.error : null}
73
73
  empty={notAvailable} emptyTitle={GPU_NOT_AVAILABLE} subject="GPU rental prices">
74
- {data ? tab === "board" ? <GpuBoard rows={rows} models={models} asOf={data.asOf} model={activeModel} setModel={setModel} selectedId={selectedId} emptyTitle={emptyTitle}
74
+ {data ? tab === "board" ? <GpuBoard rows={rows} settles={data.settles} models={models} asOf={data.asOf} model={activeModel} setModel={setModel} selectedId={selectedId} emptyTitle={emptyTitle}
75
75
  select={(row, open) => { setSelectedId(row.id); if (open) setTab("history"); }} {...body} />
76
76
  : tab === "history" ? <GpuHistory rows={rows} models={models} model={unknownModel ? "" : activeModel} setModel={setModel} selectedId={selectedId} onSelect={setSelectedId} reloadBoard={reloadBoard} {...body} />
77
77
  : tab === "changes" ? <GpuChanges models={models} model={unknownModel ? "" : activeModel} setModel={setModel} reloadBoard={reloadBoard} {...body} />
@@ -1,5 +1,5 @@
1
1
  import type { SeriesDefinition } from "../../../series/types";
2
- import { cachedSeriesWarning } from "../shared/series-warning";
2
+ import { cachedSeriesNote } from "../shared/series-warning";
3
3
  import { loadGpuBoard, loadGpuHistory } from "./client";
4
4
  import { GPU_MODELS, gpuListIndex } from "./model";
5
5
 
@@ -27,12 +27,11 @@ export const GPU_SERIES: readonly SeriesDefinition[] = GPU_MODELS.map((model): S
27
27
  if (!index) throw new Error(`No ${model} list index is listed yet.`);
28
28
  const history = await loadGpuHistory(index.id);
29
29
  if (history.payload.access?.locked) throw new Error(GPU_PRO_ONLY);
30
- const warning = cachedSeriesWarning(history);
31
30
  return {
32
31
  points: history.payload.points
33
32
  .filter((point) => `${point.source}:${point.skuKey}` === index.id)
34
33
  .map((point) => ({ date: point.observedAt, value: point.pricePerGpuHr })),
35
- ...(warning ? { warning } : {}),
34
+ ...cachedSeriesNote(history),
36
35
  };
37
36
  },
38
37
  }));
@@ -425,7 +425,7 @@ export const helpModule: PluginModule = {
425
425
  name: "Help",
426
426
  icon: "?",
427
427
  component: HelpPane,
428
- reportFreshness: { status: "not-a-feed", basis: "documentation" },
428
+ reportFreshness: { status: "not-a-feed", basis: "documentation", local: true },
429
429
  defaultPosition: "right",
430
430
  defaultMode: "floating",
431
431
  defaultFloatingSize: { width: 88, height: 32 },
@@ -11,6 +11,7 @@ import {
11
11
  } from "../../../components";
12
12
  import { useCompanyDataFooter } from "../../../components/layout/pane/status-footer";
13
13
  import { handleRefreshKey, loadingErrorFooterInfo } from "../../../components/data-table/table-pane";
14
+ import { STALE_SEGMENT } from "../../../components/layout/pane/status-footer";
14
15
  import type { IssuerListingParams } from "../../../api-client/paths";
15
16
  import { useAsyncResource } from "../../../react/async-resource";
16
17
  import { useAutoRefresh } from "../../../react/auto-refresh";
@@ -43,8 +44,6 @@ import {
43
44
  import type { Holder13FMatch } from "./thirteenf-match";
44
45
  import type { HolderRow } from "./types";
45
46
 
46
- const STALE_SEGMENT = { id: "stale", parts: [{ text: "stale", tone: "warning" as const }] };
47
-
48
47
  /**
49
48
  * A table line: a filer's row, or the line beneath it giving the same shares
50
49
  * against today's count when the report's base is no longer today's.
@@ -7,7 +7,7 @@ import { nonUsHolderCaveat } from "./report-header";
7
7
  /**
8
8
  * A US listing's 13F holders from the SEC 13F holder index, the list `fn 13F`
9
9
  * pages through: every filer that reported the security for the latest
10
- * quarter, largest first, 25 to a page. HDS shows them in the units of the
10
+ * quarter, largest first. HDS shows them in the units of the
11
11
  * source's own short list (each position at the latest price, the stake as a
12
12
  * share of the class that list divides by), and keeps that short list for a
13
13
  * listing abroad and whenever the index cannot be read.
@@ -96,26 +96,35 @@ export function indexedHolderPage(holdings: TickerHoldings): IndexedHolderList {
96
96
  };
97
97
  }
98
98
 
99
- /** Rows the service gives a page, as `fn 13F` asks for them. */
100
- const INDEX_PAGE_ROWS = 25;
101
99
  /**
102
- * Pages one list reads at most: twice what the most rows take, for pages
103
- * whose options and exits leave fewer holders, and never without end.
100
+ * Rows the pane asks a page for. The service reads a stored quarter's holders
101
+ * by key and gives up to 200 a page; one that builds the page from the funds'
102
+ * filings (an older one, or a quarter not stored yet) gives 25, and the next
103
+ * page starts where it stopped.
104
104
  */
105
- const INDEX_PAGES_MAX = Math.ceil(HOLDER_ROWS_MAX / INDEX_PAGE_ROWS) * 2;
105
+ const INDEX_PAGE_ROWS = 100;
106
+ /** Rows the service gives a page at most, as one that builds it from the funds' filings does. */
107
+ const INDEX_PAGE_ROWS_MIN = 25;
108
+ /**
109
+ * Pages one list reads at most: twice what the most rows take at the
110
+ * smallest page, for pages whose options and exits leave fewer holders, and
111
+ * never without end.
112
+ */
113
+ const INDEX_PAGES_MAX = Math.ceil(HOLDER_ROWS_MAX / INDEX_PAGE_ROWS_MIN) * 2;
106
114
 
107
115
  /** The client the pages come through; headless reports pass their own. */
108
116
  export type IndexedHolderClient = Pick<typeof apiClient, "getCloudSec13F">;
109
117
 
110
- /** One page of 25, as `fn 13F` asks for it, so the two share the service's copy. */
118
+ /** One page of the holders from `offset`, `rows` asked (the service may give fewer). */
111
119
  export async function loadIndexedHolderPage(
112
120
  ticker: string,
113
121
  offset: number,
114
122
  signal?: AbortSignal,
115
123
  client: IndexedHolderClient = apiClient,
124
+ rows = INDEX_PAGE_ROWS,
116
125
  ): Promise<IndexedHolderList> {
117
126
  signal?.throwIfAborted();
118
- const holdings = await client.getCloudSec13F("ticker-holdings", { tickers: ticker, offset, limit: INDEX_PAGE_ROWS }, { signal }) as TickerHoldings;
127
+ const holdings = await client.getCloudSec13F("ticker-holdings", { tickers: ticker, offset, limit: rows }, { signal }) as TickerHoldings;
119
128
  signal?.throwIfAborted();
120
129
  if (!Array.isArray(holdings?.rows) || typeof holdings.period !== "string") throw new Error(`13F holders of ${ticker} are unreadable`);
121
130
  const page = indexedHolderPage(holdings);
@@ -132,8 +141,9 @@ function appendIndexedHolders(current: IndexedHolderList, page: IndexedHolderLis
132
141
 
133
142
  /**
134
143
  * The largest `limit` holders, paging as far as that takes, never more than
135
- * `INDEX_PAGES_MAX` pages. A later page that fails keeps the holders already
136
- * loaded, marked partial; only a first page that fails leaves the source's list.
144
+ * `INDEX_PAGES_MAX` pages: one request for a stored quarter. A later page
145
+ * that fails keeps the holders already loaded, marked partial; only a first
146
+ * page that fails leaves the source's list.
137
147
  */
138
148
  export async function loadIndexedHolders(
139
149
  ticker: string,
@@ -141,12 +151,14 @@ export async function loadIndexedHolders(
141
151
  signal?: AbortSignal,
142
152
  client: IndexedHolderClient = apiClient,
143
153
  ): Promise<IndexedHolderList> {
144
- let list = await loadIndexedHolderPage(ticker, 0, signal, client);
154
+ // Asked for in full: exits and options can leave a page with fewer holders than rows.
155
+ const rows = Math.min(HOLDER_ROWS_MAX, Math.max(limit, 1));
156
+ let list = await loadIndexedHolderPage(ticker, 0, signal, client, rows);
145
157
  for (let pages = 1; list.hasMore && list.rows.length < limit && pages < INDEX_PAGES_MAX; pages += 1) {
146
158
  const offset = list.nextOffset;
147
159
  let page: IndexedHolderList;
148
160
  try {
149
- page = await loadIndexedHolderPage(ticker, offset, signal, client);
161
+ page = await loadIndexedHolderPage(ticker, offset, signal, client, rows);
150
162
  } catch (error) {
151
163
  if (signal?.aborted) throw error;
152
164
  logFailure("holders.index", "Read a later page of the 13F holder index failed", null)(error);
@@ -2,7 +2,6 @@ import type { PluginModule } from "../plugin-module";
2
2
  import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
3
3
  import { beneficialOwnersCache } from "./beneficial-client";
4
4
  import { holdersHeadless } from "./headless";
5
- import { HoldersView } from "./pane";
6
5
 
7
6
  export const holdersModule: PluginModule = {
8
7
  setup(ctx) {
@@ -11,7 +10,7 @@ export const holdersModule: PluginModule = {
11
10
  id: "holders",
12
11
  name: "Holders",
13
12
  order: 42,
14
- component: HoldersView,
13
+ get component(): typeof import("./pane").HoldersView { const { HoldersView } = require("./pane"); return HoldersView; },
15
14
  instruments: ["equity"],
16
15
  });
17
16
  },
@@ -24,7 +23,7 @@ export const holdersModule: PluginModule = {
24
23
  id: "holders",
25
24
  name: "Holders",
26
25
  icon: "H",
27
- component: HoldersView,
26
+ get component(): typeof import("./pane").HoldersView { const { HoldersView } = require("./pane"); return HoldersView; },
28
27
  defaultPosition: "right",
29
28
  tickerFollower: true,
30
29
  defaultMode: "floating",
@@ -3,7 +3,6 @@ import { commandRequestParams } from "../shared/command-tab";
3
3
  import { ipoCalendarCache } from "./client";
4
4
  import { ipoCalendarHeadless } from "./headless";
5
5
  import { IPO_CALENDAR_PANE_ID } from "./model";
6
- import { IpoCalendarPane } from "./pane";
7
6
 
8
7
  /**
9
8
  * `IPO japan` opens the calendar on that search, and so does a later `IPO europe`: one pane, the open one
@@ -44,7 +43,7 @@ export const ipoCalendarPlugin: GloomPlugin = {
44
43
  id: IPO_CALENDAR_PANE_ID,
45
44
  name: "IPO Calendar",
46
45
  icon: "I",
47
- component: IpoCalendarPane,
46
+ get component(): typeof import("./pane").IpoCalendarPane { const { IpoCalendarPane } = require("./pane"); return IpoCalendarPane; },
48
47
  defaultPosition: "right",
49
48
  defaultMode: "floating",
50
49
  defaultFloatingSize: { width: 110, height: 28 },
@@ -3,14 +3,13 @@ import type { PluginModule } from "../plugin-module";
3
3
  import { IV_SERIES } from "./series";
4
4
  import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
5
5
  import { ivHistoryHeadless, ivScreenHeadless } from "./headless";
6
- import { IvHistoryPane } from "./pane";
7
6
  import { IvScreenPane, VCA_SCOPE_OPTIONS } from "./screen-pane";
8
7
 
9
8
 
10
9
  export const ivHistoryModule: PluginModule = {
11
10
  series: IV_SERIES,
12
11
  panes: [{
13
- id: "iv-history", name: "Implied Volatility History", icon: "V", component: IvHistoryPane,
12
+ id: "iv-history", name: "Implied Volatility History", icon: "V", get component(): typeof import("./pane").IvHistoryPane { const { IvHistoryPane } = require("./pane"); return IvHistoryPane; },
14
13
  defaultPosition: "right", tickerFollower: true, defaultMode: "floating", defaultFloatingSize: { width: 118, height: 36 },
15
14
  tableExport: true,
16
15
  settings: { title: "Implied Volatility History", fields: [