@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,8 @@
1
+ import type { CompanyFrom } from "../types/company-from";
1
2
  import { logFailure } from "../utils/log-failure";
2
3
  import type { NewsCapability } from "../capabilities";
3
4
  import type { ConnectionHealthRegistry } from "../core/connection-health";
4
- import type { NewsArticle, NewsQuery, NewsQueryState } from "./types";
5
+ import type { NewsAccess, NewsArticle, NewsQuery, NewsQueryState } from "./types";
5
6
  import { isAppVisible, subscribeAppVisibility } from "../state/app/activity";
6
7
  import {
7
8
  DEFAULT_GLOBAL_QUERY,
@@ -28,6 +29,18 @@ export interface NewsServiceOptions {
28
29
  connectionHealth?: ConnectionHealthRegistry;
29
30
  /** Defaults to the app's visibility; injectable for tests. */
30
31
  visibility?: { isVisible(): boolean; subscribe(listener: () => void): () => void };
32
+ /**
33
+ * The session the sources read news with. Its key changes when what the
34
+ * service answers changes: a session restored at launch, a sign-in or
35
+ * sign-out, an upgrade. Every open query is then read again and its stories
36
+ * replaced, so a feed read without the session is not left on screen.
37
+ */
38
+ session?: NewsSessionSignal;
39
+ }
40
+
41
+ export interface NewsSessionSignal {
42
+ key(): string;
43
+ subscribe(listener: () => void): () => void;
31
44
  }
32
45
 
33
46
  export type NewsQueryListener = (state: NewsQueryState) => void;
@@ -50,17 +63,31 @@ interface SourceFetchResult {
50
63
  failedSourceIds: string[];
51
64
  errors: string[];
52
65
  nextCursor: string | null;
66
+ access: NewsAccess | null;
67
+ companyFrom?: CompanyFrom | null;
53
68
  }
54
69
 
55
70
  interface NewsQueryEntry {
56
71
  query: NewsQuery;
57
72
  state: NewsQueryState;
58
73
  inFlight: Promise<NewsQueryState> | null;
74
+ /** The session generation the read in flight went out under. */
75
+ inFlightGeneration: number;
59
76
  loadMoreInFlight: Promise<void> | null;
77
+ loadMoreGeneration: number;
78
+ /** The session generation the stories on screen were read under. */
79
+ generation: number;
60
80
  refs: number;
61
81
  lastAccessedAt: number;
62
82
  }
63
83
 
84
+ /** Held back beats live: one source the service held back is enough to say the list is not the live wire. */
85
+ function combineNewsAccess(values: ReadonlyArray<NewsAccess | null | undefined>): NewsAccess | null {
86
+ if (values.includes("delayed")) return "delayed";
87
+ if (values.includes("realtime")) return "realtime";
88
+ return null;
89
+ }
90
+
64
91
  function newsCapabilityPriority(source: NewsCapability): number {
65
92
  return source.priority ?? 1000;
66
93
  }
@@ -85,6 +112,12 @@ export class NewsService {
85
112
  private readonly visibility: NonNullable<NewsServiceOptions["visibility"]>;
86
113
  private lastPollAt = 0;
87
114
  private unsubscribeVisibility: (() => void) | null = null;
115
+ private readonly session?: NewsSessionSignal;
116
+ private sessionKey: string | null = null;
117
+ /** Bumped when the session changes; a read from an older generation is dropped. */
118
+ private sessionGeneration = 0;
119
+ private sessionRefetchQueued = false;
120
+ private unsubscribeSession: (() => void) | null = null;
88
121
 
89
122
  constructor(options: NewsServiceOptions = {}) {
90
123
  const pollInterval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
@@ -94,6 +127,7 @@ export class NewsService {
94
127
  this.now = options.now ?? Date.now;
95
128
  this.connectionHealth = options.connectionHealth;
96
129
  this.visibility = options.visibility ?? { isVisible: isAppVisible, subscribe: subscribeAppVisibility };
130
+ this.session = options.session;
97
131
  }
98
132
 
99
133
  register(source: NewsCapability): () => void {
@@ -125,6 +159,10 @@ export class NewsService {
125
159
  this.clearPollTimer();
126
160
  this.scheduleNextPoll();
127
161
  });
162
+ if (this.session) {
163
+ this.sessionKey = this.session.key();
164
+ this.unsubscribeSession = this.session.subscribe(() => this.handleSessionChange());
165
+ }
128
166
  this.scheduleNextPoll();
129
167
  }
130
168
 
@@ -132,9 +170,46 @@ export class NewsService {
132
170
  this.polling = false;
133
171
  this.unsubscribeVisibility?.();
134
172
  this.unsubscribeVisibility = null;
173
+ this.unsubscribeSession?.();
174
+ this.unsubscribeSession = null;
135
175
  this.clearPollTimer();
136
176
  }
137
177
 
178
+ /**
179
+ * A new session answers with other news: the live wire once a Pro session
180
+ * is in place, the held-back one after signing out. Reads still out under
181
+ * the old session are dropped, open queries are read again and replace
182
+ * their stories rather than merge into them, and closed ones are forgotten.
183
+ * A token and its user arrive as separate changes, so the reads wait for
184
+ * the end of the tick and go out once.
185
+ */
186
+ private handleSessionChange(): void {
187
+ if (!this.session) return;
188
+ const key = this.session.key();
189
+ if (key === this.sessionKey) return;
190
+ this.sessionKey = key;
191
+ this.sessionGeneration++;
192
+ if (this.sessionRefetchQueued) return;
193
+ this.sessionRefetchQueued = true;
194
+ queueMicrotask(() => {
195
+ this.sessionRefetchQueued = false;
196
+ if (!this.polling) return;
197
+ let dropped = false;
198
+ for (const [queryKey, entry] of this.queries) {
199
+ if (entry.refs > 0) continue;
200
+ this.queries.delete(queryKey);
201
+ dropped = true;
202
+ }
203
+ if (dropped) {
204
+ this.rebuildArticlePool();
205
+ this.notify();
206
+ }
207
+ for (const entry of this.queries.values()) {
208
+ void this.refreshQuery(entry.query, true);
209
+ }
210
+ });
211
+ }
212
+
138
213
  private pollInFlight = false;
139
214
 
140
215
  private clearPollTimer(): void {
@@ -226,6 +301,7 @@ export class NewsService {
226
301
  return;
227
302
  }
228
303
 
304
+ const generation = this.sessionGeneration;
229
305
  const request = (async () => {
230
306
  entry.state = { ...entry.state, loadingMore: true };
231
307
  this.notify();
@@ -234,6 +310,12 @@ export class NewsService {
234
310
  ...normalized,
235
311
  cursor: entry.state.nextCursor ?? undefined,
236
312
  });
313
+ // An older page from another session must not join the list the new session read.
314
+ if (generation !== this.sessionGeneration || entry.generation !== generation) {
315
+ entry.state = { ...entry.state, loadingMore: false };
316
+ this.notify();
317
+ return;
318
+ }
237
319
  entry.state = {
238
320
  ...entry.state,
239
321
  loadingMore: false,
@@ -253,6 +335,7 @@ export class NewsService {
253
335
  }
254
336
  })();
255
337
  entry.loadMoreInFlight = request;
338
+ entry.loadMoreGeneration = generation;
256
339
  await request;
257
340
  }
258
341
 
@@ -318,7 +401,9 @@ export class NewsService {
318
401
  showLoading: boolean,
319
402
  ): Promise<NewsQueryState> {
320
403
  const entry = this.getOrCreateQueryEntry(query);
321
- if (entry.inFlight) return entry.inFlight;
404
+ const generation = this.sessionGeneration;
405
+ // A read that went out under an older session answers for that session: a new one goes out beside it.
406
+ if (entry.inFlight && entry.inFlightGeneration === generation) return entry.inFlight;
322
407
 
323
408
  const current = entry.state;
324
409
  if (showLoading) {
@@ -333,14 +418,17 @@ export class NewsService {
333
418
  const promise = (async () => {
334
419
  try {
335
420
  const result = await this.fetchFromSources(query);
421
+ if (generation !== this.sessionGeneration) return this.currentRead(entry);
336
422
  if (result.sourceIds.length === 0 && result.failedSourceIds.length > 0) {
337
423
  throw new Error("News sources unavailable.");
338
424
  }
339
- if (entry.loadMoreInFlight) return entry.state;
425
+ if (entry.loadMoreInFlight && entry.loadMoreGeneration === generation) return entry.state;
340
426
  const incoming = filterNewsArticlesForQuery(dedupeNewsArticles(result.articles), query);
427
+ // Stories read under another session are another feed (held back, or live): the new read replaces them.
428
+ const sameSession = entry.generation === generation;
341
429
  // The stories a coin's fallback found are not the pair's own: once stories are filed under it, they replace them.
342
- const existing = incoming.length > 0 && entry.state.articles.length > 0
343
- && (incoming[0]!.searchedFor ?? null) !== (entry.state.articles[0]!.searchedFor ?? null)
430
+ const existing = !sameSession || (incoming.length > 0 && entry.state.articles.length > 0
431
+ && (incoming[0]!.searchedFor ?? null) !== (entry.state.articles[0]!.searchedFor ?? null))
344
432
  ? []
345
433
  : entry.state.articles;
346
434
  const incomingIds = new Set(incoming.map((article) => article.id));
@@ -363,13 +451,18 @@ export class NewsService {
363
451
  sourceIds: result.sourceIds,
364
452
  nextCursor: hasOlderPages ? entry.state.nextCursor : result.nextCursor,
365
453
  loadingMore: entry.state.loadingMore,
454
+ // A page that does not say keeps what this session's earlier answer said.
455
+ access: result.access ?? (sameSession ? entry.state.access ?? null : null),
456
+ companyFrom: result.companyFrom ?? null,
366
457
  };
367
458
  entry.state = state;
459
+ entry.generation = generation;
368
460
  entry.lastAccessedAt = this.now();
369
461
  this.rebuildArticlePool();
370
462
  this.notify();
371
463
  return state;
372
464
  } catch (error) {
465
+ if (generation !== this.sessionGeneration) return this.currentRead(entry);
373
466
  const state: NewsQueryState = {
374
467
  ...current,
375
468
  phase: "error",
@@ -380,15 +473,21 @@ export class NewsService {
380
473
  this.notify();
381
474
  return state;
382
475
  } finally {
383
- entry.inFlight = null;
476
+ if (entry.inFlightGeneration === generation) entry.inFlight = null;
384
477
  this.pruneInactiveQueries();
385
478
  }
386
479
  })();
387
480
 
388
481
  entry.inFlight = promise;
482
+ entry.inFlightGeneration = generation;
389
483
  return promise;
390
484
  }
391
485
 
486
+ /** What a dropped read answers its caller: the current session's read when one is out, else the state as it is. */
487
+ private currentRead(entry: NewsQueryEntry): Promise<NewsQueryState> | NewsQueryState {
488
+ return entry.inFlight && entry.inFlightGeneration === this.sessionGeneration ? entry.inFlight : entry.state;
489
+ }
490
+
392
491
  private getOrCreateQueryEntry(query: NewsQuery): NewsQueryEntry {
393
492
  const key = buildNewsQueryKey(query);
394
493
  const now = this.now();
@@ -402,7 +501,10 @@ export class NewsService {
402
501
  query,
403
502
  state: createIdleNewsQueryState(),
404
503
  inFlight: null,
504
+ inFlightGeneration: this.sessionGeneration,
405
505
  loadMoreInFlight: null,
506
+ loadMoreGeneration: this.sessionGeneration,
507
+ generation: this.sessionGeneration,
406
508
  refs: 0,
407
509
  lastAccessedAt: now,
408
510
  };
@@ -451,7 +553,7 @@ export class NewsService {
451
553
  private async readSourcePage(
452
554
  source: NewsCapability,
453
555
  query: NewsQuery,
454
- ): Promise<{ articles: NewsArticle[]; nextCursor: string | null; error?: string | null }> {
556
+ ): Promise<{ articles: NewsArticle[]; nextCursor: string | null; error?: string | null; access?: NewsAccess | null; companyFrom?: CompanyFrom | null }> {
455
557
  if (source.provider.fetchNewsPage) {
456
558
  const page = await this.trackSourceRequest(
457
559
  source,
@@ -462,6 +564,8 @@ export class NewsService {
462
564
  articles: page.articles.map((article) => markDetailCapableArticle(source, article)),
463
565
  nextCursor: page.nextCursor ?? null,
464
566
  error: page.error,
567
+ access: page.access,
568
+ companyFrom: page.companyFrom ?? null,
465
569
  };
466
570
  }
467
571
  const articles = (await this.trackSourceRequest(
@@ -484,6 +588,8 @@ export class NewsService {
484
588
  failedSourceIds,
485
589
  errors: page.error ? [page.error] : [],
486
590
  nextCursor: page.nextCursor,
591
+ access: page.access ?? null,
592
+ companyFrom: page.companyFrom ?? null,
487
593
  };
488
594
  if (page.articles.length > 0) return result;
489
595
  firstEmpty ??= result;
@@ -491,7 +597,7 @@ export class NewsService {
491
597
  failedSourceIds.push(newsCapabilitySourceId(source));
492
598
  }
493
599
  }
494
- return firstEmpty ?? { articles: [], sourceIds: [], failedSourceIds, errors: [], nextCursor: null };
600
+ return firstEmpty ?? { articles: [], sourceIds: [], failedSourceIds, errors: [], nextCursor: null, access: null };
495
601
  }
496
602
 
497
603
  private async fetchMergedNews(query: NewsQuery, sources: NewsCapability[]): Promise<SourceFetchResult> {
@@ -505,6 +611,7 @@ export class NewsService {
505
611
  const errors: string[] = [];
506
612
  const sourceIds: string[] = [];
507
613
  const failedSourceIds: string[] = [];
614
+ const access: Array<NewsAccess | null | undefined> = [];
508
615
  let nextCursor: string | null = null;
509
616
  settled.forEach((result, index) => {
510
617
  if (result.status !== "fulfilled") {
@@ -515,9 +622,10 @@ export class NewsService {
515
622
  articles.push(...result.value.page.articles);
516
623
  if (result.value.page.error) errors.push(result.value.page.error);
517
624
  sourceIds.push(newsCapabilitySourceId(result.value.source));
625
+ access.push(result.value.page.access);
518
626
  nextCursor ??= result.value.page.nextCursor;
519
627
  });
520
- return { articles, sourceIds, failedSourceIds, errors, nextCursor };
628
+ return { articles, sourceIds, failedSourceIds, errors, nextCursor, access: combineNewsAccess(access) };
521
629
  }
522
630
 
523
631
  private trackSourceRequest<T>(
@@ -550,6 +658,8 @@ export class NewsService {
550
658
  sourceIds: [...new Set([...entry.state.sourceIds, newsCapabilitySourceId(source)])],
551
659
  nextCursor: entry.state.nextCursor,
552
660
  loadingMore: false,
661
+ access: entry.state.access ?? null,
662
+ companyFrom: entry.state.companyFrom ?? null,
553
663
  };
554
664
  changed = true;
555
665
  }
@@ -0,0 +1,21 @@
1
+ import { apiClient } from "../api-client";
2
+ import { hasProAccess } from "../api-client/plan-rules";
3
+ import type { NewsSessionSignal } from "./aggregator";
4
+
5
+ /**
6
+ * What decides which news Gloom Cloud answers with: whether requests carry a
7
+ * session, whose it is, and whether that account has real-time news. Changes
8
+ * when the session is restored at launch, on sign-in and sign-out, and when
9
+ * the plan changes.
10
+ */
11
+ export const cloudNewsSession: NewsSessionSignal = {
12
+ key() {
13
+ const user = apiClient.getCurrentUser();
14
+ return [
15
+ apiClient.hasSessionCredential() ? "session" : "none",
16
+ user?.id ?? "",
17
+ hasProAccess(user) ? "realtime" : "delayed",
18
+ ].join(":");
19
+ },
20
+ subscribe: (listener) => apiClient.subscribeCurrentUser(listener),
21
+ };
package/src/news/hooks.ts CHANGED
@@ -22,6 +22,7 @@ const IDLE_NEWS_QUERY_STATE: NewsQueryState = {
22
22
  sourceIds: [],
23
23
  nextCursor: null,
24
24
  loadingMore: false,
25
+ access: null,
25
26
  };
26
27
 
27
28
  export function useNewsArticles(query: NewsQuery | null | undefined): NewsQueryState {
@@ -92,6 +92,7 @@ export function createIdleNewsQueryState(): NewsQueryState {
92
92
  sourceIds: [],
93
93
  nextCursor: null,
94
94
  loadingMore: false,
95
+ access: null,
95
96
  };
96
97
  }
97
98
 
package/src/news/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { CompanyFrom } from "../types/company-from";
2
+
1
3
  type NewsQueryScope = "global" | "ticker";
2
4
  export type NewsFeed = "latest" | "top" | "breaking" | "ticker" | "sector" | "topic";
3
5
  type NewsSentiment = "positive" | "neutral" | "negative";
@@ -81,11 +83,22 @@ export interface NewsQuery {
81
83
 
82
84
  type NewsQueryPhase = "idle" | "loading" | "ready" | "refreshing" | "error";
83
85
 
86
+ /**
87
+ * Which news a source answered with: the live wire, or the one held back for
88
+ * an account without real-time news. Read from the answer itself, never from
89
+ * the plan the app remembers.
90
+ */
91
+ export type NewsAccess = "realtime" | "delayed";
92
+
84
93
  export interface NewsPage {
85
94
  /** Active source failure, including a partial failure with usable articles. */
86
95
  error?: string | null;
87
96
  articles: NewsArticle[];
88
97
  nextCursor?: string | null;
98
+ /** Which news the source answered with; null or absent when the answer does not say. */
99
+ access?: NewsAccess | null;
100
+ /** The company's home line the stories are read from, when not the listing asked (a receipt, a foreign line). */
101
+ companyFrom?: CompanyFrom | null;
89
102
  }
90
103
 
91
104
  export interface NewsQueryState {
@@ -96,4 +109,8 @@ export interface NewsQueryState {
96
109
  sourceIds: string[];
97
110
  nextCursor: string | null;
98
111
  loadingMore: boolean;
112
+ /** Which news the stories are, from the last answer; null or absent when it did not say. */
113
+ access?: NewsAccess | null;
114
+ /** The company's home line the stories are read from, when not the listing asked. */
115
+ companyFrom?: CompanyFrom | null;
99
116
  }
@@ -1,5 +1,7 @@
1
1
  import { metricDescription, readAlertMetric, validMetricRule } from "./metric-model";
2
2
  import type { AlertCondition, AlertRule } from "./types";
3
+ import { alertListing } from "./format";
4
+ import { canonicalExchange } from "../../../utils/exchanges";
3
5
  import { logFailure } from "../../../utils/log-failure";
4
6
 
5
7
  export type { AlertRule };
@@ -33,10 +35,14 @@ export function editAlert(
33
35
  targetPrice: number,
34
36
  ): AlertRule {
35
37
  const nextSymbol = symbol.trim().toUpperCase();
38
+ const sameListing = nextSymbol === alert.symbol.trim().toUpperCase();
36
39
  return {
37
40
  id: alert.id,
38
41
  symbol: nextSymbol,
39
- exchange: nextSymbol === alert.symbol.trim().toUpperCase() ? alert.exchange : undefined,
42
+ exchange: sameListing ? alert.exchange : undefined,
43
+ // The listing's currency and venue stay with it; a new symbol learns its own at its first check.
44
+ ...(sameListing && alert.lastQuoteCurrency ? { lastQuoteCurrency: alert.lastQuoteCurrency } : {}),
45
+ ...(sameListing && alert.lastQuoteExchange ? { lastQuoteExchange: alert.lastQuoteExchange } : {}),
40
46
  condition,
41
47
  targetPrice,
42
48
  createdAt: alert.createdAt,
@@ -51,6 +57,19 @@ export function rearmAlert(alert: AlertRule): AlertRule {
51
57
  return editAlert(alert, alert.symbol, alert.condition, alert.targetPrice);
52
58
  }
53
59
 
60
+ /**
61
+ * Whether two level alerts are one rule: the same measure, condition and
62
+ * target on the same listing, whether it was named with its exchange or read
63
+ * from the symbol alone. A second add of one names the first and adds nothing.
64
+ */
65
+ export function sameLevelRule(a: AlertRule, b: AlertRule): boolean {
66
+ const venue = (alert: AlertRule) => canonicalExchange(alert.exchange ?? "");
67
+ return a.symbol.toUpperCase() === b.symbol.toUpperCase()
68
+ && (venue(a) === venue(b) || alertListing(a) === alertListing(b))
69
+ && a.condition === b.condition && a.targetPrice === b.targetPrice
70
+ && JSON.stringify(readAlertMetric(a.metric)) === JSON.stringify(readAlertMetric(b.metric));
71
+ }
72
+
54
73
  export function evaluateAlert(alert: AlertRule, currentPrice: number): boolean {
55
74
  if (alert.metric || alert.status !== "active") return false;
56
75
 
@@ -0,0 +1,33 @@
1
+ import { metricDescription } from "./metric-model";
2
+ import { eventAlertTarget, type EventAlertRule } from "./events";
3
+ import { alertChangeEntry, appendFiredAlert, readFiredAlerts, type AlertLogAction, type FiredAlert } from "./fired-log";
4
+ import { alertListing } from "./format";
5
+ import { isResearchAlertKind, readResearchRule } from "./research-rules";
6
+ import { readScheduledRule, scheduledEventName } from "./scheduled";
7
+ import type { AlertRule } from "./types";
8
+
9
+ /**
10
+ * A rule as the alert log names it: its kind, the rule in words (`EWG:ARCA
11
+ * below 40`, as a fired price alert reads) and the symbol it follows.
12
+ */
13
+ export function alertLogRule(rule: AlertRule | EventAlertRule) {
14
+ if ("symbol" in rule) {
15
+ return {
16
+ kind: rule.metric?.kind ?? "price", ruleId: rule.id,
17
+ rule: rule.metric ? metricDescription(rule) : `${alertListing(rule)} ${rule.condition} ${rule.targetPrice}`,
18
+ symbol: rule.symbol, ...rule.exchange ? { exchange: rule.exchange } : {},
19
+ };
20
+ }
21
+ const scheduled = readScheduledRule(rule);
22
+ const symbol = scheduled?.target.kind === "earnings" ? scheduled.target.symbol
23
+ : isResearchAlertKind(rule.kind) ? readResearchRule(rule.kind, rule.value)?.symbol : undefined;
24
+ return {
25
+ kind: rule.kind, ruleId: rule.id, rule: scheduled ? scheduledEventName(scheduled.target) : eventAlertTarget(rule),
26
+ ...symbol ? { symbol } : {},
27
+ };
28
+ }
29
+
30
+ /** The saved log with one change more: what the pane's `usePluginState(FIRED_LOG_KEY)` setter takes. */
31
+ export function withAlertChange(saved: unknown, action: AlertLogAction, rule: AlertRule | EventAlertRule, at = Date.now()): FiredAlert[] {
32
+ return appendFiredAlert(readFiredAlerts(saved), alertChangeEntry(action, alertLogRule(rule), at));
33
+ }
@@ -3,7 +3,7 @@
3
3
  // release's rows as the calendar's feeds name it from week to week, and the
4
4
  // notifications sent before it and once its figures are out. CPI and FOMC are
5
5
  // two cases of the same alert, whose dates come from their own calendars.
6
- import { dateKey, eventCountry, matchesCountry, type CountryFilter } from "../econ/calendar-model";
6
+ import { dateKey, econEventName, eventCountry, matchesCountry, type CountryFilter } from "../econ/calendar-model";
7
7
  import { isAllDayEvent, hasNoReleaseTime, type EconEvent, type EconImpact } from "../econ/types";
8
8
  import { RENAMED_RELEASES } from "../shared/macro-releases";
9
9
  import { MACRO_EVENT_NAMES } from "./research-builder";
@@ -126,7 +126,11 @@ export interface CalendarEventChoice {
126
126
 
127
127
  const IMPACT_RANK: Record<EconImpact, number> = { high: 0, medium: 1, low: 2 };
128
128
 
129
- type RankedChoice = CalendarEventChoice & { words: string[] };
129
+ type RankedChoice = CalendarEventChoice & {
130
+ words: string[];
131
+ /** The words of the name `econ` shows it under, when that is not the feed's (the Fed's rate as its upper bound). */
132
+ shownWords: string[];
133
+ };
130
134
 
131
135
  /**
132
136
  * The calendar's releases, one per country and name, each with its next row
@@ -150,6 +154,7 @@ function calendarChoices(events: readonly EconEvent[], now: number): RankedChoic
150
154
  impact: rows.reduce<EconImpact>((best, event) => IMPACT_RANK[event.impact] < IMPACT_RANK[best] ? event.impact : best, "low"),
151
155
  next,
152
156
  words: calendarNameWords(named.event),
157
+ shownWords: calendarNameWords(econEventName(named)),
153
158
  };
154
159
  });
155
160
  }
@@ -246,7 +251,8 @@ function matchCalendarWords(
246
251
  now: number,
247
252
  ): CalendarEventMatch {
248
253
  const choices = calendarChoices(country ? events.filter((event) => matchesCountry(event, country)) : events, now);
249
- const exact = choices.filter((choice) => choice.words.join(" ") === words.join(" "));
254
+ // The name as the feed gives it, or as `econ` shows it: the alert keeps the feed's, which the calendar's rows carry.
255
+ const exact = choices.filter((choice) => [choice.words, choice.shownWords].some((name) => name.join(" ") === words.join(" ")));
250
256
  const naming = (typed: readonly string[], loose: boolean) => typed.length === 0 ? [] : choices.filter((choice) =>
251
257
  typed.every((word) => loose ? wordFound(word, choice.words) : choice.words.includes(word)));
252
258
  // CPI by the name one feed gives it everywhere but the US (China's Inflation Rate YoY).
@@ -278,7 +284,7 @@ function matchCalendarWords(
278
284
  return { kind: "match", choice: strip(ranked[0]!), others: ranked.slice(1).map(strip) };
279
285
  }
280
286
 
281
- function strip({ words: _words, ...choice }: RankedChoice): CalendarEventChoice {
287
+ function strip({ words: _words, shownWords: _shown, ...choice }: RankedChoice): CalendarEventChoice {
282
288
  return choice;
283
289
  }
284
290