gloomberb 0.18.4 → 0.19.0

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 (600) 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 +169 -5
  5. package/src/account/auth-model.ts +7 -3
  6. package/src/api-client/auth.ts +9 -4
  7. package/src/api-client/catalysts.ts +2 -1
  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 +31 -6
  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/index.ts +171 -11
  14. package/src/api-client/live-scan.ts +91 -0
  15. package/src/api-client/market-discovery.ts +10 -0
  16. package/src/api-client/market-movers.ts +7 -0
  17. package/src/api-client/members.ts +13 -1
  18. package/src/api-client/perps.ts +43 -0
  19. package/src/api-client/request.ts +222 -41
  20. package/src/api-client/research-activity.ts +6 -12
  21. package/src/api-client/types.ts +74 -0
  22. package/src/app/keybindings/index.ts +3 -0
  23. package/src/app/keybindings/labels.ts +18 -1
  24. package/src/app/keybindings/resolve.ts +14 -0
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +42 -27
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +1 -1
  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/fred-series.ts +5 -0
  35. package/src/cli/commands/keybindings.ts +23 -0
  36. package/src/cli/commands/market.ts +138 -46
  37. package/src/cli/commands/options-chain.ts +107 -10
  38. package/src/cli/commands/options-leaps.ts +9 -3
  39. package/src/cli/commands/overview.ts +176 -39
  40. package/src/cli/commands/search.ts +39 -3
  41. package/src/cli/commands/themes.ts +11 -2
  42. package/src/cli/commands/ticker.ts +57 -35
  43. package/src/cli/company-name-hint.ts +41 -0
  44. package/src/cli/earnings-calendar.ts +1 -1
  45. package/src/cli/errors.ts +10 -2
  46. package/src/cli/export-source.ts +29 -1
  47. package/src/cli/glossary.ts +73 -1
  48. package/src/cli/history-rows.ts +87 -16
  49. package/src/cli/index.ts +2 -0
  50. package/src/cli/known-gaps.ts +5 -2
  51. package/src/cli/listing-arg.ts +195 -56
  52. package/src/cli/output-file.ts +68 -0
  53. package/src/cli/pane-functions/capabilities.ts +3 -0
  54. package/src/cli/pane-functions/catalog.ts +80 -7
  55. package/src/cli/pane-functions/dom.ts +13 -3
  56. package/src/cli/pane-functions/freshness.ts +111 -16
  57. package/src/cli/pane-functions/function-help.ts +23 -13
  58. package/src/cli/pane-functions/headless.ts +56 -12
  59. package/src/cli/pane-functions/index.ts +58 -16
  60. package/src/cli/pane-functions/market-session.ts +81 -9
  61. package/src/cli/pane-functions/options.ts +19 -1
  62. package/src/cli/pane-functions/report.ts +5 -1
  63. package/src/cli/pane-functions/resolver.ts +35 -8
  64. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  65. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  66. package/src/cli/quote-answer.ts +30 -13
  67. package/src/cli/report-tables.ts +18 -7
  68. package/src/cli/restore-plugins.ts +13 -1
  69. package/src/cli/result.ts +57 -5
  70. package/src/cli/ticker-resolution.ts +3 -2
  71. package/src/components/chart/composite/composite-chart.tsx +26 -4
  72. package/src/components/chart/composite/format.ts +27 -0
  73. package/src/components/chart/composite/interactions.ts +6 -0
  74. package/src/components/chart/composite/legend.tsx +45 -7
  75. package/src/components/chart/composite/levels.ts +2 -0
  76. package/src/components/chart/composite/panel-gestures.ts +33 -0
  77. package/src/components/chart/composite/panel-series.ts +1 -0
  78. package/src/components/chart/composite/panel-surface.tsx +61 -5
  79. package/src/components/chart/composite/range-statistics.ts +62 -0
  80. package/src/components/chart/composite/rasterizer.ts +24 -13
  81. package/src/components/chart/composite/scene.ts +46 -7
  82. package/src/components/chart/composite/series-axis-tags.tsx +16 -9
  83. package/src/components/chart/composite/series-cues.ts +14 -0
  84. package/src/components/chart/composite/text-renderer.ts +9 -2
  85. package/src/components/chart/composite/tool-catalog.ts +36 -0
  86. package/src/components/chart/composite/tools.ts +203 -44
  87. package/src/components/chart/composite/types.ts +4 -0
  88. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  89. package/src/components/command-bar/assist/inventory.ts +2 -1
  90. package/src/components/command-bar/commands/direct/index.ts +28 -13
  91. package/src/components/command-bar/pane-templates/items.ts +3 -2
  92. package/src/components/command-bar/panel/prompt-binding.ts +0 -8
  93. package/src/components/command-bar/workflow/alert.ts +1 -1
  94. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  95. package/src/components/layout/header.tsx +26 -40
  96. package/src/components/layout/pane/footer/index.tsx +1 -0
  97. package/src/components/layout/pane/footer/model.ts +27 -0
  98. package/src/components/onboarding/onboarding-frame.tsx +1 -1
  99. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  100. package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
  101. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  102. package/src/components/onboarding/wizard-positions.ts +3 -3
  103. package/src/components/ticker/list-table-view.tsx +4 -1
  104. package/src/components/ui/choice-dialog.tsx +23 -18
  105. package/src/components/ui/choice-groups.ts +18 -0
  106. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  107. package/src/components/ui/data-table/opentui/model.ts +26 -1
  108. package/src/components/ui/data-table/types.ts +2 -0
  109. package/src/components/ui/display.tsx +5 -1
  110. package/src/components/ui/list-view.tsx +8 -4
  111. package/src/components/ui/query-bar-keys.tsx +2 -0
  112. package/src/components/ui/query-bar.tsx +1 -0
  113. package/src/components/ui/select-button.tsx +5 -1
  114. package/src/components/ui/select-field.tsx +1 -0
  115. package/src/components/ui/table-layout.ts +8 -5
  116. package/src/core/app-runtime.ts +2 -0
  117. package/src/core/app-services.ts +2 -2
  118. package/src/core/state/app/layout.ts +2 -0
  119. package/src/core/state/app/state.ts +9 -0
  120. package/src/core/state/app/types.ts +3 -0
  121. package/src/data/config/chart-settings.ts +1 -1
  122. package/src/data/json-ticker-repository.ts +17 -8
  123. package/src/data/saved-copies.ts +55 -16
  124. package/src/data/ticker-repository.ts +16 -9
  125. package/src/data/ticker-writes.ts +81 -0
  126. package/src/data/ttl-cache.ts +165 -0
  127. package/src/docs/snapshot.json +427 -216
  128. package/src/i18n/es.ts +19 -3
  129. package/src/i18n/ja.ts +19 -3
  130. package/src/i18n/ko.ts +19 -3
  131. package/src/i18n/zh-cn.ts +19 -3
  132. package/src/i18n/zh-tw.ts +19 -3
  133. package/src/market-data/coordinator/financials.ts +10 -2
  134. package/src/market-data/coordinator/index.ts +3 -1
  135. package/src/market-data/coordinator/quotes.ts +4 -2
  136. package/src/market-data/depositary-pair.ts +2 -2
  137. package/src/market-data/fund-facts.ts +50 -0
  138. package/src/market-data/market/change-basis.ts +48 -7
  139. package/src/market-data/market/dated-previous-close.ts +203 -0
  140. package/src/market-data/market/format.ts +25 -0
  141. package/src/market-data/market/freshness.ts +2 -1
  142. package/src/market-data/market/futures-week.ts +68 -0
  143. package/src/market-data/market/status.ts +16 -4
  144. package/src/market-data/options-alternatives.ts +141 -4
  145. package/src/market-data/quotes/contributions.ts +20 -2
  146. package/src/market-data/quotes/resolution.ts +3 -0
  147. package/src/market-data/selectors.ts +3 -0
  148. package/src/news/aggregator.ts +113 -9
  149. package/src/news/cloud-session.ts +21 -0
  150. package/src/news/hooks.ts +1 -0
  151. package/src/news/news-model.ts +1 -0
  152. package/src/news/types.ts +11 -0
  153. package/src/notifications/notification-log.ts +6 -4
  154. package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
  155. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  156. package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
  157. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  158. package/src/plugins/builtin/alerts/cli.ts +380 -82
  159. package/src/plugins/builtin/alerts/command.ts +31 -0
  160. package/src/plugins/builtin/alerts/constants.ts +1 -1
  161. package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
  162. package/src/plugins/builtin/alerts/fired-log.ts +113 -8
  163. package/src/plugins/builtin/alerts/format.ts +80 -4
  164. package/src/plugins/builtin/alerts/headless.ts +30 -7
  165. package/src/plugins/builtin/alerts/history-rows.ts +7 -3
  166. package/src/plugins/builtin/alerts/history.ts +2 -0
  167. package/src/plugins/builtin/alerts/index.tsx +135 -22
  168. package/src/plugins/builtin/alerts/live.ts +3 -3
  169. package/src/plugins/builtin/alerts/metric-command.ts +134 -0
  170. package/src/plugins/builtin/alerts/metric-data.ts +111 -0
  171. package/src/plugins/builtin/alerts/metric-model.ts +324 -0
  172. package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
  173. package/src/plugins/builtin/alerts/pane.tsx +90 -39
  174. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  175. package/src/plugins/builtin/alerts/storage.ts +37 -10
  176. package/src/plugins/builtin/alerts/types.ts +7 -0
  177. package/src/plugins/builtin/analytics/index.tsx +2 -1
  178. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  179. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  180. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  181. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  182. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  183. package/src/plugins/builtin/attention/index.tsx +2 -1
  184. package/src/plugins/builtin/attention/listing.ts +14 -0
  185. package/src/plugins/builtin/backtest/headless.ts +10 -5
  186. package/src/plugins/builtin/backtest/pane.tsx +9 -4
  187. package/src/plugins/builtin/backtest/presets.ts +29 -3
  188. package/src/plugins/builtin/broker-manager/index.tsx +10 -5
  189. package/src/plugins/builtin/broker-manager/model.ts +6 -1
  190. package/src/plugins/builtin/broker-trading/controller.ts +22 -12
  191. package/src/plugins/builtin/broker-trading/index.tsx +13 -1
  192. package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
  193. package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
  194. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  195. package/src/plugins/builtin/buildout/format.ts +38 -0
  196. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  197. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  198. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  199. package/src/plugins/builtin/builtin-plugin-meta.ts +7 -3
  200. package/src/plugins/builtin/catalysts/model.ts +2 -1
  201. package/src/plugins/builtin/changelog/index.tsx +9 -6
  202. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
  203. package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
  204. package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
  205. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
  206. package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
  207. package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
  208. package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
  209. package/src/plugins/builtin/chart-composer/formula.ts +71 -18
  210. package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
  211. package/src/plugins/builtin/chart-composer/headless.ts +19 -3
  212. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  213. package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
  214. package/src/plugins/builtin/chart-composer/presets.ts +50 -15
  215. package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
  216. package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
  217. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  218. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
  219. package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
  220. package/src/plugins/builtin/chat/content/composer.tsx +13 -6
  221. package/src/plugins/builtin/chat/content/footer.tsx +6 -3
  222. package/src/plugins/builtin/chat/content/index.tsx +50 -21
  223. package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
  224. package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
  225. package/src/plugins/builtin/chat/index.tsx +23 -0
  226. package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
  227. package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
  228. package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
  229. package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
  230. package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
  231. package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
  232. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
  233. package/src/plugins/builtin/clinical-trials/model.ts +0 -9
  234. package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
  235. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
  236. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  237. package/src/plugins/builtin/cloud/plugin.tsx +1 -1
  238. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  239. package/src/plugins/builtin/company-attention/client.ts +8 -9
  240. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  241. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  242. package/src/plugins/builtin/company-attention/pane.tsx +9 -2
  243. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  244. package/src/plugins/builtin/correlation/headless.ts +13 -5
  245. package/src/plugins/builtin/correlation/history.ts +22 -0
  246. package/src/plugins/builtin/cot/client.ts +22 -18
  247. package/src/plugins/builtin/cot/headless.ts +15 -10
  248. package/src/plugins/builtin/cot/model.ts +14 -1
  249. package/src/plugins/builtin/cot/pane.tsx +22 -14
  250. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  251. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  252. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  253. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  254. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  255. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  256. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  257. package/src/plugins/builtin/credit-documents/headless.ts +7 -7
  258. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  259. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  260. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  261. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  262. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  263. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  264. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  265. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  266. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  267. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  268. package/src/plugins/builtin/docs/targets.ts +5 -1
  269. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  270. package/src/plugins/builtin/earnings/board.tsx +62 -71
  271. package/src/plugins/builtin/earnings/headless.ts +40 -4
  272. package/src/plugins/builtin/earnings/scope.ts +31 -43
  273. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  274. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  275. package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
  276. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  277. package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
  278. package/src/plugins/builtin/econ/calendar-model.ts +14 -9
  279. package/src/plugins/builtin/econ/figures.ts +72 -0
  280. package/src/plugins/builtin/econ/index.tsx +33 -3
  281. package/src/plugins/builtin/econ/types.ts +4 -0
  282. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  283. package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
  284. package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
  285. package/src/plugins/builtin/econ-statistics/view.ts +3 -0
  286. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  287. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  288. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  289. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  290. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  291. package/src/plugins/builtin/equity-screener/pane.tsx +70 -15
  292. package/src/plugins/builtin/exposure/charts.tsx +2 -2
  293. package/src/plugins/builtin/exposure/model.ts +13 -1
  294. package/src/plugins/builtin/exposure/pane.tsx +4 -4
  295. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  296. package/src/plugins/builtin/futures/index.tsx +1 -0
  297. package/src/plugins/builtin/futures/model.ts +2 -1
  298. package/src/plugins/builtin/futures/table.tsx +2 -2
  299. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  300. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  301. package/src/plugins/builtin/fx-matrix/client.ts +11 -3
  302. package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
  303. package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
  304. package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -3
  305. package/src/plugins/builtin/gpu/headless.ts +8 -2
  306. package/src/plugins/builtin/help/function-index.ts +4 -1
  307. package/src/plugins/builtin/help/index.tsx +1 -0
  308. package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
  309. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  310. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  311. package/src/plugins/builtin/holders/headless.ts +18 -1
  312. package/src/plugins/builtin/iv-history/client.ts +52 -11
  313. package/src/plugins/builtin/iv-history/format.ts +36 -23
  314. package/src/plugins/builtin/iv-history/headless.ts +36 -18
  315. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  316. package/src/plugins/builtin/iv-history/model.ts +163 -27
  317. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  318. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  319. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  320. package/src/plugins/builtin/jobs/client.ts +25 -29
  321. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  322. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  323. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  324. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  325. package/src/plugins/builtin/macro-day/pane.tsx +7 -5
  326. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  327. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  328. package/src/plugins/builtin/market-movers/client.ts +25 -7
  329. package/src/plugins/builtin/market-movers/footer.ts +2 -2
  330. package/src/plugins/builtin/market-movers/headless.ts +32 -12
  331. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  332. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  333. package/src/plugins/builtin/market-movers/model.ts +4 -1
  334. package/src/plugins/builtin/market-movers/screener.ts +14 -0
  335. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  336. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  337. package/src/plugins/builtin/market-movers/table.ts +17 -5
  338. package/src/plugins/builtin/members/breadth-pane.tsx +82 -0
  339. package/src/plugins/builtin/members/client.ts +19 -4
  340. package/src/plugins/builtin/members/headless.ts +20 -7
  341. package/src/plugins/builtin/members/index.tsx +9 -9
  342. package/src/plugins/builtin/members/model.ts +23 -4
  343. package/src/plugins/builtin/members/pane.tsx +25 -15
  344. package/src/plugins/builtin/news/index.tsx +1 -0
  345. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  346. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  347. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  348. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  349. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  350. package/src/plugins/builtin/openfda/client.ts +14 -2
  351. package/src/plugins/builtin/openfda/index.tsx +3 -2
  352. package/src/plugins/builtin/openfda/model.ts +9 -0
  353. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  354. package/src/plugins/builtin/options/analytics.ts +54 -6
  355. package/src/plugins/builtin/options/enrichment.ts +17 -5
  356. package/src/plugins/builtin/options/model-spot.ts +5 -2
  357. package/src/plugins/builtin/options/strike-window.ts +2 -1
  358. package/src/plugins/builtin/options/table.ts +6 -9
  359. package/src/plugins/builtin/options/types.ts +2 -0
  360. package/src/plugins/builtin/options/view.tsx +42 -12
  361. package/src/plugins/builtin/options-positioning/client.ts +30 -0
  362. package/src/plugins/builtin/options-positioning/headless.ts +74 -21
  363. package/src/plugins/builtin/options-positioning/index.tsx +4 -4
  364. package/src/plugins/builtin/options-positioning/model.ts +122 -14
  365. package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
  366. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  367. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  368. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  369. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  370. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  371. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  372. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  373. package/src/plugins/builtin/pe-band/model.ts +12 -49
  374. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  375. package/src/plugins/builtin/perps/cascade.ts +236 -0
  376. package/src/plugins/builtin/perps/client.ts +56 -2
  377. package/src/plugins/builtin/perps/freshness.ts +12 -10
  378. package/src/plugins/builtin/perps/headless.ts +65 -9
  379. package/src/plugins/builtin/perps/index.tsx +7 -5
  380. package/src/plugins/builtin/perps/live-rows.ts +10 -3
  381. package/src/plugins/builtin/perps/model.ts +18 -4
  382. package/src/plugins/builtin/perps/pane.tsx +32 -31
  383. package/src/plugins/builtin/perps/status-line.ts +25 -0
  384. package/src/plugins/builtin/plugin-module.ts +5 -1
  385. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  386. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  387. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  388. package/src/plugins/builtin/portfolio-list/cli/render.ts +12 -7
  389. package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
  390. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  391. package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
  392. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  393. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  394. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
  395. package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
  396. package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
  397. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  398. package/src/plugins/builtin/rate-path/pane.tsx +2 -2
  399. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  400. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  401. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  402. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  403. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  404. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  405. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  406. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  407. package/src/plugins/builtin/research/event-model.ts +10 -8
  408. package/src/plugins/builtin/research/headless.ts +1 -1
  409. package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
  410. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  411. package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
  412. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  413. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  414. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  415. package/src/plugins/builtin/scanner/feed.ts +2 -2
  416. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  417. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  418. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  419. package/src/plugins/builtin/scanner/headless.ts +204 -35
  420. package/src/plugins/builtin/scanner/index.ts +40 -6
  421. package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
  422. package/src/plugins/builtin/scanner/live-feed.ts +168 -0
  423. package/src/plugins/builtin/scanner/live-headless.ts +138 -0
  424. package/src/plugins/builtin/scanner/live-model.ts +362 -0
  425. package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
  426. package/src/plugins/builtin/scanner/live-track.ts +186 -0
  427. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  428. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  429. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  430. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  431. package/src/plugins/builtin/sec/headless.ts +2 -1
  432. package/src/plugins/builtin/series-inventory.ts +3 -0
  433. package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
  434. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  435. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  436. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  437. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  438. package/src/plugins/builtin/shared/macro-releases.ts +65 -0
  439. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  440. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  441. package/src/plugins/builtin/shared/report-freshness.ts +3 -2
  442. package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
  443. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  444. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  445. package/src/plugins/builtin/shared/use-quote-board.ts +10 -3
  446. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  447. package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
  448. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  449. package/src/plugins/builtin/themes/headless.ts +6 -4
  450. package/src/plugins/builtin/themes/model.ts +17 -8
  451. package/src/plugins/builtin/themes/pane.tsx +7 -7
  452. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  453. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  454. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  455. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  456. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  457. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  458. package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
  459. package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
  460. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  461. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  462. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  463. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  464. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  465. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  466. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  467. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  468. package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
  469. package/src/plugins/builtin/vol-surface/client.ts +9 -17
  470. package/src/plugins/builtin/vol-surface/headless.ts +63 -14
  471. package/src/plugins/builtin/vol-surface/index.tsx +2 -2
  472. package/src/plugins/builtin/vol-surface/model.ts +22 -7
  473. package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
  474. package/src/plugins/builtin/vol-surface/tables.ts +31 -3
  475. package/src/plugins/builtin/volatility/dvol.ts +46 -0
  476. package/src/plugins/builtin/volatility/index.tsx +2 -2
  477. package/src/plugins/builtin/volatility/pane.tsx +4 -4
  478. package/src/plugins/builtin/volatility/series.ts +26 -0
  479. package/src/plugins/builtin/world-indices/footer.ts +1 -1
  480. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  481. package/src/plugins/catalog-browser.ts +13 -8
  482. package/src/plugins/catalog-manifest.ts +2 -1
  483. package/src/plugins/marketplace/model.ts +18 -5
  484. package/src/portfolio/column-values.ts +5 -5
  485. package/src/portfolio/futures-positions.ts +6 -3
  486. package/src/portfolio/position-quantity.ts +15 -9
  487. package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
  488. package/src/remote/resources.ts +2 -1
  489. package/src/renderers/browser/app-services.ts +1 -7
  490. package/src/renderers/browser/config-host.ts +2 -55
  491. package/src/renderers/browser/main.tsx +7 -57
  492. package/src/renderers/dom/data-table/row.tsx +26 -0
  493. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  494. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  495. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  496. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  497. package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
  498. package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
  499. package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
  500. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  501. package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
  502. package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
  503. package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
  504. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  505. package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
  506. package/src/renderers/electrobun/bun/index.ts +25 -1
  507. package/src/renderers/electrobun/shared/app-name.ts +30 -0
  508. package/src/renderers/electrobun/shared/protocol.ts +10 -0
  509. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  510. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
  511. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
  512. package/src/renderers/electrobun/view/main.tsx +24 -1
  513. package/src/renderers/electrobun/view/news-source.ts +3 -1
  514. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  515. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  516. package/src/renderers/opentui/start.tsx +5 -0
  517. package/src/renderers/opentui/toast-host.tsx +71 -43
  518. package/src/series/registry.ts +1 -0
  519. package/src/series/types.ts +6 -0
  520. package/src/series/validate.ts +1 -0
  521. package/src/sources/gloomberb-cloud/index.ts +1 -0
  522. package/src/sources/gloomberb-cloud/news.ts +47 -7
  523. package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
  524. package/src/sources/provider-router/batches.ts +10 -2
  525. package/src/sources/provider-router/financial-routes.ts +3 -3
  526. package/src/sources/provider-router/financials.ts +15 -3
  527. package/src/sources/provider-router/history.ts +28 -7
  528. package/src/sources/provider-router/index.ts +149 -10
  529. package/src/sources/provider-router/search.ts +13 -2
  530. package/src/state/app/context/index.tsx +3 -0
  531. package/src/state/ticker-cleanup.ts +50 -0
  532. package/src/sync/auth-transition.ts +19 -9
  533. package/src/sync/baseline.ts +10 -4
  534. package/src/sync/controller.ts +70 -19
  535. package/src/sync/core-contributors.ts +90 -31
  536. package/src/sync/react.ts +4 -1
  537. package/src/sync/types.ts +3 -0
  538. package/src/telemetry/stall-reports-dom.ts +38 -0
  539. package/src/telemetry/stall-reports-node.ts +75 -0
  540. package/src/telemetry/stall-reports.ts +214 -0
  541. package/src/telemetry/usage-counts.ts +0 -18
  542. package/src/test-support/preload.ts +14 -0
  543. package/src/theme/colors.ts +8 -0
  544. package/src/theme/themes.ts +10 -0
  545. package/src/tickers/company-name.ts +103 -3
  546. package/src/tickers/listing-venues.ts +38 -3
  547. package/src/tickers/search/bare-symbol.ts +36 -0
  548. package/src/tickers/search/index.ts +36 -7
  549. package/src/tickers/search/ranking.ts +10 -1
  550. package/src/tickers/search/types.ts +5 -0
  551. package/src/tickers/terminal-codes.ts +19 -0
  552. package/src/time-series/alignment.ts +1 -1
  553. package/src/time-series/base-series.ts +13 -5
  554. package/src/time-series/daily-valuation.ts +62 -0
  555. package/src/time-series/economic.ts +18 -4
  556. package/src/time-series/field-catalog.ts +2 -4
  557. package/src/time-series/filed-trailing-eps.ts +197 -0
  558. package/src/time-series/forward-valuation.ts +32 -0
  559. package/src/time-series/fundamentals.ts +198 -7
  560. package/src/time-series/hooks.ts +1 -0
  561. package/src/time-series/ice-credit.ts +3 -0
  562. package/src/time-series/price-comparison.ts +30 -2
  563. package/src/time-series/price-units.ts +72 -2
  564. package/src/time-series/resolve-context.ts +7 -1
  565. package/src/time-series/resolve.ts +116 -15
  566. package/src/time-series/series-presentation.ts +40 -6
  567. package/src/time-series/session-bar.ts +11 -0
  568. package/src/time-series/spec.ts +5 -3
  569. package/src/time-series/studies.ts +278 -74
  570. package/src/time-series/total-return.ts +160 -0
  571. package/src/time-series/transforms.ts +3 -3
  572. package/src/time-series/types.ts +27 -1
  573. package/src/time-series/valuation-releases.ts +39 -0
  574. package/src/types/data-provider.ts +9 -1
  575. package/src/types/desktop-window.ts +2 -0
  576. package/src/types/financials.ts +73 -2
  577. package/src/types/headless.ts +61 -5
  578. package/src/types/instrument.ts +3 -0
  579. package/src/types/news-source.ts +1 -0
  580. package/src/types/plugin.ts +10 -1
  581. package/src/ui/host.tsx +3 -0
  582. package/src/updater/command-names.ts +12 -7
  583. package/src/updater/failure.ts +4 -1
  584. package/src/utils/cli-output.ts +42 -1
  585. package/src/utils/crypto-pair.ts +44 -0
  586. package/src/utils/financial-statements.ts +6 -1
  587. package/src/utils/forward-dividend.ts +2 -0
  588. package/src/utils/fundamentals.ts +24 -3
  589. package/src/utils/fx-market-hours.ts +26 -3
  590. package/src/utils/main-thread-stall.ts +35 -2
  591. package/src/utils/market-capitalization.ts +24 -1
  592. package/src/utils/network-failure.ts +9 -6
  593. package/src/utils/option-expiry.ts +48 -1
  594. package/src/utils/options.ts +10 -0
  595. package/src/utils/plugin-proxy-hosts.json +1 -0
  596. package/src/utils/price-history.ts +12 -1
  597. package/src/utils/shell-word.ts +8 -0
  598. package/src/utils/trailing-dividend.ts +53 -4
  599. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
  600. package/src/renderers/browser/first-screen.ts +0 -193
@@ -1,7 +1,7 @@
1
1
  import { logFailure } from "../utils/log-failure";
2
2
  import type { NewsCapability } from "../capabilities";
3
3
  import type { ConnectionHealthRegistry } from "../core/connection-health";
4
- import type { NewsArticle, NewsQuery, NewsQueryState } from "./types";
4
+ import type { NewsAccess, NewsArticle, NewsQuery, NewsQueryState } from "./types";
5
5
  import { isAppVisible, subscribeAppVisibility } from "../state/app/activity";
6
6
  import {
7
7
  DEFAULT_GLOBAL_QUERY,
@@ -28,6 +28,18 @@ export interface NewsServiceOptions {
28
28
  connectionHealth?: ConnectionHealthRegistry;
29
29
  /** Defaults to the app's visibility; injectable for tests. */
30
30
  visibility?: { isVisible(): boolean; subscribe(listener: () => void): () => void };
31
+ /**
32
+ * The session the sources read news with. Its key changes when what the
33
+ * service answers changes: a session restored at launch, a sign-in or
34
+ * sign-out, an upgrade. Every open query is then read again and its stories
35
+ * replaced, so a feed read without the session is not left on screen.
36
+ */
37
+ session?: NewsSessionSignal;
38
+ }
39
+
40
+ export interface NewsSessionSignal {
41
+ key(): string;
42
+ subscribe(listener: () => void): () => void;
31
43
  }
32
44
 
33
45
  export type NewsQueryListener = (state: NewsQueryState) => void;
@@ -50,17 +62,30 @@ interface SourceFetchResult {
50
62
  failedSourceIds: string[];
51
63
  errors: string[];
52
64
  nextCursor: string | null;
65
+ access: NewsAccess | null;
53
66
  }
54
67
 
55
68
  interface NewsQueryEntry {
56
69
  query: NewsQuery;
57
70
  state: NewsQueryState;
58
71
  inFlight: Promise<NewsQueryState> | null;
72
+ /** The session generation the read in flight went out under. */
73
+ inFlightGeneration: number;
59
74
  loadMoreInFlight: Promise<void> | null;
75
+ loadMoreGeneration: number;
76
+ /** The session generation the stories on screen were read under. */
77
+ generation: number;
60
78
  refs: number;
61
79
  lastAccessedAt: number;
62
80
  }
63
81
 
82
+ /** Held back beats live: one source the service held back is enough to say the list is not the live wire. */
83
+ function combineNewsAccess(values: ReadonlyArray<NewsAccess | null | undefined>): NewsAccess | null {
84
+ if (values.includes("delayed")) return "delayed";
85
+ if (values.includes("realtime")) return "realtime";
86
+ return null;
87
+ }
88
+
64
89
  function newsCapabilityPriority(source: NewsCapability): number {
65
90
  return source.priority ?? 1000;
66
91
  }
@@ -85,6 +110,12 @@ export class NewsService {
85
110
  private readonly visibility: NonNullable<NewsServiceOptions["visibility"]>;
86
111
  private lastPollAt = 0;
87
112
  private unsubscribeVisibility: (() => void) | null = null;
113
+ private readonly session?: NewsSessionSignal;
114
+ private sessionKey: string | null = null;
115
+ /** Bumped when the session changes; a read from an older generation is dropped. */
116
+ private sessionGeneration = 0;
117
+ private sessionRefetchQueued = false;
118
+ private unsubscribeSession: (() => void) | null = null;
88
119
 
89
120
  constructor(options: NewsServiceOptions = {}) {
90
121
  const pollInterval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
@@ -94,6 +125,7 @@ export class NewsService {
94
125
  this.now = options.now ?? Date.now;
95
126
  this.connectionHealth = options.connectionHealth;
96
127
  this.visibility = options.visibility ?? { isVisible: isAppVisible, subscribe: subscribeAppVisibility };
128
+ this.session = options.session;
97
129
  }
98
130
 
99
131
  register(source: NewsCapability): () => void {
@@ -125,6 +157,10 @@ export class NewsService {
125
157
  this.clearPollTimer();
126
158
  this.scheduleNextPoll();
127
159
  });
160
+ if (this.session) {
161
+ this.sessionKey = this.session.key();
162
+ this.unsubscribeSession = this.session.subscribe(() => this.handleSessionChange());
163
+ }
128
164
  this.scheduleNextPoll();
129
165
  }
130
166
 
@@ -132,9 +168,46 @@ export class NewsService {
132
168
  this.polling = false;
133
169
  this.unsubscribeVisibility?.();
134
170
  this.unsubscribeVisibility = null;
171
+ this.unsubscribeSession?.();
172
+ this.unsubscribeSession = null;
135
173
  this.clearPollTimer();
136
174
  }
137
175
 
176
+ /**
177
+ * A new session answers with other news: the live wire once a Pro session
178
+ * is in place, the held-back one after signing out. Reads still out under
179
+ * the old session are dropped, open queries are read again and replace
180
+ * their stories rather than merge into them, and closed ones are forgotten.
181
+ * A token and its user arrive as separate changes, so the reads wait for
182
+ * the end of the tick and go out once.
183
+ */
184
+ private handleSessionChange(): void {
185
+ if (!this.session) return;
186
+ const key = this.session.key();
187
+ if (key === this.sessionKey) return;
188
+ this.sessionKey = key;
189
+ this.sessionGeneration++;
190
+ if (this.sessionRefetchQueued) return;
191
+ this.sessionRefetchQueued = true;
192
+ queueMicrotask(() => {
193
+ this.sessionRefetchQueued = false;
194
+ if (!this.polling) return;
195
+ let dropped = false;
196
+ for (const [queryKey, entry] of this.queries) {
197
+ if (entry.refs > 0) continue;
198
+ this.queries.delete(queryKey);
199
+ dropped = true;
200
+ }
201
+ if (dropped) {
202
+ this.rebuildArticlePool();
203
+ this.notify();
204
+ }
205
+ for (const entry of this.queries.values()) {
206
+ void this.refreshQuery(entry.query, true);
207
+ }
208
+ });
209
+ }
210
+
138
211
  private pollInFlight = false;
139
212
 
140
213
  private clearPollTimer(): void {
@@ -226,6 +299,7 @@ export class NewsService {
226
299
  return;
227
300
  }
228
301
 
302
+ const generation = this.sessionGeneration;
229
303
  const request = (async () => {
230
304
  entry.state = { ...entry.state, loadingMore: true };
231
305
  this.notify();
@@ -234,6 +308,12 @@ export class NewsService {
234
308
  ...normalized,
235
309
  cursor: entry.state.nextCursor ?? undefined,
236
310
  });
311
+ // An older page from another session must not join the list the new session read.
312
+ if (generation !== this.sessionGeneration || entry.generation !== generation) {
313
+ entry.state = { ...entry.state, loadingMore: false };
314
+ this.notify();
315
+ return;
316
+ }
237
317
  entry.state = {
238
318
  ...entry.state,
239
319
  loadingMore: false,
@@ -253,6 +333,7 @@ export class NewsService {
253
333
  }
254
334
  })();
255
335
  entry.loadMoreInFlight = request;
336
+ entry.loadMoreGeneration = generation;
256
337
  await request;
257
338
  }
258
339
 
@@ -318,7 +399,9 @@ export class NewsService {
318
399
  showLoading: boolean,
319
400
  ): Promise<NewsQueryState> {
320
401
  const entry = this.getOrCreateQueryEntry(query);
321
- if (entry.inFlight) return entry.inFlight;
402
+ const generation = this.sessionGeneration;
403
+ // A read that went out under an older session answers for that session: a new one goes out beside it.
404
+ if (entry.inFlight && entry.inFlightGeneration === generation) return entry.inFlight;
322
405
 
323
406
  const current = entry.state;
324
407
  if (showLoading) {
@@ -333,14 +416,17 @@ export class NewsService {
333
416
  const promise = (async () => {
334
417
  try {
335
418
  const result = await this.fetchFromSources(query);
419
+ if (generation !== this.sessionGeneration) return this.currentRead(entry);
336
420
  if (result.sourceIds.length === 0 && result.failedSourceIds.length > 0) {
337
421
  throw new Error("News sources unavailable.");
338
422
  }
339
- if (entry.loadMoreInFlight) return entry.state;
423
+ if (entry.loadMoreInFlight && entry.loadMoreGeneration === generation) return entry.state;
340
424
  const incoming = filterNewsArticlesForQuery(dedupeNewsArticles(result.articles), query);
425
+ // Stories read under another session are another feed (held back, or live): the new read replaces them.
426
+ const sameSession = entry.generation === generation;
341
427
  // 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)
428
+ const existing = !sameSession || (incoming.length > 0 && entry.state.articles.length > 0
429
+ && (incoming[0]!.searchedFor ?? null) !== (entry.state.articles[0]!.searchedFor ?? null))
344
430
  ? []
345
431
  : entry.state.articles;
346
432
  const incomingIds = new Set(incoming.map((article) => article.id));
@@ -363,13 +449,17 @@ export class NewsService {
363
449
  sourceIds: result.sourceIds,
364
450
  nextCursor: hasOlderPages ? entry.state.nextCursor : result.nextCursor,
365
451
  loadingMore: entry.state.loadingMore,
452
+ // A page that does not say keeps what this session's earlier answer said.
453
+ access: result.access ?? (sameSession ? entry.state.access ?? null : null),
366
454
  };
367
455
  entry.state = state;
456
+ entry.generation = generation;
368
457
  entry.lastAccessedAt = this.now();
369
458
  this.rebuildArticlePool();
370
459
  this.notify();
371
460
  return state;
372
461
  } catch (error) {
462
+ if (generation !== this.sessionGeneration) return this.currentRead(entry);
373
463
  const state: NewsQueryState = {
374
464
  ...current,
375
465
  phase: "error",
@@ -380,15 +470,21 @@ export class NewsService {
380
470
  this.notify();
381
471
  return state;
382
472
  } finally {
383
- entry.inFlight = null;
473
+ if (entry.inFlightGeneration === generation) entry.inFlight = null;
384
474
  this.pruneInactiveQueries();
385
475
  }
386
476
  })();
387
477
 
388
478
  entry.inFlight = promise;
479
+ entry.inFlightGeneration = generation;
389
480
  return promise;
390
481
  }
391
482
 
483
+ /** What a dropped read answers its caller: the current session's read when one is out, else the state as it is. */
484
+ private currentRead(entry: NewsQueryEntry): Promise<NewsQueryState> | NewsQueryState {
485
+ return entry.inFlight && entry.inFlightGeneration === this.sessionGeneration ? entry.inFlight : entry.state;
486
+ }
487
+
392
488
  private getOrCreateQueryEntry(query: NewsQuery): NewsQueryEntry {
393
489
  const key = buildNewsQueryKey(query);
394
490
  const now = this.now();
@@ -402,7 +498,10 @@ export class NewsService {
402
498
  query,
403
499
  state: createIdleNewsQueryState(),
404
500
  inFlight: null,
501
+ inFlightGeneration: this.sessionGeneration,
405
502
  loadMoreInFlight: null,
503
+ loadMoreGeneration: this.sessionGeneration,
504
+ generation: this.sessionGeneration,
406
505
  refs: 0,
407
506
  lastAccessedAt: now,
408
507
  };
@@ -451,7 +550,7 @@ export class NewsService {
451
550
  private async readSourcePage(
452
551
  source: NewsCapability,
453
552
  query: NewsQuery,
454
- ): Promise<{ articles: NewsArticle[]; nextCursor: string | null; error?: string | null }> {
553
+ ): Promise<{ articles: NewsArticle[]; nextCursor: string | null; error?: string | null; access?: NewsAccess | null }> {
455
554
  if (source.provider.fetchNewsPage) {
456
555
  const page = await this.trackSourceRequest(
457
556
  source,
@@ -462,6 +561,7 @@ export class NewsService {
462
561
  articles: page.articles.map((article) => markDetailCapableArticle(source, article)),
463
562
  nextCursor: page.nextCursor ?? null,
464
563
  error: page.error,
564
+ access: page.access,
465
565
  };
466
566
  }
467
567
  const articles = (await this.trackSourceRequest(
@@ -484,6 +584,7 @@ export class NewsService {
484
584
  failedSourceIds,
485
585
  errors: page.error ? [page.error] : [],
486
586
  nextCursor: page.nextCursor,
587
+ access: page.access ?? null,
487
588
  };
488
589
  if (page.articles.length > 0) return result;
489
590
  firstEmpty ??= result;
@@ -491,7 +592,7 @@ export class NewsService {
491
592
  failedSourceIds.push(newsCapabilitySourceId(source));
492
593
  }
493
594
  }
494
- return firstEmpty ?? { articles: [], sourceIds: [], failedSourceIds, errors: [], nextCursor: null };
595
+ return firstEmpty ?? { articles: [], sourceIds: [], failedSourceIds, errors: [], nextCursor: null, access: null };
495
596
  }
496
597
 
497
598
  private async fetchMergedNews(query: NewsQuery, sources: NewsCapability[]): Promise<SourceFetchResult> {
@@ -505,6 +606,7 @@ export class NewsService {
505
606
  const errors: string[] = [];
506
607
  const sourceIds: string[] = [];
507
608
  const failedSourceIds: string[] = [];
609
+ const access: Array<NewsAccess | null | undefined> = [];
508
610
  let nextCursor: string | null = null;
509
611
  settled.forEach((result, index) => {
510
612
  if (result.status !== "fulfilled") {
@@ -515,9 +617,10 @@ export class NewsService {
515
617
  articles.push(...result.value.page.articles);
516
618
  if (result.value.page.error) errors.push(result.value.page.error);
517
619
  sourceIds.push(newsCapabilitySourceId(result.value.source));
620
+ access.push(result.value.page.access);
518
621
  nextCursor ??= result.value.page.nextCursor;
519
622
  });
520
- return { articles, sourceIds, failedSourceIds, errors, nextCursor };
623
+ return { articles, sourceIds, failedSourceIds, errors, nextCursor, access: combineNewsAccess(access) };
521
624
  }
522
625
 
523
626
  private trackSourceRequest<T>(
@@ -550,6 +653,7 @@ export class NewsService {
550
653
  sourceIds: [...new Set([...entry.state.sourceIds, newsCapabilitySourceId(source)])],
551
654
  nextCursor: entry.state.nextCursor,
552
655
  loadingMore: false,
656
+ access: entry.state.access ?? null,
553
657
  };
554
658
  changed = true;
555
659
  }
@@ -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
@@ -81,11 +81,20 @@ export interface NewsQuery {
81
81
 
82
82
  type NewsQueryPhase = "idle" | "loading" | "ready" | "refreshing" | "error";
83
83
 
84
+ /**
85
+ * Which news a source answered with: the live wire, or the one held back for
86
+ * an account without real-time news. Read from the answer itself, never from
87
+ * the plan the app remembers.
88
+ */
89
+ export type NewsAccess = "realtime" | "delayed";
90
+
84
91
  export interface NewsPage {
85
92
  /** Active source failure, including a partial failure with usable articles. */
86
93
  error?: string | null;
87
94
  articles: NewsArticle[];
88
95
  nextCursor?: string | null;
96
+ /** Which news the source answered with; null or absent when the answer does not say. */
97
+ access?: NewsAccess | null;
89
98
  }
90
99
 
91
100
  export interface NewsQueryState {
@@ -96,4 +105,6 @@ export interface NewsQueryState {
96
105
  sourceIds: string[];
97
106
  nextCursor: string | null;
98
107
  loadingMore: boolean;
108
+ /** Which news the stories are, from the last answer; null or absent when it did not say. */
109
+ access?: NewsAccess | null;
99
110
  }
@@ -6,22 +6,24 @@ export const NOTIFICATION_LOG_LIMIT = 300;
6
6
 
7
7
  /**
8
8
  * The notices worth finding again after their toast is gone: a price alert or
9
- * a catalyst that fired, a high-impact economic print, and a team card. Chat
9
+ * a catalyst that fired, a high-impact economic print, a team card, and new
10
+ * matches of a live scan with its alerts on. Chat
10
11
  * keeps its own unread state on the account, and plain info, success and
11
12
  * error toasts are not logged.
12
13
  */
13
- export type MissedNoticeKind = "price" | "catalyst" | "econ" | "team";
14
+ export type MissedNoticeKind = "price" | "catalyst" | "econ" | "team" | "scan";
14
15
 
15
16
  const KIND_PREFIXES: ReadonlyArray<readonly [MissedNoticeKind, string]> = [
16
17
  ["price", "price:"],
17
18
  ["catalyst", "catalyst:"],
18
19
  ["econ", "econ:"],
19
20
  ["team", "team:"],
21
+ ["scan", "scan:"],
20
22
  ];
21
23
 
22
24
  export interface NotificationLogEntry {
23
25
  id: string;
24
- /** `price:<alertId>`, `catalyst:<eventId>`, `econ:<eventId>` or `team:<cardId>`. A later notice with the same ref updates this row. */
26
+ /** `price:<alertId>`, `catalyst:<eventId>`, `econ:<eventId>`, `team:<cardId>` or `scan:<paneId>`. A later notice with the same ref updates this row. */
25
27
  refId: string;
26
28
  title?: string;
27
29
  body: string;
@@ -137,7 +139,7 @@ export function unreadNotificationLogCount(log: readonly NotificationLogEntry[]
137
139
 
138
140
  /**
139
141
  * Records a notice whose `refId` names a price alert, a catalyst, an economic
140
- * release or a team card; anything else returns null and is not kept. The same ref replaces its
142
+ * release, a team card or a live scan; anything else returns null and is not kept. The same ref replaces its
141
143
  * row: a price alert that fires again is new and unread again.
142
144
  */
143
145
  export function appendNotificationLog(
@@ -1,4 +1,7 @@
1
+ import { metricDescription, readAlertMetric, validMetricRule } from "./metric-model";
1
2
  import type { AlertCondition, AlertRule } from "./types";
3
+ import { alertListing } from "./format";
4
+ import { canonicalExchange } from "../../../utils/exchanges";
2
5
  import { logFailure } from "../../../utils/log-failure";
3
6
 
4
7
  export type { AlertRule };
@@ -32,16 +35,21 @@ export function editAlert(
32
35
  targetPrice: number,
33
36
  ): AlertRule {
34
37
  const nextSymbol = symbol.trim().toUpperCase();
38
+ const sameListing = nextSymbol === alert.symbol.trim().toUpperCase();
35
39
  return {
36
40
  id: alert.id,
37
41
  symbol: nextSymbol,
38
- 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 } : {}),
39
46
  condition,
40
47
  targetPrice,
41
48
  createdAt: alert.createdAt,
42
49
  armedAt: Date.now(),
43
50
  status: "active",
44
51
  message: alert.message,
52
+ ...(alert.metric ? { metric: alert.metric } : {}),
45
53
  };
46
54
  }
47
55
 
@@ -49,8 +57,21 @@ export function rearmAlert(alert: AlertRule): AlertRule {
49
57
  return editAlert(alert, alert.symbol, alert.condition, alert.targetPrice);
50
58
  }
51
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
+
52
73
  export function evaluateAlert(alert: AlertRule, currentPrice: number): boolean {
53
- if (alert.status !== "active") return false;
74
+ if (alert.metric || alert.status !== "active") return false;
54
75
 
55
76
  switch (alert.condition) {
56
77
  case "above":
@@ -69,6 +90,7 @@ export function evaluateAlert(alert: AlertRule, currentPrice: number): boolean {
69
90
  }
70
91
 
71
92
  export function formatAlertDescription(alert: AlertRule): string {
93
+ if (alert.metric) return metricDescription(alert);
72
94
  const prefix = alert.condition === "above" ? ">"
73
95
  : alert.condition === "below" ? "<" : "↕";
74
96
  return `${alert.symbol} ${prefix} ${alert.targetPrice}`;
@@ -94,6 +116,7 @@ function readAlerts(json: unknown): { alerts: AlertRule[]; error: string | null
94
116
  a != null && typeof a.id === "string" && !!a.id && typeof a.symbol === "string" && !!a.symbol
95
117
  && typeof a.condition === "string" && !!a.condition && typeof a.targetPrice === "number"
96
118
  && (a.exchange == null || typeof a.exchange === "string")
119
+ && (a.metric == null || (readAlertMetric(a.metric) != null && validMetricRule(a)))
97
120
  ));
98
121
  return {
99
122
  alerts,
@@ -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
+ }
@@ -5,6 +5,7 @@
5
5
  // two cases of the same alert, whose dates come from their own calendars.
6
6
  import { dateKey, eventCountry, matchesCountry, type CountryFilter } from "../econ/calendar-model";
7
7
  import { isAllDayEvent, hasNoReleaseTime, type EconEvent, type EconImpact } from "../econ/types";
8
+ import { RENAMED_RELEASES } from "../shared/macro-releases";
8
9
  import { MACRO_EVENT_NAMES } from "./research-builder";
9
10
  import type { MacroAlertEvent } from "./research-rules";
10
11
 
@@ -52,6 +53,39 @@ export function calendarNameWords(name: string): string[] {
52
53
 
53
54
  const nameKey = (name: string) => calendarNameWords(name).join(" ");
54
55
 
56
+ /** A release series' former name a typed name used, read as its current one. */
57
+ export interface ReleaseRename {
58
+ former: string;
59
+ current: string;
60
+ since: string;
61
+ }
62
+
63
+ /**
64
+ * A name's words with a renamed series' former name put as its current one
65
+ * (`caixin pmi` as `ratingdog pmi`), and the rename; null for a name that
66
+ * carries none.
67
+ */
68
+ function withCurrentName(words: readonly string[]): { words: string[]; rename: ReleaseRename } | null {
69
+ for (const { former, current, since } of RENAMED_RELEASES) {
70
+ for (const name of former) {
71
+ const old = calendarNameWords(name);
72
+ for (let at = 0; at + old.length <= words.length; at += 1) {
73
+ if (!old.every((word, index) => words[at + index] === word)) continue;
74
+ return {
75
+ words: [...words.slice(0, at), ...calendarNameWords(current), ...words.slice(at + old.length)],
76
+ rename: { former: name, current, since },
77
+ };
78
+ }
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+
84
+ /** "Caixin releases were renamed RatingDog from the August 2025 survey." */
85
+ export function releaseRenameNote({ former, current, since }: ReleaseRename): string {
86
+ return `${former} releases were renamed ${current} from ${since}.`;
87
+ }
88
+
55
89
  /** The UTC day the calendar lists a row under. */
56
90
  const utcDay = (time: number) => new Date(time).toISOString().slice(0, 10);
57
91
 
@@ -121,13 +155,17 @@ function calendarChoices(events: readonly EconEvent[], now: number): RankedChoic
121
155
  }
122
156
 
123
157
  /** What a typed release names in the calendar. */
124
- export type CalendarEventMatch =
158
+ export type CalendarEventMatch = (
125
159
  /** One release, the best of those the words name, and the others they name in the same country. */
126
160
  | { kind: "match"; choice: CalendarEventChoice; others: CalendarEventChoice[] }
127
161
  /** The words name releases in several countries: the best of each. */
128
162
  | { kind: "countries"; choices: CalendarEventChoice[] }
129
163
  /** Nothing the words name; the nearest names, best first. */
130
- | { kind: "none"; suggestions: CalendarEventChoice[] };
164
+ | { kind: "none"; suggestions: CalendarEventChoice[] }
165
+ ) & {
166
+ /** The former name the words used, read as the series' current one (`Caixin PMI` as RatingDog's). */
167
+ renamed?: ReleaseRename;
168
+ };
131
169
 
132
170
  /** Character pairs of a name, for how near a slip is to it. */
133
171
  function pairs(text: string): Map<string, number> {
@@ -186,7 +224,8 @@ function oneSlipApart(left: string, right: string): boolean {
186
224
  * then the closest name and the soonest date, so `NBS PMI` is the headline
187
225
  * NBS Manufacturing PMI and an old name listed only in the past never beats
188
226
  * the live one. Several countries ask which; no match offers the nearest
189
- * names.
227
+ * names. A series' former name reads as its current one (`Caixin PMI` is
228
+ * RatingDog's), and the match says so.
190
229
  */
191
230
  export function matchCalendarEvent(
192
231
  query: string,
@@ -194,7 +233,18 @@ export function matchCalendarEvent(
194
233
  events: readonly EconEvent[],
195
234
  now: number,
196
235
  ): CalendarEventMatch {
197
- const words = calendarNameWords(query);
236
+ const typedWords = calendarNameWords(query);
237
+ const current = withCurrentName(typedWords);
238
+ const match = matchCalendarWords(current?.words ?? typedWords, country, events, now);
239
+ return current ? { ...match, renamed: current.rename } : match;
240
+ }
241
+
242
+ function matchCalendarWords(
243
+ words: readonly string[],
244
+ country: CountryFilter | null,
245
+ events: readonly EconEvent[],
246
+ now: number,
247
+ ): CalendarEventMatch {
198
248
  const choices = calendarChoices(country ? events.filter((event) => matchesCountry(event, country)) : events, now);
199
249
  const exact = choices.filter((choice) => choice.words.join(" ") === words.join(" "));
200
250
  const naming = (typed: readonly string[], loose: boolean) => typed.length === 0 ? [] : choices.filter((choice) =>
@@ -266,9 +316,10 @@ function renamedBy(words: readonly string[], other: readonly string[]): number |
266
316
  */
267
317
  function releaseRows(target: CalendarReleaseTarget, events: readonly EconEvent[]): EconEvent[] {
268
318
  const words = calendarNameWords(target.event);
269
- const key = words.join(" ");
319
+ // An alert kept under a series' former name follows the rows under its current one.
320
+ const keys = new Set([words.join(" "), withCurrentName(words)?.words.join(" ")]);
270
321
  const own = events.filter((event) => eventCountry(event) === target.country);
271
- const named = own.filter((event) => nameKey(event.event) === key);
322
+ const named = own.filter((event) => keys.has(nameKey(event.event)));
272
323
  if (!target.publisher) return named;
273
324
  const namedDays = new Set(named.map((event) => dateKey(event.date)));
274
325
  const renamed = own.flatMap((event) => {