@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
@@ -40,7 +40,6 @@ import {
40
40
  DEFAULT_ISSUER_SORT,
41
41
  DEFAULT_TRADE_SORT,
42
42
  ISSUER_SORT_COLUMN_IDS,
43
- TRADE_SORT_COLUMN_IDS,
44
43
  buildIssuerColumns,
45
44
  buildTradeColumns,
46
45
  cdsFreshness,
@@ -48,9 +47,11 @@ import {
48
47
  formatEventTime,
49
48
  formatMaturity,
50
49
  formatNotional,
50
+ formatPoints,
51
51
  formatQuoted,
52
52
  formatQuotedMove,
53
53
  formatUpfront,
54
+ hasPointsQuotes,
54
55
  issuerListSort,
55
56
  lastTradeOf,
56
57
  noLevelMessage,
@@ -63,6 +64,7 @@ import {
63
64
  spreadChartPoints,
64
65
  spreadFigures,
65
66
  summarizeIssuers,
67
+ tradeSortColumnIds,
66
68
  tradesForIssuer,
67
69
  withSpreadLevels,
68
70
  type CdsIssuerSummary,
@@ -138,6 +140,11 @@ function renderTradeCell(row: CdsTrade, column: TradeColumn, lineMaturity: strin
138
140
  text: formatBp(row.spreadBp),
139
141
  color: dimmed ?? (row.spreadBp == null ? colors.textDim : colors.textBright),
140
142
  };
143
+ case "points":
144
+ return {
145
+ text: formatPoints(row.pointsUpfront),
146
+ color: dimmed ?? (row.pointsUpfront == null ? colors.textDim : colors.textBright),
147
+ };
141
148
  case "upfront":
142
149
  return {
143
150
  text: formatUpfront(row),
@@ -175,7 +182,8 @@ function CdsTradeTable({
175
182
  /** What an empty table says: the window and the name searched. */
176
183
  emptyMessage: string;
177
184
  }) {
178
- const columns = useMemo(() => buildTradeColumns(width), [width]);
185
+ const points = hasPointsQuotes(trades);
186
+ const columns = useMemo(() => buildTradeColumns(width, points), [points, width]);
179
187
  const renderTradeRow = useCallback((trade: CdsTrade, column: TradeColumn) => (
180
188
  renderTradeCell(trade, column, lineMaturity)
181
189
  ), [lineMaturity]);
@@ -276,7 +284,7 @@ function IssuerHeader({ issuer, line, trades, width, height, focused, loading, s
276
284
  });
277
285
  return (
278
286
  <ChartTableHeader width={width} height={height} tableRows={trades.length} figures={figures}
279
- tableColumns={buildTradeColumns(width)}
287
+ tableColumns={buildTradeColumns(width, hasPointsQuotes(trades))}
280
288
  chart={points.length >= 2 || loading || why ? {
281
289
  series,
282
290
  formatValue: (value: number) => formatQuoted(unit, value),
@@ -388,12 +396,13 @@ export function CdsPane({
388
396
  setDetailOpen(false);
389
397
  }, [issuerQuery, issuers, selectedIssuerKey]);
390
398
 
399
+ const tradePoints = hasPointsQuotes(visibleTrades);
391
400
  const cycleTradeSort = useCallback((step: 1 | -1) => {
392
401
  setTradeSort((current) => {
393
- const next = cycleSortPreference<TradeColumnId>(TRADE_SORT_COLUMN_IDS, current, step);
402
+ const next = cycleSortPreference<TradeColumnId>(tradeSortColumnIds(tradePoints), current, step);
394
403
  return { columnId: next.columnId ?? DEFAULT_TRADE_SORT.columnId, direction: next.direction };
395
404
  });
396
- }, []);
405
+ }, [tradePoints]);
397
406
  const cycleIssuerSort = useCallback((step: 1 | -1) => {
398
407
  setIssuerSort((current) => {
399
408
  const next = cycleSortPreference<IssuerColumnId>(ISSUER_SORT_COLUMN_IDS, current, step);
@@ -451,6 +460,8 @@ export function CdsPane({
451
460
  query: issuerQuery ? activity?.query ?? issuerQuery : selectedSummary?.issuer ?? null,
452
461
  issuer: issuerQuery ? activity?.issuer ?? null : selectedSummary?.issuer ?? null,
453
462
  days: activity?.days ?? CDS_TRADE_DAYS,
463
+ // A name the covered issuers do not have, once the tape has neither trades nor a line under it.
464
+ unmatched: issuerQuery && activity?.unmatched && !trades.length && history.data && !history.data.issuer ? activity.unmatched : null,
454
465
  });
455
466
 
456
467
  if (status === "loading" && !activity) {
@@ -11,6 +11,7 @@ import {
11
11
  } from "../../../components";
12
12
  import { DetailReader } from "../../../components/reader/detail-reader";
13
13
  import { loadingErrorFooterInfo, usePaneRefreshKey } from "../../../components/data-table/table-pane";
14
+ import { createTtlCache } from "../../../data/ttl-cache";
14
15
  import { fetchChangelogReleases, type ChangelogRelease } from "../../../updater/github-releases";
15
16
  import { colors } from "../../../theme/colors";
16
17
  import type { PaneProps } from "../../../types/plugin";
@@ -32,7 +33,8 @@ const CHANGELOG_CACHE_TTL_MS = 10 * 60 * 1000;
32
33
 
33
34
  // Process-lifetime cache. Move it into the updater module if other
34
35
  // surfaces start reading releases too.
35
- let cachedReleases: { releases: ChangelogRelease[]; fetchedAt: number } | null = null;
36
+ const releaseCache = createTtlCache<ChangelogRelease[]>({ ttlMs: CHANGELOG_CACHE_TTL_MS });
37
+ const RELEASES = "releases";
36
38
 
37
39
  type ChangelogColumn = DataTableColumn & { id: ChangelogColumnId };
38
40
  type LoadStatus = "loading" | "loaded" | "error";
@@ -83,10 +85,10 @@ function ChangelogDetail({
83
85
  function ChangelogPane({ focused, width, height }: PaneProps) {
84
86
  const paneInstance = usePaneInstance();
85
87
  const requestedVersion = paneInstance?.params?.version ?? null;
86
- const [releases, setReleases] = useState<ChangelogRelease[]>(() => cachedReleases?.releases ?? []);
88
+ const [releases, setReleases] = useState<ChangelogRelease[]>(() => releaseCache.peek(RELEASES, { stale: true }) ?? []);
87
89
  // The mount effect starts the request immediately, so the first paint is a
88
90
  // spinner and never "No changelog entries found".
89
- const [status, setStatus] = useState<LoadStatus>(() => (cachedReleases ? "loaded" : "loading"));
91
+ const [status, setStatus] = useState<LoadStatus>(() => (releaseCache.peek(RELEASES, { stale: true }) ? "loaded" : "loading"));
90
92
  const [error, setError] = useState<string | null>(null);
91
93
  // The selected row and the open release are pane state, so a reload or a
92
94
  // shared layout comes back to the same release.
@@ -98,8 +100,9 @@ function ChangelogPane({ focused, width, height }: PaneProps) {
98
100
  const requestedVersionOpenedRef = useRef(false);
99
101
 
100
102
  const loadReleases = useCallback(async (force = false) => {
101
- if (!force && cachedReleases && Date.now() - cachedReleases.fetchedAt < CHANGELOG_CACHE_TTL_MS) {
102
- setReleases(cachedReleases.releases);
103
+ const fresh = force ? undefined : releaseCache.peek(RELEASES);
104
+ if (fresh) {
105
+ setReleases(fresh);
103
106
  setError(null);
104
107
  setStatus("loaded");
105
108
  return;
@@ -118,7 +121,7 @@ function ChangelogPane({ focused, width, height }: PaneProps) {
118
121
 
119
122
  try {
120
123
  const nextReleases = await fetchChangelogReleases(CHANGELOG_LIMIT, controller.signal);
121
- cachedReleases = { releases: nextReleases, fetchedAt: Date.now() };
124
+ releaseCache.set(RELEASES, nextReleases);
122
125
  setReleases(nextReleases);
123
126
  setStatus("loaded");
124
127
  } catch (loadError) {
@@ -31,6 +31,8 @@ export interface CatalogSeriesRow {
31
31
  /** The registered expression, `{ticker}` where a symbol goes. */
32
32
  template: string;
33
33
  url?: string;
34
+ /** A limit the publisher sets on the series' data, such as how far back its history goes. */
35
+ limitation?: string;
34
36
  /** Asks for a symbol before charting. */
35
37
  input?: SeriesInput;
36
38
  /** The symbol a row filled in for a typed ticker charts. */
@@ -120,6 +122,7 @@ function rowFromEntry(entry: SeriesCatalogEntry): Omit<CatalogSeriesRow, "order"
120
122
  expression,
121
123
  template: entry.expression,
122
124
  ...(entry.url ? { url: entry.url } : {}),
125
+ ...(entry.limitation ? { limitation: entry.limitation } : {}),
123
126
  ...(entry.input ? { input: entry.input } : {}),
124
127
  ...(entry.placement || entry.lines?.length ? {
125
128
  seriesName: entry.name,
@@ -81,6 +81,10 @@ export function applyChartComposerCapabilityOptions(
81
81
  next = { ...next, viewport: { ...next.viewport, extendedHours: true } };
82
82
  }
83
83
 
84
+ if (capabilityId === "price-comparison" && options.totalReturn === true) {
85
+ next = { ...next, viewport: { ...next.viewport, totalReturn: true } };
86
+ }
87
+
84
88
  if (PRICE_CAPABILITIES.has(capabilityId) && options.axisMode) {
85
89
  const transform = options.axisMode === "percent" ? "percent" : "raw";
86
90
  next = {
@@ -4,6 +4,7 @@ import { Box } from "../../../ui";
4
4
  import {
5
5
  DataTableView,
6
6
  QueryBar,
7
+ usePaneNoticeFooter,
7
8
  usePaneTabs,
8
9
  useQueryBarSearch,
9
10
  type DataTableCell,
@@ -242,6 +243,12 @@ export function DataCatalogPane({ focused, width, height }: PaneProps) {
242
243
  }
243
244
  }, []);
244
245
 
246
+ usePaneNoticeFooter({
247
+ registrationId: `${DATA_CATALOG_PANE_ID}:notices`,
248
+ focused,
249
+ notices: selectedRow?.limitation ? [`${selectedRow.label}: ${selectedRow.limitation}`] : [],
250
+ });
251
+
245
252
  usePaneStatusLinkFooter({
246
253
  registrationId: DATA_CATALOG_PANE_ID,
247
254
  focused,
@@ -10,7 +10,7 @@ import type {
10
10
  SeriesTimestampMode,
11
11
  SeriesTransform,
12
12
  } from "../../../../time-series/types";
13
- import { hasStudyInputCount, validateChartSpec } from "../../../../time-series/spec";
13
+ import { hasStudyInputCount, isOhlcSeriesStyle, validateChartSpec } from "../../../../time-series/spec";
14
14
  import { isPlainKey } from "../../../../utils/keyboard";
15
15
  import { getSharedRegistry } from "../../../registry";
16
16
  import {
@@ -415,7 +415,7 @@ export function useSeriesEditorController({
415
415
  : current.panels,
416
416
  series: replaceAt(current.series, selectedIndex, (() => {
417
417
  const currentSeries = current.series[selectedIndex]!;
418
- const ohlcStyle = currentSeries.style === "candles" || currentSeries.style === "ohlc" || currentSeries.style === "hlc";
418
+ const ohlcStyle = isOhlcSeriesStyle(currentSeries.style);
419
419
  return {
420
420
  ...currentSeries,
421
421
  style: transform !== "raw" && ohlcStyle
@@ -21,6 +21,16 @@ function normalized(instrument: InstrumentRef): InstrumentRef {
21
21
  const key = (instrument: InstrumentRef) => instrumentIdentityKey(normalized(instrument));
22
22
  const publicKey = (instrument: InstrumentRef) => publicTickerKey(instrument.symbol, instrument.exchange);
23
23
 
24
+ /**
25
+ * One listing however a series names it: the same symbol and broker contract, on the same venue
26
+ * or with the venue left out on either side (`AAPL` added beside a revenue line on `AAPL:NASDAQ`).
27
+ */
28
+ function sameListing(left: InstrumentRef, right: InstrumentRef): boolean {
29
+ const a = normalized(left), b = normalized(right);
30
+ return key({ ...a, exchange: "" }) === key({ ...b, exchange: "" })
31
+ && (!a.exchange || !b.exchange || canonicalExchange(a.exchange) === canonicalExchange(b.exchange));
32
+ }
33
+
24
34
  /** Bootstrap old charts once; persisted IDs keep ownership through comparison collisions. */
25
35
  export function resolveFollowSeriesIds(
26
36
  spec: ChartSpec,
@@ -44,8 +54,10 @@ export function resolveFollowSeriesIds(
44
54
  || (next && securities.find(series => series.source.kind === "security" && publicKey(normalized(series.source.instrument)) === publicKey(next)))
45
55
  || securities[0];
46
56
  if (!owner || owner.source.kind !== "security") return [];
47
- const ownerKey = key(owner.source.instrument);
48
- return securities.filter(series => series.source.kind === "security" && key(series.source.instrument) === ownerKey).map(series => series.id);
57
+ const ownerInstrument = owner.source.instrument;
58
+ // Every field of the followed ticker moves with it (revenue and total debt); other tickers stay.
59
+ return securities.filter(series => series.source.kind === "security" && sameListing(series.source.instrument, ownerInstrument))
60
+ .map(series => series.id);
49
61
  }
50
62
 
51
63
  /** Only the followed series move; other contracts and comparisons stay authored. */
@@ -138,6 +138,12 @@ export const paneSchemas = {
138
138
  ],
139
139
  defaultValue: "percent",
140
140
  },
141
+ {
142
+ key: "totalReturn",
143
+ description: "Reinvest each cash distribution at its ex-date close, so the returns are total returns, not price returns.",
144
+ type: "boolean",
145
+ defaultValue: false,
146
+ },
141
147
  {
142
148
  key: "chartResolution",
143
149
  description: BAR_SIZE_DESCRIPTION,
@@ -151,7 +157,7 @@ export const paneSchemas = {
151
157
  discovery: {
152
158
  id: "price-comparison",
153
159
  aliases: ["stock comparison", "price performance comparison", "returns comparison", "compare stock prices"],
154
- limitations: ["Compares closing-price changes in each listing's currency over shared observation dates. Cash distributions and FX conversion are excluded; these are not reinvested total returns."],
160
+ limitations: ["Compares closing-price changes in each listing's currency over shared observation dates. Cash distributions and FX conversion are excluded unless totalReturn is set, which reinvests each distribution at its ex-date close; FX conversion is never applied."],
155
161
  screenshotReadiness: "ready",
156
162
  },
157
163
  },
@@ -228,6 +228,8 @@ export async function loadChartPaneModel(
228
228
  ...reported,
229
229
  ...name ? { name } : {},
230
230
  ...valueDecimals !== undefined ? { textDecimals: valueDecimals } : {},
231
+ // A company's reported total (revenue, market cap) reads as 63.33B in the report, as in the legend and the tables.
232
+ ...series.unitGroup.split(":")[0] === "currency-total" ? { textCompact: true } : {},
231
233
  derived: !ids.has(series.id),
232
234
  points: series.points.map((point) => ({
233
235
  ...point,
@@ -251,7 +253,7 @@ export async function loadChartPaneModel(
251
253
  ...(chart.priceHistoryIntegrity?.length ? { priceHistoryIntegrity: chart.priceHistoryIntegrity } : {}),
252
254
  ...(valuationPriceIssues.length ? { valuationPriceIssues } : {}),
253
255
  ...(periodCoverage.length ? { periodCoverage } : {}),
254
- notices: [...chart.warnings.filter((warning) => warning === FINANCIAL_VINTAGE_NOTICE || warning === SEC_EPS_BASIS_NOTICE || warning === chart.priceComparison?.notice
256
+ notices: [...chart.coverageNotices ?? [], ...chart.returnBasisNotice ? [chart.returnBasisNotice] : [], ...chart.warnings.filter((warning) => warning === FINANCIAL_VINTAGE_NOTICE || warning === SEC_EPS_BASIS_NOTICE || warning === chart.priceComparison?.notice
255
257
  || chart.delistings?.some((delisting) => delisting.notice === warning)), ...integrityNotices],
256
258
  priceComparison: chart.priceComparison ?? null,
257
259
  summaries: chart.series.filter((series) => !series.profile).map((series) => ({ id: series.id, ...summarizeResolvedSeries(series) })),
@@ -723,6 +725,9 @@ export function chartHeadless(template: keyof typeof paneSchemas): HeadlessPaneD
723
725
  }, context.marketData) };
724
726
  }
725
727
  const model = await loadChartPaneModel(spec, context);
728
+ // A bare coin ticker charted as another listing (ETH, the Grayscale fund) says where the coin is, as `quote` does.
729
+ const coinNotes = (await Promise.all(listings.values())).flatMap((read) => read?.coinNote ?? []);
730
+ if (coinNotes.length) model.warnings = [...model.warnings ?? [], ...coinNotes];
726
731
  if (template === "graph-intraday-price-pane" && !intradayHistories.length && model.chart.viewport) {
727
732
  const { start, end } = model.chart.viewport;
728
733
  for (const { target, data } of [
@@ -10,7 +10,6 @@ import { parseTickerListInput } from "../../../tickers/list";
10
10
  import { canonicalExchange, parsePublicTickerKey, publicTickerKey } from "../../../utils/exchanges";
11
11
  import { tickerInstrumentLabel } from "../../../tickers/instrument-label";
12
12
  import type { ChartSpec } from "../../../time-series/types";
13
- import { isPriceOnlyMarketFieldId } from "../../../time-series/field-catalog";
14
13
  import { ChartComposerPane, ChartComposerResearchTab } from "./pane";
15
14
  import { DataCatalogPane } from "./data-catalog-pane";
16
15
  import { listFieldSeries } from "./field-series";
@@ -453,7 +452,7 @@ const chartComposerTemplates: PaneTemplateDef[] = [
453
452
  prefix: "CMP",
454
453
  aliases: ["COMP", "TRA"],
455
454
  label: "Comparison Chart",
456
- description: "Compare price returns over shared observation dates for two or more tickers. A leveraged or inverse fund gets its daily objective drawn from its index fund (3× daily QQQ beside TQQQ) and its decay against it.",
455
+ description: "Compare price returns over shared observation dates for two or more tickers, or total returns with distributions reinvested. A leveraged or inverse fund gets its daily objective drawn from its index fund (3× daily QQQ beside TQQQ) and its decay against it.",
457
456
  // Leveraged and inverse funds: the daily line and the decay come with the comparison.
458
457
  keywords: ["leveraged", "inverse", "decay", "daily-reset", "daily reset", "volatility decay"],
459
458
  argKind: "ticker-list",
@@ -484,9 +483,10 @@ const chartComposerTemplates: PaneTemplateDef[] = [
484
483
  ];
485
484
 
486
485
  /**
487
- * A price chart of one security (GP, GIP, a custom price chart of one ticker) can follow a list.
488
- * A comparison would move only its first ticker, a FRED-only chart has no ticker to follow, and
489
- * fundamental and valuation graphs (GF, GE) stay out even for one ticker.
486
+ * A chart whose securities are all one ticker can follow a list: GP, GIP, and GF or GE of one
487
+ * ticker, whatever fields it shows (revenue beside total debt). Every series of that ticker moves
488
+ * to the selected one and keeps its field; FRED and other series stay as they are. A comparison
489
+ * of several tickers would move only one of them, and a FRED-only chart has no ticker to follow.
490
490
  */
491
491
  function chartShowsOneSecurity(pane: PaneInstanceConfig): boolean {
492
492
  const spec = parseChartSpec(pane.settings?.[CHART_SPEC_SETTING_KEY]);
@@ -495,7 +495,6 @@ function chartShowsOneSecurity(pane: PaneInstanceConfig): boolean {
495
495
  const symbols = new Set<string>();
496
496
  for (const series of spec.series) {
497
497
  if (series.source.kind !== "security") continue;
498
- if (!isPriceOnlyMarketFieldId(series.source.fieldId)) return false;
499
498
  symbols.add(parsePublicTickerKey(series.source.instrument.symbol).symbol.toUpperCase());
500
499
  }
501
500
  return symbols.size === 1;
@@ -55,7 +55,9 @@ import {
55
55
  import { colors } from "../../../theme/colors";
56
56
  import { isUsListingExchange, parsePublicTickerKey, publicTickerKey } from "../../../utils/exchanges";
57
57
  import { getTimeSeriesField, isMarketFieldId } from "../../../time-series/field-catalog";
58
- import { isDatasetChart } from "../../../time-series/spec";
58
+ import { isDatasetChart, isOhlcSeriesStyle } from "../../../time-series/spec";
59
+ import { useThemeColors } from "../../../theme/theme-context";
60
+ import { priceComparisonSeriesIds } from "../../../time-series/price-comparison";
59
61
  import { seriesMessageName, studyMessageName } from "../../../time-series/series-name";
60
62
  import { CHART_COMPOSER_PANE_ID } from "../../../types/config";
61
63
  import { useRemoteUiNode } from "../../../remote/semantic-tree";
@@ -117,6 +119,8 @@ import {
117
119
  CHART_BAR_SIZE_PRESETS,
118
120
  CHART_RANGES as RANGES,
119
121
  CHART_RESOLUTIONS,
122
+ CHART_CANDLE_COLOURS_SETTING_KEY,
123
+ chartCandleColours,
120
124
  chartStudyOptionsFor,
121
125
  chartStudyPeriodTitle,
122
126
  chartStudySettingLabel,
@@ -342,6 +346,14 @@ function ChartComposerSurface({
342
346
  // plausible time is dropped without pinning. This handle only sets the
343
347
  // pin; the chart it opens on is what shows and keeps it.
344
348
  const [pinnedMoment, setPinnedMoment] = usePaneSettingValue<unknown>(CHART_PINNED_MOMENT_SETTING_KEY, null);
349
+ const [candleColoursSetting] = usePaneSettingValue<unknown>(CHART_CANDLE_COLOURS_SETTING_KEY, null);
350
+ const gainColour = useThemeColors().positive;
351
+ // Rising candles in the gain colour; falling ones already take the loss colour.
352
+ const candleColour = useCallback((list: ResolvedSeries[]) => (
353
+ chartCandleColours(candleColoursSetting) === "change"
354
+ ? list.map((entry) => isOhlcSeriesStyle(entry.style) ? { ...entry, color: gainColour } : entry)
355
+ : list
356
+ ), [candleColoursSetting, gainColour]);
345
357
  const momentLink = useTabCursorLink(paneId ? `${paneId}:moment` : null, { viewer: false });
346
358
  useEffect(() => {
347
359
  if (pinnedMoment == null || !momentLink?.pin) return;
@@ -475,9 +487,9 @@ function ChartComposerSurface({
475
487
  });
476
488
  const resolvedIds = new Set(resolved.map((entry) => entry.id));
477
489
  const missing = spec.series.filter((entry) => !resolvedIds.has(entry.id));
478
- if (missing.length === 0) return resolved;
490
+ if (missing.length === 0) return candleColour(resolved);
479
491
  const bySpecOrder = new Map(spec.series.map((entry, index) => [entry.id, index] as const));
480
- return [
492
+ return candleColour([
481
493
  ...resolved,
482
494
  ...missing.map((entry): ResolvedSeries => ({
483
495
  id: entry.id,
@@ -495,15 +507,15 @@ function ChartComposerSurface({
495
507
  hidden: true,
496
508
  points: [],
497
509
  })),
498
- ].sort((a, b) => (bySpecOrder.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (bySpecOrder.get(b.id) ?? Number.MAX_SAFE_INTEGER));
499
- }, [listings.names, nameSeries, resolution.legendSeries, spec.series]);
510
+ ].sort((a, b) => (bySpecOrder.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (bySpecOrder.get(b.id) ?? Number.MAX_SAFE_INTEGER)));
511
+ }, [candleColour, listings.names, nameSeries, resolution.legendSeries, spec.series]);
500
512
  const plottedSeries = useMemo(
501
- () => projectVisibleChartSeries(
513
+ () => candleColour(projectVisibleChartSeries(
502
514
  spec,
503
515
  resolution.bufferedSeries ?? resolution.series,
504
516
  resolution.legendSeries,
505
- ),
506
- [resolution.bufferedSeries, resolution.legendSeries, resolution.series, spec],
517
+ )),
518
+ [candleColour, resolution.bufferedSeries, resolution.legendSeries, resolution.series, spec],
507
519
  );
508
520
  const { notify } = usePluginAppActions();
509
521
  const { runtime } = usePluginRenderContext();
@@ -1190,6 +1202,22 @@ function ChartComposerSurface({
1190
1202
  }]
1191
1203
  : [];
1192
1204
 
1205
+ // Returns of two or more prices compare as price returns; the toggle reinvests their cash distributions.
1206
+ const totalReturnOn = spec.viewport.totalReturn === true;
1207
+ const totalReturnFilters = totalReturnOn || priceComparisonSeriesIds(spec) !== null
1208
+ ? [{
1209
+ id: "total-return",
1210
+ kind: "toggle" as const,
1211
+ label: "Total return",
1212
+ short: "Total ret",
1213
+ value: totalReturnOn,
1214
+ onChange: (value: boolean) => {
1215
+ const { totalReturn: _previous, ...viewport } = specRef.current.viewport;
1216
+ updateSpec({ ...specRef.current, viewport: value ? { ...viewport, totalReturn: true } : viewport });
1217
+ },
1218
+ }]
1219
+ : [];
1220
+
1193
1221
  // A company that reports no quarterly figures says so, and where its annual ones are.
1194
1222
  const unreported = resolution.legendSeries?.find((entry) => entry.unreportedPeriod && entry.points.length === 0);
1195
1223
  const emptyMessage = spec.series.length === 0
@@ -1216,6 +1244,7 @@ function ChartComposerSurface({
1216
1244
  onChange: (value: string) => setRange(value as TimeRange) },
1217
1245
  ...genericFilters,
1218
1246
  ...extendedHoursFilters,
1247
+ ...totalReturnFilters,
1219
1248
  ]}
1220
1249
  view={resolutionOptions.length > 1 ? {
1221
1250
  value: spec.viewport.resolution,
@@ -129,6 +129,20 @@ export const CHART_SETTING_KEYS = {
129
129
  realizedVolEstimator: "chartRealizedVolEstimator",
130
130
  } as const;
131
131
 
132
+ /**
133
+ * Whether rising candles take their series' colour or the theme's gain colour.
134
+ * Kept beside the spec in the pane's settings, so a build without it opens the
135
+ * chart unchanged; it is not one of CHART_SETTING_KEYS, which a spec edit
136
+ * clears as derived from the spec.
137
+ */
138
+ export const CHART_CANDLE_COLOURS_SETTING_KEY = "chartCandleColours";
139
+
140
+ export type ChartCandleColours = "series" | "change";
141
+
142
+ export function chartCandleColours(value: unknown): ChartCandleColours {
143
+ return value === "change" ? "change" : "series";
144
+ }
145
+
132
146
  function fallbackSpec(symbol: string | null | undefined): ChartSpec {
133
147
  return symbol ? buildPriceChartPreset(symbol) : buildEmptyChartPreset();
134
148
  }
@@ -417,6 +431,7 @@ export function buildChartComposerPaneSettingsDef(
417
431
  [CHART_SETTING_KEYS.range]: spec.viewport.range,
418
432
  [CHART_SETTING_KEYS.resolution]: spec.viewport.resolution,
419
433
  [CHART_SETTING_KEYS.mode]: inlineStyleTarget?.style ?? "",
434
+ [CHART_CANDLE_COLOURS_SETTING_KEY]: chartCandleColours(settings[CHART_CANDLE_COLOURS_SETTING_KEY]),
420
435
  ...(realizedVol ? {
421
436
  [CHART_SETTING_KEYS.realizedVolWindow]: String(realizedVol.parameters.window ?? 30),
422
437
  [CHART_SETTING_KEYS.realizedVolEstimator]: realizedVol.parameters.estimator ?? "close-to-close",
@@ -494,6 +509,18 @@ export function buildChartComposerPaneSettingsDef(
494
509
  options: modes.map((mode) => ({ value: mode, label: mode.toUpperCase() })),
495
510
  }]
496
511
  : []),
512
+ ...(spec.series.some((series) => isOhlcSeriesStyle(series.style))
513
+ ? [{
514
+ key: CHART_CANDLE_COLOURS_SETTING_KEY,
515
+ label: "Candle Colours",
516
+ description: "Draw rising candles in their series' colour, or in the theme's gain colour against its loss colour.",
517
+ type: "select" as const,
518
+ options: [
519
+ { value: "series", label: "Series colour" },
520
+ { value: "change", label: "Gains & losses" },
521
+ ],
522
+ }]
523
+ : []),
497
524
  ],
498
525
  applyValue: applyChartComposerPaneSetting,
499
526
  };
@@ -55,6 +55,7 @@ export function filterCommandSuggestions(
55
55
  const byCode: ChatFunction[] = [];
56
56
  const byName: ChatFunction[] = [];
57
57
  for (const fn of new Set(functions.values())) {
58
+ if (fn.unlisted) continue;
58
59
  if (fn.code.startsWith(upper) || fn.aliases.includes(upper)) byCode.push(fn);
59
60
  else if (fn.name.toLowerCase().split(/[\s/&()-]+/).some((word) => word.startsWith(lower))) byName.push(fn);
60
61
  }
@@ -74,6 +74,7 @@ import { ChatControllerMessageLoading } from "./message-loading";
74
74
  import { ChatMentionCandidates } from "./mention-candidates";
75
75
  import { ChatControllerStorage } from "./storage";
76
76
  import { listUnreadInboxItems } from "../unread-inbox";
77
+ import { ChatStatusPreview } from "../status-preview";
77
78
  import type { ChatConversationList } from "../conversations";
78
79
  import {
79
80
  conversationActivityAt,
@@ -118,6 +119,8 @@ export class ChatController {
118
119
  );
119
120
  private notifyFn: (notification: AppNotificationRequest) => AppNotificationDelivery | void = () => {};
120
121
  private openMessageFn: ((channelId: string, messageId: string) => void) | undefined;
122
+ /** A DM, mention or reply that shows in the status bar on arrival; the status bar widget draws it. */
123
+ readonly statusPreview = new ChatStatusPreview();
121
124
  private readonly alerts = new ChatNotificationAlerts({
122
125
  getAppActive: () => this.appActive,
123
126
  ensureChannelState: (channelId) => this.ensureChannelState(channelId),
@@ -125,6 +128,7 @@ export class ChatController {
125
128
  getChannel: (channelId) => this.channelCatalog.getChannels().find((channel) => channel.id === channelId),
126
129
  getNotify: () => this.notifyFn,
127
130
  getOpenMessage: () => this.openMessageFn,
131
+ statusPreview: this.statusPreview,
128
132
  });
129
133
 
130
134
  private readonly storage = new ChatControllerStorage({
@@ -271,7 +275,7 @@ export class ChatController {
271
275
  * Keeps who sent each channel's unread messages (`unreadPeople` in the
272
276
  * snapshot) current until the returned stop is called, reading what the
273
277
  * device does not hold. Nothing is read, and the snapshot leaves it empty,
274
- * unless something tracks it: only the app badge needs it.
278
+ * unless something tracks it: the app badge and the status bar widget.
275
279
  */
276
280
  trackUnreadPeople(): () => void {
277
281
  return this.unreadPeople.track();
@@ -5,7 +5,9 @@ import type {
5
5
  import { apiClient, type ChatChannel, type ChatMessage, type ChatNotification } from "../../../../api-client";
6
6
  import type { ChannelRuntimeState, MergeMessagesOptions } from "./state";
7
7
  import { formatChatPaneTitle } from "../channel-labels";
8
+ import type { ChatStatusPreview } from "../status-preview";
8
9
  import {
10
+ chatMessageLine,
9
11
  formatChannelToast,
10
12
  formatMentionToast,
11
13
  formatReplyToast,
@@ -28,6 +30,8 @@ interface ChatNotificationAlertsOptions {
28
30
  getChannel: (channelId: string) => ChatChannel | undefined;
29
31
  getNotify: () => Notify;
30
32
  getOpenMessage: () => ((channelId: string, messageId: string) => void) | undefined;
33
+ /** Where a message for you shows on arrival instead of a toast, while the status bar has room. */
34
+ statusPreview: ChatStatusPreview;
31
35
  }
32
36
 
33
37
  /** Whether this window holds the message as read: its read marker is at or after it. */
@@ -53,9 +57,9 @@ function acknowledge(notification: ChatNotification, seen: boolean): void {
53
57
  }
54
58
 
55
59
  /**
56
- * Decides, once per message, whether a chat notification becomes a toast or
57
- * a desktop banner. A message already read here or on another device stays
58
- * quiet, whether it arrives live or again on reconnect.
60
+ * Decides, once per message, whether a chat notification shows in the status
61
+ * bar, as a toast or as a desktop banner. A message already read here or on
62
+ * another device stays quiet, whether it arrives live or again on reconnect.
59
63
  */
60
64
  export class ChatNotificationAlerts {
61
65
  private readonly notifiedMessageIds = new Set<string>();
@@ -94,6 +98,7 @@ export class ChatNotificationAlerts {
94
98
  for (const timer of this.waiting.values()) clearTimeout(timer);
95
99
  this.waiting.clear();
96
100
  this.notifiedMessageIds.clear();
101
+ this.options.statusPreview.dismiss();
97
102
  }
98
103
 
99
104
  /** Read here, or open in front of the reader: nothing to raise, and the server hears it was seen. */
@@ -124,8 +129,33 @@ export class ChatNotificationAlerts {
124
129
  this.present(notification);
125
130
  }
126
131
 
132
+ /**
133
+ * A DM or group message, a mention or a reply, while the app is in front:
134
+ * the status bar shows it in place of the toast when it has room, and the
135
+ * server hears it was seen, as for a toast. Channel chatter never shows there.
136
+ */
137
+ private presentInStatusBar(notification: ChatNotification, channel: ChatChannel | undefined): boolean {
138
+ const direct = channel?.kind === "direct" || channel?.kind === "group";
139
+ if (!this.options.getAppActive() || (notification.type === "channel" && !direct)) return false;
140
+ const { message } = notification;
141
+ const sentAt = Date.parse(message.createdAt);
142
+ const shown = this.options.statusPreview.offer({
143
+ channelId: notification.channelId,
144
+ messageId: notification.messageId,
145
+ senderName: message.user.username?.trim() || message.user.displayName,
146
+ direct,
147
+ text: chatMessageLine(message),
148
+ at: Number.isFinite(sentAt) ? sentAt : Date.now(),
149
+ });
150
+ if (!shown) return false;
151
+ this.notifiedMessageIds.add(notification.messageId);
152
+ acknowledge(notification, true);
153
+ return true;
154
+ }
155
+
127
156
  private present(notification: ChatNotification): void {
128
157
  const channel = this.options.getChannel(notification.channelId);
158
+ if (this.presentInStatusBar(notification, channel)) return;
129
159
  const body = notification.type === "reply"
130
160
  ? formatReplyToast(notification.message)
131
161
  : notification.type === "mention"
@@ -46,12 +46,34 @@ export interface PersistedTranscript {
46
46
  messages: ChatMessage[];
47
47
  }
48
48
 
49
+ /** An unread message for you: who sent it, and when. */
50
+ export interface UnreadMessageForYou {
51
+ /** Null for a DM whose unread messages are not on this device: only the sender is known. */
52
+ messageId: string | null;
53
+ senderId: string;
54
+ /** The sender's username, or their name without one. */
55
+ senderName: string;
56
+ /** When it was sent, in ms; zero when not known. */
57
+ at: number;
58
+ }
59
+
49
60
  /** Who the unread messages of one channel are from, by account id. */
50
61
  export interface ChannelUnreadPeople {
51
62
  /** Everyone who sent an unread message there. */
52
63
  senders: string[];
53
- /** The ones among them with an unread message that mentions you. */
64
+ /** The ones among them with an unread message that mentions you or replies to you. */
54
65
  mentioners: string[];
66
+ /**
67
+ * The newest unread message for you: in a DM or group chat the newest
68
+ * unread message, elsewhere the newest that mentions or replies to you.
69
+ * Null when none is known on this device.
70
+ */
71
+ latestForYou: UnreadMessageForYou | null;
72
+ /**
73
+ * How many unread messages are for you: every one in a DM or group chat,
74
+ * the ones known to mention or reply to you elsewhere.
75
+ */
76
+ forYouCount: number;
55
77
  }
56
78
 
57
79
  export interface ChatControllerSnapshot {
@@ -83,7 +105,8 @@ export interface ChatControllerSnapshot {
83
105
  unreadMentionCount: number;
84
106
  /**
85
107
  * Who sent the unread messages of each channel with any, by channel id: what
86
- * the app badge counts people from. Empty unless the badge asked for it
108
+ * the app badge counts people from and the status bar names them from.
109
+ * Empty unless one of them asked for it
87
110
  * ({@link ChatController.trackUnreadPeople}). A channel whose unread messages
88
111
  * are not on this device yet lists only the ones that are, until they load.
89
112
  */