@gloom-sh/terminal 0.18.5 → 0.19.0

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 (436) hide show
  1. package/package.json +1 -1
  2. package/patches/electrobun@1.18.1.patch +140 -7
  3. package/src/api-client/cot.ts +6 -2
  4. package/src/api-client/credit-documents.ts +6 -0
  5. package/src/api-client/data.ts +17 -6
  6. package/src/api-client/equity-screener.ts +24 -0
  7. package/src/api-client/fund-facts.ts +45 -4
  8. package/src/api-client/index.ts +11 -0
  9. package/src/api-client/market-discovery.ts +10 -0
  10. package/src/api-client/market-movers.ts +7 -0
  11. package/src/api-client/members.ts +1 -1
  12. package/src/api-client/perps.ts +43 -0
  13. package/src/api-client/research-activity.ts +3 -2
  14. package/src/api-client/types.ts +70 -2
  15. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  16. package/src/app/runtime/broker-import.ts +0 -2
  17. package/src/app/runtime/plugin-bindings.ts +73 -56
  18. package/src/architecture/pane-tabs-allowlist.json +3 -0
  19. package/src/architecture/ttl-caches-allowlist.json +19 -0
  20. package/src/brokers/sync-broker-instance.ts +2 -8
  21. package/src/capabilities/chart-series.ts +1 -1
  22. package/src/cli/commands/crypto-hints.ts +40 -24
  23. package/src/cli/commands/market.ts +72 -26
  24. package/src/cli/commands/options-chain.ts +48 -7
  25. package/src/cli/commands/options-leaps.ts +9 -3
  26. package/src/cli/commands/overview.ts +61 -12
  27. package/src/cli/commands/search.ts +21 -1
  28. package/src/cli/commands/themes.ts +11 -2
  29. package/src/cli/commands/ticker.ts +55 -34
  30. package/src/cli/company-name-hint.ts +41 -0
  31. package/src/cli/earnings-calendar.ts +1 -1
  32. package/src/cli/errors.ts +8 -0
  33. package/src/cli/glossary.ts +25 -2
  34. package/src/cli/history-rows.ts +57 -9
  35. package/src/cli/index.ts +2 -0
  36. package/src/cli/listing-arg.ts +180 -52
  37. package/src/cli/output-file.ts +68 -0
  38. package/src/cli/pane-functions/capabilities.ts +3 -0
  39. package/src/cli/pane-functions/catalog.ts +80 -7
  40. package/src/cli/pane-functions/dom.ts +13 -3
  41. package/src/cli/pane-functions/freshness.ts +86 -3
  42. package/src/cli/pane-functions/function-help.ts +13 -10
  43. package/src/cli/pane-functions/headless.ts +17 -7
  44. package/src/cli/pane-functions/index.ts +49 -14
  45. package/src/cli/pane-functions/market-session.ts +66 -4
  46. package/src/cli/pane-functions/options.ts +19 -1
  47. package/src/cli/pane-functions/resolver.ts +19 -8
  48. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  49. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  50. package/src/cli/quote-answer.ts +22 -11
  51. package/src/cli/ticker-resolution.ts +3 -2
  52. package/src/components/chart/composite/composite-chart.tsx +4 -0
  53. package/src/components/chart/composite/interactions.ts +6 -0
  54. package/src/components/chart/composite/legend.tsx +45 -7
  55. package/src/components/chart/composite/panel-gestures.ts +33 -0
  56. package/src/components/chart/composite/panel-surface.tsx +52 -3
  57. package/src/components/chart/composite/rasterizer.ts +24 -13
  58. package/src/components/chart/composite/scene.ts +39 -4
  59. package/src/components/chart/composite/series-cues.ts +14 -0
  60. package/src/components/chart/composite/text-renderer.ts +9 -2
  61. package/src/components/chart/composite/tool-catalog.ts +36 -0
  62. package/src/components/chart/composite/tools.ts +203 -44
  63. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  64. package/src/components/command-bar/assist/inventory.ts +2 -1
  65. package/src/components/command-bar/commands/direct/index.ts +28 -13
  66. package/src/components/command-bar/pane-templates/items.ts +3 -2
  67. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  68. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  69. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  70. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  71. package/src/components/onboarding/wizard-positions.ts +3 -3
  72. package/src/components/ticker/list-table-view.tsx +4 -1
  73. package/src/components/ui/choice-dialog.tsx +23 -18
  74. package/src/components/ui/choice-groups.ts +18 -0
  75. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  76. package/src/components/ui/data-table/opentui/model.ts +26 -1
  77. package/src/components/ui/data-table/types.ts +2 -0
  78. package/src/components/ui/list-view.tsx +8 -4
  79. package/src/components/ui/query-bar-keys.tsx +2 -0
  80. package/src/components/ui/query-bar.tsx +1 -0
  81. package/src/components/ui/select-button.tsx +5 -1
  82. package/src/components/ui/select-field.tsx +1 -0
  83. package/src/components/ui/table-layout.ts +8 -5
  84. package/src/core/app-runtime.ts +2 -0
  85. package/src/core/app-services.ts +2 -2
  86. package/src/data/config/chart-settings.ts +1 -1
  87. package/src/data/json-ticker-repository.ts +17 -8
  88. package/src/data/ticker-repository.ts +16 -9
  89. package/src/data/ticker-writes.ts +81 -0
  90. package/src/data/ttl-cache.ts +165 -0
  91. package/src/docs/snapshot.json +71 -137
  92. package/src/i18n/es.ts +18 -2
  93. package/src/i18n/ja.ts +18 -2
  94. package/src/i18n/ko.ts +18 -2
  95. package/src/i18n/zh-cn.ts +18 -2
  96. package/src/i18n/zh-tw.ts +18 -2
  97. package/src/market-data/depositary-pair.ts +2 -2
  98. package/src/market-data/fund-facts.ts +50 -0
  99. package/src/market-data/market/change-basis.ts +16 -3
  100. package/src/market-data/market/format.ts +25 -0
  101. package/src/market-data/market/freshness.ts +2 -1
  102. package/src/market-data/options-alternatives.ts +81 -4
  103. package/src/news/aggregator.ts +113 -9
  104. package/src/news/cloud-session.ts +21 -0
  105. package/src/news/hooks.ts +1 -0
  106. package/src/news/news-model.ts +1 -0
  107. package/src/news/types.ts +11 -0
  108. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  109. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  110. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  111. package/src/plugins/builtin/alerts/cli.ts +336 -77
  112. package/src/plugins/builtin/alerts/command.ts +25 -1
  113. package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
  114. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  115. package/src/plugins/builtin/alerts/format.ts +75 -5
  116. package/src/plugins/builtin/alerts/headless.ts +24 -6
  117. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  118. package/src/plugins/builtin/alerts/history.ts +1 -1
  119. package/src/plugins/builtin/alerts/index.tsx +79 -16
  120. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  121. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  122. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  123. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  124. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  125. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  126. package/src/plugins/builtin/alerts/storage.ts +12 -8
  127. package/src/plugins/builtin/alerts/types.ts +4 -0
  128. package/src/plugins/builtin/analytics/index.tsx +2 -1
  129. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  130. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  131. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  132. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  133. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  134. package/src/plugins/builtin/attention/index.tsx +2 -1
  135. package/src/plugins/builtin/attention/listing.ts +14 -0
  136. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  137. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  138. package/src/plugins/builtin/buildout/format.ts +38 -0
  139. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  140. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  141. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  142. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  143. package/src/plugins/builtin/changelog/index.tsx +9 -6
  144. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  145. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  146. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  147. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  148. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  149. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  150. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  151. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  152. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  153. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  154. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  155. package/src/plugins/builtin/clinical-trials/model.ts +0 -9
  156. package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
  157. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
  158. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  159. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  160. package/src/plugins/builtin/company-attention/client.ts +8 -9
  161. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  162. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  163. package/src/plugins/builtin/company-attention/pane.tsx +9 -2
  164. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  165. package/src/plugins/builtin/correlation/headless.ts +13 -5
  166. package/src/plugins/builtin/correlation/history.ts +22 -0
  167. package/src/plugins/builtin/cot/client.ts +22 -18
  168. package/src/plugins/builtin/cot/headless.ts +15 -10
  169. package/src/plugins/builtin/cot/model.ts +14 -1
  170. package/src/plugins/builtin/cot/pane.tsx +22 -14
  171. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  172. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  173. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  174. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  175. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  176. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  177. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  178. package/src/plugins/builtin/credit-documents/headless.ts +7 -7
  179. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  180. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  181. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  182. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  183. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  184. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  185. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  186. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  187. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  188. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  189. package/src/plugins/builtin/docs/targets.ts +5 -1
  190. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  191. package/src/plugins/builtin/earnings/board.tsx +62 -71
  192. package/src/plugins/builtin/earnings/headless.ts +40 -4
  193. package/src/plugins/builtin/earnings/scope.ts +31 -43
  194. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  195. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  196. package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
  197. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  198. package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
  199. package/src/plugins/builtin/econ/types.ts +2 -0
  200. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  201. package/src/plugins/builtin/econ-statistics/headless.ts +6 -4
  202. package/src/plugins/builtin/econ-statistics/stats.ts +36 -9
  203. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  204. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  205. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  206. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  207. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  208. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  209. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  210. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  211. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  212. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  213. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  214. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  215. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  216. package/src/plugins/builtin/gpu/headless.ts +8 -2
  217. package/src/plugins/builtin/help/function-index.ts +4 -1
  218. package/src/plugins/builtin/help/index.tsx +1 -0
  219. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  220. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  221. package/src/plugins/builtin/holders/headless.ts +18 -1
  222. package/src/plugins/builtin/iv-history/client.ts +52 -11
  223. package/src/plugins/builtin/iv-history/format.ts +36 -23
  224. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  225. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  226. package/src/plugins/builtin/iv-history/model.ts +163 -27
  227. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  228. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  229. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  230. package/src/plugins/builtin/jobs/client.ts +25 -29
  231. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  232. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  233. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  234. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  235. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  236. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  237. package/src/plugins/builtin/market-movers/client.ts +25 -7
  238. package/src/plugins/builtin/market-movers/headless.ts +32 -12
  239. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  240. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  241. package/src/plugins/builtin/market-movers/model.ts +4 -1
  242. package/src/plugins/builtin/market-movers/screener.ts +14 -0
  243. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  244. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  245. package/src/plugins/builtin/market-movers/table.ts +17 -5
  246. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  247. package/src/plugins/builtin/news/index.tsx +1 -0
  248. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  249. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  250. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  251. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  252. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  253. package/src/plugins/builtin/openfda/client.ts +14 -2
  254. package/src/plugins/builtin/openfda/index.tsx +3 -2
  255. package/src/plugins/builtin/openfda/model.ts +9 -0
  256. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  257. package/src/plugins/builtin/options/analytics.ts +29 -5
  258. package/src/plugins/builtin/options/enrichment.ts +17 -5
  259. package/src/plugins/builtin/options/table.ts +5 -2
  260. package/src/plugins/builtin/options/types.ts +2 -0
  261. package/src/plugins/builtin/options/view.tsx +17 -4
  262. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  263. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  264. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  265. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  266. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  267. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  268. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  269. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  270. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  271. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  272. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  273. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  274. package/src/plugins/builtin/pe-band/model.ts +12 -49
  275. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  276. package/src/plugins/builtin/perps/cascade.ts +236 -0
  277. package/src/plugins/builtin/perps/client.ts +56 -2
  278. package/src/plugins/builtin/perps/headless.ts +65 -9
  279. package/src/plugins/builtin/perps/index.tsx +7 -5
  280. package/src/plugins/builtin/perps/model.ts +18 -4
  281. package/src/plugins/builtin/perps/pane.tsx +19 -22
  282. package/src/plugins/builtin/perps/status-line.ts +25 -0
  283. package/src/plugins/builtin/plugin-module.ts +5 -1
  284. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  285. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  286. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  287. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  288. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  289. package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
  290. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  291. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  292. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
  293. package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
  294. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  295. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  296. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  297. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  298. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  299. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  300. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  301. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  302. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  303. package/src/plugins/builtin/research/event-model.ts +10 -8
  304. package/src/plugins/builtin/research/headless.ts +1 -1
  305. package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
  306. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  307. package/src/plugins/builtin/research/relative-valuation-pane.tsx +7 -5
  308. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  309. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  310. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  311. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  312. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  313. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  314. package/src/plugins/builtin/scanner/headless.ts +204 -35
  315. package/src/plugins/builtin/scanner/index.ts +16 -5
  316. package/src/plugins/builtin/scanner/live-headless.ts +27 -3
  317. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  318. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  319. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  320. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  321. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  322. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  323. package/src/plugins/builtin/sec/headless.ts +2 -1
  324. package/src/plugins/builtin/series-inventory.ts +2 -0
  325. package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
  326. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  327. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  328. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  329. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  330. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  331. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  332. package/src/plugins/builtin/shared/report-freshness.ts +3 -2
  333. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  334. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  335. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  336. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  337. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  338. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  339. package/src/plugins/builtin/themes/model.ts +16 -7
  340. package/src/plugins/builtin/themes/pane.tsx +5 -5
  341. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  342. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  343. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  344. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  345. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  346. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  347. package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
  348. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  349. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  350. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  351. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  352. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  353. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  354. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  355. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  356. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  357. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  358. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  359. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  360. package/src/plugins/builtin/volatility/index.tsx +2 -2
  361. package/src/plugins/builtin/volatility/pane.tsx +2 -2
  362. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  363. package/src/plugins/catalog-manifest.ts +2 -1
  364. package/src/plugins/marketplace/model.ts +18 -5
  365. package/src/portfolio/futures-positions.ts +6 -3
  366. package/src/portfolio/position-quantity.ts +15 -9
  367. package/src/remote/resources.ts +2 -1
  368. package/src/renderers/dom/data-table/row.tsx +26 -0
  369. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  370. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  371. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  372. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  373. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  374. package/src/renderers/electrobun/bun/desktop/backend.ts +12 -0
  375. package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
  376. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  377. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  378. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  379. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  380. package/src/renderers/electrobun/shared/protocol.ts +10 -0
  381. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  382. package/src/renderers/electrobun/view/main.tsx +24 -1
  383. package/src/renderers/electrobun/view/news-source.ts +3 -1
  384. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  385. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  386. package/src/renderers/opentui/start.tsx +5 -0
  387. package/src/series/registry.ts +1 -0
  388. package/src/series/types.ts +6 -0
  389. package/src/series/validate.ts +1 -0
  390. package/src/sources/gloomberb-cloud/index.ts +1 -0
  391. package/src/sources/gloomberb-cloud/news.ts +47 -7
  392. package/src/sources/provider-router/financials.ts +15 -3
  393. package/src/sources/provider-router/index.ts +8 -5
  394. package/src/state/ticker-cleanup.ts +50 -0
  395. package/src/sync/controller.ts +4 -3
  396. package/src/sync/core-contributors.ts +90 -31
  397. package/src/telemetry/stall-reports-dom.ts +38 -0
  398. package/src/telemetry/stall-reports-node.ts +75 -0
  399. package/src/telemetry/stall-reports.ts +214 -0
  400. package/src/test-support/preload.ts +14 -0
  401. package/src/theme/themes.ts +10 -0
  402. package/src/tickers/company-name.ts +44 -3
  403. package/src/tickers/listing-venues.ts +15 -3
  404. package/src/tickers/terminal-codes.ts +19 -0
  405. package/src/time-series/alignment.ts +1 -1
  406. package/src/time-series/economic.ts +18 -4
  407. package/src/time-series/field-catalog.ts +1 -1
  408. package/src/time-series/filed-trailing-eps.ts +197 -0
  409. package/src/time-series/fundamentals.ts +44 -1
  410. package/src/time-series/ice-credit.ts +3 -0
  411. package/src/time-series/price-comparison.ts +30 -2
  412. package/src/time-series/resolve-context.ts +3 -1
  413. package/src/time-series/resolve.ts +56 -4
  414. package/src/time-series/session-bar.ts +11 -0
  415. package/src/time-series/spec.ts +3 -2
  416. package/src/time-series/studies.ts +8 -2
  417. package/src/time-series/total-return.ts +160 -0
  418. package/src/time-series/transforms.ts +2 -3
  419. package/src/time-series/types.ts +22 -1
  420. package/src/types/financials.ts +32 -1
  421. package/src/types/headless.ts +21 -3
  422. package/src/types/news-source.ts +1 -0
  423. package/src/types/plugin.ts +9 -0
  424. package/src/ui/host.tsx +3 -0
  425. package/src/updater/failure.ts +4 -1
  426. package/src/utils/cli-output.ts +42 -1
  427. package/src/utils/crypto-pair.ts +44 -0
  428. package/src/utils/forward-dividend.ts +2 -0
  429. package/src/utils/fundamentals.ts +24 -3
  430. package/src/utils/fx-market-hours.ts +26 -3
  431. package/src/utils/main-thread-stall.ts +35 -2
  432. package/src/utils/market-capitalization.ts +24 -1
  433. package/src/utils/price-history.ts +7 -1
  434. package/src/utils/shell-word.ts +8 -0
  435. package/src/utils/trailing-dividend.ts +53 -4
  436. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -1,8 +1,10 @@
1
+ import { logFailure } from "../../../../utils/log-failure";
1
2
  import { useCallback, useEffect, useMemo, useState } from "react";
2
3
  import { TextAttributes } from "../../../../ui";
3
4
  import {
4
5
  DataTableView,
5
6
  QueryBar,
7
+ StatGrid,
6
8
  loadingText,
7
9
  unavailableText,
8
10
  usePaneNoticeFooter,
@@ -12,11 +14,13 @@ import {
12
14
  type DataTableColumn,
13
15
  type PaneFooterSegment,
14
16
  type PaneHint,
17
+ type StatItem,
15
18
  } from "../../../../components";
16
19
  import { usePaneRefreshKey } from "../../../../components/data-table/table-pane";
17
20
  import { CHART_RESOLUTIONS } from "../../../../time-series/range";
18
21
  import {
19
22
  getChartResolutionLabel,
23
+ isIntradayResolution,
20
24
  type ChartResolutionSupport,
21
25
  type ManualChartResolution,
22
26
  } from "../../../../time-series/resolution";
@@ -25,7 +29,7 @@ import { cleanFloat32Price, historyPriceDecimals } from "../../../../cli/history
25
29
  import { formatPriceObservation, quoteFormatOptions } from "../../../../market-data/market/format";
26
30
  import { usePaneTicker } from "../../../../state/app/context";
27
31
  import type { PaneProps } from "../../../../types/plugin";
28
- import { colors, priceColor } from "../../../../theme/colors";
32
+ import { colors, shownPriceColor } from "../../../../theme/colors";
29
33
  import { formatPercent } from "../../../../utils/format";
30
34
  import { publicTickerKey } from "../../../../utils/exchanges";
31
35
  import { priceHistoryIntegrityNotice } from "../../../../utils/price-history-integrity";
@@ -39,9 +43,12 @@ import {
39
43
  effectiveReturnResolution,
40
44
  isTodayReturnRow,
41
45
  returnBaseCloseDay,
46
+ returnTimeHeader,
47
+ returnTimeText,
42
48
  RETURN_RANGES,
43
49
  type ReturnRange,
44
50
  type ReturnRow,
51
+ summarizeReturns,
45
52
  } from "./returns-model";
46
53
  import { loadReturnSeries, type ReturnSeries } from "./returns-history";
47
54
  import { formingHistoryBar, formingHistoryBarText, historyBarDate } from "./history-bars";
@@ -64,12 +71,6 @@ const COLUMNS: ReturnColumn[] = [
64
71
  { id: "cumulativePercent", label: "TOTAL %", width: 9, align: "right", flexGrow: 1 },
65
72
  ];
66
73
 
67
- function formatDateTime(date: Date): string {
68
- const iso = date.toISOString();
69
- const hasTime = date.getUTCHours() !== 0 || date.getUTCMinutes() !== 0 || date.getUTCSeconds() !== 0;
70
- return hasTime ? iso.slice(0, 16).replace("T", " ") : iso.slice(0, 10);
71
- }
72
-
73
74
  /** Every price and dollar change at one decimal count, so the columns line up. */
74
75
  function returnPriceDecimals(rows: readonly ReturnRow[], assetCategory?: string): number {
75
76
  return historyPriceDecimals(rows.flatMap((row) => row.price === null ? [] : [{
@@ -95,6 +96,12 @@ function formatMaybePercent(value: number | null): string {
95
96
  return value === null ? "-" : formatPercent(value);
96
97
  }
97
98
 
99
+ /** A move's tone as it is printed: one that rounds to 0.00% is neutral, never red or green. */
100
+ function moveTone(fraction: number): StatItem["tone"] {
101
+ const shown = Number((fraction * 100).toFixed(2));
102
+ return shown > 0 ? "positive" : shown < 0 ? "negative" : "neutral";
103
+ }
104
+
98
105
  function sortValue(row: ReturnRow, id: ReturnColumnId): number | null {
99
106
  switch (id) {
100
107
  case "time":
@@ -127,7 +134,7 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
127
134
  symbol && dataProvider?.getChartResolutionSupport
128
135
  ? dataProvider.getChartResolutionSupport(symbol, exchange)
129
136
  : NO_SUPPORT,
130
- ).catch(() => NO_SUPPORT), [dataProvider, exchange, symbol]);
137
+ ).catch(logFailure("ticker-detail.data-panes.returns-pane", "Read return interval support failed", NO_SUPPORT)), [dataProvider, exchange, symbol]);
131
138
  const [support, setSupport] = useState<readonly ChartResolutionSupport[] | null>(null);
132
139
  useEffect(() => {
133
140
  let cancelled = false;
@@ -162,15 +169,20 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
162
169
  () => data ? buildReturnRows(data.points, data.visibleFrom, data.visibleTo) : [],
163
170
  [data],
164
171
  );
172
+ const barSize = data?.resolution ?? shownResolution;
173
+ const venue = data?.venue || undefined;
174
+ // The times are dated on the venue's own clock, which the column's header names.
175
+ const timeHeader = returnTimeHeader(barSize, venue);
176
+ const columns = useMemo(() => COLUMNS.map((column) => column.id === "time" ? { ...column, label: timeHeader } : column), [timeHeader]);
165
177
  const table = useMemo(() => {
166
178
  const searchable = returnRows.length >= SEARCH_MIN_ROWS;
167
179
  const needle = searchable ? query.trim().toLowerCase() : "";
168
180
  const rows = (needle
169
- ? returnRows.filter((row) => formatDateTime(new Date(row.timestamp)).toLowerCase().includes(needle))
181
+ ? returnRows.filter((row) => returnTimeText(row.timestamp, barSize, venue).includes(needle))
170
182
  : returnRows.slice()
171
183
  ).sort((left, right) => compareSortValues(sortValue(left, sort.columnId), sortValue(right, sort.columnId), sort.direction));
172
184
  return { rows, searchable };
173
- }, [query, returnRows, sort]);
185
+ }, [barSize, query, returnRows, sort, venue]);
174
186
  // The quote knows the instrument type even when the saved ticker does not,
175
187
  // so prices take the same decimals as Historical Prices.
176
188
  const { financials } = usePaneTicker();
@@ -199,7 +211,6 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
199
211
  // Today's daily bar is the session still trading, marked as Historical Prices marks it.
200
212
  const dayToDate = !!data && isTodayReturnRow(returnRows[0], data.resolution, data.venue || undefined, Date.now());
201
213
  const formingKey = forming || dayToDate ? returnRows[0]?.key : undefined;
202
- const barSize = data?.resolution ?? shownResolution;
203
214
  // The close every total is measured from: `from Mon 21 Sep close`.
204
215
  const baseText = useMemo(() => {
205
216
  const day = data && returnRows.length ? returnBaseCloseDay(data.points, data.visibleFrom, data.resolution, data.venue || undefined, Date.now()) : null;
@@ -225,6 +236,29 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
225
236
  title: "Price history data",
226
237
  });
227
238
 
239
+ // The window's total and its best and worst interval, over every bar it holds, whatever the search narrows the table to.
240
+ const summary = useMemo<StatItem[]>(() => {
241
+ const { total, best, worst } = summarizeReturns(returnRows);
242
+ const moveItem = (id: string, label: string, row: ReturnRow | null): StatItem[] => {
243
+ if (row?.intervalPercent == null) return [];
244
+ const when = historyBarDate(returnTimeText(row.timestamp, barSize, venue), barSize, false);
245
+ return [{
246
+ id,
247
+ label,
248
+ value: formatPercent(row.intervalPercent),
249
+ tone: moveTone(row.intervalPercent),
250
+ detail: when,
251
+ // A narrow pane keeps the day without its year, so the three figures stay on one row.
252
+ ...isIntradayResolution(barSize) ? { detailVariants: [when.slice(5)] } : {},
253
+ }];
254
+ };
255
+ return [
256
+ ...total === null ? [] : [{ id: "total", label: "Total", value: formatPercent(total), tone: moveTone(total) }],
257
+ ...moveItem("best", "Best", best),
258
+ ...moveItem("worst", "Worst", worst),
259
+ ];
260
+ }, [barSize, returnRows, venue]);
261
+
228
262
  const chooseSort = useCallback((id: string) => {
229
263
  setSort((current) => nextHeaderSort(current, id as ReturnColumnId, { firstDirection: "desc" }));
230
264
  }, []);
@@ -232,10 +266,10 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
232
266
  const renderCell = useCallback((row: ReturnRow, column: ReturnColumn): DataTableCell => {
233
267
  switch (column.id) {
234
268
  case "time": {
235
- const shown = formatDateTime(new Date(row.timestamp));
269
+ const shown = returnTimeText(row.timestamp, barSize, venue);
236
270
  // A daily or longer bar is a date; a month's reads as the month, and one still running says so.
237
271
  return {
238
- text: shown.length > 10 ? shown : historyBarDate(shown, barSize, row.key === formingKey),
272
+ text: isIntradayResolution(barSize) ? shown : historyBarDate(shown, barSize, row.key === formingKey),
239
273
  value: new Date(row.timestamp).toISOString(),
240
274
  color: colors.textDim,
241
275
  };
@@ -243,15 +277,15 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
243
277
  case "price":
244
278
  return { text: formatMaybePrice(row.price, priceDecimals, column.width), value: exportPrice(row.price, priceDecimals), color: colors.textBright, attributes: TextAttributes.BOLD };
245
279
  case "intervalChange":
246
- return { text: formatMaybePrice(row.intervalChange, priceDecimals, column.width), value: exportPrice(row.intervalChange, priceDecimals), color: priceColor(row.intervalChange ?? 0) };
280
+ return { text: formatMaybePrice(row.intervalChange, priceDecimals, column.width), value: exportPrice(row.intervalChange, priceDecimals), color: shownPriceColor(row.intervalChange ?? 0, priceDecimals) };
247
281
  case "intervalPercent":
248
- return { text: formatMaybePercent(row.intervalPercent), value: row.intervalPercent === null ? null : row.intervalPercent * 100, color: priceColor(row.intervalPercent ?? 0) };
282
+ return { text: formatMaybePercent(row.intervalPercent), value: row.intervalPercent === null ? null : row.intervalPercent * 100, color: shownPriceColor((row.intervalPercent ?? 0) * 100, 2) };
249
283
  case "cumulativeChange":
250
- return { text: formatMaybePrice(row.cumulativeChange, priceDecimals, column.width), value: exportPrice(row.cumulativeChange, priceDecimals), color: priceColor(row.cumulativeChange ?? 0) };
284
+ return { text: formatMaybePrice(row.cumulativeChange, priceDecimals, column.width), value: exportPrice(row.cumulativeChange, priceDecimals), color: shownPriceColor(row.cumulativeChange ?? 0, priceDecimals) };
251
285
  case "cumulativePercent":
252
- return { text: formatMaybePercent(row.cumulativePercent), value: row.cumulativePercent === null ? null : row.cumulativePercent * 100, color: priceColor(row.cumulativePercent ?? 0) };
286
+ return { text: formatMaybePercent(row.cumulativePercent), value: row.cumulativePercent === null ? null : row.cumulativePercent * 100, color: shownPriceColor((row.cumulativePercent ?? 0) * 100, 2) };
253
287
  }
254
- }, [barSize, formingKey, priceDecimals]);
288
+ }, [barSize, formingKey, priceDecimals, venue]);
255
289
 
256
290
  return (
257
291
  <DataTableView<ReturnRow, ReturnColumn>
@@ -259,7 +293,7 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
259
293
  selection={{ kind: "none" }}
260
294
  rootWidth={width}
261
295
  rootHeight={height}
262
- columns={COLUMNS}
296
+ columns={columns}
263
297
  items={table.rows}
264
298
  sortColumnId={sort.columnId}
265
299
  sortDirection={sort.direction}
@@ -268,33 +302,36 @@ export function ReturnsPane({ focused, width, height }: PaneProps) {
268
302
  renderCell={renderCell}
269
303
  selectedTextOverridesCellColor
270
304
  rootBefore={(
271
- <QueryBar
272
- width={width}
273
- search={table.searchable ? {
274
- value: query,
275
- onChange: setQuery,
276
- placeholder: "date or time",
277
- focused,
278
- ...searchProps,
279
- } : undefined}
280
- filters={[{
281
- id: "range",
282
- label: "Range",
283
- value: range,
284
- options: RETURN_RANGES.map((value) => ({ value, label: value })),
285
- onChange: setRange,
286
- }, {
287
- id: "interval",
288
- label: "Interval",
289
- value: shownResolution,
290
- options: INTERVAL_OPTIONS.map((value) => ({
291
- value,
292
- label: getChartResolutionLabel(value),
293
- disabled: support !== null && !availableResolutions.has(value),
294
- })),
295
- onChange: setResolution,
296
- }]}
297
- />
305
+ <>
306
+ <QueryBar
307
+ width={width}
308
+ search={table.searchable ? {
309
+ value: query,
310
+ onChange: setQuery,
311
+ placeholder: "date or time",
312
+ focused,
313
+ ...searchProps,
314
+ } : undefined}
315
+ filters={[{
316
+ id: "range",
317
+ label: "Range",
318
+ value: range,
319
+ options: RETURN_RANGES.map((value) => ({ value, label: value })),
320
+ onChange: setRange,
321
+ }, {
322
+ id: "interval",
323
+ label: "Interval",
324
+ value: shownResolution,
325
+ options: INTERVAL_OPTIONS.map((value) => ({
326
+ value,
327
+ label: getChartResolutionLabel(value),
328
+ disabled: support !== null && !availableResolutions.has(value),
329
+ })),
330
+ onChange: setResolution,
331
+ }]}
332
+ />
333
+ <StatGrid items={summary} width={width} />
334
+ </>
298
335
  )}
299
336
  getExportMetadata={() => [
300
337
  ["Ticker", symbol ? publicTickerKey(symbol, exchange) : ""],
@@ -4,7 +4,7 @@ import { EXTENDED_SESSION_LABELS, getQuoteSessionFields, quoteHeadlineTime, type
4
4
  import type { HeadlessPaneColumn, HeadlessPaneDefinition, HeadlessPaneFreshness, HeadlessPaneOptionValues } from "../../../types/headless";
5
5
  import type { TimeRange } from "../../../time-series/range";
6
6
  import { formatNumber, formatPercentRaw } from "../../../utils/format";
7
- import { formatMarketPrice, formatMarketPriceWithCurrency, formatPriceObservation, quoteFormatOptions, withCurrencyMinorDigits, type MarketFormatOptions } from "../../../market-data/market/format";
7
+ import { formatMarketPrice, formatMarketPriceWithCurrency, formatPriceObservation, penceFigure, quoteFormatOptions, withCurrencyMinorDigits, type MarketFormatOptions } from "../../../market-data/market/format";
8
8
  import {
9
9
  MISSING_VOLUME_TEXT, missingVolumeCount, missingVolumeNotice, pricePointValues, priceHistoryIntegrityNotice,
10
10
  } from "../../../utils/price-history-integrity";
@@ -36,7 +36,8 @@ import {
36
36
  } from "../shared/headless-market-data";
37
37
  import { barHistoryFreshness, quoteFreshnessFields, REPORTED_DATA } from "../shared/report-freshness";
38
38
  import {
39
- BAR_INTERVAL_LABELS, barPriceDecimals, cleanFloat32Price, formatBarPrice, historyAllStart, historyAllStartNote, historyMissingPeriodsNote, historyPriceDecimals,
39
+ BAR_INTERVAL_LABELS, barPriceDecimals, cleanBarPrice, cleanFloat32Price, formatBarPrice, historyAllStart, historyAllStartNote, historyMissingPeriodsNote, historyPriceDecimals,
40
+ quotedPriceDivisor,
40
41
  } from "../../../cli/history-rows";
41
42
  import { formatUtcTime } from "../../../utils/utc-time";
42
43
  import { maxDrawdown, maxDrawdownText } from "../../../utils/max-drawdown";
@@ -197,7 +198,7 @@ async function loadRatioReport(
197
198
  const history = oldest && tab.key === "valuation"
198
199
  ? await resolveHeadlessInstrument(ctx, symbol)
199
200
  .then(({ symbol: bare, exchange }) => loadPeriodEndHistory(ctx.marketData, bare, exchange ?? "", oldest))
200
- .catch(() => null)
201
+ .catch(logFailure("ticker-detail.headless", "Load valuation price history failed", null))
201
202
  : null;
202
203
  const table = hasRequestedStatements ? ratioTableForFinancials(financials, tab, period, history) : null;
203
204
  const statementCurrency = financialStatementCurrency(financials, [
@@ -339,12 +340,16 @@ export const quoteComparisonHeadless: HeadlessPaneDefinition<"rows"> = {
339
340
  * report declares before it has bars keep every digit the source sent. A bar without
340
341
  * volume in a history that reports it (`volumeMissing`) prints it as missing, never as 0.
341
342
  */
342
- const historyPriceColumns = (volumeMissing: boolean, decimals?: number): HeadlessPaneColumn[] => ["open", "high", "low", "close", "volume"].map((key) => ({
343
+ const historyPriceColumns = (volumeMissing: boolean, decimals?: number, pence?: (close: number) => string | null): HeadlessPaneColumn[] => ["open", "high", "low", "close", "volume"].map((key) => ({
343
344
  key, header: key, align: "right" as const,
344
- format: (value: unknown) => value == null || !Number.isFinite(Number(value)) ? key === "volume" && volumeMissing ? MISSING_VOLUME_TEXT : "-"
345
- : key === "volume" ? formatNumber(Number(value), 0)
346
- : decimals == null ? formatPriceObservation(Number(value), { minimumFractionDigits: 2 })
347
- : formatBarPrice(Number(value), decimals),
345
+ format: (value: unknown) => {
346
+ if (value == null || !Number.isFinite(Number(value))) return key === "volume" && volumeMissing ? MISSING_VOLUME_TEXT : "-";
347
+ if (key === "volume") return formatNumber(Number(value), 0);
348
+ if (decimals == null) return formatPriceObservation(Number(value), { minimumFractionDigits: 2 });
349
+ // A London line's close also reads in pence, as the pane's close notes it: 6.385 638.5p.
350
+ const note = key === "close" ? pence?.(Number(value)) : null;
351
+ return `${formatBarPrice(Number(value), decimals)}${note ? ` ${note}` : ""}`;
352
+ },
348
353
  }));
349
354
 
350
355
  /**
@@ -353,7 +358,14 @@ const historyPriceColumns = (volumeMissing: boolean, decimals?: number): Headles
353
358
  * whose period runs on (`2026-10 MTD`); the export keeps each bar's own date
354
359
  * and adds a `Partial` column when one is.
355
360
  */
356
- function historyColumns(size: HistoryBarSize, partial: boolean, text: boolean, volumeMissing = false, decimals?: number): HeadlessPaneColumn[] {
361
+ function historyColumns(
362
+ size: HistoryBarSize,
363
+ partial: boolean,
364
+ text: boolean,
365
+ volumeMissing = false,
366
+ decimals?: number,
367
+ pence?: (close: number) => string | null,
368
+ ): HeadlessPaneColumn[] {
357
369
  const daily = isDailyOrLongerBar(size);
358
370
  const date: HeadlessPaneColumn = {
359
371
  key: "date",
@@ -365,7 +377,7 @@ function historyColumns(size: HistoryBarSize, partial: boolean, text: boolean, v
365
377
  const marked: HeadlessPaneColumn[] = partial && !text
366
378
  ? [{ key: "partial", header: "Partial", format: (value) => value === true ? "true" : "" }]
367
379
  : [];
368
- return [date, ...historyPriceColumns(volumeMissing, decimals), ...marked];
380
+ return [date, ...historyPriceColumns(volumeMissing, decimals, text ? pence : undefined), ...marked];
369
381
  }
370
382
 
371
383
  export const historicalPricesHeadless: HeadlessPaneDefinition<"rows"> = {
@@ -386,14 +398,16 @@ export const historicalPricesHeadless: HeadlessPaneDefinition<"rows"> = {
386
398
  window ? loadPriceWindowHistory(ctx.marketData, listing, exchange ?? "", window).then((result) => result.points)
387
399
  : loadHeadlessPriceHistory(ctx, symbol, range),
388
400
  pastWindow || (!window && isIntradayResolution(RANGE_HISTORY_RESOLUTION[range])) ? null
389
- : ctx.marketData.getQuote(listing, exchange ?? "").catch(() => null),
401
+ : ctx.marketData.getQuote(listing, exchange ?? "").catch(logFailure("ticker-detail.headless", "Load historical prices quote failed", null)),
390
402
  ]);
391
403
  // The venue whose sessions say whether a day, week or month is still running: the key's, or for a bare symbol its quote's.
392
404
  const venue = exchange || quote?.listingExchangeName || quote?.exchangeName || undefined;
393
405
  const now = Date.now();
394
406
  const points = withSessionBar(loaded, quote, { now, venue });
395
- // Providers serve float32 prices (4013.39990234 for 4013.4): the rows hold the decimal each is, as `history` does.
396
- const clean = (value: number | null) => value == null ? null : cleanFloat32Price(value);
407
+ // Providers serve float32 prices (4013.39990234 for 4013.4): the rows hold the decimal each is, as `history` does,
408
+ // a London line's cleaned in the pence it was priced in.
409
+ const priceDivisor = quotedPriceDivisor(quote);
410
+ const clean = (value: number | null) => value == null ? null : cleanBarPrice(value, priceDivisor);
397
411
  const rows = points.map((point) => {
398
412
  const date = new Date(point.date);
399
413
  const values = pricePointValues(point);
@@ -417,7 +431,7 @@ export const historicalPricesHeadless: HeadlessPaneDefinition<"rows"> = {
417
431
  && quote.lastUpdated >= Date.parse(`${rows.at(-1)!.date.slice(0, 10)}T00:00:00Z`) ? new Date(quote.lastUpdated).toISOString() : null;
418
432
  const shown = forming ? [...rows.slice(0, -1), { ...rows.at(-1)!, partial: true }] : rows;
419
433
  // One decimal count for the table, by the instrument's display rules when the quote names its kind.
420
- const decimals = barPriceDecimals(rows, quoteFormatOptions(quote).assetCategory);
434
+ const decimals = barPriceDecimals(rows, quoteFormatOptions(quote).assetCategory, priceDivisor);
421
435
  const marked = shown.some((row) => row.partial === true);
422
436
  const volumeMissing = missingVolumeCount(shown);
423
437
  const volumeNote = missingVolumeNotice(volumeMissing, shown.length);
@@ -443,7 +457,8 @@ export const historicalPricesHeadless: HeadlessPaneDefinition<"rows"> = {
443
457
  return {
444
458
  rows: shown,
445
459
  columns: historyColumns(size, marked, false, false, decimals),
446
- textColumns: historyColumns(size, marked, true, volumeMissing > 0, decimals),
460
+ textColumns: historyColumns(size, marked, true, volumeMissing > 0, decimals,
461
+ (close) => penceFigure(Number(close.toFixed(Math.min(20, decimals))), quote, decimals)),
447
462
  freshness,
448
463
  ...(notes.length > 0 ? { notes } : {}),
449
464
  ...(notice ? { complete: false } : {}),
@@ -474,7 +489,7 @@ export const returnsHeadless: HeadlessPaneDefinition<"rows"> = {
474
489
  // --from 2007 --to 2009 measures those dates' returns in place of the range.
475
490
  const window = readReportWindow(options);
476
491
  const pastWindow = !!window && isPastPriceWindow(window);
477
- const support = await Promise.resolve(ctx.marketData.getChartResolutionSupport?.(symbol, exchange ?? "") ?? []).catch(() => []);
492
+ const support = await Promise.resolve(ctx.marketData.getChartResolutionSupport?.(symbol, exchange ?? "") ?? []).catch(logFailure("ticker-detail.headless", "Read return interval support failed", []));
478
493
  const now = Date.now();
479
494
  const series = await loadReturnSeries(ctx.marketData, {
480
495
  symbol, exchange: exchange ?? "", range, resolution: requested, support, now, forceRefresh: ctx.refresh, window,
@@ -221,7 +221,7 @@ export function buildOverviewStats({
221
221
  const count = ratioCount(pair.ratio);
222
222
  stats.push(receiptSide
223
223
  ? { label: "ADR Ratio", value: `1:${count}`, detail: [adrRatioText(pair.ratio)!, `${count} sh per ADR`], link: ADR_PAIR }
224
- : { label: "ADR", value: pair.receipt, detail: [`1 ADR = ${count} of these share${pair.ratio === 1 ? "" : "s"}`, `1:${count}`], link: ADR_PAIR });
224
+ : { label: "ADR", value: pair.receipt, detail: [`1 ADR = ${count} of these shares`, `1:${count}`], link: ADR_PAIR });
225
225
  }
226
226
  const trailingPE = liveTrailingPE(quote, fundamentals);
227
227
  if (trailingPE != null) {
@@ -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]);
@@ -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
  }
@@ -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,