gloomberb 0.18.1 → 0.18.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (416) hide show
  1. package/package.json +1 -1
  2. package/patches/electrobun@1.18.1.patch +106 -5
  3. package/src/api-client/commodity-balances.ts +6 -0
  4. package/src/api-client/company-kpis.ts +9 -0
  5. package/src/api-client/crypto-markets.ts +9 -0
  6. package/src/api-client/data.ts +21 -5
  7. package/src/api-client/equity-screener.ts +2 -0
  8. package/src/api-client/index.ts +2 -0
  9. package/src/api-client/request.ts +5 -3
  10. package/src/api-client/telemetry.ts +17 -0
  11. package/src/api-client/types.ts +5 -0
  12. package/src/app/global-shortcuts.ts +1 -16
  13. package/src/app/pane-runtime/plugin-bindings.ts +3 -1
  14. package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
  15. package/src/app/runtime/plugin-bindings.ts +6 -2
  16. package/src/app/runtime/update.ts +28 -8
  17. package/src/architecture/pane-tabs-allowlist.json +65 -0
  18. package/src/brokers/logos.ts +66 -0
  19. package/src/brokers/profile-form.ts +3 -0
  20. package/src/brokers/remote-broker-adapter.ts +13 -3
  21. package/src/capabilities/index.ts +1 -1
  22. package/src/capabilities/service-capabilities.ts +7 -1
  23. package/src/cli/base-converter.ts +60 -18
  24. package/src/cli/commands/market.ts +354 -187
  25. package/src/cli/commands/news-search.ts +35 -8
  26. package/src/cli/commands/system.ts +35 -1
  27. package/src/cli/commands/ticker.ts +167 -60
  28. package/src/cli/currency-hints.ts +27 -0
  29. package/src/cli/fx-pair.ts +41 -4
  30. package/src/cli/glossary.ts +57 -4
  31. package/src/cli/help.ts +1 -1
  32. package/src/cli/helpers.ts +36 -3
  33. package/src/cli/index.ts +12 -1
  34. package/src/cli/issuer-membership.ts +103 -0
  35. package/src/cli/known-gaps.ts +37 -1
  36. package/src/cli/listing-arg.ts +51 -3
  37. package/src/cli/name-entries.ts +51 -12
  38. package/src/cli/pane-functions/capabilities.ts +2 -0
  39. package/src/cli/pane-functions/catalog.ts +19 -1
  40. package/src/cli/pane-functions/discovery.ts +15 -1
  41. package/src/cli/pane-functions/dom.ts +11 -5
  42. package/src/cli/pane-functions/freshness.ts +4 -4
  43. package/src/cli/pane-functions/function-help.ts +27 -3
  44. package/src/cli/pane-functions/headless.ts +31 -11
  45. package/src/cli/pane-functions/index.ts +67 -7
  46. package/src/cli/pane-functions/lock-note.ts +7 -4
  47. package/src/cli/pane-functions/resolver.ts +21 -6
  48. package/src/cli/quote-answer.ts +179 -0
  49. package/src/cli/report-tables.ts +136 -18
  50. package/src/cli/result.ts +12 -4
  51. package/src/cli/series-search.ts +2 -1
  52. package/src/cli/symbol-list.ts +47 -11
  53. package/src/cli/types.ts +4 -0
  54. package/src/components/chart/composite/composite-chart.tsx +25 -7
  55. package/src/components/chart/composite/drawing-store.ts +26 -6
  56. package/src/components/chart/composite/format.ts +13 -1
  57. package/src/components/chart/composite/interactions.ts +2 -1
  58. package/src/components/chart/composite/level-labels.ts +30 -0
  59. package/src/components/chart/composite/levels.ts +17 -6
  60. package/src/components/chart/composite/panel-surface.tsx +69 -8
  61. package/src/components/chart/composite/types.ts +6 -0
  62. package/src/components/chart/composite/use-composite-chart-keyboard.ts +1 -1
  63. package/src/components/command-bar/routes/root/command-items.ts +32 -6
  64. package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
  65. package/src/components/data-table/view.tsx +23 -3
  66. package/src/components/index.ts +3 -0
  67. package/src/components/layout/detached-pane-shell.tsx +1 -0
  68. package/src/components/layout/floating-pane.tsx +1 -0
  69. package/src/components/layout/header.tsx +228 -55
  70. package/src/components/layout/pane/content.tsx +16 -14
  71. package/src/components/layout/pane/footer/index.tsx +22 -3
  72. package/src/components/layout/pane/footer/model.ts +99 -4
  73. package/src/components/layout/pane/header-tabs.tsx +15 -1
  74. package/src/components/layout/pane/header.tsx +10 -3
  75. package/src/components/layout/pane/index.tsx +1 -0
  76. package/src/components/layout/pane/pane-tabs.tsx +9 -1
  77. package/src/components/onboarding/onboarding-frame.tsx +9 -11
  78. package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
  79. package/src/components/onboarding/wizard-model.ts +10 -3
  80. package/src/components/ui/button.tsx +11 -4
  81. package/src/components/ui/choice-dialog.tsx +8 -5
  82. package/src/components/ui/display.tsx +11 -6
  83. package/src/components/ui/fields.tsx +100 -20
  84. package/src/components/ui/icon.tsx +6 -1
  85. package/src/components/ui/list-row-lines.tsx +87 -0
  86. package/src/components/ui/list-view.tsx +14 -0
  87. package/src/components/ui/logo-mark.tsx +64 -0
  88. package/src/components/ui/number-step.ts +54 -0
  89. package/src/components/ui/select-button.tsx +5 -4
  90. package/src/components/ui/select-field.tsx +3 -3
  91. package/src/components/ui/stat-grid.tsx +28 -3
  92. package/src/components/ui/status.tsx +20 -4
  93. package/src/components/ui/tabs.tsx +9 -1
  94. package/src/components/ui/toggle.tsx +17 -3
  95. package/src/core/app-services.ts +4 -0
  96. package/src/core/state/app/instrument.ts +8 -3
  97. package/src/data/config/store/normalize.ts +4 -1
  98. package/src/data/plugin-cache.ts +32 -6
  99. package/src/docs/snapshot.json +5 -5
  100. package/src/i18n/es.ts +31 -3
  101. package/src/i18n/ja.ts +31 -3
  102. package/src/i18n/ko.ts +31 -3
  103. package/src/i18n/zh-cn.ts +32 -4
  104. package/src/i18n/zh-tw.ts +32 -4
  105. package/src/market-data/commodity-names.ts +80 -0
  106. package/src/market-data/coordinator/auxiliary.ts +3 -1
  107. package/src/market-data/coordinator/chart.ts +3 -1
  108. package/src/market-data/futures-board.ts +38 -1
  109. package/src/market-data/futures-contract.ts +91 -0
  110. package/src/market-data/fx-closes.ts +78 -1
  111. package/src/market-data/market/change-basis.ts +219 -0
  112. package/src/market-data/market/format.ts +28 -12
  113. package/src/market-data/market/freshness.ts +38 -0
  114. package/src/market-data/market/status.ts +55 -9
  115. package/src/market-data/market/year-to-date.ts +46 -0
  116. package/src/market-data/quotes/contributions.ts +12 -0
  117. package/src/market-data/quotes/delisting.ts +34 -0
  118. package/src/market-data/quotes/live-valuation.ts +23 -0
  119. package/src/market-data/quotes/resolution.ts +8 -2
  120. package/src/market-data/tax-equivalent.ts +10 -2
  121. package/src/notifications/app-notifier.ts +2 -2
  122. package/src/notifications/notification-log.ts +8 -6
  123. package/src/plugins/builtin/alerts/alert-engine.ts +3 -1
  124. package/src/plugins/builtin/alerts/cli.ts +26 -18
  125. package/src/plugins/builtin/alerts/events.ts +1 -0
  126. package/src/plugins/builtin/alerts/headless.ts +1 -1
  127. package/src/plugins/builtin/alerts/index.tsx +13 -4
  128. package/src/plugins/builtin/alerts/research-rules.ts +1 -0
  129. package/src/plugins/builtin/analytics/risk-pane.tsx +10 -7
  130. package/src/plugins/builtin/attention/pane.tsx +16 -14
  131. package/src/plugins/builtin/attention/trending.tsx +8 -6
  132. package/src/plugins/builtin/awards/pane.tsx +6 -6
  133. package/src/plugins/builtin/backtest/client.ts +5 -2
  134. package/src/plugins/builtin/backtest/export.ts +1 -1
  135. package/src/plugins/builtin/backtest/window.ts +16 -3
  136. package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
  137. package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
  138. package/src/plugins/builtin/broker-manager/index.tsx +20 -9
  139. package/src/plugins/builtin/broker-manager/model.ts +5 -0
  140. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
  141. package/src/plugins/builtin/broker-manager/table.ts +5 -1
  142. package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
  143. package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
  144. package/src/plugins/builtin/broker-trading/command.ts +103 -0
  145. package/src/plugins/builtin/broker-trading/controller.ts +688 -0
  146. package/src/plugins/builtin/broker-trading/dock.tsx +184 -0
  147. package/src/plugins/builtin/broker-trading/headless.ts +76 -0
  148. package/src/plugins/builtin/broker-trading/index.tsx +77 -0
  149. package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
  150. package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
  151. package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
  152. package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
  153. package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
  154. package/src/plugins/builtin/broker-trading/pane.tsx +507 -0
  155. package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
  156. package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
  157. package/src/plugins/builtin/broker-trading/ticket.tsx +578 -0
  158. package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
  159. package/src/plugins/builtin/buildout/format.ts +1 -0
  160. package/src/plugins/builtin/buildout/model/index.ts +16 -7
  161. package/src/plugins/builtin/buildout/pane/index.tsx +11 -7
  162. package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
  163. package/src/plugins/builtin/catalysts/client.ts +2 -2
  164. package/src/plugins/builtin/catalysts/pane.tsx +3 -3
  165. package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
  166. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
  167. package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
  168. package/src/plugins/builtin/chart-composer/headless.ts +2 -1
  169. package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
  170. package/src/plugins/builtin/chart-composer/pane.tsx +32 -11
  171. package/src/plugins/builtin/chart-composer/presets.ts +2 -2
  172. package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
  173. package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
  174. package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
  175. package/src/plugins/builtin/chat/index.tsx +1 -1
  176. package/src/plugins/builtin/chat/status-widget.tsx +1 -1
  177. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
  178. package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
  179. package/src/plugins/builtin/commodity-balances/client.ts +6 -4
  180. package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
  181. package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
  182. package/src/plugins/builtin/commodity-balances/model.ts +22 -0
  183. package/src/plugins/builtin/commodity-balances/pane.tsx +39 -9
  184. package/src/plugins/builtin/company-attention/app-rank.tsx +4 -5
  185. package/src/plugins/builtin/company-attention/pane.tsx +13 -11
  186. package/src/plugins/builtin/company-kpis/client.ts +3 -2
  187. package/src/plugins/builtin/company-kpis/headless.ts +5 -7
  188. package/src/plugins/builtin/company-kpis/model.ts +85 -2
  189. package/src/plugins/builtin/company-kpis/pane.tsx +30 -15
  190. package/src/plugins/builtin/composite-plugins.ts +3 -2
  191. package/src/plugins/builtin/correlation/headless.ts +2 -1
  192. package/src/plugins/builtin/correlation/relationship/model.ts +1 -0
  193. package/src/plugins/builtin/correlation/settings.ts +1 -0
  194. package/src/plugins/builtin/cot/headless.ts +1 -1
  195. package/src/plugins/builtin/cot/index.tsx +1 -1
  196. package/src/plugins/builtin/cot/model.ts +35 -12
  197. package/src/plugins/builtin/cot/pane.tsx +4 -3
  198. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  199. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
  200. package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
  201. package/src/plugins/builtin/credit-documents/pane.tsx +8 -7
  202. package/src/plugins/builtin/crypto-board/client.ts +33 -2
  203. package/src/plugins/builtin/crypto-board/headless.ts +5 -2
  204. package/src/plugins/builtin/crypto-board/model.ts +28 -8
  205. package/src/plugins/builtin/debt-maturities/pane.tsx +4 -3
  206. package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
  207. package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
  208. package/src/plugins/builtin/doe/pane.tsx +1 -1
  209. package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
  210. package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
  211. package/src/plugins/builtin/econ/calendar-model.ts +35 -0
  212. package/src/plugins/builtin/econ/index.tsx +31 -4
  213. package/src/plugins/builtin/econ/print-watch.ts +279 -0
  214. package/src/plugins/builtin/equity-screener/headless.ts +30 -5
  215. package/src/plugins/builtin/equity-screener/model.ts +68 -2
  216. package/src/plugins/builtin/equity-screener/pane.tsx +129 -119
  217. package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
  218. package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
  219. package/src/plugins/builtin/exposure/client.ts +4 -1
  220. package/src/plugins/builtin/exposure/editor.tsx +3 -3
  221. package/src/plugins/builtin/exposure/evidence.ts +4 -1
  222. package/src/plugins/builtin/exposure/holdings.ts +3 -1
  223. package/src/plugins/builtin/exposure/pane.tsx +43 -21
  224. package/src/plugins/builtin/filing-events/index.tsx +9 -3
  225. package/src/plugins/builtin/futures/table.tsx +6 -3
  226. package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
  227. package/src/plugins/builtin/gpu/board.tsx +4 -1
  228. package/src/plugins/builtin/gpu/changes.tsx +10 -10
  229. package/src/plugins/builtin/gpu/equities.tsx +4 -1
  230. package/src/plugins/builtin/gpu/history.tsx +4 -2
  231. package/src/plugins/builtin/gpu/pane.tsx +5 -3
  232. package/src/plugins/builtin/holders/client.ts +2 -1
  233. package/src/plugins/builtin/holders/headless.ts +2 -1
  234. package/src/plugins/builtin/holders/thirteenf-match.ts +3 -1
  235. package/src/plugins/builtin/insider/base-values.ts +76 -0
  236. package/src/plugins/builtin/insider/client.ts +3 -0
  237. package/src/plugins/builtin/insider/headless.ts +48 -10
  238. package/src/plugins/builtin/insider/index.tsx +17 -8
  239. package/src/plugins/builtin/insider/issuer.ts +34 -4
  240. package/src/plugins/builtin/insider/signals-client.ts +2 -2
  241. package/src/plugins/builtin/insider/table-model.ts +35 -13
  242. package/src/plugins/builtin/insider/table.tsx +5 -2
  243. package/src/plugins/builtin/iv-history/events.ts +145 -0
  244. package/src/plugins/builtin/iv-history/format.ts +3 -0
  245. package/src/plugins/builtin/iv-history/headless.ts +91 -7
  246. package/src/plugins/builtin/jobs/headless.ts +2 -2
  247. package/src/plugins/builtin/jobs/lock-note.ts +14 -3
  248. package/src/plugins/builtin/jodi/pane.tsx +4 -4
  249. package/src/plugins/builtin/market-halts/model.ts +31 -6
  250. package/src/plugins/builtin/market-halts/pane.tsx +3 -0
  251. package/src/plugins/builtin/market-movers/client.ts +4 -3
  252. package/src/plugins/builtin/market-movers/screener.ts +4 -3
  253. package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
  254. package/src/plugins/builtin/members/pane.tsx +8 -3
  255. package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
  256. package/src/plugins/builtin/news/wire/index.tsx +7 -2
  257. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
  258. package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
  259. package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
  260. package/src/plugins/builtin/notes/cli.ts +1 -1
  261. package/src/plugins/builtin/notes/export.ts +17 -6
  262. package/src/plugins/builtin/notes/files.ts +16 -5
  263. package/src/plugins/builtin/options/enrichment-model.ts +10 -9
  264. package/src/plugins/builtin/options/footer.ts +25 -20
  265. package/src/plugins/builtin/options/live-quotes.ts +33 -18
  266. package/src/plugins/builtin/options/model-spot.ts +70 -4
  267. package/src/plugins/builtin/options/view.tsx +118 -35
  268. package/src/plugins/builtin/options-calculator/pane.tsx +7 -4
  269. package/src/plugins/builtin/options-positioning/headless.ts +11 -3
  270. package/src/plugins/builtin/options-positioning/model.ts +24 -0
  271. package/src/plugins/builtin/options-positioning/pane.tsx +10 -12
  272. package/src/plugins/builtin/options-scenario/client.ts +19 -1
  273. package/src/plugins/builtin/options-scenario/headless.ts +13 -6
  274. package/src/plugins/builtin/options-scenario/pane.tsx +54 -11
  275. package/src/plugins/builtin/plugin-marketplace/pane.tsx +5 -4
  276. package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
  277. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -5
  278. package/src/plugins/builtin/portfolio-list/cli/render.ts +34 -10
  279. package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
  280. package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
  281. package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
  282. package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
  283. package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
  284. package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
  285. package/src/plugins/builtin/power/client.ts +2 -2
  286. package/src/plugins/builtin/power/pane.tsx +36 -31
  287. package/src/plugins/builtin/primary-dealers/client.ts +137 -62
  288. package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
  289. package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
  290. package/src/plugins/builtin/primary-dealers/model.ts +179 -129
  291. package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
  292. package/src/plugins/builtin/research/analyst-model.ts +5 -1
  293. package/src/plugins/builtin/reverse-dcf/client.ts +2 -1
  294. package/src/plugins/builtin/reverse-dcf/model.ts +4 -3
  295. package/src/plugins/builtin/risk-factors/index.tsx +12 -3
  296. package/src/plugins/builtin/scanner/feed.ts +6 -1
  297. package/src/plugins/builtin/scanner/headless.ts +1 -1
  298. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  299. package/src/plugins/builtin/seasonality/client.ts +2 -1
  300. package/src/plugins/builtin/sectors/client.ts +4 -3
  301. package/src/plugins/builtin/series-inventory.ts +1 -0
  302. package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
  303. package/src/plugins/builtin/shared/cloud-resource.ts +15 -2
  304. package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
  305. package/src/plugins/builtin/shared/free-routes.ts +24 -0
  306. package/src/plugins/builtin/shared/headless-options.ts +11 -0
  307. package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
  308. package/src/plugins/builtin/shared/research-trading.tsx +64 -0
  309. package/src/plugins/builtin/shared/series-list-detail.tsx +3 -1
  310. package/src/plugins/builtin/shared/volatility/options.ts +2 -1
  311. package/src/plugins/builtin/social-mentions/client.ts +2 -1
  312. package/src/plugins/builtin/social-mentions/headless.ts +1 -1
  313. package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
  314. package/src/plugins/builtin/themes/headless.ts +8 -2
  315. package/src/plugins/builtin/themes/model.ts +43 -10
  316. package/src/plugins/builtin/themes/pane.tsx +8 -6
  317. package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
  318. package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
  319. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
  320. package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
  321. package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
  322. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
  323. package/src/plugins/builtin/time-sales/pane.tsx +2 -2
  324. package/src/plugins/builtin/trace-bonds/client.ts +14 -3
  325. package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
  326. package/src/plugins/builtin/vol-surface/client.ts +23 -3
  327. package/src/plugins/builtin/vol-surface/headless.ts +33 -14
  328. package/src/plugins/builtin/vol-surface/model.ts +30 -10
  329. package/src/plugins/builtin/vol-surface/pane.tsx +35 -15
  330. package/src/plugins/builtin/vol-surface/stored.ts +34 -3
  331. package/src/plugins/builtin/vol-surface/tables.ts +14 -0
  332. package/src/plugins/builtin-editorial.ts +1 -1
  333. package/src/plugins/compat.ts +3 -0
  334. package/src/plugins/external-runtime.ts +5 -0
  335. package/src/plugins/loader.ts +6 -0
  336. package/src/plugins/plugin-icon.ts +61 -0
  337. package/src/plugins/registry/host-actions.ts +2 -1
  338. package/src/plugins/registry/index.ts +9 -1
  339. package/src/plugins/runtime/context.tsx +5 -0
  340. package/src/plugins/runtime/index.tsx +3 -0
  341. package/src/portfolio/column-values.ts +13 -4
  342. package/src/portfolio/cost-fx.ts +5 -28
  343. package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
  344. package/src/public/broker.ts +8 -0
  345. package/src/public/market-data.ts +5 -0
  346. package/src/renderers/dom/desktop/control-styles.ts +7 -1
  347. package/src/renderers/dom/desktop/controls.tsx +40 -8
  348. package/src/renderers/dom/desktop/icons.tsx +14 -0
  349. package/src/renderers/dom/desktop/list-view.tsx +51 -19
  350. package/src/renderers/dom/desktop/menu.tsx +10 -1
  351. package/src/renderers/dom/desktop/query-bar.tsx +2 -0
  352. package/src/renderers/dom/desktop/select-field.tsx +8 -6
  353. package/src/renderers/dom/host/style.ts +6 -1
  354. package/src/renderers/dom/host/tabs.tsx +22 -5
  355. package/src/renderers/dom/host/top-surface.ts +127 -0
  356. package/src/renderers/dom/styles.css +51 -5
  357. package/src/renderers/electrobun/bun/core-capabilities.ts +21 -1
  358. package/src/renderers/electrobun/bun/desktop/backend.ts +10 -0
  359. package/src/renderers/electrobun/bun/desktop/update.ts +210 -30
  360. package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
  361. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  362. package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
  363. package/src/renderers/electrobun/view/external-plugins.ts +1 -0
  364. package/src/renderers/electrobun/view/news-source.ts +4 -2
  365. package/src/renderers/electrobun/view/remote/asset-data-client.ts +8 -1
  366. package/src/renderers/opentui/start.tsx +7 -0
  367. package/src/series/validate.ts +2 -2
  368. package/src/sources/gloomberb-cloud/index.ts +57 -17
  369. package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
  370. package/src/sources/history-result.ts +3 -0
  371. package/src/sources/provider-errors.ts +3 -1
  372. package/src/sources/provider-router/history.ts +49 -6
  373. package/src/sources/provider-router/index.ts +18 -1
  374. package/src/state/app/activity.ts +13 -0
  375. package/src/sync/controller.ts +4 -0
  376. package/src/sync/core-contributors.ts +62 -11
  377. package/src/sync/local-only.ts +121 -0
  378. package/src/sync/react.ts +6 -0
  379. package/src/sync/types.ts +3 -0
  380. package/src/telemetry/crash-reports.ts +4 -0
  381. package/src/telemetry/update-failures-node.ts +59 -0
  382. package/src/telemetry/update-failures.ts +307 -0
  383. package/src/tickers/company-name-listing.ts +9 -7
  384. package/src/tickers/company-name.ts +43 -0
  385. package/src/tickers/listing-fate.ts +57 -8
  386. package/src/tickers/search/index.ts +24 -3
  387. package/src/time-series/price-comparison.ts +35 -0
  388. package/src/time-series/resolve.ts +52 -11
  389. package/src/time-series/spec.ts +1 -1
  390. package/src/time-series/types.ts +5 -0
  391. package/src/types/broker.ts +56 -1
  392. package/src/types/config.ts +6 -0
  393. package/src/types/data-provider.ts +11 -1
  394. package/src/types/financials.ts +23 -0
  395. package/src/types/headless.ts +19 -2
  396. package/src/types/instrument.ts +4 -0
  397. package/src/types/issuer-listings.ts +28 -0
  398. package/src/types/plugin.ts +24 -6
  399. package/src/types/price-history.ts +6 -1
  400. package/src/types/trading.ts +67 -0
  401. package/src/ui/host.tsx +4 -0
  402. package/src/updater/failure.ts +163 -0
  403. package/src/updater/index.ts +87 -23
  404. package/src/utils/base-currency.ts +108 -0
  405. package/src/utils/currency-names.ts +128 -0
  406. package/src/utils/currency-pair.ts +28 -1
  407. package/src/utils/currency-units.ts +32 -1
  408. package/src/utils/dom-screenshot.ts +22 -9
  409. package/src/utils/exchange-notation.generated.ts +360 -0
  410. package/src/utils/exchanges.ts +15 -322
  411. package/src/utils/format.ts +31 -1
  412. package/src/utils/instrument-identity.ts +38 -1
  413. package/src/utils/options.ts +46 -1
  414. package/src/utils/price-history.ts +4 -0
  415. package/src/utils/reported-money.ts +8 -4
  416. package/src/utils/sec.ts +68 -0
@@ -11,6 +11,7 @@ import { ChartToolbar, CHART_TOOLBAR_WIDTH } from "./toolbar";
11
11
  import { AsciiText, Box, ChartSurface, Text, useUiCapabilities, type BoxRenderable } from "../../../ui";
12
12
  import { useLiveStretch } from "../../../ui/live-stretch";
13
13
  import { useOptionalPaneInstanceId } from "../../../state/app/context";
14
+ import { usePaneFocused } from "../../../state/app/activity";
14
15
  import { useTabCursorLink } from "../../../state/app/tab-cursor";
15
16
  import { useThemeColors, useThemeId } from "../../../theme/theme-context";
16
17
  import { themedSeriesColor, themeSeriesColors } from "../../../theme/series-colors";
@@ -144,6 +145,7 @@ function ThemedCompositeChart({
144
145
  onCursorDateChange,
145
146
  onViewportChange,
146
147
  onActivate,
148
+ onPress,
147
149
  onToggleSeries,
148
150
  isSeriesToggleable,
149
151
  timePick,
@@ -155,6 +157,8 @@ function ThemedCompositeChart({
155
157
  const showTextFallback = useShowChartTextFallback();
156
158
  const [internalCursorDate, setInternalCursorDate] = useState<Date | null>(null);
157
159
  const [toolSpan, setToolSpan] = useState<ChartToolSpan | null>(null);
160
+ const [pointerDragging, setPointerDragging] = useState(false);
161
+ const paneFocused = usePaneFocused();
158
162
  const paneInstanceId = useOptionalPaneInstanceId();
159
163
  const [drawings, setDrawings] = useState<readonly ChartDrawing[]>(NO_DRAWINGS);
160
164
  const [selectedDrawingId, setSelectedDrawingId] = useState<string | null>(null);
@@ -333,6 +337,7 @@ function ThemedCompositeChart({
333
337
  availableAxisWidth,
334
338
  Math.max(
335
339
  Math.min(MINIMUM_AXIS_LABEL_WIDTH, maximumAxisWidth),
340
+ ...(levels?.items ?? []).map((level) => level.axisLabel?.length ?? 0),
336
341
  ...(layoutPanels ?? []).flatMap((panel) => (["left", "right"] as const).map((side) => {
337
342
  const width = compositeAxisLabelWidth(panel.axes[side], formatAxisValue, includeCursorLabels);
338
343
  if (!includeSeriesTags || !width) return includeCursorLabels ? width : Math.min(width, maximumAxisWidth);
@@ -343,7 +348,7 @@ function ThemedCompositeChart({
343
348
  return (width + 1) * lanes;
344
349
  })),
345
350
  ),
346
- ), [availableAxisWidth, formatAxisValue, includeCursorLabels, includeSeriesTags, layoutPanels, maximumAxisWidth]);
351
+ ), [availableAxisWidth, formatAxisValue, includeCursorLabels, includeSeriesTags, layoutPanels, maximumAxisWidth, levels?.items]);
347
352
  const leftAxisWidth = hasLeftAxis ? resolvedAxisWidth : 0;
348
353
  const rightAxisWidth = hasRightAxis ? resolvedAxisWidth : 0;
349
354
  const axisGap = resolvedAxisWidth > 0 ? 1 : 0;
@@ -523,6 +528,14 @@ function ThemedCompositeChart({
523
528
  removeDrawing,
524
529
  pickDrawColor,
525
530
  });
531
+ const pressFromPointer = useCallback(() => {
532
+ onPress?.();
533
+ activateFromPointer();
534
+ }, [activateFromPointer, onPress]);
535
+ const pressPlot = useCallback(() => {
536
+ cancelKeyboardPlacement();
537
+ onPress?.();
538
+ }, [cancelKeyboardPlacement, onPress]);
526
539
  const handleEmptyMouseScroll = useCallback((event: ChartMouseEvent) => {
527
540
  const scroll = event.scroll;
528
541
  if (!interactive || !navigationFrame || !scroll) return;
@@ -593,7 +606,7 @@ function ThemedCompositeChart({
593
606
  accessoryWidth={legendAccessoryWidth}
594
607
  formatValue={formatValue}
595
608
  showLatestChangePercent={showLatestChangePercent}
596
- onActivate={activateFromPointer}
609
+ onActivate={pressFromPointer}
597
610
  onToggleSeries={onToggleSeries}
598
611
  isSeriesToggleable={isSeriesToggleable}
599
612
  keyboardIndex={legendKeyboardIndex}
@@ -650,7 +663,11 @@ function ThemedCompositeChart({
650
663
  );
651
664
  }
652
665
 
653
- const showToolbar = interactive && navigable && plotWidth > CHART_TOOLBAR_WIDTH + 4;
666
+ // The tools belong to the focused pane, and stay while one is in hand or a
667
+ // press drags the plot, so focus moving elsewhere never hides what is
668
+ // drawing. A chart outside a pane (a share page) keeps them.
669
+ const toolbarWanted = paneFocused !== false || armedTool !== null || pointerDragging || toolSpan !== null;
670
+ const showToolbar = interactive && navigable && toolbarWanted && plotWidth > CHART_TOOLBAR_WIDTH + 4;
654
671
  // Bars shorter than a day carry a time of day the cursor reads out.
655
672
  const intradayBars = scene.timeZone !== undefined || visibleSeries.some((entry) => (
656
673
  !!entry.historyResolution && isIntradayResolution(entry.historyResolution)
@@ -708,7 +725,7 @@ function ThemedCompositeChart({
708
725
  accessoryWidth={legendAccessoryWidth}
709
726
  formatValue={formatValue}
710
727
  showLatestChangePercent={showLatestChangePercent}
711
- onActivate={activateFromPointer}
728
+ onActivate={pressFromPointer}
712
729
  onToggleSeries={onToggleSeries}
713
730
  isSeriesToggleable={isSeriesToggleable}
714
731
  keyboardIndex={legendKeyboardIndex}
@@ -726,11 +743,11 @@ function ThemedCompositeChart({
726
743
  showColors={isDrawingTool(armedTool) || !!selectedDrawingId}
727
744
  levelTool={levelsEditable}
728
745
  onArmTool={(tool) => {
729
- activateFromPointer();
746
+ pressFromPointer();
730
747
  armTool(tool);
731
748
  }}
732
749
  onPickColor={(color) => {
733
- activateFromPointer();
750
+ pressFromPointer();
734
751
  pickDrawColor(color);
735
752
  }}
736
753
  />
@@ -772,8 +789,9 @@ function ThemedCompositeChart({
772
789
  onZoomViewport={zoomViewport}
773
790
  onSetViewport={setViewportRange}
774
791
  onToolSpanChange={setToolSpan}
792
+ onDragActiveChange={setPointerDragging}
775
793
  keyboardToolDrag={keyboardToolDrag?.panelId === panel.id ? keyboardToolDrag : null}
776
- onPointerPress={cancelKeyboardPlacement}
794
+ onPointerPress={pressPlot}
777
795
  showTextFallback={showTextFallback}
778
796
  onPickTime={timePick?.onPick}
779
797
  levels={levelHost?.panel.id === panel.id ? levels ?? null : null}
@@ -1,4 +1,4 @@
1
- import { useEffect, useRef } from "react";
1
+ import { useCallback, useEffect, useRef } from "react";
2
2
  import { usePaneSettingValue } from "../../../state/app/context";
3
3
  import { CHART_DRAWINGS_SETTING_KEY, parseChartDrawings, type ChartDrawing } from "./tools";
4
4
 
@@ -40,6 +40,12 @@ export function ChartDrawingStore({
40
40
  if (restoredRef.current === null) {
41
41
  restoredRef.current = parseChartDrawings(stored);
42
42
  }
43
+ // What the settings hold, so removing the last drawing is told even when the
44
+ // pane opened with none: it is not "nothing to say" but "the stored one is gone".
45
+ const writtenRef = useRef<readonly ChartDrawing[]>(restoredRef.current);
46
+ const pendingRef = useRef<readonly ChartDrawing[] | null>(null);
47
+ const setStoredRef = useRef(setStored);
48
+ setStoredRef.current = setStored;
43
49
 
44
50
  useEffect(() => {
45
51
  const restored = restoredRef.current;
@@ -47,13 +53,27 @@ export function ChartDrawingStore({
47
53
  // Restoring once on mount: later writes must not scroll back in time.
48
54
  }, []);
49
55
 
56
+ const write = useCallback((next: readonly ChartDrawing[]) => {
57
+ pendingRef.current = null;
58
+ writtenRef.current = next;
59
+ setStoredRef.current(next);
60
+ }, []);
61
+
50
62
  useEffect(() => {
51
- const restored = restoredRef.current ?? NO_DRAWINGS;
52
- if (drawings === restored) return;
53
- if (drawings.length === 0 && restored.length === 0) return;
54
- const timer = setTimeout(() => setStored(drawings), DRAWING_PERSIST_DELAY_MS);
63
+ // Untouched, or still waiting for the restore above: nothing of the user's to write.
64
+ if (drawings === NO_DRAWINGS || drawings === writtenRef.current || (drawings.length === 0 && writtenRef.current.length === 0)) {
65
+ pendingRef.current = null;
66
+ return;
67
+ }
68
+ pendingRef.current = drawings;
69
+ const timer = setTimeout(() => write(drawings), DRAWING_PERSIST_DELAY_MS);
55
70
  return () => clearTimeout(timer);
56
- }, [drawings, setStored]);
71
+ }, [drawings, write]);
72
+
73
+ // A change made just before the chart goes away (a tab switch) is still saved.
74
+ useEffect(() => () => {
75
+ if (pendingRef.current) write(pendingRef.current);
76
+ }, [write]);
57
77
 
58
78
  return null;
59
79
  }
@@ -298,6 +298,14 @@ export function formatChartLegendValue(
298
298
  return trimmed && trimmed.length <= 6 ? `${compact}${trimmed.startsWith("/") ? "" : " "}${trimmed}` : compact;
299
299
  }
300
300
 
301
+ /** compactNumber's scale, with the decimals a tick gap needs at that scale. */
302
+ function preciseCompactNumber(value: number, gap: number): string {
303
+ const absolute = Math.abs(value);
304
+ const [divisor, suffix] = ([[1e12, "T"], [1e9, "B"], [1e6, "M"], [1e3, "K"]] as const).find(([scale]) => absolute >= scale) ?? [1, ""];
305
+ const digits = Math.min(12, Math.max(0, Math.ceil(-Math.log10(gap / divisor) - 1e-9)));
306
+ return `${(value / divisor).toFixed(digits)}${suffix}`;
307
+ }
308
+
301
309
  const COMPACT_LABEL = /^(-?\d+)(?:\.(\d+))?([KMBT]?)$/;
302
310
 
303
311
  function trimmedDecimals(label: string): number {
@@ -332,13 +340,17 @@ export function formatCompositeAxisValue(value: number, domain: CompositeAxisDom
332
340
  // Where that happens the axis spends the digits needed to tell them apart,
333
341
  // shared across every tick so the column reads as one scale.
334
342
  const gap = narrowestAxisTickGap(domain);
343
+ const prefix = currencyPrefix(domain.unit);
335
344
  if (gap !== null && gap < compactResolution(value)) {
336
345
  const resolved = formatAxisPriceValue(value, domain, {
337
346
  fixedFractionDigits: Math.max(0, Math.ceil(-Math.log10(gap))),
338
347
  });
339
348
  if (resolved) return resolved;
349
+ // No currency to format with, such as a price whose quote has not
350
+ // arrived: the same digits still keep the ticks apart.
351
+ const precise = preciseCompactNumber(value, gap);
352
+ return prefix && precise.startsWith("-") ? `-${prefix}${precise.slice(1)}` : `${prefix}${precise}`;
340
353
  }
341
- const prefix = currencyPrefix(domain.unit);
342
354
  return prefix && compact.startsWith("-") ? `-${prefix}${compact.slice(1)}` : `${prefix}${compact}`;
343
355
  }
344
356
 
@@ -362,7 +362,8 @@ export function resolveCompositeChartInteraction(event: {
362
362
  if (shifted && key === "d") return "arm-line";
363
363
  if (shifted && key === "p") return "arm-pencil";
364
364
  if (shifted && key === "h") return "arm-level";
365
- if (key === "backspace") return "delete-drawing";
365
+ // Delete is the same act on a keyboard that has the key, or on a Mac with fn.
366
+ if (key === "backspace" || key === "delete") return "delete-drawing";
366
367
  if (key === "c" && !event.shift) return "cycle-colour";
367
368
  if (event.shift) return null;
368
369
  if (key === "a") return "pan-left";
@@ -0,0 +1,30 @@
1
+ import type { CSSProperties } from "react";
2
+ import { blendForContrast, higherContrast } from "../../../theme/color-utils";
3
+ import { fitAxisLabels, type CompositeChartLevel, type ProjectedLevel } from "./levels";
4
+
5
+ /** Keep a precise price in the tag only when it has no readable axis marker. */
6
+ export function levelLabelText(level: CompositeChartLevel, axisVisible: boolean): string {
7
+ return `${level.label ?? ""}${!axisVisible && level.axisLabel ? ` ${level.axisLabel}` : ""}`;
8
+ }
9
+
10
+ /** Compact DOM tags straddle their line; crowded or clipped tags yield. */
11
+ export function layoutLevelLabels(levels: readonly ProjectedLevel[], plotHeight: number, textHeight: number) {
12
+ const candidates = levels.filter(({ level }) => level.label).map((entry) => ({
13
+ ...entry, top: entry.yRatio * plotHeight - textHeight / 2,
14
+ })).filter(({ top }) => top >= 0 && top + textHeight <= plotHeight);
15
+ return fitAxisLabels(candidates, [], ({ top }) => top / textHeight);
16
+ }
17
+
18
+ export function levelLabelStyle({ yRatio, level }: ProjectedLevel, background: string): CSSProperties {
19
+ return {
20
+ top: `${yRatio * 100}%`,
21
+ transform: "translateY(-50%)",
22
+ pointerEvents: "none",
23
+ paddingInline: 3,
24
+ height: "auto",
25
+ width: "max-content",
26
+ border: `1px solid ${level.color}`,
27
+ backgroundColor: background,
28
+ color: blendForContrast(level.color, background, higherContrast("#000000", "#ffffff", background), 4.5),
29
+ };
30
+ }
@@ -14,6 +14,11 @@ export interface CompositeChartLevel {
14
14
  editable: boolean;
15
15
  /** Whether the chart's level action applies to it. */
16
16
  actionable: boolean;
17
+ /** Optional read-only annotation, independent of drawing-tool selection. */
18
+ label?: string;
19
+ /** Exact instrument price, independent of the grid's compact tick formatter. */
20
+ axisLabel?: string;
21
+ dashed?: boolean;
17
22
  }
18
23
 
19
24
  export type CompositeLevelEdit =
@@ -111,13 +116,17 @@ export function fitAxisLabels<T>(
111
116
  });
112
117
  }
113
118
 
119
+ function levelSegments(dashed?: boolean): Array<[number, number]> {
120
+ return dashed ? Array.from({ length: 48 }, (_, i) => [i / 48, (i + 0.55) / 48]) : [[0, 1]];
121
+ }
122
+
114
123
  export function levelVectors(levels: readonly ProjectedLevel[], selectedId: string | null): ChartVectorShape[] {
115
- return levels.map(({ level, yRatio }) => ({
116
- id: `level:${level.id}`,
117
- points: [{ x: 0, y: yRatio }, { x: 1, y: yRatio }],
124
+ return levels.flatMap(({ level, yRatio }) => levelSegments(level.dashed).map(([start, end], index) => ({
125
+ id: `level:${level.id}:${index}`,
126
+ points: [{ x: start, y: yRatio }, { x: end, y: yRatio }],
118
127
  color: level.color,
119
128
  strokeWidth: level.id === selectedId ? 2.2 : 1.2,
120
- }));
129
+ })));
121
130
  }
122
131
 
123
132
  /** Levels painted on a copy of the panel raster, the one layer the terminal uploads. */
@@ -131,7 +140,9 @@ export function paintLevels(
131
140
  const maxY = Math.max(base.height - 1, 0);
132
141
  for (const { level, yRatio } of levels) {
133
142
  const y = Math.round(yRatio * maxY);
134
- drawLine(data, base.width, base.height, 0, y, maxX, y, parseHex(level.color), level.id === selectedId ? 2.2 : 1.2);
143
+ for (const [start, end] of levelSegments(level.dashed)) {
144
+ drawLine(data, base.width, base.height, start * maxX, y, end * maxX, y, parseHex(level.color), level.id === selectedId ? 2.2 : 1.2);
145
+ }
135
146
  }
136
147
  return { width: base.width, height: base.height, pixels: data };
137
148
  }
@@ -145,7 +156,7 @@ export function writeLevelText(lines: readonly string[], levels: readonly Projec
145
156
  const rows = lines.map((line) => Array.from(line));
146
157
  for (const { level, yRatio } of levels) {
147
158
  const row = rows[clamp(Math.round(yRatio * (rows.length - 1)), 0, rows.length - 1)]!;
148
- const glyph = level.id === selectedId ? "━" : level.editable ? "─" : "┄";
159
+ const glyph = level.id === selectedId ? "━" : level.dashed || !level.editable ? "┄" : "─";
149
160
  for (let x = 0; x < row.length; x += 1) {
150
161
  if (row[x] === " " || row[x] === "·" || row[x] === "╌") row[x] = glyph;
151
162
  }
@@ -12,7 +12,8 @@ import { colors as themeColors } from "../../../theme/colors";
12
12
  import { consumeChartMouseEvent, getGlobalMouseX, getLocalPlotPointer, type ChartMouseEvent } from "../core/pointer";
13
13
  import { useStaticChartBitmapSize } from "./bitmap";
14
14
  import { useMeasuredCells } from "../../../ui/measured-cells";
15
- import { PriceAxisLabels } from "./price-axis-labels";
15
+ import { PriceAxisLabels, placeAxisTicks } from "./price-axis-labels";
16
+ import { layoutLevelLabels, levelLabelStyle, levelLabelText } from "./level-labels";
16
17
  import { formatCompositeCursorValue, type CompositeAxisValueFormatter } from "./format";
17
18
  import {
18
19
  compositeViewportPositions,
@@ -111,6 +112,8 @@ interface CompositePanelSurfaceProps {
111
112
  onZoomViewport: (zoomFactor: number, anchorRatio: number) => void;
112
113
  onSetViewport: (range: CompositeViewportRange) => void;
113
114
  onToolSpanChange: (span: ChartToolSpan | null) => void;
115
+ /** A press on the plot started (true) or stopped (false) dragging: a pan, a tool, a drawing or a level. */
116
+ onDragActiveChange?: (dragging: boolean) => void;
114
117
  /** The tool being placed from the keyboard, when it lives in this panel. */
115
118
  keyboardToolDrag: KeyboardToolDrag | null;
116
119
  /** A press on the plot: the pointer takes over from a keyboard placement. */
@@ -154,6 +157,7 @@ export function CompositePanelSurface({
154
157
  onZoomViewport,
155
158
  onSetViewport,
156
159
  onToolSpanChange,
160
+ onDragActiveChange,
157
161
  keyboardToolDrag,
158
162
  onPointerPress,
159
163
  showTextFallback,
@@ -477,14 +481,16 @@ export function CompositePanelSurface({
477
481
  .sort((left, right) => Number(right.level.id === selectedLevelId) - Number(left.level.id === selectedLevelId)
478
482
  || Number(left.level.editable) - Number(right.level.editable))
479
483
  .map(({ level, yRatio }) => ({
484
+ id: level.id,
480
485
  side: levelDomain.side,
481
486
  yRatio,
482
- label: formatAxisValue ? formatAxisValue(level.value, levelDomain) : formatCompositeCursorValue(level.value, levelDomain),
487
+ label: level.axisLabel ?? (formatAxisValue ? formatAxisValue(level.value, levelDomain) : formatCompositeCursorValue(level.value, levelDomain)),
483
488
  color: level.color,
484
489
  })) : [], [formatAxisValue, levelDomain, projectedLevels, selectedLevelId]);
485
490
  const buildAxisMarkers = (side: "left" | "right") => {
486
491
  if (!panel.axes[side]) return undefined;
487
- const toAxisMarker = (marker: { yRatio: number; label: string; color: string }) => ({
492
+ const toAxisMarker = (marker: { id?: string; yRatio: number; label: string; color: string }) => ({
493
+ id: marker.id,
488
494
  row: Math.round(marker.yRatio * Math.max(panel.height - 1, 0)),
489
495
  pixelY: marker.yRatio * Math.max(panel.height * cellHeightPx - 1, 0),
490
496
  label: marker.label,
@@ -499,8 +505,14 @@ export function CompositePanelSurface({
499
505
  // A label is a row tall, so close levels would stack into an unreadable
500
506
  // pile and hide the last price: each keeps its line, and only the labels
501
507
  // with a row to themselves show. Desktop badges sit between rows.
508
+ const grid = fractionalViewport
509
+ ? placeAxisTicks({ ticks: side === "left" ? leftAxisTicks : rightAxisTicks, height: panel.height, cellHeightPx }).map((tick) => ({
510
+ ...toAxisMarker({ yRatio: tick.ratio, label: tick.label, color: colors.textDim }),
511
+ pixelY: tick.topPercent / 100 * Math.max(panel.height * cellHeightPx - 1, 1),
512
+ }))
513
+ : [...(side === "left" ? leftAxisLabels : rightAxisLabels)].map(([row, label]) => ({ id: undefined, row, pixelY: row * cellHeightPx, label, color: colors.textDim }));
502
514
  const markers = [
503
- ...fitAxisLabels(levelLabels, fixed, (marker) => fractionalViewport ? marker.pixelY / cellHeightPx : marker.row),
515
+ ...fitAxisLabels(levelLabels, [...fixed, ...grid], (marker) => fractionalViewport ? marker.pixelY / cellHeightPx : marker.row),
504
516
  ...fixed,
505
517
  ];
506
518
  return markers.length > 0 ? markers : undefined;
@@ -611,6 +623,17 @@ export function CompositePanelSurface({
611
623
  updateCursor(event);
612
624
  return;
613
625
  }
626
+ // With no tool in hand, a press on a drawing or a level picks it so Backspace
627
+ // or Delete can remove it; the press still pans, and one on bare plot lets go.
628
+ const ratios = pointerRatios(event);
629
+ if (ratios) {
630
+ const drawingHit = drawings.length > 0 ? hitTestDrawings(drawings, scene, panel, ratios, plotAspect) : null;
631
+ const levelHit = !drawingHit && levels?.onEdit && levelDomain
632
+ ? hitTestLevel(projectedLevels, ratios.yRatio, levelGrabRatio(panel.height))
633
+ : null;
634
+ onSelectDrawing(drawingHit ? drawingHit.drawing.id : null);
635
+ onSelectLevel(levelHit?.editable ? levelHit.id : null);
636
+ }
614
637
  if (!updateCursor(event)) return;
615
638
  dragRef.current = startPanGesture(
616
639
  frame,
@@ -736,12 +759,29 @@ export function CompositePanelSurface({
736
759
  }
737
760
  finishToolDrag();
738
761
  }, [finishToolDrag]);
762
+ // Said once per change, so a pointer move costs the chart nothing.
763
+ const reportedDragRef = useRef(false);
764
+ const reportDrag = useCallback((dragging: boolean) => {
765
+ if (reportedDragRef.current === dragging) return;
766
+ reportedDragRef.current = dragging;
767
+ onDragActiveChange?.(dragging);
768
+ }, [onDragActiveChange]);
769
+ const pressPlot = useCallback((event: ChartMouseEvent) => {
770
+ const before = dragRef.current;
771
+ startDrag(event);
772
+ if (dragRef.current !== null && dragRef.current !== before) reportDrag(true);
773
+ }, [reportDrag, startDrag]);
774
+ const releasePlot = useCallback(() => {
775
+ resetDrag();
776
+ reportDrag(false);
777
+ }, [reportDrag, resetDrag]);
739
778
  const handleMouseMove = useCallback((event: ChartMouseEvent) => {
740
779
  // A release over a neighbouring pane never reaches this surface, so treat a
741
780
  // plain move, which only fires with no button held, as the missing release.
742
781
  finishToolDrag();
782
+ reportDrag(false);
743
783
  updateCursor(event);
744
- }, [finishToolDrag, updateCursor]);
784
+ }, [finishToolDrag, reportDrag, updateCursor]);
745
785
  // Trackpads fire wheel events faster than the plot can paint, so one frame
746
786
  // absorbs every event that arrived since the last one.
747
787
  const pendingWheelRef = useRef<PendingWheel | null>(null);
@@ -876,10 +916,10 @@ export function CompositePanelSurface({
876
916
  crosshair={crosshair}
877
917
  vectors={vectors}
878
918
  onMouseMove={interactive ? handleMouseMove : undefined}
879
- onMouseDown={interactive ? navigable ? startDrag : pressCursor : undefined}
919
+ onMouseDown={interactive ? navigable ? pressPlot : pressCursor : undefined}
880
920
  onMouseDrag={interactive && navigable ? dragViewport : undefined}
881
- onMouseUp={interactive && navigable ? resetDrag : undefined}
882
- onMouseDragEnd={interactive && navigable ? resetDrag : undefined}
921
+ onMouseUp={interactive && navigable ? releasePlot : undefined}
922
+ onMouseDragEnd={interactive && navigable ? releasePlot : undefined}
883
923
  onMouseScroll={interactive && navigable ? panFromWheel : undefined}
884
924
  onMouseOut={interactive ? clearCursor : undefined}
885
925
  cursor={interactive ? toolDrag || !navigable || onPickTime ? "crosshair" : "grab" : undefined}
@@ -890,6 +930,27 @@ export function CompositePanelSurface({
890
930
  >
891
931
  {textLines.map((line, index) => <Text key={index} fg={colors.text}>{line}</Text>)}
892
932
  </ChartSurface>
933
+ {(isDesktopWeb
934
+ ? layoutLevelLabels(projectedLevels, plotSize.height * cellHeightPx, cellHeightPx)
935
+ : fitAxisLabels(projectedLevels.filter((entry) => entry.level.label), [], (entry) => entry.yRatio * Math.max(1, panel.height - 1))
936
+ ).map((entry) => {
937
+ const { level, yRatio } = entry;
938
+ const axisWidth = levelDomain?.side === "left" ? leftAxisWidth : rightAxisWidth;
939
+ const axisMarkers = levelDomain?.side === "left" ? leftAxisMarkers : rightAxisMarkers;
940
+ const cursorCovers = activeCursorYRatio !== null && Math.abs(activeCursorYRatio - yRatio) * panel.height < 1;
941
+ const axisVisible = !cursorCovers && (level.axisLabel?.length ?? 0) <= axisWidth && !!axisMarkers?.some((marker) => marker.id === level.id);
942
+ const label = levelLabelText(level, axisVisible);
943
+ const labelStyle = isDesktopWeb ? {
944
+ ...levelLabelStyle(entry, colors.background), left: "auto", right: (rightAxisWidth + (rightAxisWidth ? axisGap : 0) + 1) * cellWidthPx,
945
+ maxWidth: Math.max(1, plotWidth - 2) * cellWidthPx,
946
+ } : undefined;
947
+ const labelWidth = Math.min(plotWidth - 2, label.length + 2);
948
+ return <Box key={`annotation:${level.id}`} position="absolute" left={leftAxisWidth + (leftAxisWidth ? axisGap : 0) + plotWidth - labelWidth - 1} top={yRatio * Math.max(0, panel.height - 1)} height={1}
949
+ width={labelWidth} flexDirection="row" justifyContent="flex-end" backgroundColor={colors.background} zIndex={20}
950
+ style={labelStyle} data-gloom-role="chart-level-label">
951
+ <Text fg={labelStyle?.color ?? level.color} wrapMode="none" style={isDesktopWeb ? { lineHeight: 1 } : undefined}>{label}</Text>
952
+ </Box>;
953
+ })}
893
954
  {rightAxisWidth > 0 ? (
894
955
  <>
895
956
  <Box width={axisGap} />
@@ -315,6 +315,12 @@ export interface CompositeChartProps {
315
315
  interaction: "pan" | "reset" | "zoom",
316
316
  ) => void;
317
317
  onActivate?: () => void;
318
+ /**
319
+ * A press on the chart itself (plot, legend or tool bar), not a wheel turn.
320
+ * The chart consumes the press, so an owner that shares the pane with a
321
+ * field that takes typed keys hears it here to hand the keyboard back.
322
+ */
323
+ onPress?: () => void;
318
324
  onToggleSeries?: (seriesId: string) => void;
319
325
  isSeriesToggleable?: (series: ResolvedSeries) => boolean;
320
326
  /**
@@ -424,7 +424,7 @@ export function useCompositeChartKeyboard({
424
424
  stepLevel(event.name === "[" ? -1 : 1);
425
425
  return;
426
426
  }
427
- if (toolsActive && selectedLevel?.editable && levelsEditable && isPlainKey(event, "backspace")) {
427
+ if (toolsActive && selectedLevel?.editable && levelsEditable && isPlainKey(event, "backspace", "delete")) {
428
428
  consume();
429
429
  removeSelectedLevel();
430
430
  return;
@@ -3,11 +3,26 @@ import type { TickerRecord } from "../../../../types/ticker";
3
3
  import { isManualPortfolio } from "../../../../portfolio/mutations";
4
4
  import type { Command } from "../../commands/registry";
5
5
  import type { ResultItem } from "../../list/model";
6
- import { canRestartToApply } from "../../../../updater";
6
+ import { canRestartToApply, type UpdateProgress } from "../../../../updater";
7
7
  import { NIGHTLY_CHANNEL_SUMMARY, resolveUpdateChannel } from "../../../../updater/channel";
8
+ import { updateFailureReason } from "../../../../updater/failure";
9
+ import { t, tf } from "../../../../i18n";
8
10
  import { isPrereleaseVersion } from "../../../../utils/semver";
9
11
  import { VERSION } from "../../../../version";
10
12
 
13
+ /** "Update failed: download interrupted", as the header says it. */
14
+ function failedUpdateLabel(progress: UpdateProgress): string {
15
+ const reason = progress.failure ? t(updateFailureReason(progress.failure)) : (progress.error ?? "").replace(/\s+/g, " ").trim();
16
+ return tf("Update failed: {error}", { error: reason });
17
+ }
18
+
19
+ /** The whole error under the failed update's row, unless the label already says all of it. */
20
+ function failedUpdateLines(progress: UpdateProgress): ResultItem["lines"] {
21
+ const error = (progress.error ?? "").replace(/\s+/g, " ").trim();
22
+ if (!progress.failure || !error) return undefined;
23
+ return [{ segments: [{ text: error, emphasis: "muted" }] }];
24
+ }
25
+
11
26
  interface RootCommandItemBuilderOptions {
12
27
  activeCollectionId: string | null;
13
28
  activeTickerData: TickerRecord | null | undefined;
@@ -88,9 +103,12 @@ export function createRootCommandItemBuilder({
88
103
  return activeTickerSymbol ? `Remove ${activeTickerSymbol} from Portfolio` : command.label;
89
104
  case "set-portfolio-position":
90
105
  return activeTickerSymbol ? `Set Position for ${activeTickerSymbol}` : command.label;
91
- // The row only shows its label, so a downloaded update is announced there.
106
+ // The row only shows its label, so a downloaded update is announced there,
107
+ // and a failed one with its reason; Enter restarts into the one and retries the other.
92
108
  case "check-for-updates":
93
- return state.updateProgress?.phase === "ready" ? "Update ready, restart to apply" : command.label;
109
+ if (state.updateProgress?.phase === "ready") return "Update ready, restart to apply";
110
+ if (state.updateProgress?.phase === "error") return failedUpdateLabel(state.updateProgress);
111
+ return command.label;
94
112
  default:
95
113
  return command.label;
96
114
  }
@@ -140,7 +158,7 @@ export function createRootCommandItemBuilder({
140
158
  return state.updateProgress.message ?? "Update ready, restart to apply";
141
159
  }
142
160
  if (state.updateProgress?.phase === "error") {
143
- return `Update failed: ${state.updateProgress.error}`;
161
+ return `${failedUpdateLabel(state.updateProgress)}. ${state.updateProgress.error ?? ""}`.trim();
144
162
  }
145
163
  if (state.updateCheckInProgress) return "Checking releases now";
146
164
  if (state.updateAvailable) {
@@ -198,8 +216,11 @@ export function createRootCommandItemBuilder({
198
216
  // "update" is a whole word here so it lands on this command before Set
199
217
  // Portfolio Position (whose keywords say "update position") and Release
200
218
  // Channel, which keeps the word further back.
219
+ // Ready and failed rename the row; its own name still finds it.
201
220
  case "check-for-updates":
202
- return state.updateProgress?.phase === "ready" ? "update check for updates restart relaunch" : "update";
221
+ if (state.updateProgress?.phase === "ready") return "update check for updates restart relaunch";
222
+ if (state.updateProgress?.phase === "error") return "update check for updates retry";
223
+ return "update";
203
224
  case "release-channel":
204
225
  return "nightly stable beta prerelease update updates channel";
205
226
  default:
@@ -218,9 +239,14 @@ export function createRootCommandItemBuilder({
218
239
  right: smartRight(command, arg),
219
240
  shortcutQuery: command.prefix || undefined,
220
241
  searchText: smartSearchText(command),
242
+ ...(command.id === "check-for-updates" && state.updateProgress?.phase === "error"
243
+ ? { lines: failedUpdateLines(state.updateProgress) }
244
+ : {}),
245
+ // Busy while it checks, downloads or installs; a failed update is retried from here.
221
246
  disabled:
222
247
  command.id === "check-for-updates" &&
223
- (state.updateCheckInProgress || (!!state.updateProgress && !canRestartToApply(state.updateProgress))),
248
+ (state.updateCheckInProgress
249
+ || (!!state.updateProgress && !canRestartToApply(state.updateProgress) && state.updateProgress.phase !== "error")),
224
250
  action: () => runDirectCommand(command, arg),
225
251
  };
226
252
  };
@@ -17,6 +17,7 @@ import {
17
17
  type TickerSearchInstrumentClass,
18
18
  } from "../../../../tickers/search";
19
19
  import type { ResultItem } from "../../list/model";
20
+ import { delistingLabel, parseListingDelisting } from "../../../../market-data/quotes/delisting";
20
21
  import {
21
22
  buildTickerSearchCacheKey,
22
23
  createQuickLookTickerCandidates,
@@ -74,6 +75,9 @@ export function useCommandBarTickerSearchActions({
74
75
  // The candidate's detail leads with the name; the rest repeats the badge or venue.
75
76
  const leadingName = candidate.detail.split(" | ")[0]?.trim();
76
77
  const name = leadingName && leadingName.toUpperCase() !== candidate.label.toUpperCase() ? leadingName : undefined;
78
+ // A listing that no longer trades says so under its row, with its last session.
79
+ const delisting = parseListingDelisting(candidate.result?.delisting);
80
+ const lines = delisting ? [{ segments: [{ text: delistingLabel(delisting), emphasis: "muted" as const }] }] : undefined;
77
81
 
78
82
  if (candidate.kind === "ticker" && candidate.ticker) {
79
83
  return {
@@ -86,6 +90,7 @@ export function useCommandBarTickerSearchActions({
86
90
  badge,
87
91
  name,
88
92
  right,
93
+ ...(lines ? { lines } : {}),
89
94
  category: candidate.category,
90
95
  kind: "ticker",
91
96
  resolveTicker: async () => candidate.ticker!,
@@ -112,6 +117,7 @@ export function useCommandBarTickerSearchActions({
112
117
  badge,
113
118
  name,
114
119
  right,
120
+ ...(lines ? { lines } : {}),
115
121
  category: candidate.category,
116
122
  kind: "search",
117
123
  resolveTicker: () => resolveSearchTicker(candidate.result!, publicTickerKey(
@@ -11,7 +11,7 @@ import {
11
11
  import type { ScrollBoxRenderable } from "../../ui";
12
12
  import { useBeforeShortcut, useShortcut } from "../../react/input";
13
13
  import { isPlainKey, isPlainKeyboardEvent } from "../../utils/keyboard";
14
- import { DataTable, type DataTableColumn, type DataTableProps } from "../ui";
14
+ import { DataTable, PaneStatusBody, type DataTableColumn, type DataTableProps, type PaneStatusBodyProps } from "../ui";
15
15
  import { useDataTableSortMenu } from "./sort-menu";
16
16
  import {
17
17
  TableViewFrame,
@@ -89,6 +89,19 @@ export type DataTableSelection<T> =
89
89
  ) => void;
90
90
  };
91
91
 
92
+ /**
93
+ * What the table's body shows instead of rows while it has none to show: its
94
+ * first page loading after a tab or filter change, a failed load, a state
95
+ * with nothing to list. The same states `PaneStatusBody` draws, in the
96
+ * table's place under `rootBefore`, so the query bar and figures above stay
97
+ * on screen (and a search field keeps its focus) while the body waits.
98
+ */
99
+ export type DataTableBodyStatus = Omit<PaneStatusBodyProps, "children" | "width" | "height" | "align">;
100
+
101
+ function bodyStatusShown(status: DataTableBodyStatus | undefined): status is DataTableBodyStatus {
102
+ return !!status && (!!status.error || status.loading === true || status.empty === true);
103
+ }
104
+
92
105
  interface SelectionCommitTarget {
93
106
  index: number;
94
107
  id?: string;
@@ -124,6 +137,12 @@ export interface DataTableViewProps<
124
137
  isNavigable?: (item: T, index: number) => boolean;
125
138
  rootBefore?: ReactNode;
126
139
  rootAfter?: ReactNode;
140
+ /**
141
+ * Replaces the table (header and rows) with a loading, error or empty
142
+ * state while `rootBefore` stays. Precedence is the caller's, as with
143
+ * `PaneStatusBody`: pass `loading` only while there is nothing to show.
144
+ */
145
+ status?: DataTableBodyStatus;
127
146
  rootWidth?: number;
128
147
  rootHeight?: number;
129
148
  rootBackgroundColor?: string;
@@ -165,6 +184,7 @@ export function DataTableView<
165
184
  isNavigable,
166
185
  rootBefore,
167
186
  rootAfter,
187
+ status,
168
188
  rootWidth,
169
189
  rootHeight,
170
190
  rootBackgroundColor,
@@ -728,7 +748,7 @@ export function DataTableView<
728
748
  before={rootBefore}
729
749
  after={rootAfter}
730
750
  >
731
- <DataTable<T, C>
751
+ {bodyStatusShown(status) ? <PaneStatusBody {...status} /> : <DataTable<T, C>
732
752
  {...tableProps}
733
753
  headerScrollRef={effectiveHeaderScrollRef}
734
754
  scrollRef={effectiveScrollRef}
@@ -741,7 +761,7 @@ export function DataTableView<
741
761
  onActivate={handleTableActivate}
742
762
  onRowMouseDown={handleRowMouseDown}
743
763
  onRowContextMenu={handleRowContextMenu}
744
- />
764
+ />}
745
765
  </TableViewFrame>
746
766
  );
747
767
  }