gloomberb 0.18.4 → 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 (600) 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 +169 -5
  5. package/src/account/auth-model.ts +7 -3
  6. package/src/api-client/auth.ts +9 -4
  7. package/src/api-client/catalysts.ts +2 -1
  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 +31 -6
  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/index.ts +171 -11
  14. package/src/api-client/live-scan.ts +91 -0
  15. package/src/api-client/market-discovery.ts +10 -0
  16. package/src/api-client/market-movers.ts +7 -0
  17. package/src/api-client/members.ts +13 -1
  18. package/src/api-client/perps.ts +43 -0
  19. package/src/api-client/request.ts +222 -41
  20. package/src/api-client/research-activity.ts +6 -12
  21. package/src/api-client/types.ts +74 -0
  22. package/src/app/keybindings/index.ts +3 -0
  23. package/src/app/keybindings/labels.ts +18 -1
  24. package/src/app/keybindings/resolve.ts +14 -0
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +42 -27
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +1 -1
  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/fred-series.ts +5 -0
  35. package/src/cli/commands/keybindings.ts +23 -0
  36. package/src/cli/commands/market.ts +138 -46
  37. package/src/cli/commands/options-chain.ts +107 -10
  38. package/src/cli/commands/options-leaps.ts +9 -3
  39. package/src/cli/commands/overview.ts +176 -39
  40. package/src/cli/commands/search.ts +39 -3
  41. package/src/cli/commands/themes.ts +11 -2
  42. package/src/cli/commands/ticker.ts +57 -35
  43. package/src/cli/company-name-hint.ts +41 -0
  44. package/src/cli/earnings-calendar.ts +1 -1
  45. package/src/cli/errors.ts +10 -2
  46. package/src/cli/export-source.ts +29 -1
  47. package/src/cli/glossary.ts +73 -1
  48. package/src/cli/history-rows.ts +87 -16
  49. package/src/cli/index.ts +2 -0
  50. package/src/cli/known-gaps.ts +5 -2
  51. package/src/cli/listing-arg.ts +195 -56
  52. package/src/cli/output-file.ts +68 -0
  53. package/src/cli/pane-functions/capabilities.ts +3 -0
  54. package/src/cli/pane-functions/catalog.ts +80 -7
  55. package/src/cli/pane-functions/dom.ts +13 -3
  56. package/src/cli/pane-functions/freshness.ts +111 -16
  57. package/src/cli/pane-functions/function-help.ts +23 -13
  58. package/src/cli/pane-functions/headless.ts +56 -12
  59. package/src/cli/pane-functions/index.ts +58 -16
  60. package/src/cli/pane-functions/market-session.ts +81 -9
  61. package/src/cli/pane-functions/options.ts +19 -1
  62. package/src/cli/pane-functions/report.ts +5 -1
  63. package/src/cli/pane-functions/resolver.ts +35 -8
  64. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  65. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  66. package/src/cli/quote-answer.ts +30 -13
  67. package/src/cli/report-tables.ts +18 -7
  68. package/src/cli/restore-plugins.ts +13 -1
  69. package/src/cli/result.ts +57 -5
  70. package/src/cli/ticker-resolution.ts +3 -2
  71. package/src/components/chart/composite/composite-chart.tsx +26 -4
  72. package/src/components/chart/composite/format.ts +27 -0
  73. package/src/components/chart/composite/interactions.ts +6 -0
  74. package/src/components/chart/composite/legend.tsx +45 -7
  75. package/src/components/chart/composite/levels.ts +2 -0
  76. package/src/components/chart/composite/panel-gestures.ts +33 -0
  77. package/src/components/chart/composite/panel-series.ts +1 -0
  78. package/src/components/chart/composite/panel-surface.tsx +61 -5
  79. package/src/components/chart/composite/range-statistics.ts +62 -0
  80. package/src/components/chart/composite/rasterizer.ts +24 -13
  81. package/src/components/chart/composite/scene.ts +46 -7
  82. package/src/components/chart/composite/series-axis-tags.tsx +16 -9
  83. package/src/components/chart/composite/series-cues.ts +14 -0
  84. package/src/components/chart/composite/text-renderer.ts +9 -2
  85. package/src/components/chart/composite/tool-catalog.ts +36 -0
  86. package/src/components/chart/composite/tools.ts +203 -44
  87. package/src/components/chart/composite/types.ts +4 -0
  88. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  89. package/src/components/command-bar/assist/inventory.ts +2 -1
  90. package/src/components/command-bar/commands/direct/index.ts +28 -13
  91. package/src/components/command-bar/pane-templates/items.ts +3 -2
  92. package/src/components/command-bar/panel/prompt-binding.ts +0 -8
  93. package/src/components/command-bar/workflow/alert.ts +1 -1
  94. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  95. package/src/components/layout/header.tsx +26 -40
  96. package/src/components/layout/pane/footer/index.tsx +1 -0
  97. package/src/components/layout/pane/footer/model.ts +27 -0
  98. package/src/components/onboarding/onboarding-frame.tsx +1 -1
  99. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  100. package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
  101. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  102. package/src/components/onboarding/wizard-positions.ts +3 -3
  103. package/src/components/ticker/list-table-view.tsx +4 -1
  104. package/src/components/ui/choice-dialog.tsx +23 -18
  105. package/src/components/ui/choice-groups.ts +18 -0
  106. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  107. package/src/components/ui/data-table/opentui/model.ts +26 -1
  108. package/src/components/ui/data-table/types.ts +2 -0
  109. package/src/components/ui/display.tsx +5 -1
  110. package/src/components/ui/list-view.tsx +8 -4
  111. package/src/components/ui/query-bar-keys.tsx +2 -0
  112. package/src/components/ui/query-bar.tsx +1 -0
  113. package/src/components/ui/select-button.tsx +5 -1
  114. package/src/components/ui/select-field.tsx +1 -0
  115. package/src/components/ui/table-layout.ts +8 -5
  116. package/src/core/app-runtime.ts +2 -0
  117. package/src/core/app-services.ts +2 -2
  118. package/src/core/state/app/layout.ts +2 -0
  119. package/src/core/state/app/state.ts +9 -0
  120. package/src/core/state/app/types.ts +3 -0
  121. package/src/data/config/chart-settings.ts +1 -1
  122. package/src/data/json-ticker-repository.ts +17 -8
  123. package/src/data/saved-copies.ts +55 -16
  124. package/src/data/ticker-repository.ts +16 -9
  125. package/src/data/ticker-writes.ts +81 -0
  126. package/src/data/ttl-cache.ts +165 -0
  127. package/src/docs/snapshot.json +427 -216
  128. package/src/i18n/es.ts +19 -3
  129. package/src/i18n/ja.ts +19 -3
  130. package/src/i18n/ko.ts +19 -3
  131. package/src/i18n/zh-cn.ts +19 -3
  132. package/src/i18n/zh-tw.ts +19 -3
  133. package/src/market-data/coordinator/financials.ts +10 -2
  134. package/src/market-data/coordinator/index.ts +3 -1
  135. package/src/market-data/coordinator/quotes.ts +4 -2
  136. package/src/market-data/depositary-pair.ts +2 -2
  137. package/src/market-data/fund-facts.ts +50 -0
  138. package/src/market-data/market/change-basis.ts +48 -7
  139. package/src/market-data/market/dated-previous-close.ts +203 -0
  140. package/src/market-data/market/format.ts +25 -0
  141. package/src/market-data/market/freshness.ts +2 -1
  142. package/src/market-data/market/futures-week.ts +68 -0
  143. package/src/market-data/market/status.ts +16 -4
  144. package/src/market-data/options-alternatives.ts +141 -4
  145. package/src/market-data/quotes/contributions.ts +20 -2
  146. package/src/market-data/quotes/resolution.ts +3 -0
  147. package/src/market-data/selectors.ts +3 -0
  148. package/src/news/aggregator.ts +113 -9
  149. package/src/news/cloud-session.ts +21 -0
  150. package/src/news/hooks.ts +1 -0
  151. package/src/news/news-model.ts +1 -0
  152. package/src/news/types.ts +11 -0
  153. package/src/notifications/notification-log.ts +6 -4
  154. package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
  155. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  156. package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
  157. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  158. package/src/plugins/builtin/alerts/cli.ts +380 -82
  159. package/src/plugins/builtin/alerts/command.ts +31 -0
  160. package/src/plugins/builtin/alerts/constants.ts +1 -1
  161. package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
  162. package/src/plugins/builtin/alerts/fired-log.ts +113 -8
  163. package/src/plugins/builtin/alerts/format.ts +80 -4
  164. package/src/plugins/builtin/alerts/headless.ts +30 -7
  165. package/src/plugins/builtin/alerts/history-rows.ts +7 -3
  166. package/src/plugins/builtin/alerts/history.ts +2 -0
  167. package/src/plugins/builtin/alerts/index.tsx +135 -22
  168. package/src/plugins/builtin/alerts/live.ts +3 -3
  169. package/src/plugins/builtin/alerts/metric-command.ts +134 -0
  170. package/src/plugins/builtin/alerts/metric-data.ts +111 -0
  171. package/src/plugins/builtin/alerts/metric-model.ts +324 -0
  172. package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
  173. package/src/plugins/builtin/alerts/pane.tsx +90 -39
  174. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  175. package/src/plugins/builtin/alerts/storage.ts +37 -10
  176. package/src/plugins/builtin/alerts/types.ts +7 -0
  177. package/src/plugins/builtin/analytics/index.tsx +2 -1
  178. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  179. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  180. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  181. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  182. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  183. package/src/plugins/builtin/attention/index.tsx +2 -1
  184. package/src/plugins/builtin/attention/listing.ts +14 -0
  185. package/src/plugins/builtin/backtest/headless.ts +10 -5
  186. package/src/plugins/builtin/backtest/pane.tsx +9 -4
  187. package/src/plugins/builtin/backtest/presets.ts +29 -3
  188. package/src/plugins/builtin/broker-manager/index.tsx +10 -5
  189. package/src/plugins/builtin/broker-manager/model.ts +6 -1
  190. package/src/plugins/builtin/broker-trading/controller.ts +22 -12
  191. package/src/plugins/builtin/broker-trading/index.tsx +13 -1
  192. package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
  193. package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
  194. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  195. package/src/plugins/builtin/buildout/format.ts +38 -0
  196. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  197. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  198. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  199. package/src/plugins/builtin/builtin-plugin-meta.ts +7 -3
  200. package/src/plugins/builtin/catalysts/model.ts +2 -1
  201. package/src/plugins/builtin/changelog/index.tsx +9 -6
  202. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
  203. package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
  204. package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
  205. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
  206. package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
  207. package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
  208. package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
  209. package/src/plugins/builtin/chart-composer/formula.ts +71 -18
  210. package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
  211. package/src/plugins/builtin/chart-composer/headless.ts +19 -3
  212. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  213. package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
  214. package/src/plugins/builtin/chart-composer/presets.ts +50 -15
  215. package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
  216. package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
  217. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  218. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
  219. package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
  220. package/src/plugins/builtin/chat/content/composer.tsx +13 -6
  221. package/src/plugins/builtin/chat/content/footer.tsx +6 -3
  222. package/src/plugins/builtin/chat/content/index.tsx +50 -21
  223. package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
  224. package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
  225. package/src/plugins/builtin/chat/index.tsx +23 -0
  226. package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
  227. package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
  228. package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
  229. package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
  230. package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
  231. package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
  232. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
  233. package/src/plugins/builtin/clinical-trials/model.ts +0 -9
  234. package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
  235. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
  236. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  237. package/src/plugins/builtin/cloud/plugin.tsx +1 -1
  238. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  239. package/src/plugins/builtin/company-attention/client.ts +8 -9
  240. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  241. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  242. package/src/plugins/builtin/company-attention/pane.tsx +9 -2
  243. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  244. package/src/plugins/builtin/correlation/headless.ts +13 -5
  245. package/src/plugins/builtin/correlation/history.ts +22 -0
  246. package/src/plugins/builtin/cot/client.ts +22 -18
  247. package/src/plugins/builtin/cot/headless.ts +15 -10
  248. package/src/plugins/builtin/cot/model.ts +14 -1
  249. package/src/plugins/builtin/cot/pane.tsx +22 -14
  250. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  251. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  252. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  253. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  254. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  255. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  256. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  257. package/src/plugins/builtin/credit-documents/headless.ts +7 -7
  258. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  259. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  260. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  261. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  262. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  263. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  264. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  265. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  266. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  267. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  268. package/src/plugins/builtin/docs/targets.ts +5 -1
  269. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  270. package/src/plugins/builtin/earnings/board.tsx +62 -71
  271. package/src/plugins/builtin/earnings/headless.ts +40 -4
  272. package/src/plugins/builtin/earnings/scope.ts +31 -43
  273. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  274. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  275. package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
  276. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  277. package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
  278. package/src/plugins/builtin/econ/calendar-model.ts +14 -9
  279. package/src/plugins/builtin/econ/figures.ts +72 -0
  280. package/src/plugins/builtin/econ/index.tsx +33 -3
  281. package/src/plugins/builtin/econ/types.ts +4 -0
  282. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  283. package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
  284. package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
  285. package/src/plugins/builtin/econ-statistics/view.ts +3 -0
  286. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  287. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  288. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  289. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  290. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  291. package/src/plugins/builtin/equity-screener/pane.tsx +70 -15
  292. package/src/plugins/builtin/exposure/charts.tsx +2 -2
  293. package/src/plugins/builtin/exposure/model.ts +13 -1
  294. package/src/plugins/builtin/exposure/pane.tsx +4 -4
  295. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  296. package/src/plugins/builtin/futures/index.tsx +1 -0
  297. package/src/plugins/builtin/futures/model.ts +2 -1
  298. package/src/plugins/builtin/futures/table.tsx +2 -2
  299. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  300. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  301. package/src/plugins/builtin/fx-matrix/client.ts +11 -3
  302. package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
  303. package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
  304. package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -3
  305. package/src/plugins/builtin/gpu/headless.ts +8 -2
  306. package/src/plugins/builtin/help/function-index.ts +4 -1
  307. package/src/plugins/builtin/help/index.tsx +1 -0
  308. package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
  309. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  310. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  311. package/src/plugins/builtin/holders/headless.ts +18 -1
  312. package/src/plugins/builtin/iv-history/client.ts +52 -11
  313. package/src/plugins/builtin/iv-history/format.ts +36 -23
  314. package/src/plugins/builtin/iv-history/headless.ts +36 -18
  315. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  316. package/src/plugins/builtin/iv-history/model.ts +163 -27
  317. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  318. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  319. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  320. package/src/plugins/builtin/jobs/client.ts +25 -29
  321. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  322. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  323. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  324. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  325. package/src/plugins/builtin/macro-day/pane.tsx +7 -5
  326. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  327. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  328. package/src/plugins/builtin/market-movers/client.ts +25 -7
  329. package/src/plugins/builtin/market-movers/footer.ts +2 -2
  330. package/src/plugins/builtin/market-movers/headless.ts +32 -12
  331. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  332. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  333. package/src/plugins/builtin/market-movers/model.ts +4 -1
  334. package/src/plugins/builtin/market-movers/screener.ts +14 -0
  335. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  336. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  337. package/src/plugins/builtin/market-movers/table.ts +17 -5
  338. package/src/plugins/builtin/members/breadth-pane.tsx +82 -0
  339. package/src/plugins/builtin/members/client.ts +19 -4
  340. package/src/plugins/builtin/members/headless.ts +20 -7
  341. package/src/plugins/builtin/members/index.tsx +9 -9
  342. package/src/plugins/builtin/members/model.ts +23 -4
  343. package/src/plugins/builtin/members/pane.tsx +25 -15
  344. package/src/plugins/builtin/news/index.tsx +1 -0
  345. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  346. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  347. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  348. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  349. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  350. package/src/plugins/builtin/openfda/client.ts +14 -2
  351. package/src/plugins/builtin/openfda/index.tsx +3 -2
  352. package/src/plugins/builtin/openfda/model.ts +9 -0
  353. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  354. package/src/plugins/builtin/options/analytics.ts +54 -6
  355. package/src/plugins/builtin/options/enrichment.ts +17 -5
  356. package/src/plugins/builtin/options/model-spot.ts +5 -2
  357. package/src/plugins/builtin/options/strike-window.ts +2 -1
  358. package/src/plugins/builtin/options/table.ts +6 -9
  359. package/src/plugins/builtin/options/types.ts +2 -0
  360. package/src/plugins/builtin/options/view.tsx +42 -12
  361. package/src/plugins/builtin/options-positioning/client.ts +30 -0
  362. package/src/plugins/builtin/options-positioning/headless.ts +74 -21
  363. package/src/plugins/builtin/options-positioning/index.tsx +4 -4
  364. package/src/plugins/builtin/options-positioning/model.ts +122 -14
  365. package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
  366. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  367. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  368. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  369. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  370. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  371. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  372. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  373. package/src/plugins/builtin/pe-band/model.ts +12 -49
  374. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  375. package/src/plugins/builtin/perps/cascade.ts +236 -0
  376. package/src/plugins/builtin/perps/client.ts +56 -2
  377. package/src/plugins/builtin/perps/freshness.ts +12 -10
  378. package/src/plugins/builtin/perps/headless.ts +65 -9
  379. package/src/plugins/builtin/perps/index.tsx +7 -5
  380. package/src/plugins/builtin/perps/live-rows.ts +10 -3
  381. package/src/plugins/builtin/perps/model.ts +18 -4
  382. package/src/plugins/builtin/perps/pane.tsx +32 -31
  383. package/src/plugins/builtin/perps/status-line.ts +25 -0
  384. package/src/plugins/builtin/plugin-module.ts +5 -1
  385. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  386. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  387. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  388. package/src/plugins/builtin/portfolio-list/cli/render.ts +12 -7
  389. package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
  390. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  391. package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
  392. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  393. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  394. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
  395. package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
  396. package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
  397. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  398. package/src/plugins/builtin/rate-path/pane.tsx +2 -2
  399. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  400. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  401. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  402. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  403. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  404. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  405. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  406. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  407. package/src/plugins/builtin/research/event-model.ts +10 -8
  408. package/src/plugins/builtin/research/headless.ts +1 -1
  409. package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
  410. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  411. package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
  412. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  413. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  414. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  415. package/src/plugins/builtin/scanner/feed.ts +2 -2
  416. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  417. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  418. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  419. package/src/plugins/builtin/scanner/headless.ts +204 -35
  420. package/src/plugins/builtin/scanner/index.ts +40 -6
  421. package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
  422. package/src/plugins/builtin/scanner/live-feed.ts +168 -0
  423. package/src/plugins/builtin/scanner/live-headless.ts +138 -0
  424. package/src/plugins/builtin/scanner/live-model.ts +362 -0
  425. package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
  426. package/src/plugins/builtin/scanner/live-track.ts +186 -0
  427. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  428. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  429. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  430. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  431. package/src/plugins/builtin/sec/headless.ts +2 -1
  432. package/src/plugins/builtin/series-inventory.ts +3 -0
  433. package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
  434. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  435. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  436. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  437. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  438. package/src/plugins/builtin/shared/macro-releases.ts +65 -0
  439. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  440. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  441. package/src/plugins/builtin/shared/report-freshness.ts +3 -2
  442. package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
  443. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  444. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  445. package/src/plugins/builtin/shared/use-quote-board.ts +10 -3
  446. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  447. package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
  448. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  449. package/src/plugins/builtin/themes/headless.ts +6 -4
  450. package/src/plugins/builtin/themes/model.ts +17 -8
  451. package/src/plugins/builtin/themes/pane.tsx +7 -7
  452. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  453. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  454. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  455. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  456. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  457. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  458. package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
  459. package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
  460. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  461. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  462. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  463. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  464. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  465. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  466. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  467. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  468. package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
  469. package/src/plugins/builtin/vol-surface/client.ts +9 -17
  470. package/src/plugins/builtin/vol-surface/headless.ts +63 -14
  471. package/src/plugins/builtin/vol-surface/index.tsx +2 -2
  472. package/src/plugins/builtin/vol-surface/model.ts +22 -7
  473. package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
  474. package/src/plugins/builtin/vol-surface/tables.ts +31 -3
  475. package/src/plugins/builtin/volatility/dvol.ts +46 -0
  476. package/src/plugins/builtin/volatility/index.tsx +2 -2
  477. package/src/plugins/builtin/volatility/pane.tsx +4 -4
  478. package/src/plugins/builtin/volatility/series.ts +26 -0
  479. package/src/plugins/builtin/world-indices/footer.ts +1 -1
  480. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  481. package/src/plugins/catalog-browser.ts +13 -8
  482. package/src/plugins/catalog-manifest.ts +2 -1
  483. package/src/plugins/marketplace/model.ts +18 -5
  484. package/src/portfolio/column-values.ts +5 -5
  485. package/src/portfolio/futures-positions.ts +6 -3
  486. package/src/portfolio/position-quantity.ts +15 -9
  487. package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
  488. package/src/remote/resources.ts +2 -1
  489. package/src/renderers/browser/app-services.ts +1 -7
  490. package/src/renderers/browser/config-host.ts +2 -55
  491. package/src/renderers/browser/main.tsx +7 -57
  492. package/src/renderers/dom/data-table/row.tsx +26 -0
  493. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  494. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  495. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  496. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  497. package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
  498. package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
  499. package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
  500. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  501. package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
  502. package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
  503. package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
  504. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  505. package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
  506. package/src/renderers/electrobun/bun/index.ts +25 -1
  507. package/src/renderers/electrobun/shared/app-name.ts +30 -0
  508. package/src/renderers/electrobun/shared/protocol.ts +10 -0
  509. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  510. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
  511. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
  512. package/src/renderers/electrobun/view/main.tsx +24 -1
  513. package/src/renderers/electrobun/view/news-source.ts +3 -1
  514. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  515. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  516. package/src/renderers/opentui/start.tsx +5 -0
  517. package/src/renderers/opentui/toast-host.tsx +71 -43
  518. package/src/series/registry.ts +1 -0
  519. package/src/series/types.ts +6 -0
  520. package/src/series/validate.ts +1 -0
  521. package/src/sources/gloomberb-cloud/index.ts +1 -0
  522. package/src/sources/gloomberb-cloud/news.ts +47 -7
  523. package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
  524. package/src/sources/provider-router/batches.ts +10 -2
  525. package/src/sources/provider-router/financial-routes.ts +3 -3
  526. package/src/sources/provider-router/financials.ts +15 -3
  527. package/src/sources/provider-router/history.ts +28 -7
  528. package/src/sources/provider-router/index.ts +149 -10
  529. package/src/sources/provider-router/search.ts +13 -2
  530. package/src/state/app/context/index.tsx +3 -0
  531. package/src/state/ticker-cleanup.ts +50 -0
  532. package/src/sync/auth-transition.ts +19 -9
  533. package/src/sync/baseline.ts +10 -4
  534. package/src/sync/controller.ts +70 -19
  535. package/src/sync/core-contributors.ts +90 -31
  536. package/src/sync/react.ts +4 -1
  537. package/src/sync/types.ts +3 -0
  538. package/src/telemetry/stall-reports-dom.ts +38 -0
  539. package/src/telemetry/stall-reports-node.ts +75 -0
  540. package/src/telemetry/stall-reports.ts +214 -0
  541. package/src/telemetry/usage-counts.ts +0 -18
  542. package/src/test-support/preload.ts +14 -0
  543. package/src/theme/colors.ts +8 -0
  544. package/src/theme/themes.ts +10 -0
  545. package/src/tickers/company-name.ts +103 -3
  546. package/src/tickers/listing-venues.ts +38 -3
  547. package/src/tickers/search/bare-symbol.ts +36 -0
  548. package/src/tickers/search/index.ts +36 -7
  549. package/src/tickers/search/ranking.ts +10 -1
  550. package/src/tickers/search/types.ts +5 -0
  551. package/src/tickers/terminal-codes.ts +19 -0
  552. package/src/time-series/alignment.ts +1 -1
  553. package/src/time-series/base-series.ts +13 -5
  554. package/src/time-series/daily-valuation.ts +62 -0
  555. package/src/time-series/economic.ts +18 -4
  556. package/src/time-series/field-catalog.ts +2 -4
  557. package/src/time-series/filed-trailing-eps.ts +197 -0
  558. package/src/time-series/forward-valuation.ts +32 -0
  559. package/src/time-series/fundamentals.ts +198 -7
  560. package/src/time-series/hooks.ts +1 -0
  561. package/src/time-series/ice-credit.ts +3 -0
  562. package/src/time-series/price-comparison.ts +30 -2
  563. package/src/time-series/price-units.ts +72 -2
  564. package/src/time-series/resolve-context.ts +7 -1
  565. package/src/time-series/resolve.ts +116 -15
  566. package/src/time-series/series-presentation.ts +40 -6
  567. package/src/time-series/session-bar.ts +11 -0
  568. package/src/time-series/spec.ts +5 -3
  569. package/src/time-series/studies.ts +278 -74
  570. package/src/time-series/total-return.ts +160 -0
  571. package/src/time-series/transforms.ts +3 -3
  572. package/src/time-series/types.ts +27 -1
  573. package/src/time-series/valuation-releases.ts +39 -0
  574. package/src/types/data-provider.ts +9 -1
  575. package/src/types/desktop-window.ts +2 -0
  576. package/src/types/financials.ts +73 -2
  577. package/src/types/headless.ts +61 -5
  578. package/src/types/instrument.ts +3 -0
  579. package/src/types/news-source.ts +1 -0
  580. package/src/types/plugin.ts +10 -1
  581. package/src/ui/host.tsx +3 -0
  582. package/src/updater/command-names.ts +12 -7
  583. package/src/updater/failure.ts +4 -1
  584. package/src/utils/cli-output.ts +42 -1
  585. package/src/utils/crypto-pair.ts +44 -0
  586. package/src/utils/financial-statements.ts +6 -1
  587. package/src/utils/forward-dividend.ts +2 -0
  588. package/src/utils/fundamentals.ts +24 -3
  589. package/src/utils/fx-market-hours.ts +26 -3
  590. package/src/utils/main-thread-stall.ts +35 -2
  591. package/src/utils/market-capitalization.ts +24 -1
  592. package/src/utils/network-failure.ts +9 -6
  593. package/src/utils/option-expiry.ts +48 -1
  594. package/src/utils/options.ts +10 -0
  595. package/src/utils/plugin-proxy-hosts.json +1 -0
  596. package/src/utils/price-history.ts +12 -1
  597. package/src/utils/shell-word.ts +8 -0
  598. package/src/utils/trailing-dividend.ts +53 -4
  599. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
  600. package/src/renderers/browser/first-screen.ts +0 -193
@@ -19,12 +19,15 @@ import { splitSymbolArgs, stripStrayEdges } from "../symbol-args";
19
19
  import { readNameEntries } from "../name-entries";
20
20
  import { bankFigurePointer } from "../known-gaps";
21
21
  import { readTerminalCodeArg } from "../../tickers/company-name-listing";
22
+ import { unknownBloombergMarket } from "../../tickers/terminal-codes";
23
+ import { notATickerWords } from "../company-name-hint";
22
24
  import type { MarketContext } from "../types";
23
25
  import {
24
26
  buildPaneFunctionLookup,
25
27
  buildTemplateContext,
26
28
  findFunctionCode,
27
29
  nearFunctionCodes,
30
+ paneFunctionCode,
28
31
  suggestedFunctionCodes,
29
32
  suggestPaneFunctions,
30
33
  type NearFunctionCode,
@@ -142,8 +145,9 @@ export function parsePaneFunctionArgsFor(
142
145
  const found = parsed.target ? findPaneFunction(registry, parsed.target) : null;
143
146
  if (!found?.pane) return parsed;
144
147
  const capability = getPaneFunctionCapability(found.template, found.pane);
145
- if (!capability.options.some((option) => (option.valueWords ?? 1) > 1)) return parsed;
146
- return parsePaneFunctionArgs(args, globalOptions, (key) => findCapabilityOption(capability, key)?.valueWords ?? 1);
148
+ if (!capability.options.some((option) => (option.valueWords ?? 1) > 1 || option.list)) return parsed;
149
+ return parsePaneFunctionArgs(args, globalOptions, (key) => findCapabilityOption(capability, key)?.valueWords ?? 1,
150
+ (key) => findCapabilityOption(capability, key)?.list === true);
147
151
  }
148
152
 
149
153
  /**
@@ -168,20 +172,30 @@ export async function applyListingArgument(
168
172
  const takesOneTicker = kind === "ticker";
169
173
  const takesList = kind === "tickers" || kind === "symbol-list" || (!headless && kind === "ticker-list");
170
174
  // A function with an exchange option of its own (DIST) keeps it.
171
- // A ticker with spaces is no listing, unless it is a terminal's code for one ("JPM US Equity").
172
- if (!(takesOneTicker || takesList) || (takesOneTicker && /\s/.test(args.arg) && !await readTerminalCodeArg(context.dataProvider, args.arg))
173
- || getPaneFunctionCapability(found.template, found.pane).options.some((option) => option.key === "exchange")) {
175
+ if (!(takesOneTicker || takesList) || getPaneFunctionCapability(found.template, found.pane).options.some((option) => option.key === "exchange")) {
174
176
  return args;
175
177
  }
178
+ // A ticker with spaces is no listing, unless it is a terminal's code for one ("JPM US Equity"). A company's name
179
+ // typed there (`fn RDCF "Palantir Technologies"`) stops before a report of dashes is drawn, as `quote` words a
180
+ // name that is no ticker, with the company search finds for it.
181
+ if (takesOneTicker && /\s/.test(args.arg) && !await readTerminalCodeArg(context.dataProvider, args.arg)) {
182
+ // A line written as a code with a market no venue answers to ("AAPL GP") is refused as the resolver words it.
183
+ if (unknownBloombergMarket(args.arg)) await resolveCliListing(args.arg, undefined, context);
184
+ throw new ListingArgError(await notATickerWords(args.arg, context));
185
+ }
176
186
  const exchange = typeof args.options.exchange === "string" ? args.options.exchange : undefined;
177
187
  const { exchange: _exchange, ...options } = args.options;
178
188
  // A list entry with spaces, or an argument of words, is a company's name search finds, or the symbols it holds.
179
- const typed = takesOneTicker ? [args.arg] : await readNameEntries(splitSymbolArgs([args.arg], { phrases: true }), context.dataProvider);
180
- if (typed.length === 0) return args;
189
+ const entered = takesOneTicker ? [args.arg] : await readNameEntries(splitSymbolArgs([args.arg], { phrases: true }), context.dataProvider);
190
+ if (entered.length === 0) return args;
191
+ // A symbol the function reads as another (OPX's BTC is the coin, BTC-USD) is named in a note; --exchange keeps it as typed.
192
+ const aliases = exchange ? [] : entered.map((raw) => headless?.symbolAlias?.(raw) ?? null);
193
+ const typed = entered.map((raw, index) => aliases[index]?.symbol ?? raw);
181
194
  // RY.TO is the listing its quote names, RY:TSX, as `quote` reads it: a function's data is read by venue.
182
195
  let listings = await Promise.all(typed.map((raw) => resolveCliListing(raw, exchange, context, { ownExchangeWins: typed.length > 1, venueSuffix: true })));
183
196
  // A separator stuck to the end of a pasted list (`UPST;`) is not part of a symbol; it is named.
184
197
  const notes: string[] = takesOneTicker ? [] : stripStrayEdges(args.arg).notes;
198
+ notes.push(...aliases.flatMap((alias) => alias ? [alias.note] : []));
185
199
  const token = found.template?.shortcut?.prefix ?? args.target.toUpperCase();
186
200
  // Words search finds no company for, and that are not symbols either, name nothing a function can read: tickers do.
187
201
  const unnamed = takesList ? listings.filter((listing) => /\s/.test(listing.symbol)) : [];
@@ -213,6 +227,19 @@ export async function applyListingArgument(
213
227
  notes.push(...answers.map((answer) => `${answer.message} ${answer.details}`));
214
228
  }
215
229
  }
230
+ // A listing the function has nothing for (OPX's SOL-USD) says what exists instead; a list leaves it out.
231
+ const refusals = headless?.refuseSymbol ? listings.flatMap((listing) => {
232
+ const refusal = headless.refuseSymbol!(listing.key);
233
+ return refusal ? [{ listing, refusal }] : [];
234
+ }) : [];
235
+ if (refusals.length > 0) {
236
+ listings = listings.filter((listing) => !refusals.some((entry) => entry.listing === listing));
237
+ if (listings.length === 0) {
238
+ const details = refusals.flatMap(({ refusal }) => refusal.details ? [refusal.details] : []).join("\n");
239
+ throw new ListingArgError(refusals.map(({ refusal }) => refusal.message).join("\n"), details || undefined);
240
+ }
241
+ notes.push(...refusals.map(({ refusal }) => [refusal.message, refusal.details].filter(Boolean).join(" ")));
242
+ }
216
243
  const usOnly = headless?.usListingsOnly;
217
244
  const abroad = usOnly ? listings.filter((listing) => listingAbroadVenue(listing.key) != null) : [];
218
245
  if (usOnly && abroad.length > 0) {
@@ -332,7 +359,7 @@ export async function resolvePaneFunction(
332
359
  tableSection = taken.tableSection;
333
360
  }
334
361
  // What the user types for the function, which every error about it names.
335
- const token = template?.shortcut?.prefix ?? template?.id ?? pane.id;
362
+ const token = paneFunctionCode(template, pane);
336
363
  // A word after a function that takes none is most often one of its option values (`fn JODI gas`).
337
364
  // Every function the catalog lists with no argument refuses one, a rendered view (HALT) as a report (CBR).
338
365
  // The catalog lists templates and the panes no template opens (HELP); a pane opened by its id stands for its templates.
@@ -4,6 +4,7 @@ import type { DesktopPaneShotPayload, DesktopPaneShotRenderResult } from "../des
4
4
  import type { ResolvedPaneFunction } from "./resolver";
5
5
  import type { RemoteUiNodeSnapshot } from "../../remote/types";
6
6
  import { accessGateSentence, accessGateStatus } from "./access-gate";
7
+ import { isDataPaneForDomFallback } from "./capabilities";
7
8
  import { paneEvidenceMismatches, paneScreenshotEvidenceHook } from "./screenshot-evidence";
8
9
  import { collectShotSymbols } from "./data";
9
10
  import { renderedReportNotices } from "./report-notices";
@@ -21,10 +22,9 @@ import {
21
22
  import { shotExpectedChart, chartEvidenceMismatchesFor, intradayChartEvidenceMismatchesFor } from "./screenshot-chart-evidence";
22
23
 
23
24
  export function isPaneScreenshotUsable(signals: PaneScreenshotReadinessSignals): boolean {
24
- return signals.rowCount > 0
25
+ return (signals.interactive || (signals.rowCount > 0 && !signals.emptyStateDetected))
25
26
  && !signals.loadingStateDetected
26
27
  && !signals.errorStateDetected
27
- && !signals.emptyStateDetected
28
28
  && signals.complete
29
29
  && !signals.semanticMismatch
30
30
  && (!signals.requiresStructuredDataEvidence || signals.hasStructuredDataEvidence);
@@ -111,12 +111,14 @@ export function assessPaneScreenshot(
111
111
  const semanticMismatch = missingExpectedText.length > 0
112
112
  || missingExpectedSelections.length > 0
113
113
  || chartEvidenceMismatches.length > 0;
114
+ // Layouts and the other panes that are surfaces to operate have no rows to count: a capture of one is not empty for that.
115
+ const interactive = !isDataPaneForDomFallback(resolved.pane);
114
116
  const empty = render.loadingStateDetected
115
117
  || render.errorStateDetected
116
- || render.emptyStateDetected
117
- || rowCount === 0
118
+ || (!interactive && (render.emptyStateDetected || rowCount === 0))
118
119
  || semanticMismatch;
119
120
  const usable = isPaneScreenshotUsable({
121
+ interactive,
120
122
  rowCount,
121
123
  loadingStateDetected: render.loadingStateDetected,
122
124
  errorStateDetected: render.errorStateDetected,
@@ -108,6 +108,11 @@ export type PaneScreenshotDataEvidence =
108
108
  | PaneScreenshotEvidence;
109
109
 
110
110
  export interface PaneScreenshotReadinessSignals {
111
+ /**
112
+ * A surface to operate rather than data to read (Layouts, Brokers): its rows
113
+ * and an empty-state marker inside it say nothing of whether it rendered.
114
+ */
115
+ interactive?: boolean;
111
116
  rowCount: number;
112
117
  loadingStateDetected: boolean;
113
118
  errorStateDetected: boolean;
@@ -14,8 +14,8 @@ import { knownGapForTicker } from "./known-gaps";
14
14
  import {
15
15
  futuresTryLine,
16
16
  isNoProviderError,
17
+ isUnsettledNoProvider,
17
18
  listingChoiceNotes,
18
- listingReadNotes,
19
19
  ListingArgError,
20
20
  loadBareVenues,
21
21
  notTradedMessage,
@@ -29,6 +29,8 @@ import {
29
29
  type ListingVenue,
30
30
  } from "./listing-arg";
31
31
  import { withListingSuggestion } from "./not-a-ticker";
32
+ import { notATickerWords } from "./company-name-hint";
33
+ import { isNetworkFailure } from "../utils/network-failure";
32
34
  import type { MarketContext } from "./types";
33
35
 
34
36
  /** The text of an error, or null for none. */
@@ -76,23 +78,27 @@ export async function answerQuote(
76
78
  request: ListingDataRequest,
77
79
  refresh: boolean,
78
80
  venues: Promise<ListingVenue[]> | null,
81
+ /** The row prints what its move is from: a quote asked for again is dated too. */
82
+ { datesChangeBasis = false }: { datesChangeBasis?: boolean } = {},
79
83
  ): Promise<QuoteAnswer> {
80
84
  const root = listing.exchange ? null : indexRootFor(listing.symbol);
81
85
  // A bare root that quoted something else, such as MOVE (Corvex), points at the index too.
82
86
  const also = (quote: Quote) => root && quote.instrumentType?.trim().toUpperCase() !== "INDEX"
83
87
  ? [indexRootAlsoLine(root, request.command)] : [];
84
88
  if (result.quote) {
85
- // "CAR = Avis Budget Group, NASDAQ (also XETRA, ASX)" names the venue too, so it replaces "SXR8 -> XETRA".
86
- const choice = venues ? await listingChoiceNotes(listing, market, result.quote, venues) : listingReadNotes(listing);
89
+ // "CAR = Avis Budget Group, NASDAQ (different companies on XETRA, ASX)" names the venue too, so it replaces "SXR8 -> XETRA".
90
+ const choice = await listingChoiceNotes(listing, market, result.quote, venues);
87
91
  const venue = choice.length > 0 ? null : bareListingNote(listing, result.quote);
88
92
  // A row saved as 247540:KRX is quoted from KOSDAQ, the board it trades on.
89
93
  const board = tradingBoardNote(listing, result.quote);
90
94
  return { result, listing, notes: [...choice, venue ?? [], board ?? [], also(result.quote), currencyCodeNote(listing, result.quote) ?? []].flat() };
91
95
  }
96
+ // No answer from the data service: no venue lookup or other listing would get one either.
97
+ if (isNetworkFailure(result.error)) return { result, listing, notes: [] };
92
98
  const notTraded = await notTradedMessage(listing, market);
93
99
  if (notTraded) return { result, listing, failure: { message: notTraded, oneLine: notTraded }, notes: [] };
94
100
  if (!isNoProviderError(result.error)) return { result, listing, notes: [] };
95
- const other = await quoteBareListing(listing, market, request, { refresh });
101
+ const other = await quoteBareListing(listing, market, request, { refresh, datesChangeBasis });
96
102
  if (other?.kind === "quoted") {
97
103
  return {
98
104
  result: { target: other.listing.request, quote: other.quote },
@@ -110,7 +116,7 @@ export async function answerQuote(
110
116
  listing.exchange ? null : knownGapForTicker(listing.symbol),
111
117
  ].filter((line): line is string => !!line);
112
118
  if (!other && tryLines.length === 0) {
113
- return await answerCompanyName(result, listing, market, request, refresh)
119
+ return await answerCompanyName(result, listing, market, request, refresh, datesChangeBasis)
114
120
  ?? { result: await withCurrencyPairHint(result, market), listing, notes: [] };
115
121
  }
116
122
  const message = other?.message ?? `${listing.symbol} has no ${request.noun}.`;
@@ -127,8 +133,12 @@ export async function answerQuote(
127
133
  * A company's name typed as one word (`Apple`) that the source says is not a
128
134
  * ticker, answered as the listing search names for it with the line saying so
129
135
  * ("Read "Apple" as AAPL:NASDAQ (Apple Inc.)."), as a name of several words
130
- * is. Null when the word names no company, or that listing has no quote
131
- * either: the word keeps its own "Not a ticker".
136
+ * is. A source that could not answer for the word (a timeout, a rate limit)
137
+ * says nothing of it, but when search resolves it to a company, it is read the
138
+ * same way; when that listing has no quote either, the row says the word is not
139
+ * a ticker and names the company. Null when the word names no company, or that
140
+ * listing has no quote though the source did say "not a ticker": the word keeps
141
+ * its own answer.
132
142
  */
133
143
  async function answerCompanyName(
134
144
  result: QuoteBatchResult,
@@ -136,19 +146,26 @@ async function answerCompanyName(
136
146
  market: MarketContext,
137
147
  request: ListingDataRequest,
138
148
  refresh: boolean,
149
+ datesChangeBasis: boolean,
139
150
  ): Promise<QuoteAnswer | null> {
140
- if (!listing.typed || listing.exchange || !notATickerSymbol(errorMessage(result.error) ?? "")) return null;
151
+ if (!listing.typed || listing.exchange) return null;
152
+ const notATicker = !!notATickerSymbol(errorMessage(result.error) ?? "");
153
+ const unsettled = !notATicker && isUnsettledNoProvider(result.error);
154
+ if (!notATicker && !unsettled) return null;
141
155
  const named = await oneWordCompanyName(listing.typed, market);
142
- if (!named) return null;
156
+ // A real ticker a source failed to quote is not a company's name, even when the company carries the word.
157
+ if (!named || (unsettled && named.symbol.toUpperCase() === listing.symbol.toUpperCase())) return null;
143
158
  // A listing on a venue the app cannot read is no answer: the word keeps its own "Not a ticker".
144
159
  const resolved = await resolveCliListing(named.key, null, market).catch((error: unknown) => {
145
160
  if (error instanceof ListingArgError) return null;
146
161
  throw error;
147
162
  });
148
- if (!resolved) return null;
149
- const found = { ...resolved, readAs: named.readAs, ...named.meant ? { meant: named.meant } : {} };
150
- const [quoted] = await market.dataProvider.getQuotesBatch([found.request], { forceRefresh: refresh, revalidate: true });
151
- return quoted?.quote ? answerQuote(quoted, found, market, request, refresh, null) : null;
163
+ const found = resolved ? { ...resolved, readAs: named.readAs, ...named.meant ? { meant: named.meant } : {} } : null;
164
+ const [quoted] = found ? await market.dataProvider.getQuotesBatch([found.request], { forceRefresh: refresh, revalidate: true, datesChangeBasis }) : [];
165
+ if (found && quoted?.quote) return answerQuote(quoted, found, market, request, refresh, null, { datesChangeBasis });
166
+ if (!unsettled) return null;
167
+ const sentence = await notATickerWords(listing.typed, market);
168
+ return { result: { ...result, error: new Error(sentence) }, listing, notes: [] };
152
169
  }
153
170
 
154
171
  /** A listing a command found a quote for, with what `quote` says beside it. */
@@ -18,7 +18,9 @@ import {
18
18
  import { isEpochMilliseconds, parseReportTime } from "../utils/utc-time";
19
19
  import type { ReportFreshness } from "./pane-functions/freshness";
20
20
  import {
21
+ EXPORT_DATING_COLUMNS,
21
22
  EXPORT_IDENTITY_COLUMNS,
23
+ exportDatingCells,
22
24
  exportIdentityCells,
23
25
  exportSourceLines,
24
26
  type ExportIdentity,
@@ -46,6 +48,12 @@ export interface CliReportTables {
46
48
  footer: string[];
47
49
  /** What the rows are of, for the fixed columns every row ends with (`EXPORT_IDENTITY_COLUMNS`). */
48
50
  identity?: ExportIdentity;
51
+ /**
52
+ * The rows are statistics dated by a period and a release: every table also
53
+ * ends with `period`, `released` and `retrieved` (`EXPORT_DATING_COLUMNS`),
54
+ * less any it has by name, with `as_of` the release.
55
+ */
56
+ dated?: boolean;
49
57
  }
50
58
 
51
59
  /** A column as a report declares it: the text view draws `format(value, row)`, or the value itself. */
@@ -623,19 +631,22 @@ function csvLine(row: readonly unknown[]): string {
623
631
  function identifiedTable(table: CliReportTable, report: CliReportTables, freshness: ReportFreshness | undefined): CliReportTable {
624
632
  if (!freshness || table.columns.length === 0) return table;
625
633
  const identity = { ...report.identity, ...table.identity };
626
- const cells = exportIdentityCells(freshness, identity);
627
- const rowCells = (index: number) => {
634
+ const identityFor = (index: number) => {
628
635
  const own = table.rowIdentities?.[index];
629
- return own ? exportIdentityCells(freshness, { ...identity, ...own }) : cells;
636
+ return own ? { ...identity, ...own } : identity;
630
637
  };
631
638
  const added = EXPORT_IDENTITY_COLUMNS.flatMap((column, index) => table.columns.includes(column) ? [] : [index]);
632
- if (added.length === 0) return table;
639
+ const dating = report.dated
640
+ ? EXPORT_DATING_COLUMNS.flatMap((column, index) => table.columns.includes(column) ? [] : [index]) : [];
641
+ if (added.length === 0 && dating.length === 0) return table;
633
642
  return {
634
643
  ...table,
635
- columns: [...table.columns, ...added.map((index) => EXPORT_IDENTITY_COLUMNS[index]!)],
644
+ columns: [...table.columns, ...added.map((index) => EXPORT_IDENTITY_COLUMNS[index]!), ...dating.map((index) => EXPORT_DATING_COLUMNS[index]!)],
636
645
  rows: table.rows.map((row, index) => {
637
- const identityCells = rowCells(index);
638
- return [...row, ...added.map((column) => identityCells[column]!)];
646
+ const rowIdentity = identityFor(index);
647
+ const identityCells = exportIdentityCells(freshness, rowIdentity);
648
+ const datingCells = exportDatingCells(freshness, rowIdentity);
649
+ return [...row, ...added.map((column) => identityCells[column]!), ...dating.map((column) => datingCells[column]!)];
639
650
  }),
640
651
  };
641
652
  }
@@ -5,9 +5,18 @@ import { pluginSeedFailure, pluginSeedNotice, samePluginSeedFailure, seedExtract
5
5
  import { setPluginSeedNotice } from "../plugins/seed-notice";
6
6
  import { debugLog } from "../utils/debug-log";
7
7
  import type { AppConfig } from "../types/config";
8
+ import { withDeadline } from "../utils/async-deadline";
8
9
 
9
10
  const log = debugLog.createLogger("plugin-seed");
10
11
 
12
+ /**
13
+ * How long a launch waits for the plugin registry before it starts without
14
+ * the plugins left to install. A connection that hangs would otherwise hold
15
+ * the screen blank: the registry is read first, and git would hang the same
16
+ * way after it. They are retried at the next launch.
17
+ */
18
+ const REGISTRY_WAIT_MS = 8_000;
19
+
11
20
  /**
12
21
  * Reinstalls plugins that moved out of this repository into their own.
13
22
  *
@@ -40,7 +49,10 @@ export async function restoreExtractedPlugins(): Promise<Pick<AppConfig, "seeded
40
49
  // Without git every clone fails the same way: no registry read, no attempt.
41
50
  gitMissing ??= !gitInstalled();
42
51
  if (gitMissing) throw new Error("git is not installed");
43
- listings ??= loadRegistryListings();
52
+ // Only no answer at all stops here: the network hangs, and a clone would
53
+ // too, so nothing is tried until the next launch. A registry that fails
54
+ // is not reported as silent: the plugins install from their repositories.
55
+ listings ??= withDeadline(loadRegistryListings(), REGISTRY_WAIT_MS, "the plugin registry did not answer");
44
56
  return installListedPlugin(repo, { quiet: true }, await listings);
45
57
  });
46
58
  // Recorded, so doctor knows a launch tried, and a failure the next launch
package/src/cli/result.ts CHANGED
@@ -18,7 +18,10 @@ import { formatFreshnessLine, withSavedCopy, type ReportFreshness } from "./pane
18
18
  import { withSearchHints, type FredSeriesCheck } from "./not-a-ticker";
19
19
  import { renderReportCsv, renderReportNdjson, withCsvFooter, type CliReportTables } from "./report-tables";
20
20
  import {
21
+ EXPORT_DATING_COLUMNS,
21
22
  EXPORT_IDENTITY_COLUMNS,
23
+ exportDatingCells,
24
+ exportDatingFields,
22
25
  exportIdentityCells,
23
26
  exportIdentityFields,
24
27
  exportSource,
@@ -120,6 +123,15 @@ export interface CliResultRenderOptions<T = unknown, Row = Record<string, unknow
120
123
  exportIdentity?: ExportIdentity | ((row: Row) => ExportIdentity);
121
124
  /** What CSV says after the source lines as `# note:` lines: what the rows do not show, such as where a series begins. */
122
125
  exportNotes?: readonly string[];
126
+ /**
127
+ * The rows are statistics dated by a period and a release (`econ`, `fred`):
128
+ * CSV and NDJSON rows also end with `period`, `released` and `retrieved`
129
+ * (`EXPORT_DATING_COLUMNS`) after the five fixed columns, from the `period`
130
+ * and `released` of each row's `exportIdentity`, and `as_of` is the release.
131
+ * A column the rows already have by name keeps its own values. Needs a
132
+ * cited source, as the fixed columns do.
133
+ */
134
+ exportDated?: boolean;
123
135
  /**
124
136
  * Why a row's figures are empty (`Not a ticker: ZZZZQQ.`, `Chg%: no previous
125
137
  * close`), for the `status` column CSV writes after the row's own columns and
@@ -348,20 +360,31 @@ function renderCsv<Row extends Record<string, unknown>>(
348
360
  columns?: CliResultColumn<Row>[],
349
361
  identity?: (row: Row) => string[],
350
362
  status?: (row: Row) => string | null | undefined,
363
+ dating?: (row: Row) => string[],
351
364
  ): string {
352
365
  const resolvedColumns = columns && columns.length > 0
353
366
  ? columns
354
367
  : inferColumns(rows as Record<string, unknown>[]) as CliResultColumn<Row>[];
355
368
  const headers = resolvedColumns.map((column) => column.header);
369
+ // A dated export's own `released` column keeps its values; the others close the row.
370
+ const datingAdded = dating
371
+ ? EXPORT_DATING_COLUMNS.flatMap((column, index) => headers.includes(column) ? [] : [index]) : [];
356
372
  return serializeCsv([
357
- [...headers, ...status ? [statusHeader(headers)] : [], ...identity ? EXPORT_IDENTITY_COLUMNS : []],
373
+ [
374
+ ...headers,
375
+ ...status ? [statusHeader(headers)] : [],
376
+ ...identity ? EXPORT_IDENTITY_COLUMNS : [],
377
+ ...datingAdded.map((index) => EXPORT_DATING_COLUMNS[index]!),
378
+ ],
358
379
  ...rows.map((row) => {
359
380
  const values = resolvedColumns.map((column) => (column.value ? column.value(row) : row[column.key]));
360
381
  const blanked = resolvedColumns.flatMap((column, index) => isNonFiniteNumber(values[index]) ? [column.header] : []);
382
+ const datingCells = dating?.(row) ?? [];
361
383
  return [
362
384
  ...values.map((value) => isNonFiniteNumber(value) ? "" : value),
363
385
  ...status ? [exportStatusText(status(row), blanked)] : [],
364
386
  ...identity?.(row) ?? [],
387
+ ...datingAdded.map((index) => datingCells[index] ?? ""),
365
388
  ];
366
389
  }),
367
390
  ]);
@@ -534,13 +557,26 @@ function serializeCliRows<T, Row extends Record<string, unknown>>(
534
557
  return rows.map((row) => {
535
558
  if (!isPlainObject(row)) return JSON.stringify(row);
536
559
  const fields = ndjsonRow(row as Row, status, statusKey);
537
- return JSON.stringify(cited ? { ...fields, ...exportIdentityFields(cited, rowIdentity(row as Row, renderOptions.exportIdentity)) } : fields);
560
+ if (!cited) return JSON.stringify(fields);
561
+ const identity = rowIdentity(row as Row, renderOptions.exportIdentity);
562
+ // A dated export's own fields (a row's `released`) keep their values.
563
+ const dating = renderOptions.exportDated
564
+ ? Object.fromEntries(Object.entries(exportDatingFields(cited, identity)).filter(([key]) => !(key in fields))) : {};
565
+ return JSON.stringify({ ...fields, ...exportIdentityFields(cited, identity), ...dating });
538
566
  }).join("\n");
539
567
  }
540
568
  if (options.format === "csv") {
541
- if (!cited) return renderCsv(rows as Row[], renderOptions.columns, undefined, status);
542
- const csv = renderCsv(rows as Row[], renderOptions.columns, (row) => exportIdentityCells(cited, rowIdentity(row, renderOptions.exportIdentity)), status);
543
- return withCsvFooter(csv, [...exportSourceLines(cited), ...(renderOptions.exportNotes ?? []).map((note) => `note: ${note}`)]);
569
+ // The rows a window cut say so after them, as text and JSON do: a script has no other way to know.
570
+ const cutNote = window.note ? [`note: ${window.note}`] : [];
571
+ if (!cited) return withCsvFooter(renderCsv(rows as Row[], renderOptions.columns, undefined, status), cutNote);
572
+ const csv = renderCsv(
573
+ rows as Row[],
574
+ renderOptions.columns,
575
+ (row) => exportIdentityCells(cited, rowIdentity(row, renderOptions.exportIdentity)),
576
+ status,
577
+ renderOptions.exportDated ? (row) => exportDatingCells(cited, rowIdentity(row, renderOptions.exportIdentity)) : undefined,
578
+ );
579
+ return withCsvFooter(csv, [...exportSourceLines(cited), ...cutNote, ...(renderOptions.exportNotes ?? []).map((note) => `note: ${note}`)]);
544
580
  }
545
581
  if (renderOptions.text) {
546
582
  return renderOptions.text(result.data);
@@ -608,6 +644,22 @@ export function printCliResult<T, Row extends Record<string, unknown> = Record<s
608
644
  console.error(`${cliStyles.warning("warning:")} ${hinted(warning)}`);
609
645
  }
610
646
  }
647
+ // CSV says what its rows do not in closing `# note:` lines; NDJSON has no place for one among its rows,
648
+ // so the same notes go to stderr, where the warnings are.
649
+ if (options.format === "ndjson" && !options.quiet) {
650
+ for (const note of exportNdjsonNotes(result, options, renderOptions)) console.error(`note: ${note}`);
651
+ }
652
+ }
653
+
654
+ /** What a `--limit`, or the default a command caps its rows at, left out of a list, then the command's own notes: what CSV closes with as `# note:` lines. */
655
+ function exportNdjsonNotes<T, Row extends Record<string, unknown>>(
656
+ result: CliResult<T>,
657
+ options: CliGlobalOptions,
658
+ renderOptions: CliResultRenderOptions<T, Row>,
659
+ ): string[] {
660
+ if (renderOptions.tables || renderOptions.text) return [];
661
+ const cut = windowed(asRows(result.data, renderOptions.rows), options, renderOptions).note;
662
+ return [...cut ? [cut] : [], ...renderOptions.exportNotes ?? []];
611
663
  }
612
664
 
613
665
  export function serializeCliError(error: CliErrorObject, options: CliGlobalOptions): string {
@@ -85,7 +85,8 @@ export interface CliTicker {
85
85
  /**
86
86
  * A bare coin ticker (ETH) read as a listing that is not the coin, such as a
87
87
  * fund named for it: "ETH is Grayscale Ethereum Mini Trust ETF, not ethereum.
88
- * For ethereum use ETH-USD." It is also the last of `notes`.
88
+ * For ethereum use ETH-USD." A live company that only shares the letters gets
89
+ * "Also a coin: LINK-USD" (`notTheCoinNote`). It is also the last of `notes`.
89
90
  */
90
91
  coinNote: string | null;
91
92
  /**
@@ -367,7 +368,7 @@ async function describeResolution(
367
368
  const formerIssuer = describeFormerIssuer(listing.symbol, payload, current);
368
369
  // One line: a company that left the symbol, else one that merged away from it.
369
370
  const line = tickerChangeLine(change) ?? formerIssuer?.note;
370
- const coinNote = listing.exchange ? null : savedListingCoinNote(listing.symbol, resolved.ticker);
371
+ const coinNote = listing.exchange ? null : savedListingCoinNote(listing.symbol, resolved.ticker, { ended: !!resolved.ended });
371
372
  return { ...resolved, notes: [...line ? [line] : [], ...resolved.notes, ...coinNote ? [coinNote] : []], change, formerIssuer, coinNote,
372
373
  readAs: listing.readAs ?? null, bareSymbol: listing.exchange ? null : listing.symbol, otherVenues: resolved.otherVenues ?? null };
373
374
  }
@@ -7,6 +7,7 @@ import { AXIS_DOUBLE_PRESS_MS, isVerticalWheelDirection } from "./panel-gestures
7
7
  import { type ChartToolSpan, CHART_DRAWING_COLORS, isDrawingTool, type ChartDrawing } from "./tools";
8
8
  import { ChartDrawingStore, NO_DRAWINGS } from "./drawing-store";
9
9
  import { CompositeLegend } from "./legend";
10
+ import { compositeRangeStatisticsLevels } from "./range-statistics";
10
11
  import { ChartToolbar, CHART_TOOLBAR_WIDTH } from "./toolbar";
11
12
  import { AsciiText, Box, ChartSurface, Text, useUiCapabilities, type BoxRenderable } from "../../../ui";
12
13
  import { Button } from "../../ui/button";
@@ -26,6 +27,7 @@ import { usePaneFocused } from "../../../state/app/activity";
26
27
  import { useTabCursorLink } from "../../../state/app/tab-cursor";
27
28
  import { useThemeColors, useThemeId } from "../../../theme/theme-context";
28
29
  import { themedSeriesColor, themeSeriesColors } from "../../../theme/series-colors";
30
+ import { themeKeysDirection } from "../../../theme/themes";
29
31
  import { assignSeriesLineCues } from "./series-cues";
30
32
  import { chartSurfaceBackground, usePaneSurface } from "../../layout/pane/surface";
31
33
  import { CHART_WATERMARK_ROLE } from "../../../utils/screenshot-watermark";
@@ -204,10 +206,12 @@ function ThemedCompositeChart({
204
206
  timePick,
205
207
  pricePick,
206
208
  levels,
209
+ rangeStatisticsSeriesId,
207
210
  chartLayout,
208
211
  onChartLayoutChange,
209
212
  }: CompositeChartProps & { chartLayout: ChartLayoutPrefs; onChartLayoutChange: ChartLayoutUpdate }) {
210
213
  const activeThemeColors = useThemeColors();
214
+ const keysDirection = themeKeysDirection(useThemeId());
211
215
  const { cellWidthPx = 8, cellHeightPx = 18, pixelRatio = 1, fractionalViewport = false } = useUiCapabilities();
212
216
  const isDesktopWeb = useUiCapabilities().nativePaneChrome === true;
213
217
  const showTextFallback = useShowChartTextFallback();
@@ -337,6 +341,20 @@ function ThemedCompositeChart({
337
341
  textDim: colors?.textDim ?? activeThemeColors.textDim,
338
342
  negative: colors?.negative ?? activeThemeColors.negative,
339
343
  }), [activeThemeColors, colors, paneSurface]);
344
+ const rangeStatisticsLevels = useMemo(() => {
345
+ void lastTickKey;
346
+ return compositeRangeStatisticsLevels(
347
+ panelSeries.find((entry) => entry.id === rangeStatisticsSeriesId),
348
+ effectiveViewport,
349
+ resolvedColors.textDim,
350
+ formatValue,
351
+ );
352
+ }, [effectiveViewport, formatValue, lastTickKey, panelSeries, rangeStatisticsSeriesId, resolvedColors.textDim]);
353
+ const displayedLevels = useMemo(() => (
354
+ levels && rangeStatisticsLevels && levels.seriesId === rangeStatisticsLevels.seriesId
355
+ ? { ...levels, items: [...levels.items, ...rangeStatisticsLevels.items] }
356
+ : levels ?? rangeStatisticsLevels
357
+ ), [levels, rangeStatisticsLevels]);
340
358
  // A custom x axis means x is not time: a tenor, a fraction, a return. Its
341
359
  // last observation belongs at the right edge, under its own label.
342
360
  const rightOffsetRatio = xAxis ? 0 : COMPOSITE_RIGHT_OFFSET_RATIO;
@@ -351,6 +369,7 @@ function ThemedCompositeChart({
351
369
  timelineSeries: marketTimelineSeries,
352
370
  rightOffsetRatio,
353
371
  valueScales,
372
+ referenceLevels: rangeStatisticsLevels,
354
373
  });
355
374
  }, [
356
375
  clipToViewport,
@@ -361,6 +380,7 @@ function ThemedCompositeChart({
361
380
  panelSeries,
362
381
  panels,
363
382
  rightOffsetRatio,
383
+ rangeStatisticsLevels,
364
384
  valueScales,
365
385
  ]);
366
386
  // Gutters follow the axes the scene actually built. Reading the series list
@@ -426,7 +446,7 @@ function ThemedCompositeChart({
426
446
  availableAxisWidth,
427
447
  Math.max(
428
448
  Math.min(MINIMUM_AXIS_LABEL_WIDTH, maximumAxisWidth),
429
- ...(levels?.items ?? []).map((level) => level.axisLabel?.length ?? 0),
449
+ ...(displayedLevels?.items ?? []).map((level) => level.axisLabel?.length ?? 0),
430
450
  ...(layoutPanels ?? []).flatMap((panel) => (["left", "right"] as const).map((side) => {
431
451
  const width = compositeAxisLabelWidth(panel.axes[side], formatAxisValue, includeCursorLabels);
432
452
  if (!includeSeriesTags || !width) return includeCursorLabels ? width : Math.min(width, maximumAxisWidth);
@@ -437,7 +457,7 @@ function ThemedCompositeChart({
437
457
  return (width + 1) * lanes;
438
458
  })),
439
459
  ),
440
- ), [availableAxisWidth, formatAxisValue, includeCursorLabels, includeSeriesTags, layoutPanels, maximumAxisWidth, levels?.items]);
460
+ ), [availableAxisWidth, displayedLevels?.items, formatAxisValue, includeCursorLabels, includeSeriesTags, layoutPanels, maximumAxisWidth]);
441
461
  const leftAxisWidth = hasLeftAxis ? resolvedAxisWidth : 0;
442
462
  const rightAxisWidth = hasRightAxis ? resolvedAxisWidth : 0;
443
463
  const axisGap = resolvedAxisWidth > 0 ? 1 : 0;
@@ -689,7 +709,7 @@ function ThemedCompositeChart({
689
709
  onActivate,
690
710
  timePick,
691
711
  pricePick,
692
- levels,
712
+ levels: displayedLevels,
693
713
  visibleLegendSeries,
694
714
  visibleSeriesIds,
695
715
  onToggleSeries,
@@ -1016,6 +1036,8 @@ function ThemedCompositeChart({
1016
1036
  visibleSeriesIds={visibleSeriesIds}
1017
1037
  lineCues={lineCues}
1018
1038
  cellPlot={showTextFallback && !isDesktopWeb}
1039
+ // A color-blind safe theme's blue and vermillion are no convention: a candle series keys them.
1040
+ directionDownColor={keysDirection ? resolvedColors.negative : null}
1019
1041
  width={totalWidth}
1020
1042
  accessory={legendAccessory}
1021
1043
  accessoryWidth={legendAccessoryWidth}
@@ -1136,7 +1158,7 @@ function ThemedCompositeChart({
1136
1158
  showTextFallback={showTextFallback}
1137
1159
  onPickTime={timePick?.onPick}
1138
1160
  onPickPrice={pricePick?.onPick}
1139
- levels={levelHost?.panel.id === panel.id ? levels ?? null : null}
1161
+ levels={levelHost?.panel.id === panel.id ? displayedLevels ?? null : null}
1140
1162
  selectedLevelId={selectedLevelId}
1141
1163
  onSelectLevel={setSelectedLevelId}
1142
1164
  valueScalable={interactive}
@@ -2,6 +2,7 @@ import { formatMarketPrice, formatMarketPriceWithCurrency, stablePriceFractionDi
2
2
  import { INDEX_POINTS_UNIT } from "../../../market-data/market/index-level";
3
3
  import type { ResolvedSeries, TimeSeriesPoint } from "../../../time-series/types";
4
4
  import type { CompositeAxisDomain, CompositePanelScene } from "./types";
5
+ import { formatNumber, formatPercentRaw } from "../../../utils/format";
5
6
  import { timeZoneLabel, zonedWallClockMs } from "../../../utils/zoned-date-time";
6
7
 
7
8
  const CURRENCY_SYMBOLS: Record<string, string> = {
@@ -237,6 +238,28 @@ function compactResolution(value: number): number {
237
238
  return absolute > 0 ? 10 ** (Math.floor(Math.log10(absolute)) - 2) : 0;
238
239
  }
239
240
 
241
+ /** A series of changes over a baseline: returns since the range began, year-on-year or quarter-on-quarter growth. */
242
+ function isChangeSeries(series: ResolvedSeries): boolean {
243
+ return series.unit.trim() === "%" && (series.transform === "percent" || series.transform === "yoy" || series.transform === "qoq");
244
+ }
245
+
246
+ /**
247
+ * A value worked out from prices (a formula, a spread, a price over an FX
248
+ * rate) at its legs' decimals: bare beside a stated unit (92.6164 USD /
249
+ * barrel), with its derived unit, or with its currency's sign ($12.34).
250
+ */
251
+ function formatDerivedValue(value: number, series: ResolvedSeries, decimals: number): string {
252
+ const number = formatNumber(Math.abs(value), decimals);
253
+ const sign = value < 0 && /[1-9]/.test(number) ? "-" : "";
254
+ if (series.statedUnit) return `${sign}${number}`;
255
+ const unit = series.unit.trim();
256
+ if (series.unitGroup.toLowerCase().startsWith("derived-unit:")) return `${sign}${number} ${unit}`;
257
+ const currency = unitCurrencyCode(unit);
258
+ if (!currency) return `${sign}${number}`;
259
+ const symbol = CURRENCY_SYMBOLS[currency];
260
+ return symbol ? `${sign}${symbol}${number}` : `${sign}${number} ${currency}`;
261
+ }
262
+
240
263
  /** `referencePrice` defaults to the series' own; a study on a price axis
241
264
  * passes the axis's reference so it shares the price's decimals. */
242
265
  export function formatCompositeSeriesValue(
@@ -244,6 +267,10 @@ export function formatCompositeSeriesValue(
244
267
  series: ResolvedSeries,
245
268
  referencePrice = seriesPriceReference(series),
246
269
  ): string {
270
+ // A return reads as every other return in the app, signed with two decimals, so a legend of them lines up
271
+ // (+34.02%, +0.76%, +15.80%) instead of one precision per magnitude.
272
+ if (isChangeSeries(series)) return formatPercentRaw(value);
273
+ if (series.valueDecimals !== undefined) return formatDerivedValue(value, series, series.valueDecimals);
247
274
  if (series.statedUnit) {
248
275
  // The number alone: the legend prints the unit beside it, as FUT prints
249
276
  // a price beside its unit (4.6242 USD / gallon, not $4.6242).
@@ -17,6 +17,9 @@ export type CompositeChartInteraction =
17
17
  | "arm-line"
18
18
  | "arm-measure"
19
19
  | "arm-pencil"
20
+ | "arm-ray"
21
+ | "arm-rect"
22
+ | "arm-fib"
20
23
  | "arm-zoom"
21
24
  | "cycle-colour"
22
25
  | "clear-cursor"
@@ -366,6 +369,9 @@ export function resolveCompositeChartInteraction(event: {
366
369
  if (shifted && key === "z") return "arm-zoom";
367
370
  if (shifted && key === "d") return "arm-line";
368
371
  if (shifted && key === "p") return "arm-pencil";
372
+ if (shifted && key === "j") return "arm-ray";
373
+ if (shifted && key === "b") return "arm-rect";
374
+ if (shifted && key === "f") return "arm-fib";
369
375
  if (shifted && key === "h") return "arm-level";
370
376
  // Delete is the same act on a keyboard that has the key, or on a Mac with fn.
371
377
  if (key === "backspace" || key === "delete") return "delete-drawing";