@gloom-sh/terminal 0.18.5 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (570) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/package.json +1 -1
  4. package/patches/electrobun@1.18.1.patch +163 -8
  5. package/src/api-client/app-attention.ts +3 -0
  6. package/src/api-client/beneficial-owners.ts +3 -0
  7. package/src/api-client/company-kpis.ts +3 -0
  8. package/src/api-client/cot.ts +6 -2
  9. package/src/api-client/credit-documents.ts +6 -0
  10. package/src/api-client/data.ts +19 -7
  11. package/src/api-client/equity-screener.ts +24 -0
  12. package/src/api-client/fund-facts.ts +45 -4
  13. package/src/api-client/hiring.ts +3 -0
  14. package/src/api-client/index.ts +15 -0
  15. package/src/api-client/ipo.ts +24 -1
  16. package/src/api-client/market-discovery.ts +10 -0
  17. package/src/api-client/market-movers.ts +7 -0
  18. package/src/api-client/members.ts +1 -1
  19. package/src/api-client/mna.ts +3 -0
  20. package/src/api-client/paths.ts +9 -1
  21. package/src/api-client/perps.ts +43 -0
  22. package/src/api-client/research-activity.ts +3 -2
  23. package/src/api-client/roadmap.ts +85 -0
  24. package/src/api-client/types.ts +82 -2
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +0 -2
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +2 -2
  29. package/src/architecture/pane-tabs-allowlist.json +3 -0
  30. package/src/architecture/ttl-caches-allowlist.json +19 -0
  31. package/src/brokers/sync-broker-instance.ts +2 -8
  32. package/src/capabilities/chart-series.ts +1 -1
  33. package/src/cli/commands/crypto-hints.ts +40 -24
  34. package/src/cli/commands/market.ts +107 -38
  35. package/src/cli/commands/news-search.ts +22 -0
  36. package/src/cli/commands/options-chain.ts +48 -7
  37. package/src/cli/commands/options-leaps.ts +9 -3
  38. package/src/cli/commands/overview.ts +147 -32
  39. package/src/cli/commands/search.ts +21 -1
  40. package/src/cli/commands/themes.ts +11 -2
  41. package/src/cli/commands/ticker.ts +87 -36
  42. package/src/cli/company-name-hint.ts +41 -0
  43. package/src/cli/earnings-calendar.ts +1 -1
  44. package/src/cli/errors.ts +25 -2
  45. package/src/cli/glossary.ts +31 -2
  46. package/src/cli/history-rows.ts +57 -9
  47. package/src/cli/index-roots.ts +0 -7
  48. package/src/cli/index.ts +2 -0
  49. package/src/cli/listing-arg.ts +235 -60
  50. package/src/cli/output-file.ts +68 -0
  51. package/src/cli/pane-functions/capabilities.ts +4 -0
  52. package/src/cli/pane-functions/catalog.ts +80 -7
  53. package/src/cli/pane-functions/discovery.ts +21 -1
  54. package/src/cli/pane-functions/dom.ts +13 -3
  55. package/src/cli/pane-functions/freshness.ts +93 -4
  56. package/src/cli/pane-functions/function-help.ts +20 -10
  57. package/src/cli/pane-functions/headless.ts +17 -7
  58. package/src/cli/pane-functions/index.ts +54 -16
  59. package/src/cli/pane-functions/market-session.ts +66 -4
  60. package/src/cli/pane-functions/options.ts +19 -1
  61. package/src/cli/pane-functions/resolver.ts +19 -8
  62. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  63. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  64. package/src/cli/quote-answer.ts +22 -11
  65. package/src/cli/saved-copy-persistence.ts +88 -0
  66. package/src/cli/ticker-resolution.ts +3 -2
  67. package/src/components/chart/composite/composite-chart.tsx +4 -0
  68. package/src/components/chart/composite/interactions.ts +6 -0
  69. package/src/components/chart/composite/legend.tsx +45 -7
  70. package/src/components/chart/composite/panel-gestures.ts +33 -0
  71. package/src/components/chart/composite/panel-surface.tsx +52 -3
  72. package/src/components/chart/composite/rasterizer.ts +24 -13
  73. package/src/components/chart/composite/scene.ts +39 -4
  74. package/src/components/chart/composite/series-cues.ts +14 -0
  75. package/src/components/chart/composite/text-renderer.ts +9 -2
  76. package/src/components/chart/composite/tool-catalog.ts +36 -0
  77. package/src/components/chart/composite/tools.ts +203 -44
  78. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  79. package/src/components/command-bar/assist/inventory.ts +2 -1
  80. package/src/components/command-bar/commands/direct/index.ts +28 -13
  81. package/src/components/command-bar/list/model.ts +5 -0
  82. package/src/components/command-bar/pane-templates/items.ts +3 -2
  83. package/src/components/command-bar/routes/root/runtime.ts +6 -7
  84. package/src/components/command-bar/selection-runtime.ts +52 -3
  85. package/src/components/command-bar/surface/index.tsx +2 -0
  86. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  87. package/src/components/layout/pane/capture-stamp.ts +2 -2
  88. package/src/components/layout/pane/footer/index.tsx +22 -6
  89. package/src/components/layout/pane/footer/model.ts +83 -15
  90. package/src/components/layout/pane/status-footer.ts +35 -2
  91. package/src/components/layout/status-widget-space.ts +9 -0
  92. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  93. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  94. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  95. package/src/components/onboarding/wizard-positions.ts +3 -3
  96. package/src/components/ticker/list-table-view.tsx +8 -21
  97. package/src/components/ui/choice-dialog.tsx +23 -18
  98. package/src/components/ui/choice-groups.ts +18 -0
  99. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  100. package/src/components/ui/data-table/opentui/model.ts +26 -1
  101. package/src/components/ui/data-table/types.ts +2 -0
  102. package/src/components/ui/display.tsx +18 -3
  103. package/src/components/ui/icon.tsx +4 -1
  104. package/src/components/ui/list-view.tsx +8 -4
  105. package/src/components/ui/query-bar-keys.tsx +2 -0
  106. package/src/components/ui/query-bar.tsx +1 -0
  107. package/src/components/ui/select-button.tsx +5 -1
  108. package/src/components/ui/select-field.tsx +1 -0
  109. package/src/components/ui/table-layout.ts +8 -5
  110. package/src/core/app-runtime.ts +2 -0
  111. package/src/core/app-services.ts +2 -2
  112. package/src/data/config/chart-settings.ts +1 -1
  113. package/src/data/json-ticker-repository.ts +17 -8
  114. package/src/data/plugin-cache.ts +5 -3
  115. package/src/data/saved-copies.ts +8 -0
  116. package/src/data/ticker-repository.ts +16 -9
  117. package/src/data/ticker-writes.ts +81 -0
  118. package/src/data/ttl-cache.ts +165 -0
  119. package/src/docs/snapshot.json +103 -142
  120. package/src/i18n/es.ts +20 -6
  121. package/src/i18n/ja.ts +20 -6
  122. package/src/i18n/ko.ts +20 -6
  123. package/src/i18n/zh-cn.ts +20 -6
  124. package/src/i18n/zh-tw.ts +20 -6
  125. package/src/market-data/depositary-pair.ts +27 -3
  126. package/src/market-data/fund-facts.ts +50 -0
  127. package/src/market-data/market/change-basis.ts +16 -3
  128. package/src/market-data/market/format.ts +25 -0
  129. package/src/market-data/market/freshness.ts +2 -1
  130. package/src/market-data/options-alternatives.ts +81 -4
  131. package/src/market-data/quotes/metadata.ts +7 -0
  132. package/src/news/aggregator.ts +119 -9
  133. package/src/news/cloud-session.ts +21 -0
  134. package/src/news/hooks.ts +1 -0
  135. package/src/news/news-model.ts +1 -0
  136. package/src/news/types.ts +17 -0
  137. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  138. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  139. package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
  140. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  141. package/src/plugins/builtin/alerts/cli.ts +362 -80
  142. package/src/plugins/builtin/alerts/command.ts +25 -1
  143. package/src/plugins/builtin/alerts/event-rows.ts +24 -3
  144. package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
  145. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  146. package/src/plugins/builtin/alerts/format.ts +75 -5
  147. package/src/plugins/builtin/alerts/headless.ts +34 -12
  148. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  149. package/src/plugins/builtin/alerts/history.ts +1 -1
  150. package/src/plugins/builtin/alerts/index.tsx +79 -16
  151. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  152. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  153. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  154. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  155. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  156. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  157. package/src/plugins/builtin/alerts/storage.ts +12 -8
  158. package/src/plugins/builtin/alerts/types.ts +4 -0
  159. package/src/plugins/builtin/analytics/index.tsx +2 -1
  160. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  161. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  162. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  163. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  164. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  165. package/src/plugins/builtin/attention/index.tsx +2 -1
  166. package/src/plugins/builtin/attention/listing.ts +14 -0
  167. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  168. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  169. package/src/plugins/builtin/buildout/format.ts +38 -0
  170. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  171. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  172. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  173. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  174. package/src/plugins/builtin/cds/client.ts +142 -30
  175. package/src/plugins/builtin/cds/headless.ts +35 -8
  176. package/src/plugins/builtin/cds/model.ts +145 -18
  177. package/src/plugins/builtin/cds/pane.tsx +16 -5
  178. package/src/plugins/builtin/changelog/index.tsx +9 -6
  179. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  180. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  181. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  182. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  183. package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
  184. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  185. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  186. package/src/plugins/builtin/chart-composer/index.tsx +5 -6
  187. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  188. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  189. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  190. package/src/plugins/builtin/chat/controller/index.ts +5 -1
  191. package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
  192. package/src/plugins/builtin/chat/controller/state.ts +25 -2
  193. package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
  194. package/src/plugins/builtin/chat/controller/utils.ts +7 -2
  195. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  196. package/src/plugins/builtin/chat/status-preview.ts +79 -0
  197. package/src/plugins/builtin/chat/status-widget.tsx +147 -10
  198. package/src/plugins/builtin/chat/unread-badge.ts +51 -10
  199. package/src/plugins/builtin/clinical-trials/client.ts +49 -4
  200. package/src/plugins/builtin/clinical-trials/model.ts +86 -31
  201. package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
  202. package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
  203. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
  204. package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
  205. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  206. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  207. package/src/plugins/builtin/company-attention/client.ts +8 -9
  208. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  209. package/src/plugins/builtin/company-attention/headless.ts +2 -1
  210. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  211. package/src/plugins/builtin/company-attention/pane.tsx +12 -2
  212. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  213. package/src/plugins/builtin/company-kpis/headless.ts +8 -3
  214. package/src/plugins/builtin/company-kpis/model.ts +73 -5
  215. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  216. package/src/plugins/builtin/composite-plugins.ts +2 -1
  217. package/src/plugins/builtin/correlation/headless.ts +13 -5
  218. package/src/plugins/builtin/correlation/history.ts +22 -0
  219. package/src/plugins/builtin/cot/client.ts +22 -18
  220. package/src/plugins/builtin/cot/headless.ts +15 -10
  221. package/src/plugins/builtin/cot/model.ts +14 -1
  222. package/src/plugins/builtin/cot/pane.tsx +22 -14
  223. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  224. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  225. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  226. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  227. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  228. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  229. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  230. package/src/plugins/builtin/credit-documents/headless.ts +10 -10
  231. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  232. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  233. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  234. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  235. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  236. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  237. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  238. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  239. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  240. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  241. package/src/plugins/builtin/docs/targets.ts +5 -1
  242. package/src/plugins/builtin/doe/headless.ts +3 -2
  243. package/src/plugins/builtin/doe/pane.tsx +13 -3
  244. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  245. package/src/plugins/builtin/earnings/board.tsx +62 -71
  246. package/src/plugins/builtin/earnings/headless.ts +40 -4
  247. package/src/plugins/builtin/earnings/scope.ts +31 -43
  248. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  249. package/src/plugins/builtin/earnings-calls/data.ts +18 -6
  250. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  251. package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
  252. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  253. package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
  254. package/src/plugins/builtin/econ/calendar-model.ts +48 -1
  255. package/src/plugins/builtin/econ/calendar-source.ts +4 -2
  256. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  257. package/src/plugins/builtin/econ/figures.ts +48 -0
  258. package/src/plugins/builtin/econ/index.tsx +14 -5
  259. package/src/plugins/builtin/econ/print-watch.ts +3 -1
  260. package/src/plugins/builtin/econ/types.ts +2 -0
  261. package/src/plugins/builtin/econ-statistics/client.ts +11 -0
  262. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  263. package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
  264. package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
  265. package/src/plugins/builtin/econ-statistics/stats.ts +39 -9
  266. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  267. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  268. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  269. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  270. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  271. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  272. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  273. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  274. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  275. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  276. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  277. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  278. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  279. package/src/plugins/builtin/gpu/headless.ts +8 -2
  280. package/src/plugins/builtin/help/function-index.ts +4 -1
  281. package/src/plugins/builtin/help/index.tsx +62 -59
  282. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  283. package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
  284. package/src/plugins/builtin/help/table-cells.tsx +13 -2
  285. package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
  286. package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
  287. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  288. package/src/plugins/builtin/holders/headless.ts +22 -3
  289. package/src/plugins/builtin/holders/pane.tsx +3 -0
  290. package/src/plugins/builtin/holders/report-rows.ts +4 -1
  291. package/src/plugins/builtin/insider/headless.ts +2 -2
  292. package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
  293. package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
  294. package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
  295. package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
  296. package/src/plugins/builtin/iv-history/client.ts +52 -11
  297. package/src/plugins/builtin/iv-history/format.ts +36 -23
  298. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  299. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  300. package/src/plugins/builtin/iv-history/model.ts +163 -27
  301. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  302. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  303. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  304. package/src/plugins/builtin/jobs/client.ts +25 -29
  305. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  306. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  307. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  308. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  309. package/src/plugins/builtin/macro-day/headless.ts +12 -5
  310. package/src/plugins/builtin/macro-day/index.tsx +1 -1
  311. package/src/plugins/builtin/macro-day/model.ts +39 -5
  312. package/src/plugins/builtin/macro-day/pane.tsx +27 -19
  313. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  314. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  315. package/src/plugins/builtin/market-movers/client.ts +27 -7
  316. package/src/plugins/builtin/market-movers/headless.ts +34 -12
  317. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  318. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  319. package/src/plugins/builtin/market-movers/model.ts +4 -1
  320. package/src/plugins/builtin/market-movers/screener.ts +22 -4
  321. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  322. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  323. package/src/plugins/builtin/market-movers/table.ts +17 -5
  324. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  325. package/src/plugins/builtin/mna/headless.ts +2 -0
  326. package/src/plugins/builtin/mna/pane.tsx +3 -1
  327. package/src/plugins/builtin/news/headless.ts +3 -1
  328. package/src/plugins/builtin/news/index.tsx +4 -0
  329. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  330. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  331. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  332. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  333. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  334. package/src/plugins/builtin/openfda/client.ts +14 -2
  335. package/src/plugins/builtin/openfda/index.tsx +3 -2
  336. package/src/plugins/builtin/openfda/model.ts +9 -0
  337. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  338. package/src/plugins/builtin/options/analytics.ts +29 -5
  339. package/src/plugins/builtin/options/enrichment.ts +17 -5
  340. package/src/plugins/builtin/options/table.ts +5 -2
  341. package/src/plugins/builtin/options/types.ts +2 -0
  342. package/src/plugins/builtin/options/view.tsx +17 -4
  343. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  344. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  345. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  346. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  347. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  348. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  349. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  350. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  351. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  352. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  353. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  354. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  355. package/src/plugins/builtin/pe-band/model.ts +12 -49
  356. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  357. package/src/plugins/builtin/perps/cascade.ts +236 -0
  358. package/src/plugins/builtin/perps/client.ts +56 -2
  359. package/src/plugins/builtin/perps/headless.ts +65 -9
  360. package/src/plugins/builtin/perps/index.tsx +7 -5
  361. package/src/plugins/builtin/perps/model.ts +18 -4
  362. package/src/plugins/builtin/perps/pane.tsx +19 -22
  363. package/src/plugins/builtin/perps/status-line.ts +25 -0
  364. package/src/plugins/builtin/plugin-module.ts +5 -1
  365. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  366. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  367. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  368. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  369. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  370. package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -7
  371. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  372. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  373. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
  374. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
  375. package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
  376. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  377. package/src/plugins/builtin/realized-vol/client.ts +13 -5
  378. package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
  379. package/src/plugins/builtin/realized-vol/headless.ts +8 -4
  380. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  381. package/src/plugins/builtin/realized-vol/model.ts +37 -6
  382. package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
  383. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  384. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  385. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  386. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  387. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  388. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  389. package/src/plugins/builtin/research/event-model.ts +40 -12
  390. package/src/plugins/builtin/research/headless.ts +1 -1
  391. package/src/plugins/builtin/research/index.tsx +7 -3
  392. package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
  393. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  394. package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
  395. package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
  396. package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
  397. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  398. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  399. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  400. package/src/plugins/builtin/risk-factors/data.ts +4 -2
  401. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  402. package/src/plugins/builtin/roadmap/index.tsx +28 -0
  403. package/src/plugins/builtin/roadmap/model.ts +67 -0
  404. package/src/plugins/builtin/roadmap/pane.tsx +244 -0
  405. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  406. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  407. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  408. package/src/plugins/builtin/scanner/headless.ts +204 -35
  409. package/src/plugins/builtin/scanner/index.ts +16 -5
  410. package/src/plugins/builtin/scanner/live-headless.ts +48 -5
  411. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  412. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  413. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  414. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  415. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  416. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  417. package/src/plugins/builtin/sec/headless.ts +4 -2
  418. package/src/plugins/builtin/sec/index.tsx +3 -0
  419. package/src/plugins/builtin/series-inventory.ts +2 -0
  420. package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
  421. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  422. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  423. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  424. package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
  425. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  426. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  427. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  428. package/src/plugins/builtin/shared/report-freshness.ts +11 -2
  429. package/src/plugins/builtin/shared/research-cells.tsx +1 -1
  430. package/src/plugins/builtin/shared/series-cache.ts +4 -4
  431. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  432. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  433. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  434. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  435. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  436. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  437. package/src/plugins/builtin/themes/model.ts +16 -7
  438. package/src/plugins/builtin/themes/pane.tsx +5 -5
  439. package/src/plugins/builtin/thirteenf/api.ts +12 -1
  440. package/src/plugins/builtin/thirteenf/data.ts +7 -7
  441. package/src/plugins/builtin/thirteenf/headless.ts +31 -4
  442. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  443. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  444. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  445. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  446. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  447. package/src/plugins/builtin/ticker-detail/overview/model.ts +11 -3
  448. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
  449. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
  450. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  451. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  452. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  453. package/src/plugins/builtin/treasury-auctions/headless.ts +21 -12
  454. package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
  455. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  456. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  457. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  458. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  459. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  460. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  461. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  462. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  463. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  464. package/src/plugins/builtin/volatility/index.tsx +2 -2
  465. package/src/plugins/builtin/volatility/pane.tsx +7 -4
  466. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  467. package/src/plugins/builtin/yield-curve/client.ts +61 -1
  468. package/src/plugins/builtin/yield-curve/headless.ts +9 -6
  469. package/src/plugins/builtin/yield-curve/index.tsx +34 -10
  470. package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
  471. package/src/plugins/catalog-browser.ts +2 -1
  472. package/src/plugins/catalog-manifest.ts +2 -1
  473. package/src/plugins/host-link.ts +1 -1
  474. package/src/plugins/marketplace/model.ts +18 -5
  475. package/src/portfolio/column-values.ts +11 -7
  476. package/src/portfolio/futures-positions.ts +6 -3
  477. package/src/portfolio/position-quantity.ts +15 -9
  478. package/src/remote/resources.ts +2 -1
  479. package/src/renderers/dom/data-table/row.tsx +26 -0
  480. package/src/renderers/dom/desktop/icons.tsx +7 -0
  481. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  482. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  483. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  484. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  485. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  486. package/src/renderers/electrobun/bun/desktop/backend.ts +21 -10
  487. package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
  488. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  489. package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
  490. package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
  491. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  492. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  493. package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
  494. package/src/renderers/electrobun/bun/index.ts +26 -2
  495. package/src/renderers/electrobun/bun/window/create.ts +43 -4
  496. package/src/renderers/electrobun/bun/window/events.ts +20 -0
  497. package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
  498. package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
  499. package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
  500. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  501. package/src/renderers/electrobun/shared/protocol.ts +12 -0
  502. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  503. package/src/renderers/electrobun/view/main.tsx +24 -1
  504. package/src/renderers/electrobun/view/news-source.ts +5 -1
  505. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  506. package/src/renderers/electrobun/view/ui-host.tsx +8 -0
  507. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  508. package/src/renderers/opentui/start.tsx +5 -0
  509. package/src/series/registry.ts +1 -0
  510. package/src/series/types.ts +6 -0
  511. package/src/series/validate.ts +1 -0
  512. package/src/sources/gloomberb-cloud/fred-series.ts +3 -1
  513. package/src/sources/gloomberb-cloud/index.ts +43 -8
  514. package/src/sources/gloomberb-cloud/news.ts +47 -7
  515. package/src/sources/provider-router/financials.ts +15 -3
  516. package/src/sources/provider-router/index.ts +8 -5
  517. package/src/state/ticker-cleanup.ts +50 -0
  518. package/src/sync/controller.ts +4 -3
  519. package/src/sync/core-contributors.ts +90 -31
  520. package/src/telemetry/stall-reports-dom.ts +38 -0
  521. package/src/telemetry/stall-reports-node.ts +75 -0
  522. package/src/telemetry/stall-reports.ts +214 -0
  523. package/src/test-support/preload.ts +14 -0
  524. package/src/theme/themes.ts +10 -0
  525. package/src/tickers/company-name-listing.ts +53 -13
  526. package/src/tickers/company-name.ts +50 -4
  527. package/src/tickers/index-roots.ts +7 -0
  528. package/src/tickers/listing-fate.ts +2 -2
  529. package/src/tickers/listing-venues.ts +15 -3
  530. package/src/tickers/search/index.ts +6 -2
  531. package/src/tickers/search/ranking.ts +5 -1
  532. package/src/tickers/search/upsert.ts +13 -0
  533. package/src/tickers/terminal-codes.ts +19 -0
  534. package/src/tickers/ticker-changes.ts +8 -4
  535. package/src/time-series/alignment.ts +1 -1
  536. package/src/time-series/economic.ts +18 -4
  537. package/src/time-series/field-catalog.ts +1 -1
  538. package/src/time-series/filed-trailing-eps.ts +197 -0
  539. package/src/time-series/fundamentals.ts +44 -1
  540. package/src/time-series/ice-credit.ts +3 -0
  541. package/src/time-series/price-comparison.ts +30 -2
  542. package/src/time-series/resolve-context.ts +3 -1
  543. package/src/time-series/resolve.ts +56 -4
  544. package/src/time-series/session-bar.ts +11 -0
  545. package/src/time-series/spec.ts +3 -2
  546. package/src/time-series/studies.ts +8 -2
  547. package/src/time-series/total-return.ts +160 -0
  548. package/src/time-series/transforms.ts +2 -3
  549. package/src/time-series/types.ts +22 -1
  550. package/src/types/company-from.ts +40 -0
  551. package/src/types/data-provider.ts +7 -0
  552. package/src/types/financials.ts +59 -1
  553. package/src/types/headless.ts +27 -3
  554. package/src/types/news-source.ts +1 -0
  555. package/src/types/plugin.ts +9 -0
  556. package/src/ui/host.tsx +3 -0
  557. package/src/updater/failure.ts +4 -1
  558. package/src/utils/cli-output.ts +42 -1
  559. package/src/utils/crypto-pair.ts +165 -7
  560. package/src/utils/declared-dividends.ts +1 -1
  561. package/src/utils/forward-dividend.ts +2 -0
  562. package/src/utils/fundamentals.ts +60 -8
  563. package/src/utils/fx-market-hours.ts +26 -3
  564. package/src/utils/main-thread-stall.ts +35 -2
  565. package/src/utils/market-capitalization.ts +24 -1
  566. package/src/utils/price-history.ts +7 -1
  567. package/src/utils/shell-word.ts +8 -0
  568. package/src/utils/trailing-dividend.ts +53 -4
  569. package/src/version.ts +2 -2
  570. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -1,7 +1,7 @@
1
1
  import type { HeadlessBundleResult, HeadlessPaneColumn } from "../../../types/headless";
2
2
  import { formatPercent } from "../../../utils/format";
3
3
  import type { DividendData } from "./client";
4
- import { formatDividendYield } from "./view";
4
+ import { formatDividendYield, growthPaymentsDetail, PAYOUT_NOT_MEANINGFUL, trailingPaymentsText } from "./view";
5
5
  import type { FundBondFacts } from "../../../api-client/fund-facts";
6
6
  import { NOT_SEC_YIELD } from "../../../market-data/bond-funds";
7
7
 
@@ -38,10 +38,19 @@ function declaredPayDate(data: Pick<DividendData, "declared" | "metrics">, now =
38
38
  */
39
39
  function incompleteNote(entry: DividendRankingEntry): string | null {
40
40
  if ("error" in entry) return "not read";
41
- const { historyError, summaryError } = entry.data;
42
- if (historyError && summaryError) return "no cash history or summary";
41
+ const { historyError, summaryError, forwardFromFundamentals, metrics } = entry.data;
42
+ // A forward yield read from fundamentals in place of the summary is dated by the ex-date it annualizes.
43
+ const forward = summaryError && forwardFromFundamentals && metrics.forwardRateAsOf
44
+ ? `; fwd yield from fundamentals, ex ${metrics.forwardRateAsOf}` : "";
45
+ if (historyError && summaryError) return `no cash history or summary${forward}`;
43
46
  if (historyError) return "no cash history";
44
- return summaryError ? "no summary" : null;
47
+ return summaryError ? `no summary${forward}` : null;
48
+ }
49
+
50
+ /** How a row's trailing yield was counted, when its year holds fewer payments than its cadence pays; null otherwise. */
51
+ function yieldBasis(entry: DividendRankingEntry): string | null {
52
+ const counted = "data" in entry ? trailingPaymentsText(entry.data.metrics.trailingPayments, { cadence: true }) : null;
53
+ return counted ? `counts ${counted}` : null;
45
54
  }
46
55
 
47
56
  /** What a row is missing, in full, for the report's errors; null for a whole read. */
@@ -90,12 +99,17 @@ function rankDividendEntries(entries: readonly DividendRankingEntry[], now = new
90
99
  forwardYield: roundedRatio(metrics.forwardYield),
91
100
  ...bonds ? secYieldFields(entry) : {},
92
101
  payoutRatio: roundedRatio(metrics.payoutRatio),
102
+ payoutNotMeaningful: metrics.payoutNotMeaningful === true,
93
103
  growth1Y: roundedRatio(metrics.growth1Y),
94
104
  growth3Y: roundedRatio(metrics.growth3Y),
105
+ // Set when a year the figure compares is short of the cadence's payments: the figure is then n/a.
106
+ ...metrics.growth1YPayments ? { growth1YPayments: growthPaymentsDetail(metrics.growth1YPayments) } : {},
107
+ ...metrics.growth3YPayments ? { growth3YPayments: growthPaymentsDetail(metrics.growth3YPayments) } : {},
95
108
  lastExDate: isoDay(metrics.lastExDividendDate),
96
109
  payDate: declaredPayDate(data, now),
97
110
  frequency: metrics.paymentFrequency,
98
111
  note: incompleteNote(entry),
112
+ yieldBasis: yieldBasis(entry),
99
113
  error: incompleteReason(entry),
100
114
  },
101
115
  };
@@ -111,7 +125,9 @@ function rankDividendEntries(entries: readonly DividendRankingEntry[], now = new
111
125
  }
112
126
 
113
127
  const ratio = (value: unknown) => formatDividendYield(typeof value === "number" ? value : null);
114
- const growth = (value: unknown) => formatPercent(typeof value === "number" ? value : undefined);
128
+ /** A growth figure, or n/a when one of its years is short (the row's `<key>Payments`). */
129
+ const growth = (key: "growth1Y" | "growth3Y") => (value: unknown, row: Record<string, unknown>) =>
130
+ typeof value !== "number" && typeof row[`${key}Payments`] === "string" ? "n/a" : formatPercent(typeof value === "number" ? value : undefined);
115
131
  const day = (value: unknown) => typeof value === "string" && value ? value : "—";
116
132
 
117
133
  const RANKING_COLUMNS: HeadlessPaneColumn[] = [
@@ -120,12 +136,15 @@ const RANKING_COLUMNS: HeadlessPaneColumn[] = [
120
136
  description: "Cash distributions with ex-dates in the past twelve months over the reference price." },
121
137
  { key: "forwardYield", header: "Fwd yield", align: "right", format: ratio, stackPriority: 1,
122
138
  description: "Indicated annual rate over the reference price; none once the dividend has stopped." },
123
- { key: "payoutRatio", header: "Payout", align: "right", format: ratio,
124
- description: "Trailing twelve-month dividends over trailing twelve-month diluted EPS." },
125
- { key: "growth1Y", header: "1Y growth", align: "right", format: growth,
126
- description: "Trailing twelve-month cash per share against a year earlier." },
127
- { key: "growth3Y", header: "3Y CAGR", align: "right", format: growth,
128
- description: "Compound annual growth of trailing twelve-month cash per share over three years." },
139
+ { key: "payoutRatio", header: "Payout", align: "right",
140
+ // CSV and NDJSON say `n/m` where JSON has null; the text report draws `N/M` as P/E does.
141
+ format: (value, row) => row.payoutNotMeaningful === true ? PAYOUT_NOT_MEANINGFUL.toLowerCase() : ratio(value),
142
+ textFormat: (value, row) => row.payoutNotMeaningful === true ? PAYOUT_NOT_MEANINGFUL : ratio(value),
143
+ description: "Trailing twelve-month dividends over trailing twelve-month diluted EPS; N/M when that EPS is zero or negative." },
144
+ { key: "growth1Y", header: "1Y growth", align: "right", format: growth("growth1Y"),
145
+ description: "Trailing twelve-month cash per share against a year earlier; n/a when either year holds fewer payments than the cadence pays." },
146
+ { key: "growth3Y", header: "3Y CAGR", align: "right", format: growth("growth3Y"),
147
+ description: "Compound annual growth of trailing twelve-month cash per share over three years; n/a when either year holds fewer payments than the cadence pays." },
129
148
  { key: "lastExDate", header: "Last ex", format: day, shrink: false },
130
149
  { key: "payDate", header: "Pay date", format: day, shrink: false, stackPriority: 2,
131
150
  description: "Pay date of the next declared distribution; past distributions carry ex-dates only." },
@@ -148,7 +167,11 @@ function rankingColumns(bonds: boolean): HeadlessPaneColumn[] {
148
167
 
149
168
  /** Shown once a row is incomplete, so a missing figure never reads as a non-payer's. */
150
169
  const NOTE_COLUMN: HeadlessPaneColumn = { key: "note", header: "Note", format: (value) => typeof value === "string" ? value : "",
151
- description: "What the read of this ticker is missing: not read, no cash history (yields and growth), or no summary (forward yield, payout, pay date)." };
170
+ description: "What the read of this ticker is missing: not read, no cash history (yields and growth), or no summary (forward yield, payout, pay date); a forward yield read from fundamentals instead is dated by its ex-date." };
171
+
172
+ /** Shown once a regular payer's trailing year holds fewer payments than its cadence pays, which says nothing is missing from the read itself. */
173
+ const YIELD_BASIS_COLUMN: HeadlessPaneColumn = { key: "yieldBasis", header: "Yield basis", format: (value) => typeof value === "string" ? value : "",
174
+ description: "How many payments the trailing yield counts when a regular payer's year holds fewer than its cadence pays (a gap in its ex-dates)." };
152
175
 
153
176
  /** `fn DVD A B C`: one table of every symbol, ranked by trailing yield. */
154
177
  export function projectDividendRanking(entries: readonly DividendRankingEntry[], now = new Date()): HeadlessBundleResult {
@@ -167,7 +190,7 @@ export function projectDividendRanking(entries: readonly DividendRankingEntry[],
167
190
  } : {}),
168
191
  sections: [{
169
192
  title: `Ranked by trailing yield; payout is over TTM diluted EPS${bonds ? `; a bond fund's yields are distribution yields, ${NOT_SEC_YIELD}` : ""}`,
170
- columns: incomplete.length > 0 ? [...rankingColumns(bonds), NOTE_COLUMN] : rankingColumns(bonds),
193
+ columns: [...rankingColumns(bonds), ...incomplete.length > 0 ? [NOTE_COLUMN] : [], ...rows.some((row) => row.yieldBasis) ? [YIELD_BASIS_COLUMN] : []],
171
194
  rows,
172
195
  // Every figure is a percent; each exported row names its ticker and the time of the price its yields are over.
173
196
  unit: "pct",
@@ -178,7 +201,7 @@ export function projectDividendRanking(entries: readonly DividendRankingEntry[],
178
201
  rankedBy: "trailingYield",
179
202
  symbols: entries.map((entry) => entry.symbol),
180
203
  yieldMethod: "Cash distributions with ex-dates in the preceding 12 months, one payment per period for a regular cadence, divided by the reference share price, in the listing's own currency.",
181
- payoutMethod: "Trailing twelve-month dividends over trailing twelve-month diluted EPS, as the source states it.",
204
+ payoutMethod: "Trailing twelve-month dividends over trailing twelve-month diluted EPS, as the source states it; N/M (not meaningful) when that EPS is zero or negative.",
182
205
  growthMethod: "Trailing twelve-month cash per share now against one and three years before; 3Y is a compound annual rate.",
183
206
  payDateMethod: "Pay date of the next declared distribution; past distributions carry ex-dates only.",
184
207
  ...bonds ? { secYieldMethod: `A bond fund's 30-day SEC yield as its sponsor publishes it, dated by secYieldAsOf, as a ratio like the other yields; its trailingYield and forwardYield are distribution yields, ${NOT_SEC_YIELD}.` } : {},
@@ -1,5 +1,5 @@
1
1
  import type { DividendLapse } from "../../../utils/forward-dividend";
2
- import type { DistributionCadence } from "../../../utils/trailing-dividend";
2
+ import type { DistributionCadence, TrailingPayments } from "../../../utils/trailing-dividend";
3
3
 
4
4
  export interface DividendPayment {
5
5
  exDate: Date;
@@ -9,6 +9,14 @@ export interface DividendPayment {
9
9
  amount: number;
10
10
  currency: string;
11
11
  type: "cash" | "special" | "stock" | "unknown";
12
+ /** The amount as the company declared it, in another currency than `currency` (BP's dollars beside the pence its London line paid). */
13
+ declared?: { amount: number; currency: string };
14
+ }
15
+
16
+ /** A short year a growth figure would compare: its payments, and the day it ends (absent for the trailing year). */
17
+ export interface GrowthPayments extends TrailingPayments {
18
+ /** YYYY-MM-DD; absent when the short year is the trailing one. */
19
+ yearTo?: string;
12
20
  }
13
21
 
14
22
  export interface DividendMetrics {
@@ -16,13 +24,23 @@ export interface DividendMetrics {
16
24
  forwardYield: number | null;
17
25
  trailingRate: number | null;
18
26
  forwardRate: number | null;
19
- /** The ex-date of the distribution the forward rate annualizes, YYYY-MM-DD. */
27
+ /** The ex-date of the distribution the forward rate annualizes, YYYY-MM-DD; with `forwardBasis`, the latest of the year's payments. */
20
28
  forwardRateAsOf: string | null;
29
+ /** `uneven`: the year's regular payments differ widely, so the forward rate is their total rather than one annualized (unevenRegularPayments). */
30
+ forwardBasis?: "uneven" | null;
21
31
  /** Set once the dividend has stopped (lapsedDividend), when there is no forward rate or yield. */
22
32
  lapse?: DividendLapse | null;
23
33
  payoutRatio: number | null;
34
+ /** Trailing EPS is zero or negative, so a payout ratio says nothing: shown "N/M", never a ratio. */
35
+ payoutNotMeaningful?: boolean;
36
+ /** A regular payer's trailing year holds fewer payments than its cadence: the yield counts only these. */
37
+ trailingPayments?: TrailingPayments | null;
24
38
  growth1Y: number | null;
25
39
  growth3Y: number | null;
40
+ /** Set when one of the two years 1Y growth compares holds fewer payments than the cadence pays: no growth, and how many it holds. */
41
+ growth1YPayments?: GrowthPayments | null;
42
+ /** The same for the two years the 3Y rate compares. */
43
+ growth3YPayments?: GrowthPayments | null;
26
44
  paymentFrequency: DistributionCadence | null;
27
45
  lastExDividendDate: Date | null;
28
46
  /** Announced ex-date after now, from the summary or a future-dated history record. */
@@ -1,7 +1,8 @@
1
- import type { DividendPayment } from "./types";
1
+ import type { DividendPayment, GrowthPayments } from "./types";
2
2
  import type { ProjectedChartPoint } from "../../../components/chart/core/data";
3
3
  import { calendarMonthsBefore } from "../../../utils/calendar-date";
4
- import { LATE_GRACE_DAYS, trailingCashAt } from "../../../utils/trailing-dividend";
4
+ import { NOT_MEANINGFUL, formatPriceEarnings } from "../../../utils/price-earnings";
5
+ import { LATE_GRACE_DAYS, trailingCashAt, type TrailingPayments } from "../../../utils/trailing-dividend";
5
6
 
6
7
  const DAY = 24 * 60 * 60 * 1000;
7
8
 
@@ -9,6 +10,30 @@ export function formatDividendYield(value: number | null): string {
9
10
  return value != null && Number.isFinite(value) ? `${(value * 100).toFixed(2)}%` : "—";
10
11
  }
11
12
 
13
+ /** A payout over zero or negative earnings, written as a P/E over a loss is. */
14
+ export const PAYOUT_NOT_MEANINGFUL = formatPriceEarnings(NOT_MEANINGFUL);
15
+
16
+ /** How many payments a trailing yield counts, for a regular payer whose year holds fewer than its cadence pays: "9 of 12 payments". */
17
+ export function trailingPaymentsText(payments: TrailingPayments | null | undefined, { cadence = false }: { cadence?: boolean } = {}): string | null {
18
+ return payments ? `${payments.counted} of ${payments.expected} ${cadence ? `${payments.cadence} ` : ""}payments` : null;
19
+ }
20
+
21
+ /** How many payments a growth figure's short year holds: "1 of 2 payments", naming an earlier year by its end. */
22
+ export function growthPaymentsDetail(payments: GrowthPayments): string {
23
+ return `${payments.counted} of ${payments.expected} payments${payments.yearTo ? ` in the year to ${payments.yearTo}` : ""}`;
24
+ }
25
+
26
+ /** What a growth figure reads when one of its years is short: "n/a: 1 of 2 payments". */
27
+ export function growthPaymentsText(payments: GrowthPayments): string {
28
+ return `n/a: ${growthPaymentsDetail(payments)}`;
29
+ }
30
+
31
+ /** A history served stale, dated by its read when the source gave it. */
32
+ export function staleHistoryText(fetchedAt: string | undefined): string {
33
+ const day = fetchedAt && /^\d{4}-\d{2}-\d{2}/.test(fetchedAt) ? fetchedAt.slice(0, 10) : null;
34
+ return `Stale cash history${day ? ` as of ${day}` : ""}; recent distributions may be missing.`;
35
+ }
36
+
12
37
  /** Cash history does not contain historical share prices, so cannot establish historical yields. */
13
38
  export function buildTrailingCashChartPoints(payments: DividendPayment[], now = new Date()): ProjectedChartPoint[] {
14
39
  const sorted = payments.filter((payment) => payment.exDate <= now && Number.isFinite(payment.amount) && payment.amount > 0)
@@ -52,6 +77,8 @@ export interface DividendRow {
52
77
  exDate: string;
53
78
  amount: number;
54
79
  currency: string;
80
+ /** The amount as the company declared it, in another currency (DividendPayment.declared). */
81
+ declared?: { amount: number; currency: string };
55
82
  }
56
83
 
57
84
  export function toDividendRows(payments: DividendPayment[]): DividendRow[] {
@@ -60,5 +87,6 @@ export function toDividendRows(payments: DividendPayment[]): DividendRow[] {
60
87
  exDate: payment.exDate.toISOString().slice(0, 10),
61
88
  amount: payment.amount,
62
89
  currency: payment.currency,
90
+ ...(payment.declared ? { declared: payment.declared } : {}),
63
91
  }));
64
92
  }
@@ -10,6 +10,7 @@
10
10
  * entry in the docs the app ships.
11
11
  */
12
12
  import { DOCS_SITE } from "../../../docs/content";
13
+ import { ATTENTION_LISTED } from "../attention/listing";
13
14
  import type { DocsTopic } from "../../../docs/topics";
14
15
  import {
15
16
  helpFunctionForPane,
@@ -25,7 +26,10 @@ const PANE_DOCS: Readonly<Record<string, DocsTopic>> = {
25
26
  "twitter-feed": { kind: "function", code: "TWIT" },
26
27
  // One article opened from a news list.
27
28
  "news-story": { kind: "function", code: "N" },
28
- "attention-trending": { kind: "page", slug: "attention" },
29
+ // The ATTN page while it is listed. Until then the docs have none, so both
30
+ // panes open the page on Attention Counts, where their numbers come from.
31
+ "attention-trending": ATTENTION_LISTED ? { kind: "page", slug: "attention" } : { kind: "page", slug: "cloud-and-data" },
32
+ ...(ATTENTION_LISTED ? {} : { attention: { kind: "page", slug: "cloud-and-data" } as const }),
29
33
  // The reader itself: its ? goes back to the index.
30
34
  docs: { kind: "home" },
31
35
  };
@@ -1,6 +1,6 @@
1
1
  import type { DoeSeriesRow, DoeTab } from "../../../api-client/doe";
2
2
  import type { HeadlessPaneDefinition } from "../../../types/plugin";
3
- import { fetchDoeBoard } from "./client";
3
+ import { loadDoeBoard } from "./client";
4
4
  import {
5
5
  DOE_TABS,
6
6
  DOE_UNIT_LABEL,
@@ -61,7 +61,8 @@ export function doeHeadless(defaultTab: DoeTab | "all"): HeadlessPaneDefinition<
61
61
  "Five-year ranges read earlier years on the same calendar day.", "A range holds fewer than five years where a series is younger."] },
62
62
  describe: "EIA weekly petroleum and natural gas storage",
63
63
  async load(args) {
64
- const data = await fetchDoeBoard();
64
+ // Read afresh; a failed read answers from the board an earlier one saved, which the report says it is.
65
+ const data = (await loadDoeBoard(true)).payload;
65
66
  // Each tab is dated by its own EIA report (petroleum Wednesday, gas Thursday);
66
67
  // once the next one is a day overdue the tables have missed it. The basis
67
68
  // names what Vs 1Y and Vs 5Y avg are measured against.
@@ -13,8 +13,10 @@ import {
13
13
  usePaneTabs,
14
14
  type ChartTableChart,
15
15
  type DataTableColumn,
16
+ type PaneFooterSegment,
16
17
  } from "../../../components";
17
18
  import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
19
+ import { savedCopyFooterSegment } from "../../../components/layout/pane/footer";
18
20
  import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginPaneState } from "../../../public/react";
19
21
  import { usePaneInstance } from "../../../state/app/context";
20
22
  import { useThemeColors } from "../../../theme/theme-context";
@@ -136,13 +138,21 @@ export function DoePane({ width, height, focused }: PaneProps) {
136
138
  const { strip, rows: tabRows } = usePaneTabs({ tabs: [...DOE_TABS], activeValue: tab, onSelect: setTab, focused, dense: true });
137
139
  useAutoRefresh(resource.updatedAt, resource.load);
138
140
  usePaneRefreshKey(() => void resource.reload(), { focused });
141
+ // A refresh that failed shows the board an earlier read saved, and says when that copy is from.
142
+ const savedAt = resource.data?.savedAt ?? null;
139
143
  usePaneNoticeFooter({ registrationId: "doe:notices", focused,
140
- notices: [...(data?.gaps ?? []), ...(resource.data?.refreshError ? [resource.data.refreshError] : [])] });
144
+ notices: [...(data?.gaps ?? []), ...(resource.data?.refreshError && savedAt == null ? [resource.data.refreshError] : [])] });
141
145
  // What Vs 1Y and Vs 5Y are measured against on this tab: the EIA's published figures for gas storage.
142
146
  const basis = useMemo(() => data ? doeComparisonBasis(doeRows(data, tab)) : null, [data, tab]);
143
- const info = useMemo(() => basis ? [{ id: "basis", parts: [{ text: basis, tone: "muted" as const }] }] : undefined, [basis]);
147
+ const info = useMemo(() => {
148
+ const segments: PaneFooterSegment[] = [
149
+ ...(basis ? [{ id: "basis", parts: [{ text: basis, tone: "muted" as const }] }] : []),
150
+ ...(savedAt != null ? [savedCopyFooterSegment("saved-copy", savedAt)] : []),
151
+ ];
152
+ return segments.length ? segments : undefined;
153
+ }, [basis, savedAt]);
144
154
  usePaneStatusFooter({ registrationId: "doe", loading: resource.loading, error: notAvailable ? null : resource.error,
145
- stale: !!data && resource.data?.stale, info });
155
+ stale: !!data && !!resource.data?.stale && savedAt == null, info });
146
156
  return (
147
157
  <Box width={width} height={height} flexDirection="column">
148
158
  {strip}
@@ -5,8 +5,7 @@ import type { EarningsEvent } from "../../../types/data-provider";
5
5
  import type { EarningsDateStatus, EarningsEpsBasis, Quote } from "../../../types/financials";
6
6
  import type { TickerRecord } from "../../../types/ticker";
7
7
  import { earningsDateStatus, earningsEpsBasis, isProvisionalActual } from "../shared/earnings-report";
8
- import { calendarStatus } from "../shared/earnings-calendar";
9
- import { coherentEarningsValue } from "../shared/earnings-estimate-basis";
8
+ import { calendarReport } from "../shared/earnings-calendar";
10
9
  import { shortDate, surprise, weekday } from "./format";
11
10
 
12
11
  /** A held name is in a portfolio; a watched one only in watchlists. */
@@ -149,33 +148,35 @@ export function boardReport(report: EarningsCalendarReport, owned: ReadonlyMap<s
149
148
  };
150
149
  }
151
150
 
152
- /** A company the stored calendar does not carry (a listing abroad, a date past its horizon) from the per-ticker calendar. */
151
+ /**
152
+ * A company the stored calendar does not carry (a listing abroad, a date past
153
+ * its horizon) from the per-ticker calendar, read as every earnings surface
154
+ * reads its report (shared/earnings-calendar): a date gone by is reported, and
155
+ * keeps a consensus only when it is that report's, not the next quarter's.
156
+ */
153
157
  export function fallbackReport(event: EarningsEvent, owned: ReadonlyMap<string, Ownership>, today: string): BoardReport {
154
- const date = event.earningsDate.toISOString().slice(0, 10);
155
- const timing = event.timing === "BMO" ? "bmo" : event.timing === "AMC" ? "amc" : null;
158
+ const report = calendarReport(event, today);
156
159
  // Estimates in another currency would read as dollars beside the rest.
157
160
  const dollars = (field: "epsEstimate" | "revenueEstimate") => {
158
161
  const currency = event.estimateBasis?.[field]?.currency;
159
- return currency && currency !== "USD" ? null : coherentEarningsValue(event, field);
162
+ return currency && currency !== "USD" ? null : report[field];
160
163
  };
161
164
  const epsEstimate = dollars("epsEstimate");
162
165
  return {
163
- key: `${event.symbol}:${date}`,
166
+ key: `${event.symbol}:${report.date}`,
164
167
  symbol: event.symbol,
165
168
  name: event.name || event.symbol,
166
- date,
167
- timing,
169
+ date: report.date,
170
+ timing: report.timing,
168
171
  expectedTiming: false,
169
- // A date gone by is reported, as every earnings surface reads it (shared/earnings-calendar).
170
- dateStatus: calendarStatus(event, today),
172
+ dateStatus: report.dateStatus,
171
173
  marketCap: null,
172
174
  epsEstimate,
173
- // The count belongs to the estimate beside it.
174
- epsAnalysts: epsEstimate == null ? null : coherentEarningsValue(event, "epsAnalysts"),
175
+ // The count and the basis belong to the estimate beside them.
176
+ epsAnalysts: epsEstimate == null ? null : report.epsAnalysts,
175
177
  epsActual: null,
176
178
  provisional: false,
177
- // The estimate's basis goes with it.
178
- epsBasis: epsEstimate == null ? null : earningsEpsBasis(event.epsBasis),
179
+ epsBasis: epsEstimate == null ? null : report.epsBasis,
179
180
  epsSurprise: null,
180
181
  revenueEstimate: dollars("revenueEstimate"),
181
182
  impliedMove: null,
@@ -2,7 +2,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import {
3
3
  DataTableStackView,
4
4
  PageStackView,
5
- PaneStatusBody,
6
5
  QueryBar,
7
6
  usePaneFooter,
8
7
  usePaneMenuItems,
@@ -62,8 +61,7 @@ import {
62
61
  earningsScopeOptions,
63
62
  MINE_SCOPE,
64
63
  readEarningsScope,
65
- SECTOR_HOLDINGS_FUND,
66
- sectorSymbols,
64
+ fundSymbols,
67
65
  type EarningsScope,
68
66
  } from "./scope";
69
67
 
@@ -78,9 +76,8 @@ const MARKET_PER_DAY = 200;
78
76
  const MAX_SYMBOLS = 200;
79
77
 
80
78
  const MARKET_SCOPE: EarningsScope = { kind: "all" };
81
- const loadSectorHoldings = (force: boolean) => loadMembers(SECTOR_HOLDINGS_FUND, force);
82
- /** A sector fund's companies the board reads, its largest first. */
83
- const sectorNames = (...args: Parameters<typeof sectorSymbols>) => sectorSymbols(...args).slice(0, MAX_SYMBOLS);
79
+ /** A fund's companies the board reads, its largest first. */
80
+ const fundNames = (members: Parameters<typeof fundSymbols>[0]) => fundSymbols(members).slice(0, MAX_SYMBOLS);
84
81
 
85
82
  type Order = "cap" | "mine";
86
83
  type BoardView = "table" | "calendar";
@@ -100,7 +97,7 @@ interface BoardData {
100
97
  /**
101
98
  * ERN without a ticker: the market's report days, largest companies first,
102
99
  * with the implied move and each company's average move. With tickers, or the
103
- * Names filter (Mine, a portfolio, a watchlist or a sector fund's holdings),
100
+ * Names filter (Mine, a portfolio, a watchlist or a fund's holdings),
104
101
  * the upcoming reports of those companies instead.
105
102
  */
106
103
  export function EarningsBoard({ focused, width, height, scopedSymbols, highlight = null }: {
@@ -152,9 +149,7 @@ export function EarningsBoard({ focused, width, height, scopedSymbols, highlight
152
149
  () => earningsScopeOptions({ portfolios: config.portfolios, watchlists: config.watchlists }),
153
150
  [config.portfolios, config.watchlists],
154
151
  );
155
- const chosenScope = readEarningsScope(savedScope ?? (mineOnly ? MINE_SCOPE : ALL_SCOPE), scopeOptions);
156
- // The terminal keeps its Mine toggle, so a choice only the desktop offers reads as the market there.
157
- const scopeOption = desktop || chosenScope.value === MINE_SCOPE ? chosenScope : scopeOptions[0]!;
152
+ const scopeOption = readEarningsScope(savedScope ?? (mineOnly ? MINE_SCOPE : ALL_SCOPE), scopeOptions);
158
153
  const scoped = scopedSymbols.length > 0;
159
154
  const scope = scoped ? MARKET_SCOPE : scopeOption.scope;
160
155
  const view: BoardView = !desktop ? "table"
@@ -174,17 +169,17 @@ export function EarningsBoard({ focused, width, height, scopedSymbols, highlight
174
169
  .sort()
175
170
  .join(",");
176
171
  }, [scope, tickers]);
177
- const sector = scope.kind === "sector" ? scope : null;
178
- // A sector fund's companies come with its holdings, which the loader reads; the others are known here.
172
+ const fund = scope.kind === "fund" ? scope : null;
173
+ // A fund's companies come with its holdings, which the loader reads; the others are known here.
179
174
  const named = useMemo(() => {
180
175
  const names = scoped ? [...scopedSymbols]
181
176
  : scope.kind === "portfolio" || scope.kind === "watchlist" ? (collectionKey ? collectionKey.split(",") : [])
182
- : scope.kind === "sector" ? [] : [...owned.keys()];
177
+ : scope.kind === "fund" ? [] : [...owned.keys()];
183
178
  return names.slice(0, MAX_SYMBOLS);
184
179
  }, [collectionKey, owned, scope.kind, scoped, scopedSymbols]);
185
180
  // Named companies also go to the per-ticker calendar, which covers listings the stored one does not;
186
- // a sector fund's are S&P 500 companies, all in the stored one.
187
- const perTicker = !market && !sector;
181
+ // fund scopes use the stored calendar, including members outside the S&P 500.
182
+ const perTicker = !market && !fund;
188
183
  // The days ahead by default; a chosen week or day, past or ahead, for the market and for named companies alike.
189
184
  const days = useMemo(() => (market || range ? boardRangeDays(range, today) : relativeDays(today)), [market, range, today]);
190
185
  const weekOffset = boardRangeWeek(range, today);
@@ -196,13 +191,14 @@ export function EarningsBoard({ focused, width, height, scopedSymbols, highlight
196
191
  ? { from: days[0]!.from, to: days.at(-1)!.to, perDay: market ? MARKET_PER_DAY : 0, symbols: named }
197
192
  : { from: today, to: addDays(today, HORIZON_DAYS), perDay: 0, symbols: named },
198
193
  [calendar, days, market, named, range, today, week]);
199
- // A sector fund's read is named by the fund: its companies are known once its holdings are.
200
- const queryKey = sector ? `${sector.etf}|${earningsCalendarKey(query)}` : earningsCalendarKey(query);
194
+ // A fund's read is named by the fund: its companies are known once its holdings are.
195
+ const queryKey = fund ? `${fund.etf}|${earningsCalendarKey(query)}` : earningsCalendarKey(query);
201
196
 
202
197
  const loader = useCallback(async (force: boolean): Promise<BoardData> => {
203
- const names = sector ? sectorNames((await loadSectorHoldings(force)).payload.members, sector.sectors) : named;
204
- if (!market && names.length === 0) return { queryKey, reports: [], stale: false, refreshError: null };
205
- const read = sector ? { ...query, symbols: names } : query;
198
+ const holdings = fund ? await loadMembers(fund.etf, force) : null;
199
+ const names = holdings ? fundNames(holdings.payload.members) : named;
200
+ if (!market && names.length === 0) return { queryKey, reports: [], stale: !!holdings && (holdings.stale || holdings.payload.stale), refreshError: holdings?.refreshError ?? null };
201
+ const read = fund ? { ...query, symbols: names } : query;
206
202
  const [cloud, fallback] = await Promise.allSettled([
207
203
  loadEarningsBoard(read, force),
208
204
  !perTicker ? Promise.resolve([] as EarningsEvent[]) : loadEarningsCalendar(dataProvider, names, { force }).then((result) => result.events
@@ -218,24 +214,24 @@ export function EarningsBoard({ focused, width, height, scopedSymbols, highlight
218
214
  return {
219
215
  queryKey,
220
216
  reports: [...reports, ...extra],
221
- stale: cloud.status === "fulfilled" && cloud.value.stale,
217
+ stale: (!!holdings && (holdings.stale || holdings.payload.stale)) || (cloud.status === "fulfilled" && cloud.value.stale),
222
218
  refreshError: cloud.status === "rejected"
223
219
  ? (cloud.reason instanceof Error ? cloud.reason.message : String(cloud.reason))
224
- : cloud.value.refreshError,
220
+ : holdings?.refreshError ?? cloud.value.refreshError,
225
221
  };
226
- }, [calendar, dataProvider, market, named, owned, perTicker, query, queryKey, range, sector, today]);
222
+ }, [calendar, dataProvider, market, named, owned, perTicker, query, queryKey, range, fund, today]);
227
223
  const resource = useAsyncResource(loader, {
228
224
  keepPreviousData: true,
229
225
  initialData: () => {
230
- const holdings = sector ? cachedMembers(SECTOR_HOLDINGS_FUND) : null;
231
- if (sector && !holdings) return null;
232
- const cached = cachedEarningsCalendar(sector && holdings ? { ...query, symbols: sectorNames(holdings.payload.members, sector.sectors) } : query);
233
- return cached ? { queryKey, reports: cached.payload.reports.map((report) => boardReport(report, owned)), stale: false, refreshError: null } : null;
226
+ const holdings = fund ? cachedMembers(fund.etf) : null;
227
+ if (fund && !holdings) return null;
228
+ const cached = cachedEarningsCalendar(holdings ? { ...query, symbols: fundNames(holdings.payload.members) } : query);
229
+ return cached ? { queryKey, reports: cached.payload.reports.map((report) => boardReport(report, owned)), stale: !!holdings?.payload.stale, refreshError: null } : null;
234
230
  },
235
231
  });
236
- const data = resource.data;
237
- // The week on screen is only the one read for it, never the last week's kept while this one loads or after it failed.
238
- const weekData = data?.queryKey === queryKey ? data : null;
232
+ // A new fund or range never shows the previous scope while its holdings load or fail.
233
+ const data = resource.data?.queryKey === queryKey ? resource.data : null;
234
+ const weekData = data;
239
235
  const rows = useMemo(() => boardRows(data?.reports ?? [], days, sort, order === "mine"), [data, days, order, sort]);
240
236
  const reportRows = useMemo(() => rows.filter((row): row is BoardRow & { kind: "report" } => row.kind === "report"), [rows]);
241
237
  const slotsPerRow = calendarSlotsPerRow(width);
@@ -336,16 +332,12 @@ export function EarningsBoard({ focused, width, height, scopedSymbols, highlight
336
332
  usePaneRefreshKey(() => void resource.reload(), { focused, enabled: calendar && !open });
337
333
 
338
334
  const sortColumn = (Object.entries(SORTABLE).find(([, value]) => value === sort.column)?.[0]) ?? null;
339
- const mineFilter: QueryBarFilter[] = scoped ? [] : [
340
- { id: "mine", kind: "toggle", label: "Mine", value: scope.kind === "mine", defaultValue: false, onChange: (mine: boolean) => setScope(mine ? MINE_SCOPE : ALL_SCOPE) },
341
- ];
342
- // The desktop's Mine is one choice of the Names filter, beside each portfolio, watchlist and sector fund.
343
335
  const namesFilter: QueryBarFilter[] = scoped ? [] : [{
344
336
  id: "names",
345
337
  label: "Names",
346
338
  value: scopeOption.value,
347
339
  defaultValue: ALL_SCOPE,
348
- options: scopeOptions.map(({ value, label, description }) => ({ value, label, description })),
340
+ options: scopeOptions.map(({ value, label, description, group, scope }) => ({ value, label, description, group, short: !desktop && scope.kind === "fund" ? scope.etf : undefined })),
349
341
  onChange: (value: string) => setScope(value),
350
342
  }];
351
343
  const orderView: QueryBarView<Order> = { value: order, options: ORDER_OPTIONS, onChange: setOrder };
@@ -371,7 +363,7 @@ export function EarningsBoard({ focused, width, height, scopedSymbols, highlight
371
363
  onChange: (value: string) => { setRange(value); setSelectedKey(null); },
372
364
  };
373
365
  // On the desktop the view picks the table or the week, and the order is a filter beside the names and the Range.
374
- const queryBar = !desktop ? <QueryBar width={width} filters={[...mineFilter, rangeFilter]} view={orderView} /> : (
366
+ const queryBar = !desktop ? <QueryBar width={width} filters={[...namesFilter, rangeFilter]} view={orderView} /> : (
375
367
  <QueryBar
376
368
  width={width}
377
369
  filters={[
@@ -431,41 +423,40 @@ export function EarningsBoard({ focused, width, height, scopedSymbols, highlight
431
423
  }
432
424
 
433
425
  return (
434
- <PaneStatusBody loading={resource.loading && !data} error={!data ? resource.error : null} subject="the earnings calendar">
435
- <DataTableStackView<BoardRow, BoardColumn>
436
- focused={focused}
437
- rootWidth={width}
438
- rootHeight={height}
439
- rootBefore={queryBar}
440
- columns={columns}
441
- items={rows}
442
- getItemKey={(row) => row.key}
443
- isNavigable={(row) => row.kind === "report"}
444
- renderSectionHeader={renderBoardSection}
445
- renderCell={renderCell}
446
- getRowVersion={rowVersion}
447
- selectedTextOverridesCellColor
448
- sortColumnId={sortColumn}
449
- sortDirection={sort.direction}
450
- onHeaderClick={(columnId) => {
451
- const column = SORTABLE[columnId as BoardColumn["id"]];
452
- if (!column) return;
453
- setSort((current) => ({ column, direction: current.column === column && current.direction === "desc" ? "asc" : column === "symbol" ? "asc" : "desc" }));
454
- }}
455
- selection={{
456
- kind: "index",
457
- selectedIndex: selected ? rows.indexOf(selected) : -1,
458
- onChange: (_index, row) => { if (row.kind === "report") setSelectedKey(row.key); },
459
- }}
460
- onActivate={(row) => { if (row.kind === "report") setOpenKey(row.key); }}
461
- onRootKeyDown={handleKeyDown}
462
- detailOpen={!!open}
463
- onBack={() => setOpenKey(null)}
464
- detailTitle={open ? `${open.symbol} · ${open.name}` : undefined}
465
- detailContent={history}
466
- emptyStateTitle={emptyStateTitle}
467
- />
468
- </PaneStatusBody>
426
+ <DataTableStackView<BoardRow, BoardColumn>
427
+ focused={focused}
428
+ rootWidth={width}
429
+ rootHeight={height}
430
+ rootBefore={queryBar}
431
+ status={{ loading: resource.loading && !data, error: !data ? resource.error : null, subject: "the earnings calendar" }}
432
+ columns={columns}
433
+ items={rows}
434
+ getItemKey={(row) => row.key}
435
+ isNavigable={(row) => row.kind === "report"}
436
+ renderSectionHeader={renderBoardSection}
437
+ renderCell={renderCell}
438
+ getRowVersion={rowVersion}
439
+ selectedTextOverridesCellColor
440
+ sortColumnId={sortColumn}
441
+ sortDirection={sort.direction}
442
+ onHeaderClick={(columnId) => {
443
+ const column = SORTABLE[columnId as BoardColumn["id"]];
444
+ if (!column) return;
445
+ setSort((current) => ({ column, direction: current.column === column && current.direction === "desc" ? "asc" : column === "symbol" ? "asc" : "desc" }));
446
+ }}
447
+ selection={{
448
+ kind: "index",
449
+ selectedIndex: selected ? rows.indexOf(selected) : -1,
450
+ onChange: (_index, row) => { if (row.kind === "report") setSelectedKey(row.key); },
451
+ }}
452
+ onActivate={(row) => { if (row.kind === "report") setOpenKey(row.key); }}
453
+ onRootKeyDown={handleKeyDown}
454
+ detailOpen={!!open}
455
+ onBack={() => setOpenKey(null)}
456
+ detailTitle={open ? `${open.symbol} · ${open.name}` : undefined}
457
+ detailContent={history}
458
+ emptyStateTitle={emptyStateTitle}
459
+ />
469
460
  );
470
461
  }
471
462