gloomberb 0.19.2 → 0.19.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (472) hide show
  1. package/README.md +7 -1
  2. package/package.json +1 -1
  3. package/patches/electrobun@1.18.1.patch +108 -1
  4. package/src/account/upgrade-dialog.tsx +4 -23
  5. package/src/api-client/coalesce.ts +118 -0
  6. package/src/api-client/credit-boards.ts +13 -0
  7. package/src/api-client/crypto-etf-flows.ts +6 -0
  8. package/src/api-client/crypto-markets.ts +40 -0
  9. package/src/api-client/data.ts +201 -14
  10. package/src/api-client/equity-screener.ts +10 -0
  11. package/src/api-client/errors.ts +20 -0
  12. package/src/api-client/gpu.ts +29 -0
  13. package/src/api-client/index.ts +5 -2
  14. package/src/api-client/plan-access.ts +10 -1
  15. package/src/api-client/plan-rules.ts +35 -0
  16. package/src/api-client/research-activity.ts +1 -32
  17. package/src/api-client/types.ts +21 -1
  18. package/src/app/attention-notice.tsx +94 -0
  19. package/src/app.tsx +23 -1
  20. package/src/architecture/core-builtin-allowlist.json +2 -1
  21. package/src/capabilities/factories.ts +1 -0
  22. package/src/cli/ascii-output.ts +42 -0
  23. package/src/cli/base-converter.ts +10 -2
  24. package/src/cli/command-options.ts +16 -1
  25. package/src/cli/commands/crypto-hints.ts +23 -4
  26. package/src/cli/commands/market.ts +92 -23
  27. package/src/cli/commands/news-search.ts +24 -5
  28. package/src/cli/commands/overview.ts +9 -4
  29. package/src/cli/commands/search.ts +71 -12
  30. package/src/cli/commands/system.ts +22 -30
  31. package/src/cli/commands/ticker.ts +49 -9
  32. package/src/cli/commands/version.ts +33 -0
  33. package/src/cli/context.ts +5 -9
  34. package/src/cli/data-dir.ts +7 -0
  35. package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
  36. package/src/cli/desktop-pane-shot-browser.ts +320 -0
  37. package/src/cli/desktop-pane-shot-routes.ts +26 -1
  38. package/src/cli/desktop-pane-shot.ts +196 -256
  39. package/src/cli/early-command.ts +42 -0
  40. package/src/cli/earnings-calendar.ts +9 -0
  41. package/src/cli/entry.ts +19 -13
  42. package/src/cli/errors.ts +12 -2
  43. package/src/cli/help.ts +1 -1
  44. package/src/cli/history-export.ts +22 -2
  45. package/src/cli/history-rows.ts +154 -4
  46. package/src/cli/index.ts +18 -1
  47. package/src/cli/known-gaps.ts +40 -5
  48. package/src/cli/listing-arg.ts +46 -9
  49. package/src/cli/options.ts +1 -1
  50. package/src/cli/output-file.ts +60 -13
  51. package/src/cli/pane-functions/catalog.ts +14 -3
  52. package/src/cli/pane-functions/data.ts +1 -1
  53. package/src/cli/pane-functions/dom.ts +45 -0
  54. package/src/cli/pane-functions/freshness.ts +46 -9
  55. package/src/cli/pane-functions/function-help.ts +17 -9
  56. package/src/cli/pane-functions/headless.ts +22 -14
  57. package/src/cli/pane-functions/index.ts +6 -2
  58. package/src/cli/pane-functions/options.ts +6 -2
  59. package/src/cli/pane-functions/resolver.ts +27 -5
  60. package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
  61. package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
  62. package/src/cli/pane-functions/screenshot-http.ts +70 -0
  63. package/src/cli/pane-functions/screenshot-render.ts +19 -5
  64. package/src/cli/quote-answer.ts +20 -0
  65. package/src/cli/registry.ts +41 -13
  66. package/src/cli/result.ts +12 -6
  67. package/src/cli/ticker-resolution.ts +2 -1
  68. package/src/cli/types.ts +2 -0
  69. package/src/components/chart/composite/format.ts +3 -0
  70. package/src/components/chart-table/header.tsx +1 -1
  71. package/src/components/chart-table/layout.ts +28 -11
  72. package/src/components/command-bar/action-runtime.ts +4 -0
  73. package/src/components/command-bar/commands/direct/index.ts +18 -33
  74. package/src/components/command-bar/commands/direct/runtime.ts +5 -0
  75. package/src/components/command-bar/commands/registry.ts +10 -1
  76. package/src/components/command-bar/routes/root/command-items.ts +9 -4
  77. package/src/components/command-bar/routes/root/runtime.ts +24 -1
  78. package/src/components/command-bar/surface/index.tsx +9 -3
  79. package/src/components/company-logo.tsx +2 -1
  80. package/src/components/layout/header.tsx +8 -3
  81. package/src/components/layout/market-summary.ts +25 -7
  82. package/src/components/layout/pane/footer/index.tsx +7 -2
  83. package/src/components/layout/pane/footer/model.ts +40 -5
  84. package/src/components/market-board/index.tsx +3 -2
  85. package/src/components/onboarding/desks-step.tsx +12 -4
  86. package/src/components/onboarding/first-run-workspace.ts +81 -4
  87. package/src/components/onboarding/onboarding-frame.tsx +2 -1
  88. package/src/components/onboarding/onboarding-keyboard.ts +8 -5
  89. package/src/components/onboarding/onboarding-wizard.tsx +54 -23
  90. package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
  91. package/src/components/onboarding/run-setup-again.ts +24 -0
  92. package/src/components/onboarding/use-onboarding-progress.ts +33 -15
  93. package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
  94. package/src/components/onboarding/wizard-model.ts +57 -3
  95. package/src/components/onboarding/wizard-positions.ts +1 -1
  96. package/src/components/ui/delayed-mark.tsx +56 -0
  97. package/src/components/ui/external-link.tsx +3 -6
  98. package/src/components/ui/icon.tsx +8 -1
  99. package/src/components/ui/logo-mark.tsx +8 -5
  100. package/src/components/ui/stat-grid.tsx +263 -19
  101. package/src/data/config/store/node.ts +11 -5
  102. package/src/data/config/store/normalize.ts +16 -3
  103. package/src/data/profile-permissions.ts +47 -0
  104. package/src/data/resource-store.ts +34 -31
  105. package/src/data/sqlite/access.ts +19 -8
  106. package/src/data/sqlite/checkpointer.ts +95 -8
  107. package/src/data/sqlite/database.ts +40 -7
  108. package/src/docs/snapshot.json +191 -78
  109. package/src/i18n/es.ts +18 -2
  110. package/src/i18n/ja.ts +18 -2
  111. package/src/i18n/ko.ts +18 -2
  112. package/src/i18n/zh-cn.ts +18 -2
  113. package/src/i18n/zh-tw.ts +18 -2
  114. package/src/layout/desk-keys.ts +4 -1
  115. package/src/layout/desks.ts +38 -5
  116. package/src/layout-marketplace/gallery-desktop.tsx +45 -16
  117. package/src/market-data/batched-reads.ts +16 -0
  118. package/src/market-data/coordinator/auxiliary.ts +4 -2
  119. package/src/market-data/coordinator/index.ts +11 -2
  120. package/src/market-data/futures-board.ts +1 -1
  121. package/src/market-data/futures-contract.ts +3 -1
  122. package/src/market-data/market/index-level.ts +6 -4
  123. package/src/market-data/published-apac-sessions.ts +6 -0
  124. package/src/news/aggregator.ts +7 -4
  125. package/src/news/news-model.ts +11 -0
  126. package/src/news/story-id.ts +22 -0
  127. package/src/news/ticker-symbols.ts +17 -1
  128. package/src/notifications/app-notifier.ts +35 -14
  129. package/src/plugins/builtin/account-management/form-components.tsx +1 -1
  130. package/src/plugins/builtin/account-management/index.tsx +2 -3
  131. package/src/plugins/builtin/account-management/model.ts +1 -1
  132. package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
  133. package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
  134. package/src/plugins/builtin/alerts/events.ts +10 -2
  135. package/src/plugins/builtin/alerts/index.tsx +5 -5
  136. package/src/plugins/builtin/alerts/research-builder.ts +5 -1
  137. package/src/plugins/builtin/alerts/research-rules.ts +18 -0
  138. package/src/plugins/builtin/alerts/targets.ts +4 -1
  139. package/src/plugins/builtin/attention/headless.ts +1 -1
  140. package/src/plugins/builtin/attention/index.tsx +2 -4
  141. package/src/plugins/builtin/attention/listing.ts +1 -1
  142. package/src/plugins/builtin/attention/series.ts +1 -1
  143. package/src/plugins/builtin/awards/index.tsx +1 -2
  144. package/src/plugins/builtin/backtest/client.ts +1 -0
  145. package/src/plugins/builtin/backtest/index.tsx +1 -2
  146. package/src/plugins/builtin/backtest/rules.ts +3 -1
  147. package/src/plugins/builtin/backtest/window.ts +8 -7
  148. package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
  149. package/src/plugins/builtin/broker-manager/index.tsx +1 -1
  150. package/src/plugins/builtin/buildout/cells.tsx +5 -0
  151. package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
  152. package/src/plugins/builtin/buildout/company/id.ts +2 -0
  153. package/src/plugins/builtin/buildout/company/model.ts +247 -0
  154. package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
  155. package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
  156. package/src/plugins/builtin/buildout/facilities.ts +309 -0
  157. package/src/plugins/builtin/buildout/format.ts +9 -0
  158. package/src/plugins/builtin/buildout/index.tsx +51 -5
  159. package/src/plugins/builtin/buildout/model/index.ts +62 -10
  160. package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
  161. package/src/plugins/builtin/buildout/model/types.ts +11 -2
  162. package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
  163. package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
  164. package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
  165. package/src/plugins/builtin/buildout/table-model.ts +26 -2
  166. package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
  167. package/src/plugins/builtin/cds/headless.ts +10 -2
  168. package/src/plugins/builtin/cds/model.ts +7 -3
  169. package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
  170. package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
  171. package/src/plugins/builtin/changelog/index.tsx +3 -2
  172. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -2
  173. package/src/plugins/builtin/chart-composer/headless.ts +88 -21
  174. package/src/plugins/builtin/chart-composer/index.tsx +2 -3
  175. package/src/plugins/builtin/chart-composer/pane.tsx +26 -2
  176. package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
  177. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  178. package/src/plugins/builtin/chat/controller/index.ts +1 -1
  179. package/src/plugins/builtin/chat/controller/messages.ts +15 -4
  180. package/src/plugins/builtin/chat/ghost-user.ts +8 -0
  181. package/src/plugins/builtin/chat/index.tsx +5 -5
  182. package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
  183. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  184. package/src/plugins/builtin/cloud/index.tsx +1 -2
  185. package/src/plugins/builtin/cloud/plugin.tsx +6 -4
  186. package/src/plugins/builtin/cloud/team/module.tsx +1 -1
  187. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
  188. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
  189. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
  190. package/src/plugins/builtin/comment-letters/index.tsx +1 -2
  191. package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
  192. package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
  193. package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
  194. package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
  195. package/src/plugins/builtin/congress-trades/headless.ts +2 -0
  196. package/src/plugins/builtin/congress-trades/model.ts +92 -64
  197. package/src/plugins/builtin/congress-trades/table.tsx +14 -2
  198. package/src/plugins/builtin/connections/index.ts +2 -3
  199. package/src/plugins/builtin/cot/index.tsx +1 -2
  200. package/src/plugins/builtin/cpi/index.tsx +1 -2
  201. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  202. package/src/plugins/builtin/credit-boards/client.ts +7 -2
  203. package/src/plugins/builtin/credit-boards/headless.ts +25 -12
  204. package/src/plugins/builtin/credit-boards/model.ts +29 -0
  205. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
  206. package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
  207. package/src/plugins/builtin/crypto-board/headless.ts +4 -3
  208. package/src/plugins/builtin/crypto-board/index.tsx +4 -2
  209. package/src/plugins/builtin/crypto-board/series.ts +2 -0
  210. package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
  211. package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
  212. package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
  213. package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
  214. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
  215. package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
  216. package/src/plugins/builtin/debt-maturities/model.ts +17 -1
  217. package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
  218. package/src/plugins/builtin/debug/index.tsx +1 -1
  219. package/src/plugins/builtin/dividend-yield/client.ts +32 -2
  220. package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
  221. package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
  222. package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
  223. package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
  224. package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
  225. package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
  226. package/src/plugins/builtin/dividend-yield/view.ts +21 -0
  227. package/src/plugins/builtin/docs/index.tsx +2 -3
  228. package/src/plugins/builtin/doe/headless.ts +16 -3
  229. package/src/plugins/builtin/doe/index.tsx +5 -4
  230. package/src/plugins/builtin/earnings/board.tsx +2 -1
  231. package/src/plugins/builtin/earnings/history.tsx +2 -1
  232. package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
  233. package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
  234. package/src/plugins/builtin/econ/calendar-model.ts +29 -6
  235. package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
  236. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  237. package/src/plugins/builtin/equity-screener/headless.ts +1 -0
  238. package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
  239. package/src/plugins/builtin/equity-screener/index.tsx +1 -2
  240. package/src/plugins/builtin/equity-screener/model.ts +28 -4
  241. package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
  242. package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
  243. package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
  244. package/src/plugins/builtin/exposure/holdings.ts +35 -13
  245. package/src/plugins/builtin/exposure/index.tsx +1 -2
  246. package/src/plugins/builtin/fear-greed/cli.ts +44 -0
  247. package/src/plugins/builtin/fear-greed/index.tsx +5 -53
  248. package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
  249. package/src/plugins/builtin/fixed-income/index.tsx +1 -2
  250. package/src/plugins/builtin/futures/board-reads.ts +25 -0
  251. package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
  252. package/src/plugins/builtin/futures/index.tsx +15 -0
  253. package/src/plugins/builtin/futures/model.ts +14 -0
  254. package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
  255. package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
  256. package/src/plugins/builtin/futures-curve/headless.ts +6 -1
  257. package/src/plugins/builtin/futures-curve/index.tsx +1 -2
  258. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  259. package/src/plugins/builtin/gpu/board.tsx +51 -12
  260. package/src/plugins/builtin/gpu/client.ts +15 -2
  261. package/src/plugins/builtin/gpu/equities.tsx +5 -2
  262. package/src/plugins/builtin/gpu/headless.ts +14 -2
  263. package/src/plugins/builtin/gpu/index.tsx +1 -2
  264. package/src/plugins/builtin/gpu/model.ts +20 -2
  265. package/src/plugins/builtin/gpu/pane.tsx +1 -1
  266. package/src/plugins/builtin/help/index.tsx +1 -1
  267. package/src/plugins/builtin/holders/index-holders.ts +24 -12
  268. package/src/plugins/builtin/holders/index.tsx +2 -3
  269. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
  270. package/src/plugins/builtin/iv-history/index.tsx +1 -2
  271. package/src/plugins/builtin/jodi/index.tsx +1 -2
  272. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
  273. package/src/plugins/builtin/layout-manager/index.tsx +9 -8
  274. package/src/plugins/builtin/macro-day/pane.tsx +4 -2
  275. package/src/plugins/builtin/market-halts/index.tsx +1 -2
  276. package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
  277. package/src/plugins/builtin/market-movers/index.tsx +7 -1
  278. package/src/plugins/builtin/market-movers/session-body.tsx +4 -1
  279. package/src/plugins/builtin/market-valuation/index.tsx +1 -2
  280. package/src/plugins/builtin/members/index.tsx +1 -2
  281. package/src/plugins/builtin/money-markets/index.tsx +1 -2
  282. package/src/plugins/builtin/money-markets/pane.tsx +2 -1
  283. package/src/plugins/builtin/news/headless.ts +7 -0
  284. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  285. package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
  286. package/src/plugins/builtin/notes/index.tsx +3 -6
  287. package/src/plugins/builtin/openfda/index.tsx +1 -2
  288. package/src/plugins/builtin/options/view.tsx +6 -5
  289. package/src/plugins/builtin/options-calculator/index.tsx +1 -2
  290. package/src/plugins/builtin/options-positioning/index.tsx +1 -2
  291. package/src/plugins/builtin/options-scenario/index.tsx +2 -4
  292. package/src/plugins/builtin/perps/index.tsx +1 -2
  293. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
  294. package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
  295. package/src/plugins/builtin/plugin-module.ts +16 -2
  296. package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
  297. package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
  298. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
  299. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
  300. package/src/plugins/builtin/power/index.tsx +1 -2
  301. package/src/plugins/builtin/rate-path/index.tsx +1 -2
  302. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  303. package/src/plugins/builtin/realized-vol/index.tsx +1 -2
  304. package/src/plugins/builtin/realized-vol/model.ts +16 -2
  305. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  306. package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
  307. package/src/plugins/builtin/research/analyst-headless.ts +33 -6
  308. package/src/plugins/builtin/research/analyst-model.ts +37 -2
  309. package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
  310. package/src/plugins/builtin/research/index.tsx +5 -8
  311. package/src/plugins/builtin/research-search/index.tsx +1 -2
  312. package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
  313. package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
  314. package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
  315. package/src/plugins/builtin/roadmap/index.tsx +2 -3
  316. package/src/plugins/builtin/roadmap/pane.tsx +49 -2
  317. package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
  318. package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
  319. package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
  320. package/src/plugins/builtin/sec/filing-content.ts +38 -6
  321. package/src/plugins/builtin/sec/headless.ts +5 -1
  322. package/src/plugins/builtin/sec/index.tsx +4 -2
  323. package/src/plugins/builtin/sec/model.ts +2 -0
  324. package/src/plugins/builtin/sectors/index.tsx +11 -5
  325. package/src/plugins/builtin/series-inventory.ts +2 -0
  326. package/src/plugins/builtin/shared/cloud-resource.ts +60 -0
  327. package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
  328. package/src/plugins/builtin/shared/forming-bars.ts +29 -0
  329. package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
  330. package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
  331. package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
  332. package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
  333. package/src/plugins/builtin/short-volume/pane.tsx +2 -1
  334. package/src/plugins/builtin/social-mentions/index.tsx +1 -2
  335. package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
  336. package/src/plugins/builtin/supply-chain/client.ts +22 -1
  337. package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
  338. package/src/plugins/builtin/supply-chain/index.tsx +1 -2
  339. package/src/plugins/builtin/thirteenf/api.ts +116 -13
  340. package/src/plugins/builtin/thirteenf/data.ts +132 -30
  341. package/src/plugins/builtin/thirteenf/index.tsx +2 -4
  342. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  343. package/src/plugins/builtin/thirteenf/pane.tsx +20 -11
  344. package/src/plugins/builtin/thirteenf/types.ts +12 -0
  345. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +17 -4
  346. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
  347. package/src/plugins/builtin/ticker-detail/headless.ts +11 -3
  348. package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
  349. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
  350. package/src/plugins/builtin/time-sales/index.tsx +1 -2
  351. package/src/plugins/builtin/time-sales/pane.tsx +3 -1
  352. package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
  353. package/src/plugins/builtin/trace-bonds/model.ts +44 -10
  354. package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
  355. package/src/plugins/builtin/treasury-auctions/pane.tsx +2 -2
  356. package/src/plugins/builtin/vol-surface/client.ts +18 -3
  357. package/src/plugins/builtin/vol-surface/index.tsx +1 -2
  358. package/src/plugins/builtin/volatility/index.tsx +1 -2
  359. package/src/plugins/builtin/world-indices/footer.ts +6 -1
  360. package/src/plugins/builtin/world-venue-map/client.ts +10 -1
  361. package/src/plugins/builtin/world-venue-map/index.ts +6 -2
  362. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
  363. package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
  364. package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
  365. package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
  366. package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
  367. package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
  368. package/src/plugins/builtin/yield-curve/curves.ts +16 -2
  369. package/src/plugins/builtin/yield-curve/headless.ts +3 -3
  370. package/src/plugins/bundle.ts +52 -2
  371. package/src/plugins/catalog-browser.ts +2 -2
  372. package/src/plugins/catalog-cli.ts +20 -0
  373. package/src/plugins/host-link.ts +13 -1
  374. package/src/plugins/installer.ts +15 -4
  375. package/src/plugins/loader.ts +43 -11
  376. package/src/plugins/peer-imports.ts +201 -0
  377. package/src/plugins/peer-retry.ts +26 -29
  378. package/src/plugins/plugin-process.ts +128 -0
  379. package/src/plugins/redirection-guard.ts +45 -0
  380. package/src/remote/endpoint.ts +16 -3
  381. package/src/renderers/browser/main.tsx +3 -2
  382. package/src/renderers/cloudflare/http-proxy.ts +7 -123
  383. package/src/renderers/dom/desktop/icons.tsx +2 -0
  384. package/src/renderers/dom/host/image-surface.tsx +46 -21
  385. package/src/renderers/dom/styles.css +7 -0
  386. package/src/renderers/dom/toast-host.tsx +2 -0
  387. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  388. package/src/renderers/electrobun/bun/desktop/initialization.ts +3 -2
  389. package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
  390. package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
  391. package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
  392. package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
  393. package/src/renderers/electrobun/bun/window/create.ts +4 -2
  394. package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
  395. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
  396. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
  397. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
  398. package/src/renderers/electrobun/view/main.tsx +7 -2
  399. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
  400. package/src/renderers/opentui/browser-open.ts +10 -4
  401. package/src/renderers/opentui/image/surface.tsx +1 -1
  402. package/src/renderers/opentui/start.tsx +12 -6
  403. package/src/renderers/opentui/toast-host.tsx +2 -1
  404. package/src/series/registry.ts +18 -0
  405. package/src/series/standalone.ts +10 -1
  406. package/src/series/types.ts +7 -0
  407. package/src/series/validate.ts +11 -2
  408. package/src/sources/gloomberb-cloud/index.ts +28 -5
  409. package/src/sources/gloomberb-cloud/news.ts +24 -3
  410. package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
  411. package/src/sources/provider-errors.ts +26 -3
  412. package/src/sources/provider-router/history.ts +5 -0
  413. package/src/sources/provider-router/index.ts +13 -0
  414. package/src/sync/core-contributors.ts +32 -0
  415. package/src/telemetry/attention-counts.ts +64 -6
  416. package/src/telemetry/attention-setting.ts +33 -0
  417. package/src/telemetry/crash-reports-node.ts +3 -1
  418. package/src/telemetry/stall-clocks.ts +93 -0
  419. package/src/telemetry/stall-reports-node.ts +12 -7
  420. package/src/telemetry/stall-reports.ts +34 -5
  421. package/src/test-support/preload.ts +9 -0
  422. package/src/tickers/company-name.ts +19 -0
  423. package/src/tickers/listing-fate.ts +14 -3
  424. package/src/tickers/saved-instrument.ts +17 -0
  425. package/src/tickers/search/home-listing.ts +13 -2
  426. package/src/tickers/search/index.ts +4 -1
  427. package/src/tickers/search/types.ts +6 -1
  428. package/src/time-series/bar-aggregation.ts +7 -1
  429. package/src/time-series/base-series.ts +3 -3
  430. package/src/time-series/chart-bounds.ts +1 -1
  431. package/src/time-series/chart-data.ts +5 -2
  432. package/src/time-series/chart-seed.ts +1 -1
  433. package/src/time-series/coin-session-close.ts +211 -0
  434. package/src/time-series/live-bars.ts +13 -2
  435. package/src/time-series/live-quotes.ts +1 -1
  436. package/src/time-series/official-close.ts +159 -0
  437. package/src/time-series/price-comparison.ts +55 -6
  438. package/src/time-series/resolve.ts +12 -3
  439. package/src/time-series/series-presentation.ts +1 -1
  440. package/src/time-series/studies/formula.ts +205 -0
  441. package/src/time-series/studies/indicators.ts +299 -0
  442. package/src/time-series/studies/leverage.ts +75 -0
  443. package/src/time-series/studies/pair.ts +138 -0
  444. package/src/time-series/studies/resolve.ts +333 -0
  445. package/src/time-series/studies/shared.ts +141 -0
  446. package/src/time-series/studies/units.ts +243 -0
  447. package/src/time-series/studies/vwap.ts +274 -0
  448. package/src/types/config.ts +18 -2
  449. package/src/types/data-provider.ts +23 -0
  450. package/src/types/financials.ts +22 -0
  451. package/src/types/headless.ts +42 -3
  452. package/src/types/plugin.ts +32 -3
  453. package/src/ui/host.tsx +11 -0
  454. package/src/ui/toast.tsx +26 -1
  455. package/src/utils/ascii-text.ts +140 -0
  456. package/src/utils/cli-output.ts +91 -18
  457. package/src/utils/concurrency.ts +24 -0
  458. package/src/utils/exchanges.ts +6 -0
  459. package/src/utils/external-url.ts +20 -7
  460. package/src/utils/futures-generic.ts +4 -4
  461. package/src/utils/http-proxy-upstream.ts +124 -0
  462. package/src/utils/main-thread-stall.ts +75 -3
  463. package/src/utils/perf-marks.ts +36 -2
  464. package/src/utils/private-files.ts +141 -0
  465. package/src/utils/public-address.ts +99 -0
  466. package/src/account/upgrade-personalization.ts +0 -57
  467. package/src/account/upgrade-tickers.ts +0 -124
  468. package/src/api-client/upgrade-experiment.ts +0 -91
  469. package/src/plugins/builtin/cloud/browser.tsx +0 -3
  470. package/src/renderers/electrobun/launcher/preload.ts +0 -13
  471. package/src/renderers/electrobun/launcher/windows-emulation.ts +0 -50
  472. package/src/time-series/studies.ts +0 -1628
@@ -12,6 +12,7 @@ import {
12
12
  type DataTableCell,
13
13
  type StatItem,
14
14
  } from "../../../components";
15
+ import { ageFooterSegment } from "../../../components/layout/pane/footer";
15
16
  import { formatCompactAxis } from "../../../components/chart-table";
16
17
  import { useChartViewReset } from "../../../components/chart/core/view-reset";
17
18
  import { scalarPoint, staticSeries } from "../../../components/chart/static/series";
@@ -36,6 +37,7 @@ import {
36
37
  flowColor,
37
38
  formatFlow,
38
39
  formatFlowAmount,
40
+ formatPriceChange,
39
41
  fundTickerOfColumn,
40
42
  pendingText,
41
43
  type CryptoEtfFlowColumn,
@@ -76,13 +78,15 @@ export function CryptoEtfFlowsPane({ focused, width, height }: PaneProps) {
76
78
  }, [asset, setSelectedByAsset]);
77
79
 
78
80
  const label = cryptoEtfFlowAssetLabel(asset);
79
- // The running total since the first fund launched, drawn over the range the table lists.
81
+ // The running total since the first fund launched, drawn over the range the table lists. Its legend is the one
82
+ // place the total and the day it starts from are said; the figures above carry only the table's windows.
80
83
  const series = useMemo(() => {
81
84
  if (!data) return [];
82
85
  const first = days.at(-1)?.date ?? "";
83
86
  const points = cumulativeFlows(data).filter((point) => point.date >= first)
84
87
  .map((point) => scalarPoint(new Date(`${point.date}T00:00:00Z`), point.value * 1_000_000));
85
- return [staticSeries(points, { id: `etf-flows:${asset}`, label: `${label} ETF net flows since launch`, color: colors.positive, calendarSpaced: true })];
88
+ const launch = data.days.at(-1)?.date;
89
+ return [staticSeries(points, { id: `etf-flows:${asset}`, label: `${label} ETF net flows since ${launch ?? "launch"}`, color: colors.positive, calendarSpaced: true })];
86
90
  }, [asset, data, days, label]);
87
91
  const link = useChartTableSelection({ rows: days, getId: rowId, getDate: rowDate, selectedId, onSelect: selectDay, focused });
88
92
 
@@ -93,14 +97,12 @@ export function CryptoEtfFlowsPane({ focused, width, height }: PaneProps) {
93
97
  detail: [latest?.date, pending].filter(Boolean).join(" · ") || undefined },
94
98
  { id: "week", label: "5 days", value: formatFlowAmount(summary.week), color: flowColor(summary.week, colors) },
95
99
  { id: "month", label: "20 days", value: formatFlowAmount(summary.month), color: flowColor(summary.month, colors) },
96
- { id: "launch", label: "Since launch", value: formatFlowAmount(summary.sinceLaunch), color: flowColor(summary.sinceLaunch, colors),
97
- detail: summary.firstDate ? `since ${summary.firstDate}` : undefined },
98
100
  ] : [];
99
101
 
100
102
  const updatedAgo = useUpdatedAgo(resource.updatedAt);
101
103
  const footerInfo = useMemo(() => data ? [
102
104
  { id: "as-of", parts: [{ text: `as of ${data.asOf}`, tone: "muted" as const }] },
103
- ...(updatedAgo ? [{ id: "updated", parts: [{ text: updatedAgo, tone: "muted" as const }] }] : []),
105
+ ...(updatedAgo ? [ageFooterSegment(updatedAgo)] : []),
104
106
  ] : [], [data, updatedAgo]);
105
107
  useAutoRefresh(resource.updatedAt, resource.load);
106
108
  const refresh = useCallback(() => void resource.reload(), [resource.reload]);
@@ -135,6 +137,7 @@ export function CryptoEtfFlowsPane({ focused, width, height }: PaneProps) {
135
137
  const renderCell = useCallback((day: CryptoEtfFlowDay, column: CryptoEtfFlowColumn): DataTableCell => {
136
138
  if (column.id === "date") return { text: day.date, value: day.date, color: colors.text };
137
139
  if (column.id === "total") return { text: formatFlow(day.total), value: day.total, color: flowColor(day.total, colors) };
140
+ if (column.id === "price") return { text: formatPriceChange(day.priceChangePct), value: day.priceChangePct ?? null, color: flowColor(day.priceChangePct, colors) };
138
141
  const ticker = fundTickerOfColumn(column.id) ?? "";
139
142
  const value = day.flows[ticker] ?? null;
140
143
  // A fund that reported the day before and not yet today: its figure is to come, not none.
@@ -1,6 +1,6 @@
1
1
  import type { HeadlessPaneDefinition } from "../../../types/plugin";
2
2
  import { fetchDebtMaturities } from "./client";
3
- import { bucketShare, debtAmount, debtNewestAsOf, debtNotices, debtPercent, newerQuarter } from "./model";
3
+ import { bucketShare, debtAmount, debtNewestAsOf, debtNewestForm, debtNotices, debtPercent, newerQuarter } from "./model";
4
4
  import { SEC_FILINGS } from "../shared/report-freshness";
5
5
  import { resolveHeadlessIssuerListing } from "../shared/headless-market-data";
6
6
 
@@ -118,8 +118,9 @@ export const debtMaturitiesHeadless: HeadlessPaneDefinition<"bundle"> = {
118
118
  },
119
119
  ],
120
120
  metadata: { ...data },
121
- // The latest filing dates the report (a newer 10-Q's balance date); the history rows are past years, not stale data.
122
- freshness: { asOf: debtNewestAsOf(data) },
121
+ // The latest filing dates the report (a newer 10-Q's balance date), named by its form so the date is
122
+ // not read as the schedule's; the history rows are past years, not stale data.
123
+ freshness: { asOf: debtNewestAsOf(data), asOfLabel: debtNewestForm(data) ?? undefined },
123
124
  };
124
125
  },
125
126
  };
@@ -219,9 +219,25 @@ export function newerQuarter(data: DebtMaturitiesPayload): DebtQuarter | null {
219
219
  const quarter = data.latestQuarter ?? null;
220
220
  return quarter && data.latest && quarter.asOf > data.latest.asOf ? quarter : null;
221
221
  }
222
+ /** The newest filing the payload reports: the 10-Q when it is newer, else the annual schedule's. */
223
+ function newestDebtFiling(data: DebtMaturitiesPayload): Pick<DebtQuarter, "form" | "asOf" | "filed"> | null {
224
+ return newerQuarter(data) ?? data.latest ?? null;
225
+ }
222
226
  /** The newest balance date the payload reports: the 10-Q's when it is newer, else the schedule's. */
223
227
  export function debtNewestAsOf(data: DebtMaturitiesPayload): string | null {
224
- return newerQuarter(data)?.asOf ?? data.latest?.asOf ?? null;
228
+ return newestDebtFiling(data)?.asOf ?? null;
229
+ }
230
+ /** The form of the filing `debtNewestAsOf` dates, which names that date: `10-Q`, `10-K`. */
231
+ export function debtNewestForm(data: DebtMaturitiesPayload): string | null {
232
+ return newestDebtFiling(data)?.form ?? null;
233
+ }
234
+ /**
235
+ * Which filing the pane's date is, as its footer says it, longest first:
236
+ * `10-Q 2026-09-30, filed 2026-10-27`, then `10-Q 2026-09-30`.
237
+ */
238
+ export function debtNewestFilingText(data: DebtMaturitiesPayload): string[] {
239
+ const filing = newestDebtFiling(data);
240
+ return filing ? [`${filing.form} ${filing.asOf}, filed ${filing.filed}`, `${filing.form} ${filing.asOf}`] : [];
225
241
  }
226
242
  /** The 10-Q's debt in one line, `Current 212.00M · Non-current 6.84B USD`. */
227
243
  export function quarterDebtText(quarter: DebtQuarter): string {
@@ -24,6 +24,7 @@ import {
24
24
  type DataTableCell,
25
25
  type StatItem,
26
26
  } from "../../../components";
27
+ import { ageFooterSegment } from "../../../components/layout/pane/footer";
27
28
  import { colors } from "../../../theme/colors";
28
29
  import type {
29
30
  DebtFact,
@@ -53,6 +54,7 @@ import {
53
54
  debtFilingUrl,
54
55
  debtMetricCaption,
55
56
  debtMetricValue,
57
+ debtNewestFilingText,
56
58
  debtNotices,
57
59
  debtPercent,
58
60
  historyAxis,
@@ -431,10 +433,13 @@ export function DebtMaturitiesPane({ width, height, focused }: PaneProps) {
431
433
  metric("next12", "Due next 12 months", latest.next12MonthsShare),
432
434
  metric("next3", "Due next 3 years", latest.next3YearsShare),
433
435
  { id: "as-of", label: "As of", value: latest.asOf },
434
- // A 10-Q filed since: its own row, dated, so it never reads as the schedule's.
436
+ // A 10-Q filed since: its own row, named by its period, so it never reads
437
+ // as the schedule's. The filed date goes beside the figures where it fits
438
+ // and gives way to them where it does not: the footer says it
439
+ // ("10-Q 2026-09-30, filed 2026-10-27").
435
440
  ...(quarter ? [{
436
- id: "latest-10q", label: "Latest 10-Q", value: quarterDebtText(quarter),
437
- detail: `${quarter.asOf} · filed ${quarter.filed}`, wide: true,
441
+ id: "latest-10q", label: `10-Q ${quarter.asOf}`, value: quarterDebtText(quarter),
442
+ detail: `filed ${quarter.filed}`, detailVariants: [""], wide: true,
438
443
  }] : []),
439
444
  ];
440
445
  }, [latest, quarter]);
@@ -445,6 +450,15 @@ export function DebtMaturitiesPane({ width, height, focused }: PaneProps) {
445
450
  label: "Principal total", values, value: debtAmount(last), color: colors.warning,
446
451
  };
447
452
  }, [bars]);
453
+ // Which filing the pane's date is, then how long ago it loaded: the age goes first in a narrow footer.
454
+ const filingFooter = useMemo(() => {
455
+ const [filing, ...shorter] = data ? debtNewestFilingText(data) : [];
456
+ return [
457
+ ...(filing ? [{ id: "filing", asOf: true, parts: [{ text: filing, tone: "muted" as const }],
458
+ variants: shorter.map((text) => [{ text, tone: "muted" as const }]) }] : []),
459
+ ...(data && updatedAgo ? [ageFooterSegment(updatedAgo)] : []),
460
+ ];
461
+ }, [data, updatedAgo]);
448
462
  useAutoRefresh(resource.updatedAt, resource.load);
449
463
  usePaneRefreshKey(() => void resource.reload(), { focused });
450
464
  usePaneNoticeFooter({
@@ -465,7 +479,7 @@ export function DebtMaturitiesPane({ width, height, focused }: PaneProps) {
465
479
  ? debtFilingUrl(data.cik, selectedHistoryRow.accession)
466
480
  : (latest?.filingUrl ?? data?.source.url ?? null),
467
481
  showOpenHint: true,
468
- info: data && updatedAgo ? [{ id: "updated", parts: [{ text: updatedAgo, tone: "muted" as const }] }] : [],
482
+ info: filingFooter,
469
483
  stale: !!data && resource.data?.stale,
470
484
  });
471
485
  const renderBucket = (
@@ -288,7 +288,7 @@ export const debugPlugin: GloomPlugin = {
288
288
  name: "Debug Log",
289
289
  icon: "D",
290
290
  component: DebugPane,
291
- reportFreshness: { status: "not-a-feed", basis: "debug log" },
291
+ reportFreshness: { status: "not-a-feed", basis: "debug log", local: true },
292
292
  defaultPosition: "right",
293
293
  defaultMode: "floating",
294
294
  defaultFloatingSize: { width: 120, height: 30 },
@@ -75,6 +75,19 @@ function statedRateSummary(stated: StatedForwardRate): QuoteSummaryDividendField
75
75
  interface DividendReadOptions {
76
76
  /** The forward rate fundamentals state (statedForwardRate), asked only when the summary is missing. */
77
77
  loadStatedForwardRate?: () => Promise<StatedForwardRate | null> | StatedForwardRate | null;
78
+ /** The history already asked for (requestDividendHistory), so it need not wait for the quote the price comes from. */
79
+ history?: Promise<DividendHistoryResponse>;
80
+ }
81
+
82
+ type DividendHistoryResponse = Awaited<ReturnType<typeof apiClient.getMarketDividends>>;
83
+
84
+ /** Asks Gloom Cloud for a listing's dividend history, for fetchDividendData to read once the price is known. */
85
+ export function requestDividendHistory(symbol: string, exchange = ""): Promise<DividendHistoryResponse> {
86
+ const pending = trackRequest("dividends", () => apiClient.getMarketDividends(symbol, exchange));
87
+ pending.catch(() => {
88
+ // Intentional ignore: fetchDividendData awaits this same promise and reports its failure.
89
+ });
90
+ return pending;
78
91
  }
79
92
 
80
93
  let connectionHealth: ConnectionHealthRegistry | null = null;
@@ -125,8 +138,22 @@ function declaredCashByExDate(declared: DeclaredCash | undefined, cashCurrency:
125
138
  ? [[row.exDate, { amount: row.amount / unit.divisor, currency: unit.currency }] as const] : []));
126
139
  }
127
140
 
141
+ /**
142
+ * The currency the company declares in when it pays the listing another
143
+ * (South32's dollars, which its rand line converts): the listing's amounts are
144
+ * conversions of it. Undefined for a listing paid in what was declared.
145
+ */
146
+ function declaredCurrencyOf(declared: DeclaredCash | undefined, cashCurrency: string): string | undefined {
147
+ const currency = resolveCurrencyUnit(declared?.currency).currency;
148
+ return currency && currency !== cashCurrency ? currency : undefined;
149
+ }
150
+
128
151
  export interface DividendData {
129
152
  payments: DividendPayment[];
153
+ /** The currency the company declares in, when its amounts here are converted from it (declaredCurrencyOf). */
154
+ convertedFrom?: string;
155
+ /** Beside convertedFrom, the sub-unit the source states the cash in (GBp, ZAc): converted amounts are written in it. */
156
+ convertedSubUnit?: string;
130
157
  /** Declared and unpaid, in `currency`; never in `payments` or any figure. Absent from older servers. */
131
158
  declared?: DeclaredDividendAction[];
132
159
  metrics: DividendMetrics;
@@ -155,7 +182,7 @@ export async function fetchDividendData(
155
182
  currentPriceCurrency?: string,
156
183
  options: DividendReadOptions = {},
157
184
  ): Promise<DividendData> {
158
- const response = await trackRequest("dividends", () => apiClient.getMarketDividends(symbol, exchange));
185
+ const response = await (options.history ?? requestDividendHistory(symbol, exchange));
159
186
  if (!response.data) throw new Error(`No dividend data found for ${symbol}`);
160
187
  const { actions: sourceActions, quote: rawQuote, summary, triedAt } = response.data;
161
188
  // In the major unit as the history arrives, as every other reader of it gets it.
@@ -180,6 +207,9 @@ export async function fetchDividendData(
180
207
  historyError = MISSING_DIVIDEND_CURRENCY;
181
208
  }
182
209
  const declaredCash = cashCurrency ? declaredCashByExDate(actions?.declaredCash, cashCurrency) : new Map();
210
+ const convertedFrom = cashCurrency ? declaredCurrencyOf(actions?.declaredCash, cashCurrency) : undefined;
211
+ const sourceUnit = sourceActions?.currency;
212
+ const convertedSubUnit = convertedFrom && sourceUnit && resolveCurrencyUnit(sourceUnit).divisor !== 1 ? sourceUnit : undefined;
183
213
  const payments = actions && cashCurrency ? actions.dividends.flatMap(action => {
184
214
  const payment = toDividendPayment(action.exDate, action.amount, cashCurrency);
185
215
  const declared = declaredCash.get(action.exDate);
@@ -203,7 +233,7 @@ export async function fetchDividendData(
203
233
  throw new Error(`No dividend data found for ${symbol}`);
204
234
  }
205
235
  return {
206
- payments, ...(declared.length > 0 ? { declared } : {}), metrics, price, currency: currency || undefined, historyAvailable,
236
+ payments, ...(declared.length > 0 ? { declared } : {}), ...(convertedFrom ? { convertedFrom } : {}), ...(convertedSubUnit ? { convertedSubUnit } : {}), metrics, price, currency: currency || undefined, historyAvailable,
207
237
  ...(suppliedPrice == null && price != null && quote ? dividendQuotePriceMetadata(quote) : {}),
208
238
  ...(historyError ? { historyError } : {}), ...(summaryError ? { summaryError } : {}),
209
239
  ...(statedFields && metrics.forwardRate != null ? { forwardFromFundamentals: true } : {}),
@@ -4,19 +4,21 @@ import type {
4
4
  HeadlessPaneDefinition,
5
5
  HeadlessPaneLoadArgs,
6
6
  } from "../../../types/plugin";
7
- import { fetchDividendData, statedForwardRate, type DividendData } from "./client";
7
+ import { fetchDividendData, requestDividendHistory, statedForwardRate, type DividendData } from "./client";
8
8
  import { dividendReferencePrice } from "../../../utils/trailing-dividend";
9
9
  import type { DividendPayment, GrowthPayments } from "./types";
10
- import { formatDividendYield, growthPaymentsText, PAYOUT_NOT_MEANINGFUL, staleHistoryText, toDividendRows, trailingPaymentsText } from "./view";
10
+ import {
11
+ convertedFromText, formatConvertedAmount, formatDividendYield, growthPaymentsText, PAYOUT_NOT_MEANINGFUL, staleHistoryText, toDividendRows, trailingPaymentsText,
12
+ } from "./view";
11
13
  import { formatDistributionAmount, formatPercent } from "../../../utils/format";
12
14
  import { forwardDividendBasis, noCurrentDividend, TRAILING_DIVIDEND_BASIS, UNEVEN_REGULAR_PAYMENTS } from "../../../utils/forward-dividend";
13
15
  import { currencyPriceMinDigits, formatMarketPriceWithCurrency } from "../../../market-data/market/format";
14
16
  import type { Quote } from "../../../types/financials";
15
17
  import { regularSessionValuationPrice } from "../../../market-data/quotes/regular-session-valuation";
16
18
  import { dividendPriceStatus, dividendQuotePriceMetadata } from "./reference-price";
17
- import { mapWithConcurrency } from "../../../utils/concurrency";
18
19
  import { logFailure } from "../../../utils/log-failure";
19
- import { projectDividendRanking, roundedRatio, type DividendRankingEntry } from "./ranking";
20
+ import { projectDividendRanking, roundedRatio } from "./ranking";
21
+ import { MAX_RANKED_SYMBOLS, readDividendRanking, type DividendRankingReads } from "./ranking-reads";
20
22
  import { REPORT_FILTER, REPORT_ROWS } from "../shared/headless-options";
21
23
  import type { FundBondFacts } from "../../../api-client/fund-facts";
22
24
  import { resolveTickerInstrumentKind } from "../../../tickers/instrument-kind";
@@ -27,31 +29,35 @@ import {
27
29
  type TaxEquivalentAnalysis, type TreasuryTenor,
28
30
  } from "../../../market-data/tax-equivalent";
29
31
 
30
- /** Symbols one `fn DVD` ranks at most, and how many it reads at once. */
31
- const MAX_RANKED_SYMBOLS = 25;
32
- const RANKING_CONCURRENCY = 4;
33
32
  /** The history options' defaults: a ranking lists no history, so only these leave it unchanged. */
34
33
  const DEFAULT_TYPE = "all";
35
34
  const DEFAULT_LIMIT = 40;
36
35
 
37
- const PAYMENT_COLUMNS = [
38
- { key: "exDate", header: "Ex-date" },
39
- { key: "amount", header: "Amount", align: "right" as const,
40
- format: (value: unknown, row: Record<string, unknown>) => typeof value === "number" && typeof row.currency === "string" && row.currency
41
- ? formatDistributionAmount(value, row.currency) : value == null ? "-" : String(value) },
42
- { key: "currency", header: "CCY" },
43
- { key: "type", header: "Type" },
44
- ];
36
+ /** The history's columns; amounts converted from the currency the company declares in go to two decimals of their source's unit. */
37
+ function paymentColumns(converted: boolean, subUnit?: string) {
38
+ const amount = converted ? (value: number, currency: string) => formatConvertedAmount(value, currency, subUnit) : formatDistributionAmount;
39
+ return [
40
+ { key: "exDate", header: "Ex-date" },
41
+ { key: "amount", header: "Amount", align: "right" as const,
42
+ format: (value: unknown, row: Record<string, unknown>) => typeof value === "number" && typeof row.currency === "string" && row.currency
43
+ ? amount(value, row.currency) : value == null ? "-" : String(value) },
44
+ { key: "currency", header: "CCY" },
45
+ { key: "type", header: "Type" },
46
+ ];
47
+ }
45
48
 
46
49
  /** After the amount, for a company declaring in another currency than the listing pays: what it declared. */
47
50
  const DECLARED_COLUMN = { key: "declared", header: "Declared", align: "right" as const,
48
51
  format: (value: unknown, row: Record<string, unknown>) => typeof value === "number" && typeof row.declaredCurrency === "string"
49
52
  ? formatDistributionAmount(value, row.declaredCurrency) : "-" };
50
53
 
54
+ /** What a ticker's read needs: the market data, Gloom Cloud, and the listing a typed key stands for. */
55
+ export type DividendReadContext = Pick<HeadlessPaneContext, "marketData" | "apiClient" | "resolveInstrument">;
56
+
51
57
  export interface DividendYieldHeadlessDependencies {
52
- loadData(symbol: string, context: HeadlessPaneContext): Promise<DividendData>;
58
+ loadData(symbol: string, context: DividendReadContext): Promise<DividendData>;
53
59
  /** A US-listed fund's bond facts (SEC yield, yield to worst, duration); null for any other listing. */
54
- loadBond?(symbol: string, context: HeadlessPaneContext): Promise<FundBondFacts | null>;
60
+ loadBond?(symbol: string, context: DividendReadContext): Promise<FundBondFacts | null>;
55
61
  /** The Treasury par curve GC shows, for the muni/Treasury ratio; null when it cannot be read. */
56
62
  loadTreasury?(context: HeadlessPaneContext): Promise<TreasuryTenor[] | null>;
57
63
  }
@@ -68,6 +74,8 @@ const defaultDependencies: DividendYieldHeadlessDependencies = {
68
74
  const instrument = await context.resolveInstrument?.(symbol);
69
75
  const listingSymbol = instrument?.symbol ?? symbol;
70
76
  const exchange = instrument?.exchange ?? "";
77
+ // The history is asked for beside the quote, not after it: only its yields need the price.
78
+ const history = requestDividendHistory(listingSymbol, exchange);
71
79
  try {
72
80
  const quote = await context.marketData.getQuote(listingSymbol, exchange);
73
81
  // The regular session's price: after the close, the close rather than an after-hours print.
@@ -79,6 +87,7 @@ const defaultDependencies: DividendYieldHeadlessDependencies = {
79
87
  currentPrice = null;
80
88
  }
81
89
  const data = await fetchDividendData(listingSymbol, currentPrice, exchange, currentPriceCurrency, {
90
+ history,
82
91
  // Read only when the summary is missing: the forward rate fundamentals state, as `fundamentals` shows it.
83
92
  loadStatedForwardRate: async () => statedForwardRate(await context.marketData.getTickerFinancials(listingSymbol, exchange)),
84
93
  });
@@ -102,6 +111,25 @@ const defaultDependencies: DividendYieldHeadlessDependencies = {
102
111
  loadTreasury: (context) => loadTreasuryTenors(context.apiClient),
103
112
  };
104
113
 
114
+ /**
115
+ * How a ranking reads each ticker: `fn DVD`'s, and the DVD pane's with the
116
+ * app's market data, so both read the same listing at the same price. A
117
+ * fund's bond facts never fail its row: without them it ranks as before.
118
+ */
119
+ export function dividendRankingReads(
120
+ context: DividendReadContext,
121
+ dependencies: Pick<DividendYieldHeadlessDependencies, "loadData" | "loadBond"> = defaultDependencies,
122
+ ): DividendRankingReads {
123
+ const { loadBond } = dependencies;
124
+ return {
125
+ loadData: (symbol) => dependencies.loadData(symbol, context),
126
+ ...loadBond ? {
127
+ loadBond: (symbol: string) => loadBond(symbol, context).catch((error) => error instanceof BondClassificationError ? null
128
+ : logFailure("dividend-yield.headless", "Read bond fund facts for ranking failed", null)(error)),
129
+ } : {},
130
+ };
131
+ }
132
+
105
133
  /** A growth figure, or what its short year holds: "n/a: 1 of 2 payments". */
106
134
  function growthText(growth: number | null, payments: GrowthPayments | null | undefined): string {
107
135
  return growth == null && payments ? growthPaymentsText(payments) : formatPercent(growth ?? undefined);
@@ -147,10 +175,16 @@ export function projectDividendYieldHeadless(
147
175
  ...(row.declared ? { declared: row.declared.amount, declaredCurrency: row.declared.currency } : {}),
148
176
  };
149
177
  });
178
+ // A listing paid in another currency than its company declares (South32's rand) has its amounts converted from that one.
179
+ const convertedFrom = data.convertedFrom;
180
+ const subUnit = data.convertedSubUnit;
181
+ const paymentColumnList = paymentColumns(!!convertedFrom, subUnit);
150
182
  const declaredColumns = rows.some((row) => row.declared)
151
- ? [...PAYMENT_COLUMNS.slice(0, 2), DECLARED_COLUMN, ...PAYMENT_COLUMNS.slice(2)] : PAYMENT_COLUMNS;
183
+ ? [...paymentColumnList.slice(0, 2), DECLARED_COLUMN, ...paymentColumnList.slice(2)] : paymentColumnList;
152
184
  const metrics = data.metrics;
153
185
  const currency = data.currency ?? data.payments[0]?.currency ?? "";
186
+ const cashRate = (value: number | null) => !currency ? "—" : !convertedFrom ? formatDistributionAmount(value ?? undefined, currency)
187
+ : value == null ? formatConvertedAmount(value, currency) : `${formatConvertedAmount(value, currency, subUnit)} (${convertedFromText(convertedFrom)})`;
154
188
  const priceStatus = dividendPriceStatus(data.price, data.priceAsOf, data.priceStale);
155
189
  // A regular payer's year with fewer payments than its cadence pays: the yield says how many it counts.
156
190
  const shortfall = trailingPaymentsText(metrics.trailingPayments, { cadence: true });
@@ -180,8 +214,8 @@ export function projectDividendYieldHeadless(
180
214
  value: figure.value,
181
215
  formatted: `${figure.text}${figure.date ? ` (as of ${figure.date})` : ""}`,
182
216
  })) : []),
183
- { label: "Trailing rate", value: metrics.trailingRate, formatted: currency ? formatDistributionAmount(metrics.trailingRate ?? undefined, currency) : "—" },
184
- { label: "Forward rate", value: metrics.forwardRate, formatted: currency ? formatDistributionAmount(metrics.forwardRate ?? undefined, currency) : "—" },
217
+ { label: "Trailing rate", value: metrics.trailingRate, formatted: cashRate(metrics.trailingRate) },
218
+ { label: "Forward rate", value: metrics.forwardRate, formatted: cashRate(metrics.forwardRate) },
185
219
  { label: "Earnings Payout", value: roundedRatio(metrics.payoutRatio), formatted: metrics.payoutNotMeaningful ? PAYOUT_NOT_MEANINGFUL : formatDividendYield(metrics.payoutRatio) },
186
220
  { label: "1Y Cash Growth", value: roundedRatio(metrics.growth1Y), formatted: growthText(metrics.growth1Y, metrics.growth1YPayments) },
187
221
  { label: "3Y Cash CAGR", value: roundedRatio(metrics.growth3Y), formatted: growthText(metrics.growth3Y, metrics.growth3YPayments) },
@@ -198,7 +232,7 @@ export function projectDividendYieldHeadless(
198
232
  entries: taxEquivalentLines(tax).map((line) => ({ label: line.label, value: line.value, formatted: line.text })),
199
233
  }] : []),
200
234
  {
201
- title: "Dividend history",
235
+ title: convertedFrom ? `Dividend history (amounts ${convertedFromText(convertedFrom)})` : "Dividend history",
202
236
  columns: declaredColumns,
203
237
  rows,
204
238
  // Each amount is in its own row's currency.
@@ -211,6 +245,8 @@ export function projectDividendYieldHeadless(
211
245
  truncated: rows.length < matching.length,
212
246
  type,
213
247
  currency: data.currency ?? data.payments[0]?.currency ?? null,
248
+ // The currency the company declares in, when the amounts are converted from it; null for one paid as declared.
249
+ convertedFrom: convertedFrom ?? null,
214
250
  historyAvailable: data.historyAvailable ?? true,
215
251
  historyError: data.historyError ?? null,
216
252
  summaryError: data.summaryError ?? null,
@@ -321,18 +357,7 @@ export function createDividendYieldHeadless(
321
357
  if (historyOptions.length > 0) {
322
358
  throw new Error(`${historyOptions.join(" and ")} ${historyOptions.length === 1 ? "filters" : "filter"} one ticker's payment history, and a ranking of several lists none. Drop ${historyOptions.length === 1 ? "it" : "them"}, or run DVD on one ticker.`);
323
359
  }
324
- const entries = await mapWithConcurrency(args.symbols, RANKING_CONCURRENCY, async (symbol): Promise<DividendRankingEntry> => {
325
- // A fund's bond facts never fail its row: without them it ranks as before.
326
- const bond = dependencies.loadBond?.(symbol, context)
327
- .catch((error) => error instanceof BondClassificationError ? null
328
- : logFailure("dividend-yield.headless", "Read bond fund facts for ranking failed", null)(error)) ?? null;
329
- try {
330
- return { symbol, data: await dependencies.loadData(symbol, context), bond: await bond };
331
- } catch (error) {
332
- return { symbol, error: error instanceof Error ? error.message : String(error) };
333
- }
334
- });
335
- return projectDividendRanking(entries);
360
+ return projectDividendRanking(await readDividendRanking(args.symbols, dividendRankingReads(context, dependencies)));
336
361
  },
337
362
  };
338
363
  }
@@ -8,6 +8,9 @@ import {
8
8
  } from "./client";
9
9
  import { DividendYieldPane } from "./pane";
10
10
  import { dividendYieldHeadless } from "./headless";
11
+ import { RANK_SYMBOLS_SETTING } from "./ranking-view";
12
+ import { MAX_RANKED_SYMBOLS } from "./ranking-reads";
13
+ import type { PaneTemplateCreateOptions, PaneTemplateDef } from "../../../types/plugin";
11
14
 
12
15
  function createDividendYieldModule({
13
16
  component = DividendYieldPane,
@@ -65,7 +68,7 @@ function createDividendYieldModule({
65
68
 
66
69
  paneTemplates: [
67
70
  {
68
- ...createTickerSurfacePaneTemplate({
71
+ ...withRanking(createTickerSurfacePaneTemplate({
69
72
  id: "dividend-yield-pane",
70
73
  paneId: "dividend-yield",
71
74
  label: "Dividend Yield",
@@ -75,11 +78,43 @@ function createDividendYieldModule({
75
78
  "dividend growth", "payout ratio", "drip", "reinvest", "reinvestment", "dividend reinvestment", "rank dividend payers",
76
79
  "sec yield", "distribution yield", "muni", "munis", "municipal", "municipal bonds", "tax-exempt", "tax-equivalent yield", "muni treasury ratio"],
77
80
  shortcut: "DVD",
78
- }),
81
+ })),
79
82
  headless,
80
83
  },
81
84
  ],
82
85
  };
83
86
  }
84
87
 
88
+ /**
89
+ * The tickers a DVD is opened with when there are several: the list a command
90
+ * passes (`shot DVD KO JNJ`), or a comma list typed as its argument.
91
+ */
92
+ function rankedSymbols(options: PaneTemplateCreateOptions | undefined): string[] {
93
+ const list = options?.symbols?.length ? options.symbols : (options?.arg ?? options?.symbol ?? "").split(",");
94
+ // The first 25, as `fn DVD` ranks at most.
95
+ const symbols = [...new Set(list.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean))].slice(0, MAX_RANKED_SYMBOLS);
96
+ return symbols.length > 1 ? symbols : [];
97
+ }
98
+
99
+ /** DVD with several tickers opens one pane that ranks them by yield, as `fn DVD A B C` does; with one, its own pane. */
100
+ function withRanking(template: PaneTemplateDef): PaneTemplateDef {
101
+ return {
102
+ ...template,
103
+ // `fn` and `shot` pass a typed list as `symbols`, so `shot DVD KO JNJ` draws the ranking `fn DVD KO JNJ` prints.
104
+ opensTickerList: true,
105
+ canCreate: (context, options) => rankedSymbols(options).length > 1 || (template.canCreate?.(context, options) ?? true),
106
+ createInstance: (context, options) => {
107
+ const symbols = rankedSymbols(options);
108
+ if (symbols.length < 2) return template.createInstance?.(context, options) ?? null;
109
+ return {
110
+ instanceId: `dividend-yield:rank:${symbols.map((symbol) => encodeURIComponent(symbol)).join(",")}`,
111
+ title: `DVD ${symbols.join(", ")}`,
112
+ binding: { kind: "none" },
113
+ placement: "floating",
114
+ settings: { [RANK_SYMBOLS_SETTING]: symbols },
115
+ };
116
+ },
117
+ };
118
+ }
119
+
85
120
  export const dividendYieldModule = createDividendYieldModule();
@@ -31,7 +31,7 @@ import { isCloudSessionRequired, useResearchCloudSession } from "../shared/resea
31
31
  import { fetchDividendData, repriceDividendMetrics, statedForwardRate, type DividendData } from "./client";
32
32
  import { dividendReferencePrice } from "../../../utils/trailing-dividend";
33
33
  import { useTickerQuoteStream } from "../../../state/hooks/live-ticker-financials";
34
- import { buildTrailingCashChartPoints, formatDividendYield, growthPaymentsDetail, PAYOUT_NOT_MEANINGFUL, staleHistoryText, toDividendRows, trailingPaymentsText, type DividendRow } from "./view";
34
+ import { buildTrailingCashChartPoints, convertedFromText, formatConvertedAmount, formatDividendYield, growthPaymentsDetail, PAYOUT_NOT_MEANINGFUL, staleHistoryText, toDividendRows, trailingPaymentsText, type DividendRow } from "./view";
35
35
  import {
36
36
  DEFAULT_SORT_PREFERENCE,
37
37
  buildDividendColumns,
@@ -46,11 +46,14 @@ import type { FundBondFacts } from "../../../api-client/fund-facts";
46
46
  import { resolveTickerInstrumentKind } from "../../../tickers/instrument-kind";
47
47
  import { mayBeUsEquityOrFundTicker } from "../../../utils/sec";
48
48
  import { useFundFacts } from "../sec/fund-facts-view";
49
+ import { usePaneInstance } from "../../../state/app/context";
50
+ import { DividendRankingView, rankSymbolsOf } from "./ranking-view";
49
51
  import { bondFigures } from "../../../market-data/bond-funds";
50
52
 
51
- function formatRate(value: number | null, currency: string): string {
53
+ /** A per-share rate; one converted from the currency the company declares in goes to two decimals of its source's unit. */
54
+ function formatRate(value: number | null, currency: string, converted = false, subUnit?: string): string {
52
55
  if (value == null) return "—";
53
- return formatDistributionAmount(value, currency);
56
+ return converted ? formatConvertedAmount(value, currency, subUnit) : formatDistributionAmount(value, currency);
54
57
  }
55
58
 
56
59
  function formatGrowth(value: number | null): string {
@@ -90,7 +93,9 @@ function formatFrequency(freq: DividendMetrics["paymentFrequency"]): string {
90
93
  * say they are not its SEC yield, and the SEC yield follows them with its date.
91
94
  */
92
95
  function buildMetricItems(metrics: DividendMetrics, currency: string, hasHistory: boolean, short: boolean,
93
- declared: DividendData["declared"], bond: FundBondFacts | null = null): StatItem[] {
96
+ declared: DividendData["declared"], bond: FundBondFacts | null = null, convertedFrom?: string, convertedSubUnit?: string): StatItem[] {
97
+ // The rates of a listing paid in a conversion of what its company declares say what they were converted from.
98
+ const converted = convertedFrom ? { detail: convertedFromText(convertedFrom) } : {};
94
99
  const exDate: StatItem[] = metrics.nextExDividendDate
95
100
  ? [{ id: "next-ex", label: "Next ex", value: formatDate(metrics.nextExDividendDate) }]
96
101
  : hasHistory ? [] : [{ id: "last-ex", label: "Last ex", value: formatDate(metrics.lastExDividendDate) }];
@@ -116,8 +121,10 @@ function buildMetricItems(metrics: DividendMetrics, currency: string, hasHistory
116
121
  ...(forwardNote ? { detail: forwardNote } : {}) },
117
122
  // The 30-day SEC yield as the fund's sponsor publishes it, dated.
118
123
  ...(sec ? [{ id: "sec-yield", label: short ? "SEC yld" : "SEC yield", value: sec.text, ...(sec.date ? { detail: `as of ${sec.date}` } : {}) }] : []),
119
- { id: "ttm-rate", label: short ? "TTM/sh" : "TTM/share", value: formatRate(metrics.trailingRate, currency) },
120
- { id: "forward-rate", label: short ? "Fwd/sh" : "Fwd/share", value: formatRate(metrics.forwardRate, currency) },
124
+ { id: "ttm-rate", label: short ? "TTM/sh" : "TTM/share", value: formatRate(metrics.trailingRate, currency, !!convertedFrom, convertedSubUnit),
125
+ ...metrics.trailingRate != null ? converted : {} },
126
+ { id: "forward-rate", label: short ? "Fwd/sh" : "Fwd/share", value: formatRate(metrics.forwardRate, currency, !!convertedFrom, convertedSubUnit),
127
+ ...metrics.forwardRate != null ? converted : {} },
121
128
  growthItem("growth-1y", short ? "1Y chg" : "1Y growth", metrics.growth1Y, metrics.growth1YPayments),
122
129
  growthItem("cagr-3y", "3Y CAGR", metrics.growth3Y, metrics.growth3YPayments),
123
130
  { id: "payout", label: "Payout", value: metrics.payoutNotMeaningful ? PAYOUT_NOT_MEANINGFUL : metrics.payoutRatio != null ? `${(metrics.payoutRatio * 100).toFixed(1)}%` : "—" },
@@ -144,13 +151,15 @@ const rowKey = (row: DividendRow) => row.key;
144
151
  function renderCell(
145
152
  row: DividendRow,
146
153
  column: DividendColumn,
154
+ converted = false,
155
+ subUnit?: string,
147
156
  ): DataTableCell {
148
157
  switch (column.id) {
149
158
  case "exDate":
150
159
  return { text: row.exDate, color: colors.textDim };
151
160
  case "amount":
152
161
  return {
153
- text: formatDistributionAmount(row.amount, row.currency),
162
+ text: converted ? formatConvertedAmount(row.amount, row.currency, subUnit) : formatDistributionAmount(row.amount, row.currency),
154
163
  color: colors.textBright,
155
164
  attributes: TextAttributes.BOLD,
156
165
  };
@@ -161,9 +170,15 @@ function renderCell(
161
170
  }
162
171
  }
163
172
 
164
- export function DividendYieldPane({ focused, width, height, loadData = fetchDividendData }: {
165
- focused: boolean; width: number; height: number; loadData?: typeof fetchDividendData;
166
- }) {
173
+ type DividendPaneProps = { focused: boolean; width: number; height: number; loadData?: typeof fetchDividendData };
174
+
175
+ /** One ticker's dividends, or several ranked by yield when the pane was opened with a list (`shot DVD KO JNJ`). */
176
+ export function DividendYieldPane(props: DividendPaneProps) {
177
+ const ranked = rankSymbolsOf(usePaneInstance()?.settings);
178
+ return ranked.length > 1 ? <DividendRankingView symbols={ranked} {...props} /> : <DividendTickerPane {...props} />;
179
+ }
180
+
181
+ function DividendTickerPane({ focused, width, height, loadData = fetchDividendData }: DividendPaneProps) {
167
182
  const { symbol, ticker, financials } = usePaneTicker();
168
183
  const { nativePaneChrome } = useUiCapabilities();
169
184
  // Yields are repriced from the quote on every render, so streaming it is all
@@ -275,13 +290,17 @@ export function DividendYieldPane({ focused, width, height, loadData = fetchDivi
275
290
  });
276
291
  // The header row, plus the scrollbar row once the columns overflow the pane.
277
292
  const tableChromeRows = chartTableChromeRows(columns, width);
278
- const fullFigures = metrics ? buildMetricItems(metrics, currency, sortedRows.length > 0, false, data?.declared, bond) : [];
293
+ const convertedFrom = historyData?.convertedFrom;
294
+ const convertedSubUnit = historyData?.convertedSubUnit;
295
+ const renderRowCell = useCallback((row: DividendRow, column: DividendColumn) => renderCell(row, column, !!convertedFrom, convertedSubUnit),
296
+ [convertedFrom, convertedSubUnit]);
297
+ const fullFigures = metrics ? buildMetricItems(metrics, currency, sortedRows.length > 0, false, data?.declared, bond, convertedFrom, convertedSubUnit) : [];
279
298
  const keptFigures = chartTableLayout({
280
299
  width, height, figures: fullFigures, tableRows: sortedRows.length, tableChromeRows, chart: chartPoints.length >= 2 ? {} : null,
281
300
  }).figures;
282
301
  // The desktop grid never clips a label; the terminal shortens them rather than clip.
283
302
  const figures = metrics && !nativePaneChrome && labelsClip(keptFigures, width)
284
- ? buildMetricItems(metrics, currency, sortedRows.length > 0, true, data?.declared, bond) : fullFigures;
303
+ ? buildMetricItems(metrics, currency, sortedRows.length > 0, true, data?.declared, bond, convertedFrom, convertedSubUnit) : fullFigures;
285
304
 
286
305
  const handleHeaderClick = useCallback((columnId: string) => {
287
306
  setSortPreference((current) => nextHeaderSort(current, columnId as DividendColumnId, {
@@ -324,7 +343,7 @@ export function DividendYieldPane({ focused, width, height, loadData = fetchDivi
324
343
  sortDirection={sortPreference.direction}
325
344
  onHeaderClick={handleHeaderClick}
326
345
  getItemKey={rowKey}
327
- renderCell={renderCell}
346
+ renderCell={renderRowCell}
328
347
  selectedTextOverridesCellColor
329
348
  emptyStateTitle={emptyTitle}
330
349
  />