@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
@@ -9,7 +9,7 @@ import type {
9
9
  HeadlessRowsResult,
10
10
  } from "../../../types/plugin";
11
11
  import type { EarningsEvent } from "../../../types/data-provider";
12
- import { addDays, boardReport, fallbackReport, MIN_AVERAGE_REPORTS, newYorkToday } from "./board-model";
12
+ import { addDays, boardReport, fallbackReport, MIN_AVERAGE_REPORTS, newYorkToday, type BoardReport } from "./board-model";
13
13
  import { BOARD_WEEKS, boardRangeDays, boardRangeLabel, isBoardWeek, requestedBoardRange, type BoardRange } from "./calendar-model";
14
14
  import { fetchEarningsCalendar, fetchEarningsHistory } from "./client";
15
15
  import { loadEarningsCalendar } from "./data/cache";
@@ -104,6 +104,41 @@ function boardTickerNotice(symbol: string, board: ReadonlyArray<{ symbol: string
104
104
  };
105
105
  }
106
106
 
107
+ /** Two dates this close are one report under the per-ticker calendar and the stored board. */
108
+ const SAME_REPORT_DAYS = 3;
109
+ const eventDay = (event: EarningsEvent) => event.earningsDate.toISOString().slice(0, 10);
110
+ const dayGap = (left: string, right: string) => Math.abs(Date.parse(`${left}T00:00:00Z`) - Date.parse(`${right}T00:00:00Z`)) / 86_400_000;
111
+
112
+ /**
113
+ * The stored board's own report for each named company whose per-ticker
114
+ * calendar still names a report gone by (META the week after it reported),
115
+ * read over those days as `ERN --date` reads them: the estimate it was
116
+ * measured against, its analysts, the actual, its basis and the cap, as the
117
+ * date board and `ERN <ticker>` show them. One the board does not have is
118
+ * left to the per-ticker calendar.
119
+ */
120
+ async function storedPastReports(
121
+ events: readonly EarningsEvent[],
122
+ today: string,
123
+ context: HeadlessPaneContext,
124
+ deps: EarningsHeadlessDependencies,
125
+ ): Promise<Map<EarningsEvent, BoardReport>> {
126
+ const past = events.filter((event) => eventDay(event) < today);
127
+ const found = new Map<EarningsEvent, BoardReport>();
128
+ if (!past.length) return found;
129
+ const from = addDays(past.map(eventDay).sort()[0]!, -SAME_REPORT_DAYS);
130
+ const symbols = [...new Set(past.map((event) => event.symbol))];
131
+ const payload = await deps.calendar({ from, to: addDays(today, -1), perDay: 0, symbols }, context)
132
+ .catch(logFailure("earnings.headless", "Read the board's past reports for named companies failed", null));
133
+ for (const event of past) {
134
+ const nearest = (payload?.reports ?? [])
135
+ .filter((report) => report.symbol === event.symbol && dayGap(report.date, eventDay(event)) <= SAME_REPORT_DAYS)
136
+ .sort((left, right) => dayGap(left.date, eventDay(event)) - dayGap(right.date, eventDay(event)))[0];
137
+ if (nearest) found.set(event, boardReport(nearest, NO_OWNERSHIP));
138
+ }
139
+ return found;
140
+ }
141
+
107
142
  async function boardRows(args: HeadlessPaneLoadArgs, context: HeadlessPaneContext, deps: EarningsHeadlessDependencies, highlight?: string, range: BoardRange = ""): Promise<HeadlessRowsResult> {
108
143
  const today = newYorkToday();
109
144
  const named = args.symbols;
@@ -124,9 +159,10 @@ async function boardRows(args: HeadlessPaneLoadArgs, context: HeadlessPaneContex
124
159
  const payload = cloud.status === "fulfilled" ? cloud.value : null;
125
160
  const reports = (payload?.reports ?? []).map((report) => boardReport(report, NO_OWNERSHIP));
126
161
  const covered = new Set(reports.map((report) => report.symbol));
127
- const extra = fallback.status === "fulfilled"
128
- ? fallback.value.filter((event) => !covered.has(event.symbol)).map((event) => fallbackReport(event, NO_OWNERSHIP, today))
129
- : [];
162
+ const uncovered = fallback.status === "fulfilled" ? fallback.value.filter((event) => !covered.has(event.symbol)) : [];
163
+ // The days ahead leave out a report just gone by, which the stored board has as the date board shows it.
164
+ const stored = named.length && !range ? await storedPastReports(uncovered, today, context, deps) : new Map<EarningsEvent, BoardReport>();
165
+ const extra = uncovered.map((event) => stored.get(event) ?? fallbackReport(event, NO_OWNERSHIP, today));
130
166
  const limit = Number(args.options.limit ?? 200);
131
167
  const all = [...reports, ...extra].sort((left, right) => left.date.localeCompare(right.date) || (right.marketCap ?? 0) - (left.marketCap ?? 0));
132
168
  const rows = all.slice(0, limit).map((report) => ({
@@ -2,40 +2,31 @@ import type { FundMember } from "../../../api-client/members";
2
2
  import type { Portfolio, Watchlist } from "../../../types/ticker";
3
3
  import { getSectorCollection } from "../sectors/sector-data";
4
4
 
5
- /**
6
- * The fund whose holdings name each company's GICS sector: the S&P 500's, as
7
- * MEMB reads it. A Select Sector SPDR holds the S&P 500's companies of one
8
- * sector, so its members are these holdings in that sector.
9
- */
10
- export const SECTOR_HOLDINGS_FUND = "IVV";
5
+ /** Focused industries, named by the fund whose published holdings define the group. */
6
+ const INDUSTRIES = [
7
+ { etf: "SOXX", name: "Semiconductors" },
8
+ { etf: "XPH", name: "Pharmaceuticals" },
9
+ { etf: "XBI", name: "Biotech" },
10
+ { etf: "ITA", name: "Aerospace & defense" },
11
+ { etf: "KRE", name: "Regional banks" },
12
+ { etf: "IGV", name: "Software" },
13
+ { etf: "XRT", name: "Retail" },
14
+ { etf: "XHB", name: "Homebuilders" },
15
+ { etf: "XOP", name: "Oil & gas exploration" },
16
+ ] as const;
11
17
 
12
- /** The GICS sector each of SECT's sector funds holds, as the S&P 500's holdings name it. */
13
- const SECTOR_OF_FUND: Readonly<Record<string, readonly string[]>> = {
14
- XLK: ["Information Technology"],
15
- XLV: ["Health Care"],
16
- XLF: ["Financials"],
17
- XLY: ["Consumer Discretionary"],
18
- // The holdings file says "Communication"; GICS's own name is the longer one.
19
- XLC: ["Communication", "Communication Services"],
20
- XLI: ["Industrials"],
21
- XLP: ["Consumer Staples"],
22
- XLE: ["Energy"],
23
- XLU: ["Utilities"],
24
- XLRE: ["Real Estate"],
25
- XLB: ["Materials"],
26
- };
27
-
28
- /** Which companies ERN lists: the market's, the user's, one portfolio or watchlist, or one sector fund's. */
18
+ /** Which companies ERN lists: the market, the user's lists, or a fund's holdings. */
29
19
  export type EarningsScope =
30
20
  | { kind: "all" }
31
21
  | { kind: "mine" }
32
22
  | { kind: "portfolio" | "watchlist"; id: string; name: string }
33
- | { kind: "sector"; etf: string; name: string; sectors: readonly string[] };
23
+ | { kind: "fund"; etf: string; name: string };
34
24
 
35
25
  export interface EarningsScopeOption {
36
26
  value: string;
37
27
  label: string;
38
28
  description?: string;
29
+ group?: string;
39
30
  scope: EarningsScope;
40
31
  }
41
32
 
@@ -47,30 +38,28 @@ export const MINE_SCOPE = "mine";
47
38
  const kindNote = (name: string, kind: "Portfolio" | "Watchlist") =>
48
39
  name.toLowerCase().includes(kind.toLowerCase()) ? undefined : kind;
49
40
 
50
- /**
51
- * The choices of ERN's Names filter, in menu order: the market, every held or
52
- * watched name, each portfolio and watchlist, then SECT's sector funds, each
53
- * named with its fund ("Technology (XLK)").
54
- */
41
+ /** Personal lists, broad sectors and focused industries stay separate in the Names menu. */
55
42
  export function earningsScopeOptions(collections: { portfolios: readonly Portfolio[]; watchlists: readonly Watchlist[] }): EarningsScopeOption[] {
56
43
  return [
57
44
  { value: ALL_SCOPE, label: "All", scope: { kind: "all" } },
58
45
  { value: MINE_SCOPE, label: "Mine", description: "Portfolios and watchlists", scope: { kind: "mine" } },
59
46
  ...collections.portfolios.map((portfolio): EarningsScopeOption => ({
60
- value: `portfolio:${portfolio.id}`, label: portfolio.name, description: kindNote(portfolio.name, "Portfolio"),
47
+ value: `portfolio:${portfolio.id}`, label: portfolio.name, description: kindNote(portfolio.name, "Portfolio"), group: "Your lists",
61
48
  scope: { kind: "portfolio", id: portfolio.id, name: portfolio.name },
62
49
  })),
63
50
  ...collections.watchlists.map((watchlist): EarningsScopeOption => ({
64
- value: `watchlist:${watchlist.id}`, label: watchlist.name, description: kindNote(watchlist.name, "Watchlist"),
51
+ value: `watchlist:${watchlist.id}`, label: watchlist.name, description: kindNote(watchlist.name, "Watchlist"), group: "Your lists",
65
52
  scope: { kind: "watchlist", id: watchlist.id, name: watchlist.name },
66
53
  })),
67
- ...getSectorCollection("sectors").items.flatMap((sector): EarningsScopeOption[] => {
68
- const sectors = SECTOR_OF_FUND[sector.etf];
69
- return sectors ? [{
70
- value: `sector:${sector.etf}`, label: `${sector.name} (${sector.etf})`,
71
- scope: { kind: "sector", etf: sector.etf, name: sector.name, sectors },
72
- }] : [];
73
- }),
54
+ ...getSectorCollection("sectors").items.map((sector): EarningsScopeOption => ({
55
+ // Keep the saved sector choice when switching to that fund's own holdings.
56
+ value: `sector:${sector.etf}`, label: `${sector.name} (${sector.etf})`, group: "Sectors",
57
+ scope: { kind: "fund", etf: sector.etf, name: sector.name },
58
+ })),
59
+ ...INDUSTRIES.map((industry): EarningsScopeOption => ({
60
+ value: `industry:${industry.etf}`, label: `${industry.name} (${industry.etf})`, group: "Industries",
61
+ scope: { kind: "fund", etf: industry.etf, name: industry.name },
62
+ })),
74
63
  ];
75
64
  }
76
65
 
@@ -79,11 +68,10 @@ export function readEarningsScope(value: string, options: readonly EarningsScope
79
68
  return options.find((option) => option.value === value) ?? options[0]!;
80
69
  }
81
70
 
82
- /** A sector fund's companies: the S&P 500's holdings in its sector, largest weight first. */
83
- export function sectorSymbols(members: readonly FundMember[], sectors: readonly string[]): string[] {
84
- const symbols = members
85
- .filter((member) => member.symbol && member.sector && sectors.includes(member.sector))
71
+ /** A fund's companies, largest weight first; cash and other unlisted holdings have no symbol. */
72
+ export function fundSymbols(members: readonly FundMember[]): string[] {
73
+ const symbols = [...members]
86
74
  .sort((left, right) => (right.weight ?? 0) - (left.weight ?? 0))
87
- .map((member) => member.symbol!.trim().toUpperCase());
75
+ .flatMap((member) => member.symbol?.trim() ? [member.symbol.trim().toUpperCase()] : []);
88
76
  return [...new Set(symbols)];
89
77
  }
@@ -29,7 +29,7 @@ import { usePluginPaneState } from "../../runtime";
29
29
  import { newYorkToday } from "../earnings/board-model";
30
30
  import { ProWall, SignInWall } from "../cloud/auth-actions";
31
31
  import { cachedUpcomingCalls, calendarAway, loadUpcomingCalls, statusOf, upcomingCutNotice, type UpcomingCallsResult } from "./data";
32
- import { formatPeriod } from "./format";
32
+ import { callPeriod } from "./format";
33
33
  import {
34
34
  calendarDays,
35
35
  calendarSections,
@@ -106,7 +106,7 @@ function renderCalendarCell(row: CalendarRow, column: CalendarColumn, now: numbe
106
106
  return clock ? { text: clock, color: tone(colors.textDim) } : { text: MISSING, color: colors.textMuted };
107
107
  }
108
108
  case "period":
109
- return { text: formatPeriod(call.fiscalYear, call.fiscalQuarter), color: tone(colors.textDim) };
109
+ return { text: callPeriod(call), color: tone(colors.textDim) };
110
110
  case "away":
111
111
  return { text: calendarAway(call, now), color: over ? colors.textMuted : colors.textBright };
112
112
  }
@@ -7,10 +7,12 @@ import {
7
7
  } from "../../../api-client";
8
8
  import { ApiRequestError } from "../../../api-client/errors";
9
9
  import { listingAbroad, type IssuerListingParams, type ListingAbroad } from "../../../api-client/paths";
10
+ import { servedFromSavedCopy } from "../../../data/saved-copies";
10
11
  import { areDifferentCompanies } from "../../../sources/sec-registrant";
11
12
  import type { HeadlessPaneApiClient, PluginPersistence } from "../../../types/plugin";
12
13
  import { shortDate } from "../earnings/format";
13
14
  import { callCountdown, callDay, callTimeKnown, isUpcomingCall } from "./upcoming";
15
+ import { type CompanyFrom, readCompanyFrom } from "../../../types/company-from";
14
16
 
15
17
  type EarningsCallsApiClient = Pick<
16
18
  HeadlessPaneApiClient,
@@ -50,6 +52,8 @@ export interface EarningsCallsResult {
50
52
  pending?: boolean;
51
53
  /** The symbol is not one the SEC knows, so there is nothing to look for. */
52
54
  unknownTicker?: boolean;
55
+ /** The company's home line the calls are read from, when not the listing asked. */
56
+ companyFrom?: CompanyFrom | null;
53
57
  /** Maximum rows requested from the server, which supplies no total/hasMore. */
54
58
  sourceLimit?: number;
55
59
  /** The response filled its request; additional calls may exist. */
@@ -216,8 +220,15 @@ export async function loadEarningsCallsWithClient(
216
220
  ...(offset > 0 ? { offset } : {}),
217
221
  })
218
222
  .then((payload) => {
219
- const calls = listingCalls(payload.calls ?? [], abroad);
220
- const value = { calls, pending: payload.pending === true, unknownTicker: payload.unknownTicker === true };
223
+ // Calls the service read from the company's home line are the company's, whatever the line's own name.
224
+ const companyFrom = readCompanyFrom(payload.companyFrom);
225
+ const calls = companyFrom ? payload.calls ?? [] : listingCalls(payload.calls ?? [], abroad);
226
+ const value = {
227
+ calls,
228
+ pending: payload.pending === true,
229
+ unknownTicker: payload.unknownTicker === true,
230
+ ...(companyFrom ? { companyFrom } : {}),
231
+ };
221
232
  // A list with calls still being produced changes by the minute, so it
222
233
  // is not worth keeping; a list of finished transcripts is.
223
234
  const settled = !value.pending && calls.every((call) => call.hasTranscript);
@@ -250,14 +261,15 @@ export async function loadEarningsCallsWithClient(
250
261
  allowExpired: true,
251
262
  });
252
263
  if (expired && errorStatus !== 401 && errorStatus !== 402 && errorStatus !== 403) {
253
- return {
264
+ // A command that shows it ends with when the copy was saved, not with the failure.
265
+ return servedFromSavedCopy({
254
266
  ...expired.value,
255
267
  ...sourceScope(expired.value.calls),
256
268
  fetchedAt: expired.fetchedAt,
257
269
  stale: true,
258
270
  refreshError,
259
271
  errorStatus,
260
- };
272
+ }, expired.fetchedAt);
261
273
  }
262
274
  throw error;
263
275
  })
@@ -356,11 +368,11 @@ export async function loadUpcomingCallsWithClient(
356
368
  // Losing Pro access re-gates the calendar, as it does the list.
357
369
  const expired = read(true);
358
370
  if (expired && errorStatus !== 401 && errorStatus !== 402 && errorStatus !== 403) {
359
- return {
371
+ return servedFromSavedCopy({
360
372
  ...upcomingFromCache(expired.value, expired.fetchedAt, true),
361
373
  refreshError: error instanceof Error ? error.message : String(error),
362
374
  errorStatus,
363
- };
375
+ }, expired.fetchedAt);
364
376
  }
365
377
  throw error;
366
378
  })
@@ -2,7 +2,7 @@ import type { CloudEarningsCallPayload } from "../../../api-client";
2
2
  import { formatShortDate } from "../../../utils/datetime-format";
3
3
 
4
4
  /** "FQ4 26" — fiscal period, compact enough for a table column. */
5
- export function formatPeriod(
5
+ function formatPeriod(
6
6
  fiscalYear: number | null,
7
7
  fiscalQuarter: number | null,
8
8
  ): string {
@@ -36,8 +36,37 @@ export function formatTimestamp(seconds: number): string {
36
36
  return `${minutes}:${String(remainder).padStart(2, "0")}`;
37
37
  }
38
38
 
39
+ type NamedCall = Pick<
40
+ CloudEarningsCallPayload,
41
+ "ticker" | "fiscalYear" | "fiscalQuarter" | "eventKind" | "eventLabel"
42
+ >;
43
+
44
+ /** An event that is not an earnings call (a deal call, an investor day), shown by its label and never as a quarter. */
45
+ export function eventLabelOf(call: Omit<NamedCall, "ticker">): string | null {
46
+ return call.eventKind === "other" && call.eventLabel ? call.eventLabel : null;
47
+ }
48
+
49
+ // Fits the period column; the full label is in the detail title.
50
+ const SHORT_EVENT_LABELS: Record<string, string> = {
51
+ "M&A call": "M&A",
52
+ conference: "Conf",
53
+ "investor day": "Inv day",
54
+ "business update": "Update",
55
+ "trial results": "Trial",
56
+ "shareholder call": "Holders",
57
+ };
58
+
59
+ /** The period column: "FQ4 26", or a short event label ("M&A", "Inv day"). */
60
+ export function callPeriod(call: Omit<NamedCall, "ticker">): string {
61
+ const label = eventLabelOf(call);
62
+ if (label) return SHORT_EVENT_LABELS[label] ?? label;
63
+ return formatPeriod(call.fiscalYear, call.fiscalQuarter);
64
+ }
65
+
39
66
  /** Title for the detail stack: names the call so the body need not repeat it. */
40
- export function callTitle(call: CloudEarningsCallPayload): string {
67
+ export function callTitle(call: NamedCall): string {
68
+ const label = eventLabelOf(call);
69
+ if (label) return `${call.ticker} ${label}`;
41
70
  const period = formatPeriod(call.fiscalYear, call.fiscalQuarter);
42
71
  return period === "—" ? call.ticker : `${call.ticker} ${period}`;
43
72
  }
@@ -23,7 +23,7 @@ import {
23
23
  type UpcomingCallsResult,
24
24
  type UpcomingDays,
25
25
  } from "./data";
26
- import { formatPeriod } from "./format";
26
+ import { callPeriod, eventLabelOf } from "./format";
27
27
  import type { MarketDay } from "../earnings/board-model";
28
28
  import {
29
29
  calendarDays,
@@ -46,6 +46,10 @@ import {
46
46
  type TranscriptSection,
47
47
  } from "./model";
48
48
  import { REPORT_FILTER, REPORT_PAGE, REPORT_ROWS } from "../shared/headless-options";
49
+ import { companyDataNotes } from "../../../types/company-from";
50
+ import { savedCopyMark, unansweredRefreshError } from "../../../data/saved-copies";
51
+
52
+ const optionalErrors = (error: string | null) => error ? [error] : undefined;
49
53
 
50
54
  const CALL_COLUMNS = [
51
55
  { key: "ticker", header: "Ticker" },
@@ -53,6 +57,7 @@ const CALL_COLUMNS = [
53
57
  { key: "callAt", header: "Call date" },
54
58
  { key: "fiscalYear", header: "FY", align: "right" as const },
55
59
  { key: "fiscalQuarter", header: "FQ", align: "right" as const },
60
+ { key: "event", header: "Event" },
56
61
  { key: "status", header: "Status" },
57
62
  { key: "durationSeconds", header: "Seconds", align: "right" as const },
58
63
  { key: "wordCount", header: "Words", align: "right" as const },
@@ -124,6 +129,8 @@ function callRow(call: CloudEarningsCallPayload, now: number) {
124
129
  callAt: call.callAt,
125
130
  fiscalYear: call.fiscalYear,
126
131
  fiscalQuarter: call.fiscalQuarter,
132
+ // A deal call, a conference, an investor day: not a quarter's earnings call.
133
+ event: eventLabelOf(call),
127
134
  status: call.hasTranscript ? "transcribed" : callStatusLabel(call, now) || call.status,
128
135
  upcoming,
129
136
  in: upcoming ? callCountdown(call, now) : null,
@@ -167,9 +174,12 @@ function projectEarningsCallsHeadless(
167
174
  columns: CALL_COLUMNS,
168
175
  rows,
169
176
  ...(next ? { textRows: rows.filter((row) => row.id !== next.id) } : {}),
170
- errors: result.refreshError ? [result.refreshError] : undefined,
177
+ errors: optionalErrors(unansweredRefreshError(result)),
171
178
  ...(sourceLimitReached || result.pending ? { complete: false } : {}),
179
+ ...(result.companyFrom ? { notes: companyDataNotes(result.companyFrom) } : {}),
172
180
  metadata: {
181
+ // A list served from a saved copy ends with when it was saved, instead of its refresh failure.
182
+ ...savedCopyMark(result),
173
183
  ...(next ? { notices: [nextCallNotice(next, now)], nextCall: { id: next.id, callAt: next.callAt, timeKnown: callTimeKnown(next), in: callCountdown(next, now) } } : {}),
174
184
  fetchedAt: result.fetchedAt,
175
185
  stale: result.stale,
@@ -208,9 +218,11 @@ function transcriptResult(
208
218
  return {
209
219
  columns: TRANSCRIPT_COLUMNS,
210
220
  rows,
211
- errors: calls.refreshError ? [calls.refreshError] : undefined,
221
+ errors: optionalErrors(unansweredRefreshError(calls)),
212
222
  ...(calls.stale || calls.pending ? { complete: false } : {}),
223
+ ...(calls.companyFrom ? { notes: companyDataNotes(calls.companyFrom) } : {}),
213
224
  metadata: {
225
+ ...savedCopyMark(calls),
214
226
  stale: calls.stale,
215
227
  callListFetchedAt: calls.fetchedAt,
216
228
  callListPending: calls.pending ?? false,
@@ -219,6 +231,7 @@ function transcriptResult(
219
231
  company: transcript.companyName,
220
232
  fiscalYear: transcript.fiscalYear,
221
233
  fiscalQuarter: transcript.fiscalQuarter,
234
+ event: eventLabelOf(transcript),
222
235
  callAt: transcript.callAt,
223
236
  durationSeconds: transcript.durationSeconds,
224
237
  wordCount: transcript.wordCount,
@@ -259,7 +272,8 @@ function projectCalendar(
259
272
  timeKnown: callTimeKnown(call),
260
273
  local: callLocalClock(call),
261
274
  newYork: callNewYorkClock(call),
262
- period: formatPeriod(call.fiscalYear, call.fiscalQuarter),
275
+ period: callPeriod(call),
276
+ event: eventLabelOf(call),
263
277
  fiscalYear: call.fiscalYear,
264
278
  fiscalQuarter: call.fiscalQuarter,
265
279
  in: calendarAway(call, now),
@@ -278,10 +292,11 @@ function projectCalendar(
278
292
  columns: CALENDAR_COLUMNS,
279
293
  textColumns: sameClock ? CALENDAR_TEXT_COLUMNS.filter((column) => column.key !== "local") : CALENDAR_TEXT_COLUMNS,
280
294
  rows,
281
- errors: result.refreshError ? [result.refreshError] : undefined,
295
+ errors: optionalErrors(unansweredRefreshError(result)),
282
296
  ...(result.stale || result.truncated ? { complete: false } : {}),
283
297
  empty: "No calls on the calendar these days.",
284
298
  metadata: {
299
+ ...savedCopyMark(result),
285
300
  view: "upcoming",
286
301
  fetchedAt: result.fetchedAt,
287
302
  stale: result.stale,
@@ -3,6 +3,7 @@ import type {
3
3
  CloudEarningsTranscriptPayload,
4
4
  CloudTranscriptTurnPayload,
5
5
  } from "../../../api-client";
6
+ import { eventLabelOf } from "./format";
6
7
  import { splitParagraphs } from "./prose";
7
8
 
8
9
  export type TranscriptSection =
@@ -210,7 +211,11 @@ export function findCallForQuarter(
210
211
  ): CloudEarningsCallPayload | null {
211
212
  const sorted = sortCallsNewest(calls);
212
213
  const token = requestedQuarter.trim().toUpperCase().replace(/[\s_-]+/g, "");
213
- if (!token || token === "LATEST") return sorted[0] ?? null;
214
+ // The latest earnings call: a deal call or an investor day held after it is
215
+ // not the quarter's call, unless the company has nothing else.
216
+ if (!token || token === "LATEST") {
217
+ return sorted.find((call) => !eventLabelOf(call)) ?? sorted[0] ?? null;
218
+ }
214
219
 
215
220
  const quarterFirst = token.match(/^(?:F?Q)?([1-4])(?:\/?(\d{2}|\d{4}))?$/);
216
221
  if (quarterFirst) {
@@ -19,6 +19,7 @@ import {
19
19
  type QueryBarView,
20
20
  type StatItem,
21
21
  } from "../../../components";
22
+ import { useCompanyDataFooter } from "../../../components/layout/pane/status-footer";
22
23
  import { handleRefreshKey } from "../../../components/data-table/table-pane";
23
24
  import { AudioPlayer, useCanPlayAudio } from "../../../components/ui";
24
25
  import { useShortcut } from "../../../react/input";
@@ -49,7 +50,7 @@ import {
49
50
  loadTranscript,
50
51
  statusOf,
51
52
  } from "./data";
52
- import { callTitle, formatCallDate, formatDuration, formatPeriod, formatSentiment } from "./format";
53
+ import { callPeriod, callTitle, eventLabelOf, formatCallDate, formatDuration, formatSentiment } from "./format";
53
54
  import { findCallForQuarter, findInLines, MIN_FIND_LENGTH, turnParagraphs } from "./model";
54
55
  import { READER_TABS, TranscriptView, type ReaderTab, type TranscriptPlayback } from "./transcript-view";
55
56
  import { PLAYBACK_RATES, SEEK_STEP_SECONDS, useCallPlayback } from "./use-call-playback";
@@ -101,7 +102,7 @@ function renderCell(call: CloudEarningsCallPayload, column: CallColumn, now: num
101
102
  case "date":
102
103
  return { text: formatCallDate(call.callAt), color: colors.textDim };
103
104
  case "period":
104
- return { text: formatPeriod(call.fiscalYear, call.fiscalQuarter) };
105
+ return { text: callPeriod(call) };
105
106
  case "length":
106
107
  return { text: formatDuration(call.durationSeconds), color: colors.textDim };
107
108
  case "sentiment": {
@@ -277,6 +278,8 @@ function CallsListView({
277
278
  const [listStatus, setListStatus] = useState<"idle" | "loading" | "loaded" | "error">(initialShelf ? "loaded" : "idle");
278
279
  const [listError, setListError] = useState<{ message: string; status?: number } | null>(null);
279
280
  const [stale, setStale] = useState(initialShelf?.stale === true);
281
+ // The company's home line the shelf is read from, for a receipt or a foreign line.
282
+ const [companyFrom, setCompanyFrom] = useState(initialShelf?.companyFrom ?? null);
280
283
  const [listFetchedAt, setListFetchedAt] = useState(initialShelf?.fetchedAt ?? 0);
281
284
  const listRequestVersion = useRef(0);
282
285
  const [listCompletedRequest, setListCompletedRequest] = useState(0);
@@ -340,6 +343,7 @@ function CallsListView({
340
343
  setNextOffset(result.calls.length);
341
344
  setHasMore(result.sourceLimitReached === true);
342
345
  setStale(result.stale);
346
+ setCompanyFrom(result.companyFrom ?? null);
343
347
  setListFetchedAt(result.fetchedAt);
344
348
  setListPending(result.pending === true && result.calls.length === 0);
345
349
  setListError(
@@ -410,6 +414,7 @@ function CallsListView({
410
414
  setListStatus(held ? "loaded" : "idle");
411
415
  setListPending(held?.pending === true && held.calls.length === 0);
412
416
  setStale(held?.stale === true);
417
+ setCompanyFrom(held?.companyFrom ?? null);
413
418
  setListFetchedAt(held?.fetchedAt ?? 0);
414
419
  setListError(null);
415
420
  setLookup(null);
@@ -525,7 +530,8 @@ function CallsListView({
525
530
  [
526
531
  call.ticker,
527
532
  call.companyName ?? "",
528
- formatPeriod(call.fiscalYear, call.fiscalQuarter),
533
+ callPeriod(call),
534
+ eventLabelOf(call) ?? "",
529
535
  formatCallDate(call.callAt),
530
536
  ]
531
537
  .join(" ")
@@ -816,6 +822,8 @@ function CallsListView({
816
822
  [focusSearch, scrollTranscriptBy, openSource, playerShown, playback.toggle, playback.seekBy],
817
823
  );
818
824
 
825
+ // A receipt's or foreign line's calls are its company's, read from the home line.
826
+ useCompanyDataFooter(companyFrom, !readerOpen);
819
827
  usePaneFooter(
820
828
  footerId,
821
829
  () => {
@@ -1020,7 +1028,7 @@ function CallsListView({
1020
1028
  <AudioPlayer
1021
1029
  src={playback.audio.url}
1022
1030
  sourceKey={selected.id}
1023
- label={`${callTitle(selected)} call recording`}
1031
+ label={`${callTitle(selected)}${eventLabelOf(selected) ? "" : " call"} recording`}
1024
1032
  durationSeconds={playback.audio.durationSeconds}
1025
1033
  rates={PLAYBACK_RATES}
1026
1034
  rate={playback.rate}
@@ -1,7 +1,7 @@
1
1
  import { createPluginCache } from "../../../data/plugin-cache";
2
2
  import type { DataTableColumn } from "../../../components";
3
3
  import { colors } from "../../../theme/colors";
4
- import { displayWidth } from "../../../utils/format";
4
+ import { displayWidth, MISSING_VALUE } from "../../../utils/format";
5
5
  import { fetchEconCalendar } from "./calendar-source";
6
6
  import { parseEconFigure } from "./figures";
7
7
  import { isAllDayEvent, type EconEvent, type EconImpact } from "./types";
@@ -235,6 +235,51 @@ export function eventCountry(event: Pick<EconEvent, "country" | "event">): strin
235
235
  return euroAreaMember(event) ?? event.country;
236
236
  }
237
237
 
238
+ /** What the feeds call the Fed's rate decision. */
239
+ const FED_RATE_DECISION_NAMES = new Set(["federal funds rate", "fed interest rate decision"]);
240
+ /** A figure that is a range (3.75-4.00%) rather than one level. */
241
+ const RANGE_FIGURE = /\d\s*[-\u2013]\s*\d/;
242
+ const FED_FUNDS_UPPER_BOUND = "Federal Funds Rate (upper bound)";
243
+
244
+ /**
245
+ * A row's name as the calendar shows it. The Fed's decision is figured as one
246
+ * level, the top of the target range (4.00% for 3.75-4.00%), so it is named
247
+ * for that bound rather than read as the range or the effective rate; a row
248
+ * whose figures are the range keeps the feed's name. Whatever matches rows by
249
+ * name (alerts, the FRED history) reads the feed's own, `event.event`.
250
+ */
251
+ export function econEventName(event: Pick<EconEvent, "country" | "event" | "actual" | "forecast" | "prior">): string {
252
+ if ((event.country !== "US" && event.country !== "USD") || !FED_RATE_DECISION_NAMES.has(event.event.trim().toLowerCase())) return event.event;
253
+ return [event.actual, event.forecast, event.prior].some((figure) => !!figure && RANGE_FIGURE.test(figure)) ? event.event : FED_FUNDS_UPPER_BOUND;
254
+ }
255
+
256
+ /** Letters and digits only, lower case: "Non-Farm" and "nonfarm" read alike. */
257
+ const compactName = (text: string) => text.toLowerCase().replace(/[^a-z0-9]+/g, "");
258
+
259
+ /**
260
+ * Whether a row's name carries every word of `query`, case, spaces and
261
+ * punctuation aside: `nonfarm` finds Non Farm Payrolls and Non-Farm
262
+ * Employment Change, `fed funds` the Federal Funds Rate. The name shown and the
263
+ * feed's own are both read.
264
+ */
265
+ export function matchesEventSearch(event: Pick<EconEvent, "country" | "event" | "actual" | "forecast" | "prior">, query: string): boolean {
266
+ const words = query.split(/\s+/).map(compactName).filter(Boolean);
267
+ if (words.length === 0) return true;
268
+ const names = [event.event, econEventName(event)].map(compactName);
269
+ return names.some((name) => words.every((word) => name.includes(word)));
270
+ }
271
+
272
+ /**
273
+ * A release still to come that has a prior and no forecast: the consensus is
274
+ * not in yet, which the calendar draws as a dash.
275
+ */
276
+ export function awaitsConsensus(event: Pick<EconEvent, "actual" | "forecast" | "prior">): boolean {
277
+ return !event.actual?.trim() && !event.forecast?.trim() && !!event.prior?.trim();
278
+ }
279
+
280
+ /** What a dash under Forecast means on a row still to come. */
281
+ export const NO_CONSENSUS_NOTE = `${MISSING_VALUE} forecast = no consensus yet`;
282
+
238
283
  /** A national euro-area release matches EU (and G7, as EU) and its own member's code. */
239
284
  export function matchesCountry(event: Pick<EconEvent, "country" | "event">, filter: CountryFilter): boolean {
240
285
  const member = euroAreaMember(event);
@@ -376,6 +421,8 @@ export function subscribeCalendar(listener: () => void): () => void {
376
421
  }
377
422
 
378
423
  export interface EconCalendarLoadResult extends EconCalendarCacheEntry {
424
+ /** `stale-fallback`: the copy an earlier read saved, answering because its refresh failed. */
425
+ source?: "cache" | "network" | "stale-fallback";
379
426
  refreshError?: string;
380
427
  }
381
428
 
@@ -1,5 +1,6 @@
1
1
  import { apiClient, type CloudEconEventPayload } from "../../../api-client";
2
2
  import { ApiRequestError, isPermanentClientError } from "../../../api-client/errors";
3
+ import { withPercentUnits } from "./figures";
3
4
  import type { EconEvent } from "./types";
4
5
 
5
6
  function toEconEvent(event: CloudEconEventPayload): EconEvent {
@@ -9,9 +10,10 @@ function toEconEvent(event: CloudEconEventPayload): EconEvent {
9
10
  };
10
11
  }
11
12
 
13
+ /** The calendar, a percent indicator's figures in percent on every row (`withPercentUnits`). */
12
14
  export async function fetchEconCalendar(): Promise<EconEvent[]> {
13
15
  const events = await apiClient.getCloudEconomicCalendar();
14
- return events.map(toEconEvent);
16
+ return withPercentUnits(events.map(toEconEvent));
15
17
  }
16
18
 
17
19
  /** The stored calendar from a day, or why it was not read so the caller reads the current calendar instead. */
@@ -40,7 +42,7 @@ export async function fetchEconCalendarFrom(from: string): Promise<EconCalendarF
40
42
  if (!payload || !Array.isArray(payload.events) || !(payload.storedFrom === null || /^\d{4}-\d{2}-\d{2}$/.test(String(payload.storedFrom)))) {
41
43
  return { events: null, failure: "the server returned an unreadable calendar" };
42
44
  }
43
- return { events: payload.events.map(toEconEvent), storedFrom: payload.storedFrom };
45
+ return { events: withPercentUnits(payload.events.map(toEconEvent)), storedFrom: payload.storedFrom };
44
46
  } catch (error) {
45
47
  if (error instanceof ApiRequestError && error.status === 404) return { events: null, failure: null };
46
48
  if (isPermanentClientError(error)) throw error;
@@ -31,7 +31,7 @@ import { Box, Text } from "../../../ui";
31
31
  import { displayWidth, truncateToDisplayWidth } from "../../../utils/format";
32
32
  import { useOpenTickerChoice } from "../shared/ticker-choice";
33
33
  import { resolveFredMapping, projectFredHistory, fredHistoryUnits } from "./fred-series-map";
34
- import { actualColor, CALENDAR_TIME_ZONE_LABEL, timeLabel } from "./calendar-model";
34
+ import { actualColor, CALENDAR_TIME_ZONE_LABEL, econEventName, timeLabel } from "./calendar-model";
35
35
  import { hasNoStatedTime, isAllDayEvent, type EconEvent } from "./types";
36
36
 
37
37
  interface EconDetailViewProps {
@@ -238,7 +238,7 @@ export function EconDetailView({ event, width, height, focused }: EconDetailView
238
238
  const rows = useMemo<HistoryRow[]>(() => projected
239
239
  .map((obs, index) => ({ date: obs.date, value: obs.value, change: index > 0 ? obs.value - projected[index - 1]!.value : null }))
240
240
  .reverse(), [projected]);
241
- const label = event.event;
241
+ const label = econEventName(event);
242
242
  const series = useMemo(() => [staticSeries(
243
243
  projected.map((obs) => scalarPoint(new Date(`${obs.date}T00:00:00Z`), obs.value)),
244
244
  { id: "fred-history", label, color: colors.positive, calendarSpaced: true },