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
@@ -81,10 +81,74 @@ function mutablePaneRegistryMap(map: ReadonlyMap<string, unknown>): Map<string,
81
81
  }
82
82
 
83
83
  describe("CommandBar", () => {
84
- test("keeps generic command filtering separate from ticker search", async () => {
84
+ test("runs symbol search for plain text and folds the hits under the local matches", async () => {
85
85
  const searchQueries: string[] = [];
86
86
  testSetup = await testRender(<CommandBarHarness
87
- query="MSFT"
87
+ query="msf"
88
+ dataProvider={makeDataProvider(async (query) => {
89
+ searchQueries.push(query);
90
+ return [
91
+ { providerId: "yahoo", symbol: "MSFT", name: "Microsoft Corp", exchange: "NASDAQ", type: "EQUITY" },
92
+ { providerId: "yahoo", symbol: "MSF", name: "MFS Municipal Fund", exchange: "NYSE", type: "ETF" },
93
+ ];
94
+ })}
95
+ />, {
96
+ width: 80,
97
+ height: 24,
98
+ });
99
+
100
+ await testSetup.renderOnce();
101
+ const frame = await waitForFrameToContain("Instruments");
102
+ expect(searchQueries.length).toBeGreaterThan(0);
103
+ // The listing split of the DES route is collapsed into one section, with
104
+ // one row per symbol, and a symbol the query spells out is promoted.
105
+ expect(frame).not.toContain("Other Listings");
106
+ // Rows lead with a class badge, so the symbol is the second token, not the line start.
107
+ expect(frame.split("\n").filter((line) => /^\s*\S+\s+MSFT\b/.test(line))).toHaveLength(1);
108
+ expect(frame).toContain("Exact Match");
109
+ expect(frame.indexOf("Exact Match")).toBeLessThan(frame.indexOf("Instruments"));
110
+ // Signed out, the AI section is a sign-up offer, which sits under the answers.
111
+ expect(frame.indexOf("Instruments")).toBeLessThan(frame.indexOf("Ask AI"));
112
+ });
113
+
114
+ test("keeps the row the user picked when an exact symbol lands above it", async () => {
115
+ const created: Array<{ templateId: string; options?: PaneTemplateCreateOptions }> = [];
116
+ let releaseSearch = () => {};
117
+ const held = new Promise<void>((resolve) => { releaseSearch = resolve; });
118
+ testSetup = await testRender(<CommandBarHarness
119
+ query="list"
120
+ configurePluginRegistry={(pluginRegistry) => {
121
+ pluginRegistry.createPaneFromTemplateAsyncFn = async (templateId, options) => {
122
+ created.push({ templateId, options });
123
+ };
124
+ }}
125
+ dataProvider={makeDataProvider(async () => {
126
+ await held;
127
+ return [{ providerId: "yahoo", symbol: "LIST", name: "List Corp", exchange: "NYSE", type: "EQUITY" }];
128
+ })}
129
+ />, {
130
+ width: 100,
131
+ height: 20,
132
+ });
133
+
134
+ await testSetup.renderOnce();
135
+ const before = testSetup.captureCharFrame();
136
+ expect(before).not.toContain("Exact Match");
137
+ // Down moves off the first pane match onto the second one.
138
+ await emitKeypress(testSetup, { name: "down" });
139
+ releaseSearch();
140
+ await waitForFrameToContain("Exact Match");
141
+
142
+ // The symbol row renumbered everything under it; Enter still runs the
143
+ // pane the user had picked, not whatever now sits at its old index.
144
+ await emitKeypress(testSetup, { name: "return", sequence: "\r" });
145
+ expect(created).toEqual([{ templateId: "new-watchlist-pane", options: undefined }]);
146
+ });
147
+
148
+ test("keeps symbol search out of a query a prefix claims", async () => {
149
+ const searchQueries: string[] = [];
150
+ testSetup = await testRender(<CommandBarHarness
151
+ query="PF"
88
152
  dataProvider={makeDataProvider(async (query) => {
89
153
  searchQueries.push(query);
90
154
  return [];
@@ -98,9 +162,7 @@ describe("CommandBar", () => {
98
162
  await Bun.sleep(260);
99
163
  await testSetup.renderOnce();
100
164
 
101
- const frame = testSetup.captureCharFrame();
102
- expect(frame).not.toContain("Tickers");
103
- expect(frame).not.toContain("Microsoft Corp.");
165
+ expect(testSetup.captureCharFrame()).toContain("Shortcut: Portfolio");
104
166
  expect(searchQueries).toEqual([]);
105
167
  });
106
168
 
@@ -120,7 +182,7 @@ describe("CommandBar", () => {
120
182
  await testSetup.renderOnce();
121
183
 
122
184
  expect(calls).toHaveLength(1);
123
- expect(testSetup.captureCharFrame()).not.toContain("Commands");
185
+ expect(testSetup.captureCharFrame()).toContain("Search or run a command");
124
186
  });
125
187
 
126
188
  test("shows one account management result when searching profile", async () => {
@@ -269,52 +331,6 @@ describe("CommandBar", () => {
269
331
  expect(frame).toContain("SA");
270
332
  });
271
333
 
272
- test("treats plugin route trigger words as an empty plugin filter", async () => {
273
- testSetup = await testRender(<CommandBarHarness query="PL plugins" />, {
274
- width: 80,
275
- height: 18,
276
- });
277
-
278
- await testSetup.renderOnce();
279
-
280
- const frame = testSetup.captureCharFrame();
281
- expect(frame).toContain("News");
282
- expect(frame).toContain("Notes");
283
- expect(frame).not.toContain("No plugins match");
284
- });
285
-
286
- test("finds grouped plugins through their owned feature terms", async () => {
287
- testSetup = await testRender(<CommandBarHarness
288
- query="PL kelly"
289
- configurePluginRegistry={(pluginRegistry) => {
290
- (pluginRegistry.allPlugins as Map<string, any>).set("portfolio", {
291
- id: "portfolio",
292
- name: "Portfolio",
293
- version: "1.0.0",
294
- description: "Portfolio management",
295
- toggleable: true,
296
- });
297
- (pluginRegistry.panes as Map<string, any>).set("kelly-sizer", {
298
- id: "kelly-sizer",
299
- name: "Position Sizer",
300
- component: () => null,
301
- defaultPosition: "right",
302
- });
303
- const getPluginPaneIds = pluginRegistry.getPluginPaneIds;
304
- pluginRegistry.getPluginPaneIds = (pluginId: string) => (
305
- pluginId === "portfolio" ? ["kelly-sizer"] : getPluginPaneIds(pluginId)
306
- );
307
- }}
308
- />, {
309
- width: 80,
310
- height: 18,
311
- });
312
-
313
- await testSetup.renderOnce();
314
-
315
- expect(testSetup.captureCharFrame()).toContain("Portfolio");
316
- });
317
-
318
334
  test("opens plugin command shortcut arguments in the wizard for confirmation", async () => {
319
335
  const calls: Array<Record<string, string> | undefined> = [];
320
336
 
@@ -543,7 +559,9 @@ describe("CommandBar", () => {
543
559
  const frame = await waitForFrameToContain("Security Description");
544
560
  expectSingleBackControl(frame);
545
561
  expect(frame).toContain("BRK.B");
546
- expect(frame).toContain("Equity NYSE");
562
+ // The class moved into the left badge; the trailing text carries only the venue.
563
+ expect(frame).toMatch(/EQ\s+BRK\.B.*NYSE/);
564
+ expect(frame).not.toContain("Equity NYSE");
547
565
  });
548
566
 
549
567
  test("opens ticker search when activating the Ticker Research pane item without a ticker", async () => {
@@ -557,7 +575,8 @@ describe("CommandBar", () => {
557
575
  const tickerResearchRow = rootFrame
558
576
  .split("\n")
559
577
  .find((line) => line.includes("Ticker Research"));
560
- expect(tickerResearchRow).toMatch(/\bT\s*$/);
578
+ // The shortcut sits in the badge column left of the label, not on the right.
579
+ expect(tickerResearchRow).toMatch(/^\s*T\s+Ticker Research\s*$/);
561
580
 
562
581
  await act(async () => {
563
582
  testSetup!.mockInput.pressEnter();
@@ -579,7 +598,6 @@ describe("CommandBar", () => {
579
598
  await testSetup.renderOnce();
580
599
 
581
600
  let frame = testSetup.captureCharFrame();
582
- expect(frame).toContain("Commands");
583
601
  expect(frame).toContain("DES");
584
602
  expect(frame).toContain("Type a ticker symbol");
585
603
  expect(frame).not.toContain("Back");
@@ -753,14 +771,10 @@ describe("CommandBar", () => {
753
771
  await testSetup.renderOnce();
754
772
  expect(testSetup.captureCharFrame()).toContain("DES AMD");
755
773
 
756
- await act(async () => {
757
- testSetup!.mockInput.pressKey("backspace", { meta: true });
758
- await testSetup!.renderOnce();
759
- });
774
+ await emitKeypress(testSetup, { name: "backspace", meta: true });
760
775
 
761
776
  const frame = testSetup.captureCharFrame();
762
- expect(frame).toContain("Commands");
763
- expect(frame).toContain("Search");
777
+ expect(frame).toContain("Type a ticker symbol");
764
778
  expect(frame).not.toContain("DES AMD");
765
779
  });
766
780
 
@@ -777,7 +791,7 @@ describe("CommandBar", () => {
777
791
  await testSetup!.renderOnce();
778
792
  });
779
793
 
780
- expect(testSetup.captureCharFrame()).not.toContain("Commands");
794
+ expect(testSetup.captureCharFrame()).toContain("Search or run a command");
781
795
  });
782
796
 
783
797
  test("DES MSFT opens an exact ticker directly", async () => {
@@ -886,13 +900,15 @@ describe("CommandBar", () => {
886
900
 
887
901
  await testSetup.renderOnce();
888
902
 
903
+ // Below the sheet; the header row above it hosts the input and is not
904
+ // click-away territory.
889
905
  await act(async () => {
890
- await testSetup!.mockMouse.click(0, 0);
906
+ await testSetup!.mockMouse.click(0, 22);
891
907
  await testSetup!.renderOnce();
892
908
  });
893
909
  await testSetup.renderOnce();
894
910
 
895
- expect(testSetup.captureCharFrame()).not.toContain("Commands");
911
+ expect(testSetup.captureCharFrame()).toContain("Search or run a command");
896
912
  });
897
913
 
898
914
  test("groups ticker search sections and keeps saved matches above looser provider results", async () => {
@@ -917,8 +933,8 @@ describe("CommandBar", () => {
917
933
  const rows = frame.split("\n");
918
934
  const savedHeadings = frame.split("\n").filter((line) => line.trim() === "Saved");
919
935
  const otherListingsHeadings = frame.split("\n").filter((line) => line.trim() === "Other Listings");
920
- const aaplRow = rows.findIndex((line) => line.trimStart().startsWith("AAPL"));
921
- const appRow = rows.findIndex((line) => line.trimStart().startsWith("APP"));
936
+ const aaplRow = rows.findIndex((line) => /^\s*\S+\s+AAPL\b/.test(line));
937
+ const appRow = rows.findIndex((line) => /^\s*\S+\s+APP\b/.test(line));
922
938
  expect(savedHeadings).toHaveLength(1);
923
939
  expect(otherListingsHeadings).toHaveLength(1);
924
940
  expect(aaplRow).toBeGreaterThanOrEqual(0);
@@ -983,8 +999,8 @@ describe("CommandBar", () => {
983
999
  await testSetup.renderOnce();
984
1000
  const frame = await waitForFrameToContain("APLY");
985
1001
  const rows = frame.split("\n");
986
- const aaplRow = rows.findIndex((line) => line.trimStart().startsWith("AAPL"));
987
- const apcRow = rows.findIndex((line) => line.trimStart().startsWith("APC"));
1002
+ const aaplRow = rows.findIndex((line) => /^\s*\S+\s+AAPL\b/.test(line));
1003
+ const apcRow = rows.findIndex((line) => /^\s*\S+\s+APC\b/.test(line));
988
1004
 
989
1005
  expect(aaplRow).toBeGreaterThanOrEqual(0);
990
1006
  expect(apcRow).toBeGreaterThanOrEqual(0);
@@ -7,13 +7,10 @@ import { usePlanAccess } from "../../../plugins/builtin/shared/plan-access";
7
7
  import { buildAssistCommandInventory } from "../assist/inventory";
8
8
  import { useCommandBarAssist } from "../assist/runtime";
9
9
  import { shouldAutoAskAssist, type AssistRowHandlers } from "../assist/model";
10
- import { useNewsArticles } from "../../../news/hooks";
11
10
  import {
12
- ARTICLE_SEARCH_QUERY,
13
- cachedNewsArticles,
14
- looksLikeArticleQuery,
15
- } from "../../../plugins/builtin/news/wire/article-search";
16
- import { buildArticleSearchResultItems } from "../routes/root/article-results";
11
+ getAvailableCommandBarSearchProviders,
12
+ useCommandBarSearchProviders,
13
+ } from "../routes/root/search-providers";
17
14
  import { openUrl } from "../../ui/external-link";
18
15
  import { useRouteListState } from "../routing/list-state";
19
16
  import { useCommandBarRootRuntime } from "../routes/root/runtime";
@@ -57,6 +54,7 @@ export function CommandBar({
57
54
  getCommittedThemeId,
58
55
  nativeListScrollRef,
59
56
  nativePaneChrome,
57
+ nativeWindowChrome,
60
58
  persistConfig,
61
59
  skipTickerSearchDebounceRef,
62
60
  state,
@@ -113,7 +111,6 @@ export function CommandBar({
113
111
  adaptTickerSearchRouteResult,
114
112
  buildLayoutItems,
115
113
  buildPaneSettingItems,
116
- buildPluginItems,
117
114
  buildTickerSearchResultItems,
118
115
  buildWindowModeItems,
119
116
  collectionWorkflowActions,
@@ -258,31 +255,30 @@ export function CommandBar({
258
255
  ),
259
256
  }), [askAssistNow, assistActive, assistAutoAsk, assistState, planAccess.emailVerified, startAssistSignUp]);
260
257
 
261
- const newsState = useNewsArticles(looksLikeArticleQuery(rootQuery) ? ARTICLE_SEARCH_QUERY : null);
262
- const articleResultItems = useMemo(() => {
263
- const cached = cachedNewsArticles();
264
- const seen = new Set<string>();
265
- const articles = [];
266
- for (const article of [...cached, ...newsState.articles]) {
267
- if (seen.has(article.id)) continue;
268
- seen.add(article.id);
269
- articles.push(article);
270
- }
271
- const localReady = cached.length > 0
272
- || newsState.phase === "ready"
273
- || newsState.phase === "refreshing"
274
- || newsState.phase === "error";
275
- const stillLoading = !localReady && (newsState.phase === "idle" || newsState.phase === "loading");
276
- return buildArticleSearchResultItems({
277
- articles,
278
- query: rootQuery,
279
- phase: stillLoading ? "loading" : "ready",
280
- onOpen: (article) => {
281
- openUrl(article.url);
282
- closeAll({ revertThemePreview: false });
283
- },
284
- });
285
- }, [closeAll, newsState.articles, newsState.phase, rootQuery]);
258
+ const searchProviders = useMemo(
259
+ () => getAvailableCommandBarSearchProviders(pluginRegistry, state.config.disabledPlugins),
260
+ [pluginRegistry, state.config.disabledPlugins],
261
+ );
262
+ const searchProviderContext = useMemo(() => ({
263
+ activeTicker: activeTickerSymbol,
264
+ activeCollectionId,
265
+ }), [activeCollectionId, activeTickerSymbol]);
266
+ const closeAfterProviderResult = useCallback(() => {
267
+ closeAll({ revertThemePreview: false });
268
+ }, [closeAll]);
269
+ const { providerResultItems, providerSearching } = useCommandBarSearchProviders({
270
+ providers: searchProviders,
271
+ query: rootQuery,
272
+ // A resolved prefix means the user is running a command, so free-text
273
+ // providers neither ask the network nor add rows.
274
+ enabled: !currentRoute && rootShortcutIntent.kind === "none",
275
+ context: searchProviderContext,
276
+ onExecuted: closeAfterProviderResult,
277
+ });
278
+ const providerCategoryPriorities = useMemo(
279
+ () => new Map(searchProviders.map((provider) => [provider.category, provider.priority ?? 0])),
280
+ [searchProviders],
281
+ );
286
282
 
287
283
  const {
288
284
  activeMatch,
@@ -302,7 +298,6 @@ export function CommandBar({
302
298
  availableCommands,
303
299
  buildLayoutItems,
304
300
  buildPaneSettingItems,
305
- buildPluginItems,
306
301
  buildTickerSearchResultItems,
307
302
  buildWindowModeItems,
308
303
  createPaneTemplateItem,
@@ -320,7 +315,9 @@ export function CommandBar({
320
315
  paneShortcutItems,
321
316
  pluginCommandItems,
322
317
  pluginCommandResultItems,
323
- articleResultItems,
318
+ providerResultItems,
319
+ providerCategoryPriorities,
320
+ providerSearching,
324
321
  readTickerSearchCache,
325
322
  rootModeKind: rootModeInfo.kind,
326
323
  rootQuery,
@@ -384,10 +381,10 @@ export function CommandBar({
384
381
  adaptTickerSearchRouteResult,
385
382
  buildLayoutItems,
386
383
  buildPaneSettingItems,
387
- buildPluginItems,
388
384
  currentRoute,
389
385
  orderedRootResults,
390
386
  pluginRegistry,
387
+ rootCategoryPriorities: providerCategoryPriorities,
391
388
  rootHoveredIdx,
392
389
  rootModeKind: rootModeInfo.kind,
393
390
  rootQuery,
@@ -432,6 +429,7 @@ export function CommandBar({
432
429
  moveWorkflowFocus,
433
430
  nativeListScrollRef,
434
431
  nativePaneChrome,
432
+ nativeWindowChrome,
435
433
  onNativeOccluderChange,
436
434
  openWorkflowFieldPicker,
437
435
  persistConfig,
@@ -440,7 +438,6 @@ export function CommandBar({
440
438
  resetAssist,
441
439
  rootModeKind: rootModeInfo.kind,
442
440
  rootGhostSuffix,
443
- rootQueryLength: rootQuery.length,
444
441
  rootShortcutFeedback,
445
442
  routeListState,
446
443
  setActiveListQuery,
@@ -18,7 +18,8 @@ import type { TickerRecord } from "../../../types/ticker";
18
18
  import type { PluginRegistry } from "../../../plugins/registry";
19
19
  import type { PaneSettingField } from "../../../types/plugin";
20
20
  import { useShortcut } from "../../../react/input";
21
- import { Text } from "../../../ui";
21
+ import { Box, Text } from "../../../ui";
22
+ import { Header } from "../../layout/header";
22
23
 
23
24
  export async function emitKeypress(
24
25
  renderer: Awaited<ReturnType<typeof testRender>>,
@@ -213,6 +214,8 @@ function makePluginRegistry(hasPaneSettings: (paneId: string) => boolean = () =>
213
214
  execute: async () => {},
214
215
  }],
215
216
  ]),
217
+ commandBarSearchProviders: new Map<string, any>(),
218
+ getCommandBarSearchProviderPluginId: () => undefined,
216
219
  tickerActions: new Map<string, any>([
217
220
  ["pin", {
218
221
  id: "pin",
@@ -385,8 +388,17 @@ export function CommandBarHarness({
385
388
  <ThemeProvider themeId={getEffectiveThemeId(currentState)}>
386
389
  <AppContext value={{ state: currentState, dispatch: currentDispatch }}>
387
390
  <TestDialogProvider>
388
- {live && <ThemeProbe />}
389
- {showQueryState && <Text>{`query:${currentState.commandBarQuery}`}</Text>}
391
+ {/* The header hosts the bar's input while it is open, so typing in a
392
+ test needs the real header on the first row. */}
393
+ <Header />
394
+ <Box flexGrow={1} />
395
+ {/* The sheet spans the full width and can reach as far down as
396
+ termHeight - 6 (panel/layout.ts), so probes live on the bottom row,
397
+ the one place no sheet height can cover. */}
398
+ <Box flexDirection="row" gap={1} height={1}>
399
+ {live && <ThemeProbe />}
400
+ {showQueryState && <Text>{`query:${currentState.commandBarQuery}`}</Text>}
401
+ </Box>
390
402
  {currentState.commandBarOpen && (
391
403
  <CommandBar
392
404
  dataProvider={dataProvider}
@@ -13,14 +13,42 @@ import { Box, Text } from "../../ui";
13
13
  import { TextAttributes } from "../../ui";
14
14
  import { ListView, type ListViewItem } from "../ui";
15
15
  import type { ListRowState } from "../ui/list-view";
16
- import { getThemeIds, themes as themeRegistry } from "../../theme/themes";
16
+ import { getThemeIds, isDarkTheme, themes as themeRegistry } from "../../theme/themes";
17
17
  import { truncateText } from "./view-model";
18
18
 
19
19
  const THEME_PREVIEW_DEBOUNCE_MS = 120;
20
- const THEME_OPTIONS = getThemeIds().map((id) => ({
21
- id,
22
- name: themeRegistry[id]!.name,
23
- }));
20
+ /**
21
+ * Marks the dark half of the list. The registry's own order groups themes by
22
+ * family, which only helps if you already know which family you want; sorted by
23
+ * name you can find one by reading, and the glyph carries the grouping the
24
+ * order used to.
25
+ */
26
+ const DARK_THEME_GLYPH = "☾";
27
+ /** The glyph plus the space that keeps names on one left edge, dark or light. */
28
+ const GLYPH_GUTTER_WIDTH = 2;
29
+
30
+ export interface ThemeOption {
31
+ id: string;
32
+ name: string;
33
+ dark: boolean;
34
+ }
35
+
36
+ const THEME_OPTIONS: ThemeOption[] = getThemeIds()
37
+ .map((id) => ({ id, name: themeRegistry[id]!.name, dark: isDarkTheme(id) }))
38
+ .sort((a, b) => a.name.localeCompare(b.name));
39
+
40
+ /**
41
+ * Shared with the panel layout, which sizes the sheet to whatever this returns
42
+ * so the picker never opens taller than the themes it can show.
43
+ */
44
+ export function matchThemeOptions(filter: string): ThemeOption[] {
45
+ const normalized = filter.trim().toLowerCase();
46
+ if (!normalized) return THEME_OPTIONS;
47
+ return THEME_OPTIONS.filter((theme) => (
48
+ theme.name.toLowerCase().includes(normalized)
49
+ || theme.id.toLowerCase().includes(normalized)
50
+ ));
51
+ }
24
52
 
25
53
  interface ThemePickerScrollEvent {
26
54
  stopPropagation: () => void;
@@ -83,13 +111,7 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
83
111
  const onPreviewRef = useRef(onPreview);
84
112
  const onCommitRef = useRef(onCommit);
85
113
  const normalizedFilter = filter.trim().toLowerCase();
86
- const themes = useMemo(() => {
87
- if (!normalizedFilter) return THEME_OPTIONS;
88
- return THEME_OPTIONS.filter((theme) => (
89
- theme.name.toLowerCase().includes(normalizedFilter)
90
- || theme.id.toLowerCase().includes(normalizedFilter)
91
- ));
92
- }, [normalizedFilter]);
114
+ const themes = useMemo(() => matchThemeOptions(normalizedFilter), [normalizedFilter]);
93
115
  const [selectedIndex, setSelectedIndex] = useState(() => (
94
116
  Math.max(0, themes.findIndex((theme) => theme.id === committedThemeId))
95
117
  ));
@@ -199,8 +221,9 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
199
221
  onCommitRef.current(selected.id);
200
222
  }, [cancelPreview]);
201
223
 
224
+ const nameWidth = Math.max(1, labelWidth - GLYPH_GUTTER_WIDTH);
202
225
  const renderRow = useCallback((item: ListViewItem, state: ListRowState) => {
203
- const label = truncateText(item.label, labelWidth);
226
+ const label = truncateText(item.label, nameWidth);
204
227
  const trailing = item.current ? "current" : "";
205
228
  return (
206
229
  <Box
@@ -211,7 +234,12 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
211
234
  data-command-bar-row-selected={nativePaneChrome && state.selected ? "true" : undefined}
212
235
  style={nativePaneChrome ? { borderRadius: 6 } : undefined}
213
236
  >
214
- <Box width={labelWidth}>
237
+ <Box width={GLYPH_GUTTER_WIDTH}>
238
+ <Text fg={state.selected ? paletteSelectedText : paletteSubtleText}>
239
+ {isDarkTheme(item.id) ? DARK_THEME_GLYPH : ""}
240
+ </Text>
241
+ </Box>
242
+ <Box width={nameWidth}>
215
243
  <Text
216
244
  fg={state.selected ? paletteSelectedText : paletteText}
217
245
  attributes={item.current ? TextAttributes.BOLD : undefined}
@@ -228,7 +256,7 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
228
256
  );
229
257
  }, [
230
258
  contentPadding,
231
- labelWidth,
259
+ nameWidth,
232
260
  nativePaneChrome,
233
261
  paletteSelectedText,
234
262
  paletteSubtleText,
@@ -13,7 +13,6 @@ describe("command bar view model helpers", () => {
13
13
  expect(resolveCommandBarMode("DES NVDA")).toMatchObject({ kind: "search", badge: "DES" });
14
14
  expect(resolveCommandBarMode("T NVDA")).toMatchObject({ kind: "search", badge: "T" });
15
15
  expect(resolveCommandBarMode("TH ")).toMatchObject({ kind: "themes", badge: "THEMES" });
16
- expect(resolveCommandBarMode("PL notes")).toMatchObject({ kind: "plugins", badge: "PLUGINS" });
17
16
  expect(resolveCommandBarMode("LAY")).toMatchObject({ kind: "direct-command", badge: "COMMAND" });
18
17
  expect(resolveCommandBarMode("LMA ")).toMatchObject({ kind: "layout", badge: "LAYOUT" });
19
18
  expect(resolveCommandBarMode("NP ")).toMatchObject({ kind: "default", badge: "FILTER" });
@@ -48,23 +47,69 @@ describe("command bar view model helpers", () => {
48
47
  expect(sections.map((section) => section.category)).toEqual(["Tickers", "Config", "Danger", "Debug"]);
49
48
  });
50
49
 
51
- test("drops an offer-only section below real matches even when its category leads", () => {
50
+ test("drops an offer-only section below real matches whatever its category priority", () => {
52
51
  const sections = buildSections([
53
- { id: "assist:sign-up", category: "Ask AI", disabled: true, defaultSelectable: false },
52
+ { id: "exact", category: "Exact Match", disabled: true, defaultSelectable: false },
54
53
  { id: "holders", category: "Panes" },
55
54
  ]);
56
55
 
57
- expect(sections.map((section) => section.category)).toEqual(["Panes", "Ask AI"]);
56
+ expect(sections.map((section) => section.category)).toEqual(["Panes", "Exact Match"]);
58
57
  expect(sections[0]?.items[0]?.id).toBe("holders");
59
58
  });
60
59
 
61
- test("keeps an answered AI section leading the list", () => {
62
- const sections = buildSections([
60
+ test("leads with an exact symbol, then the AI, then the async sections in arrival order", () => {
61
+ // An exactly matching symbol is the most certain answer available, so it
62
+ // outranks the AI's reading of the same query. Below it the AI leads even
63
+ // though it answers last, since it turns the typed sentence into commands;
64
+ // its placeholder holds the rows meanwhile. Symbol search and documents
65
+ // arrive in that order under the local matches, so each only pushes rows
66
+ // below itself. Documents come from a provider, which contributes its own
67
+ // priority.
68
+ const items = [
69
+ { id: "doc", category: "Documents" },
70
+ { id: "nvda-mx", category: "Instruments" },
71
+ { id: "nvda", category: "Exact Match" },
72
+ { id: "holders", category: "Panes" },
73
+ { id: "help", category: "Commands" },
74
+ { id: "quit", category: "Application" },
75
+ { id: "plugin-row", category: "Portfolio" },
63
76
  { id: "assist:candidate:0", category: "Ask AI" },
77
+ ];
78
+ const categoryPriorities = new Map([["Documents", 200]]);
79
+
80
+ for (const sectionOrder of ["default", "app-first"] as const) {
81
+ const sections = buildSections(items, { sectionOrder, categoryPriorities });
82
+ expect(sections.map((section) => section.category)).toEqual([
83
+ "Exact Match",
84
+ "Ask AI",
85
+ "Panes",
86
+ "Commands",
87
+ "Application",
88
+ "Portfolio",
89
+ "Instruments",
90
+ "Documents",
91
+ ]);
92
+ }
93
+ });
94
+
95
+ test("drops the AI's sign-up offer under the async sections", () => {
96
+ const sections = buildSections([
97
+ { id: "assist:sign-up", category: "Ask AI", defaultSelectable: false },
98
+ { id: "doc", category: "Documents" },
99
+ { id: "nvda-mx", category: "Instruments" },
64
100
  { id: "holders", category: "Panes" },
65
- ]);
101
+ ], { categoryPriorities: new Map([["Documents", 200]]) });
102
+
103
+ expect(sections.map((section) => section.category)).toEqual(["Panes", "Instruments", "Documents", "Ask AI"]);
104
+ });
105
+
106
+ test("lets a provider's contributed priority override the built-in band", () => {
107
+ const sections = buildSections([
108
+ { id: "nvda-mx", category: "Instruments" },
109
+ { id: "doc", category: "Documents" },
110
+ ], { categoryPriorities: new Map([["Documents", -200]]) });
66
111
 
67
- expect(sections.map((section) => section.category)).toEqual(["Ask AI", "Panes"]);
112
+ expect(sections.map((section) => section.category)).toEqual(["Documents", "Instruments"]);
68
113
  });
69
114
 
70
115
  test("keeps non-exact ticker suggestions behind app sections in app-first order", () => {