@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
@@ -0,0 +1,143 @@
1
+ import { apiClient } from "../../../api-client";
2
+ import { ApiRequestError } from "../../../api-client/errors";
3
+ import type { BitcoinNetworkDailyValue, BitcoinNetworkPayload } from "../../../api-client/crypto-markets";
4
+ import { createPluginCache } from "../../../data/plugin-cache";
5
+ import type { SeriesAttribution, SeriesDefinition, SeriesPoint } from "../../../series/types";
6
+ import { isFiniteNumber } from "../../../utils/guards";
7
+ import { loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
8
+ import { cachedSeriesNote } from "../shared/series-warning";
9
+
10
+ const bitcoinNetworkCache = createPluginCache<BitcoinNetworkPayload>({
11
+ kind: "bitcoin-network",
12
+ source: "gloom-cloud",
13
+ schemaVersion: 1,
14
+ // The server reads the estimate every half hour and the daily figures every six hours.
15
+ policy: { staleMs: 30 * 60_000, expireMs: 30 * 86_400_000 },
16
+ });
17
+
18
+ export const BITCOIN_NETWORK_CACHES = [bitcoinNetworkCache] as const;
19
+
20
+ const MAX_DAYS = 20_000;
21
+ const date = (value: unknown): value is string => typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value);
22
+ const instant = (value: unknown): value is string => typeof value === "string" && Number.isFinite(Date.parse(value));
23
+ const daily = (rows: unknown): rows is BitcoinNetworkDailyValue[] => Array.isArray(rows) && rows.length <= MAX_DAYS
24
+ && rows.every((row) => !!row && date((row as BitcoinNetworkDailyValue).date) && isFiniteNumber((row as BitcoinNetworkDailyValue).value));
25
+
26
+ function validateBitcoinNetwork(data: BitcoinNetworkPayload): BitcoinNetworkPayload {
27
+ const retarget = data?.retarget;
28
+ if (
29
+ !data
30
+ || data.version !== 1
31
+ || !instant(data.generatedAt)
32
+ || typeof data.source?.name !== "string" || !data.source.name.trim()
33
+ || !["available", "stale", "unavailable"].includes(data.status)
34
+ || !daily(data.hashrate)
35
+ || !daily(data.hashprice)
36
+ || !daily(data.retargetHistory)
37
+ || !Array.isArray(data.difficulty)
38
+ || !data.difficulty.every((row) => !!row && date(row.date) && isFiniteNumber(row.difficulty) && row.difficulty > 0)
39
+ || (retarget !== null && (!retarget || !isFiniteNumber(retarget.changePercent) || !instant(retarget.estimatedAt) || !instant(retarget.readAt)))
40
+ || !Array.isArray(data.warnings)
41
+ ) {
42
+ throw new Error("The server returned invalid bitcoin network data");
43
+ }
44
+ return data;
45
+ }
46
+
47
+ async function fetchBitcoinNetwork(client: Pick<typeof apiClient, "getCloudBitcoinNetwork"> = apiClient): Promise<BitcoinNetworkPayload> {
48
+ try {
49
+ return validateBitcoinNetwork(await client.getCloudBitcoinNetwork());
50
+ } catch (error) {
51
+ if (error instanceof ApiRequestError && error.status === 503) throw unavailableOnServer(error, "Bitcoin network data is temporarily unavailable", [503]);
52
+ throw unavailableOnServer(error, "Bitcoin network data is not available yet.");
53
+ }
54
+ }
55
+
56
+ function loadBitcoinNetwork() {
57
+ return loadCloudResource(bitcoinNetworkCache, "all", () => fetchBitcoinNetwork(), { validate: validateBitcoinNetwork });
58
+ }
59
+
60
+ const points = (rows: readonly BitcoinNetworkDailyValue[]): SeriesPoint[] =>
61
+ rows.map((row) => ({ date: Date.parse(`${row.date}T00:00:00Z`), value: row.value }));
62
+
63
+ const TAGS = ["bitcoin", "BTC", "BTC-USD", "mining", "miner", "miners", "bitcoin mining", "network", "on-chain"];
64
+
65
+ /**
66
+ * A series of the network board: its points, cited as the server names its
67
+ * source, with the board's warnings about this figure (`concerns`: "Hash
68
+ * rate, difficulty and hashprice could not refresh" is three series', "The
69
+ * retarget estimate is unavailable" the estimate's alone) and the
70
+ * cached-copy note.
71
+ */
72
+ function networkSeries(
73
+ definition: Omit<Extract<SeriesDefinition, { load: unknown }>, "load" | "frequency" | "group">
74
+ & { frequency?: SeriesDefinition["frequency"] },
75
+ read: (payload: BitcoinNetworkPayload) => SeriesPoint[],
76
+ { concerns, derived }: { concerns: RegExp; derived?: string },
77
+ ): SeriesDefinition {
78
+ return {
79
+ frequency: "daily",
80
+ group: "Crypto",
81
+ refreshMs: 30 * 60_000,
82
+ ...definition,
83
+ load: async () => {
84
+ const resource = await loadBitcoinNetwork();
85
+ const found = read(resource.payload);
86
+ if (!found.length) throw new Error(`${definition.name} is not available yet.`);
87
+ const note = cachedSeriesNote(resource);
88
+ const own = resource.payload.warnings.filter((line) => concerns.test(line)).join(" ");
89
+ const warning = note.warning ?? (own || undefined);
90
+ const source = resource.payload.source.name.trim();
91
+ const attribution: SeriesAttribution = { source: derived ? `${source}, ${derived}` : source };
92
+ return { points: found, attribution, ...note, ...warning ? { warning } : {} };
93
+ },
94
+ } as SeriesDefinition;
95
+ }
96
+
97
+ /**
98
+ * The bitcoin network for `fn G`: hash rate, difficulty, the next
99
+ * retarget's estimate and hashprice. `fn G hashrate`, `difficulty`,
100
+ * `retarget` and `hashprice` chart them by their aliases; `catalog
101
+ * hashrate`, `hashprice`, `difficulty`, `mining` and `miner` find them by
102
+ * their tags.
103
+ */
104
+ export const BITCOIN_NETWORK_SERIES: readonly SeriesDefinition[] = [
105
+ networkSeries({
106
+ id: "bitcoin:hashrate",
107
+ name: "Bitcoin network hash rate",
108
+ unit: "EH/s",
109
+ tags: [...TAGS, "hashrate", "hash rate"],
110
+ aliases: ["hashrate"],
111
+ description: "The bitcoin network's hash rate in exahashes per second, each UTC day's average, estimated from the blocks found; the newest day is the day so far.",
112
+ }, (payload) => points(payload.hashrate), { concerns: /\bhash rate\b/i }),
113
+ networkSeries({
114
+ id: "bitcoin:difficulty",
115
+ name: "Bitcoin mining difficulty (trillions)",
116
+ unit: "number",
117
+ frequency: "irregular",
118
+ style: "step",
119
+ tags: [...TAGS, "difficulty", "retarget", "difficulty adjustment"],
120
+ aliases: ["difficulty"],
121
+ description: "The difficulty each retarget set, every 2,016 blocks, in trillions, dated by the block that set it.",
122
+ }, (payload) => payload.difficulty.map((row) => ({ date: Date.parse(`${row.date}T00:00:00Z`), value: Number((row.difficulty / 1e12).toFixed(4)) })),
123
+ { concerns: /\bdifficulty\b/i }),
124
+ networkSeries({
125
+ id: "bitcoin:retarget-estimate",
126
+ name: "Bitcoin next difficulty retarget estimate",
127
+ unit: "%",
128
+ tags: [...TAGS, "difficulty", "retarget", "difficulty adjustment", "estimate"],
129
+ aliases: ["retarget"],
130
+ description: "The estimated change in difficulty at the next retarget, in percent, as read each UTC day since Gloom began keeping it in October 2026.",
131
+ }, (payload) => points(payload.retargetHistory), { concerns: /\bretarget\b/i }),
132
+ networkSeries({
133
+ id: "bitcoin:hashprice",
134
+ name: "Bitcoin hashprice (derived)",
135
+ unit: "USD/PH/s/day",
136
+ tags: [...TAGS, "hashprice", "hash price", "miner revenue"],
137
+ aliases: ["hashprice"],
138
+ description: "Derived, not quoted: what 1 PH/s is expected to earn in a day, in USD, at the day's difficulty, average block reward (subsidy and fees) and BTC price.",
139
+ }, (payload) => points(payload.hashprice), {
140
+ concerns: /\bhashprice\b/i,
141
+ derived: "derived from the block rewards, the BTC price and the difficulty",
142
+ }),
143
+ ];
@@ -145,10 +145,10 @@ export async function fetchCryptoMarkets(
145
145
  try {
146
146
  return validateCryptoMarkets(await client.getCloudCryptoMarkets());
147
147
  } catch (error) {
148
- if (error instanceof ApiRequestError && error.status === 503) {
149
- throw new Error("Crypto prices are temporarily unavailable");
150
- }
151
- throw unavailableOnServer(error, "Crypto markets are not available yet.");
148
+ // Each keeps its status: a 503 passes with time and a saved copy answers it; a 404 does not.
149
+ throw error instanceof ApiRequestError && error.status === 503
150
+ ? unavailableOnServer(error, "Crypto prices are temporarily unavailable", [503])
151
+ : unavailableOnServer(error, "Crypto markets are not available yet.");
152
152
  }
153
153
  }
154
154
 
@@ -248,7 +248,7 @@ function validateStablecoinSupplyHistory(data: StablecoinSupplyHistoryPayload):
248
248
  }
249
249
 
250
250
  function supplyError(error: unknown, what: string): unknown {
251
- if (error instanceof ApiRequestError && error.status === 503) return new Error(`${what} is temporarily unavailable`);
251
+ if (error instanceof ApiRequestError && error.status === 503) return unavailableOnServer(error, `${what} is temporarily unavailable`, [503]);
252
252
  return unavailableOnServer(error, `${what} is not available yet.`);
253
253
  }
254
254
 
@@ -31,15 +31,16 @@ const COIN_COLUMNS: HeadlessPaneColumn[] = [
31
31
  { key: "rank", header: "#", align: "right" },
32
32
  { key: "code", header: "Coin" },
33
33
  { key: "name", header: "Name" },
34
- { key: "price", header: "Price", align: "right", format: (_value, row) => String(row.priceText ?? "—") },
34
+ // Every coin is priced, traded and valued in US dollars: the text headers say so; an export keeps its headers.
35
+ { key: "price", header: "Price", textHeader: "Price USD", align: "right", format: (_value, row) => String(row.priceText ?? "—") },
35
36
  // Since 00:00 UTC, the day `quote` gives a coin (CRYPTO_CHANGE_BASIS_LABEL): the text says so, as the pane's
36
37
  // CHG% UTC does, and an export keeps the header it has always had.
37
38
  { key: "changePercent", header: "Chg %", textHeader: "Chg % UTC", align: "right", format: percent },
38
39
  { key: "return7d", header: "7D %", align: "right", format: percent },
39
40
  { key: "return30d", header: "30D %", align: "right", format: percent },
40
41
  { key: "return1y", header: "1Y %", align: "right", format: percent },
41
- { key: "volume24h", header: "Vol 24h", align: "right", format: compact },
42
- { key: "marketCap", header: "Market cap", align: "right", format: compact },
42
+ { key: "volume24h", header: "Vol 24h", textHeader: "Vol 24h USD", align: "right", format: compact },
43
+ { key: "marketCap", header: "Market cap", textHeader: "Market cap USD", align: "right", format: compact },
43
44
  ];
44
45
 
45
46
  /**
@@ -1,8 +1,8 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
2
  import { LIVE_STREAMING_QUICK_SETTING, withLiveStreamingSetting } from "../../../state/hooks/live-streaming";
3
3
  import { cryptoMarketsCache, DEFI_TVL_CACHES, STABLECOIN_SUPPLY_CACHES } from "./client";
4
+ import { BITCOIN_NETWORK_CACHES } from "./bitcoin-network";
4
5
  import { cryptoBoardHeadless } from "./headless";
5
- import { CryptoBoardPane } from "./pane";
6
6
  import { CRYPTO_SERIES } from "./series";
7
7
 
8
8
  export const cryptoBoardModule: PluginModule = {
@@ -12,7 +12,7 @@ export const cryptoBoardModule: PluginModule = {
12
12
  id: "crypto-board",
13
13
  name: "Crypto",
14
14
  icon: "C",
15
- component: CryptoBoardPane,
15
+ get component(): typeof import("./pane").CryptoBoardPane { const { CryptoBoardPane } = require("./pane"); return CryptoBoardPane; },
16
16
  defaultPosition: "right",
17
17
  defaultMode: "floating",
18
18
  defaultFloatingSize: { width: 120, height: 32 },
@@ -40,10 +40,12 @@ export const cryptoBoardModule: PluginModule = {
40
40
  cryptoMarketsCache.attach(ctx.persistence);
41
41
  for (const cache of STABLECOIN_SUPPLY_CACHES) cache.attach(ctx.persistence);
42
42
  for (const cache of DEFI_TVL_CACHES) cache.attach(ctx.persistence);
43
+ for (const cache of BITCOIN_NETWORK_CACHES) cache.attach(ctx.persistence);
43
44
  },
44
45
  dispose() {
45
46
  cryptoMarketsCache.reset();
46
47
  for (const cache of STABLECOIN_SUPPLY_CACHES) cache.reset();
47
48
  for (const cache of DEFI_TVL_CACHES) cache.reset();
49
+ for (const cache of BITCOIN_NETWORK_CACHES) cache.reset();
48
50
  },
49
51
  };
@@ -1,6 +1,7 @@
1
1
  import type { SeriesDefinition, SeriesPoint } from "../../../series/types";
2
- import { cachedSeriesWarning } from "../shared/series-warning";
2
+ import { cachedSeriesNote } from "../shared/series-warning";
3
3
  import { loadDefiTvl, loadStablecoinSupply, loadStablecoinSupplyHistory } from "./client";
4
+ import { BITCOIN_NETWORK_SERIES } from "./bitcoin-network";
4
5
 
5
6
  const DAY_MS = 86_400_000;
6
7
 
@@ -29,7 +30,7 @@ const STABLECOIN_SUPPLY_SERIES: readonly SeriesDefinition[] = [
29
30
  const resource = await loadStablecoinSupply();
30
31
  const history = resource.payload.history;
31
32
  if (!history) throw new Error("Stablecoin supply is not available yet.");
32
- return { points: dailyPoints(history.start, history.total), warning: cachedSeriesWarning(resource) };
33
+ return { points: dailyPoints(history.start, history.total), ...cachedSeriesNote(resource) };
33
34
  },
34
35
  },
35
36
  {
@@ -45,7 +46,7 @@ const STABLECOIN_SUPPLY_SERIES: readonly SeriesDefinition[] = [
45
46
  const resource = await loadStablecoinSupplyHistory(stablecoinSymbol(ticker!));
46
47
  const history = resource.payload.history;
47
48
  if (!history) throw new Error(`${resource.payload.symbol} supply history is not available yet.`);
48
- return { points: dailyPoints(history.start, history.values), warning: cachedSeriesWarning(resource) };
49
+ return { points: dailyPoints(history.start, history.values), ...cachedSeriesNote(resource) };
49
50
  },
50
51
  },
51
52
  ];
@@ -63,7 +64,7 @@ const DEFI_TVL_SERIES: SeriesDefinition = {
63
64
  const resource = await loadDefiTvl();
64
65
  const history = resource.payload.history;
65
66
  if (!history) throw new Error("DeFi TVL is not available yet.");
66
- return { points: dailyPoints(history.start, history.total), warning: cachedSeriesWarning(resource) };
67
+ return { points: dailyPoints(history.start, history.total), ...cachedSeriesNote(resource) };
67
68
  },
68
69
  };
69
70
 
@@ -114,4 +115,5 @@ export const CRYPTO_SERIES: readonly SeriesDefinition[] = [
114
115
  },
115
116
  ...STABLECOIN_SUPPLY_SERIES,
116
117
  DEFI_TVL_SERIES,
118
+ ...BITCOIN_NETWORK_SERIES,
117
119
  ];
@@ -18,7 +18,9 @@ const textOrNull = (value: unknown) => value === null || text(value);
18
18
  /**
19
19
  * The flows as served, or an error: the asset asked for, its funds once each,
20
20
  * every day newest first with a figure or none for each fund, and where they
21
- * are from. A payload that breaks any of it is not drawn.
21
+ * are from. A payload that breaks any of it is not drawn. A day's price move
22
+ * that is not a number or null is dropped from that day alone: the flows
23
+ * stand without it.
22
24
  */
23
25
  export function validateCryptoEtfFlows(payload: CryptoEtfFlowsPayload, asset: CryptoEtfFlowAsset): CryptoEtfFlowsPayload {
24
26
  const invalid = (): never => { throw new Error("The server returned invalid crypto ETF flows"); };
@@ -35,7 +37,12 @@ export function validateCryptoEtfFlows(payload: CryptoEtfFlowsPayload, asset: Cr
35
37
  || !Array.isArray(day.pending) || day.pending.some((ticker) => !known.has(ticker))) return invalid();
36
38
  }
37
39
  if (payload.days[0] && payload.days[0].date !== payload.asOf) return invalid();
38
- return payload;
40
+ if (payload.days.every((day) => day.priceChangePct === undefined || flow(day.priceChangePct))) return payload;
41
+ return { ...payload, days: payload.days.map((day) => {
42
+ if (day.priceChangePct === undefined || flow(day.priceChangePct)) return day;
43
+ const { priceChangePct: _dropped, ...rest } = day;
44
+ return rest;
45
+ }) };
39
46
  }
40
47
 
41
48
  export async function fetchCryptoEtfFlows(
@@ -2,6 +2,7 @@ import type { CryptoEtfFlowsPayload } from "../../../api-client/crypto-etf-flows
2
2
  import type { HeadlessPaneColumn } from "../../../types/headless";
3
3
  import type { HeadlessPaneDefinition } from "../../../types/plugin";
4
4
  import { REPORT_ROWS } from "../shared/headless-options";
5
+ import { readPriceWindow, type PriceWindow } from "../../../utils/price-window";
5
6
  import { fetchCryptoEtfFlows } from "./client";
6
7
  import {
7
8
  cryptoEtfFlowAsset,
@@ -9,16 +10,22 @@ import {
9
10
  cryptoEtfFlowSummary,
10
11
  formatFlow,
11
12
  formatFlowAmount,
13
+ formatPriceChange,
14
+ hasFundPriceChanges,
12
15
  pendingText,
13
16
  } from "./model";
14
17
 
15
18
  const flowCell = (value: unknown) => formatFlow(typeof value === "number" ? value : null);
19
+ const optionText = (value: unknown) => typeof value === "string" && value.trim() ? value : undefined;
16
20
 
17
- /** Date, the day's total and each fund, in US$ millions as published. */
21
+ /** Date, the day's total, the funds' price move when served, and each fund, in US$ millions as published. */
18
22
  function flowColumns(payload: CryptoEtfFlowsPayload): HeadlessPaneColumn[] {
19
23
  return [
20
24
  { key: "date", header: "Date", shrink: false },
21
25
  { key: "total", header: "Total", align: "right", format: flowCell },
26
+ ...hasFundPriceChanges(payload)
27
+ ? [{ key: "priceChangePct", header: "Price %", literalHeader: true, align: "right" as const, format: (value: unknown) => formatPriceChange(typeof value === "number" ? value : null) }]
28
+ : [],
22
29
  // A fund the day still waits on reads "pending", as in the pane; none at all is a dash.
23
30
  ...payload.funds.map((fund): HeadlessPaneColumn => ({
24
31
  key: fund.ticker, header: fund.ticker, literalHeader: true, align: "right",
@@ -28,6 +35,13 @@ function flowColumns(payload: CryptoEtfFlowsPayload): HeadlessPaneColumn[] {
28
35
  ];
29
36
  }
30
37
 
38
+ /** "Net flows of the US spot bitcoin ETFs in US$ millions. 2026-10-19 to 2026-10-23: +$301.20M over 5 trading days." */
39
+ function windowSummaryLine(payload: CryptoEtfFlowsPayload, window: PriceWindow, days: CryptoEtfFlowsPayload["days"]): string {
40
+ const total = days.some((day) => day.total != null) ? days.reduce((sum, day) => sum + (day.total ?? 0), 0) : null;
41
+ return `Net flows of the US spot ${cryptoEtfFlowAssetLabel(payload.asset).toLowerCase()} ETFs in US$ millions. ${window.from} to ${window.to}: `
42
+ + `${formatFlowAmount(total)} over ${days.length} trading ${days.length === 1 ? "day" : "days"}.`;
43
+ }
44
+
31
45
  /** "Net flows of the US spot bitcoin ETFs in US$ millions. 2026-10-20: -$6.70M (IBIT pending); 5 days ..., since 2024-01-11 +$57.47B." */
32
46
  function summaryLine(payload: CryptoEtfFlowsPayload): string {
33
47
  const summary = cryptoEtfFlowSummary(payload);
@@ -43,6 +57,15 @@ function summaryLine(payload: CryptoEtfFlowsPayload): string {
43
57
  ].join(" ");
44
58
  }
45
59
 
60
+ /**
61
+ * "Funds: IBIT iShares Bitcoin Trust ETF, ..., BTC Grayscale Bitcoin Mini Trust ETF.": a column is headed by
62
+ * the fund's ticker, and BTC or ETH there is Grayscale's mini fund, not the coin. Null when no fund is named.
63
+ */
64
+ function fundsLine(payload: CryptoEtfFlowsPayload): string | null {
65
+ const named = payload.funds.flatMap((fund) => fund.name ? [`${fund.ticker} ${fund.name}`] : []);
66
+ return named.length ? `Funds: ${named.join(", ")}.` : null;
67
+ }
68
+
46
69
  export const cryptoEtfFlowsHeadless: HeadlessPaneDefinition<"rows"> = {
47
70
  shape: "rows",
48
71
  argument: { kind: "none" },
@@ -69,6 +92,22 @@ export const cryptoEtfFlowsHeadless: HeadlessPaneDefinition<"rows"> = {
69
92
  minimum: 1,
70
93
  maximum: 2000,
71
94
  },
95
+ {
96
+ key: "from",
97
+ type: "string",
98
+ placeholder: "year|date",
99
+ description: "First US trading day listed, as a year (2024) or a date (2026-10-19); lists every day of the window in place of --limit.",
100
+ example: "--from 2026-10-19 --to 2026-10-23",
101
+ shot: { reportOnly: "lists the report's days for those dates" },
102
+ replaces: ["limit"],
103
+ },
104
+ {
105
+ key: "to",
106
+ type: "string",
107
+ placeholder: "year|date",
108
+ description: "Last US trading day listed, with --from (default the newest).",
109
+ shot: { reportOnly: "lists the report's days for those dates" },
110
+ },
72
111
  ],
73
112
  columns: [
74
113
  { key: "date", header: "Date" },
@@ -81,24 +120,36 @@ export const cryptoEtfFlowsHeadless: HeadlessPaneDefinition<"rows"> = {
81
120
  limitations: [
82
121
  "US spot bitcoin and ether ETFs only, net creations and redemptions in US$ millions as Farside Investors publishes them.",
83
122
  "The newest day fills in fund by fund; a fund that has not reported shows as pending.",
123
+ "0.0 is the publisher's zero for the fund that day; a dash is a day it published no figure for.",
124
+ "Price % is the funds' price move that day: the median of their close-to-close changes.",
84
125
  ],
85
126
  },
86
- freshness: { source: "Farside Investors", publisher: "Farside Investors", status: "not-a-feed", basis: "daily fund flows", cadence: "daily", tradingDayMarket: "US" },
127
+ // Stale once a trading day's flows are missing (the load's `cadence`), except in a window that ended before the newest day.
128
+ freshness: { source: "Farside Investors", publisher: "Farside Investors", status: "not-a-feed", basis: "daily fund flows", tradingDayMarket: "US" },
87
129
  async load(args, ctx) {
88
130
  const asset = cryptoEtfFlowAsset(args.options.asset);
131
+ // --from 2026-10-19 --to 2026-10-23 lists those days, every one of them, in place of the newest --limit.
132
+ const window = readPriceWindow(optionText(args.options.from), optionText(args.options.to));
89
133
  const payload = await fetchCryptoEtfFlows(asset, ctx.apiClient);
90
134
  const limit = Math.max(1, Number(args.options.limit) || 20);
91
- const rows = payload.days.slice(0, limit).map((day) => ({
135
+ const days = window ? payload.days.filter((day) => day.date >= window.from && day.date <= window.to) : payload.days.slice(0, limit);
136
+ const priced = hasFundPriceChanges(payload);
137
+ const rows = days.map((day) => ({
92
138
  date: day.date,
93
139
  total: day.total,
140
+ ...(priced ? { priceChangePct: day.priceChangePct ?? null } : {}),
94
141
  ...Object.fromEntries(payload.funds.map((fund) => [fund.ticker, day.flows[fund.ticker] ?? null])),
95
142
  pending: day.pending,
96
143
  }));
97
144
  const summary = cryptoEtfFlowSummary(payload);
145
+ const funds = fundsLine(payload);
146
+ // A window that ends before the newest day is dated by its own last day.
147
+ const asOf = window ? days[0]?.date ?? null : payload.asOf;
98
148
  return {
99
149
  rows,
100
150
  columns: flowColumns(payload),
101
- notes: [summaryLine(payload), `Source: ${payload.source.publisher}, ${payload.source.url}`],
151
+ ...(window && !days.length ? { errors: [`No US trading day with flows from ${window.from} to ${window.to}.`] } : {}),
152
+ notes: [window ? windowSummaryLine(payload, window, days) : summaryLine(payload), ...funds ? [funds] : [], `Source: ${payload.source.publisher}, ${payload.source.url}`],
102
153
  complete: !payload.stale,
103
154
  ...(payload.stale ? { warnings: [`The flows were last read ${payload.fetchedAt.slice(0, 10)}; newer days may be missing.`] } : {}),
104
155
  freshness: {
@@ -106,10 +157,12 @@ export const cryptoEtfFlowsHeadless: HeadlessPaneDefinition<"rows"> = {
106
157
  publisher: payload.source.publisher,
107
158
  status: "not-a-feed",
108
159
  basis: "daily fund flows",
109
- cadence: "daily",
160
+ ...(window && asOf !== payload.asOf ? {} : { cadence: "daily" as const }),
110
161
  tradingDayMarket: "US",
111
- asOf: payload.asOf,
162
+ ...(asOf ? { asOf } : {}),
112
163
  oldest: null,
164
+ // When the server last read the publisher's table, which is what this report retrieved.
165
+ retrievedAt: payload.fetchedAt,
113
166
  },
114
167
  metadata: {
115
168
  asset,
@@ -117,6 +170,8 @@ export const cryptoEtfFlowsHeadless: HeadlessPaneDefinition<"rows"> = {
117
170
  asOf: payload.asOf,
118
171
  fetchedAt: payload.fetchedAt,
119
172
  stale: payload.stale,
173
+ ...(window ? { requestedWindow: window } : {}),
174
+ ...(priced ? { priceChangeBasis: "median of the funds' close-to-close price changes, percent" } : {}),
120
175
  source: payload.source,
121
176
  funds: payload.funds,
122
177
  summary: {
@@ -2,7 +2,6 @@ import type { PluginModule } from "../plugin-module";
2
2
  import { cryptoEtfFlowsCache } from "./client";
3
3
  import { cryptoEtfFlowsHeadless } from "./headless";
4
4
  import { CRYPTO_ETF_FLOWS_PANE_ID } from "./model";
5
- import { CryptoEtfFlowsPane } from "./pane";
6
5
 
7
6
  /** ETFF: the daily net flows of the US spot bitcoin and ether ETFs, per fund. */
8
7
  export const cryptoEtfFlowsModule: PluginModule = {
@@ -11,7 +10,7 @@ export const cryptoEtfFlowsModule: PluginModule = {
11
10
  id: CRYPTO_ETF_FLOWS_PANE_ID,
12
11
  name: "Crypto ETF Flows",
13
12
  icon: "F",
14
- component: CryptoEtfFlowsPane,
13
+ get component(): typeof import("./pane").CryptoEtfFlowsPane { const { CryptoEtfFlowsPane } = require("./pane"); return CryptoEtfFlowsPane; },
15
14
  defaultPosition: "right",
16
15
  defaultMode: "floating",
17
16
  defaultFloatingSize: { width: 120, height: 32 },
@@ -103,11 +103,24 @@ export function pendingText(day: CryptoEtfFlowDay | null | undefined): string |
103
103
 
104
104
  export type CryptoEtfFlowColumn = Omit<DataTableColumn, "id"> & { id: string };
105
105
 
106
- /** Date and the day's total, then each fund in the publisher's order. */
106
+ /** Whether the server sent the funds' daily price move beside the flows. */
107
+ export const hasFundPriceChanges = (payload: CryptoEtfFlowsPayload | null | undefined) =>
108
+ !!payload?.days.some((day) => day.priceChangePct !== undefined);
109
+
110
+ /** A day's price move as the table shows it: `+2.15%`, `-0.40%`, `—` for none. */
111
+ export function formatPriceChange(value: number | null | undefined): string {
112
+ if (value == null || !Number.isFinite(value)) return "—";
113
+ return `${value > 0 ? "+" : ""}${value.toFixed(2)}%`;
114
+ }
115
+
116
+ /** Date, the day's total and the funds' price move that day, then each fund in the publisher's order. */
107
117
  export function cryptoEtfFlowColumns(payload: CryptoEtfFlowsPayload | null | undefined): CryptoEtfFlowColumn[] {
108
118
  return [
109
119
  { id: "date", label: "Date", width: 12, align: "left" },
110
120
  { id: "total", label: "Total", width: 9, align: "right", title: "The day's net flow across the funds, US$ millions" },
121
+ ...hasFundPriceChanges(payload)
122
+ ? [{ id: "price", label: "Price %", width: 9, align: "right" as const, title: "The funds' price move that day: the median of their close-to-close changes" }]
123
+ : [],
111
124
  ...(payload?.funds ?? []).map((fund): CryptoEtfFlowColumn => ({
112
125
  id: `fund:${fund.ticker}`,
113
126
  label: fund.ticker,
@@ -12,6 +12,7 @@ import {
12
12
  type DataTableCell,
13
13
  type StatItem,
14
14
  } from "../../../components";
15
+ import { ageFooterSegment } from "../../../components/layout/pane/footer";
15
16
  import { formatCompactAxis } from "../../../components/chart-table";
16
17
  import { useChartViewReset } from "../../../components/chart/core/view-reset";
17
18
  import { scalarPoint, staticSeries } from "../../../components/chart/static/series";
@@ -36,6 +37,7 @@ import {
36
37
  flowColor,
37
38
  formatFlow,
38
39
  formatFlowAmount,
40
+ formatPriceChange,
39
41
  fundTickerOfColumn,
40
42
  pendingText,
41
43
  type CryptoEtfFlowColumn,
@@ -76,13 +78,15 @@ export function CryptoEtfFlowsPane({ focused, width, height }: PaneProps) {
76
78
  }, [asset, setSelectedByAsset]);
77
79
 
78
80
  const label = cryptoEtfFlowAssetLabel(asset);
79
- // The running total since the first fund launched, drawn over the range the table lists.
81
+ // The running total since the first fund launched, drawn over the range the table lists. Its legend is the one
82
+ // place the total and the day it starts from are said; the figures above carry only the table's windows.
80
83
  const series = useMemo(() => {
81
84
  if (!data) return [];
82
85
  const first = days.at(-1)?.date ?? "";
83
86
  const points = cumulativeFlows(data).filter((point) => point.date >= first)
84
87
  .map((point) => scalarPoint(new Date(`${point.date}T00:00:00Z`), point.value * 1_000_000));
85
- return [staticSeries(points, { id: `etf-flows:${asset}`, label: `${label} ETF net flows since launch`, color: colors.positive, calendarSpaced: true })];
88
+ const launch = data.days.at(-1)?.date;
89
+ return [staticSeries(points, { id: `etf-flows:${asset}`, label: `${label} ETF net flows since ${launch ?? "launch"}`, color: colors.positive, calendarSpaced: true })];
86
90
  }, [asset, data, days, label]);
87
91
  const link = useChartTableSelection({ rows: days, getId: rowId, getDate: rowDate, selectedId, onSelect: selectDay, focused });
88
92
 
@@ -93,14 +97,12 @@ export function CryptoEtfFlowsPane({ focused, width, height }: PaneProps) {
93
97
  detail: [latest?.date, pending].filter(Boolean).join(" · ") || undefined },
94
98
  { id: "week", label: "5 days", value: formatFlowAmount(summary.week), color: flowColor(summary.week, colors) },
95
99
  { id: "month", label: "20 days", value: formatFlowAmount(summary.month), color: flowColor(summary.month, colors) },
96
- { id: "launch", label: "Since launch", value: formatFlowAmount(summary.sinceLaunch), color: flowColor(summary.sinceLaunch, colors),
97
- detail: summary.firstDate ? `since ${summary.firstDate}` : undefined },
98
100
  ] : [];
99
101
 
100
102
  const updatedAgo = useUpdatedAgo(resource.updatedAt);
101
103
  const footerInfo = useMemo(() => data ? [
102
104
  { id: "as-of", parts: [{ text: `as of ${data.asOf}`, tone: "muted" as const }] },
103
- ...(updatedAgo ? [{ id: "updated", parts: [{ text: updatedAgo, tone: "muted" as const }] }] : []),
105
+ ...(updatedAgo ? [ageFooterSegment(updatedAgo)] : []),
104
106
  ] : [], [data, updatedAgo]);
105
107
  useAutoRefresh(resource.updatedAt, resource.load);
106
108
  const refresh = useCallback(() => void resource.reload(), [resource.reload]);
@@ -135,6 +137,7 @@ export function CryptoEtfFlowsPane({ focused, width, height }: PaneProps) {
135
137
  const renderCell = useCallback((day: CryptoEtfFlowDay, column: CryptoEtfFlowColumn): DataTableCell => {
136
138
  if (column.id === "date") return { text: day.date, value: day.date, color: colors.text };
137
139
  if (column.id === "total") return { text: formatFlow(day.total), value: day.total, color: flowColor(day.total, colors) };
140
+ if (column.id === "price") return { text: formatPriceChange(day.priceChangePct), value: day.priceChangePct ?? null, color: flowColor(day.priceChangePct, colors) };
138
141
  const ticker = fundTickerOfColumn(column.id) ?? "";
139
142
  const value = day.flows[ticker] ?? null;
140
143
  // A fund that reported the day before and not yet today: its figure is to come, not none.
@@ -1,6 +1,6 @@
1
1
  import type { HeadlessPaneDefinition } from "../../../types/plugin";
2
2
  import { fetchDebtMaturities } from "./client";
3
- import { bucketShare, debtAmount, debtNewestAsOf, debtNotices, debtPercent, newerQuarter } from "./model";
3
+ import { bucketShare, debtAmount, debtNewestAsOf, debtNewestForm, debtNotices, debtPercent, newerQuarter } from "./model";
4
4
  import { SEC_FILINGS } from "../shared/report-freshness";
5
5
  import { resolveHeadlessIssuerListing } from "../shared/headless-market-data";
6
6
 
@@ -118,8 +118,9 @@ export const debtMaturitiesHeadless: HeadlessPaneDefinition<"bundle"> = {
118
118
  },
119
119
  ],
120
120
  metadata: { ...data },
121
- // The latest filing dates the report (a newer 10-Q's balance date); the history rows are past years, not stale data.
122
- freshness: { asOf: debtNewestAsOf(data) },
121
+ // The latest filing dates the report (a newer 10-Q's balance date), named by its form so the date is
122
+ // not read as the schedule's; the history rows are past years, not stale data.
123
+ freshness: { asOf: debtNewestAsOf(data), asOfLabel: debtNewestForm(data) ?? undefined },
123
124
  };
124
125
  },
125
126
  };
@@ -219,9 +219,25 @@ export function newerQuarter(data: DebtMaturitiesPayload): DebtQuarter | null {
219
219
  const quarter = data.latestQuarter ?? null;
220
220
  return quarter && data.latest && quarter.asOf > data.latest.asOf ? quarter : null;
221
221
  }
222
+ /** The newest filing the payload reports: the 10-Q when it is newer, else the annual schedule's. */
223
+ function newestDebtFiling(data: DebtMaturitiesPayload): Pick<DebtQuarter, "form" | "asOf" | "filed"> | null {
224
+ return newerQuarter(data) ?? data.latest ?? null;
225
+ }
222
226
  /** The newest balance date the payload reports: the 10-Q's when it is newer, else the schedule's. */
223
227
  export function debtNewestAsOf(data: DebtMaturitiesPayload): string | null {
224
- return newerQuarter(data)?.asOf ?? data.latest?.asOf ?? null;
228
+ return newestDebtFiling(data)?.asOf ?? null;
229
+ }
230
+ /** The form of the filing `debtNewestAsOf` dates, which names that date: `10-Q`, `10-K`. */
231
+ export function debtNewestForm(data: DebtMaturitiesPayload): string | null {
232
+ return newestDebtFiling(data)?.form ?? null;
233
+ }
234
+ /**
235
+ * Which filing the pane's date is, as its footer says it, longest first:
236
+ * `10-Q 2026-09-30, filed 2026-10-27`, then `10-Q 2026-09-30`.
237
+ */
238
+ export function debtNewestFilingText(data: DebtMaturitiesPayload): string[] {
239
+ const filing = newestDebtFiling(data);
240
+ return filing ? [`${filing.form} ${filing.asOf}, filed ${filing.filed}`, `${filing.form} ${filing.asOf}`] : [];
225
241
  }
226
242
  /** The 10-Q's debt in one line, `Current 212.00M · Non-current 6.84B USD`. */
227
243
  export function quarterDebtText(quarter: DebtQuarter): string {
@@ -24,6 +24,7 @@ import {
24
24
  type DataTableCell,
25
25
  type StatItem,
26
26
  } from "../../../components";
27
+ import { ageFooterSegment } from "../../../components/layout/pane/footer";
27
28
  import { colors } from "../../../theme/colors";
28
29
  import type {
29
30
  DebtFact,
@@ -53,6 +54,7 @@ import {
53
54
  debtFilingUrl,
54
55
  debtMetricCaption,
55
56
  debtMetricValue,
57
+ debtNewestFilingText,
56
58
  debtNotices,
57
59
  debtPercent,
58
60
  historyAxis,
@@ -431,10 +433,13 @@ export function DebtMaturitiesPane({ width, height, focused }: PaneProps) {
431
433
  metric("next12", "Due next 12 months", latest.next12MonthsShare),
432
434
  metric("next3", "Due next 3 years", latest.next3YearsShare),
433
435
  { id: "as-of", label: "As of", value: latest.asOf },
434
- // A 10-Q filed since: its own row, dated, so it never reads as the schedule's.
436
+ // A 10-Q filed since: its own row, named by its period, so it never reads
437
+ // as the schedule's. The filed date goes beside the figures where it fits
438
+ // and gives way to them where it does not: the footer says it
439
+ // ("10-Q 2026-09-30, filed 2026-10-27").
435
440
  ...(quarter ? [{
436
- id: "latest-10q", label: "Latest 10-Q", value: quarterDebtText(quarter),
437
- detail: `${quarter.asOf} · filed ${quarter.filed}`, wide: true,
441
+ id: "latest-10q", label: `10-Q ${quarter.asOf}`, value: quarterDebtText(quarter),
442
+ detail: `filed ${quarter.filed}`, detailVariants: [""], wide: true,
438
443
  }] : []),
439
444
  ];
440
445
  }, [latest, quarter]);
@@ -445,6 +450,15 @@ export function DebtMaturitiesPane({ width, height, focused }: PaneProps) {
445
450
  label: "Principal total", values, value: debtAmount(last), color: colors.warning,
446
451
  };
447
452
  }, [bars]);
453
+ // Which filing the pane's date is, then how long ago it loaded: the age goes first in a narrow footer.
454
+ const filingFooter = useMemo(() => {
455
+ const [filing, ...shorter] = data ? debtNewestFilingText(data) : [];
456
+ return [
457
+ ...(filing ? [{ id: "filing", asOf: true, parts: [{ text: filing, tone: "muted" as const }],
458
+ variants: shorter.map((text) => [{ text, tone: "muted" as const }]) }] : []),
459
+ ...(data && updatedAgo ? [ageFooterSegment(updatedAgo)] : []),
460
+ ];
461
+ }, [data, updatedAgo]);
448
462
  useAutoRefresh(resource.updatedAt, resource.load);
449
463
  usePaneRefreshKey(() => void resource.reload(), { focused });
450
464
  usePaneNoticeFooter({
@@ -465,7 +479,7 @@ export function DebtMaturitiesPane({ width, height, focused }: PaneProps) {
465
479
  ? debtFilingUrl(data.cik, selectedHistoryRow.accession)
466
480
  : (latest?.filingUrl ?? data?.source.url ?? null),
467
481
  showOpenHint: true,
468
- info: data && updatedAgo ? [{ id: "updated", parts: [{ text: updatedAgo, tone: "muted" as const }] }] : [],
482
+ info: filingFooter,
469
483
  stale: !!data && resource.data?.stale,
470
484
  });
471
485
  const renderBucket = (