@gloom-sh/terminal 0.19.1 → 0.19.3

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 (554) hide show
  1. package/README.md +7 -1
  2. package/package.json +2 -1
  3. package/patches/electrobun@1.18.1.patch +108 -1
  4. package/src/account/upgrade-dialog.tsx +4 -23
  5. package/src/api-client/coalesce.ts +118 -0
  6. package/src/api-client/credit-boards.ts +13 -0
  7. package/src/api-client/crypto-etf-flows.ts +6 -0
  8. package/src/api-client/crypto-markets.ts +40 -0
  9. package/src/api-client/data.ts +201 -14
  10. package/src/api-client/equity-screener.ts +10 -0
  11. package/src/api-client/errors.ts +58 -0
  12. package/src/api-client/gpu.ts +29 -0
  13. package/src/api-client/index.ts +5 -2
  14. package/src/api-client/plan-access.ts +10 -1
  15. package/src/api-client/plan-rules.ts +35 -0
  16. package/src/api-client/research-activity.ts +1 -32
  17. package/src/api-client/types.ts +21 -1
  18. package/src/app/attention-notice.tsx +94 -0
  19. package/src/app/runtime/startup.ts +3 -1
  20. package/src/app.tsx +23 -1
  21. package/src/architecture/core-builtin-allowlist.json +2 -1
  22. package/src/capabilities/chart-series.ts +2 -1
  23. package/src/capabilities/factories.ts +1 -0
  24. package/src/cli/ascii-output.ts +42 -0
  25. package/src/cli/base-converter.ts +10 -2
  26. package/src/cli/command-options.ts +16 -1
  27. package/src/cli/commands/crypto-hints.ts +23 -4
  28. package/src/cli/commands/market.ts +101 -27
  29. package/src/cli/commands/news-search.ts +24 -5
  30. package/src/cli/commands/overview.ts +14 -10
  31. package/src/cli/commands/search.ts +84 -25
  32. package/src/cli/commands/system.ts +22 -30
  33. package/src/cli/commands/ticker.ts +49 -9
  34. package/src/cli/commands/version.ts +33 -0
  35. package/src/cli/context.ts +6 -9
  36. package/src/cli/data-dir.ts +7 -0
  37. package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
  38. package/src/cli/desktop-pane-shot-browser.ts +320 -0
  39. package/src/cli/desktop-pane-shot-routes.ts +26 -1
  40. package/src/cli/desktop-pane-shot.ts +196 -256
  41. package/src/cli/early-command.ts +42 -0
  42. package/src/cli/earnings-calendar.ts +9 -0
  43. package/src/cli/entry.ts +19 -13
  44. package/src/cli/errors.ts +12 -2
  45. package/src/cli/glossary.ts +2 -1
  46. package/src/cli/help.ts +1 -1
  47. package/src/cli/history-export.ts +22 -2
  48. package/src/cli/history-rows.ts +189 -11
  49. package/src/cli/index.ts +18 -1
  50. package/src/cli/known-gaps.ts +51 -5
  51. package/src/cli/listing-arg.ts +63 -10
  52. package/src/cli/options.ts +1 -1
  53. package/src/cli/output-file.ts +60 -13
  54. package/src/cli/pane-functions/catalog.ts +14 -3
  55. package/src/cli/pane-functions/data.ts +1 -1
  56. package/src/cli/pane-functions/dom.ts +45 -0
  57. package/src/cli/pane-functions/freshness.ts +46 -9
  58. package/src/cli/pane-functions/function-help.ts +17 -9
  59. package/src/cli/pane-functions/headless.ts +22 -14
  60. package/src/cli/pane-functions/index.ts +6 -2
  61. package/src/cli/pane-functions/options.ts +6 -2
  62. package/src/cli/pane-functions/resolver.ts +27 -5
  63. package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
  64. package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
  65. package/src/cli/pane-functions/screenshot-http.ts +70 -0
  66. package/src/cli/pane-functions/screenshot-render.ts +19 -5
  67. package/src/cli/quote-answer.ts +49 -6
  68. package/src/cli/registry.ts +41 -13
  69. package/src/cli/result.ts +12 -6
  70. package/src/cli/ticker-resolution.ts +2 -1
  71. package/src/cli/types.ts +2 -0
  72. package/src/components/chart/composite/format.ts +3 -0
  73. package/src/components/chart-table/header.tsx +1 -1
  74. package/src/components/chart-table/layout.ts +28 -11
  75. package/src/components/command-bar/action-runtime.ts +4 -0
  76. package/src/components/command-bar/commands/direct/index.ts +18 -33
  77. package/src/components/command-bar/commands/direct/runtime.ts +5 -0
  78. package/src/components/command-bar/commands/registry.ts +10 -1
  79. package/src/components/command-bar/routes/root/command-items.ts +9 -4
  80. package/src/components/command-bar/routes/root/runtime.ts +51 -6
  81. package/src/components/command-bar/selection-runtime.ts +12 -29
  82. package/src/components/command-bar/surface/index.tsx +9 -3
  83. package/src/components/company-logo.tsx +2 -1
  84. package/src/components/layout/detached-pane-shell.tsx +2 -2
  85. package/src/components/layout/header.tsx +10 -5
  86. package/src/components/layout/market-summary.ts +25 -7
  87. package/src/components/layout/pane/footer/index.tsx +8 -2
  88. package/src/components/layout/pane/footer/model.ts +73 -6
  89. package/src/components/layout/pane/header.tsx +2 -2
  90. package/src/components/layout/pane/status-footer.ts +2 -1
  91. package/src/components/market-board/index.tsx +3 -2
  92. package/src/components/onboarding/desks-step.tsx +12 -4
  93. package/src/components/onboarding/first-run-workspace.ts +81 -4
  94. package/src/components/onboarding/onboarding-frame.tsx +4 -2
  95. package/src/components/onboarding/onboarding-keyboard.ts +8 -5
  96. package/src/components/onboarding/onboarding-wizard.tsx +60 -26
  97. package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
  98. package/src/components/onboarding/run-setup-again.ts +24 -0
  99. package/src/components/onboarding/use-onboarding-progress.ts +33 -15
  100. package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
  101. package/src/components/onboarding/wizard-model.ts +57 -3
  102. package/src/components/onboarding/wizard-positions.ts +1 -1
  103. package/src/components/ui/delayed-mark.tsx +56 -0
  104. package/src/components/ui/display.tsx +16 -5
  105. package/src/components/ui/external-link.tsx +3 -6
  106. package/src/components/ui/icon.tsx +16 -3
  107. package/src/components/ui/logo-mark.tsx +8 -5
  108. package/src/components/ui/stat-grid.tsx +263 -19
  109. package/src/core/app-services.ts +11 -2
  110. package/src/data/app-persistence.ts +9 -7
  111. package/src/data/config/store/node.ts +11 -5
  112. package/src/data/config/store/normalize.ts +16 -3
  113. package/src/data/plugin-cache.ts +15 -0
  114. package/src/data/plugin-state-store.ts +81 -24
  115. package/src/data/profile-permissions.ts +47 -0
  116. package/src/data/resource-store.ts +165 -90
  117. package/src/data/session-store.ts +31 -8
  118. package/src/data/sqlite/access.ts +324 -0
  119. package/src/data/sqlite/checkpointer.ts +285 -0
  120. package/src/data/sqlite/database.ts +80 -4
  121. package/src/data/sqlite/retry.ts +50 -9
  122. package/src/data/ticker-store.ts +57 -23
  123. package/src/docs/snapshot.json +191 -78
  124. package/src/i18n/es.ts +18 -2
  125. package/src/i18n/ja.ts +18 -2
  126. package/src/i18n/ko.ts +18 -2
  127. package/src/i18n/zh-cn.ts +18 -2
  128. package/src/i18n/zh-tw.ts +18 -2
  129. package/src/layout/desk-keys.ts +4 -1
  130. package/src/layout/desks.ts +38 -5
  131. package/src/layout-marketplace/gallery-desktop.tsx +45 -16
  132. package/src/market-data/batched-reads.ts +16 -0
  133. package/src/market-data/coordinator/auxiliary.ts +4 -2
  134. package/src/market-data/coordinator/index.ts +11 -2
  135. package/src/market-data/futures-board.ts +1 -1
  136. package/src/market-data/futures-contract.ts +3 -1
  137. package/src/market-data/market/index-level.ts +6 -4
  138. package/src/market-data/published-apac-sessions.ts +6 -0
  139. package/src/market-data/realized-volatility.ts +4 -2
  140. package/src/news/aggregator.ts +7 -4
  141. package/src/news/news-model.ts +11 -0
  142. package/src/news/story-id.ts +22 -0
  143. package/src/news/ticker-symbols.ts +17 -1
  144. package/src/notifications/app-notifier.ts +35 -14
  145. package/src/plugins/auto-update-node.ts +10 -6
  146. package/src/plugins/builtin/account-management/form-components.tsx +1 -1
  147. package/src/plugins/builtin/account-management/index.tsx +2 -3
  148. package/src/plugins/builtin/account-management/model.ts +1 -1
  149. package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
  150. package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
  151. package/src/plugins/builtin/alerts/events.ts +10 -2
  152. package/src/plugins/builtin/alerts/index.tsx +5 -5
  153. package/src/plugins/builtin/alerts/research-builder.ts +5 -1
  154. package/src/plugins/builtin/alerts/research-rules.ts +18 -0
  155. package/src/plugins/builtin/alerts/targets.ts +4 -1
  156. package/src/plugins/builtin/attention/headless.ts +1 -1
  157. package/src/plugins/builtin/attention/index.tsx +2 -4
  158. package/src/plugins/builtin/attention/listing.ts +1 -1
  159. package/src/plugins/builtin/attention/series.ts +1 -1
  160. package/src/plugins/builtin/awards/index.tsx +1 -2
  161. package/src/plugins/builtin/backtest/client.ts +1 -0
  162. package/src/plugins/builtin/backtest/index.tsx +1 -2
  163. package/src/plugins/builtin/backtest/rules.ts +3 -1
  164. package/src/plugins/builtin/backtest/window.ts +8 -7
  165. package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
  166. package/src/plugins/builtin/broker-manager/index.tsx +1 -1
  167. package/src/plugins/builtin/buildout/cells.tsx +5 -0
  168. package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
  169. package/src/plugins/builtin/buildout/company/id.ts +2 -0
  170. package/src/plugins/builtin/buildout/company/model.ts +247 -0
  171. package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
  172. package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
  173. package/src/plugins/builtin/buildout/facilities.ts +309 -0
  174. package/src/plugins/builtin/buildout/format.ts +9 -0
  175. package/src/plugins/builtin/buildout/index.tsx +51 -5
  176. package/src/plugins/builtin/buildout/model/index.ts +62 -10
  177. package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
  178. package/src/plugins/builtin/buildout/model/types.ts +11 -2
  179. package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
  180. package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
  181. package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
  182. package/src/plugins/builtin/buildout/table-model.ts +26 -2
  183. package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
  184. package/src/plugins/builtin/cds/headless.ts +10 -2
  185. package/src/plugins/builtin/cds/model.ts +7 -3
  186. package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
  187. package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
  188. package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
  189. package/src/plugins/builtin/changelog/index.tsx +3 -2
  190. package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
  191. package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
  192. package/src/plugins/builtin/chart-composer/headless.ts +88 -21
  193. package/src/plugins/builtin/chart-composer/index.tsx +2 -3
  194. package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
  195. package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
  196. package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
  197. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  198. package/src/plugins/builtin/chat/controller/index.ts +1 -1
  199. package/src/plugins/builtin/chat/controller/messages.ts +15 -4
  200. package/src/plugins/builtin/chat/ghost-user.ts +8 -0
  201. package/src/plugins/builtin/chat/index.tsx +5 -5
  202. package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
  203. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  204. package/src/plugins/builtin/cloud/index.tsx +1 -2
  205. package/src/plugins/builtin/cloud/plugin.tsx +6 -4
  206. package/src/plugins/builtin/cloud/team/module.tsx +1 -1
  207. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
  208. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
  209. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
  210. package/src/plugins/builtin/comment-letters/index.tsx +1 -2
  211. package/src/plugins/builtin/commodity-balances/client.ts +4 -1
  212. package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
  213. package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
  214. package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
  215. package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
  216. package/src/plugins/builtin/congress-trades/headless.ts +2 -0
  217. package/src/plugins/builtin/congress-trades/model.ts +92 -64
  218. package/src/plugins/builtin/congress-trades/table.tsx +14 -2
  219. package/src/plugins/builtin/connections/index.ts +2 -3
  220. package/src/plugins/builtin/cot/client.ts +28 -2
  221. package/src/plugins/builtin/cot/headless.ts +4 -1
  222. package/src/plugins/builtin/cot/index.tsx +5 -4
  223. package/src/plugins/builtin/cot/model.ts +58 -7
  224. package/src/plugins/builtin/cot/pane.tsx +6 -2
  225. package/src/plugins/builtin/cpi/client.ts +6 -5
  226. package/src/plugins/builtin/cpi/index.tsx +1 -2
  227. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  228. package/src/plugins/builtin/credit-boards/client.ts +7 -2
  229. package/src/plugins/builtin/credit-boards/headless.ts +25 -12
  230. package/src/plugins/builtin/credit-boards/model.ts +29 -0
  231. package/src/plugins/builtin/credit-boards/series.ts +3 -4
  232. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
  233. package/src/plugins/builtin/credit-conditions/client.ts +22 -14
  234. package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
  235. package/src/plugins/builtin/crypto-board/client.ts +5 -5
  236. package/src/plugins/builtin/crypto-board/headless.ts +4 -3
  237. package/src/plugins/builtin/crypto-board/index.tsx +4 -2
  238. package/src/plugins/builtin/crypto-board/series.ts +6 -4
  239. package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
  240. package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
  241. package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
  242. package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
  243. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
  244. package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
  245. package/src/plugins/builtin/debt-maturities/model.ts +17 -1
  246. package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
  247. package/src/plugins/builtin/debt-maturities/series.ts +3 -3
  248. package/src/plugins/builtin/debug/index.tsx +1 -1
  249. package/src/plugins/builtin/dividend-yield/client.ts +32 -2
  250. package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
  251. package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
  252. package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
  253. package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
  254. package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
  255. package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
  256. package/src/plugins/builtin/dividend-yield/view.ts +21 -0
  257. package/src/plugins/builtin/docs/index.tsx +2 -3
  258. package/src/plugins/builtin/doe/client.ts +6 -5
  259. package/src/plugins/builtin/doe/headless.ts +16 -3
  260. package/src/plugins/builtin/doe/index.tsx +5 -4
  261. package/src/plugins/builtin/doe/prices.ts +2 -3
  262. package/src/plugins/builtin/doe/series.ts +4 -4
  263. package/src/plugins/builtin/earnings/board.tsx +2 -1
  264. package/src/plugins/builtin/earnings/history.tsx +2 -1
  265. package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
  266. package/src/plugins/builtin/earnings-calls/data.ts +19 -11
  267. package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
  268. package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
  269. package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
  270. package/src/plugins/builtin/econ/calendar-model.ts +29 -6
  271. package/src/plugins/builtin/econ/detail-view.tsx +16 -2
  272. package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
  273. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  274. package/src/plugins/builtin/equity-screener/headless.ts +1 -0
  275. package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
  276. package/src/plugins/builtin/equity-screener/index.tsx +1 -2
  277. package/src/plugins/builtin/equity-screener/model.ts +28 -4
  278. package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
  279. package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
  280. package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
  281. package/src/plugins/builtin/executives/data.ts +6 -1
  282. package/src/plugins/builtin/executives/pane.tsx +9 -21
  283. package/src/plugins/builtin/exposure/holdings.ts +35 -13
  284. package/src/plugins/builtin/exposure/index.tsx +1 -2
  285. package/src/plugins/builtin/fear-greed/cache.ts +4 -0
  286. package/src/plugins/builtin/fear-greed/cli.ts +44 -0
  287. package/src/plugins/builtin/fear-greed/index.tsx +5 -53
  288. package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
  289. package/src/plugins/builtin/fear-greed/series.ts +6 -4
  290. package/src/plugins/builtin/fixed-income/index.tsx +1 -2
  291. package/src/plugins/builtin/futures/board-reads.ts +25 -0
  292. package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
  293. package/src/plugins/builtin/futures/index.tsx +15 -0
  294. package/src/plugins/builtin/futures/model.ts +14 -0
  295. package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
  296. package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
  297. package/src/plugins/builtin/futures-curve/headless.ts +6 -1
  298. package/src/plugins/builtin/futures-curve/index.tsx +1 -2
  299. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  300. package/src/plugins/builtin/gpu/board.tsx +51 -12
  301. package/src/plugins/builtin/gpu/client.ts +19 -3
  302. package/src/plugins/builtin/gpu/equities.tsx +5 -2
  303. package/src/plugins/builtin/gpu/headless.ts +14 -2
  304. package/src/plugins/builtin/gpu/index.tsx +1 -2
  305. package/src/plugins/builtin/gpu/model.ts +20 -2
  306. package/src/plugins/builtin/gpu/pane.tsx +1 -1
  307. package/src/plugins/builtin/gpu/series.ts +2 -3
  308. package/src/plugins/builtin/help/index.tsx +1 -1
  309. package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
  310. package/src/plugins/builtin/holders/index-holders.ts +24 -12
  311. package/src/plugins/builtin/holders/index.tsx +2 -3
  312. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
  313. package/src/plugins/builtin/iv-history/index.tsx +1 -2
  314. package/src/plugins/builtin/jodi/client.ts +4 -1
  315. package/src/plugins/builtin/jodi/index.tsx +1 -2
  316. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
  317. package/src/plugins/builtin/layout-manager/index.tsx +9 -8
  318. package/src/plugins/builtin/macro-day/pane.tsx +4 -2
  319. package/src/plugins/builtin/market-halts/index.tsx +1 -2
  320. package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
  321. package/src/plugins/builtin/market-movers/client.ts +1 -0
  322. package/src/plugins/builtin/market-movers/index.tsx +16 -5
  323. package/src/plugins/builtin/market-movers/screener.ts +23 -6
  324. package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
  325. package/src/plugins/builtin/market-valuation/index.tsx +1 -2
  326. package/src/plugins/builtin/members/index.tsx +1 -2
  327. package/src/plugins/builtin/money-markets/index.tsx +1 -2
  328. package/src/plugins/builtin/money-markets/pane.tsx +2 -1
  329. package/src/plugins/builtin/money-markets/series.ts +3 -5
  330. package/src/plugins/builtin/news/headless.ts +7 -0
  331. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  332. package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
  333. package/src/plugins/builtin/notes/index.tsx +3 -6
  334. package/src/plugins/builtin/openfda/index.tsx +1 -2
  335. package/src/plugins/builtin/options/view.tsx +6 -5
  336. package/src/plugins/builtin/options-calculator/index.tsx +1 -2
  337. package/src/plugins/builtin/options-positioning/index.tsx +1 -2
  338. package/src/plugins/builtin/options-scenario/index.tsx +2 -4
  339. package/src/plugins/builtin/perps/index.tsx +1 -2
  340. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
  341. package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
  342. package/src/plugins/builtin/plugin-module.ts +16 -2
  343. package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
  344. package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
  345. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
  346. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
  347. package/src/plugins/builtin/power/gas-client.ts +2 -1
  348. package/src/plugins/builtin/power/index.tsx +1 -2
  349. package/src/plugins/builtin/power/prices-client.ts +3 -2
  350. package/src/plugins/builtin/rate-path/index.tsx +1 -2
  351. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  352. package/src/plugins/builtin/realized-vol/index.tsx +1 -2
  353. package/src/plugins/builtin/realized-vol/model.ts +16 -2
  354. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  355. package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
  356. package/src/plugins/builtin/research/analyst-headless.ts +33 -6
  357. package/src/plugins/builtin/research/analyst-model.ts +37 -2
  358. package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
  359. package/src/plugins/builtin/research/index.tsx +5 -8
  360. package/src/plugins/builtin/research-search/index.tsx +1 -2
  361. package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
  362. package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
  363. package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
  364. package/src/plugins/builtin/risk-factors/data.ts +8 -4
  365. package/src/plugins/builtin/risk-factors/index.tsx +3 -1
  366. package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
  367. package/src/plugins/builtin/roadmap/index.tsx +2 -3
  368. package/src/plugins/builtin/roadmap/pane.tsx +49 -2
  369. package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
  370. package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
  371. package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
  372. package/src/plugins/builtin/scanner/lock-note.ts +26 -3
  373. package/src/plugins/builtin/sec/filing-content.ts +38 -6
  374. package/src/plugins/builtin/sec/headless.ts +5 -1
  375. package/src/plugins/builtin/sec/index.tsx +4 -2
  376. package/src/plugins/builtin/sec/model.ts +2 -0
  377. package/src/plugins/builtin/sectors/index.tsx +11 -5
  378. package/src/plugins/builtin/series-inventory.ts +2 -0
  379. package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
  380. package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
  381. package/src/plugins/builtin/shared/forming-bars.ts +29 -0
  382. package/src/plugins/builtin/shared/fred-points.ts +6 -1
  383. package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
  384. package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
  385. package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
  386. package/src/plugins/builtin/shared/series-warning.ts +13 -4
  387. package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
  388. package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
  389. package/src/plugins/builtin/short-volume/pane.tsx +2 -1
  390. package/src/plugins/builtin/short-volume/series.ts +2 -3
  391. package/src/plugins/builtin/social-mentions/client.ts +15 -10
  392. package/src/plugins/builtin/social-mentions/index.tsx +1 -2
  393. package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
  394. package/src/plugins/builtin/social-mentions/series.ts +2 -3
  395. package/src/plugins/builtin/supply-chain/client.ts +22 -1
  396. package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
  397. package/src/plugins/builtin/supply-chain/index.tsx +1 -2
  398. package/src/plugins/builtin/thirteenf/api.ts +127 -29
  399. package/src/plugins/builtin/thirteenf/data.ts +165 -35
  400. package/src/plugins/builtin/thirteenf/index.tsx +2 -4
  401. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  402. package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
  403. package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
  404. package/src/plugins/builtin/thirteenf/types.ts +14 -0
  405. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
  406. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
  407. package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
  408. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
  409. package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
  410. package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
  411. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
  412. package/src/plugins/builtin/time-sales/index.tsx +1 -2
  413. package/src/plugins/builtin/time-sales/pane.tsx +3 -1
  414. package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
  415. package/src/plugins/builtin/trace-bonds/model.ts +44 -10
  416. package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
  417. package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
  418. package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
  419. package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
  420. package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
  421. package/src/plugins/builtin/vol-surface/client.ts +18 -3
  422. package/src/plugins/builtin/vol-surface/index.tsx +1 -2
  423. package/src/plugins/builtin/volatility/client.ts +43 -16
  424. package/src/plugins/builtin/volatility/headless.ts +5 -2
  425. package/src/plugins/builtin/volatility/index.tsx +1 -2
  426. package/src/plugins/builtin/volatility/model.ts +17 -6
  427. package/src/plugins/builtin/volatility/pane.tsx +6 -2
  428. package/src/plugins/builtin/volatility/series.ts +2 -1
  429. package/src/plugins/builtin/world-indices/footer.ts +6 -1
  430. package/src/plugins/builtin/world-venue-map/client.ts +10 -1
  431. package/src/plugins/builtin/world-venue-map/index.ts +6 -2
  432. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
  433. package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
  434. package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
  435. package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
  436. package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
  437. package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
  438. package/src/plugins/builtin/yield-curve/client.ts +4 -6
  439. package/src/plugins/builtin/yield-curve/curves.ts +16 -2
  440. package/src/plugins/builtin/yield-curve/headless.ts +3 -3
  441. package/src/plugins/bundle.ts +52 -2
  442. package/src/plugins/catalog-browser.ts +2 -2
  443. package/src/plugins/catalog-cli.ts +20 -0
  444. package/src/plugins/host-link.ts +13 -1
  445. package/src/plugins/installer.ts +64 -42
  446. package/src/plugins/loader.ts +43 -11
  447. package/src/plugins/peer-imports.ts +201 -0
  448. package/src/plugins/peer-retry.ts +26 -29
  449. package/src/plugins/plugin-process.ts +128 -0
  450. package/src/plugins/redirection-guard.ts +45 -0
  451. package/src/remote/endpoint.ts +16 -3
  452. package/src/renderers/browser/main.tsx +3 -2
  453. package/src/renderers/cloudflare/http-proxy.ts +7 -123
  454. package/src/renderers/dom/desktop/icons.tsx +5 -2
  455. package/src/renderers/dom/host/image-surface.tsx +46 -21
  456. package/src/renderers/dom/styles.css +7 -0
  457. package/src/renderers/dom/toast-host.tsx +2 -0
  458. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  459. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
  460. package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
  461. package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
  462. package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
  463. package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
  464. package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
  465. package/src/renderers/electrobun/bun/window/attention.ts +54 -0
  466. package/src/renderers/electrobun/bun/window/create.ts +6 -2
  467. package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
  468. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
  469. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
  470. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
  471. package/src/renderers/electrobun/view/main.tsx +7 -2
  472. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
  473. package/src/renderers/electrobun/view/ui-host.tsx +9 -2
  474. package/src/renderers/opentui/browser-open.ts +10 -4
  475. package/src/renderers/opentui/image/surface.tsx +1 -1
  476. package/src/renderers/opentui/start.tsx +12 -6
  477. package/src/renderers/opentui/toast-host.tsx +2 -1
  478. package/src/series/registry.ts +18 -0
  479. package/src/series/resolve.ts +2 -1
  480. package/src/series/standalone.ts +10 -1
  481. package/src/series/types.ts +12 -0
  482. package/src/series/validate.ts +11 -2
  483. package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
  484. package/src/sources/gloomberb-cloud/index.ts +28 -5
  485. package/src/sources/gloomberb-cloud/news.ts +24 -3
  486. package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
  487. package/src/sources/provider-errors.ts +26 -3
  488. package/src/sources/provider-router/history.ts +5 -0
  489. package/src/sources/provider-router/index.ts +13 -0
  490. package/src/sync/core-contributors.ts +32 -0
  491. package/src/telemetry/attention-counts.ts +64 -6
  492. package/src/telemetry/attention-setting.ts +33 -0
  493. package/src/telemetry/crash-reports-node.ts +3 -1
  494. package/src/telemetry/stall-clocks.ts +93 -0
  495. package/src/telemetry/stall-reports-node.ts +25 -9
  496. package/src/telemetry/stall-reports.ts +218 -13
  497. package/src/test-support/outage.ts +8 -0
  498. package/src/test-support/preload.ts +9 -0
  499. package/src/tickers/company-name-listing.ts +59 -29
  500. package/src/tickers/company-name.ts +84 -1
  501. package/src/tickers/listing-fate.ts +14 -3
  502. package/src/tickers/saved-instrument.ts +17 -0
  503. package/src/tickers/search/home-listing.ts +104 -8
  504. package/src/tickers/search/index.ts +5 -2
  505. package/src/tickers/search/types.ts +6 -1
  506. package/src/time-series/bar-aggregation.ts +7 -1
  507. package/src/time-series/base-series.ts +3 -3
  508. package/src/time-series/chart-bounds.ts +1 -1
  509. package/src/time-series/chart-data.ts +5 -2
  510. package/src/time-series/chart-seed.ts +1 -1
  511. package/src/time-series/coin-session-close.ts +211 -0
  512. package/src/time-series/live-bars.ts +13 -2
  513. package/src/time-series/live-quotes.ts +1 -1
  514. package/src/time-series/market.ts +5 -3
  515. package/src/time-series/official-close.ts +159 -0
  516. package/src/time-series/price-comparison.ts +75 -8
  517. package/src/time-series/resolve.ts +32 -4
  518. package/src/time-series/series-presentation.ts +1 -1
  519. package/src/time-series/studies/formula.ts +205 -0
  520. package/src/time-series/studies/indicators.ts +299 -0
  521. package/src/time-series/studies/leverage.ts +75 -0
  522. package/src/time-series/studies/pair.ts +138 -0
  523. package/src/time-series/studies/resolve.ts +333 -0
  524. package/src/time-series/studies/shared.ts +141 -0
  525. package/src/time-series/studies/units.ts +243 -0
  526. package/src/time-series/studies/vwap.ts +274 -0
  527. package/src/time-series/types.ts +7 -0
  528. package/src/types/config.ts +18 -2
  529. package/src/types/data-provider.ts +23 -0
  530. package/src/types/financials.ts +40 -3
  531. package/src/types/headless.ts +42 -3
  532. package/src/types/plugin.ts +32 -3
  533. package/src/ui/host.tsx +17 -1
  534. package/src/ui/toast.tsx +26 -1
  535. package/src/utils/ascii-text.ts +140 -0
  536. package/src/utils/cli-output.ts +91 -18
  537. package/src/utils/concurrency.ts +24 -0
  538. package/src/utils/exchanges.ts +6 -0
  539. package/src/utils/external-url.ts +20 -7
  540. package/src/utils/financial-statements.ts +73 -16
  541. package/src/utils/fundamentals.ts +17 -5
  542. package/src/utils/futures-generic.ts +4 -4
  543. package/src/utils/http-proxy-upstream.ts +124 -0
  544. package/src/utils/lease-liabilities.ts +54 -0
  545. package/src/utils/main-thread-stall.ts +122 -5
  546. package/src/utils/perf-marks.ts +58 -12
  547. package/src/utils/price-history-integrity.ts +47 -1
  548. package/src/utils/private-files.ts +141 -0
  549. package/src/utils/public-address.ts +99 -0
  550. package/src/account/upgrade-personalization.ts +0 -57
  551. package/src/account/upgrade-tickers.ts +0 -124
  552. package/src/api-client/upgrade-experiment.ts +0 -91
  553. package/src/plugins/builtin/cloud/browser.tsx +0 -3
  554. package/src/time-series/studies.ts +0 -1628
@@ -1,3 +1,5 @@
1
+ import type { InstrumentSearchResult } from "../types/instrument";
2
+
1
3
  /**
2
4
  * Company names as venues spell them, compared and shortened for sentences.
3
5
  * One company's listings carry its name with different legal forms and share
@@ -9,7 +11,7 @@
9
11
  const NAME_NOISE = new Set([
10
12
  "co", "company", "corp", "corporation", "inc", "incorporated", "ltd", "limited", "llc", "lp", "plc", "sa", "ag", "nv", "se",
11
13
  "spa", "ab", "as", "asa", "oyj", "bv", "gmbh", "kgaa", "bhd", "berhad", "pcl", "the", "adr", "ads", "cedear", "cedears",
12
- "sponsored", "unsponsored", "cdr", "cdi",
14
+ "sponsored", "unsponsored", "cdr", "cdi", "aktiengesellschaft", "aktiebolag",
13
15
  ]);
14
16
  // Words a venue adds to the share it lists ("Microsoft Corporation Common Stock"), never part of whose it is.
15
17
  const SHARE_NOISE = new Set(["common", "ordinary", "stock", "shares"]);
@@ -43,6 +45,56 @@ export function sameCompany(left: string | null | undefined, right: string | nul
43
45
  return [...fewer].every((word) => more.has(word));
44
46
  }
45
47
 
48
+ // Words that tell one class of a company's shares from another ("Berkshire Hathaway Inc. Class B"), never its business.
49
+ const SHARE_CLASS_WORD = /^(?:class|series|[a-z]|\d+)$/;
50
+
51
+ /** Whether two names differ only in how they name a class of shares, so they are one company's whatever the venue. */
52
+ function sameBusinessWords(left: Set<string>, right: Set<string>): boolean {
53
+ const differ = [...left].filter((word) => !right.has(word)).concat([...right].filter((word) => !left.has(word)));
54
+ return differ.every((word) => SHARE_CLASS_WORD.test(word));
55
+ }
56
+
57
+ /** A company line as `companyLines` weighs it. */
58
+ export interface CompanyLineFacts {
59
+ name: string | null | undefined;
60
+ /** The line's canonical venue. */
61
+ exchange: string;
62
+ /** A depositary receipt, whose name says whose shares it holds rather than whose it is. */
63
+ receipt: boolean;
64
+ /** The service marks it as its company's home line (`homeListing`). */
65
+ home: boolean;
66
+ }
67
+
68
+ /**
69
+ * The lines among `lines` that are `lead`'s company's own (`sameCompany`),
70
+ * less those under a name that adds words for another business, though one
71
+ * name's words are all in the other's: Siemens Energy AG and Siemens
72
+ * Healthineers AG are not Siemens AG, nor Sony Financial Group Sony Group. A
73
+ * name is another business's where a venue lists a line under it beside a
74
+ * line under the lead's own name (both on XETRA, both in Tokyo), or where it
75
+ * and a line under the lead's name are each marked as a home line, since a
76
+ * company has one (Siemens Ltd. on NSE beside Siemens AG on XETRA). Receipts
77
+ * are named for the shares they hold, so they never tell two businesses
78
+ * apart, and nor do words that name a class of shares (Class B). A name with
79
+ * more words on no shared venue stays the company's (Deutsche Lufthansa AG on
80
+ * XETRA for LUFTHANSA on Budapest). `lead` is always one of them.
81
+ */
82
+ export function companyLines<T>(lead: T, lines: readonly T[], facts: (line: T) => CompanyLineFacts): T[] {
83
+ const own = facts(lead);
84
+ const ownWords = companyWords(own.name);
85
+ const ownName = (line: CompanyLineFacts) => sameBusinessWords(companyWords(line.name), ownWords);
86
+ const candidates = lines.filter((line) => line === lead || sameCompany(own.name, facts(line).name));
87
+ const read = candidates.map((line) => ({ line, facts: facts(line), words: companyWords(facts(line).name) }));
88
+ const ownShares = read.filter((entry) => !entry.facts.receipt && ownName(entry.facts));
89
+ const ownVenues = new Set(ownShares.map((entry) => entry.facts.exchange).filter(Boolean));
90
+ const ownHome = ownShares.some((entry) => entry.facts.home);
91
+ // A name another business goes by, once one of its share lines gives it away: all its lines are that business's.
92
+ const others = read.filter((entry) => !entry.facts.receipt && !ownName(entry.facts)
93
+ && ((!!entry.facts.exchange && ownVenues.has(entry.facts.exchange)) || (entry.facts.home && ownHome)));
94
+ const otherBusiness = (words: Set<string>) => others.some((other) => sameBusinessWords(other.words, words));
95
+ return read.filter((entry) => entry.line === lead || ownName(entry.facts) || !otherBusiness(entry.words)).map((entry) => entry.line);
96
+ }
97
+
46
98
  /**
47
99
  * How many words a listing's name has beyond a company name typed for it,
48
100
  * which it must have every word of: 0 for the same name however each spells
@@ -152,6 +204,25 @@ export function groupByCompany<T>(
152
204
  return groups;
153
205
  }
154
206
 
207
+ /**
208
+ * A one-letter code a venue pads onto the end of a name after a run of
209
+ * spaces: the `R` of "Americold Realty Trust Inc. R". A longer word there
210
+ * (`AG`, `ASA`) is the name's own.
211
+ */
212
+ const STRAY_NAME_TAIL = /\s{2,}[A-Za-z0-9]$/;
213
+
214
+ /**
215
+ * A listing's name as a venue's file writes it, without the stray fragment a
216
+ * fixed-width file leaves after a run of spaces ("Americold Realty Trust
217
+ * Inc. R" on Hanover is "Americold Realty Trust Inc."), and with its spaces
218
+ * single. A name that is only the fragment keeps it.
219
+ */
220
+ export function cleanListingName(name: string): string {
221
+ const trimmed = name.trim();
222
+ const cut = trimmed.replace(STRAY_NAME_TAIL, "");
223
+ return (cut || trimmed).replace(/\s+/g, " ");
224
+ }
225
+
155
226
  /** A company's name without its legal form, for sentences: "Avis Budget Group, Inc." is "Avis Budget Group". */
156
227
  export function shortCompanyName(name: string): string {
157
228
  const words = name.trim().split(/\s+/).filter(Boolean);
@@ -234,3 +305,15 @@ export function companyNameCase(name: string): string {
234
305
  }).join("");
235
306
  });
236
307
  }
308
+
309
+ /** Letters and digits only, in capitals: "JP Morgan" and "JPMorgan" are JPMORGAN. */
310
+ export function runTogether(text: string): string {
311
+ return text.normalize("NFKD").replace(/\p{M}/gu, "").toUpperCase().replace(/[^\p{L}\p{N}]/gu, "");
312
+ }
313
+
314
+ /** Whether the service marked the row with the name as typed: the company it means by it. */
315
+ export function namedByService(typed: string, result: Pick<InstrumentSearchResult, "searchAliases">): boolean {
316
+ const wanted = runTogether(typed);
317
+ return !!wanted && Array.isArray(result.searchAliases)
318
+ && result.searchAliases.some((alias) => typeof alias === "string" && runTogether(alias) === wanted);
319
+ }
@@ -132,6 +132,12 @@ export function listingChangeLine(fate: Pick<ListingFate, "change" | "formerIssu
132
132
  export const NO_FATE: ListingFate = { endedOn: null, change: null, note: null, moved: null, formerIssuer: null };
133
133
  /** How long a fate waits for a change lookup it started late, or for a stopped line's. */
134
134
  export const LATE_CHANGE_WAIT_MS = 2_000;
135
+ /**
136
+ * How long a command that says what became of a symbol's company (GOGL's merger) waits for the lookup it started
137
+ * beside its own request, once that is in: the lookup takes about a third of a second, and a cached request answers
138
+ * at once. A lookup that hangs costs a symbol that trades no more than this.
139
+ */
140
+ export const SUCCESSOR_CHANGE_WAIT_MS = 750;
135
141
 
136
142
  // The zones of the US exchanges; a venue in another is abroad.
137
143
  const US_TIME_ZONES = new Set(["America/New_York", "America/Chicago"]);
@@ -195,7 +201,11 @@ export function lineMayHaveMoved(quote: Quote | null, now = Date.now()): boolean
195
201
  export async function loadListingFate(
196
202
  listing: { symbol: string; exchange?: string | null; request?: { symbol: string; exchange: string } },
197
203
  dataProvider: Pick<DataProvider, "getQuote" | "getTickerChanges">,
198
- options: { quote?: Quote | null | Promise<Quote | null>; changes?: TickerChangeLookup } = {},
204
+ /**
205
+ * `graceMs`: how long a lookup the command started waits once the quote is in, for a command that says what
206
+ * became of the company beside what it serves (GOGL's merger), not only whether it moved. A short grace by default.
207
+ */
208
+ options: { quote?: Quote | null | Promise<Quote | null>; changes?: TickerChangeLookup; graceMs?: number } = {},
199
209
  ): Promise<ListingFate> {
200
210
  const request = listing.request ?? { symbol: listing.symbol, exchange: listing.exchange ?? "" };
201
211
  const lookup = options.changes ?? startTickerChangeLookup(dataProvider, listing.symbol, listing.exchange);
@@ -206,7 +216,7 @@ export async function loadListingFate(
206
216
  // A lookup started beside the command's request gets a short grace once that is ready, so a slow
207
217
  // one never holds a symbol that trades. A line that stopped is waited for: only its company can
208
218
  // have moved, and then the answer decides whether the command serves anything at all.
209
- const changes = await lookup.settle(!options.changes || lineMayHaveMoved(quote) ? LATE_CHANGE_WAIT_MS : undefined);
219
+ const changes = await lookup.settle(!options.changes || lineMayHaveMoved(quote) ? LATE_CHANGE_WAIT_MS : options.graceMs);
210
220
  return describeListingFate(listing, quote, changes);
211
221
  } catch (error) {
212
222
  logFailure("tickers-listing-fate", "Could not resolve listing fate")(error);
@@ -230,7 +240,8 @@ export function listingFateNotes(fate: ListingFate, served: { served: boolean; f
230
240
  return [
231
241
  // What it served ends on the listing's last session, and says so.
232
242
  fate.delisted ?? null,
233
- tickerChangeLine(change),
243
+ // A company that left the symbol, else one that merged away from it (GOGL), as `quote` says it.
244
+ listingChangeLine(fate),
234
245
  earlier.length > 0 ? `Includes ${earlier.join("- and ")}-era bars: ${change!.note}` : null,
235
246
  ].filter((line): line is string => !!line);
236
247
  }
@@ -0,0 +1,17 @@
1
+ import type { TickerRecord } from "../types/ticker";
2
+ import { parsePublicTickerKey } from "../utils/exchanges";
3
+
4
+ /**
5
+ * The listing a typed key stands for in a report (`resolveInstrument`): the
6
+ * venue it names (`SBK:JSE`), else the venue of the ticker saved under the key
7
+ * or under its bare symbol, so a saved `S32` held on the JSE is read there.
8
+ * `fn`, `shot` and the panes that read as they do resolve keys this way.
9
+ */
10
+ export async function resolveSavedInstrument(
11
+ key: string,
12
+ loadTicker: (key: string) => Promise<TickerRecord | null | undefined> | TickerRecord | null | undefined,
13
+ ): Promise<{ symbol: string; exchange?: string }> {
14
+ const parsed = parsePublicTickerKey(key);
15
+ const ticker = await loadTicker(key) ?? (key !== parsed.symbol ? await loadTicker(parsed.symbol) : null);
16
+ return { symbol: parsed.symbol, exchange: parsed.exchange ?? ticker?.metadata.exchange };
17
+ }
@@ -1,5 +1,5 @@
1
- import { canonicalExchange } from "../../utils/exchanges";
2
- import { companyNameExtraWords, sameCompany } from "../company-name";
1
+ import { canonicalExchange, isUsListingExchange } from "../../utils/exchanges";
2
+ import { companyLines, companyNameExtraWords, namedByService, type CompanyLineFacts } from "../company-name";
3
3
  import { answersOnlyByKeyword, isQualifiedTickerQuery } from "./ranking";
4
4
  import type { TickerSearchCandidate } from "./types";
5
5
 
@@ -29,6 +29,69 @@ export function lineDistance(type: string, exchange: string, symbol: string): 0
29
29
  return SECONDARY_VENUES.has(exchange) || /\d/.test(symbol) && /[A-Za-z]/.test(symbol) ? 1 : 0;
30
30
  }
31
31
 
32
+ /** How the primary-listing choice weighs one of a company's lines. */
33
+ export interface WeighedCompanyLine {
34
+ /** `listingDistance`: 0 its own line, 1 a line quoting shares listed elsewhere, 2 a receipt. */
35
+ distance: number;
36
+ /** The search score the service gives the listings people look up most; null when it gives none. */
37
+ score: number | null;
38
+ }
39
+
40
+ /**
41
+ * The line a company's search lists as its primary listing instead of `lead`,
42
+ * the line search put first, often the one-off cross-listing whose short name
43
+ * matched the query (LUFTHANSA on Budapest): its lines go by how far each is
44
+ * from being its own line, then by the search score. The lead gives way only
45
+ * to a closer line, or to one searched at least twice as often, or any scored
46
+ * line when it has no score. With `named` (the service marked the lead with
47
+ * the name as typed) no score outweighs the mark: only a closer line does.
48
+ * `lines` holds the lead. `search` and a company's name typed for a listing
49
+ * (`findListingOfCompanyName`) choose by this one rule, so both open the same
50
+ * listing.
51
+ */
52
+ export function primaryCompanyLine<T>(
53
+ lead: T,
54
+ lines: readonly T[],
55
+ weigh: (line: T) => WeighedCompanyLine,
56
+ options: { named?: boolean } = {},
57
+ ): T {
58
+ const order = (line: T) => lines.indexOf(line);
59
+ const [home] = [...lines].sort((a, b) => weigh(a).distance - weigh(b).distance
60
+ || (weigh(b).score ?? -1) - (weigh(a).score ?? -1) || order(a) - order(b));
61
+ if (!home || home === lead) return lead;
62
+ const [best, first] = [weigh(home), weigh(lead)];
63
+ const closer = best.distance < first.distance;
64
+ const moreSearched = !options.named && best.distance === first.distance && best.score != null
65
+ && (first.score == null || best.score >= first.score * 2);
66
+ return closer || moreSearched ? home : lead;
67
+ }
68
+
69
+ /** A row as `companyLines` weighs it. */
70
+ function lineFacts(candidate: TickerSearchCandidate): CompanyLineFacts {
71
+ return {
72
+ name: candidateName(candidate),
73
+ exchange: candidateExchange(candidate),
74
+ receipt: listingDistance(candidate) === 2,
75
+ home: candidate.result?.homeListing === true,
76
+ };
77
+ }
78
+
79
+ /** The rows of `lead`'s company among `items` (`companyLines`): never a line of another business its name is in (Siemens Energy for Siemens). */
80
+ function companyRows<T extends TickerSearchCandidate>(lead: T, items: readonly T[]): T[] {
81
+ return companyLines(lead, items.filter((item) => item === lead || isListedEquity(item)), lineFacts);
82
+ }
83
+
84
+ /** `primaryCompanyLine` among `items` of `start`'s company, for the command bar's rows. */
85
+ function primaryLineOf<T extends TickerSearchCandidate>(start: T, items: readonly T[], query: string): T {
86
+ const name = candidateName(start);
87
+ const named = !!start.result && namedByService(query, start.result);
88
+ const lines = companyRows(start, items).filter((item) => item === start
89
+ || !named || companyNameExtraWords(candidateName(item), name) != null);
90
+ return primaryCompanyLine(start, lines, (item) => ({
91
+ distance: listingDistance(item), score: item.result?.popularity ?? item.popularity ?? null,
92
+ }), { named });
93
+ }
94
+
32
95
  function candidateName(candidate: TickerSearchCandidate): string {
33
96
  return candidate.result?.name || candidate.detail.split(" | ")[0] || "";
34
97
  }
@@ -51,24 +114,57 @@ function isListedEquity(candidate: TickerSearchCandidate): boolean {
51
114
  * service marks as the company's home (`homeListing`) takes the lead from
52
115
  * the company's first row. Without that mark, a receipt in the lead gives
53
116
  * way to the company's own shares on an exchange (`listingDistance` 0, not
54
- * an OTC or regional line), the name nearest the receipt's first. A query that names a venue (LVMH:TSX,
55
- * 7203.T) asked for that line, and a saved row keeps the lead.
117
+ * an OTC or regional line), the name nearest the receipt's first. Then the
118
+ * line `search` calls the company's primary listing (`primaryCompanyLine`)
119
+ * takes the lead, so a name opens what `search` lists first: LHA on XETRA
120
+ * for "lufthansa", not LUFTHANSA on Budapest. A company's lines are never
121
+ * another business's its name is in (`companyLines`: Siemens Energy AG for
122
+ * Siemens AG). The row the service marks with the word as typed is the
123
+ * company the word means, ahead of a line abroad whose symbol is the word:
124
+ * "siemens" is SIE on XETRA, not SIEMENS on NSE (Siemens Ltd.); a US line
125
+ * keeps its ticker. A symbol typed whose first row is another company's
126
+ * regional line abroad leads with the line the service marks as a home under
127
+ * that symbol (YAR is Yara on OSL, not Americold on Hanover). A query that
128
+ * names a venue (LVMH:TSX, 7203.T) asked for that line, and a saved row keeps
129
+ * the lead.
56
130
  */
57
131
  export function withHomeListingLead<T extends TickerSearchCandidate>(items: T[], query: string): T[] {
58
132
  if (isQualifiedTickerQuery(query)) return items;
59
133
  const lead = items.find((item) => item.instrumentClass !== "fund" && item.instrumentClass !== "derivative"
60
134
  && !answersOnlyByKeyword(item, query));
61
135
  if (!lead || !isListedEquity(lead)) return items;
136
+ const typedSymbol = lead.symbol.toUpperCase() === query.trim().toUpperCase();
137
+ const meant = typedSymbol && !isUsListingExchange(candidateExchange(lead)) && !(lead.result && namedByService(query, lead.result))
138
+ // A company's shares under another symbol: an index the code also names (SPX) or the word's own line elsewhere (SAP) is not it.
139
+ ? items.find((item) => item !== lead && isListedEquity(item) && item.instrumentClass === "equity" && !!item.result
140
+ && item.symbol.toUpperCase() !== lead.symbol.toUpperCase() && namedByService(query, item.result))
141
+ : undefined;
142
+ if (meant) return moveAhead(items, meant, lead);
143
+ // A line abroad that only quotes shares listed elsewhere (a German regional venue) gives way to the line the
144
+ // service marks as a company's home under the same symbol: YAR is Yara on Oslo, not Americold on Hanover.
145
+ const homeOfSymbol = typedSymbol && lead.result?.homeListing !== true && listingDistance(lead) === 1
146
+ && !isUsListingExchange(candidateExchange(lead))
147
+ ? items.find((item) => item !== lead && isListedEquity(item) && item.result?.homeListing === true
148
+ && item.symbol.toUpperCase() === lead.symbol.toUpperCase())
149
+ : undefined;
150
+ if (homeOfSymbol) return moveAhead(items, homeOfSymbol, lead);
62
151
  const name = candidateName(lead);
63
- const lines = items.filter((item) => item !== lead && isListedEquity(item) && sameCompany(name, candidateName(item)));
152
+ const lines = companyRows(lead, items).filter((item) => item !== lead);
64
153
  const nearest = (candidates: T[]) => [...candidates].sort((a, b) => nameGap(name, candidateName(a)) - nameGap(name, candidateName(b))
65
154
  || items.indexOf(a) - items.indexOf(b))[0];
66
155
  const marked = lines.filter((item) => item.result?.homeListing === true);
156
+ const own = listingDistance(lead) === 2 ? nearest(lines.filter((item) => listingDistance(item) === 0)) : undefined;
67
157
  const home = lead.result?.homeListing === true ? undefined
68
158
  : marked.length ? nearest(marked)
69
- : listingDistance(lead) === 2 ? nearest(lines.filter((item) => listingDistance(item) === 0))
70
- : undefined;
71
- if (!home) return items;
159
+ // A listing searched by its own symbol is that listing, as `search` keeps it.
160
+ : typedSymbol ? own
161
+ : primaryLineOf(own ?? lead, [lead, ...lines], query);
162
+ if (!home || home === lead) return items;
163
+ return moveAhead(items, home, lead);
164
+ }
165
+
166
+ /** `items` with `home` moved just ahead of `lead`. */
167
+ function moveAhead<T>(items: readonly T[], home: T, lead: T): T[] {
72
168
  const rest = items.filter((item) => item !== home);
73
169
  const at = rest.indexOf(lead);
74
170
  return [...rest.slice(0, at), home, ...rest.slice(at)];
@@ -54,7 +54,7 @@ export {
54
54
  rankTickerSearchItems,
55
55
  } from "./ranking";
56
56
  export { upsertTickerFromSearchResult } from "./upsert";
57
- export { candidateExchange, listingDistance } from "./home-listing";
57
+ export { candidateExchange, listingDistance, primaryCompanyLine } from "./home-listing";
58
58
 
59
59
  const MAX_NAMED_LISTINGS = 6;
60
60
 
@@ -507,7 +507,10 @@ async function resolveTickerSymbol(
507
507
  throw new AmbiguousContractError(symbol, matches.map((item) => tickerInstrumentLabel(item.symbol, item.result?.brokerContract)));
508
508
  }
509
509
  const listings = new Set(matches.map((item) => publicTickerKey(item.symbol, listingExchange(item.result!))));
510
- const ambiguity = (query: string) => new AmbiguousTickerError(query, [...listings], Object.fromEntries(matches.map((item) => [
510
+ // A line the service marks as a company's home leads the choices: YAR is Yara on Oslo before Americold's German lines.
511
+ const homeFirst = [...matches.filter((item) => item.result?.homeListing === true), ...matches.filter((item) => item.result?.homeListing !== true)];
512
+ const choices = new Set(homeFirst.map((item) => publicTickerKey(item.symbol, listingExchange(item.result!))));
513
+ const ambiguity = (query: string) => new AmbiguousTickerError(query, [...choices], Object.fromEntries(matches.map((item) => [
511
514
  publicTickerKey(item.symbol, listingExchange(item.result!)), listingChoiceName(item.result!),
512
515
  ])));
513
516
  if (pickerQuery && listings.size > 1) throw ambiguity(pickerQuery);
@@ -3,7 +3,12 @@ import type { Quote } from "../../types/financials";
3
3
  import type { TickerRecord } from "../../types/ticker";
4
4
 
5
5
  export type TickerSearchInstrumentClass = "equity" | "fund" | "derivative" | "other";
6
- type TickerSearchCategory = "Saved" | "Primary Listing" | "Other Listings" | "Funds & Derivatives" | "Related";
6
+ /**
7
+ * `Other Matches`: rows `gloom search` finds for the query that are not a listing
8
+ * of the primary listing's company (another company's DK, a line search names
9
+ * no company for), kept apart from its Other Listings.
10
+ */
11
+ type TickerSearchCategory = "Saved" | "Primary Listing" | "Other Listings" | "Other Matches" | "Funds & Derivatives" | "Related";
7
12
 
8
13
  export interface TickerSearchRankableItem {
9
14
  id: string;
@@ -182,6 +182,8 @@ function buildBar(bucket: Bucket, intraday: boolean): AggregatedPricePoint {
182
182
  let low = Number.POSITIVE_INFINITY;
183
183
  let volume: number | undefined;
184
184
  let venueVolume: PricePoint["venueVolume"] | null | undefined;
185
+ // What the volume counts carries over while every bar counts the same way.
186
+ let volumeBasis: string | null | undefined;
185
187
  let partial = false;
186
188
  let filled = false;
187
189
  const integrity: PriceHistoryIntegrity[] = [];
@@ -189,7 +191,10 @@ function buildBar(bucket: Bucket, intraday: boolean): AggregatedPricePoint {
189
191
  const close = bar.close;
190
192
  high = Math.max(high, isFiniteNumber(bar.high) ? bar.high : close);
191
193
  low = Math.min(low, isFiniteNumber(bar.low) ? bar.low : close);
192
- if (isFiniteNumber(bar.volume)) volume = (volume ?? 0) + bar.volume;
194
+ if (isFiniteNumber(bar.volume)) {
195
+ volume = (volume ?? 0) + bar.volume;
196
+ volumeBasis = volumeBasis === undefined || volumeBasis === bar.volumeBasis ? bar.volumeBasis ?? null : null;
197
+ }
193
198
  // One venue's trades add up only while every bar counts the same venue.
194
199
  if (bar.venueVolume && venueVolume !== null) {
195
200
  venueVolume = venueVolume === undefined || venueVolume.venue === bar.venueVolume.venue
@@ -207,6 +212,7 @@ function buildBar(bucket: Bucket, intraday: boolean): AggregatedPricePoint {
207
212
  low,
208
213
  close: last.close,
209
214
  ...(volume !== undefined ? { volume } : {}),
215
+ ...(volume !== undefined && volumeBasis ? { volumeBasis } : {}),
210
216
  ...(volume === undefined && venueVolume ? { venueVolume } : {}),
211
217
  ...(partial ? { partial: true } : {}),
212
218
  ...(filled ? { filled: true } : {}),
@@ -15,7 +15,7 @@ import {
15
15
  valuationPriceIssues,
16
16
  } from "./fundamentals";
17
17
  import { extractSecuritySeries } from "./market";
18
- import { activeStudyInputSeriesIds } from "./studies";
18
+ import { activeStudyInputSeriesIds } from "./studies/resolve";
19
19
  import { reportingCurrencySeries } from "./reporting-currency";
20
20
  import { compareChartQuoteRecency } from "./live-quotes";
21
21
  import { marketSeriesFrequency } from "./market";
@@ -138,8 +138,8 @@ export function baseSecuritySeries(
138
138
  : field.unit;
139
139
  const generic = field.unitGroup === "price"
140
140
  ? futuresGenericListing(spec.source.instrument.symbol, spec.source.instrument.exchange) : null;
141
- // A generic future on an index, VIX or Treasuries reads in points or 32nds, and an index
142
- // future's continuous symbol or contract in points, not dollars, as its quote does.
141
+ // Index and Treasury futures read in points or 32nds across generics,
142
+ // continuous symbols and listed contracts, as their quotes do.
143
143
  const genericBasis = field.unitGroup === "price" && isMarketFieldId(field.id)
144
144
  ? futuresListingPriceBasis(spec.source.instrument.symbol, spec.source.instrument.exchange) : null;
145
145
  // An index's level is points too, never the currency its members trade in.
@@ -15,7 +15,7 @@ import {
15
15
  type ManualChartResolution,
16
16
  } from "./resolution";
17
17
  import { TIME_RANGES, type TimeRange } from "./range";
18
- import { maxStudyWarmupPoints } from "./studies";
18
+ import { maxStudyWarmupPoints } from "./studies/resolve";
19
19
  import type { PriceComparison } from "./price-comparison";
20
20
  import { resolutionForExplicitMarketPeriods } from "./market-resolution";
21
21
  import { latestSessionStart, latestTradingSessionOpen, priorSessionClose } from "../market-data/market/trading-sessions";
@@ -103,8 +103,11 @@ function finiteOrFallback(value: number | undefined, fallback: number): number {
103
103
  }
104
104
 
105
105
  function mergeQuoteIntoLatestBar(latest: PricePoint, quotePrice: number, range?: QuoteSessionRange | null): PricePoint {
106
- // A later quote cannot establish which reported OHLC field was wrong.
107
- if (pricePointIntegrity(latest)) return latest;
106
+ if (pricePointIntegrity(latest)) {
107
+ // A later quote cannot establish which reported OHLC field was wrong, unless it names the bar's whole session: its
108
+ // open, range and price are the bar (GOGL's 2026-10-30 daily bar, served with a high under its open).
109
+ return range?.open === undefined ? latest : { ...latest, open: range.open, high: range.high, low: range.low, close: quotePrice };
110
+ }
108
111
  const open = finiteOrFallback(latest.open, latest.close);
109
112
  const high = finiteOrFallback(latest.high, Math.max(open, latest.close));
110
113
  const low = finiteOrFallback(latest.low, Math.min(open, latest.close));
@@ -10,7 +10,7 @@ import { aggregatePriceBars, futuresOpenMinuteOf, withBuiltBarSize } from "./bar
10
10
  import type { TickerFinancials } from "../types/financials";
11
11
  import { isMarketFieldId } from "./field-catalog";
12
12
  import { chartPriceHistoryIntegrityNotices } from "./market";
13
- import { resolveStudies } from "./studies";
13
+ import { resolveStudies } from "./studies/resolve";
14
14
  import { clipSeriesToWindow } from "./alignment";
15
15
  import { bufferPriceComparison, clipPriceComparison, priceComparisonBoundsForSeries, resolvePriceComparison } from "./price-comparison";
16
16
  import { readSeedListingFacts } from "./parsed-history-cache";