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
@@ -13,6 +13,7 @@ import {
13
13
  } from "../../assist/model";
14
14
  import { matchPrefix, type Command } from "../../commands/registry";
15
15
  import { isCollectionCommand } from "../../helpers";
16
+ import { dedupeById } from "../../view-model";
16
17
  import type { ResultItem } from "../../list/model";
17
18
  import type { parseRootShortcutIntent } from "./shortcuts";
18
19
  import type { CommandBarRoute } from "../../workflow/types";
@@ -21,6 +22,13 @@ import { buildRootShortcutItem } from "./shortcut-items";
21
22
 
22
23
  type RootShortcutIntent = ReturnType<typeof parseRootShortcutIntent>;
23
24
 
25
+ /** The ART plugin command claims the prefix, so article rows must still be shown. */
26
+ export function isArticleLookupShortcut(intent: RootShortcutIntent): boolean {
27
+ return intent.kind !== "none"
28
+ && intent.source === "plugin-command"
29
+ && intent.command.id === "open-news-article";
30
+ }
31
+
24
32
  interface PaneTemplateItemOptions {
25
33
  category?: string;
26
34
  createOptions?: PaneTemplateCreateOptions;
@@ -67,6 +75,7 @@ export interface RootResultModelOptions {
67
75
  pluginCommandResultItems: (command: CommandDef, shortcutArg: string) => ResultItem[];
68
76
  rootQuery: string;
69
77
  rootShortcutIntent: RootShortcutIntent;
78
+ articleResultItems?: ResultItem[];
70
79
  runDirectCommand: (command: Command, arg: string) => void;
71
80
  runSecurityDescriptionShortcut: (query?: string) => void | Promise<void>;
72
81
  state: AppState;
@@ -74,9 +83,17 @@ export interface RootResultModelOptions {
74
83
  }
75
84
 
76
85
  /** An in-flight or answered request keeps its rows even if the heuristic lapses. */
77
- function isAssistSectionVisible(assist: AssistRowHandlers, query: string, resultCount: number): boolean {
86
+ function isAssistSectionVisible(
87
+ assist: AssistRowHandlers,
88
+ query: string,
89
+ resultCount: number,
90
+ hasShortcutIntent: boolean,
91
+ ): boolean {
78
92
  if (!query.trim()) return false;
79
93
  if (assist.state.status !== "idle" && assist.state.query === query.trim()) return true;
94
+ // A resolved shortcut is the user speaking the command language, so nothing is
95
+ // asked of the AI, and a sign-up offer must not outrank that exact match.
96
+ if (hasShortcutIntent) return false;
80
97
  // Signed out there is nothing to wait for, so the older heuristic still picks
81
98
  // the queries worth offering a sign-up row for.
82
99
  if (!assist.enabled) return shouldShowAssistRow({ query, resultCount });
@@ -108,6 +125,7 @@ export function buildRootResultModel(options: RootResultModelOptions): RootResul
108
125
  pluginCommandResultItems,
109
126
  rootQuery,
110
127
  rootShortcutIntent,
128
+ articleResultItems = [],
111
129
  runDirectCommand,
112
130
  runSecurityDescriptionShortcut,
113
131
  state,
@@ -157,19 +175,18 @@ export function buildRootResultModel(options: RootResultModelOptions): RootResul
157
175
  includePromptableTickerTemplates: true,
158
176
  })
159
177
  : [];
160
- const seenItemIds = new Set<string>();
161
- for (const item of [...templateItems, ...relatedTemplateItems]) {
162
- if (seenItemIds.has(item.id)) continue;
163
- seenItemIds.add(item.id);
164
- items.push(item);
165
- }
178
+ items.push(...templateItems, ...relatedTemplateItems);
166
179
  } else if (
167
180
  rootShortcutIntent.kind !== "none"
168
181
  && rootShortcutIntent.source === "plugin-command"
169
182
  && shortcutItem
170
183
  ) {
171
- const dynamicItems = pluginCommandResultItems(rootShortcutIntent.command, rootShortcutIntent.argText);
172
- items.push(...(dynamicItems.length > 0 ? dynamicItems : [shortcutItem]));
184
+ if (isArticleLookupShortcut(rootShortcutIntent)) {
185
+ items.push(shortcutItem);
186
+ } else {
187
+ const dynamicItems = pluginCommandResultItems(rootShortcutIntent.command, rootShortcutIntent.argText);
188
+ items.push(...(dynamicItems.length > 0 ? dynamicItems : [shortcutItem]));
189
+ }
173
190
  } else if (match && match.command.id === "plugins") {
174
191
  items.push(...buildPluginItems(match.arg));
175
192
  } else if (match && match.command.id === "layout") {
@@ -227,12 +244,22 @@ export function buildRootResultModel(options: RootResultModelOptions): RootResul
227
244
  items.push(...matchedItems);
228
245
  }
229
246
 
247
+ if (rootShortcutIntent.kind === "none" || isArticleLookupShortcut(rootShortcutIntent)) {
248
+ items.push(...articleResultItems);
249
+ }
250
+
230
251
  // Built from the local matches, then moved above them: the AI answers the
231
252
  // question the user typed, so it leads the list. Rows landing here renumber
232
253
  // everything below, which the root selection effect absorbs by identity.
233
- const assistItems = assist && isAssistSectionVisible(assist, rootQuery, items.length)
254
+ const assistItems = assist
255
+ && isAssistSectionVisible(
256
+ assist,
257
+ rootQuery,
258
+ items.length,
259
+ rootShortcutIntent.kind !== "none" && !isArticleLookupShortcut(rootShortcutIntent),
260
+ )
234
261
  ? buildAssistResultItems({ ...assist, query: rootQuery })
235
262
  : [];
236
263
 
237
- return { items: [...assistItems, ...items], initialIdx };
264
+ return { items: dedupeById([...assistItems, ...items]), initialIdx };
238
265
  }
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo, useRef, type Dispatch, type RefObject, type SetStateAction } from "react";
1
+ import { useEffect, useLayoutEffect, useMemo, useRef, type Dispatch, type RefObject, type SetStateAction } from "react";
2
2
  import type { ScrollBoxRenderable } from "../../../../ui";
3
3
  import type { AppState } from "../../../../state/app/context";
4
4
  import type { DataProvider } from "../../../../types/data-provider";
@@ -63,6 +63,7 @@ interface UseCommandBarRootRuntimeOptions {
63
63
  }): ResultItem[];
64
64
  pluginCommandItems(): ResultItem[];
65
65
  pluginCommandResultItems(command: CommandDef, shortcutArg: string): ResultItem[];
66
+ articleResultItems?: ResultItem[];
66
67
  readTickerSearchCache(
67
68
  query: string,
68
69
  brokerId?: string | null,
@@ -114,6 +115,7 @@ export function useCommandBarRootRuntime({
114
115
  paneShortcutItems,
115
116
  pluginCommandItems,
116
117
  pluginCommandResultItems,
118
+ articleResultItems = [],
117
119
  readTickerSearchCache,
118
120
  rootModeKind,
119
121
  rootQuery,
@@ -187,6 +189,7 @@ export function useCommandBarRootRuntime({
187
189
  pluginCommandResultItems,
188
190
  rootQuery,
189
191
  rootShortcutIntent,
192
+ articleResultItems,
190
193
  runDirectCommand,
191
194
  runSecurityDescriptionShortcut,
192
195
  state,
@@ -215,6 +218,7 @@ export function useCommandBarRootRuntime({
215
218
  pluginCommandResultItems,
216
219
  rootQuery,
217
220
  rootShortcutIntent,
221
+ articleResultItems,
218
222
  runDirectCommand,
219
223
  runSecurityDescriptionShortcut,
220
224
  state,
@@ -246,7 +250,7 @@ export function useCommandBarRootRuntime({
246
250
  writeTickerSearchCache,
247
251
  });
248
252
 
249
- useEffect(() => {
253
+ useLayoutEffect(() => {
250
254
  if (currentRoute) return;
251
255
 
252
256
  const resultIds = orderedRootResults.map((item) => item.id);
@@ -43,7 +43,7 @@ export function useCommandBarTickerSearchActions({
43
43
  focusTicker(ticker.metadata.ticker, options);
44
44
  closeAll({ revertThemePreview: false });
45
45
  })();
46
- }, [tickerRepository, dispatch, pluginRegistry.events, focusTicker, closeAll]);
46
+ }, [closeAll, dispatch, focusTicker, pluginRegistry.events, tickerRepository]);
47
47
 
48
48
  const mapTickerSearchCandidateToResultItem = useCallback((candidate: TickerSearchCandidate): ResultItem => {
49
49
  const detail = formatTickerSearchDetail(candidate);
@@ -0,0 +1,34 @@
1
+ import { expect, test } from "bun:test";
2
+ import type { ResultItem } from "../../list/model";
3
+ import { mergeTickerSearchResultItems } from "./results";
4
+
5
+ function resultItem(id: string, label: string, right: string, kind: ResultItem["kind"] = "ticker"): ResultItem {
6
+ return {
7
+ id,
8
+ label,
9
+ detail: "Apple Inc.",
10
+ category: kind === "info" ? "Search" : "Saved",
11
+ kind,
12
+ right,
13
+ action: () => {},
14
+ };
15
+ }
16
+
17
+ test("keeps completed ticker-search results authoritative over provisional rows", () => {
18
+ const authoritative = [
19
+ resultItem("ranked:AAPL", "AAPL", "Equity NASDAQ"),
20
+ resultItem("ranked:APC", "APC", "Equity XETRA"),
21
+ ];
22
+ const provisional = [
23
+ resultItem("local:APC", "APC", "Equity XETRA"),
24
+ resultItem("local:AAPL", "AAPL", "Equity NASDAQ"),
25
+ ];
26
+
27
+ expect(mergeTickerSearchResultItems("Apple", authoritative, provisional).map((item) => item.id)).toEqual([
28
+ "ranked:AAPL",
29
+ "ranked:APC",
30
+ ]);
31
+
32
+ const noResults = resultItem("no-results", "No matches for Apple", "", "info");
33
+ expect(mergeTickerSearchResultItems("Apple", [noResults], [])).toEqual([noResults]);
34
+ });
@@ -1,7 +1,6 @@
1
1
  import type { TickerRecord } from "../../../../types/ticker";
2
2
  import {
3
3
  createLocalTickerSearchCandidates,
4
- rankTickerSearchItems,
5
4
  type TickerSearchCandidate,
6
5
  } from "../../../../tickers/search";
7
6
  import type { ResultItem } from "../../list/model";
@@ -33,7 +32,7 @@ function isExactTickerResultMatch(item: ResultItem, query: string): boolean {
33
32
 
34
33
  export function mergeTickerSearchResultItems(
35
34
  query: string,
36
- preferredItems: ResultItem[],
35
+ rankedItems: ResultItem[],
37
36
  fallbackItems: ResultItem[],
38
37
  ): ResultItem[] {
39
38
  const merged: ResultItem[] = [];
@@ -45,12 +44,11 @@ export function mergeTickerSearchResultItems(
45
44
  seen.add(key);
46
45
  merged.push(item);
47
46
  };
48
- preferredItems.forEach(addItem);
47
+ rankedItems.forEach(addItem);
49
48
  fallbackItems.forEach(addItem);
50
- if (merged.length === 0) return fallbackItems;
49
+ if (merged.length === 0) return rankedItems.length > 0 ? rankedItems : fallbackItems;
51
50
 
52
- return rankTickerSearchItems(merged, query)
53
- .map((item) => isExactTickerResultMatch(item, query) && item.category !== "Saved"
51
+ return merged.map((item) => isExactTickerResultMatch(item, query) && item.category !== "Saved"
54
52
  ? { ...item, category: "Exact Match" }
55
53
  : item);
56
54
  }
@@ -80,7 +80,7 @@ export function useTickerSearchRouteResults(options: {
80
80
  brokerInstanceId,
81
81
  );
82
82
  setTickerSearchResults(cachedCandidates
83
- ? mergeTickerSearchResultItems(searchQuery, localItems, buildTickerSearchResultItems(cachedCandidates, searchQuery))
83
+ ? mergeTickerSearchResultItems(searchQuery, buildTickerSearchResultItems(cachedCandidates, searchQuery), localItems)
84
84
  : localItems);
85
85
  const requestId = ++searchRequestIdRef.current;
86
86
  const searchDelay = skipTickerSearchDebounceRef.current ? 0 : 200;
@@ -109,8 +109,8 @@ export function useTickerSearchRouteResults(options: {
109
109
  );
110
110
  setTickerSearchResults(mergeTickerSearchResultItems(
111
111
  searchQuery,
112
- localTickerSearchResultItems(searchQuery, { limit: 6 }),
113
112
  buildTickerSearchResultItems(combined, searchQuery),
113
+ localTickerSearchResultItems(searchQuery, { limit: 6 }),
114
114
  ));
115
115
  } catch {
116
116
  if (requestId !== searchRequestIdRef.current) return;
@@ -135,12 +135,13 @@ function buildRouteListState(options: BuildRouteListStateOptions): ListScreenSta
135
135
  query: currentRoute.query,
136
136
  selectedIdx: currentRoute.selectedIdx,
137
137
  hoveredIdx: currentRoute.hoveredIdx,
138
- results: orderListResults(results),
138
+ results: orderListResults(results, { sectionOrder: "ranked" }),
139
139
  searching: tickerSearchPending,
140
140
  emptyLabel: emptyState.label,
141
141
  emptyDetail: emptyState.detail,
142
142
  footerLeft: getScreenFooterLeft(currentRoute),
143
143
  footerRight: getScreenFooterRight(currentRoute),
144
+ sectionOrder: "ranked",
144
145
  };
145
146
  }
146
147
  default:
@@ -144,12 +144,17 @@ describe("CommandBar AI assist", () => {
144
144
 
145
145
  test("keeps the row the user picked when an answer lands above it", async () => {
146
146
  signInVerified();
147
- mockAssistTransport(() => jsonResponse({
148
- candidates: [
149
- { input: "CHAT #general", title: "Open the general channel", prefix: "CHAT", confidence: 0.9 },
150
- { input: "CHAT #random", title: "Open the random channel", prefix: "CHAT", confidence: 0.4 },
151
- ],
152
- }));
147
+ let releaseResponse = () => {};
148
+ const held = new Promise<void>((resolve) => { releaseResponse = resolve; });
149
+ const requests = mockAssistTransport(async () => {
150
+ await held;
151
+ return jsonResponse({
152
+ candidates: [
153
+ { input: "CHAT #general", title: "Open the general channel", prefix: "CHAT", confidence: 0.9 },
154
+ { input: "CHAT #random", title: "Open the random channel", prefix: "CHAT", confidence: 0.4 },
155
+ ],
156
+ });
157
+ });
153
158
  const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
154
159
 
155
160
  testSetup = await testRender(
@@ -161,8 +166,10 @@ describe("CommandBar AI assist", () => {
161
166
  );
162
167
 
163
168
  await testSetup.renderOnce();
169
+ await waitForRequest(requests);
164
170
  // Down lands on the local match while a single "Thinking…" row sits above.
165
171
  await emitKeypress(testSetup, { name: "down" });
172
+ releaseResponse();
166
173
  await waitForFrameToContain("CHAT #random — Open the random channel", ASSIST_WAIT_ATTEMPTS);
167
174
 
168
175
  // Two answers replaced that one row, so the chosen row moved down by one —
@@ -926,6 +926,71 @@ describe("CommandBar", () => {
926
926
  expect(aaplRow).toBeLessThan(appRow);
927
927
  });
928
928
 
929
+ test("keeps the provider-ranked canonical listing ahead of provisional saved order", async () => {
930
+ testSetup = await testRender(
931
+ <CommandBarHarness
932
+ query=""
933
+ extraTickers={[makeTicker("APC", "Apple Inc.", {
934
+ exchange: "XETRA",
935
+ currency: "EUR",
936
+ assetCategory: "STK",
937
+ })]}
938
+ configureState={(state) => ({
939
+ ...state,
940
+ commandBarLaunchRequest: {
941
+ kind: "ticker-search",
942
+ query: "Apple",
943
+ sequence: 1,
944
+ },
945
+ tickers: new Map([
946
+ ["APC", state.tickers.get("APC")!],
947
+ ["AAPL", state.tickers.get("AAPL")!],
948
+ ["MSFT", state.tickers.get("MSFT")!],
949
+ ]),
950
+ })}
951
+ dataProvider={makeDataProvider(async () => [
952
+ {
953
+ providerId: "yahoo",
954
+ symbol: "AAPL",
955
+ name: "Apple Inc.",
956
+ exchange: "NASDAQ",
957
+ primaryExchange: "NASDAQ",
958
+ type: "EQUITY",
959
+ currency: "USD",
960
+ },
961
+ {
962
+ providerId: "broker",
963
+ symbol: "APC",
964
+ name: "Apple Inc.",
965
+ exchange: "XETRA",
966
+ primaryExchange: "XETRA",
967
+ type: "EQUITY",
968
+ currency: "EUR",
969
+ },
970
+ {
971
+ providerId: "yahoo",
972
+ symbol: "APLY",
973
+ name: "Apple Yield Shares ETF",
974
+ exchange: "NYSE Arca",
975
+ type: "ETF",
976
+ currency: "USD",
977
+ },
978
+ ])}
979
+ />,
980
+ { width: 100, height: 24 },
981
+ );
982
+
983
+ await testSetup.renderOnce();
984
+ const frame = await waitForFrameToContain("APLY");
985
+ const rows = frame.split("\n");
986
+ const aaplRow = rows.findIndex((line) => line.trimStart().startsWith("AAPL"));
987
+ const apcRow = rows.findIndex((line) => line.trimStart().startsWith("APC"));
988
+
989
+ expect(aaplRow).toBeGreaterThanOrEqual(0);
990
+ expect(apcRow).toBeGreaterThanOrEqual(0);
991
+ expect(aaplRow).toBeLessThan(apcRow);
992
+ });
993
+
929
994
  test("renders form-layout wizard fields together on one screen", async () => {
930
995
  testSetup = await testRender(
931
996
  <CommandBarHarness
@@ -7,6 +7,14 @@ import { usePlanAccess } from "../../../plugins/builtin/shared/plan-access";
7
7
  import { buildAssistCommandInventory } from "../assist/inventory";
8
8
  import { useCommandBarAssist } from "../assist/runtime";
9
9
  import { shouldAutoAskAssist, type AssistRowHandlers } from "../assist/model";
10
+ import { useNewsArticles } from "../../../news/hooks";
11
+ import {
12
+ ARTICLE_SEARCH_QUERY,
13
+ cachedNewsArticles,
14
+ looksLikeArticleQuery,
15
+ } from "../../../plugins/builtin/news/wire/article-search";
16
+ import { buildArticleSearchResultItems } from "../routes/root/article-results";
17
+ import { openUrl } from "../../ui/external-link";
10
18
  import { useRouteListState } from "../routing/list-state";
11
19
  import { useCommandBarRootRuntime } from "../routes/root/runtime";
12
20
  import { parseRootShortcutIntent } from "../routes/root/shortcuts";
@@ -247,6 +255,32 @@ export function CommandBar({
247
255
  ),
248
256
  }), [askAssistNow, assistActive, assistAutoAsk, assistState, planAccess.emailVerified, startAssistSignUp]);
249
257
 
258
+ const newsState = useNewsArticles(looksLikeArticleQuery(rootQuery) ? ARTICLE_SEARCH_QUERY : null);
259
+ const articleResultItems = useMemo(() => {
260
+ const cached = cachedNewsArticles();
261
+ const seen = new Set<string>();
262
+ const articles = [];
263
+ for (const article of [...cached, ...newsState.articles]) {
264
+ if (seen.has(article.id)) continue;
265
+ seen.add(article.id);
266
+ articles.push(article);
267
+ }
268
+ const localReady = cached.length > 0
269
+ || newsState.phase === "ready"
270
+ || newsState.phase === "refreshing"
271
+ || newsState.phase === "error";
272
+ const stillLoading = !localReady && (newsState.phase === "idle" || newsState.phase === "loading");
273
+ return buildArticleSearchResultItems({
274
+ articles,
275
+ query: rootQuery,
276
+ phase: stillLoading ? "loading" : "ready",
277
+ onOpen: (article) => {
278
+ openUrl(article.url);
279
+ closeAll({ revertThemePreview: false });
280
+ },
281
+ });
282
+ }, [closeAll, newsState.articles, newsState.phase, rootQuery]);
283
+
250
284
  const {
251
285
  activeMatch,
252
286
  orderedRootResults,
@@ -283,6 +317,7 @@ export function CommandBar({
283
317
  paneShortcutItems,
284
318
  pluginCommandItems,
285
319
  pluginCommandResultItems,
320
+ articleResultItems,
286
321
  readTickerSearchCache,
287
322
  rootModeKind: rootModeInfo.kind,
288
323
  rootQuery,
@@ -47,6 +47,25 @@ describe("command bar view model helpers", () => {
47
47
  expect(sections.map((section) => section.category)).toEqual(["Tickers", "Config", "Danger", "Debug"]);
48
48
  });
49
49
 
50
+ test("drops an offer-only section below real matches even when its category leads", () => {
51
+ const sections = buildSections([
52
+ { id: "assist:sign-up", category: "Ask AI", disabled: true, defaultSelectable: false },
53
+ { id: "holders", category: "Panes" },
54
+ ]);
55
+
56
+ expect(sections.map((section) => section.category)).toEqual(["Panes", "Ask AI"]);
57
+ expect(sections[0]?.items[0]?.id).toBe("holders");
58
+ });
59
+
60
+ test("keeps an answered AI section leading the list", () => {
61
+ const sections = buildSections([
62
+ { id: "assist:candidate:0", category: "Ask AI" },
63
+ { id: "holders", category: "Panes" },
64
+ ]);
65
+
66
+ expect(sections.map((section) => section.category)).toEqual(["Ask AI", "Panes"]);
67
+ });
68
+
50
69
  test("keeps non-exact ticker suggestions behind app sections in app-first order", () => {
51
70
  const sections = buildSections([
52
71
  { id: "pane", category: "Panes" },
@@ -67,6 +86,30 @@ describe("command bar view model helpers", () => {
67
86
  ]);
68
87
  });
69
88
 
89
+ test("orders ticker sections by their best-ranked candidate", () => {
90
+ const sections = buildSections([
91
+ { id: "primary", category: "Primary Listing" },
92
+ { id: "saved-alternate", category: "Saved" },
93
+ { id: "other", category: "Other Listings" },
94
+ { id: "saved-fallback", category: "Saved" },
95
+ { id: "fund", category: "Funds & Derivatives" },
96
+ ], { sectionOrder: "ranked" });
97
+
98
+ expect(sections.map((section) => section.category)).toEqual([
99
+ "Primary Listing",
100
+ "Saved",
101
+ "Other Listings",
102
+ "Funds & Derivatives",
103
+ ]);
104
+ expect(sections.flatMap((section) => section.items.map((item) => item.id))).toEqual([
105
+ "primary",
106
+ "saved-alternate",
107
+ "saved-fallback",
108
+ "other",
109
+ "fund",
110
+ ]);
111
+ });
112
+
70
113
  test("derives row presentation for toggles and current rows", () => {
71
114
  expect(getRowPresentation({
72
115
  id: "plugin:news",
@@ -4,6 +4,23 @@ import { truncateToDisplayWidth } from "../../utils/format";
4
4
 
5
5
  export { rankTickerSearchItems } from "../../tickers/search";
6
6
 
7
+ /**
8
+ * Drop repeats of an id, keeping the first. The root list is assembled from
9
+ * overlapping sources (pane shortcuts, commands, plugin commands, ticker
10
+ * actions, layout items) in priority order, so the first hit is the one whose
11
+ * category and detail fit best.
12
+ */
13
+ export function dedupeById<T extends { id: string }>(items: T[]): T[] {
14
+ const seen = new Set<string>();
15
+ const result: T[] = [];
16
+ for (const item of items) {
17
+ if (seen.has(item.id)) continue;
18
+ seen.add(item.id);
19
+ result.push(item);
20
+ }
21
+ return result;
22
+ }
23
+
7
24
  export type CommandBarMode = "default" | "search" | "themes" | "plugins" | "layout" | "direct-command";
8
25
 
9
26
  export interface CommandBarModeInfo {
@@ -17,7 +34,7 @@ export interface CommandBarSection<T> {
17
34
  items: T[];
18
35
  }
19
36
 
20
- export type CommandBarSectionOrder = "default" | "app-first";
37
+ export type CommandBarSectionOrder = "default" | "app-first" | "ranked";
21
38
 
22
39
  export interface CommandBarItemView {
23
40
  id: string;
@@ -66,7 +83,24 @@ export function resolveCommandBarMode(query: string, commandList?: Command[]): C
66
83
  }
67
84
  }
68
85
 
69
- export function buildSections<T extends { category: string }>(
86
+ /**
87
+ * A section whose every row is disabled or unselectable is an offer, not an
88
+ * answer, so it sorts below real matches however its category is prioritized.
89
+ */
90
+ const OFFER_SECTION_DEMOTION = 200;
91
+
92
+ interface SectionSortableItem {
93
+ category: string;
94
+ disabled?: boolean;
95
+ defaultSelectable?: boolean;
96
+ }
97
+
98
+ function isOfferOnlySection<T extends SectionSortableItem>(items: T[]): boolean {
99
+ return items.length > 0
100
+ && items.every((item) => item.disabled === true || item.defaultSelectable === false);
101
+ }
102
+
103
+ export function buildSections<T extends SectionSortableItem>(
70
104
  items: T[],
71
105
  options?: { sectionOrder?: CommandBarSectionOrder },
72
106
  ): Array<CommandBarSection<T>> {
@@ -82,8 +116,10 @@ export function buildSections<T extends { category: string }>(
82
116
  return sections
83
117
  .map((section, index) => ({ section, index }))
84
118
  .sort((a, b) => {
85
- const leftPriority = getCategoryPriority(a.section.category, options?.sectionOrder);
86
- const rightPriority = getCategoryPriority(b.section.category, options?.sectionOrder);
119
+ const leftPriority = getCategoryPriority(a.section.category, options?.sectionOrder)
120
+ + (isOfferOnlySection(a.section.items) ? OFFER_SECTION_DEMOTION : 0);
121
+ const rightPriority = getCategoryPriority(b.section.category, options?.sectionOrder)
122
+ + (isOfferOnlySection(b.section.items) ? OFFER_SECTION_DEMOTION : 0);
87
123
  const priorityDiff = leftPriority - rightPriority;
88
124
  return priorityDiff !== 0 ? priorityDiff : a.index - b.index;
89
125
  })
@@ -138,6 +174,7 @@ export function truncateText(text: string, width: number): string {
138
174
 
139
175
  function getCategoryPriority(category: string, sectionOrder: CommandBarSectionOrder = "default"): number {
140
176
  const normalized = category.trim().toLowerCase();
177
+ if (sectionOrder === "ranked") return 0;
141
178
  // The AI answers the question the user actually typed, so it leads the list.
142
179
  if (normalized === "ask ai") return -100;
143
180
  if (normalized === "exact match") return -50;
@@ -0,0 +1,94 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { Dispatch } from "react";
3
+ import { createInitialState, type AppAction, type AppState } from "../../../state/app/context";
4
+ import { createDefaultConfig } from "../../../types/config";
5
+ import type { DataProvider } from "../../../types/data-provider";
6
+ import type { TickerRecord } from "../../../types/ticker";
7
+ import { EventBus } from "../../../plugins/event-bus";
8
+ import type { PluginRegistry } from "../../../plugins/registry";
9
+ import { createCommandBarCollectionWorkflowActions } from "./collection-actions";
10
+
11
+ function ticker(symbol: string, portfolios: string[] = []): TickerRecord {
12
+ return {
13
+ metadata: {
14
+ ticker: symbol,
15
+ exchange: "NASDAQ",
16
+ currency: "USD",
17
+ name: symbol,
18
+ portfolios,
19
+ watchlists: [],
20
+ positions: [],
21
+ custom: {},
22
+ tags: [],
23
+ },
24
+ };
25
+ }
26
+
27
+ function createHarness(initialTicker: TickerRecord) {
28
+ const state: AppState = {
29
+ ...createInitialState(createDefaultConfig(":memory:")),
30
+ tickers: new Map([[initialTicker.metadata.ticker, initialTicker]]),
31
+ };
32
+ const order: string[] = [];
33
+ const payloads: Array<{ symbol: string; portfolioId: string }> = [];
34
+ const events = new EventBus();
35
+ const pluginRegistry = { events } as PluginRegistry;
36
+ events.on("command-bar:portfolio-membership-persisted", (payload) => {
37
+ order.push("event");
38
+ payloads.push(payload);
39
+ });
40
+
41
+ const actions = createCommandBarCollectionWorkflowActions({
42
+ activeCollectionId: "main",
43
+ activeTickerSymbol: initialTicker.metadata.ticker,
44
+ dataProvider: {} as DataProvider,
45
+ dispatch: ((action: AppAction) => {
46
+ expect(action.type).toBe("UPDATE_TICKER");
47
+ order.push("dispatch");
48
+ }) as Dispatch<AppAction>,
49
+ getState: () => state,
50
+ notify: () => {},
51
+ persistConfig: () => {},
52
+ pluginRegistry,
53
+ setActiveCollection: () => {},
54
+ tickerRepository: {
55
+ loadAllTickers: async () => [],
56
+ loadTicker: async () => null,
57
+ saveTicker: async () => {
58
+ order.push("save:start");
59
+ await Promise.resolve();
60
+ order.push("save:end");
61
+ },
62
+ createTicker: async () => initialTicker,
63
+ deleteTicker: async () => {},
64
+ },
65
+ });
66
+
67
+ return { actions, order, payloads };
68
+ }
69
+
70
+ describe("command-bar collection workflow actions", () => {
71
+ test("emits portfolio membership after the ticker is saved and dispatched", async () => {
72
+ const { actions, order, payloads } = createHarness(ticker("AAPL"));
73
+
74
+ await actions.addTickerMembershipFromWorkflow({
75
+ portfolioId: "main",
76
+ ticker: "AAPL",
77
+ });
78
+
79
+ expect(order).toEqual(["save:start", "save:end", "dispatch", "event"]);
80
+ expect(payloads).toEqual([{ symbol: "AAPL", portfolioId: "main" }]);
81
+ });
82
+
83
+ test("emits portfolio membership when a resumed AP finds it already durable", async () => {
84
+ const { actions, order, payloads } = createHarness(ticker("AAPL", ["main"]));
85
+
86
+ await actions.addTickerMembershipFromWorkflow({
87
+ portfolioId: "main",
88
+ ticker: "AAPL",
89
+ });
90
+
91
+ expect(order).toEqual(["event"]);
92
+ expect(payloads).toEqual([{ symbol: "AAPL", portfolioId: "main" }]);
93
+ });
94
+ });