@gloom-sh/terminal 0.18.5 → 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 (436) hide show
  1. package/package.json +1 -1
  2. package/patches/electrobun@1.18.1.patch +140 -7
  3. package/src/api-client/cot.ts +6 -2
  4. package/src/api-client/credit-documents.ts +6 -0
  5. package/src/api-client/data.ts +17 -6
  6. package/src/api-client/equity-screener.ts +24 -0
  7. package/src/api-client/fund-facts.ts +45 -4
  8. package/src/api-client/index.ts +11 -0
  9. package/src/api-client/market-discovery.ts +10 -0
  10. package/src/api-client/market-movers.ts +7 -0
  11. package/src/api-client/members.ts +1 -1
  12. package/src/api-client/perps.ts +43 -0
  13. package/src/api-client/research-activity.ts +3 -2
  14. package/src/api-client/types.ts +70 -2
  15. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  16. package/src/app/runtime/broker-import.ts +0 -2
  17. package/src/app/runtime/plugin-bindings.ts +73 -56
  18. package/src/architecture/pane-tabs-allowlist.json +3 -0
  19. package/src/architecture/ttl-caches-allowlist.json +19 -0
  20. package/src/brokers/sync-broker-instance.ts +2 -8
  21. package/src/capabilities/chart-series.ts +1 -1
  22. package/src/cli/commands/crypto-hints.ts +40 -24
  23. package/src/cli/commands/market.ts +72 -26
  24. package/src/cli/commands/options-chain.ts +48 -7
  25. package/src/cli/commands/options-leaps.ts +9 -3
  26. package/src/cli/commands/overview.ts +61 -12
  27. package/src/cli/commands/search.ts +21 -1
  28. package/src/cli/commands/themes.ts +11 -2
  29. package/src/cli/commands/ticker.ts +55 -34
  30. package/src/cli/company-name-hint.ts +41 -0
  31. package/src/cli/earnings-calendar.ts +1 -1
  32. package/src/cli/errors.ts +8 -0
  33. package/src/cli/glossary.ts +25 -2
  34. package/src/cli/history-rows.ts +57 -9
  35. package/src/cli/index.ts +2 -0
  36. package/src/cli/listing-arg.ts +180 -52
  37. package/src/cli/output-file.ts +68 -0
  38. package/src/cli/pane-functions/capabilities.ts +3 -0
  39. package/src/cli/pane-functions/catalog.ts +80 -7
  40. package/src/cli/pane-functions/dom.ts +13 -3
  41. package/src/cli/pane-functions/freshness.ts +86 -3
  42. package/src/cli/pane-functions/function-help.ts +13 -10
  43. package/src/cli/pane-functions/headless.ts +17 -7
  44. package/src/cli/pane-functions/index.ts +49 -14
  45. package/src/cli/pane-functions/market-session.ts +66 -4
  46. package/src/cli/pane-functions/options.ts +19 -1
  47. package/src/cli/pane-functions/resolver.ts +19 -8
  48. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  49. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  50. package/src/cli/quote-answer.ts +22 -11
  51. package/src/cli/ticker-resolution.ts +3 -2
  52. package/src/components/chart/composite/composite-chart.tsx +4 -0
  53. package/src/components/chart/composite/interactions.ts +6 -0
  54. package/src/components/chart/composite/legend.tsx +45 -7
  55. package/src/components/chart/composite/panel-gestures.ts +33 -0
  56. package/src/components/chart/composite/panel-surface.tsx +52 -3
  57. package/src/components/chart/composite/rasterizer.ts +24 -13
  58. package/src/components/chart/composite/scene.ts +39 -4
  59. package/src/components/chart/composite/series-cues.ts +14 -0
  60. package/src/components/chart/composite/text-renderer.ts +9 -2
  61. package/src/components/chart/composite/tool-catalog.ts +36 -0
  62. package/src/components/chart/composite/tools.ts +203 -44
  63. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  64. package/src/components/command-bar/assist/inventory.ts +2 -1
  65. package/src/components/command-bar/commands/direct/index.ts +28 -13
  66. package/src/components/command-bar/pane-templates/items.ts +3 -2
  67. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  68. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  69. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  70. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  71. package/src/components/onboarding/wizard-positions.ts +3 -3
  72. package/src/components/ticker/list-table-view.tsx +4 -1
  73. package/src/components/ui/choice-dialog.tsx +23 -18
  74. package/src/components/ui/choice-groups.ts +18 -0
  75. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  76. package/src/components/ui/data-table/opentui/model.ts +26 -1
  77. package/src/components/ui/data-table/types.ts +2 -0
  78. package/src/components/ui/list-view.tsx +8 -4
  79. package/src/components/ui/query-bar-keys.tsx +2 -0
  80. package/src/components/ui/query-bar.tsx +1 -0
  81. package/src/components/ui/select-button.tsx +5 -1
  82. package/src/components/ui/select-field.tsx +1 -0
  83. package/src/components/ui/table-layout.ts +8 -5
  84. package/src/core/app-runtime.ts +2 -0
  85. package/src/core/app-services.ts +2 -2
  86. package/src/data/config/chart-settings.ts +1 -1
  87. package/src/data/json-ticker-repository.ts +17 -8
  88. package/src/data/ticker-repository.ts +16 -9
  89. package/src/data/ticker-writes.ts +81 -0
  90. package/src/data/ttl-cache.ts +165 -0
  91. package/src/docs/snapshot.json +71 -137
  92. package/src/i18n/es.ts +18 -2
  93. package/src/i18n/ja.ts +18 -2
  94. package/src/i18n/ko.ts +18 -2
  95. package/src/i18n/zh-cn.ts +18 -2
  96. package/src/i18n/zh-tw.ts +18 -2
  97. package/src/market-data/depositary-pair.ts +2 -2
  98. package/src/market-data/fund-facts.ts +50 -0
  99. package/src/market-data/market/change-basis.ts +16 -3
  100. package/src/market-data/market/format.ts +25 -0
  101. package/src/market-data/market/freshness.ts +2 -1
  102. package/src/market-data/options-alternatives.ts +81 -4
  103. package/src/news/aggregator.ts +113 -9
  104. package/src/news/cloud-session.ts +21 -0
  105. package/src/news/hooks.ts +1 -0
  106. package/src/news/news-model.ts +1 -0
  107. package/src/news/types.ts +11 -0
  108. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  109. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  110. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  111. package/src/plugins/builtin/alerts/cli.ts +336 -77
  112. package/src/plugins/builtin/alerts/command.ts +25 -1
  113. package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
  114. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  115. package/src/plugins/builtin/alerts/format.ts +75 -5
  116. package/src/plugins/builtin/alerts/headless.ts +24 -6
  117. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  118. package/src/plugins/builtin/alerts/history.ts +1 -1
  119. package/src/plugins/builtin/alerts/index.tsx +79 -16
  120. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  121. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  122. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  123. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  124. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  125. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  126. package/src/plugins/builtin/alerts/storage.ts +12 -8
  127. package/src/plugins/builtin/alerts/types.ts +4 -0
  128. package/src/plugins/builtin/analytics/index.tsx +2 -1
  129. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  130. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  131. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  132. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  133. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  134. package/src/plugins/builtin/attention/index.tsx +2 -1
  135. package/src/plugins/builtin/attention/listing.ts +14 -0
  136. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  137. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  138. package/src/plugins/builtin/buildout/format.ts +38 -0
  139. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  140. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  141. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  142. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  143. package/src/plugins/builtin/changelog/index.tsx +9 -6
  144. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  145. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  146. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  147. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  148. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  149. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  150. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  151. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  152. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  153. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  154. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  155. package/src/plugins/builtin/clinical-trials/model.ts +0 -9
  156. package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
  157. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
  158. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  159. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  160. package/src/plugins/builtin/company-attention/client.ts +8 -9
  161. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  162. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  163. package/src/plugins/builtin/company-attention/pane.tsx +9 -2
  164. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  165. package/src/plugins/builtin/correlation/headless.ts +13 -5
  166. package/src/plugins/builtin/correlation/history.ts +22 -0
  167. package/src/plugins/builtin/cot/client.ts +22 -18
  168. package/src/plugins/builtin/cot/headless.ts +15 -10
  169. package/src/plugins/builtin/cot/model.ts +14 -1
  170. package/src/plugins/builtin/cot/pane.tsx +22 -14
  171. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  172. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  173. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  174. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  175. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  176. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  177. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  178. package/src/plugins/builtin/credit-documents/headless.ts +7 -7
  179. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  180. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  181. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  182. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  183. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  184. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  185. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  186. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  187. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  188. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  189. package/src/plugins/builtin/docs/targets.ts +5 -1
  190. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  191. package/src/plugins/builtin/earnings/board.tsx +62 -71
  192. package/src/plugins/builtin/earnings/headless.ts +40 -4
  193. package/src/plugins/builtin/earnings/scope.ts +31 -43
  194. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  195. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  196. package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
  197. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  198. package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
  199. package/src/plugins/builtin/econ/types.ts +2 -0
  200. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  201. package/src/plugins/builtin/econ-statistics/headless.ts +6 -4
  202. package/src/plugins/builtin/econ-statistics/stats.ts +36 -9
  203. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  204. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  205. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  206. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  207. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  208. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  209. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  210. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  211. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  212. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  213. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  214. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  215. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  216. package/src/plugins/builtin/gpu/headless.ts +8 -2
  217. package/src/plugins/builtin/help/function-index.ts +4 -1
  218. package/src/plugins/builtin/help/index.tsx +1 -0
  219. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  220. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  221. package/src/plugins/builtin/holders/headless.ts +18 -1
  222. package/src/plugins/builtin/iv-history/client.ts +52 -11
  223. package/src/plugins/builtin/iv-history/format.ts +36 -23
  224. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  225. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  226. package/src/plugins/builtin/iv-history/model.ts +163 -27
  227. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  228. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  229. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  230. package/src/plugins/builtin/jobs/client.ts +25 -29
  231. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  232. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  233. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  234. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  235. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  236. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  237. package/src/plugins/builtin/market-movers/client.ts +25 -7
  238. package/src/plugins/builtin/market-movers/headless.ts +32 -12
  239. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  240. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  241. package/src/plugins/builtin/market-movers/model.ts +4 -1
  242. package/src/plugins/builtin/market-movers/screener.ts +14 -0
  243. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  244. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  245. package/src/plugins/builtin/market-movers/table.ts +17 -5
  246. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  247. package/src/plugins/builtin/news/index.tsx +1 -0
  248. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  249. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  250. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  251. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  252. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  253. package/src/plugins/builtin/openfda/client.ts +14 -2
  254. package/src/plugins/builtin/openfda/index.tsx +3 -2
  255. package/src/plugins/builtin/openfda/model.ts +9 -0
  256. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  257. package/src/plugins/builtin/options/analytics.ts +29 -5
  258. package/src/plugins/builtin/options/enrichment.ts +17 -5
  259. package/src/plugins/builtin/options/table.ts +5 -2
  260. package/src/plugins/builtin/options/types.ts +2 -0
  261. package/src/plugins/builtin/options/view.tsx +17 -4
  262. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  263. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  264. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  265. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  266. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  267. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  268. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  269. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  270. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  271. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  272. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  273. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  274. package/src/plugins/builtin/pe-band/model.ts +12 -49
  275. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  276. package/src/plugins/builtin/perps/cascade.ts +236 -0
  277. package/src/plugins/builtin/perps/client.ts +56 -2
  278. package/src/plugins/builtin/perps/headless.ts +65 -9
  279. package/src/plugins/builtin/perps/index.tsx +7 -5
  280. package/src/plugins/builtin/perps/model.ts +18 -4
  281. package/src/plugins/builtin/perps/pane.tsx +19 -22
  282. package/src/plugins/builtin/perps/status-line.ts +25 -0
  283. package/src/plugins/builtin/plugin-module.ts +5 -1
  284. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  285. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  286. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  287. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  288. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  289. package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
  290. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  291. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  292. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
  293. package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
  294. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  295. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  296. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  297. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  298. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  299. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  300. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  301. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  302. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  303. package/src/plugins/builtin/research/event-model.ts +10 -8
  304. package/src/plugins/builtin/research/headless.ts +1 -1
  305. package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
  306. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  307. package/src/plugins/builtin/research/relative-valuation-pane.tsx +7 -5
  308. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  309. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  310. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  311. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  312. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  313. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  314. package/src/plugins/builtin/scanner/headless.ts +204 -35
  315. package/src/plugins/builtin/scanner/index.ts +16 -5
  316. package/src/plugins/builtin/scanner/live-headless.ts +27 -3
  317. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  318. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  319. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  320. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  321. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  322. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  323. package/src/plugins/builtin/sec/headless.ts +2 -1
  324. package/src/plugins/builtin/series-inventory.ts +2 -0
  325. package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
  326. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  327. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  328. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  329. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  330. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  331. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  332. package/src/plugins/builtin/shared/report-freshness.ts +3 -2
  333. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  334. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  335. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  336. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  337. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  338. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  339. package/src/plugins/builtin/themes/model.ts +16 -7
  340. package/src/plugins/builtin/themes/pane.tsx +5 -5
  341. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  342. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  343. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  344. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  345. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  346. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  347. package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
  348. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  349. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  350. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  351. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  352. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  353. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  354. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  355. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  356. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  357. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  358. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  359. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  360. package/src/plugins/builtin/volatility/index.tsx +2 -2
  361. package/src/plugins/builtin/volatility/pane.tsx +2 -2
  362. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  363. package/src/plugins/catalog-manifest.ts +2 -1
  364. package/src/plugins/marketplace/model.ts +18 -5
  365. package/src/portfolio/futures-positions.ts +6 -3
  366. package/src/portfolio/position-quantity.ts +15 -9
  367. package/src/remote/resources.ts +2 -1
  368. package/src/renderers/dom/data-table/row.tsx +26 -0
  369. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  370. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  371. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  372. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  373. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  374. package/src/renderers/electrobun/bun/desktop/backend.ts +12 -0
  375. package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
  376. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  377. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  378. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  379. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  380. package/src/renderers/electrobun/shared/protocol.ts +10 -0
  381. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  382. package/src/renderers/electrobun/view/main.tsx +24 -1
  383. package/src/renderers/electrobun/view/news-source.ts +3 -1
  384. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  385. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  386. package/src/renderers/opentui/start.tsx +5 -0
  387. package/src/series/registry.ts +1 -0
  388. package/src/series/types.ts +6 -0
  389. package/src/series/validate.ts +1 -0
  390. package/src/sources/gloomberb-cloud/index.ts +1 -0
  391. package/src/sources/gloomberb-cloud/news.ts +47 -7
  392. package/src/sources/provider-router/financials.ts +15 -3
  393. package/src/sources/provider-router/index.ts +8 -5
  394. package/src/state/ticker-cleanup.ts +50 -0
  395. package/src/sync/controller.ts +4 -3
  396. package/src/sync/core-contributors.ts +90 -31
  397. package/src/telemetry/stall-reports-dom.ts +38 -0
  398. package/src/telemetry/stall-reports-node.ts +75 -0
  399. package/src/telemetry/stall-reports.ts +214 -0
  400. package/src/test-support/preload.ts +14 -0
  401. package/src/theme/themes.ts +10 -0
  402. package/src/tickers/company-name.ts +44 -3
  403. package/src/tickers/listing-venues.ts +15 -3
  404. package/src/tickers/terminal-codes.ts +19 -0
  405. package/src/time-series/alignment.ts +1 -1
  406. package/src/time-series/economic.ts +18 -4
  407. package/src/time-series/field-catalog.ts +1 -1
  408. package/src/time-series/filed-trailing-eps.ts +197 -0
  409. package/src/time-series/fundamentals.ts +44 -1
  410. package/src/time-series/ice-credit.ts +3 -0
  411. package/src/time-series/price-comparison.ts +30 -2
  412. package/src/time-series/resolve-context.ts +3 -1
  413. package/src/time-series/resolve.ts +56 -4
  414. package/src/time-series/session-bar.ts +11 -0
  415. package/src/time-series/spec.ts +3 -2
  416. package/src/time-series/studies.ts +8 -2
  417. package/src/time-series/total-return.ts +160 -0
  418. package/src/time-series/transforms.ts +2 -3
  419. package/src/time-series/types.ts +22 -1
  420. package/src/types/financials.ts +32 -1
  421. package/src/types/headless.ts +21 -3
  422. package/src/types/news-source.ts +1 -0
  423. package/src/types/plugin.ts +9 -0
  424. package/src/ui/host.tsx +3 -0
  425. package/src/updater/failure.ts +4 -1
  426. package/src/utils/cli-output.ts +42 -1
  427. package/src/utils/crypto-pair.ts +44 -0
  428. package/src/utils/forward-dividend.ts +2 -0
  429. package/src/utils/fundamentals.ts +24 -3
  430. package/src/utils/fx-market-hours.ts +26 -3
  431. package/src/utils/main-thread-stall.ts +35 -2
  432. package/src/utils/market-capitalization.ts +24 -1
  433. package/src/utils/price-history.ts +7 -1
  434. package/src/utils/shell-word.ts +8 -0
  435. package/src/utils/trailing-dividend.ts +53 -4
  436. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -0,0 +1,75 @@
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
2
+ import { join } from "path";
3
+ import type { StallReportState, StallReporterHost } from "./stall-reports";
4
+
5
+ /** Next to config.json: one line per stall, newest last. */
6
+ const STALL_LOG_FILE = "stall-log.txt";
7
+ /** Next to config.json: when each kind of stall was last reported, and the counts since. */
8
+ const STATE_FILE = "stall-reports.json";
9
+ /** Past this the log keeps its newer half. */
10
+ const LOG_MAX_BYTES = 256 * 1024;
11
+
12
+ function readCounts(value: unknown): StallReportState["pending"] {
13
+ const pending: StallReportState["pending"] = {};
14
+ if (!value || typeof value !== "object") return pending;
15
+ for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {
16
+ const { count, longestMs } = (entry ?? {}) as Record<string, unknown>;
17
+ if (typeof count === "number" && Number.isFinite(count) && typeof longestMs === "number" && Number.isFinite(longestMs)) {
18
+ pending[key] = { count, longestMs };
19
+ }
20
+ }
21
+ return pending;
22
+ }
23
+
24
+ /** The saved state, keeping only entries of the right shape. */
25
+ function parseStallReportState(text: string): StallReportState {
26
+ const parsed: unknown = JSON.parse(text);
27
+ const record = parsed && typeof parsed === "object" ? parsed as Record<string, unknown> : {};
28
+ const sent: Record<string, number> = {};
29
+ if (record.sent && typeof record.sent === "object") {
30
+ for (const [key, at] of Object.entries(record.sent as Record<string, unknown>)) {
31
+ if (typeof at === "number" && Number.isFinite(at)) sent[key] = at;
32
+ }
33
+ }
34
+ return { sent, pending: readCounts(record.pending) };
35
+ }
36
+
37
+ function trimLog(path: string): void {
38
+ if (statSync(path).size <= LOG_MAX_BYTES) return;
39
+ const text = readFileSync(path, "utf-8");
40
+ const keep = text.slice(text.length - LOG_MAX_BYTES / 2);
41
+ writeFileSync(path, keep.slice(keep.indexOf("\n") + 1), "utf-8");
42
+ }
43
+
44
+ /** The stall reporter for a Bun process (the terminal app, the desktop app's main process). */
45
+ export function createNodeStallReporterHost(options: {
46
+ /** The profile's data folder, or null before the config is loaded. */
47
+ getDataDir(): string | null;
48
+ }): StallReporterHost {
49
+ const pathFor = (file: string) => {
50
+ const dir = options.getDataDir();
51
+ return dir ? join(dir, file) : null;
52
+ };
53
+ return {
54
+ platform: process.platform,
55
+ arch: process.arch,
56
+ appendLog: (line) => {
57
+ const path = pathFor(STALL_LOG_FILE);
58
+ if (!path) return;
59
+ mkdirSync(join(path, ".."), { recursive: true });
60
+ appendFileSync(path, `${line}\n`, "utf-8");
61
+ trimLog(path);
62
+ },
63
+ load: () => {
64
+ const path = pathFor(STATE_FILE);
65
+ if (!path || !existsSync(path)) return { sent: {}, pending: {} };
66
+ return parseStallReportState(readFileSync(path, "utf-8"));
67
+ },
68
+ save: (state) => {
69
+ const path = pathFor(STATE_FILE);
70
+ if (!path) return;
71
+ mkdirSync(join(path, ".."), { recursive: true });
72
+ writeFileSync(path, JSON.stringify(state), "utf-8");
73
+ },
74
+ };
75
+ }
@@ -0,0 +1,214 @@
1
+ import { logFailure } from "../utils/log-failure";
2
+ import type { StallEvent } from "../utils/main-thread-stall";
3
+ import { VERSION } from "../version";
4
+ import { reportCrash } from "./crash-reports";
5
+
6
+ /**
7
+ * Freezes, made visible outside the app's own Debug Log.
8
+ *
9
+ * The stall monitor (utils/main-thread-stall.ts) notices when a process could
10
+ * not answer the user for a while and names the last measured section, and
11
+ * hands the stall to its sink. The terminal app and the desktop app's Bun
12
+ * process record their own stalls here; the desktop window sends the stalls
13
+ * it had in front of the user (stall-reports-dom.ts) to the Bun process,
14
+ * which records them too. Where a host is installed, each
15
+ * stall:
16
+ *
17
+ * - goes to a plain-text stall log in the profile folder, one line each, so a
18
+ * user or support can read what froze and when;
19
+ * - from a second up, counts toward a report sent through the crash reporter
20
+ * (and so behind the same off switch), the way update failures are: the
21
+ * duration bucket, the section's fixed name, the process, the longest
22
+ * stall and how many there were. No free text: a section name keeps only
23
+ * its fixed leading part, so a ticker in a queue key never leaves.
24
+ *
25
+ * One report per process, bucket, section and app version a day; stalls of
26
+ * the same kind after that are counted and carried in the next report.
27
+ */
28
+
29
+ /** Which process stalled. */
30
+ export type StallProcess = "window" | "app" | "terminal";
31
+
32
+ /** What error tracking gets: fixed values only. */
33
+ export interface StallReport {
34
+ process: StallProcess;
35
+ bucket: StallBucket;
36
+ section: string;
37
+ appVersion: string;
38
+ platform: "macos" | "windows" | "linux" | "other";
39
+ arch: "arm64" | "x64" | "other";
40
+ /** Stalls of this kind since the last report, this one included. */
41
+ count: number;
42
+ /** The longest of them, rounded to 100 ms. */
43
+ longestMs: number;
44
+ }
45
+
46
+ type StallBucket = "1-2s" | "2-5s" | "5-15s" | "15s+";
47
+
48
+ /** What the host keeps on disk between launches. */
49
+ export interface StallReportState {
50
+ /** When each kind was last reported, in ms. */
51
+ sent: Record<string, number>;
52
+ /** Stalls counted since each kind was last reported. */
53
+ pending: Record<string, { count: number; longestMs: number }>;
54
+ }
55
+
56
+ export interface StallReporterHost {
57
+ /** The running version; defaults to this build's. */
58
+ appVersion?: string;
59
+ platform: string;
60
+ arch: string;
61
+ /** Appends one line to the stall log. */
62
+ appendLog(line: string): void;
63
+ load(): StallReportState;
64
+ save(state: StallReportState): void;
65
+ now?(): number;
66
+ /** Defaults to the crash reporter. */
67
+ send?(report: StallReport): void;
68
+ }
69
+
70
+ const DAY_MS = 24 * 60 * 60 * 1000;
71
+ /** Shorter freezes go to the log only. */
72
+ const STALL_REPORT_MIN_MS = 1_000;
73
+ const SECTION_PATTERN = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*/;
74
+ const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]{1,60})?$/;
75
+ /** Kinds with counts waiting for their next report; the oldest go first past this. */
76
+ const MAX_KINDS_KEPT = 200;
77
+
78
+ let host: StallReporterHost | null = null;
79
+
80
+ /** The fixed part of a section name: lower-case dotted words, cut where anything else starts. */
81
+ function stallSectionName(name: string | undefined): string {
82
+ if (!name) return "unmeasured";
83
+ const match = SECTION_PATTERN.exec(name);
84
+ const section = match?.[0].replace(/\.$/, "");
85
+ return section && section.length <= 80 ? section : "unmeasured";
86
+ }
87
+
88
+ function stallBucket(blockedMs: number): StallBucket {
89
+ if (blockedMs < 2_000) return "1-2s";
90
+ if (blockedMs < 5_000) return "2-5s";
91
+ if (blockedMs < 15_000) return "5-15s";
92
+ return "15s+";
93
+ }
94
+
95
+ /** A stall another process sent over, kept only when it has the right shape. */
96
+ export function parseStallEvent(value: unknown): StallEvent | null {
97
+ if (!value || typeof value !== "object") return null;
98
+ const { blockedMs, lastMeasured, lastMeasuredEndedMsAgo } = value as Record<string, unknown>;
99
+ if (typeof blockedMs !== "number" || !Number.isFinite(blockedMs) || blockedMs <= 0) return null;
100
+ return {
101
+ blockedMs,
102
+ ...(typeof lastMeasured === "string" && lastMeasured.length <= 200 ? { lastMeasured } : {}),
103
+ ...(typeof lastMeasuredEndedMsAgo === "number" && Number.isFinite(lastMeasuredEndedMsAgo) ? { lastMeasuredEndedMsAgo } : {}),
104
+ };
105
+ }
106
+
107
+ function formatSeconds(ms: number): string {
108
+ return `${(ms / 1000).toFixed(ms < 10_000 ? 1 : 0)} s`;
109
+ }
110
+
111
+ /** One line of the stall log: when, which process, how long, and what ran last. */
112
+ function formatStallLogLine(process: StallProcess, event: StallEvent, at: number): string {
113
+ const time = new Date(at).toISOString().replace(/\.\d{3}Z$/, "Z");
114
+ const last = event.lastMeasured
115
+ ? `last measured: ${event.lastMeasured}${event.lastMeasuredEndedMsAgo === undefined ? "" : `, ended ${Math.round(event.lastMeasuredEndedMsAgo)} ms before`}`
116
+ : "nothing measured before it";
117
+ return `${time} ${process.padEnd(8)} froze ${formatSeconds(event.blockedMs).padStart(6)} ${last}`;
118
+ }
119
+
120
+ function reportPlatform(platform: string): StallReport["platform"] {
121
+ if (platform === "darwin") return "macos";
122
+ if (platform === "win32") return "windows";
123
+ if (platform === "linux") return "linux";
124
+ return "other";
125
+ }
126
+
127
+ function reportArch(arch: string): StallReport["arch"] {
128
+ return arch === "arm64" || arch === "x64" ? arch : "other";
129
+ }
130
+
131
+ function sendThroughCrashReporter(report: StallReport): void {
132
+ const details = [
133
+ `process=${report.process}`,
134
+ `count=${report.count}`,
135
+ `longest=${report.longestMs}ms`,
136
+ `platform=${report.platform}-${report.arch}`,
137
+ ].join(" ");
138
+ // A plain object, not an Error: it has no stack, so no file path goes along.
139
+ // The message is the bucket and section, so error tracking keeps one issue per kind.
140
+ reportCrash(
141
+ { name: "MainThreadStall", message: `${report.bucket} after ${report.section}` },
142
+ { kind: "plugin", plugin: "stall-monitor", pane: details },
143
+ );
144
+ }
145
+
146
+ function loadState(current: StallReporterHost): StallReportState {
147
+ try {
148
+ const state = current.load();
149
+ return { sent: { ...state.sent }, pending: { ...state.pending } };
150
+ } catch (error) {
151
+ logFailure("telemetry-stalls", "Could not read stall report state")(error);
152
+ return { sent: {}, pending: {} };
153
+ }
154
+ }
155
+
156
+ function prune(state: StallReportState, now: number): void {
157
+ for (const [key, at] of Object.entries(state.sent)) {
158
+ if (!(now - at < DAY_MS) || at > now) delete state.sent[key];
159
+ }
160
+ const kinds = Object.keys(state.pending);
161
+ for (const key of kinds.slice(0, Math.max(0, kinds.length - MAX_KINDS_KEPT))) delete state.pending[key];
162
+ }
163
+
164
+ /** Records one stall of `process`. Does nothing where no host is installed; never throws. */
165
+ export function recordStall(process: StallProcess, event: StallEvent): void {
166
+ const current = host;
167
+ if (!current || !(event.blockedMs > 0)) return;
168
+ try {
169
+ const now = current.now?.() ?? Date.now();
170
+ current.appendLog(formatStallLogLine(process, event, now));
171
+ if (event.blockedMs < STALL_REPORT_MIN_MS) return;
172
+ const appVersion = (current.appVersion ?? VERSION).trim().replace(/^v/, "");
173
+ const section = stallSectionName(event.lastMeasured);
174
+ const bucket = stallBucket(event.blockedMs);
175
+ const version = VERSION_PATTERN.test(appVersion) ? appVersion : "unknown";
176
+ const key = `${process}|${bucket}|${section}|${version}`;
177
+ const state = loadState(current);
178
+ prune(state, now);
179
+ const pending = state.pending[key] ?? { count: 0, longestMs: 0 };
180
+ pending.count += 1;
181
+ pending.longestMs = Math.max(pending.longestMs, Math.round(event.blockedMs / 100) * 100);
182
+ if (state.sent[key] !== undefined) {
183
+ state.pending[key] = pending;
184
+ } else {
185
+ delete state.pending[key];
186
+ state.sent[key] = now;
187
+ (current.send ?? sendThroughCrashReporter)({
188
+ process,
189
+ bucket,
190
+ section,
191
+ appVersion: version,
192
+ platform: reportPlatform(current.platform),
193
+ arch: reportArch(current.arch),
194
+ count: pending.count,
195
+ longestMs: pending.longestMs,
196
+ });
197
+ }
198
+ current.save(state);
199
+ } catch (error) {
200
+ logFailure("telemetry-stalls", "Could not record a stall")(error);
201
+ }
202
+ }
203
+
204
+ export function installStallReporter(next: StallReporterHost): () => void {
205
+ host = next;
206
+ return () => {
207
+ if (host === next) host = null;
208
+ };
209
+ }
210
+
211
+ /** Test seam. */
212
+ export function resetStallReporterForTests(): void {
213
+ host = null;
214
+ }
@@ -0,0 +1,14 @@
1
+ import { beforeEach } from "bun:test";
2
+ import { resetCrashReporterForTests } from "../telemetry/crash-reports";
3
+
4
+ /**
5
+ * `bun test` runs every file in one process, so module state outlives the file
6
+ * that set it. A crash report queued while no reporter is installed, such as a
7
+ * plugin that fails to register in an app runtime test, would wait in the
8
+ * queue and go out with the next reporter another file installs, and its key
9
+ * would keep a later identical report from being queued at all. Every test
10
+ * starts with no reporter, an empty queue and nothing seen.
11
+ */
12
+ beforeEach(() => {
13
+ resetCrashReporterForTests();
14
+ });
@@ -745,6 +745,16 @@ const THEME_TRAITS: Readonly<Record<string, readonly ThemeTrait[]>> = {
745
745
  "high-contrast": ["high-contrast", "colorblind-safe"],
746
746
  };
747
747
 
748
+ /**
749
+ * Whether a theme's gains and losses are told apart by something other than
750
+ * the red and green everyone reads: a color-blind safe theme's blue and
751
+ * vermillion, which a chart keys (`▲▼` beside a candle series) so a reader
752
+ * knows which is which.
753
+ */
754
+ export function themeKeysDirection(id: string): boolean {
755
+ return (THEME_TRAITS[id] ?? []).includes("colorblind-safe");
756
+ }
757
+
748
758
  export interface ThemeSummary {
749
759
  id: string;
750
760
  name: string;
@@ -99,12 +99,53 @@ export function companyNameMatch(typed: string, name: string | null | undefined)
99
99
  return { extra: words.size - matched.size, abbreviated };
100
100
  }
101
101
 
102
- /** Items grouped by the company their names are, in the order each company first appears. Unnamed items are left out. */
103
- export function groupByCompany<T>(items: readonly T[], nameOf: (item: T) => string | null | undefined): T[][] {
102
+ // Words one fund's venues name it with or without: "iShares MSCI Japan Index Fund" in New York is "iShares MSCI Japan ETF" in Mexico.
103
+ const FUND_NOISE = new Set(["etf", "etfs", "fund", "funds", "index", "trust", "ucits"]);
104
+
105
+ function instrumentWords(name: string | null | undefined): Set<string> {
106
+ return new Set([...companyWords(name)].filter((word) => !FUND_NOISE.has(word)));
107
+ }
108
+
109
+ /**
110
+ * Whether two venues' names can be one instrument's: one company's
111
+ * (`sameCompany`), or one fund's however a venue words it, with or without
112
+ * "ETF", "Index Fund" or "Trust" ("Technology Select Sector SPDR Fund" in
113
+ * Buenos Aires is State Street's ETF in New York). Two names that are not are
114
+ * another company's or another fund's ("W.A.G Payment Solutions" is not the
115
+ * iShares fund, nor "UBS MSCI India" iShares' India fund). An empty name
116
+ * matches none.
117
+ */
118
+ export function sameListedName(left: string | null | undefined, right: string | null | undefined): boolean {
119
+ if (sameCompany(left, right)) return true;
120
+ const a = instrumentWords(left);
121
+ const b = instrumentWords(right);
122
+ if (a.size === 0 || b.size === 0) return false;
123
+ const [fewer, more] = a.size <= b.size ? [a, b] : [b, a];
124
+ return [...fewer].every((word) => more.has(word));
125
+ }
126
+
127
+ /**
128
+ * Whether a venue names its line a depositary receipt ("Toyota Motor
129
+ * Corporation CEDEAR", "SS SPDR S&P 500 ETF Trust - CEDEAR"): a receipt of a
130
+ * line elsewhere, whose short form of that line's name says little of whose it is.
131
+ */
132
+ export function namesDepositaryReceipt(name: string | null | undefined): boolean {
133
+ return /\b(?:cedears?|adrs?|ads|cdrs?|cdis?|depositary)\b/i.test(name ?? "");
134
+ }
135
+
136
+ /**
137
+ * Items grouped by the company their names are (`same`, `sameCompany` unless
138
+ * given), in the order each company first appears. Unnamed items are left out.
139
+ */
140
+ export function groupByCompany<T>(
141
+ items: readonly T[],
142
+ nameOf: (item: T) => string | null | undefined,
143
+ same: (left: string | null | undefined, right: string | null | undefined) => boolean = sameCompany,
144
+ ): T[][] {
104
145
  const groups: T[][] = [];
105
146
  for (const item of items) {
106
147
  if (companyWords(nameOf(item)).size === 0) continue;
107
- const group = groups.find((members) => members.some((member) => sameCompany(nameOf(member), nameOf(item))));
148
+ const group = groups.find((members) => members.some((member) => same(nameOf(member), nameOf(item))));
108
149
  if (group) group.push(item);
109
150
  else groups.push([item]);
110
151
  }
@@ -28,6 +28,17 @@ export interface ListingVenue {
28
28
  /** Canonical code. */
29
29
  exchange: string;
30
30
  name: string;
31
+ /**
32
+ * The listing's own symbol there, when search lists it under another
33
+ * spelling than the one asked for: BP for `BP.` (London's code for it).
34
+ */
35
+ symbol?: string;
36
+ }
37
+
38
+ /** The venue's own symbol, when it is not `asked` (ignoring case). */
39
+ function ownSymbol(listed: string | undefined, asked: string): { symbol: string } | Record<string, never> {
40
+ const symbol = listed?.trim().toUpperCase();
41
+ return symbol && symbol !== asked.trim().toUpperCase() ? { symbol } : {};
31
42
  }
32
43
 
33
44
  /**
@@ -50,14 +61,15 @@ export async function listingVenues(symbol: string, { store, dataProvider }: Lis
50
61
  : {
51
62
  exchange: resolved.result.exchange === "SMART" ? resolved.result.primaryExchange ?? "" : resolved.result.exchange || resolved.result.primaryExchange || "",
52
63
  name: resolved.result.name,
64
+ ...ownSymbol(parsePublicTickerKey(resolved.result.symbol).symbol, symbol),
53
65
  };
54
- venues = only?.exchange ? [{ exchange: canonicalExchange(only.exchange), name: only.name }] : [];
66
+ venues = only?.exchange ? [{ ...only, exchange: canonicalExchange(only.exchange) }] : [];
55
67
  } catch (error) {
56
68
  // One listing settles the picker; several are its choices.
57
69
  if (!(error instanceof AmbiguousTickerError) || error instanceof AmbiguousContractError) return [];
58
70
  venues = error.listings.flatMap((listing) => {
59
- const exchange = parsePublicTickerKey(listing).exchange;
60
- return exchange ? [{ exchange, name: error.listingNames[listing] ?? "" }] : [];
71
+ const key = parsePublicTickerKey(listing);
72
+ return key.exchange ? [{ exchange: key.exchange, name: error.listingNames[listing] ?? "", ...ownSymbol(key.symbol, symbol) }] : [];
61
73
  });
62
74
  }
63
75
  return venues.filter((venue, index) => venues.findIndex((other) => other.exchange === venue.exchange) === index);
@@ -412,6 +412,25 @@ export interface NearMissListing {
412
412
  symbol: string;
413
413
  }
414
414
 
415
+ /**
416
+ * `BP.`, `RR.`: London's code for a company with a two-letter symbol, which
417
+ * the exchange writes with a dot to make three. No other venue writes a
418
+ * trailing dot, though one can trail a US ticker by slip (`GE.`).
419
+ */
420
+ const LONDON_DOTTED = /^([A-Z]{2})\.$/;
421
+
422
+ /**
423
+ * A two-letter symbol written with London's trailing dot (`BP.`): what was
424
+ * typed and the symbol (BP). Whether it is London's line (BP:LSE) or a US
425
+ * ticker with a stray dot (`GE.`) is for where London lists it to say
426
+ * (`resolveCliListing`). Null for anything else.
427
+ */
428
+ export function readLondonDottedSymbol(raw: string): NearMissListing | null {
429
+ const typed = raw.trim();
430
+ const london = LONDON_DOTTED.exec(typed.toUpperCase());
431
+ return london ? { typed, symbol: london[1]! } : null;
432
+ }
433
+
415
434
  /** `BRK/B`: a US share class after a slash, as a broker writes it. One letter before the slash is a ratio or an abbreviation (P/E, N/A). */
416
435
  const SLASH_CLASS = /^([A-Z]{2,5})\/([A-Z])$/;
417
436
  /** `700.HK`: a Hong Kong code without its leading zeros. */
@@ -46,7 +46,7 @@ function isStrictlyOrdered(points: readonly TimeSeriesPoint[]): boolean {
46
46
  }
47
47
 
48
48
  /** Callers only read the result, so ordered input (every market series) is returned as is. */
49
- function sortedUniquePoints(points: readonly TimeSeriesPoint[]): readonly TimeSeriesPoint[] {
49
+ export function sortedUniquePoints(points: readonly TimeSeriesPoint[]): readonly TimeSeriesPoint[] {
50
50
  // Live charts re-resolve on every quote; skip the keyed sort when nothing needs it.
51
51
  if (isStrictlyOrdered(points)) return points;
52
52
  const byObservation = new Map<string, TimeSeriesPoint>();
@@ -1,6 +1,6 @@
1
1
  import type { TimeSeriesPoint } from "./types";
2
2
  import type { CloudFredSeriesInfoPayload, CloudFredSeriesPayload } from "../api-client";
3
- import { ICE_CREDIT_SERIES_IDS } from "./ice-credit";
3
+ import { ICE_CREDIT_SERIES_IDS, ICE_HISTORY_LIMIT_NOTICE } from "./ice-credit";
4
4
 
5
5
  function sourceDate(value: string | undefined): number | null {
6
6
  if (!value || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
@@ -8,6 +8,21 @@ function sourceDate(value: string | undefined): number | null {
8
8
  return Number.isFinite(time) && new Date(time).toISOString().slice(0, 10) === value ? time : null;
9
9
  }
10
10
 
11
+ const THREE_YEAR_RETENTION = /Starting in April 2026, this series will only include 3 years of observations\./;
12
+
13
+ /**
14
+ * The publisher's cap on how far back an ICE credit series goes, from the
15
+ * series' own notes, so it lapses if FRED lifts it. Null for any other series.
16
+ */
17
+ export function fredHistoryLimitNotice(
18
+ seriesId: string,
19
+ info: Pick<CloudFredSeriesInfoPayload, "id" | "notes"> | null | undefined,
20
+ ): string | null {
21
+ const id = seriesId.trim().toUpperCase();
22
+ if (!ICE_CREDIT_SERIES_IDS.has(id) || info?.id.trim().toUpperCase() !== id) return null;
23
+ return THREE_YEAR_RETENTION.test(info.notes) ? ICE_HISTORY_LIMIT_NOTICE : null;
24
+ }
25
+
11
26
  /** Source coverage is separate from query limits and calculation buffers. */
12
27
  export function fredCreditCoverageNotice(
13
28
  seriesId: string,
@@ -21,9 +36,8 @@ export function fredCreditCoverageNotice(
21
36
  const end = sourceDate(info.observationEnd);
22
37
  if (start === null || end === null || start > end) return null;
23
38
  if (visibleStart !== null && visibleStart >= start) return null;
24
- const retention = /Starting in April 2026, this series will only include 3 years of observations\./.test(info.notes)
25
- ? " FRED limits this ICE series to 3 years."
26
- : "";
39
+ const limit = fredHistoryLimitNotice(id, info);
40
+ const retention = limit ? ` ${limit}` : "";
27
41
  // Series metadata and observations can refresh independently. An observation
28
42
  // outside the declared span disproves that span; the query's first/last row
29
43
  // cannot establish replacement source coverage either.
@@ -32,7 +32,7 @@ const FIELDS = [
32
32
  unit: "currency",
33
33
  unitGroup: "price",
34
34
  nativeFrequency: "daily",
35
- styles: ["candles", "ohlc", "hlc", "line", "area"],
35
+ styles: ["candles", "hollow", "heikin-ashi", "ohlc", "hlc", "line", "area"],
36
36
  defaultStyle: "candles",
37
37
  transforms: MARKET_TRANSFORMS,
38
38
  defaultInterpolation: "none",