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
@@ -8,7 +8,9 @@ import type { TickerSearchCandidate } from "../../../../tickers/search";
8
8
  import type { AssistRowHandlers } from "../../assist/model";
9
9
  import { matchPrefix, type Command } from "../../commands/registry";
10
10
  import type { ResultItem } from "../../list/model";
11
+ import type { CommandBarCategoryPriorities } from "../../view-model";
11
12
  import type { CommandBarRoute } from "../../workflow/types";
13
+ import { normalizeCommandTickerSearchText } from "../ticker-search/results";
12
14
  import { useTickerSearchRouteResults } from "../ticker-search/route";
13
15
  import { buildRootResultModel, type RootResultModel } from "./results";
14
16
  import { useRootProviderSearch } from "./provider-search";
@@ -33,7 +35,6 @@ interface UseCommandBarRootRuntimeOptions {
33
35
  availableCommands: Command[];
34
36
  buildLayoutItems(query: string, options?: { confirmDangerousActions?: boolean }): ResultItem[];
35
37
  buildPaneSettingItems(paneId: string | null, query: string): ResultItem[];
36
- buildPluginItems(query: string): ResultItem[];
37
38
  buildTickerSearchResultItems(candidates: TickerSearchCandidate[], query: string): ResultItem[];
38
39
  buildWindowModeItems(arg: string): ResultItem[];
39
40
  createPaneTemplateItem(template: PaneTemplateDef, options?: {
@@ -55,7 +56,7 @@ interface UseCommandBarRootRuntimeOptions {
55
56
  localTickerSearchResultItems(query?: string, options?: { category?: string; limit?: number }): ResultItem[];
56
57
  nativeListScrollRef: RefObject<ScrollBoxRenderable | null>;
57
58
  nonShortcutPaneTemplateItems(filterQuery?: string): ResultItem[];
58
- openModeRoute(screen: "ticker-search" | "plugins" | "layout", initialQuery?: string): void;
59
+ openModeRoute(screen: "ticker-search" | "layout", initialQuery?: string): void;
59
60
  paneShortcutItems(options?: {
60
61
  filterQuery?: string;
61
62
  createOptions?: PaneTemplateCreateOptions;
@@ -63,7 +64,9 @@ interface UseCommandBarRootRuntimeOptions {
63
64
  }): ResultItem[];
64
65
  pluginCommandItems(): ResultItem[];
65
66
  pluginCommandResultItems(command: CommandDef, shortcutArg: string): ResultItem[];
66
- articleResultItems?: ResultItem[];
67
+ providerResultItems?: ResultItem[];
68
+ providerCategoryPriorities?: CommandBarCategoryPriorities;
69
+ providerSearching?: boolean;
67
70
  readTickerSearchCache(
68
71
  query: string,
69
72
  brokerId?: string | null,
@@ -97,7 +100,6 @@ export function useCommandBarRootRuntime({
97
100
  availableCommands,
98
101
  buildLayoutItems,
99
102
  buildPaneSettingItems,
100
- buildPluginItems,
101
103
  buildTickerSearchResultItems,
102
104
  buildWindowModeItems,
103
105
  createPaneTemplateItem,
@@ -115,7 +117,9 @@ export function useCommandBarRootRuntime({
115
117
  paneShortcutItems,
116
118
  pluginCommandItems,
117
119
  pluginCommandResultItems,
118
- articleResultItems = [],
120
+ providerResultItems = [],
121
+ providerCategoryPriorities,
122
+ providerSearching = false,
119
123
  readTickerSearchCache,
120
124
  rootModeKind,
121
125
  rootQuery,
@@ -173,7 +177,6 @@ export function useCommandBarRootRuntime({
173
177
  availableCommands,
174
178
  buildLayoutItems,
175
179
  buildPaneSettingItems,
176
- buildPluginItems,
177
180
  buildWindowModeItems,
178
181
  createPaneTemplateItem,
179
182
  createPluginCommandItem,
@@ -189,7 +192,7 @@ export function useCommandBarRootRuntime({
189
192
  pluginCommandResultItems,
190
193
  rootQuery,
191
194
  rootShortcutIntent,
192
- articleResultItems,
195
+ providerResultItems,
193
196
  runDirectCommand,
194
197
  runSecurityDescriptionShortcut,
195
198
  state,
@@ -202,7 +205,6 @@ export function useCommandBarRootRuntime({
202
205
  availableCommands,
203
206
  buildLayoutItems,
204
207
  buildPaneSettingItems,
205
- buildPluginItems,
206
208
  buildWindowModeItems,
207
209
  createPaneTemplateItem,
208
210
  createPluginCommandItem,
@@ -218,7 +220,7 @@ export function useCommandBarRootRuntime({
218
220
  pluginCommandResultItems,
219
221
  rootQuery,
220
222
  rootShortcutIntent,
221
- articleResultItems,
223
+ providerResultItems,
222
224
  runDirectCommand,
223
225
  runSecurityDescriptionShortcut,
224
226
  state,
@@ -228,27 +230,44 @@ export function useCommandBarRootRuntime({
228
230
  const rootSecurityDescriptionArg = activeMatch?.command.id === "security-description" && activeMatch.arg.length >= 1
229
231
  ? activeMatch.arg
230
232
  : null;
231
- const rootTickerSearchArg = rootSecurityDescriptionArg;
233
+ // Free text that no prefix claims also goes to symbol search, so "nvidia"
234
+ // finds NVDA without the backtick. Skipped when a local row already carries
235
+ // that exact name, since an "Exact Match" symbol would otherwise outrank it.
236
+ const rootPlainTickerSearchArg = useMemo(() => {
237
+ if (currentRoute || activeMatch || rootShortcutIntent.kind !== "none") return null;
238
+ const trimmed = rootQuery.trim();
239
+ if (trimmed.length < 2) return null;
240
+ const normalizedQuery = normalizeCommandTickerSearchText(trimmed);
241
+ const hasExactLocalRow = rootResultModel.items.some((item) => (
242
+ item.kind !== "ticker"
243
+ && item.kind !== "search"
244
+ && normalizeCommandTickerSearchText(item.label) === normalizedQuery
245
+ ));
246
+ return hasExactLocalRow ? null : trimmed;
247
+ }, [activeMatch, currentRoute, rootQuery, rootResultModel.items, rootShortcutIntent.kind]);
248
+ const rootTickerSearchArg = rootSecurityDescriptionArg ?? rootPlainTickerSearchArg;
232
249
 
233
250
  const {
234
251
  activeRootProviderResultsKey,
235
252
  orderedRootResults,
236
- rootSearching,
253
+ rootSearching: tickerSearching,
237
254
  rootSectionOrder,
238
255
  } = useRootProviderSearch({
239
256
  activeCollectionId,
240
257
  buildTickerSearchResultItems,
258
+ categoryPriorities: providerCategoryPriorities,
241
259
  currentRoute,
242
260
  dataProvider,
243
261
  localTickerSearchResultItems,
244
262
  portfolios: state.config.portfolios,
245
263
  readTickerSearchCache,
246
- rootPlainTickerSearchArg: null,
264
+ rootPlainTickerSearchArg,
247
265
  rootResultItems: rootResultModel.items,
248
266
  rootTickerSearchArg,
249
267
  tickers: state.tickers,
250
268
  writeTickerSearchCache,
251
269
  });
270
+ const rootSearching = tickerSearching || providerSearching;
252
271
 
253
272
  useLayoutEffect(() => {
254
273
  if (currentRoute) return;
@@ -263,23 +282,21 @@ export function useCommandBarRootRuntime({
263
282
  previousRootSelectionContextRef.current?.query !== rootQuery
264
283
  || previousRootSelectionContextRef.current?.mode !== rootModeKind;
265
284
  previousRootSelectionContextRef.current = { query: rootQuery, mode: rootModeKind };
266
- // Filtering the plugin list is not meant to move the row being toggled.
267
- const keepSelectionAcrossQueries = activeMatch?.command.id === "plugins";
268
- if (selectionContextChanged && !keepSelectionAcrossQueries) {
285
+ if (selectionContextChanged) {
269
286
  rootSelectionNavigatedRef.current = false;
270
287
  }
271
288
 
272
289
  setRootSelectedIdx((current) => {
273
- if (rootSelectionNavigatedRef.current || keepSelectionAcrossQueries) {
274
- // The user picked this row: rows arriving above it — an AI answer, a
275
- // provider result — may renumber it, never take the highlight from it.
290
+ if (rootSelectionNavigatedRef.current) {
291
+ // The user picked this row. Async sections append below it, but an
292
+ // exact symbol match still lands above, so the row is tracked by id
293
+ // rather than by index.
276
294
  const selectedId = previousResultIds[current];
277
295
  const shiftedIdx = selectedId ? resultIds.indexOf(selectedId) : -1;
278
296
  if (shiftedIdx >= 0) return shiftedIdx;
279
297
  return clampSelectedIdx(current, resultIds.length);
280
298
  }
281
- // Untouched, the selection follows the best row on offer, which is what
282
- // an AI answer becomes the moment it lands at the top of the list.
299
+ // Untouched, the selection follows the best row on offer.
283
300
  const defaultIdx = orderedRootResults.findIndex(isDefaultSelectable);
284
301
  return clampSelectedIdx(Math.max(rootResultModel.initialIdx, defaultIdx), resultIds.length);
285
302
  });
@@ -0,0 +1,224 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { act, useState } from "react";
3
+ import { testRender } from "../../../../renderers/opentui/test-utils";
4
+ import type { CommandBarResultDef, CommandBarSearchProvider } from "../../../../types/plugin";
5
+ import type { ResultItem } from "../../list/model";
6
+ import { toProviderResultItem, useCommandBarSearchProviders } from "./search-providers";
7
+
8
+ let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
9
+ let setHarnessQuery: ((query: string) => void) | null = null;
10
+ let latestItems: ResultItem[] = [];
11
+ let latestSearching = false;
12
+
13
+ function ProvidersHarness({
14
+ providers,
15
+ initialQuery,
16
+ }: {
17
+ providers: CommandBarSearchProvider[];
18
+ initialQuery: string;
19
+ }) {
20
+ const [query, setQuery] = useState(initialQuery);
21
+ setHarnessQuery = setQuery;
22
+ const { providerResultItems, providerSearching } = useCommandBarSearchProviders({
23
+ providers,
24
+ query,
25
+ enabled: true,
26
+ context: { activeTicker: null, activeCollectionId: null },
27
+ onExecuted: () => {},
28
+ });
29
+ latestItems = providerResultItems;
30
+ latestSearching = providerSearching;
31
+ return <text>{String(providerResultItems.length)}</text>;
32
+ }
33
+
34
+ async function renderHarness(providers: CommandBarSearchProvider[], initialQuery = ""): Promise<void> {
35
+ testSetup = await testRender(
36
+ <ProvidersHarness providers={providers} initialQuery={initialQuery} />,
37
+ { width: 20, height: 1 },
38
+ );
39
+ await act(async () => {
40
+ await testSetup!.renderOnce();
41
+ });
42
+ }
43
+
44
+ async function typeQuery(query: string): Promise<void> {
45
+ await act(async () => {
46
+ setHarnessQuery?.(query);
47
+ await testSetup!.renderOnce();
48
+ });
49
+ }
50
+
51
+ async function settle(ms = 20): Promise<void> {
52
+ await act(async () => {
53
+ await Bun.sleep(ms);
54
+ await testSetup!.renderOnce();
55
+ });
56
+ }
57
+
58
+ function makeResult(id: string): CommandBarResultDef {
59
+ return { id, label: id, execute: () => {} };
60
+ }
61
+
62
+ afterEach(async () => {
63
+ if (testSetup) {
64
+ await act(async () => {
65
+ testSetup!.renderer.destroy();
66
+ });
67
+ testSetup = undefined;
68
+ }
69
+ setHarnessQuery = null;
70
+ latestItems = [];
71
+ latestSearching = false;
72
+ });
73
+
74
+ describe("command bar search providers", () => {
75
+ test("shows only the answer to the query still in the bar, and aborts the rest", async () => {
76
+ const asked: string[] = [];
77
+ const aborted: string[] = [];
78
+ const provider: CommandBarSearchProvider = {
79
+ id: "docs",
80
+ category: "Documents",
81
+ debounceMs: 0,
82
+ async provide(query, _context, signal) {
83
+ asked.push(query);
84
+ signal.addEventListener("abort", () => aborted.push(query));
85
+ // The slower first request would otherwise land on top of the newer one.
86
+ await Bun.sleep(query === "margin" ? 40 : 0);
87
+ return [makeResult(query)];
88
+ },
89
+ };
90
+
91
+ await renderHarness([provider], "margin");
92
+ await settle(5);
93
+ await typeQuery("margin pressure");
94
+ await settle(60);
95
+
96
+ expect(asked).toEqual(["margin", "margin pressure"]);
97
+ expect(aborted).toEqual(["margin"]);
98
+ expect(latestItems.map((item) => item.label)).toEqual(["margin pressure"]);
99
+ });
100
+
101
+ test("does not ask again for a query it has already answered", async () => {
102
+ let calls = 0;
103
+ const provider: CommandBarSearchProvider = {
104
+ id: "docs",
105
+ category: "Documents",
106
+ debounceMs: 0,
107
+ async provide(query) {
108
+ calls += 1;
109
+ return [makeResult(query)];
110
+ },
111
+ };
112
+
113
+ await renderHarness([provider], "margin");
114
+ await settle();
115
+ await typeQuery("margin pressure");
116
+ await settle();
117
+ await typeQuery("margin");
118
+ await settle();
119
+
120
+ expect(calls).toBe(2);
121
+ expect(latestItems.map((item) => item.label)).toEqual(["margin"]);
122
+ });
123
+
124
+ test("contributes nothing when it fails, and stops reporting itself as loading", async () => {
125
+ const provider: CommandBarSearchProvider = {
126
+ id: "docs",
127
+ category: "Documents",
128
+ debounceMs: 0,
129
+ async provide() {
130
+ throw new Error("402");
131
+ },
132
+ };
133
+
134
+ await renderHarness([provider], "margin");
135
+ await settle();
136
+
137
+ expect(latestItems).toEqual([]);
138
+ expect(latestSearching).toBe(false);
139
+ });
140
+
141
+ test("stays quiet below the provider's minimum query length", async () => {
142
+ let calls = 0;
143
+ const provider: CommandBarSearchProvider = {
144
+ id: "docs",
145
+ category: "Documents",
146
+ debounceMs: 0,
147
+ minQueryLength: 4,
148
+ async provide(query) {
149
+ calls += 1;
150
+ return [makeResult(query)];
151
+ },
152
+ };
153
+
154
+ await renderHarness([provider], "mar");
155
+ await settle();
156
+ expect(calls).toBe(0);
157
+ expect(latestSearching).toBe(false);
158
+
159
+ await typeQuery("marg");
160
+ await settle();
161
+ expect(calls).toBe(1);
162
+ });
163
+
164
+ test("orders sections by provider priority", async () => {
165
+ const provider = (id: string, priority: number): CommandBarSearchProvider => ({
166
+ id,
167
+ category: id,
168
+ priority,
169
+ debounceMs: 0,
170
+ async provide() {
171
+ return [makeResult(id)];
172
+ },
173
+ });
174
+
175
+ await renderHarness([provider("late", 50), provider("early", 10)], "margin");
176
+ await settle();
177
+
178
+ expect(latestItems.map((item) => item.category)).toEqual(["early", "late"]);
179
+ });
180
+ });
181
+
182
+ describe("toProviderResultItem", () => {
183
+ test("namespaces the row, keeps its lines, and closes the bar after it runs", async () => {
184
+ let executed = 0;
185
+ let closed = 0;
186
+ const item = toProviderResultItem(
187
+ { id: "docs", category: "Documents", provide: async () => [] },
188
+ {
189
+ id: "hit-1",
190
+ label: "Q3 call",
191
+ detail: "CALL · Feb 02, 26",
192
+ right: "AAPL",
193
+ keywords: ["transcript"],
194
+ lines: [{ segments: [{ text: "margin ", emphasis: "muted" }, { text: "pressure", emphasis: "match" }] }],
195
+ execute: () => { executed += 1; },
196
+ },
197
+ () => { closed += 1; },
198
+ );
199
+
200
+ expect(item.id).toBe("search-provider:docs:hit-1");
201
+ expect(item.category).toBe("Documents");
202
+ expect(item.right).toBe("AAPL");
203
+ expect(item.lines?.[0]?.segments[1]?.emphasis).toBe("match");
204
+ expect(item.searchText).toContain("transcript");
205
+
206
+ await item.action();
207
+ expect(executed).toBe(1);
208
+ expect(closed).toBe(1);
209
+ });
210
+
211
+ test("does not run or close for a disabled row", async () => {
212
+ let executed = 0;
213
+ let closed = 0;
214
+ const item = toProviderResultItem(
215
+ { id: "docs", category: "Documents", provide: async () => [] },
216
+ { id: "gate", label: "Upgrade", disabled: true, execute: () => { executed += 1; } },
217
+ () => { closed += 1; },
218
+ );
219
+
220
+ await item.action();
221
+ expect(executed).toBe(0);
222
+ expect(closed).toBe(0);
223
+ });
224
+ });
@@ -0,0 +1,205 @@
1
+ import { useEffect, useMemo, useRef, useState } from "react";
2
+ import type { PluginRegistry } from "../../../../plugins/registry";
3
+ import type {
4
+ CommandBarResultDef,
5
+ CommandBarSearchContext,
6
+ CommandBarSearchProvider,
7
+ } from "../../../../types/plugin";
8
+ import type { ResultItem } from "../../list/model";
9
+
10
+ const DEFAULT_MIN_QUERY_LENGTH = 3;
11
+ const DEFAULT_DEBOUNCE_MS = 300;
12
+
13
+ export function getAvailableCommandBarSearchProviders(
14
+ pluginRegistry: Pick<PluginRegistry, "commandBarSearchProviders" | "getCommandBarSearchProviderPluginId">,
15
+ disabledPlugins: readonly string[],
16
+ ): CommandBarSearchProvider[] {
17
+ const disabledPluginIds = new Set(disabledPlugins);
18
+ return [...pluginRegistry.commandBarSearchProviders.values()].filter((provider) => {
19
+ const pluginId = pluginRegistry.getCommandBarSearchProviderPluginId(provider.id);
20
+ return !pluginId || !disabledPluginIds.has(pluginId);
21
+ });
22
+ }
23
+
24
+ /**
25
+ * Turns one provider row into a list row. The command bar owns the row model, so
26
+ * a provider never gets to decide selection, kind, or what closing means.
27
+ */
28
+ export function toProviderResultItem(
29
+ provider: CommandBarSearchProvider,
30
+ result: CommandBarResultDef,
31
+ onExecuted: () => void,
32
+ ): ResultItem {
33
+ return {
34
+ id: `search-provider:${provider.id}:${result.id}`,
35
+ label: result.label,
36
+ detail: result.detail ?? "",
37
+ category: result.category ?? provider.category,
38
+ kind: "action",
39
+ lines: result.lines,
40
+ badge: result.badge,
41
+ right: result.right,
42
+ searchText: [result.label, result.detail ?? "", ...(result.keywords ?? [])].join(" "),
43
+ disabled: result.disabled,
44
+ action: async () => {
45
+ if (result.disabled) return;
46
+ await result.execute();
47
+ onExecuted();
48
+ },
49
+ };
50
+ }
51
+
52
+ interface ProviderResults {
53
+ /** The exact query these rows answer; anything else on screen is stale. */
54
+ query: string;
55
+ items: ResultItem[];
56
+ }
57
+
58
+ interface UseCommandBarSearchProvidersOptions {
59
+ providers: readonly CommandBarSearchProvider[];
60
+ query: string;
61
+ /** False while a route is open or a prefix already claimed the query. */
62
+ enabled: boolean;
63
+ context: CommandBarSearchContext;
64
+ onExecuted: () => void;
65
+ }
66
+
67
+ /**
68
+ * Runs plugin search providers for the root list. Each provider debounces and
69
+ * aborts on its own, answers are memoized for the life of the bar, and rows are
70
+ * only ever added to the static list: a provider that fails, hangs, or answers
71
+ * late leaves what the command bar already resolved exactly as it was.
72
+ */
73
+ export function useCommandBarSearchProviders({
74
+ providers,
75
+ query,
76
+ enabled,
77
+ context,
78
+ onExecuted,
79
+ }: UseCommandBarSearchProvidersOptions): {
80
+ providerResultItems: ResultItem[];
81
+ providerSearching: boolean;
82
+ } {
83
+ const [resultsByProvider, setResultsByProvider] = useState<Record<string, ProviderResults>>({});
84
+ const [loadingProviderIds, setLoadingProviderIds] = useState<readonly string[]>([]);
85
+ const cacheRef = useRef(new Map<string, CommandBarResultDef[]>());
86
+ const timersRef = useRef(new Map<string, ReturnType<typeof setTimeout>>());
87
+ const abortsRef = useRef(new Map<string, AbortController>());
88
+ /**
89
+ * Query each provider has already been asked for. Rendered state lags a tick
90
+ * behind, so reading it instead would let a re-render start a second request
91
+ * for text that is already in flight.
92
+ */
93
+ const handledQueriesRef = useRef(new Map<string, string>());
94
+ /** Read at request time only: selecting a ticker is not a new question. */
95
+ const contextRef = useRef(context);
96
+ contextRef.current = context;
97
+ const onExecutedRef = useRef(onExecuted);
98
+ onExecutedRef.current = onExecuted;
99
+
100
+ useEffect(() => {
101
+ const trimmed = query.trim();
102
+ const cancel = (providerId: string) => {
103
+ const timer = timersRef.current.get(providerId);
104
+ if (timer) {
105
+ clearTimeout(timer);
106
+ timersRef.current.delete(providerId);
107
+ }
108
+ abortsRef.current.get(providerId)?.abort();
109
+ abortsRef.current.delete(providerId);
110
+ handledQueriesRef.current.delete(providerId);
111
+ };
112
+ const setLoading = (providerId: string, loading: boolean) => {
113
+ setLoadingProviderIds((current) => {
114
+ const isLoading = current.includes(providerId);
115
+ if (loading === isLoading) return current;
116
+ return loading ? [...current, providerId] : current.filter((id) => id !== providerId);
117
+ });
118
+ };
119
+ const publish = (provider: CommandBarSearchProvider, forQuery: string, results: CommandBarResultDef[]) => {
120
+ setResultsByProvider((current) => ({
121
+ ...current,
122
+ [provider.id]: {
123
+ query: forQuery,
124
+ items: results.map((result) => toProviderResultItem(
125
+ provider,
126
+ result,
127
+ () => onExecutedRef.current(),
128
+ )),
129
+ },
130
+ }));
131
+ };
132
+
133
+ const activeProviderIds = new Set(providers.map((provider) => provider.id));
134
+ for (const providerId of [...handledQueriesRef.current.keys()]) {
135
+ if (!activeProviderIds.has(providerId)) cancel(providerId);
136
+ }
137
+
138
+ for (const provider of providers) {
139
+ const minLength = provider.minQueryLength ?? DEFAULT_MIN_QUERY_LENGTH;
140
+ if (!enabled || trimmed.length < minLength) {
141
+ cancel(provider.id);
142
+ setLoading(provider.id, false);
143
+ continue;
144
+ }
145
+ if (handledQueriesRef.current.get(provider.id) === trimmed) continue;
146
+
147
+ cancel(provider.id);
148
+ handledQueriesRef.current.set(provider.id, trimmed);
149
+
150
+ const cached = cacheRef.current.get(`${provider.id}\n${trimmed}`);
151
+ if (cached) {
152
+ setLoading(provider.id, false);
153
+ publish(provider, trimmed, cached);
154
+ continue;
155
+ }
156
+
157
+ setLoading(provider.id, true);
158
+ timersRef.current.set(provider.id, setTimeout(() => {
159
+ timersRef.current.delete(provider.id);
160
+ const controller = new AbortController();
161
+ abortsRef.current.set(provider.id, controller);
162
+ void (async () => {
163
+ try {
164
+ const results = await provider.provide(trimmed, contextRef.current, controller.signal);
165
+ if (controller.signal.aborted) return;
166
+ cacheRef.current.set(`${provider.id}\n${trimmed}`, results);
167
+ publish(provider, trimmed, results);
168
+ } catch {
169
+ // A provider is an extra, never a precondition: its failure leaves
170
+ // the rows the command bar resolved locally untouched.
171
+ if (controller.signal.aborted) return;
172
+ publish(provider, trimmed, []);
173
+ } finally {
174
+ if (abortsRef.current.get(provider.id) === controller) {
175
+ abortsRef.current.delete(provider.id);
176
+ setLoading(provider.id, false);
177
+ }
178
+ }
179
+ })();
180
+ }, provider.debounceMs ?? DEFAULT_DEBOUNCE_MS));
181
+ }
182
+ }, [enabled, providers, query]);
183
+
184
+ useEffect(() => () => {
185
+ for (const timer of timersRef.current.values()) clearTimeout(timer);
186
+ timersRef.current.clear();
187
+ for (const controller of abortsRef.current.values()) controller.abort();
188
+ abortsRef.current.clear();
189
+ }, []);
190
+
191
+ const providerResultItems = useMemo(() => {
192
+ const trimmed = query.trim();
193
+ if (!enabled) return [];
194
+ return [...providers]
195
+ .sort((left, right) => (left.priority ?? 0) - (right.priority ?? 0))
196
+ .flatMap((provider) => {
197
+ const results = resultsByProvider[provider.id];
198
+ return results?.query === trimmed ? results.items : [];
199
+ });
200
+ }, [enabled, providers, query, resultsByProvider]);
201
+
202
+ const providerSearching = enabled && providers.some((provider) => loadingProviderIds.includes(provider.id));
203
+
204
+ return { providerResultItems, providerSearching };
205
+ }
@@ -32,7 +32,7 @@ interface RootSelectionCommandOptions {
32
32
  getAvailablePaneShortcutTemplates: (query: string) => PaneTemplateDef[];
33
33
  getAvailablePluginCommands: () => CommandDef[];
34
34
  openModeRoute: (
35
- screen: "ticker-search" | "plugins" | "layout",
35
+ screen: "ticker-search" | "layout",
36
36
  initialQuery?: string,
37
37
  payload?: Record<string, unknown>,
38
38
  ) => void;
@@ -185,17 +185,6 @@ export function buildImmediateRootSelection(options: RootSelectionCommandOptions
185
185
  };
186
186
  }
187
187
 
188
- if (match.command.id === "plugins") {
189
- return {
190
- id: "plugins-route",
191
- label: "Manage Plugins",
192
- detail: "Toggle optional plugins without leaving the command bar",
193
- category: "Plugins",
194
- kind: "command",
195
- action: () => options.openModeRoute("plugins", match.arg),
196
- };
197
- }
198
-
199
188
  if (match.command.id === "layout") {
200
189
  return {
201
190
  id: "layout-route",
@@ -40,11 +40,11 @@ const paneTemplates: PaneTemplateDef[] = [
40
40
  },
41
41
  tickerTemplate("earnings-estimates-pane", "EE"),
42
42
  {
43
- id: "provider-search-pane",
44
- paneId: "provider-search-results",
45
- label: "Provider Search",
46
- description: "Search provider instruments",
47
- shortcut: { prefix: "SRCH", argPlaceholder: "query", argKind: "text" },
43
+ id: "research-search-pane",
44
+ paneId: "research-search",
45
+ label: "Research Search",
46
+ description: "Full-text document search",
47
+ shortcut: { prefix: "SRCH", argPlaceholder: "query", argKind: "text", argOptional: true },
48
48
  },
49
49
  {
50
50
  id: "cds-pane",
@@ -125,19 +125,19 @@ describe("ticker data root shortcuts", () => {
125
125
  if (intent.kind === "none") throw new Error("Expected shortcut intent");
126
126
  expect(intent.source).toBe("pane-template");
127
127
  if (intent.source === "pane-template") {
128
- expect(intent.template.id).toBe("provider-search-pane");
128
+ expect(intent.template.id).toBe("research-search-pane");
129
129
  expect(intent.argText).toBe("apple inc");
130
130
  expect(intent.completionQuery).toBeNull();
131
131
  }
132
132
  });
133
133
 
134
- test("SRCH without a query remains a provider search shortcut", () => {
134
+ test("SRCH without a query remains a document search shortcut", () => {
135
135
  const intent = parse("SRCH");
136
136
  expect(intent.kind).toBe("partial");
137
137
  if (intent.kind === "none") throw new Error("Expected shortcut intent");
138
138
  expect(intent.source).toBe("pane-template");
139
139
  if (intent.source === "pane-template") {
140
- expect(intent.template.id).toBe("provider-search-pane");
140
+ expect(intent.template.id).toBe("research-search-pane");
141
141
  expect(intent.argText).toBe("");
142
142
  }
143
143
  });