@gloom-sh/terminal 0.18.5 → 0.19.1

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 (570) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/package.json +1 -1
  4. package/patches/electrobun@1.18.1.patch +163 -8
  5. package/src/api-client/app-attention.ts +3 -0
  6. package/src/api-client/beneficial-owners.ts +3 -0
  7. package/src/api-client/company-kpis.ts +3 -0
  8. package/src/api-client/cot.ts +6 -2
  9. package/src/api-client/credit-documents.ts +6 -0
  10. package/src/api-client/data.ts +19 -7
  11. package/src/api-client/equity-screener.ts +24 -0
  12. package/src/api-client/fund-facts.ts +45 -4
  13. package/src/api-client/hiring.ts +3 -0
  14. package/src/api-client/index.ts +15 -0
  15. package/src/api-client/ipo.ts +24 -1
  16. package/src/api-client/market-discovery.ts +10 -0
  17. package/src/api-client/market-movers.ts +7 -0
  18. package/src/api-client/members.ts +1 -1
  19. package/src/api-client/mna.ts +3 -0
  20. package/src/api-client/paths.ts +9 -1
  21. package/src/api-client/perps.ts +43 -0
  22. package/src/api-client/research-activity.ts +3 -2
  23. package/src/api-client/roadmap.ts +85 -0
  24. package/src/api-client/types.ts +82 -2
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +0 -2
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +2 -2
  29. package/src/architecture/pane-tabs-allowlist.json +3 -0
  30. package/src/architecture/ttl-caches-allowlist.json +19 -0
  31. package/src/brokers/sync-broker-instance.ts +2 -8
  32. package/src/capabilities/chart-series.ts +1 -1
  33. package/src/cli/commands/crypto-hints.ts +40 -24
  34. package/src/cli/commands/market.ts +107 -38
  35. package/src/cli/commands/news-search.ts +22 -0
  36. package/src/cli/commands/options-chain.ts +48 -7
  37. package/src/cli/commands/options-leaps.ts +9 -3
  38. package/src/cli/commands/overview.ts +147 -32
  39. package/src/cli/commands/search.ts +21 -1
  40. package/src/cli/commands/themes.ts +11 -2
  41. package/src/cli/commands/ticker.ts +87 -36
  42. package/src/cli/company-name-hint.ts +41 -0
  43. package/src/cli/earnings-calendar.ts +1 -1
  44. package/src/cli/errors.ts +25 -2
  45. package/src/cli/glossary.ts +31 -2
  46. package/src/cli/history-rows.ts +57 -9
  47. package/src/cli/index-roots.ts +0 -7
  48. package/src/cli/index.ts +2 -0
  49. package/src/cli/listing-arg.ts +235 -60
  50. package/src/cli/output-file.ts +68 -0
  51. package/src/cli/pane-functions/capabilities.ts +4 -0
  52. package/src/cli/pane-functions/catalog.ts +80 -7
  53. package/src/cli/pane-functions/discovery.ts +21 -1
  54. package/src/cli/pane-functions/dom.ts +13 -3
  55. package/src/cli/pane-functions/freshness.ts +93 -4
  56. package/src/cli/pane-functions/function-help.ts +20 -10
  57. package/src/cli/pane-functions/headless.ts +17 -7
  58. package/src/cli/pane-functions/index.ts +54 -16
  59. package/src/cli/pane-functions/market-session.ts +66 -4
  60. package/src/cli/pane-functions/options.ts +19 -1
  61. package/src/cli/pane-functions/resolver.ts +19 -8
  62. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  63. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  64. package/src/cli/quote-answer.ts +22 -11
  65. package/src/cli/saved-copy-persistence.ts +88 -0
  66. package/src/cli/ticker-resolution.ts +3 -2
  67. package/src/components/chart/composite/composite-chart.tsx +4 -0
  68. package/src/components/chart/composite/interactions.ts +6 -0
  69. package/src/components/chart/composite/legend.tsx +45 -7
  70. package/src/components/chart/composite/panel-gestures.ts +33 -0
  71. package/src/components/chart/composite/panel-surface.tsx +52 -3
  72. package/src/components/chart/composite/rasterizer.ts +24 -13
  73. package/src/components/chart/composite/scene.ts +39 -4
  74. package/src/components/chart/composite/series-cues.ts +14 -0
  75. package/src/components/chart/composite/text-renderer.ts +9 -2
  76. package/src/components/chart/composite/tool-catalog.ts +36 -0
  77. package/src/components/chart/composite/tools.ts +203 -44
  78. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  79. package/src/components/command-bar/assist/inventory.ts +2 -1
  80. package/src/components/command-bar/commands/direct/index.ts +28 -13
  81. package/src/components/command-bar/list/model.ts +5 -0
  82. package/src/components/command-bar/pane-templates/items.ts +3 -2
  83. package/src/components/command-bar/routes/root/runtime.ts +6 -7
  84. package/src/components/command-bar/selection-runtime.ts +52 -3
  85. package/src/components/command-bar/surface/index.tsx +2 -0
  86. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  87. package/src/components/layout/pane/capture-stamp.ts +2 -2
  88. package/src/components/layout/pane/footer/index.tsx +22 -6
  89. package/src/components/layout/pane/footer/model.ts +83 -15
  90. package/src/components/layout/pane/status-footer.ts +35 -2
  91. package/src/components/layout/status-widget-space.ts +9 -0
  92. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  93. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  94. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  95. package/src/components/onboarding/wizard-positions.ts +3 -3
  96. package/src/components/ticker/list-table-view.tsx +8 -21
  97. package/src/components/ui/choice-dialog.tsx +23 -18
  98. package/src/components/ui/choice-groups.ts +18 -0
  99. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  100. package/src/components/ui/data-table/opentui/model.ts +26 -1
  101. package/src/components/ui/data-table/types.ts +2 -0
  102. package/src/components/ui/display.tsx +18 -3
  103. package/src/components/ui/icon.tsx +4 -1
  104. package/src/components/ui/list-view.tsx +8 -4
  105. package/src/components/ui/query-bar-keys.tsx +2 -0
  106. package/src/components/ui/query-bar.tsx +1 -0
  107. package/src/components/ui/select-button.tsx +5 -1
  108. package/src/components/ui/select-field.tsx +1 -0
  109. package/src/components/ui/table-layout.ts +8 -5
  110. package/src/core/app-runtime.ts +2 -0
  111. package/src/core/app-services.ts +2 -2
  112. package/src/data/config/chart-settings.ts +1 -1
  113. package/src/data/json-ticker-repository.ts +17 -8
  114. package/src/data/plugin-cache.ts +5 -3
  115. package/src/data/saved-copies.ts +8 -0
  116. package/src/data/ticker-repository.ts +16 -9
  117. package/src/data/ticker-writes.ts +81 -0
  118. package/src/data/ttl-cache.ts +165 -0
  119. package/src/docs/snapshot.json +103 -142
  120. package/src/i18n/es.ts +20 -6
  121. package/src/i18n/ja.ts +20 -6
  122. package/src/i18n/ko.ts +20 -6
  123. package/src/i18n/zh-cn.ts +20 -6
  124. package/src/i18n/zh-tw.ts +20 -6
  125. package/src/market-data/depositary-pair.ts +27 -3
  126. package/src/market-data/fund-facts.ts +50 -0
  127. package/src/market-data/market/change-basis.ts +16 -3
  128. package/src/market-data/market/format.ts +25 -0
  129. package/src/market-data/market/freshness.ts +2 -1
  130. package/src/market-data/options-alternatives.ts +81 -4
  131. package/src/market-data/quotes/metadata.ts +7 -0
  132. package/src/news/aggregator.ts +119 -9
  133. package/src/news/cloud-session.ts +21 -0
  134. package/src/news/hooks.ts +1 -0
  135. package/src/news/news-model.ts +1 -0
  136. package/src/news/types.ts +17 -0
  137. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  138. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  139. package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
  140. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  141. package/src/plugins/builtin/alerts/cli.ts +362 -80
  142. package/src/plugins/builtin/alerts/command.ts +25 -1
  143. package/src/plugins/builtin/alerts/event-rows.ts +24 -3
  144. package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
  145. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  146. package/src/plugins/builtin/alerts/format.ts +75 -5
  147. package/src/plugins/builtin/alerts/headless.ts +34 -12
  148. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  149. package/src/plugins/builtin/alerts/history.ts +1 -1
  150. package/src/plugins/builtin/alerts/index.tsx +79 -16
  151. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  152. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  153. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  154. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  155. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  156. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  157. package/src/plugins/builtin/alerts/storage.ts +12 -8
  158. package/src/plugins/builtin/alerts/types.ts +4 -0
  159. package/src/plugins/builtin/analytics/index.tsx +2 -1
  160. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  161. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  162. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  163. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  164. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  165. package/src/plugins/builtin/attention/index.tsx +2 -1
  166. package/src/plugins/builtin/attention/listing.ts +14 -0
  167. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  168. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  169. package/src/plugins/builtin/buildout/format.ts +38 -0
  170. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  171. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  172. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  173. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  174. package/src/plugins/builtin/cds/client.ts +142 -30
  175. package/src/plugins/builtin/cds/headless.ts +35 -8
  176. package/src/plugins/builtin/cds/model.ts +145 -18
  177. package/src/plugins/builtin/cds/pane.tsx +16 -5
  178. package/src/plugins/builtin/changelog/index.tsx +9 -6
  179. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  180. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  181. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  182. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  183. package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
  184. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  185. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  186. package/src/plugins/builtin/chart-composer/index.tsx +5 -6
  187. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  188. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  189. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  190. package/src/plugins/builtin/chat/controller/index.ts +5 -1
  191. package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
  192. package/src/plugins/builtin/chat/controller/state.ts +25 -2
  193. package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
  194. package/src/plugins/builtin/chat/controller/utils.ts +7 -2
  195. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  196. package/src/plugins/builtin/chat/status-preview.ts +79 -0
  197. package/src/plugins/builtin/chat/status-widget.tsx +147 -10
  198. package/src/plugins/builtin/chat/unread-badge.ts +51 -10
  199. package/src/plugins/builtin/clinical-trials/client.ts +49 -4
  200. package/src/plugins/builtin/clinical-trials/model.ts +86 -31
  201. package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
  202. package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
  203. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
  204. package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
  205. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  206. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  207. package/src/plugins/builtin/company-attention/client.ts +8 -9
  208. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  209. package/src/plugins/builtin/company-attention/headless.ts +2 -1
  210. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  211. package/src/plugins/builtin/company-attention/pane.tsx +12 -2
  212. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  213. package/src/plugins/builtin/company-kpis/headless.ts +8 -3
  214. package/src/plugins/builtin/company-kpis/model.ts +73 -5
  215. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  216. package/src/plugins/builtin/composite-plugins.ts +2 -1
  217. package/src/plugins/builtin/correlation/headless.ts +13 -5
  218. package/src/plugins/builtin/correlation/history.ts +22 -0
  219. package/src/plugins/builtin/cot/client.ts +22 -18
  220. package/src/plugins/builtin/cot/headless.ts +15 -10
  221. package/src/plugins/builtin/cot/model.ts +14 -1
  222. package/src/plugins/builtin/cot/pane.tsx +22 -14
  223. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  224. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  225. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  226. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  227. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  228. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  229. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  230. package/src/plugins/builtin/credit-documents/headless.ts +10 -10
  231. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  232. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  233. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  234. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  235. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  236. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  237. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  238. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  239. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  240. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  241. package/src/plugins/builtin/docs/targets.ts +5 -1
  242. package/src/plugins/builtin/doe/headless.ts +3 -2
  243. package/src/plugins/builtin/doe/pane.tsx +13 -3
  244. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  245. package/src/plugins/builtin/earnings/board.tsx +62 -71
  246. package/src/plugins/builtin/earnings/headless.ts +40 -4
  247. package/src/plugins/builtin/earnings/scope.ts +31 -43
  248. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  249. package/src/plugins/builtin/earnings-calls/data.ts +18 -6
  250. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  251. package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
  252. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  253. package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
  254. package/src/plugins/builtin/econ/calendar-model.ts +48 -1
  255. package/src/plugins/builtin/econ/calendar-source.ts +4 -2
  256. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  257. package/src/plugins/builtin/econ/figures.ts +48 -0
  258. package/src/plugins/builtin/econ/index.tsx +14 -5
  259. package/src/plugins/builtin/econ/print-watch.ts +3 -1
  260. package/src/plugins/builtin/econ/types.ts +2 -0
  261. package/src/plugins/builtin/econ-statistics/client.ts +11 -0
  262. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  263. package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
  264. package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
  265. package/src/plugins/builtin/econ-statistics/stats.ts +39 -9
  266. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  267. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  268. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  269. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  270. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  271. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  272. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  273. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  274. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  275. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  276. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  277. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  278. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  279. package/src/plugins/builtin/gpu/headless.ts +8 -2
  280. package/src/plugins/builtin/help/function-index.ts +4 -1
  281. package/src/plugins/builtin/help/index.tsx +62 -59
  282. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  283. package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
  284. package/src/plugins/builtin/help/table-cells.tsx +13 -2
  285. package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
  286. package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
  287. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  288. package/src/plugins/builtin/holders/headless.ts +22 -3
  289. package/src/plugins/builtin/holders/pane.tsx +3 -0
  290. package/src/plugins/builtin/holders/report-rows.ts +4 -1
  291. package/src/plugins/builtin/insider/headless.ts +2 -2
  292. package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
  293. package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
  294. package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
  295. package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
  296. package/src/plugins/builtin/iv-history/client.ts +52 -11
  297. package/src/plugins/builtin/iv-history/format.ts +36 -23
  298. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  299. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  300. package/src/plugins/builtin/iv-history/model.ts +163 -27
  301. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  302. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  303. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  304. package/src/plugins/builtin/jobs/client.ts +25 -29
  305. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  306. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  307. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  308. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  309. package/src/plugins/builtin/macro-day/headless.ts +12 -5
  310. package/src/plugins/builtin/macro-day/index.tsx +1 -1
  311. package/src/plugins/builtin/macro-day/model.ts +39 -5
  312. package/src/plugins/builtin/macro-day/pane.tsx +27 -19
  313. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  314. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  315. package/src/plugins/builtin/market-movers/client.ts +27 -7
  316. package/src/plugins/builtin/market-movers/headless.ts +34 -12
  317. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  318. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  319. package/src/plugins/builtin/market-movers/model.ts +4 -1
  320. package/src/plugins/builtin/market-movers/screener.ts +22 -4
  321. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  322. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  323. package/src/plugins/builtin/market-movers/table.ts +17 -5
  324. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  325. package/src/plugins/builtin/mna/headless.ts +2 -0
  326. package/src/plugins/builtin/mna/pane.tsx +3 -1
  327. package/src/plugins/builtin/news/headless.ts +3 -1
  328. package/src/plugins/builtin/news/index.tsx +4 -0
  329. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  330. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  331. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  332. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  333. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  334. package/src/plugins/builtin/openfda/client.ts +14 -2
  335. package/src/plugins/builtin/openfda/index.tsx +3 -2
  336. package/src/plugins/builtin/openfda/model.ts +9 -0
  337. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  338. package/src/plugins/builtin/options/analytics.ts +29 -5
  339. package/src/plugins/builtin/options/enrichment.ts +17 -5
  340. package/src/plugins/builtin/options/table.ts +5 -2
  341. package/src/plugins/builtin/options/types.ts +2 -0
  342. package/src/plugins/builtin/options/view.tsx +17 -4
  343. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  344. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  345. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  346. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  347. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  348. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  349. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  350. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  351. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  352. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  353. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  354. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  355. package/src/plugins/builtin/pe-band/model.ts +12 -49
  356. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  357. package/src/plugins/builtin/perps/cascade.ts +236 -0
  358. package/src/plugins/builtin/perps/client.ts +56 -2
  359. package/src/plugins/builtin/perps/headless.ts +65 -9
  360. package/src/plugins/builtin/perps/index.tsx +7 -5
  361. package/src/plugins/builtin/perps/model.ts +18 -4
  362. package/src/plugins/builtin/perps/pane.tsx +19 -22
  363. package/src/plugins/builtin/perps/status-line.ts +25 -0
  364. package/src/plugins/builtin/plugin-module.ts +5 -1
  365. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  366. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  367. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  368. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  369. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  370. package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -7
  371. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  372. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  373. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
  374. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
  375. package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
  376. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  377. package/src/plugins/builtin/realized-vol/client.ts +13 -5
  378. package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
  379. package/src/plugins/builtin/realized-vol/headless.ts +8 -4
  380. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  381. package/src/plugins/builtin/realized-vol/model.ts +37 -6
  382. package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
  383. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  384. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  385. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  386. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  387. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  388. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  389. package/src/plugins/builtin/research/event-model.ts +40 -12
  390. package/src/plugins/builtin/research/headless.ts +1 -1
  391. package/src/plugins/builtin/research/index.tsx +7 -3
  392. package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
  393. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  394. package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
  395. package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
  396. package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
  397. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  398. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  399. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  400. package/src/plugins/builtin/risk-factors/data.ts +4 -2
  401. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  402. package/src/plugins/builtin/roadmap/index.tsx +28 -0
  403. package/src/plugins/builtin/roadmap/model.ts +67 -0
  404. package/src/plugins/builtin/roadmap/pane.tsx +244 -0
  405. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  406. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  407. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  408. package/src/plugins/builtin/scanner/headless.ts +204 -35
  409. package/src/plugins/builtin/scanner/index.ts +16 -5
  410. package/src/plugins/builtin/scanner/live-headless.ts +48 -5
  411. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  412. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  413. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  414. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  415. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  416. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  417. package/src/plugins/builtin/sec/headless.ts +4 -2
  418. package/src/plugins/builtin/sec/index.tsx +3 -0
  419. package/src/plugins/builtin/series-inventory.ts +2 -0
  420. package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
  421. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  422. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  423. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  424. package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
  425. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  426. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  427. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  428. package/src/plugins/builtin/shared/report-freshness.ts +11 -2
  429. package/src/plugins/builtin/shared/research-cells.tsx +1 -1
  430. package/src/plugins/builtin/shared/series-cache.ts +4 -4
  431. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  432. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  433. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  434. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  435. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  436. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  437. package/src/plugins/builtin/themes/model.ts +16 -7
  438. package/src/plugins/builtin/themes/pane.tsx +5 -5
  439. package/src/plugins/builtin/thirteenf/api.ts +12 -1
  440. package/src/plugins/builtin/thirteenf/data.ts +7 -7
  441. package/src/plugins/builtin/thirteenf/headless.ts +31 -4
  442. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  443. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  444. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  445. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  446. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  447. package/src/plugins/builtin/ticker-detail/overview/model.ts +11 -3
  448. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
  449. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
  450. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  451. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  452. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  453. package/src/plugins/builtin/treasury-auctions/headless.ts +21 -12
  454. package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
  455. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  456. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  457. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  458. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  459. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  460. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  461. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  462. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  463. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  464. package/src/plugins/builtin/volatility/index.tsx +2 -2
  465. package/src/plugins/builtin/volatility/pane.tsx +7 -4
  466. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  467. package/src/plugins/builtin/yield-curve/client.ts +61 -1
  468. package/src/plugins/builtin/yield-curve/headless.ts +9 -6
  469. package/src/plugins/builtin/yield-curve/index.tsx +34 -10
  470. package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
  471. package/src/plugins/catalog-browser.ts +2 -1
  472. package/src/plugins/catalog-manifest.ts +2 -1
  473. package/src/plugins/host-link.ts +1 -1
  474. package/src/plugins/marketplace/model.ts +18 -5
  475. package/src/portfolio/column-values.ts +11 -7
  476. package/src/portfolio/futures-positions.ts +6 -3
  477. package/src/portfolio/position-quantity.ts +15 -9
  478. package/src/remote/resources.ts +2 -1
  479. package/src/renderers/dom/data-table/row.tsx +26 -0
  480. package/src/renderers/dom/desktop/icons.tsx +7 -0
  481. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  482. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  483. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  484. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  485. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  486. package/src/renderers/electrobun/bun/desktop/backend.ts +21 -10
  487. package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
  488. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  489. package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
  490. package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
  491. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  492. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  493. package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
  494. package/src/renderers/electrobun/bun/index.ts +26 -2
  495. package/src/renderers/electrobun/bun/window/create.ts +43 -4
  496. package/src/renderers/electrobun/bun/window/events.ts +20 -0
  497. package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
  498. package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
  499. package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
  500. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  501. package/src/renderers/electrobun/shared/protocol.ts +12 -0
  502. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  503. package/src/renderers/electrobun/view/main.tsx +24 -1
  504. package/src/renderers/electrobun/view/news-source.ts +5 -1
  505. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  506. package/src/renderers/electrobun/view/ui-host.tsx +8 -0
  507. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  508. package/src/renderers/opentui/start.tsx +5 -0
  509. package/src/series/registry.ts +1 -0
  510. package/src/series/types.ts +6 -0
  511. package/src/series/validate.ts +1 -0
  512. package/src/sources/gloomberb-cloud/fred-series.ts +3 -1
  513. package/src/sources/gloomberb-cloud/index.ts +43 -8
  514. package/src/sources/gloomberb-cloud/news.ts +47 -7
  515. package/src/sources/provider-router/financials.ts +15 -3
  516. package/src/sources/provider-router/index.ts +8 -5
  517. package/src/state/ticker-cleanup.ts +50 -0
  518. package/src/sync/controller.ts +4 -3
  519. package/src/sync/core-contributors.ts +90 -31
  520. package/src/telemetry/stall-reports-dom.ts +38 -0
  521. package/src/telemetry/stall-reports-node.ts +75 -0
  522. package/src/telemetry/stall-reports.ts +214 -0
  523. package/src/test-support/preload.ts +14 -0
  524. package/src/theme/themes.ts +10 -0
  525. package/src/tickers/company-name-listing.ts +53 -13
  526. package/src/tickers/company-name.ts +50 -4
  527. package/src/tickers/index-roots.ts +7 -0
  528. package/src/tickers/listing-fate.ts +2 -2
  529. package/src/tickers/listing-venues.ts +15 -3
  530. package/src/tickers/search/index.ts +6 -2
  531. package/src/tickers/search/ranking.ts +5 -1
  532. package/src/tickers/search/upsert.ts +13 -0
  533. package/src/tickers/terminal-codes.ts +19 -0
  534. package/src/tickers/ticker-changes.ts +8 -4
  535. package/src/time-series/alignment.ts +1 -1
  536. package/src/time-series/economic.ts +18 -4
  537. package/src/time-series/field-catalog.ts +1 -1
  538. package/src/time-series/filed-trailing-eps.ts +197 -0
  539. package/src/time-series/fundamentals.ts +44 -1
  540. package/src/time-series/ice-credit.ts +3 -0
  541. package/src/time-series/price-comparison.ts +30 -2
  542. package/src/time-series/resolve-context.ts +3 -1
  543. package/src/time-series/resolve.ts +56 -4
  544. package/src/time-series/session-bar.ts +11 -0
  545. package/src/time-series/spec.ts +3 -2
  546. package/src/time-series/studies.ts +8 -2
  547. package/src/time-series/total-return.ts +160 -0
  548. package/src/time-series/transforms.ts +2 -3
  549. package/src/time-series/types.ts +22 -1
  550. package/src/types/company-from.ts +40 -0
  551. package/src/types/data-provider.ts +7 -0
  552. package/src/types/financials.ts +59 -1
  553. package/src/types/headless.ts +27 -3
  554. package/src/types/news-source.ts +1 -0
  555. package/src/types/plugin.ts +9 -0
  556. package/src/ui/host.tsx +3 -0
  557. package/src/updater/failure.ts +4 -1
  558. package/src/utils/cli-output.ts +42 -1
  559. package/src/utils/crypto-pair.ts +165 -7
  560. package/src/utils/declared-dividends.ts +1 -1
  561. package/src/utils/forward-dividend.ts +2 -0
  562. package/src/utils/fundamentals.ts +60 -8
  563. package/src/utils/fx-market-hours.ts +26 -3
  564. package/src/utils/main-thread-stall.ts +35 -2
  565. package/src/utils/market-capitalization.ts +24 -1
  566. package/src/utils/price-history.ts +7 -1
  567. package/src/utils/shell-word.ts +8 -0
  568. package/src/utils/trailing-dividend.ts +53 -4
  569. package/src/version.ts +2 -2
  570. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -42,7 +42,7 @@ import { appTtmRate, enterpriseValueReconciliationNote, ttmAtAppRate, ttmConvers
42
42
  import { atRegularSessionPrice } from "../../../market-data/quotes/regular-session-valuation";
43
43
  import { zonedDateKey } from "../../../utils/zoned-date-time";
44
44
  import { sharesOutstandingInReceipts } from "../../../utils/depositary-receipt";
45
- import { isReceiptSide, listingDepositaryPair } from "../../../market-data/depositary-pair";
45
+ import { isReceiptSide, listingCedearRatio, listingDepositaryPair } from "../../../market-data/depositary-pair";
46
46
  import {
47
47
  CompactRangeBar,
48
48
  FundamentalsGrid,
@@ -335,6 +335,7 @@ function ResolvedOverviewTab({ width, height, focused = false, ticker, financial
335
335
  nextEarnings: financials?.nextEarnings,
336
336
  depositaryReceipt: sharesOutstandingInReceipts(quote, fundamentals, description),
337
337
  depositaryPair: depositaryPair ? { pair: depositaryPair, receiptSide: isReceiptSide(financials) } : null,
338
+ cedearRatio: listingCedearRatio(financials),
338
339
  // Report and ex-dividend dates are the listing's calendar days.
339
340
  today: zonedDateKey(Date.now(), chartTimeZone ?? "America/New_York"),
340
341
  dividend,
@@ -1,3 +1,4 @@
1
+ import { logFailure } from "../../../utils/log-failure";
1
2
  import { Box } from "../../../ui";
2
3
  import { getCurrentPluginTarget } from "../../current-target";
3
4
  import { recordResearchActivity, recordResearchTabView } from "../../../api-client/research-activity";
@@ -283,7 +284,7 @@ export function TickerResearchPane({ focused, width, height }: PaneProps) {
283
284
  choices={choices.map((item) => ({ ...item, label: t(item.label) }))} />,
284
285
  }).then((templateId) => {
285
286
  if (templateId && choices.some((item) => item.id === templateId)) createPaneFromTemplate(templateId, { symbol: companySymbol });
286
- }).catch(() => {});
287
+ }).catch(logFailure("ticker-detail.pane", "Open company research choices failed"));
287
288
  } }];
288
289
  }, [companySymbol, createPaneFromTemplate, dialog, disabledPlugins, registry, tickerResearchTabsSnapshot]);
289
290
  usePaneMenuItems("ticker-research:go-to-tab", () => {
@@ -304,7 +305,7 @@ export function TickerResearchPane({ focused, width, height }: PaneProps) {
304
305
  ),
305
306
  }).then((tabId) => {
306
307
  if (tabId) selectResearchTab(tabId);
307
- }).catch(() => {});
308
+ }).catch(logFailure("ticker-detail.pane", "Open research tab choices failed"));
308
309
  },
309
310
  }];
310
311
  }, [dialog, resolvedTabId, selectResearchTab, showTabs, tabItems]);
@@ -325,16 +326,21 @@ export function TickerResearchPane({ focused, width, height }: PaneProps) {
325
326
  selectResearchTab(tabId);
326
327
  return true;
327
328
  }, [selectResearchTab, visibleTabIds]);
329
+ // Without a ticker the pane draws only its empty state, and the tab list has
330
+ // no Overview, so the fallback above lands on the first tab that needs no
331
+ // ticker. Nobody opened it, and keeping it mounted would load its data on
332
+ // every ticker switch.
333
+ const shownTabId = ticker && visibleTabIds.has(resolvedTabId) ? resolvedTabId : null;
328
334
  const renderedTabIds = useMemo(() => {
329
335
  const next = new Set<string>();
330
336
  for (const tabId of mountedTabIds) {
331
337
  if (visibleTabIds.has(tabId)) next.add(tabId);
332
338
  }
333
- if (visibleTabIds.has(resolvedTabId)) {
334
- next.add(resolvedTabId);
339
+ if (shownTabId) {
340
+ next.add(shownTabId);
335
341
  }
336
342
  return next;
337
- }, [mountedTabIds, resolvedTabId, visibleTabIds]);
343
+ }, [mountedTabIds, shownTabId, visibleTabIds]);
338
344
 
339
345
  const handlePluginCapture = useCallback((capturing: boolean) => {
340
346
  setPluginCaptured(capturing);
@@ -353,12 +359,12 @@ export function TickerResearchPane({ focused, width, height }: PaneProps) {
353
359
  for (const tabId of current) {
354
360
  if (visibleTabIds.has(tabId)) next.add(tabId);
355
361
  }
356
- if (visibleTabIds.has(resolvedTabId)) {
357
- next.add(resolvedTabId);
362
+ if (shownTabId) {
363
+ next.add(shownTabId);
358
364
  }
359
365
  return sameStringSet(current, next) ? current : next;
360
366
  });
361
- }, [resolvedTabId, visibleTabIds]);
367
+ }, [shownTabId, visibleTabIds]);
362
368
 
363
369
  if (!ticker) {
364
370
  const isEmptyFollowCollection = paneInstance?.binding?.kind === "follow" && !!collectionId && collectionTickerCount === 0;
@@ -1,3 +1,4 @@
1
+ import { logFailure } from "../../../../utils/log-failure";
1
2
  import { PerpEquityRow } from "../../perps/equity-row";
2
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
4
  import { Box, useUiCapabilities } from "../../../../ui";
@@ -173,7 +174,7 @@ export function QuoteMonitorPane({ paneId, focused, width, height }: PaneProps)
173
174
  useEffect(() => {
174
175
  const coordinator = getSharedMarketDataCoordinator();
175
176
  if (!coordinator || !liveStreaming || boardInstruments.length === 0) return;
176
- void coordinator.loadQuotesBatch(boardInstruments).catch(() => {});
177
+ void coordinator.loadQuotesBatch(boardInstruments).catch(logFailure("ticker-detail.quote-monitor", "Warm quote monitor board failed"));
177
178
  }, [boardInstrumentKey, liveStreaming]);
178
179
  const { pinTicker } = usePluginTickerActions();
179
180
  const { openPaneSettings } = usePluginAppActions();
@@ -119,6 +119,7 @@ export function getQuoteMonitorPaneSettings(
119
119
  try {
120
120
  symbols = parseTickerListInput(storedText, MAX_TICKER_LIST_SIZE);
121
121
  } catch {
122
+ // Intentional ignore: unparseable saved ticker text falls back to the legacy symbol or the pane's ticker.
122
123
  symbols = [];
123
124
  }
124
125
  }
@@ -1,7 +1,9 @@
1
1
  import { useEffect, useMemo, useRef, useState } from "react";
2
2
  import { Box } from "../../../ui";
3
3
  import { usePaneSettingValue, useShortcut } from "../../../public/react";
4
- import { splitDelayedStatus, DataTableStackView, KeyValueRow, PaneStatusBody, StatGrid, usePaneFooter, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, usePaneTicker, type DataTableColumn } from "../../../components";
4
+ import { createDataTableCsv } from "../../../components/data-table/export";
5
+ import { dropColumnsToFit } from "../../../components/ui/table-layout";
6
+ import { splitDelayedStatus, DataTableStackView, KeyValueRow, PaneStatusBody, StatGrid, usePaneFooter, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, usePaneTicker, type DataTableCell, type DataTableColumn } from "../../../components";
5
7
  import { useThemeColors } from "../../../theme/theme-context";
6
8
  import type { PaneProps } from "../../../types/plugin";
7
9
  import type { TapeQuote, TapeSnapshot, TapeTrade } from "../../../api-client/tape";
@@ -32,6 +34,12 @@ const QUOTES: DataTableColumn[] = [
32
34
  { id: "askExchange", label: "VENUE", width: 9, align: "left" },
33
35
  { id: "spread", label: "SPREAD BP", width: 10, align: "right" },
34
36
  ];
37
+ /**
38
+ * A narrow pane gives up the venues, then the lots, before the right edge cuts
39
+ * the spread: each pair together, since one side's alone would not say which
40
+ * side it is. The scrollbar keeps one cell, and exports keep every column.
41
+ */
42
+ const QUOTE_DROP_ORDER = [["bidExchange", "askExchange"], ["bidSize", "askSize"]];
35
43
  const rank = (value: number | null) => value == null ? "pctl --" : `${value.toFixed(0)} pctl`;
36
44
  type TapeRow = { id: string; trade?: TapeTrade; quote?: TapeQuote };
37
45
 
@@ -112,13 +120,32 @@ function TimeSalesView({ width, height, focused, symbol, exchange }: PaneProps &
112
120
  info: data ? [...splitDelayedStatus({ id: "feed", quoteStatus: true, parts: [{ text: `${data.feed === "sip" ? "real-time" : "15m delayed"} · ${tapeStampNewYork(data.asOf)}`, tone: "muted" }] }),
113
121
  ...(frozen ? [{ id: "paused", parts: [{ text: "paused", tone: "warning" as const }] }] : []),
114
122
  ...(!data.connected || resource.snapshotOnly ? [{ id: "snapshot", parts: [{ text: "snapshot", tone: "warning" as const }] }] : [])] : [] });
123
+ const quoteColumns = useMemo(() => dropColumnsToFit(QUOTES, width - 1, QUOTE_DROP_ORDER), [width]);
124
+ const columns = tab === "quotes" ? quoteColumns : TRADES;
115
125
  const selectTab = (value: string) => { setTab(value); setDetail(null); setSelected(null); };
116
126
  const { strip: tabStrip, rows: tabRows } = usePaneTabs({ tabs: TABS, activeValue: tab, onSelect: selectTab, focused: focused && !detail, dense: true });
127
+ const renderCell = (row: TapeRow, column: DataTableColumn): DataTableCell => {
128
+ if (row.trade) {
129
+ const trade = row.trade;
130
+ const tick = ticks.get(trade);
131
+ return { text: column.id === "time" ? tapeClockNewYork(trade.timestamp) : column.id === "price" ? tapePrice(trade.price, tradeDigits)
132
+ : column.id === "size" ? tapeQuantity(trade.size) : column.id === "venue" ? tapeVenue(trade.exchange)
133
+ : column.id === "conditions" ? tapeConditionWords(trade.conditions) : trade.tape,
134
+ // Price by tick, as on a tape; a block print lights the rest of its row.
135
+ color: laterThanPin(row) ? colors.textMuted : column.id === "price" ? tick === "up" ? colors.positive : tick === "down" ? colors.negative : colors.text
136
+ : trade.size >= 10_000 ? colors.warning : colors.textMuted };
137
+ }
138
+ const quote = row.quote!, spread = quoteSpread(quote);
139
+ return { text: column.id === "time" ? tapeClockNewYork(quote.timestamp) : column.id === "bid" ? tapePrice(quote.bid, quoteDigits) : column.id === "ask" ? tapePrice(quote.ask, quoteDigits)
140
+ : column.id === "bidSize" ? tapeQuantity(quote.bidSize) : column.id === "askSize" ? tapeQuantity(quote.askSize)
141
+ : column.id === "bidExchange" ? tapeVenue(quote.bidExchange) : column.id === "askExchange" ? tapeVenue(quote.askExchange)
142
+ : spread.bps == null ? "--" : spread.bps.toFixed(2), color: laterThanPin(row) ? colors.textMuted : spread.state === "normal" ? colors.text : colors.warning };
143
+ };
117
144
  if (!data && isCloudSessionRequired(resource.error)) return <SignInWall placement="time-sales-signin" action="view time and sales" needsVerification={session.needsVerification} />;
118
145
  return <Box width={width} height={height} flexDirection="column">
119
146
  {tabStrip}
120
147
  <PaneStatusBody loading={resource.loading && !data} error={!data ? resource.error : null} empty={!!data && !rows.length} subject={tab === "quotes" ? "NBBO observations" : "trade observations"}>
121
- {data && stats ? <DataTableStackView<TapeRow> columns={tab === "quotes" ? QUOTES : TRADES} items={rows}
148
+ {data && stats ? <DataTableStackView<TapeRow> columns={columns} items={rows}
122
149
  focused={focused} rootWidth={width} rootHeight={Math.max(3, height - tabRows)}
123
150
  selection={{ kind: "id", selectedId: selected, getId: (row) => row.id, onChange: setSelected }}
124
151
  onActivate={(row) => { setPaused({ data, epoch: resource.epoch }); setDetail({ row, epoch: resource.epoch }); }} getItemKey={(row) => row.id}
@@ -136,23 +163,8 @@ function TimeSalesView({ width, height, focused, symbol, exchange }: PaneProps &
136
163
  ]),
137
164
  ...(data.session.high != null && data.session.low != null ? [{ id: "session", label: "Session", value: `${tapePrice(data.session.low, sessionDigits)} to ${tapePrice(data.session.high, sessionDigits)}` }] : []),
138
165
  ]} />}
139
- renderCell={(row, column) => {
140
- if (row.trade) {
141
- const trade = row.trade;
142
- const tick = ticks.get(trade);
143
- return { text: column.id === "time" ? tapeClockNewYork(trade.timestamp) : column.id === "price" ? tapePrice(trade.price, tradeDigits)
144
- : column.id === "size" ? tapeQuantity(trade.size) : column.id === "venue" ? tapeVenue(trade.exchange)
145
- : column.id === "conditions" ? tapeConditionWords(trade.conditions) : trade.tape,
146
- // Price by tick, as on a tape; a block print lights the rest of its row.
147
- color: laterThanPin(row) ? colors.textMuted : column.id === "price" ? tick === "up" ? colors.positive : tick === "down" ? colors.negative : colors.text
148
- : trade.size >= 10_000 ? colors.warning : colors.textMuted };
149
- }
150
- const quote = row.quote!, spread = quoteSpread(quote);
151
- return { text: column.id === "time" ? tapeClockNewYork(quote.timestamp) : column.id === "bid" ? tapePrice(quote.bid, quoteDigits) : column.id === "ask" ? tapePrice(quote.ask, quoteDigits)
152
- : column.id === "bidSize" ? tapeQuantity(quote.bidSize) : column.id === "askSize" ? tapeQuantity(quote.askSize)
153
- : column.id === "bidExchange" ? tapeVenue(quote.bidExchange) : column.id === "askExchange" ? tapeVenue(quote.askExchange)
154
- : spread.bps == null ? "--" : spread.bps.toFixed(2), color: laterThanPin(row) ? colors.textMuted : spread.state === "normal" ? colors.text : colors.warning };
155
- }} emptyStateTitle="No observations in this window." /> : null}
166
+ renderCell={renderCell} exportCsv={() => createDataTableCsv({ columns: tab === "quotes" ? QUOTES : TRADES, items: rows, renderCell })}
167
+ emptyStateTitle="No observations in this window." /> : null}
156
168
  </PaneStatusBody>
157
169
  </Box>;
158
170
  }
@@ -28,27 +28,34 @@ import {
28
28
  } from "./model";
29
29
 
30
30
  /**
31
- * A completed auction's missing bid-to-cover or bidder split is a blank cell:
32
- * the source has bid-to-cover regularly from late 1999 and the split only from
33
- * 2008-04-07. "-" stays for an announced auction whose results are not out yet.
31
+ * A missing bid-to-cover or bidder split is a blank cell: the source has
32
+ * bid-to-cover regularly from late 1999 and the split only from 2008-04-07,
33
+ * and an announced auction has no results to fill them; its Rate says so.
34
34
  */
35
- const missing = (row: Record<string, unknown>) => isPendingAuction(row as unknown as TreasuryAuction) ? "-" : "";
36
- const formatPercent = (value: unknown, row: Record<string, unknown>) => value == null ? missing(row) : `${Number(value).toFixed(1)}%`;
35
+ const formatPercent = (value: unknown) => value == null ? "" : `${Number(value).toFixed(1)}%`;
36
+
37
+ /** What an announced auction's Rate reads until the source publishes its results. */
38
+ const RESULTS_NOT_PUBLISHED = "results not published";
39
+ const pending = (row: Record<string, unknown>) => isPendingAuction(row as unknown as TreasuryAuction);
37
40
 
38
41
  /** Why the stop-out column is not a tail; the docs page says the same at length. */
39
42
  const STOP_OUT_NOTE = "Stop-out vs avg is the high yield minus the average/median yield, in bp (bills: discount rates). "
40
43
  + "A true tail, the stop-out against the 1 pm when-issued yield, needs the when-issued yield, which the source does not carry.";
41
44
 
42
45
  const MISSING_METRICS_NOTE = "A blank B/C or bidder share is one the source does not have for that auction: "
43
- + "bid-to-cover is regular from late 1999, the indirect, direct and dealer split from 2008-04-07. - is a result not out yet.";
46
+ + "bid-to-cover is regular from late 1999, the indirect, direct and dealer split from 2008-04-07.";
44
47
 
45
48
  /** A completed auction the source gives no bid-to-cover or no bidder split for. */
46
49
  const lacksMetrics = (auction: TreasuryAuction) => !isPendingAuction(auction)
47
50
  && (auction.bidToCoverRatio == null || indirectPct(auction) == null || directPct(auction) == null || dealerPct(auction) == null);
48
51
 
49
- /** The rate as the table prints it; a TIPS or FRN row says what its number is (`2.653% real`, `4.0bp spread`). */
52
+ /**
53
+ * The rate as the table prints it; a TIPS or FRN row says what its number is
54
+ * (`2.653% real`, `4.0bp spread`), and an announced auction that its results
55
+ * are not published yet.
56
+ */
50
57
  const rateCell = (value: unknown, row: Record<string, unknown>) => {
51
- const text = formatAuctionRate(row as unknown as TreasuryAuction, value == null ? null : Number(value), "-");
58
+ const text = formatAuctionRate(row as unknown as TreasuryAuction, value == null ? null : Number(value), pending(row) ? RESULTS_NOT_PUBLISHED : "-");
52
59
  const qualifier = value == null ? null : rateQualifier(row as unknown as TreasuryAuction);
53
60
  return qualifier ? `${text} ${qualifier}` : text;
54
61
  };
@@ -61,14 +68,16 @@ const COLUMNS = [
61
68
  key: "rate",
62
69
  header: "Rate",
63
70
  align: "right" as const,
64
- format: (value: unknown, row: Record<string, unknown>) => formatAuctionRate(row as unknown as TreasuryAuction, value == null ? null : Number(value), "-"),
71
+ format: (value: unknown, row: Record<string, unknown>) => formatAuctionRate(
72
+ row as unknown as TreasuryAuction, value == null ? null : Number(value), pending(row) ? RESULTS_NOT_PUBLISHED : "-",
73
+ ),
65
74
  textFormat: rateCell,
66
75
  },
67
- { key: "stopOutVsAvgBp", header: "Stop-out vs avg (bp)", align: "right" as const, format: (value: unknown) => value == null ? "-" : Number(value).toFixed(1) },
68
76
  {
69
- key: "bidToCoverRatio", header: "B/C", align: "right" as const,
70
- format: (value: unknown, row: Record<string, unknown>) => value == null ? missing(row) : Number(value).toFixed(2),
77
+ key: "stopOutVsAvgBp", header: "Stop-out vs avg (bp)", align: "right" as const,
78
+ format: (value: unknown, row: Record<string, unknown>) => value == null ? pending(row) ? "" : "-" : Number(value).toFixed(1),
71
79
  },
80
+ { key: "bidToCoverRatio", header: "B/C", align: "right" as const, format: (value: unknown) => value == null ? "" : Number(value).toFixed(2) },
72
81
  { key: "indirectPercent", header: "Indirect", align: "right" as const, format: formatPercent },
73
82
  { key: "directPercent", header: "Direct", align: "right" as const, format: formatPercent },
74
83
  { key: "dealerPercent", header: "Dealer", align: "right" as const, format: formatPercent },
@@ -105,6 +105,9 @@ function secTypeColor(secType: string, selected: boolean): string {
105
105
  }
106
106
  }
107
107
 
108
+ /** An announced auction's Rate in the table, the word other panes use for a figure not out yet (KPI's Later actual). */
109
+ const RESULTS_PENDING = "Pending";
110
+
108
111
  // Stable table adapter so memoized rows survive pane re-renders.
109
112
  const auctionKey = (auction: TreasuryAuction) => auction.id;
110
113
 
@@ -119,8 +122,8 @@ function renderAuctionCell(
119
122
 
120
123
  switch (column.id) {
121
124
  case "date":
122
- // Announced auctions have no results yet; every metric cell reads "—",
123
- // so the date carries the distinction instead of a second placeholder.
125
+ // Announced auctions have no results yet: Rate reads Pending, the other
126
+ // metric cells "—", and the date is bright.
124
127
  return {
125
128
  text: showYear ? auction.auctionDate : formatShortDate(auction.auctionDate, { year: false, utc: true, fallback: "\u2014" }),
126
129
  color: rowState.selected ? colors.selectedText : isPendingAuction(auction) ? colors.textBright : colors.textDim,
@@ -134,7 +137,10 @@ function renderAuctionCell(
134
137
  case "term":
135
138
  return { text: auction.securityTerm, color: selected ?? colors.text };
136
139
  case "rate":
137
- return { text: formatAuctionRate(auction, rateValue(auction), "—"), color: selected ?? colors.textBright };
140
+ // An announced auction says its results are pending, as its detail does; other metric cells stay "—".
141
+ return isPendingAuction(auction)
142
+ ? { text: RESULTS_PENDING, color: dimmed }
143
+ : { text: formatAuctionRate(auction, rateValue(auction), "—"), color: selected ?? colors.textBright };
138
144
  case "stopOut":
139
145
  return { text: formatBp(stopOutVsAverageBp(auction)), color: selected ?? colors.text };
140
146
  case "btc":
@@ -1,8 +1,16 @@
1
1
  import { httpFetch } from "../../../utils/http-transport";
2
2
  import { numericOrNull, scalarTextOrNull } from "../../../utils/guards";
3
- import type { CashAccount, DebtDay, OperatingCash } from "./model";
3
+ import { cashLineLabel, type CashAccount, type DebtDay, type OperatingCash, type TgaClose } from "./model";
4
4
 
5
5
  const CASH_URL = "https://api.fiscaldata.treasury.gov/services/api/fiscal_service/v1/accounting/dts/operating_cash_balance?sort=-record_date&page[size]=40";
6
+ // One line of the statement, every day since it took its present form in 2022: about a thousand closes.
7
+ const TGA_CLOSING_ACCOUNT = "Treasury General Account (TGA) Closing Balance";
8
+ const TGA_HISTORY_URL = `https://api.fiscaldata.treasury.gov/services/api/fiscal_service/v1/accounting/dts/operating_cash_balance?${new URLSearchParams({
9
+ filter: `account_type:eq:${TGA_CLOSING_ACCOUNT}`,
10
+ fields: "record_date,open_today_bal",
11
+ sort: "-record_date",
12
+ "page[size]": "3000",
13
+ })}`;
6
14
  const DEBT_URL = "https://api.fiscaldata.treasury.gov/services/api/fiscal_service/v2/accounting/od/debt_to_penny?sort=-record_date&page[size]=20";
7
15
  const FETCH_TIMEOUT_MS = 15_000;
8
16
  const DEBT_DAYS = 20;
@@ -10,6 +18,9 @@ const DEBT_DAYS = 20;
10
18
  export interface TreasuryDaily {
11
19
  cash: OperatingCash;
12
20
  debt: DebtDay[];
21
+ /** The Treasury General Account's closing balance each day, oldest first. Empty when it could not be read, with the reason. */
22
+ tga: TgaClose[];
23
+ tgaError?: string;
13
24
  }
14
25
 
15
26
  function recordDate(value: unknown): string | null {
@@ -39,7 +50,7 @@ function parseOperatingCash(payload: unknown): OperatingCash {
39
50
  parsed.push({
40
51
  id: `${date}:${line ?? account}`,
41
52
  date,
42
- account,
53
+ account: cashLineLabel(account),
43
54
  // Since the 2022 statement format the day's figures are in open_today_bal
44
55
  // and close_today_bal is always "null".
45
56
  today: numericOrNull(row.open_today_bal),
@@ -60,6 +71,17 @@ function parseOperatingCash(payload: unknown): OperatingCash {
60
71
  };
61
72
  }
62
73
 
74
+ function parseTgaHistory(payload: unknown): TgaClose[] {
75
+ const byDate = new Map<string, number>();
76
+ for (const row of rowsOf(payload)) {
77
+ const date = recordDate(row.record_date);
78
+ // The closing balance is sent in open_today_bal, as the statement's other lines are.
79
+ const millions = numericOrNull(row.open_today_bal);
80
+ if (date && millions != null && !byDate.has(date)) byDate.set(date, millions);
81
+ }
82
+ return [...byDate].map(([date, millions]) => ({ date, millions })).sort((a, b) => a.date.localeCompare(b.date));
83
+ }
84
+
63
85
  function parseDebtToPenny(payload: unknown): DebtDay[] {
64
86
  const byDate = new Map<string, DebtDay>();
65
87
  for (const row of rowsOf(payload)) {
@@ -87,10 +109,24 @@ async function readJson(url: string, label: string, signal?: AbortSignal): Promi
87
109
  return response.json() as Promise<unknown>;
88
110
  }
89
111
 
90
- export async function fetchTreasuryDaily(signal?: AbortSignal): Promise<TreasuryDaily> {
91
- const [cash, debt] = await Promise.all([
112
+ /**
113
+ * The latest day's cash lines and the last 20 days of debt, with the account's
114
+ * closing balance history unless `history` is off (a report that does not draw it).
115
+ * A history that cannot be read leaves the figures as they are.
116
+ */
117
+ export async function fetchTreasuryDaily(signal?: AbortSignal, { history = true }: { history?: boolean } = {}): Promise<TreasuryDaily> {
118
+ const [cash, debt, tga] = await Promise.all([
92
119
  readJson(CASH_URL, "Treasury cash", signal),
93
120
  readJson(DEBT_URL, "Treasury debt", signal),
121
+ history ? readJson(TGA_HISTORY_URL, "Treasury account history", signal).then(parseTgaHistory).then(
122
+ (closes) => ({ closes }),
123
+ (error: unknown) => ({ closes: [] as TgaClose[], error: error instanceof Error ? error.message : String(error) }),
124
+ ) : { closes: [] as TgaClose[] },
94
125
  ]);
95
- return { cash: parseOperatingCash(cash), debt: parseDebtToPenny(debt) };
126
+ return {
127
+ cash: parseOperatingCash(cash),
128
+ debt: parseDebtToPenny(debt),
129
+ tga: tga.closes,
130
+ ...("error" in tga ? { tgaError: tga.error } : {}),
131
+ };
96
132
  }
@@ -3,7 +3,7 @@ import { fetchTreasuryDaily } from "./client";
3
3
  import { formatBillions, formatTrillions } from "./model";
4
4
 
5
5
  const CASH_COLUMNS: HeadlessPaneColumn[] = [
6
- { key: "account", header: "Account" },
6
+ { key: "account", header: "Treasury General Account" },
7
7
  { key: "today", header: "Today $bn", align: "right", description: "The day's figure, billions of dollars", format: formatBillions },
8
8
  { key: "month", header: "This month $bn", align: "right", description: "Month to date, billions of dollars", format: formatBillions },
9
9
  ];
@@ -32,7 +32,7 @@ export const treasuryDailyHeadless: HeadlessPaneDefinition<"bundle"> = {
32
32
  describe: "Treasury operating cash and public debt",
33
33
  freshness: { source: "US Treasury", status: "not-a-feed", basis: "daily statement" },
34
34
  async load(_args, ctx) {
35
- const { cash, debt } = await fetchTreasuryDaily(ctx.signal);
35
+ const { cash, debt } = await fetchTreasuryDaily(ctx.signal, { history: false });
36
36
  return {
37
37
  complete: cash.rows.length > 0 && debt.length > 0,
38
38
  sections: [
@@ -8,7 +8,7 @@ export const treasuryDailyModule: PluginModule = {
8
8
  id: "treasury-daily-pane",
9
9
  paneId: TREASURY_DAILY_PANE_ID,
10
10
  label: "Treasury Cash and Debt",
11
- description: "Treasury operating cash for the latest day, and debt held by the public for the last 20 business days.",
11
+ description: "Treasury operating cash for the latest day with the Treasury General Account's closing balance history, and debt held by the public for the last 20 business days.",
12
12
  keywords: ["treasury", "dts", "debt", "cash", "tga", "debt to the penny", "operating cash"],
13
13
  shortcut: { prefix: "DTS" },
14
14
  headless: treasuryDailyHeadless,
@@ -1,4 +1,5 @@
1
1
  import type { DataTableColumn } from "../../../components";
2
+ import { formatNumber } from "../../../utils/format";
2
3
 
3
4
  export const TREASURY_DAILY_PANE_ID = "treasury-daily";
4
5
 
@@ -23,6 +24,51 @@ export interface OperatingCash {
23
24
  rows: CashAccount[];
24
25
  }
25
26
 
27
+ /** One day's Treasury General Account closing balance, millions of dollars. */
28
+ export interface TgaClose {
29
+ date: string;
30
+ millions: number;
31
+ }
32
+
33
+ /** The longest stretch of closing balances the Cash tab charts, and the ranges it offers. */
34
+ export type TgaRange = "3M" | "6M" | "1Y" | "ALL";
35
+ export const TGA_RANGES: ReadonlyArray<{ value: TgaRange; label: string; hint: string; months: number | null }> = [
36
+ { value: "3M", label: "3M", hint: "1", months: 3 },
37
+ { value: "6M", label: "6M", hint: "2", months: 6 },
38
+ { value: "1Y", label: "1Y", hint: "3", months: 12 },
39
+ { value: "ALL", label: "All", hint: "4", months: null },
40
+ ];
41
+ const DEFAULT_TGA_RANGE: TgaRange = "1Y";
42
+
43
+ export function tgaRange(value: unknown): TgaRange {
44
+ return TGA_RANGES.find((range) => range.value === value)?.value ?? DEFAULT_TGA_RANGE;
45
+ }
46
+
47
+ /** The closes of the last `range`, counted back from the newest one, oldest first. */
48
+ export function tgaClosesInRange(closes: readonly TgaClose[], range: TgaRange): TgaClose[] {
49
+ const months = TGA_RANGES.find((entry) => entry.value === range)?.months ?? null;
50
+ const last = closes.at(-1);
51
+ if (months == null || !last) return [...closes];
52
+ const start = new Date(`${last.date}T00:00:00Z`);
53
+ start.setUTCMonth(start.getUTCMonth() - months);
54
+ const from = start.toISOString().slice(0, 10);
55
+ return closes.filter((close) => close.date >= from);
56
+ }
57
+
58
+ /**
59
+ * What the Treasury's statement calls a line of the Treasury General Account,
60
+ * said plainly: `Treasury General Account (TGA) Opening Balance` is the
61
+ * opening balance, `Total TGA Withdrawals (Table II) (-)` the withdrawals. A
62
+ * line the statement adds later keeps its own words, less the table reference.
63
+ */
64
+ export function cashLineLabel(account: string): string {
65
+ if (/^Treasury General Account \(TGA\) Opening Balance$/i.test(account)) return "Opening balance";
66
+ if (/^Treasury General Account \(TGA\) Closing Balance$/i.test(account)) return "Closing balance";
67
+ if (/^Total TGA Deposits\b/i.test(account)) return "Deposits";
68
+ if (/^Total TGA Withdrawals\b/i.test(account)) return "Withdrawals";
69
+ return account.replace(/\s*\(Table [IVX]+\)/g, "").replace(/\s*\(-\)$/, "").trim();
70
+ }
71
+
26
72
  export interface DebtDay {
27
73
  id: string;
28
74
  date: string;
@@ -40,7 +86,7 @@ export type CashColumn = DataTableColumn & { id: CashColumnId };
40
86
  export type DebtColumn = DataTableColumn & { id: DebtColumnId };
41
87
 
42
88
  export const CASH_COLUMNS: CashColumn[] = [
43
- { id: "account", label: "Account", width: 28, align: "left", flexGrow: 1 },
89
+ { id: "account", label: "Treasury General Account", width: 26, align: "left", flexGrow: 1 },
44
90
  { id: "today", label: "Today $bn", width: 12, align: "right" },
45
91
  { id: "month", label: "This month $bn", width: 15, align: "right" },
46
92
  ];
@@ -52,10 +98,11 @@ export const DEBT_COLUMNS: DebtColumn[] = [
52
98
  { id: "total", label: "Total $tn", width: 12, align: "right" },
53
99
  ];
54
100
 
101
+ /** Two decimals with thousands separators, as every money table writes a large figure: `2,594.63`. */
55
102
  function scaled(value: unknown, divisor: number): string {
56
103
  if (typeof value !== "number" || !Number.isFinite(value)) return "--";
57
- const text = (value / divisor).toFixed(2);
58
- return text === "-0.00" ? "0.00" : text;
104
+ const rounded = Number((value / divisor).toFixed(2));
105
+ return formatNumber(rounded === 0 ? 0 : rounded, 2);
59
106
  }
60
107
 
61
108
  /** Millions of dollars, drawn as billions. */
@@ -1,33 +1,52 @@
1
1
  import { useCallback, useMemo, useState } from "react";
2
2
  import {
3
+ ChartTableHeader,
3
4
  DataTableView,
4
5
  PaneStatusBody,
6
+ QueryBar,
7
+ usePaneNoticeFooter,
5
8
  usePaneStatusFooter,
6
9
  usePaneTabs,
10
+ type ChartTableChart,
7
11
  type DataTableCell,
12
+ type DataTableKeyEvent,
8
13
  } from "../../../components";
9
- import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
14
+ import { spanAxisFormatter } from "../../../components/chart-table";
15
+ import { useChartViewReset } from "../../../components/chart/core/view-reset";
16
+ import { scalarPoint, staticSeries } from "../../../components/chart/static/series";
17
+ import { handleRefreshKey, usePaneRefreshKey } from "../../../components/data-table/table-pane";
10
18
  import { useAsyncResource, useAutoRefresh, usePluginPaneState } from "../../../public/react";
11
19
  import { useThemeColors } from "../../../theme/theme-context";
12
20
  import type { PaneProps } from "../../../types/plugin";
13
21
  import { Box } from "../../../ui";
22
+ import { formatNumber } from "../../../utils/format";
23
+ import { isPlainKeyboardEvent } from "../../../utils/keyboard";
14
24
  import { fetchTreasuryDaily } from "./client";
15
25
  import {
16
26
  CASH_COLUMNS,
17
27
  DEBT_COLUMNS,
28
+ TGA_RANGES,
18
29
  TREASURY_DAILY_PANE_ID,
19
30
  TREASURY_TABS,
20
31
  formatBillions,
21
32
  formatTrillions,
33
+ tgaClosesInRange,
34
+ tgaRange,
22
35
  type CashAccount,
23
36
  type CashColumn,
24
37
  type DebtColumn,
25
38
  type DebtDay,
39
+ type TgaRange,
26
40
  type TreasuryTab,
27
41
  } from "./model";
28
42
 
29
43
  const EMPTY_CASH: CashAccount[] = [];
30
44
  const EMPTY_DEBT: DebtDay[] = [];
45
+ const RANGE_OPTIONS = TGA_RANGES.map(({ value, label, hint }) => ({ value, label, hint }));
46
+ const TGA_SERIES_LABEL = "TGA closing balance $bn";
47
+ /** Ticks in billions, with thousands separators and the decimals the plotted range needs. */
48
+ const formatBillionsAxis = spanAxisFormatter((value, digits) => formatNumber(value, digits));
49
+ const formatBillionsLevel = (value: number) => formatNumber(value, 2);
31
50
 
32
51
  function scaledCell(value: number | null, text: string, muted: string): DataTableCell {
33
52
  return value == null ? { text, value, color: muted } : { text, value };
@@ -44,6 +63,9 @@ export function TreasuryDailyPane({ width, height, focused }: PaneProps) {
44
63
  const debtRows = data?.debt ?? EMPTY_DEBT;
45
64
  const activeRows = tab === "cash" ? cashRows : debtRows;
46
65
  const recordDate = tab === "cash" ? data?.cash.recordDate ?? null : debtRows[0]?.date ?? null;
66
+ const [storedRange, setStoredRange] = usePluginPaneState<string>("range", "1Y");
67
+ const range = tgaRange(storedRange);
68
+ const resetChartView = useChartViewReset();
47
69
  const [selectedId, setSelectedId] = useState<string | null>(null);
48
70
  const resolvedId = activeRows.some((row) => row.id === selectedId) ? selectedId : activeRows[0]?.id ?? null;
49
71
  const info = useMemo(
@@ -58,13 +80,46 @@ export function TreasuryDailyPane({ width, height, focused }: PaneProps) {
58
80
  dense: true,
59
81
  });
60
82
  useAutoRefresh(resource.updatedAt, resource.load);
61
- usePaneRefreshKey(() => void resource.reload(), { focused });
83
+ const refresh = useCallback(() => void resource.reload(), [resource.reload]);
84
+ usePaneRefreshKey(refresh, { focused });
62
85
  usePaneStatusFooter({
63
86
  registrationId: TREASURY_DAILY_PANE_ID,
64
87
  loading: resource.loading,
65
88
  error: resource.error,
66
89
  info,
67
90
  });
91
+ usePaneNoticeFooter({
92
+ registrationId: `${TREASURY_DAILY_PANE_ID}:history`,
93
+ focused: focused && tab === "cash",
94
+ notices: data?.tgaError ? [`Closing balance history is unavailable: ${data.tgaError}`] : [],
95
+ });
96
+
97
+ // The closing balance the table's last line states for the day, drawn over the range chosen.
98
+ const series = useMemo(() => {
99
+ const closes = tgaClosesInRange(data?.tga ?? [], range);
100
+ return closes.length < 2 ? [] : [staticSeries(
101
+ closes.map((close) => scalarPoint(new Date(`${close.date}T00:00:00Z`), close.millions / 1_000)),
102
+ { id: "tga-closing-balance", label: TGA_SERIES_LABEL, color: colors.positive, calendarSpaced: true },
103
+ )];
104
+ }, [colors.positive, data?.tga, range]);
105
+ const chart: ChartTableChart | null = data?.tgaError ? null : {
106
+ series,
107
+ formatValue: formatBillionsLevel,
108
+ formatAxisValue: formatBillionsAxis,
109
+ remoteKind: "treasury-general-account",
110
+ empty: "No closing balance history.",
111
+ };
112
+ // The number keys pick the range, as the bar's hints say; picking the one showing sends the chart back to it.
113
+ const handleKeyDown = useCallback((event: DataTableKeyEvent) => {
114
+ if (handleRefreshKey(event, refresh, { stopPropagation: true })) return true;
115
+ if (!isPlainKeyboardEvent(event)) return false;
116
+ const picked = TGA_RANGES.find((option) => option.hint === event.name);
117
+ if (!picked) return false;
118
+ event.preventDefault?.();
119
+ if (picked.value === range) resetChartView();
120
+ else setStoredRange(picked.value);
121
+ return true;
122
+ }, [range, refresh, resetChartView, setStoredRange]);
68
123
 
69
124
  const bodyHeight = Math.max(1, height - tabRows);
70
125
  const renderCash = useCallback((row: CashAccount, column: CashColumn): DataTableCell => {
@@ -91,6 +146,30 @@ export function TreasuryDailyPane({ width, height, focused }: PaneProps) {
91
146
  focused={focused}
92
147
  rootWidth={width}
93
148
  rootHeight={bodyHeight}
149
+ rootBefore={chart ? (
150
+ <ChartTableHeader
151
+ width={width}
152
+ height={bodyHeight}
153
+ tableRows={cashRows.length}
154
+ tableColumns={CASH_COLUMNS}
155
+ query={(
156
+ <QueryBar
157
+ width={width}
158
+ filters={[{
159
+ id: "range",
160
+ label: "Range",
161
+ inline: true,
162
+ chartRange: true,
163
+ value: range,
164
+ options: RANGE_OPTIONS,
165
+ onChange: (value: string) => setStoredRange(tgaRange(value) satisfies TgaRange),
166
+ }]}
167
+ />
168
+ )}
169
+ chart={chart}
170
+ />
171
+ ) : undefined}
172
+ onRootKeyDown={handleKeyDown}
94
173
  columns={CASH_COLUMNS}
95
174
  items={cashRows}
96
175
  sortColumnId={null}