gloomberb 0.10.4 → 0.11.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 (555) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +30 -3
  3. package/README.zh-CN.md +1 -1
  4. package/package.json +5 -5
  5. package/src/api-client/auth.ts +34 -0
  6. package/src/api-client/data.ts +25 -0
  7. package/src/api-client/index.test.ts +81 -0
  8. package/src/api-client/index.ts +37 -0
  9. package/src/api-client/request.test.ts +30 -0
  10. package/src/api-client/request.ts +44 -20
  11. package/src/api-client/socket-health.test.ts +79 -0
  12. package/src/api-client/socket.ts +108 -5
  13. package/src/api-client/types.ts +139 -0
  14. package/src/app/global-shortcuts.test.tsx +144 -9
  15. package/src/app/global-shortcuts.ts +33 -11
  16. package/src/app/runtime/broker-import.ts +39 -5
  17. package/src/app/runtime/startup.ts +7 -1
  18. package/src/app/runtime/update.ts +26 -1
  19. package/src/app.tsx +36 -19
  20. package/src/brokers/broker-ticker-sync.ts +3 -8
  21. package/src/brokers/sync-broker-instance.test.ts +59 -0
  22. package/src/brokers/sync-broker-instance.ts +37 -13
  23. package/src/capabilities/chart-series.ts +318 -0
  24. package/src/capabilities/factories.ts +46 -0
  25. package/src/capabilities/index.ts +1 -0
  26. package/src/capabilities/registry.test.ts +135 -0
  27. package/src/capabilities/registry.ts +31 -4
  28. package/src/capabilities/types.ts +52 -0
  29. package/src/cli/commands/overview.ts +2 -2
  30. package/src/cli/desktop-pane-shot.ts +2 -0
  31. package/src/cli/entry.ts +11 -1
  32. package/src/cli/native-smoke.ts +5 -0
  33. package/src/cli/pane-functions/catalog.ts +4 -0
  34. package/src/cli/pane-functions/data.ts +6 -1
  35. package/src/cli/pane-functions/discovery.ts +76 -10
  36. package/src/cli/pane-functions/report.test.ts +76 -0
  37. package/src/cli/pane-functions/report.ts +7 -1
  38. package/src/cli/pane-functions/resolver.ts +4 -1
  39. package/src/cli/pane-functions/screenshot.test.ts +56 -3
  40. package/src/cli/pane-functions/screenshot.ts +68 -5
  41. package/src/components/chart/composite/composite-chart.test.tsx +54 -0
  42. package/src/components/chart/composite/composite-chart.tsx +92 -15
  43. package/src/components/chart/composite/downsample.test.ts +136 -0
  44. package/src/components/chart/composite/downsample.ts +208 -0
  45. package/src/components/chart/composite/panel-series.test.ts +66 -0
  46. package/src/components/chart/composite/panel-series.ts +101 -0
  47. package/src/components/chart/multi-line/renderer.ts +1 -0
  48. package/src/components/chart/native/kitty/index.test.ts +25 -3
  49. package/src/components/chart/native/kitty/support.ts +37 -3
  50. package/src/components/chart/native/use-chart-text-fallback.ts +19 -0
  51. package/src/components/chart/static/chart/surface.tsx +43 -12
  52. package/src/components/chart/static/multi-line-chart-surface.tsx +41 -5
  53. package/src/components/command-bar/commands/direct/index.ts +18 -0
  54. package/src/components/command-bar/commands/registry.ts +14 -0
  55. package/src/components/command-bar/routes/root/article-results.test.ts +83 -0
  56. package/src/components/command-bar/routes/root/article-results.ts +55 -0
  57. package/src/components/command-bar/routes/root/command-items.ts +3 -0
  58. package/src/components/command-bar/routes/root/provider-search.ts +7 -3
  59. package/src/components/command-bar/routes/root/results.ts +38 -11
  60. package/src/components/command-bar/routes/root/runtime.ts +6 -2
  61. package/src/components/command-bar/routes/ticker-search/actions.ts +1 -1
  62. package/src/components/command-bar/routes/ticker-search/results.test.ts +34 -0
  63. package/src/components/command-bar/routes/ticker-search/results.ts +4 -6
  64. package/src/components/command-bar/routes/ticker-search/route.ts +2 -2
  65. package/src/components/command-bar/routing/list-state.ts +2 -1
  66. package/src/components/command-bar/surface/assist.test.tsx +13 -6
  67. package/src/components/command-bar/surface/index.test.tsx +65 -0
  68. package/src/components/command-bar/surface/index.tsx +35 -0
  69. package/src/components/command-bar/view-model.test.ts +43 -0
  70. package/src/components/command-bar/view-model.ts +41 -4
  71. package/src/components/command-bar/workflow/collection-actions.test.ts +94 -0
  72. package/src/components/command-bar/workflow/collection-actions.ts +14 -3
  73. package/src/components/command-bar/workflow/ops.test.ts +136 -0
  74. package/src/components/command-bar/workflow/ops.ts +71 -1
  75. package/src/components/index.ts +1 -1
  76. package/src/components/layout/header.test.tsx +35 -0
  77. package/src/components/layout/header.tsx +101 -9
  78. package/src/components/layout/pane/cycle-order.test.ts +0 -1
  79. package/src/components/layout/pane/footer/index.test.tsx +19 -19
  80. package/src/components/layout/pane/header.tsx +2 -13
  81. package/src/components/layout/shell/index.tsx +3 -0
  82. package/src/components/layout/status-bar.test.tsx +36 -1
  83. package/src/components/layout/status-bar.tsx +48 -2
  84. package/src/components/onboarding/account-step/chooser-panel.tsx +27 -50
  85. package/src/components/onboarding/account-step/form-panel.tsx +85 -29
  86. package/src/components/onboarding/account-step/index.tsx +10 -1
  87. package/src/components/onboarding/account-step/model.ts +3 -3
  88. package/src/components/onboarding/account-step/qr-panel.tsx +68 -0
  89. package/src/components/onboarding/account-step/signed-in-panel.tsx +4 -7
  90. package/src/components/onboarding/onboarding-frame.tsx +517 -0
  91. package/src/components/onboarding/onboarding-steps.tsx +0 -264
  92. package/src/components/onboarding/onboarding-wizard.test.tsx +626 -200
  93. package/src/components/onboarding/onboarding-wizard.tsx +848 -266
  94. package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +81 -51
  95. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +14 -20
  96. package/src/components/onboarding/portfolio-step/broker-sync-panel.tsx +8 -11
  97. package/src/components/onboarding/portfolio-step/choose-panel.tsx +18 -22
  98. package/src/components/onboarding/portfolio-step/index.tsx +3 -0
  99. package/src/components/onboarding/portfolio-step/types.ts +3 -0
  100. package/src/components/onboarding/wizard-account.ts +38 -3
  101. package/src/components/onboarding/wizard-broker-sync.ts +45 -67
  102. package/src/components/onboarding/wizard-model.ts +18 -2
  103. package/src/components/ui/data-table/opentui/index.tsx +29 -9
  104. package/src/components/ui/data-table/types.ts +2 -0
  105. package/src/components/ui/external-link.tsx +4 -2
  106. package/src/components/ui/loading.tsx +1 -1
  107. package/src/components/ui/status.tsx +76 -4
  108. package/src/components/ui/table-layout.test.ts +24 -0
  109. package/src/components/ui/table-layout.ts +90 -23
  110. package/src/components/ui/tabs.tsx +86 -4
  111. package/src/core/app-services.ts +14 -3
  112. package/src/core/connection-health.test.ts +143 -0
  113. package/src/core/connection-health.ts +295 -0
  114. package/src/core/state/app/layout-reducer.ts +34 -0
  115. package/src/core/state/app/layout.ts +21 -0
  116. package/src/core/state/app/state.ts +10 -0
  117. package/src/core/state/app/types.ts +7 -1
  118. package/src/data/config/chart-settings.ts +11 -2
  119. package/src/data/config/store/index.test.ts +83 -9
  120. package/src/data/config/store/migrations.ts +16 -0
  121. package/src/data/config/store/normalize.ts +59 -1
  122. package/src/data/fred-series.ts +8 -2
  123. package/src/i18n/es.ts +126 -0
  124. package/src/i18n/ja.ts +126 -0
  125. package/src/i18n/ko.ts +126 -0
  126. package/src/i18n/zh-cn.ts +126 -0
  127. package/src/i18n/zh-tw.ts +126 -0
  128. package/src/market-data/coordinator/chart.ts +3 -2
  129. package/src/market-data/hooks.tsx +3 -2
  130. package/src/market-data/market/status.test.ts +12 -0
  131. package/src/market-data/market/status.ts +34 -17
  132. package/src/news/aggregator.test.ts +39 -0
  133. package/src/news/aggregator.ts +78 -22
  134. package/src/news/hooks.ts +4 -0
  135. package/src/notifications/app-notifier.test.ts +28 -6
  136. package/src/notifications/app-notifier.ts +24 -5
  137. package/src/plugins/broker-sync/broker-sync.test.ts +130 -0
  138. package/src/plugins/broker-sync/native-loader.ts +10 -0
  139. package/src/plugins/broker-sync/normalize.ts +271 -0
  140. package/src/plugins/broker-sync/public.ts +122 -0
  141. package/src/plugins/broker-sync/robinhood-native.ts +367 -0
  142. package/src/plugins/broker-sync/robinhood.ts +114 -0
  143. package/src/plugins/broker-sync/simplefin-native.ts +201 -0
  144. package/src/plugins/broker-sync/simplefin.ts +70 -0
  145. package/src/plugins/builtin/account-management/footer.ts +8 -19
  146. package/src/plugins/builtin/account-management/pane.tsx +13 -8
  147. package/src/plugins/builtin/ai/pi/host.ts +6 -5
  148. package/src/plugins/builtin/ai/providers.ts +1 -1
  149. package/src/plugins/builtin/ai/screener/footer.ts +0 -7
  150. package/src/plugins/builtin/ai/screener/pane.test.tsx +2 -1
  151. package/src/plugins/builtin/ai/screener/pane.tsx +3 -1
  152. package/src/plugins/builtin/ai/screener/results-view.tsx +8 -0
  153. package/src/plugins/builtin/ai/screener/runner.ts +7 -3
  154. package/src/plugins/builtin/ai/workspace/pane.tsx +13 -2
  155. package/src/plugins/builtin/alerts/alert-engine.ts +13 -0
  156. package/src/plugins/builtin/alerts/constants.ts +2 -1
  157. package/src/plugins/builtin/alerts/index.tsx +67 -36
  158. package/src/plugins/builtin/alerts/pane.tsx +17 -65
  159. package/src/plugins/builtin/analytics/index.tsx +24 -11
  160. package/src/plugins/builtin/analytics/pane-model.ts +48 -14
  161. package/src/plugins/builtin/analytics/portfolio-selection.ts +19 -0
  162. package/src/plugins/builtin/analytics/view.tsx +5 -3
  163. package/src/plugins/builtin/broker-manager/detail.tsx +7 -2
  164. package/src/plugins/builtin/broker-manager/footer.ts +4 -9
  165. package/src/plugins/builtin/broker-manager/index.tsx +1 -6
  166. package/src/plugins/builtin/buildout/detail/ui.tsx +15 -4
  167. package/src/plugins/builtin/buildout/pane/header.tsx +1 -0
  168. package/src/plugins/builtin/changelog/index.tsx +60 -45
  169. package/src/plugins/builtin/chart-composer/catalog-inventory.test.ts +97 -0
  170. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +366 -0
  171. package/src/plugins/builtin/chart-composer/chart-spec.ts +21 -1
  172. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +307 -0
  173. package/src/plugins/builtin/chart-composer/editor/controller.ts +1 -1
  174. package/src/plugins/builtin/chart-composer/index.tsx +45 -5
  175. package/src/plugins/builtin/chart-composer/pane.tsx +7 -88
  176. package/src/plugins/builtin/chart-composer/presets.test.ts +48 -0
  177. package/src/plugins/builtin/chart-composer/presets.ts +69 -3
  178. package/src/plugins/builtin/chart-composer/quick-add.tsx +8 -5
  179. package/src/plugins/builtin/chart-composer/semantic.ts +18 -14
  180. package/src/plugins/builtin/chart-composer/series-catalog.test.ts +39 -0
  181. package/src/plugins/builtin/chart-composer/series-catalog.ts +104 -3
  182. package/src/plugins/builtin/chart-composer/shortcuts.test.ts +10 -10
  183. package/src/plugins/builtin/chart-composer/shortcuts.ts +4 -12
  184. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +230 -23
  185. package/src/plugins/builtin/chat/chat.test.tsx +22 -0
  186. package/src/plugins/builtin/chat/content/composer.tsx +6 -4
  187. package/src/plugins/builtin/chat/content/index.tsx +7 -0
  188. package/src/plugins/builtin/chat/content/snapshot.ts +9 -2
  189. package/src/plugins/builtin/chat/content/transcript.tsx +10 -0
  190. package/src/plugins/builtin/chat/controller/channel-actions.ts +11 -1
  191. package/src/plugins/builtin/chat/controller/channels.ts +13 -3
  192. package/src/plugins/builtin/chat/controller/index.ts +61 -7
  193. package/src/plugins/builtin/chat/controller/lifecycle.ts +3 -0
  194. package/src/plugins/builtin/chat/controller/message-loading.ts +5 -1
  195. package/src/plugins/builtin/chat/controller/messages.ts +3 -1
  196. package/src/plugins/builtin/chat/controller/notifications.ts +32 -11
  197. package/src/plugins/builtin/chat/controller/state.ts +7 -0
  198. package/src/plugins/builtin/chat/controller/view.ts +1 -0
  199. package/src/plugins/builtin/chat/controller.test.ts +117 -28
  200. package/src/plugins/builtin/cloud/auth-commands.ts +18 -0
  201. package/src/plugins/builtin/cloud/device-signin-dialog.tsx +243 -0
  202. package/src/plugins/builtin/cloud/device-signin.test.ts +217 -0
  203. package/src/plugins/builtin/cloud/device-signin.ts +240 -0
  204. package/src/plugins/builtin/cloud/plugin.tsx +7 -1
  205. package/src/plugins/builtin/cloud-tweets/table.tsx +57 -10
  206. package/src/plugins/builtin/composite-plugins.ts +39 -5
  207. package/src/plugins/builtin/congress-trades/detail.tsx +2 -4
  208. package/src/plugins/builtin/congress-trades/footer.ts +7 -16
  209. package/src/plugins/builtin/congress-trades/pane.tsx +12 -20
  210. package/src/plugins/builtin/connections/index.ts +53 -0
  211. package/src/plugins/builtin/connections/pane.test.tsx +79 -0
  212. package/src/plugins/builtin/connections/pane.tsx +258 -0
  213. package/src/plugins/builtin/correlation/index.test.tsx +2 -1
  214. package/src/plugins/builtin/correlation/index.tsx +58 -19
  215. package/src/plugins/builtin/correlation/matrix/model.ts +18 -7
  216. package/src/plugins/builtin/correlation/relationship/pane.tsx +43 -19
  217. package/src/plugins/builtin/correlation/settings.ts +1 -1
  218. package/src/plugins/builtin/credit-conditions/client.test.ts +80 -0
  219. package/src/plugins/builtin/credit-conditions/client.ts +105 -0
  220. package/src/plugins/builtin/credit-conditions/index.test.tsx +111 -0
  221. package/src/plugins/builtin/credit-conditions/index.tsx +227 -0
  222. package/src/plugins/builtin/credit-conditions/model.test.ts +43 -0
  223. package/src/plugins/builtin/credit-conditions/model.ts +64 -0
  224. package/src/plugins/builtin/debug/index.tsx +11 -6
  225. package/src/plugins/builtin/dividend-yield/client.test.ts +58 -0
  226. package/src/plugins/builtin/dividend-yield/client.ts +252 -0
  227. package/src/plugins/builtin/dividend-yield/index.tsx +61 -0
  228. package/src/plugins/builtin/dividend-yield/model.ts +83 -0
  229. package/src/plugins/builtin/dividend-yield/pane.tsx +305 -0
  230. package/src/plugins/builtin/dividend-yield/types.ts +22 -0
  231. package/src/plugins/builtin/earnings/data/cache.test.ts +10 -7
  232. package/src/plugins/builtin/earnings/data/cache.ts +23 -9
  233. package/src/plugins/builtin/earnings/index.tsx +57 -6
  234. package/src/plugins/builtin/econ/calendar-model.test.ts +37 -3
  235. package/src/plugins/builtin/econ/calendar-model.ts +32 -26
  236. package/src/plugins/builtin/econ/fred-cache.test.ts +8 -1
  237. package/src/plugins/builtin/econ/fred-series-map.ts +18 -0
  238. package/src/plugins/builtin/econ/index.test.tsx +105 -0
  239. package/src/plugins/builtin/econ/index.tsx +95 -52
  240. package/src/plugins/builtin/fear-greed/cache.test.ts +18 -3
  241. package/src/plugins/builtin/fear-greed/cache.ts +18 -6
  242. package/src/plugins/builtin/fear-greed/charts.tsx +9 -2
  243. package/src/plugins/builtin/fear-greed/format.ts +0 -9
  244. package/src/plugins/builtin/fear-greed/pane.tsx +21 -21
  245. package/src/plugins/builtin/futures/contracts.ts +100 -0
  246. package/src/plugins/builtin/futures/index.tsx +298 -0
  247. package/src/plugins/builtin/futures/model.test.ts +129 -0
  248. package/src/plugins/builtin/futures/model.ts +136 -0
  249. package/src/plugins/builtin/futures/pane.test.tsx +239 -0
  250. package/src/plugins/builtin/futures/table.test.ts +155 -0
  251. package/src/plugins/builtin/futures/table.tsx +241 -0
  252. package/src/plugins/builtin/fx-matrix/index.tsx +160 -134
  253. package/src/plugins/builtin/fx-matrix/pairs.ts +12 -1
  254. package/src/plugins/builtin/fx-matrix/pane.test.tsx +109 -0
  255. package/src/plugins/builtin/help/components.tsx +1 -30
  256. package/src/plugins/builtin/help/index.tsx +5 -25
  257. package/src/plugins/builtin/holders/pane.tsx +17 -12
  258. package/src/plugins/builtin/holders/treemap.tsx +4 -2
  259. package/src/plugins/builtin/insider/index.tsx +20 -10
  260. package/src/plugins/builtin/ipo-calendar/cache.ts +101 -0
  261. package/src/plugins/builtin/ipo-calendar/client.test.ts +31 -0
  262. package/src/plugins/builtin/ipo-calendar/client.ts +296 -0
  263. package/src/plugins/builtin/ipo-calendar/index.tsx +65 -0
  264. package/src/plugins/builtin/ipo-calendar/model.ts +145 -0
  265. package/src/plugins/builtin/ipo-calendar/pane.tsx +328 -0
  266. package/src/plugins/builtin/ipo-calendar/types.ts +19 -0
  267. package/src/plugins/builtin/kelly-sizer/index.test.tsx +3 -3
  268. package/src/plugins/builtin/kelly-sizer/model.ts +8 -1
  269. package/src/plugins/builtin/kelly-sizer/pane.tsx +33 -15
  270. package/src/plugins/builtin/kelly-sizer/view.tsx +12 -5
  271. package/src/plugins/builtin/market-halts/client.ts +114 -0
  272. package/src/plugins/builtin/market-halts/index.tsx +29 -0
  273. package/src/plugins/builtin/market-halts/model.test.ts +187 -0
  274. package/src/plugins/builtin/market-halts/model.ts +291 -0
  275. package/src/plugins/builtin/market-halts/pane.tsx +256 -0
  276. package/src/plugins/builtin/market-heatmap/data.test.ts +1 -0
  277. package/src/plugins/builtin/market-heatmap/data.ts +12 -3
  278. package/src/plugins/builtin/market-heatmap/index.tsx +131 -46
  279. package/src/plugins/builtin/market-movers/index.tsx +145 -63
  280. package/src/plugins/builtin/market-movers/model.ts +19 -3
  281. package/src/plugins/builtin/market-movers/screener.test.ts +108 -0
  282. package/src/plugins/builtin/market-movers/screener.ts +168 -10
  283. package/src/plugins/builtin/news/index.tsx +78 -99
  284. package/src/plugins/builtin/news/wire/article-search.test.ts +72 -0
  285. package/src/plugins/builtin/news/wire/article-search.ts +140 -0
  286. package/src/plugins/builtin/news/wire/breaking/pane.tsx +18 -8
  287. package/src/plugins/builtin/news/wire/categories.ts +35 -0
  288. package/src/plugins/builtin/news/wire/default-feeds.ts +47 -1
  289. package/src/plugins/builtin/news/wire/feed-config.test.ts +10 -5
  290. package/src/plugins/builtin/news/wire/index.tsx +41 -4
  291. package/src/plugins/builtin/news/wire/industry-pane.tsx +38 -19
  292. package/src/plugins/builtin/news/wire/news/detail-view.tsx +10 -5
  293. package/src/plugins/builtin/news/wire/news/footer.ts +1 -0
  294. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +15 -6
  295. package/src/plugins/builtin/news/wire/news/query-presets.ts +16 -2
  296. package/src/plugins/builtin/news/wire/news/table.test.tsx +54 -0
  297. package/src/plugins/builtin/news/wire/news/table.tsx +111 -17
  298. package/src/plugins/builtin/notes/files.test.ts +18 -0
  299. package/src/plugins/builtin/notes/files.ts +10 -4
  300. package/src/plugins/builtin/notes/index.tsx +4 -1
  301. package/src/plugins/builtin/notes/quick-notes-pane.test.tsx +182 -0
  302. package/src/plugins/builtin/notes/quick-notes-pane.tsx +74 -31
  303. package/src/plugins/builtin/notes/ticker-notes-tab.test.tsx +380 -0
  304. package/src/plugins/builtin/notes/ticker-notes-tab.tsx +97 -40
  305. package/src/plugins/builtin/options/calc-seed.test.ts +118 -0
  306. package/src/plugins/builtin/options/calc-seed.ts +52 -0
  307. package/src/plugins/builtin/options/footer.ts +4 -1
  308. package/src/plugins/builtin/options/index.tsx +22 -1
  309. package/src/plugins/builtin/options/live-quotes.ts +8 -0
  310. package/src/plugins/builtin/options/table.ts +3 -1
  311. package/src/plugins/builtin/options/view.test.tsx +5 -1
  312. package/src/plugins/builtin/options/view.tsx +112 -17
  313. package/src/plugins/builtin/options-calculator/index.tsx +35 -0
  314. package/src/plugins/builtin/options-calculator/model.test.ts +172 -0
  315. package/src/plugins/builtin/options-calculator/model.ts +273 -0
  316. package/src/plugins/builtin/options-calculator/pane.test.tsx +138 -0
  317. package/src/plugins/builtin/options-calculator/pane.tsx +218 -0
  318. package/src/plugins/builtin/plugin-module.test.ts +16 -2
  319. package/src/plugins/builtin/plugin-module.ts +5 -1
  320. package/src/plugins/builtin/polls/client.ts +57 -0
  321. package/src/plugins/builtin/polls/hooks.ts +20 -0
  322. package/src/plugins/builtin/polls/index.tsx +35 -0
  323. package/src/plugins/builtin/polls/normalize.test.ts +230 -0
  324. package/src/plugins/builtin/polls/normalize.ts +319 -0
  325. package/src/plugins/builtin/polls/pane.tsx +722 -0
  326. package/src/plugins/builtin/polls/types.ts +77 -0
  327. package/src/plugins/builtin/portfolio-list/header.tsx +15 -4
  328. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -3
  329. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +1 -1
  330. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -0
  331. package/src/plugins/builtin/portfolio-list/summary/index.tsx +2 -1
  332. package/src/plugins/builtin/research/analyst-pane.tsx +40 -67
  333. package/src/plugins/builtin/research/corporate-actions-pane.tsx +25 -19
  334. package/src/plugins/builtin/research/index.test.ts +6 -25
  335. package/src/plugins/builtin/research/index.tsx +11 -5
  336. package/src/plugins/builtin/research/relative-valuation-pane.tsx +86 -26
  337. package/src/plugins/builtin/scanner/denied.tsx +31 -0
  338. package/src/plugins/builtin/scanner/feed.ts +94 -0
  339. package/src/plugins/builtin/scanner/flow-model.test.ts +85 -0
  340. package/src/plugins/builtin/scanner/flow-model.ts +133 -0
  341. package/src/plugins/builtin/scanner/flow-pane.tsx +197 -0
  342. package/src/plugins/builtin/scanner/hilo-bars.tsx +121 -0
  343. package/src/plugins/builtin/scanner/hilo-model.test.ts +44 -0
  344. package/src/plugins/builtin/scanner/hilo-model.ts +75 -0
  345. package/src/plugins/builtin/scanner/hilo-pane.tsx +165 -0
  346. package/src/plugins/builtin/scanner/index.ts +109 -0
  347. package/src/plugins/builtin/scanner/waiting.tsx +11 -0
  348. package/src/plugins/builtin/sec/index.tsx +11 -7
  349. package/src/plugins/builtin/sectors/index.tsx +105 -81
  350. package/src/plugins/builtin/sectors/move-bar.tsx +43 -0
  351. package/src/plugins/builtin/sectors/sector-data.ts +18 -0
  352. package/src/plugins/builtin/sectors/sector-model.ts +13 -15
  353. package/src/plugins/builtin/shared/auto-refresh.ts +51 -0
  354. package/src/plugins/builtin/shared/cloud-upgrade.ts +29 -5
  355. package/src/plugins/builtin/shared/pane-footer.ts +16 -3
  356. package/src/plugins/builtin/shared/screener-live-quotes.test.ts +91 -0
  357. package/src/plugins/builtin/shared/screener-live-quotes.ts +134 -0
  358. package/src/plugins/builtin/shared/table-pane.ts +1 -5
  359. package/src/plugins/builtin/shared/use-quote-board.test.tsx +211 -0
  360. package/src/plugins/builtin/shared/use-quote-board.ts +195 -0
  361. package/src/plugins/builtin/short-interest/client.ts +124 -0
  362. package/src/plugins/builtin/short-interest/index.tsx +62 -0
  363. package/src/plugins/builtin/short-interest/model.ts +113 -0
  364. package/src/plugins/builtin/short-interest/pane.tsx +252 -0
  365. package/src/plugins/builtin/short-interest/types.ts +9 -0
  366. package/src/plugins/builtin/substack/article-detail.tsx +15 -1
  367. package/src/plugins/builtin/substack/pane-footer.ts +3 -22
  368. package/src/plugins/builtin/substack/pane.tsx +1 -2
  369. package/src/plugins/builtin/thirteenf/api.ts +4 -1
  370. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  371. package/src/plugins/builtin/thirteenf/pane.tsx +6 -2
  372. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +10 -4
  373. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +67 -11
  374. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +15 -8
  375. package/src/plugins/builtin/ticker-detail/index.tsx +5 -21
  376. package/src/plugins/builtin/ticker-detail/overview/components.tsx +72 -32
  377. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +0 -10
  378. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -1
  379. package/src/plugins/builtin/ticker-detail/quote-monitor/card.test.tsx +70 -0
  380. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +34 -26
  381. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +96 -49
  382. package/src/plugins/builtin/ticker-detail/research-tabs.ts +32 -0
  383. package/src/plugins/builtin/ticker-research-backend-plugin.ts +2 -9
  384. package/src/plugins/builtin/ticker-research-plugin.ts +6 -0
  385. package/src/plugins/builtin/treasury-auctions/cache.test.ts +173 -0
  386. package/src/plugins/builtin/treasury-auctions/cache.ts +104 -0
  387. package/src/plugins/builtin/treasury-auctions/client.test.ts +181 -0
  388. package/src/plugins/builtin/treasury-auctions/client.ts +151 -0
  389. package/src/plugins/builtin/treasury-auctions/index.tsx +90 -0
  390. package/src/plugins/builtin/treasury-auctions/model.test.ts +147 -0
  391. package/src/plugins/builtin/treasury-auctions/model.ts +188 -0
  392. package/src/plugins/builtin/treasury-auctions/pane.test.tsx +170 -0
  393. package/src/plugins/builtin/treasury-auctions/pane.tsx +417 -0
  394. package/src/plugins/builtin/treasury-auctions/types.ts +59 -0
  395. package/src/plugins/builtin/tv/pane.tsx +67 -7
  396. package/src/plugins/builtin/tv/youtube-embed.test.ts +84 -0
  397. package/src/plugins/builtin/tv/youtube-embed.ts +173 -0
  398. package/src/plugins/builtin/tv/youtube-stream.ts +27 -2
  399. package/src/plugins/builtin/volatility/client.ts +87 -0
  400. package/src/plugins/builtin/volatility/index.test.tsx +107 -0
  401. package/src/plugins/builtin/volatility/index.tsx +193 -0
  402. package/src/plugins/builtin/volatility/model.test.ts +66 -0
  403. package/src/plugins/builtin/volatility/model.ts +113 -0
  404. package/src/plugins/builtin/world-indices/footer.ts +43 -23
  405. package/src/plugins/builtin/world-indices/index.tsx +44 -93
  406. package/src/plugins/builtin/world-indices/indices.ts +11 -2
  407. package/src/plugins/builtin/world-indices/model.ts +17 -24
  408. package/src/plugins/builtin/world-indices/pane.test.tsx +116 -0
  409. package/src/plugins/builtin/world-indices/table.tsx +82 -37
  410. package/src/plugins/builtin/yield-curve/index.tsx +16 -7
  411. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +25 -0
  412. package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -0
  413. package/src/plugins/catalog-backend.ts +8 -0
  414. package/src/plugins/catalog-ui.ts +8 -0
  415. package/src/plugins/event-bus.ts +1 -0
  416. package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
  417. package/src/plugins/ibkr/trade/tab/footer.ts +9 -23
  418. package/src/plugins/ibkr/trade/tab/index.tsx +1 -5
  419. package/src/plugins/ibkr/trading/pane-view.tsx +149 -87
  420. package/src/plugins/ibkr/trading/pane.tsx +2 -2
  421. package/src/plugins/ownership.ts +3 -0
  422. package/src/plugins/pane-manager/floating.ts +8 -2
  423. package/src/plugins/pane-manager.test.ts +13 -0
  424. package/src/plugins/prediction-markets/backend-plugin.ts +2 -0
  425. package/src/plugins/prediction-markets/capability.test.ts +281 -0
  426. package/src/plugins/prediction-markets/capability.ts +171 -0
  427. package/src/plugins/prediction-markets/chart.tsx +18 -26
  428. package/src/plugins/prediction-markets/columns.ts +3 -2
  429. package/src/plugins/prediction-markets/controller/catalog.ts +15 -12
  430. package/src/plugins/prediction-markets/controller/detail.ts +4 -0
  431. package/src/plugins/prediction-markets/controller/index.ts +0 -15
  432. package/src/plugins/prediction-markets/detail/book.tsx +3 -23
  433. package/src/plugins/prediction-markets/detail/overview.tsx +0 -18
  434. package/src/plugins/prediction-markets/detail/pane.tsx +36 -12
  435. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  436. package/src/plugins/prediction-markets/index.tsx +2 -0
  437. package/src/plugins/prediction-markets/metrics.ts +28 -18
  438. package/src/plugins/prediction-markets/navigation.ts +1 -0
  439. package/src/plugins/prediction-markets/pane.test.tsx +1 -1
  440. package/src/plugins/prediction-markets/pane.tsx +0 -1
  441. package/src/plugins/prediction-markets/services/fetch.ts +3 -2
  442. package/src/plugins/prediction-markets/services/kalshi/adapter.ts +82 -23
  443. package/src/plugins/prediction-markets/services/kalshi/normalize.ts +3 -1
  444. package/src/plugins/prediction-markets/services/kalshi/types.ts +14 -0
  445. package/src/plugins/prediction-markets/services/polymarket/adapter.ts +26 -14
  446. package/src/plugins/prediction-markets/services/polymarket/detail.ts +67 -18
  447. package/src/plugins/prediction-markets/test-helpers.tsx +0 -1
  448. package/src/plugins/prediction-markets/types.ts +3 -9
  449. package/src/plugins/registry/context.ts +6 -1
  450. package/src/plugins/registry/index.test.ts +2 -0
  451. package/src/plugins/registry/index.ts +53 -3
  452. package/src/plugins/runtime/context.tsx +4 -2
  453. package/src/plugins/runtime/index.tsx +9 -0
  454. package/src/remote/controller.test.ts +17 -4
  455. package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
  456. package/src/renderers/electrobun/bun/core-capabilities.ts +2 -2
  457. package/src/renderers/electrobun/bun/desktop/capability-bridge.test.ts +64 -0
  458. package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +41 -7
  459. package/src/renderers/electrobun/bun/desktop/host-requests.ts +5 -2
  460. package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +33 -0
  461. package/src/renderers/electrobun/bun/desktop/http-fetch.ts +53 -6
  462. package/src/renderers/electrobun/bun/desktop/initialization.test.ts +27 -0
  463. package/src/renderers/electrobun/bun/desktop/initialization.ts +6 -1
  464. package/src/renderers/electrobun/bun/index.ts +1 -0
  465. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  466. package/src/renderers/electrobun/view/app-services.ts +25 -2
  467. package/src/renderers/electrobun/view/build-assets.ts +5 -0
  468. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +186 -65
  469. package/src/renderers/electrobun/view/cli-pane-shot-health.test.ts +25 -0
  470. package/src/renderers/electrobun/view/cli-pane-shot-health.ts +31 -0
  471. package/src/renderers/electrobun/view/data-table/index.tsx +10 -11
  472. package/src/renderers/electrobun/view/data-table/row.tsx +2 -0
  473. package/src/renderers/electrobun/view/desktop/controls.test.tsx +68 -0
  474. package/src/renderers/electrobun/view/desktop/controls.tsx +25 -1
  475. package/src/renderers/electrobun/view/host/box.test.tsx +80 -0
  476. package/src/renderers/electrobun/view/host/media-surface.tsx +70 -3
  477. package/src/renderers/electrobun/view/host/tabs.tsx +97 -5
  478. package/src/renderers/electrobun/view/host/text.tsx +5 -2
  479. package/src/renderers/electrobun/view/input-host.test.tsx +140 -0
  480. package/src/renderers/electrobun/view/input-host.tsx +4 -2
  481. package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +19 -1
  482. package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +7 -0
  483. package/src/renderers/electrobun/view/native-stubs/chart/kitty-support.ts +10 -0
  484. package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +46 -0
  485. package/src/renderers/electrobun/view/remote/capability-invoker.ts +34 -13
  486. package/src/renderers/electrobun/view/remote/connection-health-backend.ts +19 -0
  487. package/src/renderers/electrobun/view/remote/connection-health.test.ts +44 -0
  488. package/src/renderers/electrobun/view/remote/connection-health.ts +49 -0
  489. package/src/renderers/electrobun/view/styles.css +5 -0
  490. package/src/renderers/opentui/dialog-host.tsx +279 -80
  491. package/src/renderers/opentui/host.tsx +15 -1
  492. package/src/renderers/opentui/terminal-media.test.ts +102 -0
  493. package/src/renderers/opentui/terminal-media.ts +142 -0
  494. package/src/renderers/opentui/test-utils.tsx +9 -6
  495. package/src/renderers/opentui/toast-host.tsx +136 -7
  496. package/src/renderers/opentui/ui-host.tsx +19 -3
  497. package/src/sources/provider-router/connection-health.test.ts +28 -0
  498. package/src/sources/provider-router/connection-health.ts +47 -0
  499. package/src/sources/provider-router/history.test.ts +53 -4
  500. package/src/sources/provider-router/history.ts +119 -14
  501. package/src/sources/provider-router/index.ts +12 -1
  502. package/src/sources/yahoo-finance/history.ts +2 -9
  503. package/src/sources/yahoo-finance/http.ts +1 -1
  504. package/src/sources/yahoo-finance/symbols.ts +1 -1
  505. package/src/sources/yahoo-finance/types.ts +19 -0
  506. package/src/state/app/context/index.test.tsx +101 -0
  507. package/src/state/app/context/index.tsx +10 -0
  508. package/src/state/config-save-scheduler.ts +1 -6
  509. package/src/state/persist-scheduler.test.ts +31 -0
  510. package/src/state/persist-scheduler.ts +21 -8
  511. package/src/sync/auth-transition.test.ts +36 -0
  512. package/src/sync/auth-transition.ts +22 -0
  513. package/src/sync/core-contributors.test.ts +58 -1
  514. package/src/sync/core-contributors.ts +39 -2
  515. package/src/sync/react.ts +9 -0
  516. package/src/test-support/plugin-runtime.ts +3 -0
  517. package/src/theme/font-scale.test.ts +14 -0
  518. package/src/theme/font-scale.ts +69 -0
  519. package/src/tickers/search/index.test.ts +284 -2
  520. package/src/tickers/search/index.ts +55 -18
  521. package/src/tickers/search/ranking.ts +143 -18
  522. package/src/tickers/search/types.ts +1 -0
  523. package/src/time-series/alignment.ts +15 -11
  524. package/src/time-series/field-catalog.ts +10 -0
  525. package/src/time-series/fundamentals.ts +19 -11
  526. package/src/time-series/hooks.ts +8 -2
  527. package/src/time-series/parsed-history-cache.ts +35 -0
  528. package/src/time-series/resolution.ts +10 -0
  529. package/src/time-series/resolve.test.ts +332 -3
  530. package/src/time-series/resolve.ts +225 -25
  531. package/src/time-series/spec-studies.test.ts +23 -0
  532. package/src/time-series/spec.ts +21 -2
  533. package/src/time-series/transforms.test.ts +76 -1
  534. package/src/time-series/transforms.ts +39 -8
  535. package/src/time-series/types.ts +9 -2
  536. package/src/time-series/use-chart-resolution.test.tsx +105 -1
  537. package/src/time-series/use-chart-resolution.ts +67 -13
  538. package/src/types/config.ts +30 -27
  539. package/src/types/media.ts +2 -0
  540. package/src/types/plugin.ts +8 -1
  541. package/src/ui/host.tsx +4 -0
  542. package/src/ui/pointer-drag.ts +12 -0
  543. package/src/ui/qr.test.ts +79 -0
  544. package/src/ui/qr.ts +82 -0
  545. package/src/updater/github-releases.test.ts +3 -3
  546. package/src/updater/github-releases.ts +1 -1
  547. package/src/updater.test.ts +117 -16
  548. package/src/updater.ts +32 -5
  549. package/src/utils/exchanges.ts +1 -0
  550. package/src/utils/external-url.test.ts +32 -0
  551. package/src/utils/external-url.ts +23 -0
  552. package/src/utils/sort-values.ts +31 -0
  553. package/src/utils/zoned-date-time.ts +67 -0
  554. package/src/components/onboarding/wizard-keyboard.ts +0 -306
  555. package/src/plugins/builtin/chart-composer/date-window-dialog.tsx +0 -120
@@ -1,6 +1,7 @@
1
1
  import type { SearchRequestContext, DataProvider } from "../../types/data-provider";
2
2
  import type { InstrumentSearchResult } from "../../types/instrument";
3
3
  import type { TickerRecord } from "../../types/ticker";
4
+ import { canonicalExchange } from "../../utils/exchanges";
4
5
  import { parseOptionSymbol } from "../../utils/options";
5
6
  import {
6
7
  buildSymbolAliases,
@@ -36,6 +37,7 @@ const SHARE_CLASS_SUFFIXES = new Set(["A", "B", "C", "D", "K"]);
36
37
 
37
38
  interface TickerSearchCandidateOptions {
38
39
  includeOptionContracts?: boolean;
40
+ providerRanks?: ReadonlyMap<string, number>;
39
41
  }
40
42
 
41
43
  export function normalizeTickerInput(activeTicker: string | null, arg?: string): string | null {
@@ -53,14 +55,21 @@ export function createLocalTickerSearchCandidates(
53
55
  if (options.includeOptionContracts === false && isOptionTickerRecord(ticker)) return [];
54
56
  const symbol = normalizeTickerSymbol(ticker.metadata.ticker);
55
57
  const hint = providerHints.get(symbol);
58
+ const exchangeLabel = ticker.metadata.exchange || hint?.exchange || hint?.primaryExchange;
59
+ const savedExchange = canonicalExchange(ticker.metadata.exchange);
60
+ const hintPrimaryExchange = canonicalExchange(hint?.primaryExchange);
61
+ const primaryExchangeLabel = !savedExchange || !hintPrimaryExchange || savedExchange === hintPrimaryExchange
62
+ ? hint?.primaryExchange
63
+ : undefined;
56
64
  return [{
57
65
  id: `goto:${ticker.metadata.ticker}`,
58
66
  label: ticker.metadata.ticker,
59
67
  symbol,
60
68
  detail: resolveLocalSearchName(ticker, hint),
61
- right: hint?.exchange || ticker.metadata.exchange,
62
- exchangeLabel: hint?.exchange || ticker.metadata.exchange,
63
- primaryExchangeLabel: hint?.primaryExchange,
69
+ right: exchangeLabel,
70
+ exchangeLabel,
71
+ primaryExchangeLabel,
72
+ providerRank: options.providerRanks?.get(symbol),
64
73
  category: "Saved",
65
74
  kind: "ticker",
66
75
  saved: true,
@@ -77,7 +86,7 @@ function createProviderTickerSearchCandidates(
77
86
  localTickers: ReadonlyMap<string, TickerRecord>,
78
87
  options: TickerSearchCandidateOptions = {},
79
88
  ): TickerSearchCandidate[] {
80
- return searchResults.flatMap((result) => {
89
+ return searchResults.flatMap((result, providerRank) => {
81
90
  if (options.includeOptionContracts === false && isOptionSearchResult(result)) return [];
82
91
  const symbol = getSearchResultSymbol(result);
83
92
  const saved = localTickers.has(symbol);
@@ -89,6 +98,7 @@ function createProviderTickerSearchCandidates(
89
98
  right: result.exchange || result.primaryExchange || result.type || undefined,
90
99
  exchangeLabel: result.exchange,
91
100
  primaryExchangeLabel: result.primaryExchange,
101
+ providerRank,
92
102
  category: saved ? "Saved" : "Other Listings",
93
103
  kind: "search",
94
104
  saved,
@@ -141,13 +151,16 @@ export function buildTickerSearchCandidates({
141
151
  totalLimit?: number;
142
152
  includeOptionContracts?: boolean;
143
153
  }): TickerSearchCandidate[] {
144
- const candidateOptions = { includeOptionContracts };
145
154
  const filteredProviderResults = includeOptionContracts
146
155
  ? providerResults
147
156
  : providerResults.filter((result) => !isOptionSearchResult(result));
148
- const providerHints = buildProviderHintMap(filteredProviderResults);
157
+ const providerHints = buildProviderHints(filteredProviderResults, tickers);
158
+ const candidateOptions = {
159
+ includeOptionContracts,
160
+ providerRanks: providerHints.ranks,
161
+ };
149
162
  const localItems = rankTickerSearchItems(
150
- createLocalTickerSearchCandidates(tickers.values(), providerHints, candidateOptions),
163
+ createLocalTickerSearchCandidates(tickers.values(), providerHints.results, candidateOptions),
151
164
  query,
152
165
  );
153
166
  const providerItems = createProviderTickerSearchCandidates(filteredProviderResults, tickers, candidateOptions);
@@ -192,16 +205,28 @@ export async function resolveTickerSearch({
192
205
  };
193
206
  }
194
207
 
195
- function buildProviderHintMap(searchResults: InstrumentSearchResult[]): Map<string, InstrumentSearchResult> {
196
- const hints = new Map<string, InstrumentSearchResult>();
197
- for (const result of searchResults) {
208
+ function buildProviderHints(
209
+ searchResults: InstrumentSearchResult[],
210
+ localTickers: ReadonlyMap<string, TickerRecord>,
211
+ ): {
212
+ results: Map<string, InstrumentSearchResult>;
213
+ ranks: Map<string, number>;
214
+ } {
215
+ const results = new Map<string, InstrumentSearchResult>();
216
+ const ranks = new Map<string, number>();
217
+ for (const [rank, result] of searchResults.entries()) {
198
218
  const symbol = getSearchResultSymbol(result);
199
- const existing = hints.get(symbol);
200
- if (!existing || getProviderHintRichness(result) > getProviderHintRichness(existing)) {
201
- hints.set(symbol, result);
219
+ if (!ranks.has(symbol)) ranks.set(symbol, rank);
220
+ const existing = results.get(symbol);
221
+ const preferredExchange = localTickers.get(symbol)?.metadata.exchange;
222
+ if (
223
+ !existing
224
+ || getProviderHintScore(result, preferredExchange) > getProviderHintScore(existing, preferredExchange)
225
+ ) {
226
+ results.set(symbol, result);
202
227
  }
203
228
  }
204
- return hints;
229
+ return { results, ranks };
205
230
  }
206
231
 
207
232
  async function searchProviderResults(
@@ -284,14 +309,26 @@ function getProviderHintRichness(result: InstrumentSearchResult): number {
284
309
  return score;
285
310
  }
286
311
 
312
+ function getProviderHintScore(result: InstrumentSearchResult, preferredExchange?: string): number {
313
+ const canonicalPreferredExchange = canonicalExchange(preferredExchange);
314
+ const matchesPreferredExchange = canonicalPreferredExchange
315
+ && [
316
+ result.exchange,
317
+ result.primaryExchange,
318
+ result.brokerContract?.exchange,
319
+ result.brokerContract?.primaryExchange,
320
+ ].some((exchange) => canonicalExchange(exchange) === canonicalPreferredExchange);
321
+ return getProviderHintRichness(result) + (matchesPreferredExchange ? 10_000 : 0);
322
+ }
323
+
287
324
  function assignTickerSearchCategories<T extends TickerSearchCandidate>(items: T[]): T[] {
288
- const hasSavedListing = items.some((item) =>
289
- item.saved && item.instrumentClass !== "fund" && item.instrumentClass !== "derivative"
290
- );
291
- let assignedPrimaryListing = hasSavedListing;
325
+ let assignedPrimaryListing = false;
292
326
 
293
327
  return items.map((item) => {
294
328
  if (item.saved || item.kind === "ticker") {
329
+ if (item.instrumentClass !== "fund" && item.instrumentClass !== "derivative") {
330
+ assignedPrimaryListing = true;
331
+ }
295
332
  return { ...item, category: "Saved" };
296
333
  }
297
334
  if (item.instrumentClass === "fund" || item.instrumentClass === "derivative") {
@@ -6,6 +6,23 @@ import type {
6
6
  const FUND_TYPES = new Set(["ETF", "ETN", "ETP", "FUND", "MUTUALFUND", "CEF", "CLOSEDEND"]);
7
7
  const DERIVATIVE_TYPES = new Set(["OPT", "OPTION", "OPTIONS", "FUT", "FUTURE", "FUTURES", "WARRANT", "WARRANTS", "RIGHT", "RIGHTS"]);
8
8
  const EQUITY_TYPES = new Set(["STK", "STOCK", "EQUITY", "COMMONSTOCK", "COMMON STOCK", "ADR", "ORDINARYSHARES", "ORDINARY SHARES"]);
9
+ const COMPANY_NAME_SUFFIXES = new Set([
10
+ "AG",
11
+ "CO",
12
+ "COMPANY",
13
+ "CORP",
14
+ "CORPORATION",
15
+ "INC",
16
+ "INCORPORATED",
17
+ "LTD",
18
+ "LIMITED",
19
+ "NV",
20
+ "PLC",
21
+ "R",
22
+ "REGISTERED",
23
+ "SA",
24
+ "SE",
25
+ ]);
9
26
 
10
27
  const EXCHANGE_HINT_ALIASES: Record<string, string[]> = {
11
28
  NASDAQ: ["NASDAQ", "NMS"],
@@ -46,10 +63,13 @@ const ASSET_HINT_MAP: Record<string, TickerSearchInstrumentClass> = {
46
63
  FUTURES: "derivative",
47
64
  };
48
65
 
66
+ const SAVED_MATCH_BONUS = 900;
67
+
49
68
  interface SearchQueryIntent {
50
69
  normalizedQuery: string;
51
70
  compactQuery: string;
52
71
  companyQuery: string;
72
+ companyQueryKey: string;
53
73
  exchangeHints: string[];
54
74
  assetPreference: TickerSearchInstrumentClass | null;
55
75
  }
@@ -82,6 +102,7 @@ export function rankTickerSearchItems<T extends Pick<TickerSearchRankableItem, "
82
102
  const normalizedLabel = normalizeSearchText(item.label);
83
103
  const normalizedDetail = normalizeSearchText(item.detail);
84
104
  const normalizedRight = normalizeSearchText(item.right || "");
105
+ const companyNameKey = getCompanyNameKey(item.detail);
85
106
  const textQueries = [intent.normalizedQuery, intent.companyQuery].filter(Boolean);
86
107
  const labelScore = maxScoreForQueries(textQueries, normalizedLabel, {
87
108
  exact: 24_000,
@@ -107,16 +128,23 @@ export function rankTickerSearchItems<T extends Pick<TickerSearchRankableItem, "
107
128
  .reduce((best, alias) => Math.max(best, scoreSearchAlias(intent, alias)), 0);
108
129
  const textScore = labelScore + detailScore + aliasScore;
109
130
  const saved = isSavedSearchItem(item);
110
- const priorityScore = scoreAssetPreference(intent, item.instrumentClass)
111
- + scoreExchangePreference(intent, item)
112
- + scoreListingPriority(item);
131
+ const explicitIntentScore = scoreAssetPreference(intent, item.instrumentClass)
132
+ + scoreExchangePreference(intent, item);
133
+ const priorityScore = explicitIntentScore + scoreListingPriority(item);
134
+ const companyMatchRank = scoreCompanyNameMatch(intent, companyNameKey);
113
135
  return {
114
136
  item,
115
137
  index,
138
+ companyMatchRank,
139
+ companyNameKey,
140
+ issuerGroupKey: companyMatchRank > 0 && item.instrumentClass === "equity"
141
+ ? companyNameKey
142
+ : null,
143
+ explicitIntentScore,
116
144
  normalizedSymbol: normalizeSearchText(item.symbol || item.label),
117
145
  symbolMatchRank: scoreSymbolMatchRank(intent, item),
118
146
  textScore,
119
- score: textScore + priorityScore + (textScore > 0 && saved ? 900 : 0),
147
+ score: textScore + priorityScore + (textScore > 0 && saved ? SAVED_MATCH_BONUS : 0),
120
148
  };
121
149
  });
122
150
 
@@ -126,21 +154,83 @@ export function rankTickerSearchItems<T extends Pick<TickerSearchRankableItem, "
126
154
  .map(({ normalizedSymbol }) => normalizedSymbol),
127
155
  );
128
156
 
129
- const filtered = ranked
130
- .filter(({ item, normalizedSymbol, textScore }) => {
131
- if (textScore <= 0) return false;
132
- if (item.kind !== "search") return true;
133
- return !matchedLocalSymbols.has(normalizedSymbol);
134
- })
135
- .sort((a, b) => {
136
- if (b.symbolMatchRank !== a.symbolMatchRank) return b.symbolMatchRank - a.symbolMatchRank;
137
- if (b.score !== a.score) return b.score - a.score;
138
- const aSaved = isSavedSearchItem(a.item);
139
- const bSaved = isSavedSearchItem(b.item);
140
- if (aSaved !== bSaved) return aSaved ? -1 : 1;
141
- if (a.item.label.length !== b.item.label.length) return a.item.label.length - b.item.label.length;
142
- return a.index - b.index;
157
+ const filtered = ranked.filter(({ item, normalizedSymbol, textScore }) => {
158
+ if (textScore <= 0) return false;
159
+ if (item.kind !== "search") return true;
160
+ return !matchedLocalSymbols.has(normalizedSymbol);
161
+ });
162
+
163
+ type RankedEntry = (typeof ranked)[number];
164
+ const compareFallbackEntries = (a: RankedEntry, b: RankedEntry): number => {
165
+ if (b.symbolMatchRank !== a.symbolMatchRank) return b.symbolMatchRank - a.symbolMatchRank;
166
+ if (b.score !== a.score) return b.score - a.score;
167
+ const aSaved = isSavedSearchItem(a.item);
168
+ const bSaved = isSavedSearchItem(b.item);
169
+ if (aSaved !== bSaved) return aSaved ? -1 : 1;
170
+ if (a.item.label.length !== b.item.label.length) return a.item.label.length - b.item.label.length;
171
+ return a.index - b.index;
172
+ };
173
+
174
+ const hasExplicitHint = intent.exchangeHints.length > 0 || intent.assetPreference != null;
175
+ const bucketGroups = new Map<string, Map<string, RankedEntry[]>>();
176
+ for (const entry of filtered) {
177
+ const exactSymbolRank = entry.symbolMatchRank >= 2 ? entry.symbolMatchRank : 0;
178
+ const bucketKey = [
179
+ exactSymbolRank,
180
+ hasExplicitHint ? entry.explicitIntentScore : 0,
181
+ entry.companyMatchRank,
182
+ ].join(":");
183
+ const groupKey = entry.issuerGroupKey
184
+ ? `issuer:${entry.issuerGroupKey}`
185
+ : `item:${entry.index}`;
186
+ const groups = bucketGroups.get(bucketKey) ?? new Map<string, RankedEntry[]>();
187
+ const group = groups.get(groupKey) ?? [];
188
+ group.push(entry);
189
+ groups.set(groupKey, group);
190
+ bucketGroups.set(bucketKey, groups);
191
+ }
192
+
193
+ const groupOrderByIndex = new Map<number, number>();
194
+ for (const groups of bucketGroups.values()) {
195
+ const orderedGroups = [...groups.values()]
196
+ .map((entries) => ({
197
+ entries,
198
+ representative: entries.reduce((best, entry) =>
199
+ compareFallbackEntries(entry, best) < 0 ? entry : best
200
+ ),
201
+ }))
202
+ .sort((a, b) => compareFallbackEntries(a.representative, b.representative));
203
+ orderedGroups.forEach(({ entries }, order) => {
204
+ entries.forEach((entry) => groupOrderByIndex.set(entry.index, order));
143
205
  });
206
+ }
207
+
208
+ filtered.sort((a, b) => {
209
+ // Precedence is exact symbol, explicit query hints, whole-word company
210
+ // relevance, issuer relevance, provider order within that issuer, partial
211
+ // symbol, saved relevance, then stable source order.
212
+ const aExactSymbolRank = a.symbolMatchRank >= 2 ? a.symbolMatchRank : 0;
213
+ const bExactSymbolRank = b.symbolMatchRank >= 2 ? b.symbolMatchRank : 0;
214
+ if (bExactSymbolRank !== aExactSymbolRank) return bExactSymbolRank - aExactSymbolRank;
215
+ if (hasExplicitHint && b.explicitIntentScore !== a.explicitIntentScore) {
216
+ return b.explicitIntentScore - a.explicitIntentScore;
217
+ }
218
+ if (b.companyMatchRank !== a.companyMatchRank) {
219
+ return b.companyMatchRank - a.companyMatchRank;
220
+ }
221
+ const aGroupOrder = groupOrderByIndex.get(a.index) ?? a.index;
222
+ const bGroupOrder = groupOrderByIndex.get(b.index) ?? b.index;
223
+ if (aGroupOrder !== bGroupOrder) return aGroupOrder - bGroupOrder;
224
+ if (
225
+ a.issuerGroupKey
226
+ && a.issuerGroupKey === b.issuerGroupKey
227
+ ) {
228
+ const aProviderRank = a.item.providerRank ?? Number.POSITIVE_INFINITY;
229
+ const bProviderRank = b.item.providerRank ?? Number.POSITIVE_INFINITY;
230
+ if (aProviderRank !== bProviderRank) return aProviderRank - bProviderRank;
231
+ }
232
+ return compareFallbackEntries(a, b);
233
+ });
144
234
 
145
235
  const deduped: T[] = [];
146
236
  const seen = new Set<string>();
@@ -216,6 +306,7 @@ function analyzeSearchQuery(query: string): SearchQueryIntent {
216
306
  normalizedQuery,
217
307
  compactQuery,
218
308
  companyQuery,
309
+ companyQueryKey: normalizeCompanyName(companyQuery),
219
310
  exchangeHints,
220
311
  assetPreference,
221
312
  };
@@ -356,6 +447,40 @@ function scoreListingPriority(item: Pick<TickerSearchRankableItem, "label"> & Pa
356
447
  return score;
357
448
  }
358
449
 
450
+ function getCompanyNameKey(detail: string): string {
451
+ return normalizeCompanyName(detail.split("|")[0] || "");
452
+ }
453
+
454
+ function normalizeCompanyName(name: string): string {
455
+ const tokens = normalizeSearchText(name).split(" ").filter(Boolean);
456
+ while (tokens.length > 1) {
457
+ if (COMPANY_NAME_SUFFIXES.has(tokens.at(-1)!)) {
458
+ tokens.pop();
459
+ continue;
460
+ }
461
+
462
+ const punctuatedSuffix = [...COMPANY_NAME_SUFFIXES].find((suffix) =>
463
+ suffix.length > 1
464
+ && tokens.length > suffix.length
465
+ && tokens.slice(-suffix.length).every((token, index) =>
466
+ token.length === 1 && token === suffix[index]
467
+ )
468
+ );
469
+ if (!punctuatedSuffix) break;
470
+ tokens.splice(-punctuatedSuffix.length);
471
+ }
472
+ return tokens.join(" ");
473
+ }
474
+
475
+ function scoreCompanyNameMatch(intent: SearchQueryIntent, companyName: string): number {
476
+ const query = intent.companyQueryKey;
477
+ if (!query || !companyName) return 0;
478
+ if (companyName === query) return 3;
479
+ if (companyName.startsWith(`${query} `)) return 2;
480
+ if (companyName.includes(` ${query} `) || companyName.endsWith(` ${query}`)) return 1;
481
+ return 0;
482
+ }
483
+
359
484
  function isSavedSearchItem(item: Pick<TickerSearchRankableItem, "kind" | "category"> & Partial<TickerSearchRankableItem>): boolean {
360
485
  return item.saved === true || item.kind === "ticker" || item.category === "Saved" || item.category === "Open";
361
486
  }
@@ -16,6 +16,7 @@ export interface TickerSearchRankableItem {
16
16
  instrumentClass?: TickerSearchInstrumentClass;
17
17
  exchangeLabel?: string;
18
18
  primaryExchangeLabel?: string;
19
+ providerRank?: number;
19
20
  searchAliases?: string[];
20
21
  }
21
22
 
@@ -80,11 +80,23 @@ export function alignTimeSeries(
80
80
  const exactMaps = series.map((entry) => (
81
81
  new Map(entry.points.map((point) => [effectiveTimeSeriesPointTime(point), point]))
82
82
  ));
83
+ const carryPoints = series.map((entry) => [...entry.points].sort((left, right) => (
84
+ effectiveTimeSeriesPointTime(left) - effectiveTimeSeriesPointTime(right)
85
+ )));
83
86
  const sortedTimes = [...timeline].sort((left, right) => left - right);
87
+ const carryPointers = new Array<number>(series.length).fill(-1);
84
88
  const rows: AlignedTimeSeriesRow[] = [];
85
89
  for (const time of sortedTimes) {
86
90
  const values: Record<string, AlignedSeriesValue | null> = {};
87
91
  series.forEach((entry, seriesIndex) => {
92
+ const points = carryPoints[seriesIndex]!;
93
+ let pointer = carryPointers[seriesIndex]!;
94
+ while (
95
+ pointer + 1 < points.length
96
+ && effectiveTimeSeriesPointTime(points[pointer + 1]!) <= time
97
+ ) pointer += 1;
98
+ carryPointers[seriesIndex] = pointer;
99
+
88
100
  const exact = exactMaps[seriesIndex]!.get(time);
89
101
  if (exact) {
90
102
  values[entry.id] = { point: exact, value: scalarPointValue(exact), carried: false };
@@ -96,20 +108,12 @@ export function alignTimeSeries(
96
108
  return;
97
109
  }
98
110
 
99
- let previous: TimeSeriesPoint | null = null;
100
- let previousEligibleAt = Number.NEGATIVE_INFINITY;
101
- for (const point of entry.points) {
102
- const eligibleAt = effectiveTimeSeriesPointTime(point);
103
- if (eligibleAt <= time && eligibleAt >= previousEligibleAt) {
104
- previous = point;
105
- previousEligibleAt = eligibleAt;
106
- }
107
- }
108
- if (!previous) {
111
+ if (pointer < 0) {
109
112
  values[entry.id] = null;
110
113
  return;
111
114
  }
112
- const age = time - previousEligibleAt;
115
+ const previous = points[pointer]!;
116
+ const age = time - effectiveTimeSeriesPointTime(previous);
113
117
  if (options.maxCarryMilliseconds !== undefined && age > options.maxCarryMilliseconds) {
114
118
  values[entry.id] = null;
115
119
  return;
@@ -300,6 +300,16 @@ export function isMarketFieldId(id: string): boolean {
300
300
  return canonicalTimeSeriesFieldId(id).startsWith("market.");
301
301
  }
302
302
 
303
+ export function isPriceOnlyMarketFieldId(id: string): boolean {
304
+ const canonical = canonicalTimeSeriesFieldId(id);
305
+ return canonical === "market.ohlcv"
306
+ || canonical === "market.open"
307
+ || canonical === "market.high"
308
+ || canonical === "market.low"
309
+ || canonical === "market.close"
310
+ || canonical === "market.volume";
311
+ }
312
+
303
313
  export function isFundamentalFieldId(id: string): boolean {
304
314
  const canonical = canonicalTimeSeriesFieldId(id);
305
315
  return canonical.startsWith("fundamental.") || canonical.startsWith("valuation.");
@@ -240,11 +240,10 @@ function mergeStatementsByPeriod(
240
240
  const groups: InternalStatement[][] = [];
241
241
  const sorted = [...statements].sort((left, right) => left.date.localeCompare(right.date));
242
242
  for (const statement of sorted) {
243
- const group = groups.find((candidate) => (
244
- areNearbyFinancialPeriodEnds(candidate[0]!.date, statement.date)
245
- ));
246
- if (group) group.push(statement as InternalStatement);
247
- else groups.push([statement as InternalStatement]);
243
+ const lastGroup = groups.at(-1);
244
+ if (lastGroup && areNearbyFinancialPeriodEnds(lastGroup[0]!.date, statement.date)) {
245
+ lastGroup.push(statement as InternalStatement);
246
+ } else groups.push([statement as InternalStatement]);
248
247
  }
249
248
  return groups
250
249
  .map(mergeStatementPeriodGroup)
@@ -601,12 +600,22 @@ function pointForStatement(
601
600
  };
602
601
  }
603
602
 
603
+ /**
604
+ * `PricePoint.date` is typed as a `Date`, but history that has crossed a JSON
605
+ * boundary (persisted caches, the screenshot payload) arrives as a string. Read
606
+ * it defensively so a serialized round trip cannot crash valuation history.
607
+ */
608
+ function pointTime(point: PricePoint): number {
609
+ const date = point.date;
610
+ return date instanceof Date ? date.getTime() : Date.parse(date as unknown as string);
611
+ }
612
+
604
613
  function priceAtOrBefore(priceHistory: readonly PricePoint[], date: string): number | null {
605
614
  const target = Date.parse(date);
606
615
  if (!Number.isFinite(target)) return null;
607
616
  let closest: { time: number; close: number } | undefined;
608
617
  for (const point of priceHistory) {
609
- const time = point.date.getTime();
618
+ const time = pointTime(point);
610
619
  if (!Number.isFinite(time) || time > target || !finiteNumber(point.close) || point.close <= 0) continue;
611
620
  if (!closest || time > closest.time) closest = { time, close: point.close };
612
621
  }
@@ -767,11 +776,10 @@ function dedupeFundamentalPeriods(points: readonly TimeSeriesPoint[]): TimeSerie
767
776
  left.observedAt.getTime() - right.observedAt.getTime()
768
777
  ));
769
778
  for (const point of sorted) {
770
- const group = groups.find((candidate) => (
771
- areNearbyFinancialPeriodEnds(candidate[0]!.observedAt, point.observedAt)
772
- ));
773
- if (group) group.push(point);
774
- else groups.push([point]);
779
+ const lastGroup = groups.at(-1);
780
+ if (lastGroup && areNearbyFinancialPeriodEnds(lastGroup[0]!.observedAt, point.observedAt)) {
781
+ lastGroup.push(point);
782
+ } else groups.push([point]);
775
783
  }
776
784
  return groups
777
785
  .map((group) => group.slice(1).reduce(preferredPeriodPoint, group[0]!))
@@ -2,7 +2,8 @@ import { useMemo } from "react";
2
2
  import { apiClient } from "../api-client";
3
3
  import { loadCachedFredSeries } from "../data/fred-series";
4
4
  import { instrumentFromTicker } from "../market-data/request-types";
5
- import { useAssetData } from "../plugins/runtime";
5
+ import { useAssetData, useCapabilityInvoker } from "../plugins/runtime";
6
+ import { createChartSeriesResolver } from "../capabilities";
6
7
  import { useAppSelector } from "../state/app/context";
7
8
  import type { FredSeriesRequest } from "../data/fred-series";
8
9
  import type { TickerRecord } from "../types/ticker";
@@ -56,11 +57,16 @@ export function useResolvedChartSpec(
56
57
  options: UseChartResolutionOptions = {},
57
58
  ): UseChartResolutionResult {
58
59
  const dataProvider = useAssetData();
60
+ const capabilityInvoker = useCapabilityInvoker();
59
61
  const tickers = useAppSelector((state) => state.tickers);
60
62
  const hydratedSpec = useMemo(
61
63
  () => hydrateChartSpecInstruments(spec, tickers),
62
64
  [spec, tickers],
63
65
  );
64
- const sources = useMemo(() => ({ dataProvider, loadFredSeries: loadFred }), [dataProvider]);
66
+ const sources = useMemo(() => ({
67
+ dataProvider,
68
+ loadFredSeries: loadFred,
69
+ resolveCapabilitySeries: createChartSeriesResolver(capabilityInvoker),
70
+ }), [capabilityInvoker, dataProvider]);
65
71
  return useChartResolution(hydratedSpec, sources, options);
66
72
  }
@@ -0,0 +1,35 @@
1
+ import type { PricePoint } from "../types/financials";
2
+ import type { TimeRange } from "./range";
3
+ import type { ManualChartResolution } from "./resolution";
4
+
5
+ const MAX_PARSED_HISTORY = 32;
6
+ const parsedHistory = new Map<string, PricePoint[]>();
7
+
8
+ export function parsedPriceHistoryKey(
9
+ symbol: string,
10
+ exchange: string,
11
+ range: TimeRange,
12
+ resolution?: ManualChartResolution,
13
+ ): string {
14
+ return [
15
+ symbol.trim().toUpperCase(),
16
+ exchange.trim().toUpperCase(),
17
+ range,
18
+ resolution ?? "",
19
+ ].join("|");
20
+ }
21
+
22
+ export function rememberParsedPriceHistory(key: string, points: PricePoint[]): void {
23
+ if (points.length === 0) return;
24
+ if (parsedHistory.has(key)) parsedHistory.delete(key);
25
+ parsedHistory.set(key, points);
26
+ while (parsedHistory.size > MAX_PARSED_HISTORY) {
27
+ const oldest = parsedHistory.keys().next().value;
28
+ if (oldest === undefined) break;
29
+ parsedHistory.delete(oldest);
30
+ }
31
+ }
32
+
33
+ export function readParsedPriceHistory(key: string): PricePoint[] | undefined {
34
+ return parsedHistory.get(key);
35
+ }
@@ -169,6 +169,16 @@ export function normalizeChartResolutionSupport(support: readonly ChartResolutio
169
169
  );
170
170
  }
171
171
 
172
+ /** Yahoo-shaped support used for first paint when broker/provider support is async. */
173
+ export const DEFAULT_CHART_RESOLUTION_SUPPORT: ChartResolutionSupport[] = normalizeChartResolutionSupport([
174
+ { resolution: "5m", maxRange: "1W" },
175
+ { resolution: "15m", maxRange: "1M" },
176
+ { resolution: "1h", maxRange: "3M" },
177
+ { resolution: "1d", maxRange: "5Y" },
178
+ { resolution: "1wk", maxRange: "ALL" },
179
+ { resolution: "1mo", maxRange: "ALL" },
180
+ ]);
181
+
172
182
  export function intersectChartResolutionSupport(
173
183
  supportSets: Array<readonly ChartResolutionSupport[]>,
174
184
  ): ChartResolutionSupport[] {