gloomberb 0.11.2 → 0.12.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 (531) hide show
  1. package/README.md +7 -4
  2. package/README.zh-CN.md +4 -2
  3. package/package.json +28 -1
  4. package/src/api-client/auth.ts +10 -0
  5. package/src/api-client/data.ts +108 -1
  6. package/src/api-client/index.test.ts +127 -0
  7. package/src/api-client/index.ts +134 -1
  8. package/src/api-client/normalizers.ts +39 -0
  9. package/src/api-client/paths.ts +85 -1
  10. package/src/api-client/types.ts +220 -0
  11. package/src/app/pane-runtime/index.tsx +15 -12
  12. package/src/app/pane-runtime/layout-placement.test.ts +27 -0
  13. package/src/app/pane-runtime/layout-placement.ts +13 -0
  14. package/src/app/pane-runtime/plugin-bindings.ts +62 -0
  15. package/src/app.tsx +60 -18
  16. package/src/architecture/browser-entry-graph.test.ts +96 -0
  17. package/src/brokers/profile-form.test.ts +25 -27
  18. package/src/brokers/test-broker.ts +68 -0
  19. package/src/capabilities/chart-series.ts +3 -2
  20. package/src/cli/commands/market.ts +2 -2
  21. package/src/cli/commands/plugins.ts +39 -11
  22. package/src/cli/desktop-pane-shot.ts +285 -16
  23. package/src/cli/index.ts +1 -1
  24. package/src/cli/pane-functions/capabilities.ts +61 -31
  25. package/src/cli/pane-functions/catalog.ts +16 -4
  26. package/src/cli/pane-functions/discovery.ts +1 -0
  27. package/src/cli/pane-functions/headless.test.ts +247 -0
  28. package/src/cli/pane-functions/headless.ts +423 -0
  29. package/src/cli/pane-functions/index.test.ts +9 -0
  30. package/src/cli/pane-functions/index.ts +6 -2
  31. package/src/cli/pane-functions/options.ts +27 -2
  32. package/src/cli/pane-functions/report.ts +4 -0
  33. package/src/cli/pane-functions/resolver.ts +26 -8
  34. package/src/cli/pane-functions/screenshot.test.ts +76 -0
  35. package/src/cli/pane-functions/screenshot.ts +236 -20
  36. package/src/cli/registry.ts +0 -22
  37. package/src/cli/restore-plugins.ts +34 -0
  38. package/src/cli/result.ts +7 -10
  39. package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
  40. package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
  41. package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
  42. package/src/components/chart/composite/composite-chart.test.tsx +68 -20
  43. package/src/components/chart/composite/composite-chart.tsx +301 -204
  44. package/src/components/chart/composite/format.ts +8 -2
  45. package/src/components/chart/composite/interactions.test.ts +68 -102
  46. package/src/components/chart/composite/interactions.ts +214 -264
  47. package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
  48. package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
  49. package/src/components/chart/composite/scene.ts +57 -3
  50. package/src/components/chart/composite/text-renderer.ts +3 -2
  51. package/src/components/chart/composite/time-scale.ts +138 -57
  52. package/src/components/chart/composite/types.ts +25 -1
  53. package/src/components/chart/core/data.ts +9 -13
  54. package/src/components/chart/core/date-window.ts +7 -1
  55. package/src/components/chart/core/pointer.ts +3 -0
  56. package/src/components/chart/core/types.ts +0 -45
  57. package/src/components/chart/native/chart-rasterizer.ts +0 -1
  58. package/src/components/chart/native/kitty/index.test.ts +0 -188
  59. package/src/components/chart/native/raster/crosshair.ts +6 -5
  60. package/src/components/chart/native/raster/primitives.ts +0 -23
  61. package/src/components/chart/native/renderer-selection.ts +81 -31
  62. package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
  63. package/src/components/chart/static/chart-surface.tsx +150 -0
  64. package/src/components/chart/static/index.ts +8 -0
  65. package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
  66. package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
  67. package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
  68. package/src/components/chart/static/series.ts +49 -0
  69. package/src/components/command-bar/action-runtime.ts +1 -3
  70. package/src/components/command-bar/assist/model.test.ts +17 -2
  71. package/src/components/command-bar/assist/model.ts +40 -9
  72. package/src/components/command-bar/commands/direct/index.ts +1 -1
  73. package/src/components/command-bar/commands/direct/runtime.ts +1 -1
  74. package/src/components/command-bar/commands/registry.ts +0 -11
  75. package/src/components/command-bar/helpers.test.ts +2 -2
  76. package/src/components/command-bar/helpers.ts +2 -6
  77. package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
  78. package/src/components/command-bar/list/badge.test.ts +54 -0
  79. package/src/components/command-bar/list/badge.ts +62 -0
  80. package/src/components/command-bar/list/model.test.ts +103 -0
  81. package/src/components/command-bar/list/model.ts +61 -4
  82. package/src/components/command-bar/list/view.test.tsx +113 -0
  83. package/src/components/command-bar/list/view.tsx +147 -131
  84. package/src/components/command-bar/pane-templates/workflow.ts +1 -1
  85. package/src/components/command-bar/panel/body.tsx +51 -65
  86. package/src/components/command-bar/panel/index.tsx +48 -13
  87. package/src/components/command-bar/panel/layout.test.ts +139 -0
  88. package/src/components/command-bar/panel/layout.ts +65 -28
  89. package/src/components/command-bar/panel/native-surface.ts +24 -0
  90. package/src/components/command-bar/panel/prompt-binding.ts +54 -0
  91. package/src/components/command-bar/panel/runtime.ts +6 -3
  92. package/src/components/command-bar/panel/state.ts +41 -7
  93. package/src/components/command-bar/panel/types.ts +7 -1
  94. package/src/components/command-bar/routes/root/provider-search.ts +34 -15
  95. package/src/components/command-bar/routes/root/results.test.ts +161 -0
  96. package/src/components/command-bar/routes/root/results.ts +30 -28
  97. package/src/components/command-bar/routes/root/runtime.ts +37 -20
  98. package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
  99. package/src/components/command-bar/routes/root/search-providers.ts +205 -0
  100. package/src/components/command-bar/routes/root/selection.ts +1 -12
  101. package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
  102. package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
  103. package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
  104. package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
  105. package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
  106. package/src/components/command-bar/routing/actions.ts +1 -81
  107. package/src/components/command-bar/routing/launch-request.ts +1 -1
  108. package/src/components/command-bar/routing/list-state.ts +6 -21
  109. package/src/components/command-bar/selection-runtime.ts +1 -1
  110. package/src/components/command-bar/surface/assist.test.tsx +14 -10
  111. package/src/components/command-bar/surface/environment.ts +8 -1
  112. package/src/components/command-bar/surface/index.test.tsx +84 -68
  113. package/src/components/command-bar/surface/index.tsx +33 -36
  114. package/src/components/command-bar/surface/test-harness.tsx +15 -3
  115. package/src/components/command-bar/theme-picker.tsx +43 -15
  116. package/src/components/command-bar/view-model.test.ts +53 -8
  117. package/src/components/command-bar/view-model.ts +43 -14
  118. package/src/components/command-bar/workflow/tickers.ts +1 -0
  119. package/src/components/command-bar/workflow/types.ts +1 -1
  120. package/src/components/data-table/export.test.ts +21 -0
  121. package/src/components/data-table/export.ts +21 -0
  122. package/src/components/data-table/view.test.tsx +45 -6
  123. package/src/components/data-table/view.tsx +55 -9
  124. package/src/components/index.ts +10 -2
  125. package/src/components/input-search-bar.test.tsx +36 -2
  126. package/src/components/input-search-bar.tsx +18 -1
  127. package/src/components/layout/header.test.tsx +57 -10
  128. package/src/components/layout/header.tsx +292 -149
  129. package/src/components/layout/market-summary.test.ts +81 -0
  130. package/src/components/layout/market-summary.ts +109 -0
  131. package/src/components/layout/shell/chrome.ts +96 -1
  132. package/src/components/layout/shell/index.tsx +11 -3
  133. package/src/components/layout/shell/menu.ts +9 -0
  134. package/src/components/layout/shell/pane/actions.ts +29 -0
  135. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  136. package/src/components/layout/shell/shortcuts.ts +3 -0
  137. package/src/components/layout/status-bar.test.tsx +9 -8
  138. package/src/components/layout/status-bar.tsx +85 -54
  139. package/src/components/onboarding/onboarding-frame.tsx +2 -26
  140. package/src/components/price-sparkline/index.ts +61 -34
  141. package/src/components/price-sparkline/view.tsx +1 -1
  142. package/src/components/sign-in-gate.test.tsx +128 -0
  143. package/src/components/sign-in-gate.tsx +149 -0
  144. package/src/components/speedometer-gauge/desktop.tsx +2 -12
  145. package/src/components/ticker/badge/list.tsx +17 -3
  146. package/src/components/ticker/list-table-view.tsx +6 -3
  147. package/src/components/ui/data-table/index.tsx +22 -4
  148. package/src/components/ui/data-table/opentui/index.tsx +165 -120
  149. package/src/components/ui/frame.tsx +28 -0
  150. package/src/components/ui/index.ts +3 -0
  151. package/src/components/ui/native-select.tsx +20 -10
  152. package/src/components/ui/select-button.tsx +138 -0
  153. package/src/components/ui/status.tsx +2 -2
  154. package/src/components/ui/table-layout.ts +1 -1
  155. package/src/components/use-double-click-activation.ts +7 -3
  156. package/src/core/app-services.ts +3 -3
  157. package/src/data/config/chart-settings.ts +4 -4
  158. package/src/data/config/layout.ts +12 -6
  159. package/src/data/config/store/index.test.ts +97 -0
  160. package/src/data/config/store/migrations.ts +41 -0
  161. package/src/data/config/store/normalize.ts +5 -3
  162. package/src/i18n/es.ts +6 -2
  163. package/src/i18n/ja.ts +6 -2
  164. package/src/i18n/ko.ts +6 -2
  165. package/src/i18n/zh-cn.ts +6 -2
  166. package/src/i18n/zh-tw.ts +6 -2
  167. package/src/layout-marketplace/gallery-terminal.tsx +126 -96
  168. package/src/layout-marketplace/gallery.test.tsx +65 -1
  169. package/src/layout-marketplace/pane-imagery.ts +1 -0
  170. package/src/layout-marketplace/payload.test.ts +17 -0
  171. package/src/layout-marketplace/payload.ts +40 -5
  172. package/src/market-data/coordinator/index.ts +3 -2
  173. package/src/market-data/coordinator/quotes.ts +173 -66
  174. package/src/market-data/coordinator/subscriptions.test.ts +215 -37
  175. package/src/pane-settings.ts +1 -1
  176. package/src/plugins/builtin/analytics/pane-model.ts +1 -1
  177. package/src/plugins/builtin/analytics/view.tsx +1 -1
  178. package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
  179. package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
  180. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
  181. package/src/plugins/builtin/chart-composer/cli-options.ts +12 -9
  182. package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
  183. package/src/plugins/builtin/chart-composer/settings.ts +2 -13
  184. package/src/plugins/builtin/chat/content/composer.tsx +8 -4
  185. package/src/plugins/builtin/cloud/auth-dialog.tsx +14 -228
  186. package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
  187. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  188. package/src/plugins/builtin/composite-plugins.ts +16 -1
  189. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  190. package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
  191. package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
  192. package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
  193. package/src/plugins/builtin/credit-conditions/index.tsx +2 -21
  194. package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
  195. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  196. package/src/plugins/builtin/earnings/index.tsx +1 -0
  197. package/src/plugins/builtin/earnings-calls/data.ts +223 -0
  198. package/src/plugins/builtin/earnings-calls/format.ts +49 -0
  199. package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
  200. package/src/plugins/builtin/earnings-calls/pane.tsx +756 -0
  201. package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
  202. package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
  203. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +334 -0
  204. package/src/plugins/builtin/econ/detail-view.tsx +1 -2
  205. package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
  206. package/src/plugins/builtin/econ/index.tsx +3 -1
  207. package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
  208. package/src/plugins/builtin/econ-statistics/client.ts +92 -0
  209. package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
  210. package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
  211. package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
  212. package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
  213. package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
  214. package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
  215. package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
  216. package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
  217. package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
  218. package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
  219. package/src/plugins/builtin/econ-statistics/view.ts +90 -0
  220. package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
  221. package/src/plugins/builtin/fear-greed/data.ts +6 -1
  222. package/src/plugins/builtin/futures/index.tsx +1 -0
  223. package/src/plugins/builtin/fx-matrix/index.tsx +1 -0
  224. package/src/plugins/builtin/help/index.tsx +4 -0
  225. package/src/plugins/builtin/holders/index.tsx +1 -0
  226. package/src/plugins/builtin/insider/index.tsx +1 -0
  227. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -0
  228. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  229. package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
  230. package/src/plugins/builtin/market-halts/index.tsx +1 -0
  231. package/src/plugins/builtin/market-movers/index.tsx +1 -0
  232. package/src/plugins/builtin/market-valuation/align.ts +122 -0
  233. package/src/plugins/builtin/market-valuation/cache.ts +32 -0
  234. package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
  235. package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
  236. package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
  237. package/src/plugins/builtin/market-valuation/client.ts +116 -0
  238. package/src/plugins/builtin/market-valuation/defs.ts +251 -0
  239. package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
  240. package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
  241. package/src/plugins/builtin/market-valuation/headless.ts +170 -0
  242. package/src/plugins/builtin/market-valuation/index.tsx +64 -0
  243. package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
  244. package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
  245. package/src/plugins/builtin/market-valuation/model.ts +8 -0
  246. package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
  247. package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
  248. package/src/plugins/builtin/market-valuation/series.ts +12 -0
  249. package/src/plugins/builtin/market-valuation/settings.ts +38 -0
  250. package/src/plugins/builtin/market-valuation/sources.ts +130 -0
  251. package/src/plugins/builtin/market-valuation/trend.ts +89 -0
  252. package/src/plugins/builtin/market-valuation/view.ts +137 -0
  253. package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
  254. package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
  255. package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
  256. package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
  257. package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
  258. package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
  259. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
  260. package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
  261. package/src/plugins/builtin/news/wire/index.tsx +31 -44
  262. package/src/plugins/builtin/options/analytics.test.ts +83 -0
  263. package/src/plugins/builtin/options/analytics.ts +110 -0
  264. package/src/plugins/builtin/options/index.tsx +18 -2
  265. package/src/plugins/builtin/options/table.ts +121 -51
  266. package/src/plugins/builtin/options/types.ts +22 -15
  267. package/src/plugins/builtin/options/view.test.tsx +53 -1
  268. package/src/plugins/builtin/options/view.tsx +92 -41
  269. package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
  270. package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
  271. package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
  272. package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
  273. package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
  274. package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
  275. package/src/plugins/builtin/plugin-module.test.ts +2 -4
  276. package/src/plugins/builtin/plugin-module.ts +9 -8
  277. package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
  278. package/src/plugins/builtin/research/index.tsx +4 -0
  279. package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
  280. package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
  281. package/src/plugins/builtin/research-search/data.ts +109 -0
  282. package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
  283. package/src/plugins/builtin/research-search/document-view.tsx +254 -0
  284. package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
  285. package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
  286. package/src/plugins/builtin/research-search/index.tsx +85 -0
  287. package/src/plugins/builtin/research-search/model.test.ts +40 -0
  288. package/src/plugins/builtin/research-search/model.ts +291 -0
  289. package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
  290. package/src/plugins/builtin/research-search/pane.tsx +678 -0
  291. package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
  292. package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
  293. package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
  294. package/src/plugins/builtin/research-search/snippet.ts +134 -0
  295. package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
  296. package/src/plugins/builtin/scanner/flow-model.ts +29 -1
  297. package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
  298. package/src/plugins/builtin/scanner/index.ts +2 -0
  299. package/src/plugins/builtin/sec/index.tsx +1 -0
  300. package/src/plugins/builtin/sectors/index.tsx +1 -0
  301. package/src/plugins/builtin/shared/series-cache.ts +93 -0
  302. package/src/plugins/builtin/short-interest/index.tsx +1 -0
  303. package/src/plugins/builtin/short-interest/pane.tsx +1 -1
  304. package/src/plugins/builtin/thirteenf/index.tsx +1 -0
  305. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +3 -5
  306. package/src/plugins/builtin/ticker-detail/index.tsx +2 -14
  307. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  308. package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
  309. package/src/plugins/builtin/volatility/index.tsx +1 -1
  310. package/src/plugins/builtin/world-indices/index.tsx +1 -0
  311. package/src/plugins/builtin/world-venue-map/index.ts +27 -0
  312. package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
  313. package/src/plugins/builtin/world-venue-map/map.tsx +481 -0
  314. package/src/plugins/builtin/world-venue-map/model.test.ts +85 -0
  315. package/src/plugins/builtin/world-venue-map/model.ts +268 -0
  316. package/src/plugins/builtin/world-venue-map/pane.tsx +300 -0
  317. package/src/plugins/builtin/world-venue-map/world-outlines.ts +2 -0
  318. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  319. package/src/plugins/bundle.test.ts +109 -0
  320. package/src/plugins/bundle.ts +139 -0
  321. package/src/plugins/catalog-backend.ts +6 -44
  322. package/src/plugins/catalog-browser.test.ts +1 -0
  323. package/src/plugins/catalog-browser.ts +12 -0
  324. package/src/plugins/catalog-manifest.test.ts +43 -0
  325. package/src/plugins/catalog-manifest.ts +92 -0
  326. package/src/plugins/catalog-ui.ts +20 -4
  327. package/src/plugins/catalog.ts +9 -2
  328. package/src/plugins/current-target.ts +28 -0
  329. package/src/plugins/host-contract.ts +37 -0
  330. package/src/plugins/host-link.ts +158 -0
  331. package/src/plugins/host-modules.ts +61 -0
  332. package/src/plugins/loader.ts +41 -22
  333. package/src/plugins/ownership.ts +1 -0
  334. package/src/plugins/prediction-markets/index.tsx +1 -0
  335. package/src/plugins/registry/context.ts +3 -0
  336. package/src/plugins/registry/contributions.ts +27 -0
  337. package/src/plugins/registry/index.test.ts +25 -0
  338. package/src/plugins/registry/index.ts +8 -0
  339. package/src/plugins/registry/pane-settings.test.ts +53 -0
  340. package/src/plugins/registry/pane-settings.ts +29 -3
  341. package/src/plugins/seed.test.ts +83 -0
  342. package/src/plugins/seed.ts +80 -0
  343. package/src/public/broker.ts +31 -0
  344. package/src/public/market-data.ts +9 -0
  345. package/src/public/pane-hooks.ts +57 -0
  346. package/src/public/public-api.test.ts +285 -0
  347. package/src/public/react.ts +62 -0
  348. package/src/public/test-support.ts +23 -0
  349. package/src/public/time-series.ts +8 -0
  350. package/src/public/utils.ts +77 -0
  351. package/src/remote/layout-helpers.ts +6 -8
  352. package/src/remote/resources.ts +2 -0
  353. package/src/remote/schema.ts +1 -0
  354. package/src/remote/semantic-tree.test.tsx +105 -0
  355. package/src/remote/semantic-tree.tsx +53 -35
  356. package/src/renderers/browser/main.tsx +6 -0
  357. package/src/renderers/browser/ui-host.tsx +11 -0
  358. package/src/renderers/cloudflare/worker.test.ts +19 -0
  359. package/src/renderers/cloudflare/worker.ts +1 -1
  360. package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -2
  361. package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
  362. package/src/renderers/electrobun/bun/index.ts +6 -2
  363. package/src/renderers/electrobun/shared/protocol.ts +25 -0
  364. package/src/renderers/electrobun/view/build-assets.ts +0 -1
  365. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +51 -1
  366. package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
  367. package/src/renderers/electrobun/view/external-plugins.ts +63 -0
  368. package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
  369. package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
  370. package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
  371. package/src/renderers/electrobun/view/host/mouse.ts +16 -2
  372. package/src/renderers/electrobun/view/main.tsx +27 -2
  373. package/src/renderers/electrobun/view/styles.css +5 -0
  374. package/src/renderers/electrobun/view/toast-host.tsx +18 -0
  375. package/src/renderers/electrobun/view/ui-host.tsx +3 -0
  376. package/src/renderers/opentui/chart-surface.tsx +5 -1
  377. package/src/renderers/opentui/host.tsx +20 -2
  378. package/src/renderers/opentui/interaction-performance.test.ts +79 -0
  379. package/src/renderers/opentui/interaction-performance.ts +188 -0
  380. package/src/renderers/opentui/start.tsx +26 -1
  381. package/src/renderers/opentui/test-utils.tsx +13 -0
  382. package/src/renderers/opentui/toast-host.tsx +26 -2
  383. package/src/renderers/opentui/ui-host.tsx +1 -0
  384. package/src/sources/provider-router/search.test.ts +191 -0
  385. package/src/sources/provider-router/search.ts +92 -39
  386. package/src/state/app/context/index.tsx +2 -0
  387. package/src/state/app/context/selector.test.tsx +33 -1
  388. package/src/state/hooks/quote-streaming.test.tsx +93 -0
  389. package/src/state/hooks/quote-streaming.ts +106 -30
  390. package/src/state/pane-table-export-registry.ts +50 -0
  391. package/src/theme/color-utils.ts +1 -1
  392. package/src/theme/colors.ts +50 -0
  393. package/src/theme/themes.test.ts +15 -2
  394. package/src/theme/themes.ts +19 -1
  395. package/src/tickers/search/index.test.ts +26 -0
  396. package/src/tickers/search/index.ts +42 -17
  397. package/src/tickers/search/ranking.ts +14 -0
  398. package/src/time-series/chart-data.test.ts +216 -0
  399. package/src/time-series/range.ts +12 -0
  400. package/src/time-series/resolution.test.ts +25 -26
  401. package/src/time-series/resolution.ts +44 -25
  402. package/src/time-series/resolve.ts +7 -0
  403. package/src/time-series/spec.ts +5 -5
  404. package/src/time-series/types.ts +3 -1
  405. package/src/time-series/use-chart-resolution.ts +5 -0
  406. package/src/types/config.ts +123 -3
  407. package/src/types/data-provider.ts +11 -0
  408. package/src/types/headless.ts +161 -0
  409. package/src/types/plugin.ts +104 -20
  410. package/src/ui/host.tsx +19 -1
  411. package/src/ui/index.tsx +1 -0
  412. package/src/ui/toast.tsx +8 -4
  413. package/src/utils/csv.test.ts +13 -0
  414. package/src/utils/csv.ts +37 -0
  415. package/src/utils/format.ts +35 -0
  416. package/src/utils/fuzzy-search.test.ts +94 -0
  417. package/src/utils/fuzzy-search.ts +81 -22
  418. package/src/utils/save-text-file.test.ts +24 -0
  419. package/src/utils/save-text-file.ts +36 -0
  420. package/src/utils/shortcut-labels.ts +14 -0
  421. package/src/components/chart/core/axis-format.ts +0 -157
  422. package/src/components/chart/core/index.test.ts +0 -1075
  423. package/src/components/chart/core/projection.ts +0 -357
  424. package/src/components/chart/core/render-utils.ts +0 -4
  425. package/src/components/chart/core/renderer.ts +0 -244
  426. package/src/components/chart/core/scene.ts +0 -180
  427. package/src/components/chart/core/time-axis.ts +0 -330
  428. package/src/components/chart/cursor-motion.ts +0 -47
  429. package/src/components/chart/indicators/types.ts +0 -26
  430. package/src/components/chart/market-session.ts +0 -176
  431. package/src/components/chart/multi-line/renderer.test.ts +0 -83
  432. package/src/components/chart/multi-line/renderer.ts +0 -161
  433. package/src/components/chart/multi-line/scene.ts +0 -188
  434. package/src/components/chart/native/raster/layout.ts +0 -129
  435. package/src/components/chart/native/raster/price-chart.ts +0 -244
  436. package/src/components/chart/rendering/chart-draw.ts +0 -270
  437. package/src/components/chart/rendering/geometry.ts +0 -95
  438. package/src/components/chart/rendering/pixel-buffer.ts +0 -267
  439. package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
  440. package/src/components/chart/scatter-chart-renderer.ts +0 -223
  441. package/src/components/chart/static/chart/surface.tsx +0 -358
  442. package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
  443. package/src/components/command-bar/routes/root/article-results.ts +0 -55
  444. package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
  445. package/src/plugins/builtin/news/wire/article-search.ts +0 -140
  446. package/src/plugins/builtin/substack/api/auth.ts +0 -185
  447. package/src/plugins/builtin/substack/api/cache.ts +0 -82
  448. package/src/plugins/builtin/substack/api/loaders.ts +0 -170
  449. package/src/plugins/builtin/substack/api/store.ts +0 -186
  450. package/src/plugins/builtin/substack/api/types.ts +0 -42
  451. package/src/plugins/builtin/substack/api.test.ts +0 -298
  452. package/src/plugins/builtin/substack/article-detail.tsx +0 -397
  453. package/src/plugins/builtin/substack/article-stack.tsx +0 -133
  454. package/src/plugins/builtin/substack/content.ts +0 -478
  455. package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
  456. package/src/plugins/builtin/substack/index.tsx +0 -49
  457. package/src/plugins/builtin/substack/login-view.tsx +0 -126
  458. package/src/plugins/builtin/substack/model.test.ts +0 -155
  459. package/src/plugins/builtin/substack/normalize.ts +0 -343
  460. package/src/plugins/builtin/substack/pane-footer.ts +0 -52
  461. package/src/plugins/builtin/substack/pane-state.ts +0 -170
  462. package/src/plugins/builtin/substack/pane.tsx +0 -547
  463. package/src/plugins/builtin/substack/read-state.ts +0 -30
  464. package/src/plugins/builtin/substack/table.ts +0 -111
  465. package/src/plugins/builtin/substack/types.ts +0 -71
  466. package/src/plugins/builtin/substack/utils.ts +0 -131
  467. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
  468. package/src/plugins/ibkr/account-cache.test.ts +0 -102
  469. package/src/plugins/ibkr/account-cache.ts +0 -24
  470. package/src/plugins/ibkr/broker-adapter.ts +0 -286
  471. package/src/plugins/ibkr/config.test.ts +0 -79
  472. package/src/plugins/ibkr/config.ts +0 -250
  473. package/src/plugins/ibkr/dialogs.tsx +0 -40
  474. package/src/plugins/ibkr/flex/index.test.ts +0 -264
  475. package/src/plugins/ibkr/flex/index.ts +0 -367
  476. package/src/plugins/ibkr/flex/statement-client.ts +0 -235
  477. package/src/plugins/ibkr/fundamental-parser.ts +0 -149
  478. package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
  479. package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
  480. package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
  481. package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
  482. package/src/plugins/ibkr/gateway/connection.ts +0 -152
  483. package/src/plugins/ibkr/gateway/contracts.ts +0 -132
  484. package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
  485. package/src/plugins/ibkr/gateway/helpers.ts +0 -33
  486. package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
  487. package/src/plugins/ibkr/gateway/history.ts +0 -120
  488. package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
  489. package/src/plugins/ibkr/gateway/market-data.ts +0 -345
  490. package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
  491. package/src/plugins/ibkr/gateway/orders.ts +0 -63
  492. package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
  493. package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
  494. package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
  495. package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
  496. package/src/plugins/ibkr/gateway/selection.ts +0 -51
  497. package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
  498. package/src/plugins/ibkr/gateway/service/index.ts +0 -429
  499. package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
  500. package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
  501. package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
  502. package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
  503. package/src/plugins/ibkr/gateway/service/native.ts +0 -543
  504. package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
  505. package/src/plugins/ibkr/gateway/types.ts +0 -27
  506. package/src/plugins/ibkr/index.test.tsx +0 -162
  507. package/src/plugins/ibkr/index.tsx +0 -159
  508. package/src/plugins/ibkr/instance-selection.test.ts +0 -120
  509. package/src/plugins/ibkr/instance-selection.ts +0 -36
  510. package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
  511. package/src/plugins/ibkr/portfolio-performance.ts +0 -129
  512. package/src/plugins/ibkr/trade/badge.tsx +0 -28
  513. package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
  514. package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
  515. package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
  516. package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
  517. package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
  518. package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
  519. package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
  520. package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
  521. package/src/plugins/ibkr/trade/tab/model.ts +0 -159
  522. package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
  523. package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
  524. package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
  525. package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
  526. package/src/plugins/ibkr/trade/utils.test.ts +0 -175
  527. package/src/plugins/ibkr/trade/utils.ts +0 -144
  528. package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
  529. package/src/plugins/ibkr/trading/pane.tsx +0 -332
  530. package/src/plugins/ibkr/trading/state.test.ts +0 -109
  531. package/src/plugins/ibkr/trading/state.ts +0 -335
@@ -1,12 +1,15 @@
1
- import { useEffect, useMemo, type Dispatch, type SetStateAction } from "react";
1
+ import { useEffect, useMemo, useRef, type Dispatch, type SetStateAction } from "react";
2
2
  import {
3
3
  buildListRows,
4
4
  buildNativeListRows,
5
+ getListRowsHeight,
6
+ resolveSelectedScrollLine,
5
7
  type ListScreenState,
6
8
  } from "../list/model";
7
9
  import {
8
10
  resolveCommandBarPanelLayout,
9
11
  } from "./layout";
12
+ import { matchThemeOptions } from "../theme-picker";
10
13
  import type { CommandBarRoute } from "../workflow/types";
11
14
 
12
15
  type RefLike<T> = { current: T };
@@ -16,12 +19,15 @@ interface UseCommandBarPanelStateOptions {
16
19
  cellWidthPx: number;
17
20
  currentRoute: CommandBarRoute | null;
18
21
  nativePaneChrome: boolean;
22
+ nativeWindowChrome?: boolean;
23
+ rootShortcutFeedback: string | null;
19
24
  routeListState: ListScreenState | null;
20
25
  setRootSelectedIdx: Dispatch<SetStateAction<number>>;
21
26
  showCustomMultiSelectPicker: boolean;
22
27
  termHeight: number;
23
28
  termWidth: number;
24
29
  themePickerActive: boolean;
30
+ themePickerFilter: string;
25
31
  titleBarOverlay: boolean | undefined;
26
32
  updateTopRoute: (updater: (route: CommandBarRoute) => CommandBarRoute) => void;
27
33
  visibleListStateRef: RefLike<ListScreenState | null>;
@@ -32,12 +38,15 @@ export function useCommandBarPanelState({
32
38
  cellWidthPx,
33
39
  currentRoute,
34
40
  nativePaneChrome,
41
+ nativeWindowChrome,
42
+ rootShortcutFeedback,
35
43
  routeListState,
36
44
  setRootSelectedIdx,
37
45
  showCustomMultiSelectPicker,
38
46
  termHeight,
39
47
  termWidth,
40
48
  themePickerActive,
49
+ themePickerFilter,
41
50
  titleBarOverlay,
42
51
  updateTopRoute,
43
52
  visibleListStateRef,
@@ -84,41 +93,67 @@ export function useCommandBarPanelState({
84
93
  );
85
94
  const listRowIndexByGlobalIndex = useMemo(() => {
86
95
  const indexByGlobalIndex = new Map<number, number>();
87
- listRows.forEach((row, index) => {
96
+ nativeListRows.forEach((row, index) => {
88
97
  if (row.kind === "item") {
89
98
  indexByGlobalIndex.set(row.globalIdx, index);
90
99
  }
91
100
  });
92
101
  return indexByGlobalIndex;
93
- }, [listRows]);
102
+ }, [nativeListRows]);
103
+
104
+ const hasRootFeedback = visibleListState?.kind === "root" && rootShortcutFeedback !== null;
105
+ // One row per matching theme, or the one row the empty message occupies.
106
+ const themePickerRowCount = useMemo(
107
+ () => (themePickerActive ? Math.max(1, matchThemeOptions(themePickerFilter).length) : 0),
108
+ [themePickerActive, themePickerFilter],
109
+ );
94
110
  const panelLayout = useMemo(() => resolveCommandBarPanelLayout({
95
111
  cellHeightPx,
96
112
  cellWidthPx,
97
113
  currentRoute,
114
+ hasRootFeedback,
98
115
  hasVisibleListState,
99
- nativeListRowCount: nativeListRows.length,
116
+ nativeListRowCount: getListRowsHeight(nativeListRows),
100
117
  nativePaneChrome,
118
+ nativeWindowChrome,
101
119
  showCustomMultiSelectPicker,
102
120
  termHeight,
103
121
  termWidth,
104
122
  themePickerActive,
123
+ themePickerRowCount,
105
124
  titleBarOverlay,
106
125
  }), [
107
126
  cellHeightPx,
108
127
  cellWidthPx,
109
128
  currentRoute,
129
+ hasRootFeedback,
110
130
  hasVisibleListState,
111
- nativeListRows.length,
131
+ nativeListRows,
112
132
  nativePaneChrome,
133
+ nativeWindowChrome,
113
134
  showCustomMultiSelectPicker,
114
135
  termHeight,
115
136
  termWidth,
116
137
  themePickerActive,
138
+ themePickerRowCount,
117
139
  titleBarOverlay,
118
140
  ]);
119
141
  const selectedListRowIndex = visibleListState
120
142
  ? listRowIndexByGlobalIndex.get(visibleListState.selectedIdx) ?? -1
121
143
  : -1;
144
+ const selectedIdx = visibleListState?.selectedIdx ?? 0;
145
+ const selectionMoveRef = useRef({ selectedIdx, movedDown: false });
146
+ if (selectionMoveRef.current.selectedIdx !== selectedIdx) {
147
+ selectionMoveRef.current = {
148
+ selectedIdx,
149
+ movedDown: selectedIdx > selectionMoveRef.current.selectedIdx,
150
+ };
151
+ }
152
+ const selectedScrollLineIndex = resolveSelectedScrollLine(
153
+ nativeListRows,
154
+ selectedListRowIndex,
155
+ selectionMoveRef.current.movedDown,
156
+ );
122
157
  const bodySlotKey = showCustomMultiSelectPicker
123
158
  ? "picker:field-multi-select"
124
159
  : themePickerActive
@@ -129,10 +164,9 @@ export function useCommandBarPanelState({
129
164
 
130
165
  return {
131
166
  bodySlotKey,
132
- listRows,
133
167
  nativeListRows,
134
168
  panelLayout,
135
- selectedScrollRowIndex: selectedListRowIndex,
169
+ selectedScrollRowIndex: selectedScrollLineIndex,
136
170
  visibleListState,
137
171
  };
138
172
  }
@@ -36,6 +36,8 @@ export interface CommandBarPanelProps {
36
36
  contentPadding: number;
37
37
  currentRoute: CommandBarRoute | null;
38
38
  getWorkflowInputRef: (fieldId: string) => RefObject<InputRenderable | TextareaRenderable | null>;
39
+ /** The sheet height already reserves the chrome row, so the body must render it whenever this is set. */
40
+ hasChromeRow: boolean;
39
41
  labelWidth: number;
40
42
  listBodyHeight: number;
41
43
  nativeListRows: CommandBarListRow[];
@@ -65,8 +67,12 @@ export interface CommandBarPanelProps {
65
67
  panelBounds: LayoutBounds;
66
68
  queryDisplayWidth: number;
67
69
  rootGhostSuffix: string | null;
68
- rootQueryLength: number;
69
70
  rootShortcutFeedback: string | null;
71
+ /**
72
+ * Line the scroll box has to reach for the selected row to be fully visible,
73
+ * or -1 when nothing is selected. Rows are not all one line tall, so this is a
74
+ * line offset rather than a row index.
75
+ */
70
76
  selectedScrollRowIndex: number;
71
77
  termHeight: number;
72
78
  termWidth: number;
@@ -9,12 +9,13 @@ import {
9
9
  QUICK_LOOK_TICKER_SEARCH_OPTIONS,
10
10
  } from "../ticker-search/results";
11
11
  import { orderListResults, type ResultItem } from "../../list/model";
12
- import type { CommandBarSectionOrder } from "../../view-model";
12
+ import type { CommandBarCategoryPriorities, CommandBarSectionOrder } from "../../view-model";
13
13
  import type { CommandBarRoute } from "../../workflow/types";
14
14
 
15
15
  export function useRootProviderSearch(options: {
16
16
  activeCollectionId: string | null;
17
17
  buildTickerSearchResultItems: (candidates: TickerSearchCandidate[], query: string) => ResultItem[];
18
+ categoryPriorities?: CommandBarCategoryPriorities;
18
19
  currentRoute: CommandBarRoute | null;
19
20
  dataProvider: DataProvider;
20
21
  localTickerSearchResultItems: (query?: string, options?: { category?: string; limit?: number }) => ResultItem[];
@@ -43,6 +44,7 @@ export function useRootProviderSearch(options: {
43
44
  const {
44
45
  activeCollectionId,
45
46
  buildTickerSearchResultItems,
47
+ categoryPriorities,
46
48
  currentRoute,
47
49
  dataProvider,
48
50
  localTickerSearchResultItems,
@@ -83,10 +85,13 @@ export function useRootProviderSearch(options: {
83
85
  }
84
86
 
85
87
  const searchQuery = rootTickerSearchArg;
86
- if (rootSearchTimerRef.current) clearTimeout(rootSearchTimerRef.current);
88
+ // A re-run for the same query (a quote refresh changed `tickers`, say) must
89
+ // leave the pending debounce alone, or the search is lost and the spinner
90
+ // never clears. Only a new query cancels it.
87
91
  if (rootLastSearchedQueryRef.current === searchQuery) {
88
92
  return;
89
93
  }
94
+ if (rootSearchTimerRef.current) clearTimeout(rootSearchTimerRef.current);
90
95
 
91
96
  rootLastSearchedQueryRef.current = searchQuery;
92
97
  setRootSearching(true);
@@ -105,15 +110,30 @@ export function useRootProviderSearch(options: {
105
110
  const requestId = ++rootSearchRequestIdRef.current;
106
111
  rootSearchTimerRef.current = setTimeout(async () => {
107
112
  try {
113
+ const publish = (candidates: TickerSearchCandidate[]) => {
114
+ setRootProviderResults(mergeTickerSearchResultItems(
115
+ searchQuery,
116
+ buildTickerSearchResultItems(candidates, searchQuery),
117
+ localTickerSearchResultItems(searchQuery, { limit: 6 }),
118
+ ));
119
+ setRootProviderResultsQuery(searchQuery);
120
+ };
108
121
  const combined = await searchTickerCandidates({
109
122
  query: searchQuery,
110
123
  tickers,
111
124
  dataProvider,
112
125
  searchContext: {
113
126
  preferBroker: true,
127
+ interactive: true,
114
128
  brokerId: activeSearchPortfolio?.brokerId,
115
129
  brokerInstanceId: activeSearchPortfolio?.brokerInstanceId,
116
130
  },
131
+ // A broker that answers after the cloud upgrades the rows in place
132
+ // rather than being dropped because the list was already drawn.
133
+ onPartial: (candidates) => {
134
+ if (requestId !== rootSearchRequestIdRef.current) return;
135
+ publish(candidates);
136
+ },
117
137
  ...QUICK_LOOK_TICKER_SEARCH_OPTIONS,
118
138
  });
119
139
  if (requestId !== rootSearchRequestIdRef.current) return;
@@ -123,12 +143,7 @@ export function useRootProviderSearch(options: {
123
143
  activeSearchPortfolio?.brokerId,
124
144
  activeSearchPortfolio?.brokerInstanceId,
125
145
  );
126
- setRootProviderResults(mergeTickerSearchResultItems(
127
- searchQuery,
128
- buildTickerSearchResultItems(combined, searchQuery),
129
- localTickerSearchResultItems(searchQuery, { limit: 6 }),
130
- ));
131
- setRootProviderResultsQuery(searchQuery);
146
+ publish(combined);
132
147
  } catch {
133
148
  if (requestId !== rootSearchRequestIdRef.current) return;
134
149
  setRootProviderResults([{
@@ -146,10 +161,6 @@ export function useRootProviderSearch(options: {
146
161
  }
147
162
  }
148
163
  }, 200);
149
-
150
- return () => {
151
- if (rootSearchTimerRef.current) clearTimeout(rootSearchTimerRef.current);
152
- };
153
164
  }, [
154
165
  activeCollectionId,
155
166
  buildTickerSearchResultItems,
@@ -163,6 +174,10 @@ export function useRootProviderSearch(options: {
163
174
  writeTickerSearchCache,
164
175
  ]);
165
176
 
177
+ useEffect(() => () => {
178
+ if (rootSearchTimerRef.current) clearTimeout(rootSearchTimerRef.current);
179
+ }, []);
180
+
166
181
  const rootResults = useMemo(() => {
167
182
  if (rootTickerSearchArg && rootProviderResultsQuery === rootTickerSearchArg && rootProviderResults) {
168
183
  if (rootPlainTickerSearchArg) {
@@ -184,16 +199,20 @@ export function useRootProviderSearch(options: {
184
199
  ? "ranked"
185
200
  : "default";
186
201
  const orderedRootResults = useMemo(
187
- () => orderListResults(rootResults, { sectionOrder: rootSectionOrder }),
188
- [rootResults, rootSectionOrder],
202
+ () => orderListResults(rootResults, { sectionOrder: rootSectionOrder, categoryPriorities }),
203
+ [categoryPriorities, rootResults, rootSectionOrder],
189
204
  );
205
+ // Only the DES route replaces the list wholesale and so wants the selection
206
+ // reset when the answer lands; a plain query's instruments append below the
207
+ // local matches, and the row the user is on must stay put.
190
208
  const activeRootProviderResultsKey = useMemo(() => {
209
+ if (rootPlainTickerSearchArg) return null;
191
210
  if (!rootTickerSearchArg || rootProviderResultsQuery !== rootTickerSearchArg || !rootProviderResults) return null;
192
211
  return [
193
212
  rootTickerSearchArg,
194
213
  ...rootProviderResults.map((item) => `${item.id}:${item.category}:${item.label}:${item.right || ""}`),
195
214
  ].join("\n");
196
- }, [rootProviderResults, rootProviderResultsQuery, rootTickerSearchArg]);
215
+ }, [rootPlainTickerSearchArg, rootProviderResults, rootProviderResultsQuery, rootTickerSearchArg]);
197
216
 
198
217
  return {
199
218
  activeRootProviderResultsKey,
@@ -0,0 +1,161 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { PaneTemplateDef } from "../../../../types/plugin";
3
+ import { orderListResults, type ResultItem } from "../../list/model";
4
+ import { buildRootResultModel, type RootResultModelOptions } from "./results";
5
+
6
+ function rootOptions(overrides: Partial<RootResultModelOptions>): RootResultModelOptions {
7
+ const empty = () => [] as ResultItem[];
8
+ return {
9
+ activeCollectionId: null,
10
+ activeTickerData: null,
11
+ activeTickerSymbol: null,
12
+ assist: null,
13
+ availableCommands: [],
14
+ buildLayoutItems: empty,
15
+ buildPaneSettingItems: empty,
16
+ buildWindowModeItems: empty,
17
+ createPaneTemplateItem: () => ({
18
+ id: "template",
19
+ label: "Template",
20
+ detail: "",
21
+ category: "Panes",
22
+ kind: "action",
23
+ action: () => {},
24
+ }),
25
+ createPluginCommandItem: () => ({
26
+ id: "plugin-command",
27
+ label: "Plugin Command",
28
+ detail: "",
29
+ category: "Commands",
30
+ kind: "command",
31
+ action: () => {},
32
+ }),
33
+ currentRoute: null,
34
+ executeCollectionCommand: () => {},
35
+ getAvailablePaneShortcutTemplates: () => [],
36
+ hasPaneSettings: () => false,
37
+ localTickerSearchResultItems: empty,
38
+ nonShortcutPaneTemplateItems: empty,
39
+ openModeRoute: () => {},
40
+ paneShortcutItems: empty,
41
+ pluginCommandItems: empty,
42
+ pluginCommandResultItems: empty,
43
+ rootQuery: "",
44
+ rootShortcutIntent: { kind: "none" },
45
+ runDirectCommand: () => {},
46
+ runSecurityDescriptionShortcut: () => {},
47
+ state: {
48
+ config: { watchlists: [], portfolios: [] },
49
+ focusedPaneId: null,
50
+ } as unknown as RootResultModelOptions["state"],
51
+ tickerActionItems: empty,
52
+ ...overrides,
53
+ };
54
+ }
55
+
56
+ const paneRow: ResultItem = {
57
+ id: "pane-template:margin-monitor",
58
+ label: "Margin Monitor",
59
+ detail: "Track account margin",
60
+ category: "Panes",
61
+ kind: "action",
62
+ action: () => {},
63
+ };
64
+
65
+ const documentRow: ResultItem = {
66
+ id: "search-provider:research-search:documents:hit-1",
67
+ label: "Q3 earnings call",
68
+ detail: "CALL",
69
+ category: "Documents",
70
+ kind: "action",
71
+ lines: [{ segments: [{ text: "margin pressure", emphasis: "match" }] }],
72
+ action: () => {},
73
+ };
74
+
75
+ describe("provider rows in the root result model", () => {
76
+ test("land after the local matches instead of displacing them", () => {
77
+ const { items } = buildRootResultModel(rootOptions({
78
+ rootQuery: "margin",
79
+ paneShortcutItems: () => [paneRow],
80
+ providerResultItems: [documentRow],
81
+ }));
82
+
83
+ expect(orderListResults(items).map((item) => item.id)).toEqual([paneRow.id, documentRow.id]);
84
+ });
85
+
86
+ test("leave the local matches alone when a provider contributes nothing", () => {
87
+ const { items } = buildRootResultModel(rootOptions({
88
+ rootQuery: "margin",
89
+ paneShortcutItems: () => [paneRow],
90
+ providerResultItems: [],
91
+ }));
92
+
93
+ expect(items.map((item) => item.id)).toEqual([paneRow.id]);
94
+ });
95
+
96
+ test("stay out of the way once a prefix claims the query", () => {
97
+ const { items } = buildRootResultModel(rootOptions({
98
+ rootQuery: "SEC AAPL",
99
+ providerResultItems: [documentRow],
100
+ rootShortcutIntent: {
101
+ kind: "complete",
102
+ source: "pane-template",
103
+ prefix: "SEC",
104
+ label: "SEC",
105
+ description: "",
106
+ argKind: "ticker",
107
+ argText: "AAPL",
108
+ completionQuery: null,
109
+ template: { id: "sec-pane" } as unknown as PaneTemplateDef,
110
+ },
111
+ }));
112
+
113
+ expect(items.map((item) => item.id)).not.toContain(documentRow.id);
114
+ });
115
+ });
116
+
117
+ describe("assist rows in the root result model", () => {
118
+ const assist = {
119
+ enabled: true,
120
+ auto: true,
121
+ state: { status: "idle" as const },
122
+ onAsk: () => {},
123
+ onSignUp: () => {},
124
+ onRunCandidate: () => {},
125
+ };
126
+
127
+ test("lead the list ahead of provider rows and keep the Thinking placeholder", () => {
128
+ const { items } = buildRootResultModel(rootOptions({
129
+ rootQuery: "margin",
130
+ assist,
131
+ paneShortcutItems: () => [paneRow],
132
+ providerResultItems: [documentRow],
133
+ }));
134
+
135
+ expect(orderListResults(items, { categoryPriorities: new Map([["Documents", 200]]) }).map((item) => item.id))
136
+ .toEqual(["assist:pending", paneRow.id, documentRow.id]);
137
+ });
138
+
139
+ test("the local matcher no longer drags in panes whose keywords scatter the letters", () => {
140
+ const optionsRow: ResultItem = {
141
+ id: "pane-template:options-calculator",
142
+ label: "Options Calculator",
143
+ detail: "Price options with the Black-Scholes model and view the Greeks",
144
+ searchText: "options greeks implied volatility derivatives pricing",
145
+ category: "Panes",
146
+ kind: "action",
147
+ action: () => {},
148
+ };
149
+ const { items } = buildRootResultModel(rootOptions({
150
+ rootQuery: "nvidia",
151
+ paneShortcutItems: () => [optionsRow],
152
+ }));
153
+ expect(items).toEqual([]);
154
+
155
+ const { items: abbreviated } = buildRootResultModel(rootOptions({
156
+ rootQuery: "opt calc",
157
+ paneShortcutItems: () => [optionsRow],
158
+ }));
159
+ expect(abbreviated.map((item) => item.id)).toEqual([optionsRow.id]);
160
+ });
161
+ });
@@ -22,13 +22,6 @@ import { buildRootShortcutItem } from "./shortcut-items";
22
22
 
23
23
  type RootShortcutIntent = ReturnType<typeof parseRootShortcutIntent>;
24
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
-
32
25
  interface PaneTemplateItemOptions {
33
26
  category?: string;
34
27
  createOptions?: PaneTemplateCreateOptions;
@@ -56,7 +49,6 @@ export interface RootResultModelOptions {
56
49
  availableCommands: Command[];
57
50
  buildLayoutItems: (query: string, options?: { confirmDangerousActions?: boolean }) => ResultItem[];
58
51
  buildPaneSettingItems: (paneId: string | null, query: string) => ResultItem[];
59
- buildPluginItems: (query: string) => ResultItem[];
60
52
  buildWindowModeItems: (arg: string) => ResultItem[];
61
53
  createPaneTemplateItem: (template: PaneTemplateDef, options?: PaneTemplateItemOptions) => ResultItem;
62
54
  createPluginCommandItem: (command: CommandDef, options?: { shortcutArg?: string }) => ResultItem;
@@ -69,13 +61,18 @@ export interface RootResultModelOptions {
69
61
  hasPaneSettings: (paneId: string) => boolean;
70
62
  localTickerSearchResultItems: (query?: string, options?: { category?: string; limit?: number }) => ResultItem[];
71
63
  nonShortcutPaneTemplateItems: (filterQuery?: string) => ResultItem[];
72
- openModeRoute: (screen: "ticker-search" | "plugins" | "layout", initialQuery?: string) => void;
64
+ openModeRoute: (screen: "ticker-search" | "layout", initialQuery?: string) => void;
73
65
  paneShortcutItems: (options?: PaneShortcutItemsOptions) => ResultItem[];
74
66
  pluginCommandItems: () => ResultItem[];
75
67
  pluginCommandResultItems: (command: CommandDef, shortcutArg: string) => ResultItem[];
76
68
  rootQuery: string;
77
69
  rootShortcutIntent: RootShortcutIntent;
78
- articleResultItems?: ResultItem[];
70
+ /**
71
+ * Rows from plugin search providers, already ordered by provider priority.
72
+ * Appended after the local matches so a late answer never moves the row the
73
+ * user is aiming at, and only ever adds to what the bar already resolved.
74
+ */
75
+ providerResultItems?: ResultItem[];
79
76
  runDirectCommand: (command: Command, arg: string) => void;
80
77
  runSecurityDescriptionShortcut: (query?: string) => void | Promise<void>;
81
78
  state: AppState;
@@ -109,7 +106,6 @@ export function buildRootResultModel(options: RootResultModelOptions): RootResul
109
106
  availableCommands,
110
107
  buildLayoutItems,
111
108
  buildPaneSettingItems,
112
- buildPluginItems,
113
109
  buildWindowModeItems,
114
110
  createPaneTemplateItem,
115
111
  createPluginCommandItem,
@@ -125,7 +121,7 @@ export function buildRootResultModel(options: RootResultModelOptions): RootResul
125
121
  pluginCommandResultItems,
126
122
  rootQuery,
127
123
  rootShortcutIntent,
128
- articleResultItems = [],
124
+ providerResultItems = [],
129
125
  runDirectCommand,
130
126
  runSecurityDescriptionShortcut,
131
127
  state,
@@ -181,14 +177,8 @@ export function buildRootResultModel(options: RootResultModelOptions): RootResul
181
177
  && rootShortcutIntent.source === "plugin-command"
182
178
  && shortcutItem
183
179
  ) {
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
- }
190
- } else if (match && match.command.id === "plugins") {
191
- items.push(...buildPluginItems(match.arg));
180
+ const dynamicItems = pluginCommandResultItems(rootShortcutIntent.command, rootShortcutIntent.argText);
181
+ items.push(...(dynamicItems.length > 0 ? dynamicItems : [shortcutItem]));
192
182
  } else if (match && match.command.id === "layout") {
193
183
  items.push(...buildLayoutItems(match.arg, { confirmDangerousActions: true }));
194
184
  } else if (match && match.command.id === "window-mode") {
@@ -240,23 +230,35 @@ export function buildRootResultModel(options: RootResultModelOptions): RootResul
240
230
  ...tickerActionItems(),
241
231
  ...pluginCommandItems(),
242
232
  ];
243
- const matchedItems = fuzzyFilter(allItems, rootQuery, (item) => `${item.label} ${item.searchText || ""} ${item.detail} ${item.right || ""}`);
233
+ const matchedItems = fuzzyFilter(
234
+ allItems,
235
+ rootQuery,
236
+ (item) => `${item.label} ${item.searchText || ""} ${item.detail} ${item.right || ""}`,
237
+ (item) => item.label,
238
+ );
244
239
  items.push(...matchedItems);
245
240
  }
246
241
 
247
- if (rootShortcutIntent.kind === "none" || isArticleLookupShortcut(rootShortcutIntent)) {
248
- items.push(...articleResultItems);
242
+ const shortcutClaimedQuery = rootShortcutIntent.kind !== "none";
243
+ // Counted before the provider rows: they arrive whenever the network answers,
244
+ // and an assist offer must not appear and vanish as they land.
245
+ const matchCount = items.length;
246
+ // A resolved prefix means the user is speaking the command language, so
247
+ // free-text providers stay out of the way.
248
+ if (!shortcutClaimedQuery) {
249
+ items.push(...providerResultItems);
249
250
  }
250
251
 
251
- // Built from the local matches, then moved above them: the AI answers the
252
- // question the user typed, so it leads the list. Rows landing here renumber
253
- // everything below, which the root selection effect absorbs by identity.
252
+ // Built from the local matches, then placed above them: the AI turns the
253
+ // typed sentence into commands, so its answer leads the list. Its Thinking
254
+ // placeholder holds the rows from the start, and the root selection effect
255
+ // follows rows by identity when the answer renumbers what sits below.
254
256
  const assistItems = assist
255
257
  && isAssistSectionVisible(
256
258
  assist,
257
259
  rootQuery,
258
- items.length,
259
- rootShortcutIntent.kind !== "none" && !isArticleLookupShortcut(rootShortcutIntent),
260
+ matchCount,
261
+ shortcutClaimedQuery,
260
262
  )
261
263
  ? buildAssistResultItems({ ...assist, query: rootQuery })
262
264
  : [];