gloomberb 0.16.2 → 0.16.4

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 (527) hide show
  1. package/package.json +4 -1
  2. package/src/api-client/askg.ts +43 -0
  3. package/src/api-client/auth.ts +15 -0
  4. package/src/api-client/beneficial-owners.ts +4 -0
  5. package/src/api-client/chat.ts +19 -0
  6. package/src/api-client/company-kpis.ts +14 -2
  7. package/src/api-client/data.ts +34 -16
  8. package/src/api-client/index.ts +12 -0
  9. package/src/api-client/normalizers.ts +27 -0
  10. package/src/api-client/paths.ts +88 -23
  11. package/src/api-client/request.ts +2 -1
  12. package/src/api-client/research-activity.ts +31 -0
  13. package/src/api-client/socket.ts +39 -8
  14. package/src/api-client/types.ts +57 -1
  15. package/src/api-client/upgrade-experiment.ts +90 -0
  16. package/src/app/global-shortcuts.ts +11 -1
  17. package/src/app/keybindings/actions.ts +10 -0
  18. package/src/app/pane-runtime/index.tsx +8 -0
  19. package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
  20. package/src/app/pane-runtime/plugin-bindings.ts +65 -25
  21. package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
  22. package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
  23. package/src/app/runtime/desktop-deeplink.ts +5 -0
  24. package/src/app/star-prompt/model.ts +98 -0
  25. package/src/app/star-prompt/runtime.ts +41 -0
  26. package/src/app/star-prompt/status-line.tsx +190 -0
  27. package/src/app.tsx +13 -7
  28. package/src/architecture/core-builtin-allowlist.json +70 -0
  29. package/src/brokers/account-cache.ts +45 -0
  30. package/src/brokers/signed-in/adapter.ts +33 -2
  31. package/src/brokers/signed-in/client.ts +10 -0
  32. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  33. package/src/brokers/signed-in/sign-in.ts +62 -5
  34. package/src/capabilities/registry.ts +8 -1
  35. package/src/capabilities/types.ts +2 -0
  36. package/src/cli/command-options.ts +74 -0
  37. package/src/cli/commands/command-utils.ts +29 -0
  38. package/src/cli/commands/crypto-hints.ts +12 -4
  39. package/src/cli/commands/market.ts +524 -148
  40. package/src/cli/commands/options-chain.ts +172 -0
  41. package/src/cli/commands/options-leaps.ts +422 -0
  42. package/src/cli/commands/overview.ts +223 -42
  43. package/src/cli/commands/search.ts +29 -8
  44. package/src/cli/commands/system.ts +99 -19
  45. package/src/cli/commands/themes.ts +64 -0
  46. package/src/cli/commands/ticker.ts +200 -73
  47. package/src/cli/desktop-pane-shot.ts +100 -16
  48. package/src/cli/errors.ts +25 -2
  49. package/src/cli/freshness.ts +7 -4
  50. package/src/cli/fx-pair.ts +57 -0
  51. package/src/cli/help.ts +40 -7
  52. package/src/cli/history-rows.ts +187 -15
  53. package/src/cli/index-roots.ts +51 -0
  54. package/src/cli/index.ts +21 -13
  55. package/src/cli/listing-arg.ts +211 -3
  56. package/src/cli/not-a-ticker.ts +37 -0
  57. package/src/cli/options.ts +55 -7
  58. package/src/cli/pane-functions/capabilities.ts +13 -2
  59. package/src/cli/pane-functions/catalog.ts +13 -2
  60. package/src/cli/pane-functions/data.ts +36 -5
  61. package/src/cli/pane-functions/dom.ts +89 -28
  62. package/src/cli/pane-functions/freshness.ts +205 -26
  63. package/src/cli/pane-functions/function-help.ts +71 -24
  64. package/src/cli/pane-functions/headless.ts +168 -35
  65. package/src/cli/pane-functions/index.ts +12 -2
  66. package/src/cli/pane-functions/market-session.ts +133 -0
  67. package/src/cli/pane-functions/options.ts +15 -4
  68. package/src/cli/pane-functions/report-notices.ts +63 -0
  69. package/src/cli/pane-functions/report.ts +12 -1
  70. package/src/cli/pane-functions/resolver.ts +32 -8
  71. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  72. package/src/cli/pane-functions/screenshot.ts +109 -23
  73. package/src/cli/registry.ts +8 -4
  74. package/src/cli/report-tables.ts +460 -0
  75. package/src/cli/result.ts +63 -16
  76. package/src/cli/row-window.ts +76 -0
  77. package/src/cli/scroll-truncation.ts +34 -0
  78. package/src/components/chart/composite/composite-chart.tsx +43 -4
  79. package/src/components/chart/composite/legend.tsx +39 -6
  80. package/src/components/chart/composite/rasterizer.ts +64 -4
  81. package/src/components/chart/composite/series-cues.ts +71 -0
  82. package/src/components/chart/composite/text-renderer.ts +16 -8
  83. package/src/components/chart/composite/types.ts +3 -0
  84. package/src/components/command-bar/commands/direct/index.ts +9 -1
  85. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  86. package/src/components/command-bar/commands/registry.ts +7 -0
  87. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  88. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  89. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  90. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  91. package/src/components/command-bar/routes/root/results.ts +40 -10
  92. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  93. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  94. package/src/components/command-bar/surface/index.tsx +18 -4
  95. package/src/components/command-bar/theme-picker.tsx +48 -13
  96. package/src/components/command-bar/workflow/ops.ts +5 -3
  97. package/src/components/data-table/export.ts +6 -51
  98. package/src/components/data-table/view.tsx +31 -10
  99. package/src/components/layout/floating-pane.tsx +6 -2
  100. package/src/components/layout/market-summary.ts +23 -9
  101. package/src/components/layout/pane/content.tsx +46 -29
  102. package/src/components/layout/pane/cycle-order.ts +2 -11
  103. package/src/components/layout/pane/header.tsx +2 -1
  104. package/src/components/layout/pane/title.ts +1 -1
  105. package/src/components/layout/presentation.ts +22 -0
  106. package/src/components/layout/shell/active-drag.ts +67 -35
  107. package/src/components/layout/shell/cursor-occlusion.ts +6 -5
  108. package/src/components/layout/shell/drag/index.ts +16 -7
  109. package/src/components/layout/shell/drag/live.ts +193 -0
  110. package/src/components/layout/shell/drag/overlays.tsx +179 -35
  111. package/src/components/layout/shell/drag/runtime.ts +62 -50
  112. package/src/components/layout/shell/drag/slide.ts +10 -0
  113. package/src/components/layout/shell/index.tsx +84 -57
  114. package/src/components/layout/shell/layout-state.ts +5 -11
  115. package/src/components/layout/shell/menu.ts +28 -1
  116. package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
  117. package/src/components/layout/shell/native/surfaces.ts +30 -14
  118. package/src/components/layout/shell/pane/actions.ts +2 -1
  119. package/src/components/layout/shell/pane/layers.tsx +461 -180
  120. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  121. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  122. package/src/components/layout/shell/shortcuts.ts +5 -2
  123. package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
  124. package/src/components/layout/shell/visible-layout.ts +44 -1
  125. package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
  126. package/src/components/layout/status-bar.tsx +4 -1
  127. package/src/components/markdown-text.tsx +9 -5
  128. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  129. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  130. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  131. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  132. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  133. package/src/components/onboarding/pro-step-layout.ts +86 -0
  134. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  135. package/src/components/ui/button.tsx +10 -0
  136. package/src/components/ui/data-table/index.tsx +38 -0
  137. package/src/components/ui/data-table/types.ts +8 -1
  138. package/src/components/ui/data-table/visible-range.ts +2 -0
  139. package/src/components/ui/icon.tsx +11 -2
  140. package/src/components/ui/index.ts +1 -1
  141. package/src/core/connection-health.ts +1 -1
  142. package/src/core/state/app/layout-reducer.ts +89 -3
  143. package/src/core/state/app/state.ts +8 -5
  144. package/src/core/state/app/types.ts +13 -2
  145. package/src/data/config/layout.ts +55 -21
  146. package/src/data/config/store/fold-panes.ts +149 -0
  147. package/src/data/config/store/migrations.ts +7 -0
  148. package/src/data/config/store/node.ts +27 -6
  149. package/src/data/config/store/normalize.ts +37 -3
  150. package/src/data/json-persistence.ts +17 -3
  151. package/src/data/json-storage.ts +2 -0
  152. package/src/data/memory-resource-store.ts +46 -0
  153. package/src/i18n/es.ts +14 -1
  154. package/src/i18n/ja.ts +14 -1
  155. package/src/i18n/ko.ts +14 -1
  156. package/src/i18n/zh-cn.ts +14 -1
  157. package/src/i18n/zh-tw.ts +14 -1
  158. package/src/layout/desks.ts +1 -2
  159. package/src/layout/pane-layout-move.ts +264 -0
  160. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  161. package/src/layout/pane-manager/index.ts +1 -0
  162. package/src/market-data/coordinator/entries.ts +7 -1
  163. package/src/market-data/coordinator/quotes.ts +2 -2
  164. package/src/market-data/market/freshness.ts +156 -11
  165. package/src/market-data/market/status.ts +40 -0
  166. package/src/market-data/options-alternatives.ts +69 -0
  167. package/src/market-data/quotes/contributions.ts +19 -2
  168. package/src/market-data/quotes/freshness.ts +5 -0
  169. package/src/market-data/quotes/metadata.ts +16 -0
  170. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  171. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  172. package/src/plugins/builtin/account-management/model.ts +1 -0
  173. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  174. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  175. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  176. package/src/plugins/builtin/analytics/pane-model.ts +10 -6
  177. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  178. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  179. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  180. package/src/plugins/builtin/attention/pane.tsx +1 -1
  181. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  182. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  183. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  184. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  185. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  186. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  187. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  188. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  189. package/src/plugins/builtin/catalysts/detail.tsx +1 -1
  190. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  191. package/src/plugins/builtin/chat/channels.ts +34 -36
  192. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  193. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  194. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  195. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  196. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  197. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  198. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  199. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  200. package/src/plugins/builtin/chat/conversations.ts +256 -0
  201. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  202. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  203. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  204. package/src/plugins/builtin/chat/index.tsx +40 -2
  205. package/src/plugins/builtin/chat/layout.ts +2 -0
  206. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  207. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  208. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  209. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  210. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  211. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  212. package/src/plugins/builtin/chat/pane.tsx +8 -0
  213. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  214. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  215. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  216. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  217. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  218. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  219. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  220. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  221. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  222. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  223. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  224. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  225. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  226. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  227. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  228. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  229. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  230. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  231. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  232. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  233. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  234. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  235. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  236. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  237. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  238. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  239. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  240. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  241. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  242. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  243. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  244. package/src/plugins/builtin/company-attention/pane.tsx +1 -1
  245. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  246. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  247. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  248. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  249. package/src/plugins/builtin/composite-plugins.ts +107 -64
  250. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  251. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  252. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  253. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  254. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  255. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  256. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  257. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  258. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  259. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  260. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  261. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  262. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  263. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  264. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  265. package/src/plugins/builtin/econ/index.tsx +68 -22
  266. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  267. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  268. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  269. package/src/plugins/builtin/executives/data.ts +17 -6
  270. package/src/plugins/builtin/executives/pane.tsx +16 -7
  271. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  272. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  273. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  274. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  275. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  276. package/src/plugins/builtin/gpu/headless.ts +9 -3
  277. package/src/plugins/builtin/gpu/model.ts +14 -5
  278. package/src/plugins/builtin/help/index.tsx +1 -0
  279. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  280. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  281. package/src/plugins/builtin/holders/client.ts +3 -6
  282. package/src/plugins/builtin/holders/format.ts +31 -1
  283. package/src/plugins/builtin/holders/headless.ts +32 -6
  284. package/src/plugins/builtin/holders/pane.tsx +25 -13
  285. package/src/plugins/builtin/holders/report-header.ts +46 -5
  286. package/src/plugins/builtin/insider/index.tsx +4 -4
  287. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  288. package/src/plugins/builtin/iv-history/client.ts +12 -2
  289. package/src/plugins/builtin/iv-history/format.ts +33 -2
  290. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  291. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  292. package/src/plugins/builtin/iv-history/model.ts +61 -13
  293. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  294. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  295. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  296. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  297. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  298. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  299. package/src/plugins/builtin/kelly-sizer/pane.tsx +121 -45
  300. package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
  301. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  302. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  303. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  304. package/src/plugins/builtin/macro-day/client.ts +24 -0
  305. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  306. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  307. package/src/plugins/builtin/macro-day/model.ts +11 -6
  308. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  309. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  310. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  311. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  312. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  313. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  314. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  315. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  316. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  317. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  318. package/src/plugins/builtin/market-movers/model.ts +71 -17
  319. package/src/plugins/builtin/market-movers/table.ts +50 -23
  320. package/src/plugins/builtin/members/pane.tsx +10 -0
  321. package/src/plugins/builtin/news/headless.ts +9 -2
  322. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  323. package/src/plugins/builtin/openfda/client.ts +1 -1
  324. package/src/plugins/builtin/options/carry.ts +95 -0
  325. package/src/plugins/builtin/options/index.tsx +58 -5
  326. package/src/plugins/builtin/options/strike-window.ts +183 -0
  327. package/src/plugins/builtin/options/table.ts +55 -3
  328. package/src/plugins/builtin/options/types.ts +8 -1
  329. package/src/plugins/builtin/options/view.tsx +161 -12
  330. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  331. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  332. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  333. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  334. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  335. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  336. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  337. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  338. package/src/plugins/builtin/pe-band/model.ts +2 -2
  339. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  340. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  341. package/src/plugins/builtin/perps/model.ts +16 -5
  342. package/src/plugins/builtin/perps/pane.tsx +1 -1
  343. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  344. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  345. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  346. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  347. package/src/plugins/builtin/plugin-module.ts +87 -4
  348. package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
  349. package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
  350. package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
  351. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  352. package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
  353. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  354. package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
  355. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
  356. package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
  357. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
  358. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  359. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  360. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  361. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  362. package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
  363. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  364. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  365. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  366. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  367. package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
  368. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  369. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  370. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  371. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  372. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  373. package/src/plugins/builtin/sec/index.tsx +5 -5
  374. package/src/plugins/builtin/sectors/client.ts +10 -2
  375. package/src/plugins/builtin/sectors/headless.ts +35 -6
  376. package/src/plugins/builtin/sectors/index.tsx +18 -1
  377. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  378. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  379. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  380. package/src/plugins/builtin/shared/members-menu.tsx +260 -0
  381. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  382. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  383. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  384. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  385. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  386. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  387. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  388. package/src/plugins/builtin/short-volume/pane.tsx +1 -1
  389. package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
  390. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  391. package/src/plugins/builtin/themes/pane.tsx +3 -0
  392. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
  393. package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
  394. package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
  395. package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
  396. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
  397. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  398. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  399. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  400. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  401. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  402. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  403. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  404. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  405. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  406. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  407. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  408. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  409. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  410. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  411. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  412. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  413. package/src/plugins/builtin/world-indices/client.ts +22 -4
  414. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  415. package/src/plugins/builtin/world-indices/model.ts +4 -2
  416. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  417. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
  418. package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
  419. package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
  420. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  421. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
  422. package/src/plugins/builtin/world-venue-map/index.ts +6 -6
  423. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
  424. package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
  425. package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
  426. package/src/plugins/builtin/world-venue-map/model.ts +16 -0
  427. package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
  428. package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
  429. package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
  430. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  431. package/src/plugins/builtin-editorial.ts +126 -0
  432. package/src/plugins/catalog-backend.ts +16 -9
  433. package/src/plugins/catalog-browser.ts +127 -63
  434. package/src/plugins/catalog-manifest.ts +73 -40
  435. package/src/plugins/catalog-ui.ts +26 -6
  436. package/src/plugins/extracted-plugins.ts +7 -1
  437. package/src/plugins/ownership.ts +150 -7
  438. package/src/plugins/plugin-functions.ts +54 -0
  439. package/src/plugins/plugin-off.ts +36 -0
  440. package/src/plugins/registry/host-actions.ts +16 -0
  441. package/src/plugins/registry/index.ts +84 -24
  442. package/src/plugins/registry/plugin-state.ts +25 -1
  443. package/src/plugins/runtime/context.tsx +29 -0
  444. package/src/react/input.ts +48 -1
  445. package/src/remote/app-host.tsx +5 -0
  446. package/src/remote/controller.ts +3 -53
  447. package/src/remote/matching.ts +69 -0
  448. package/src/remote/schema.ts +1 -1
  449. package/src/remote/semantic-tree.tsx +14 -0
  450. package/src/remote/types.ts +2 -0
  451. package/src/renderers/browser/app-services.ts +3 -1
  452. package/src/renderers/dom/data-table/dom.ts +31 -0
  453. package/src/renderers/dom/data-table/index.tsx +5 -0
  454. package/src/renderers/dom/desktop/icons.tsx +14 -0
  455. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  456. package/src/renderers/dom/host/box.tsx +113 -17
  457. package/src/renderers/dom/host/drag-shield.ts +35 -0
  458. package/src/renderers/dom/host/live-frame.ts +62 -0
  459. package/src/renderers/dom/host/mouse.ts +8 -1
  460. package/src/renderers/dom/host/scroll-box.tsx +6 -5
  461. package/src/renderers/dom/host/tabs.tsx +3 -2
  462. package/src/renderers/dom/input-host.tsx +6 -1
  463. package/src/renderers/dom/styles.css +26 -5
  464. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
  465. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  466. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  467. package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
  468. package/src/renderers/electrobun/shared/protocol.ts +12 -1
  469. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
  470. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  471. package/src/renderers/electrobun/view/host/native.ts +0 -10
  472. package/src/renderers/electrobun/view/host/window-move.ts +28 -0
  473. package/src/renderers/electrobun/view/main.tsx +4 -1
  474. package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
  475. package/src/renderers/electrobun/view/ui-host.tsx +2 -5
  476. package/src/renderers/opentui/input-host.tsx +6 -1
  477. package/src/renderers/opentui/start.tsx +9 -0
  478. package/src/renderers/opentui/ui-host.tsx +7 -1
  479. package/src/shares/portable-layout.ts +16 -6
  480. package/src/sources/gloomberb-cloud/index.ts +19 -10
  481. package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
  482. package/src/sources/listing-symbols.ts +11 -5
  483. package/src/sources/provider-errors.ts +88 -3
  484. package/src/sources/provider-router/batches.ts +3 -3
  485. package/src/sources/provider-router/cached-routes.ts +8 -4
  486. package/src/sources/provider-router/financial-routes.ts +13 -7
  487. package/src/sources/provider-router/financials.ts +106 -16
  488. package/src/sources/provider-router/history.ts +27 -11
  489. package/src/sources/provider-router/index.ts +1 -1
  490. package/src/sources/provider-router/primary.ts +24 -6
  491. package/src/sources/sec-edgar.ts +68 -8
  492. package/src/state/pane-scroll-registry.tsx +67 -35
  493. package/src/sync/core-contributors.ts +4 -3
  494. package/src/test-support/cli-context.ts +2 -1
  495. package/src/test-support/data-provider.ts +34 -0
  496. package/src/test-support/margin-account.ts +108 -0
  497. package/src/test-support/shell-pane-keys.tsx +1 -0
  498. package/src/theme/series-colors.ts +46 -0
  499. package/src/theme/themes.ts +149 -0
  500. package/src/tickers/search/index.ts +22 -2
  501. package/src/tickers/search/ranking.ts +45 -1
  502. package/src/tickers/search/types.ts +3 -1
  503. package/src/time-series/resolution.ts +15 -0
  504. package/src/time-series/resolve.ts +1 -14
  505. package/src/types/config.ts +61 -1
  506. package/src/types/financials.ts +32 -3
  507. package/src/types/headless.ts +11 -0
  508. package/src/types/instrument.ts +8 -0
  509. package/src/types/plugin.ts +49 -3
  510. package/src/ui/host.tsx +16 -0
  511. package/src/ui/index.tsx +10 -3
  512. package/src/ui/scroll-box-axis.ts +26 -0
  513. package/src/utils/cli-output.ts +14 -1
  514. package/src/utils/currency-units.ts +8 -3
  515. package/src/utils/depositary-receipt.ts +54 -10
  516. package/src/utils/display-number.ts +63 -0
  517. package/src/utils/exchanges.ts +13 -0
  518. package/src/utils/format.ts +2 -1
  519. package/src/utils/fundamentals.ts +17 -1
  520. package/src/utils/fx-market-hours.ts +34 -0
  521. package/src/utils/option-expiry.ts +126 -0
  522. package/src/utils/price-history-integrity.ts +19 -6
  523. package/src/utils/price-history.ts +39 -6
  524. package/src/utils/rpc-timeout-error.ts +6 -2
  525. package/src/utils/sec-eps-basis.ts +159 -30
  526. package/src/utils/sec.ts +38 -34
  527. package/src/utils/throttled-fetch.ts +51 -22
@@ -4,7 +4,10 @@ import {
4
4
  getDockedPaneIds,
5
5
  removePane,
6
6
  } from "../../../layout/pane-manager";
7
+ import { paneMoveBackTarget, paneNewLayoutBlock } from "../../../layout/pane-layout-move";
8
+ import { resolveTickerForPane } from "../../../state/app/context";
7
9
  import { findPaneInstance } from "../../../types/config";
10
+ import { tf } from "../../../i18n";
8
11
  import type { ResultItem } from "../list/model";
9
12
  import type { LayoutItemsContext } from "./types";
10
13
  import { WINDOW_MODE_COMMAND_OPTIONS } from "./window-mode";
@@ -20,6 +23,7 @@ export function buildFocusedPaneLayoutItems({
20
23
  persistLayoutChange,
21
24
  pluginRegistry,
22
25
  pushRoute,
26
+ state,
23
27
  }: LayoutItemsContext): ResultItem[] {
24
28
  const focusedPane = focusedPaneId ? findPaneInstance(currentLayout, focusedPaneId) : null;
25
29
  const focusedPaneDef = focusedPane ? pluginRegistry.panes.get(focusedPane.paneId) : null;
@@ -37,6 +41,12 @@ export function buildFocusedPaneLayoutItems({
37
41
 
38
42
  const dockedPaneIds = getDockedPaneIds(currentLayout);
39
43
  const focusedFloating = currentLayout.floating.find((entry) => entry.instanceId === focusedPane.instanceId);
44
+ const newLayoutBlock = paneNewLayoutBlock(
45
+ currentLayout,
46
+ focusedPane.instanceId,
47
+ (instanceId) => resolveTickerForPane(state, instanceId),
48
+ );
49
+ const moveBack = paneMoveBackTarget(state.config, focusedPane.instanceId);
40
50
 
41
51
  return [
42
52
  {
@@ -65,6 +75,34 @@ export function buildFocusedPaneLayoutItems({
65
75
  pluginRegistry.togglePaneFullscreen(focusedPane.instanceId);
66
76
  },
67
77
  },
78
+ {
79
+ id: "layout-move-to-new-layout",
80
+ label: "Move to New Layout",
81
+ detail: newLayoutBlock === "only-pane"
82
+ ? "Already the only pane in this layout"
83
+ : "Give the focused pane a layout of its own that it fills",
84
+ searchText: "own layout tab fullscreen",
85
+ category: "Focused Pane",
86
+ kind: "action",
87
+ disabled: newLayoutBlock === "only-pane" || newLayoutBlock === "detached",
88
+ action: () => {
89
+ closeAll({ revertThemePreview: false });
90
+ pluginRegistry.movePaneToNewLayout(focusedPane.instanceId);
91
+ },
92
+ },
93
+ ...(moveBack ? [{
94
+ id: "layout-move-back",
95
+ label: tf("Move Back to {layout}", { layout: moveBack.name }),
96
+ detail: moveBack.original
97
+ ? "Return the focused pane to the layout it came from"
98
+ : "The layout it came from is gone, so it docks in this one instead",
99
+ category: "Focused Pane",
100
+ kind: "action" as const,
101
+ action: () => {
102
+ closeAll({ revertThemePreview: false });
103
+ pluginRegistry.movePaneBack(focusedPane.instanceId);
104
+ },
105
+ }] : []),
68
106
  ...WINDOW_MODE_COMMAND_OPTIONS.map((option) => ({
69
107
  id: `layout-window-mode:${option.mode}`,
70
108
  label: option.label,
@@ -29,6 +29,7 @@ import {
29
29
  type SharedWorkflowDeps,
30
30
  } from "../workflow/ops";
31
31
  import type { PluginRegistry } from "../../../plugins/registry";
32
+ import { PluginOffError, notifyPluginOff } from "../../../plugins/plugin-off";
32
33
  import { shouldOpenPaneTemplateConfig } from "./workflow-route";
33
34
 
34
35
  type CloseAllFn = (options?: { revertThemePreview?: boolean }) => void;
@@ -107,6 +108,10 @@ export function useCommandBarPaneTemplateActions({
107
108
  await pluginRegistry.createPaneFromTemplateAsync(template.id, createOptions);
108
109
  closeAll({ revertThemePreview: false });
109
110
  } catch (error) {
111
+ if (error instanceof PluginOffError) {
112
+ notifyPluginOff(pluginRegistry, error, () => { void openPaneTemplateDirect(template, createOptions); });
113
+ return;
114
+ }
110
115
  const displayLabel = getPaneTemplateDisplayLabel(template);
111
116
  notify(
112
117
  error instanceof Error ? error.message : `Could not create ${displayLabel.toLowerCase()}.`,
@@ -147,6 +147,8 @@ export function createRootCommandItemBuilder({
147
147
  return command.description;
148
148
  case "toggle-value-flashing":
149
149
  return state.config.valueFlashingEnabled ? "Currently on" : "Currently off";
150
+ case "toggle-presentation-mode":
151
+ return state.config.presentationMode ? "Currently on" : "Currently off";
150
152
  case "toggle-crash-reports":
151
153
  return state.config.telemetry?.crashReports === false ? "Currently off" : "Currently on";
152
154
  case "toggle-usage-counts":
@@ -0,0 +1,92 @@
1
+ import { useMemo } from "react";
2
+ import type { DisabledPluginOwner, PluginRegistry } from "../../../../plugins/registry";
3
+ import { getPaneTemplateDisplayLabel, paneTemplateShortcutPrefixes } from "../../pane-templates/items";
4
+ import type { ResultItem } from "../../list/model";
5
+ import { PLUGIN_INSTALL_CATEGORY } from "../../view-model";
6
+
7
+ /** A typed code whose function belongs to a plugin that is switched off. */
8
+ interface PluginTurnOnOffer {
9
+ owner: DisabledPluginOwner;
10
+ code: string;
11
+ /** The function's name, as its row reads when the plugin is on. */
12
+ label: string;
13
+ }
14
+
15
+ type TurnOnRegistry = Pick<
16
+ PluginRegistry,
17
+ "allPlugins" | "commands" | "getCommandPluginId" | "getDisabledPaneTemplateOwner" | "paneTemplates"
18
+ >;
19
+
20
+ /**
21
+ * Matches the code alone or as the first word, case-insensitively, against
22
+ * the functions of switched-off plugins. Only plugins the user can switch
23
+ * back on are offered.
24
+ */
25
+ function matchPluginTurnOnOffer({
26
+ query,
27
+ pluginRegistry,
28
+ disabledPlugins,
29
+ }: {
30
+ query: string;
31
+ pluginRegistry: TurnOnRegistry;
32
+ disabledPlugins: readonly string[];
33
+ }): PluginTurnOnOffer | null {
34
+ const code = (query.trim().split(/\s+/, 1)[0] ?? "").toUpperCase();
35
+ if (!code || disabledPlugins.length === 0) return null;
36
+ const canTurnOn = (owner: DisabledPluginOwner | null): owner is DisabledPluginOwner => (
37
+ !!owner && pluginRegistry.allPlugins.get(owner.id)?.toggleable === true
38
+ );
39
+
40
+ for (const template of pluginRegistry.paneTemplates.values()) {
41
+ if (!paneTemplateShortcutPrefixes(template).some((prefix) => prefix.trim().toUpperCase() === code)) continue;
42
+ const owner = pluginRegistry.getDisabledPaneTemplateOwner(template.id, disabledPlugins);
43
+ if (canTurnOn(owner)) return { owner, code, label: getPaneTemplateDisplayLabel(template) };
44
+ }
45
+ for (const [commandId, command] of pluginRegistry.commands) {
46
+ if (command.shortcut?.trim().toUpperCase() !== code) continue;
47
+ const pluginId = pluginRegistry.getCommandPluginId(commandId);
48
+ const plugin = pluginId ? pluginRegistry.allPlugins.get(pluginId) : undefined;
49
+ const owner = plugin && disabledPlugins.includes(plugin.id) ? { id: plugin.id, name: plugin.name } : null;
50
+ if (canTurnOn(owner)) return { owner, code, label: command.label };
51
+ }
52
+ return null;
53
+ }
54
+
55
+ /**
56
+ * The row for a code whose plugin is off, or null. Enter turns the plugin on,
57
+ * then runs the typed text again, argument and all, as if just entered.
58
+ * `enabled` is false whenever something else claimed the query.
59
+ */
60
+ export function useRootPluginTurnOnItem({
61
+ enabled,
62
+ query,
63
+ pluginRegistry,
64
+ disabledPlugins,
65
+ rerunQuery,
66
+ }: {
67
+ enabled: boolean;
68
+ query: string;
69
+ pluginRegistry: PluginRegistry;
70
+ disabledPlugins: readonly string[];
71
+ rerunQuery: (query: string) => void;
72
+ }): ResultItem | null {
73
+ return useMemo(() => {
74
+ const offer = enabled ? matchPluginTurnOnOffer({ query, pluginRegistry, disabledPlugins }) : null;
75
+ if (!offer) return null;
76
+ const actionLine = `Turn on ${offer.owner.name}`;
77
+ return {
78
+ id: `plugin-turn-on:${offer.owner.id}:${offer.code}`,
79
+ label: offer.label,
80
+ detail: actionLine,
81
+ category: PLUGIN_INSTALL_CATEGORY,
82
+ kind: "action",
83
+ right: offer.code,
84
+ searchText: [offer.code, offer.label, offer.owner.name].join(" "),
85
+ lines: [{ segments: [{ text: actionLine, emphasis: "match" }] }],
86
+ action: () => {
87
+ pluginRegistry.setPluginEnabled(offer.owner.id, true);
88
+ rerunQuery(query);
89
+ },
90
+ };
91
+ }, [disabledPlugins, enabled, pluginRegistry, query, rerunQuery]);
92
+ }
@@ -86,6 +86,12 @@ export interface RootResultModelOptions {
86
86
  * user is aiming at, and only ever adds to what the bar already resolved.
87
87
  */
88
88
  providerResultItems?: ResultItem[];
89
+ /**
90
+ * Rows from providers that answer from memory, built with the keystroke. The
91
+ * empty bar shows them under Suggested; typed text ranks them as one block
92
+ * among the local matches, at the place of their best row.
93
+ */
94
+ providerMatchItems?: ResultItem[];
89
95
  runDirectCommand: (command: Command, arg: string) => void;
90
96
  runSecurityDescriptionShortcut: (query?: string) => void | Promise<void>;
91
97
  state: AppState;
@@ -134,6 +140,32 @@ function buildBindKeyItem(
134
140
  };
135
141
  }
136
142
 
143
+ /**
144
+ * Ranks a provider's in-memory rows with the local matches without reordering
145
+ * them: the provider decided which rows answer and in what order, so the block
146
+ * moves as one to where its best row would rank. A block none of whose rows
147
+ * the bar's own matching finds still shows, after the local matches.
148
+ */
149
+ function rankMatchBlock(
150
+ localItems: ResultItem[],
151
+ blockItems: ResultItem[],
152
+ query: string,
153
+ ): ResultItem[] {
154
+ if (blockItems.length === 0) {
155
+ return fuzzyFilter(localItems, query, rootItemSearchText, (item) => item.label);
156
+ }
157
+ const block = new Set(blockItems);
158
+ const ranked = fuzzyFilter([...localItems, ...blockItems], query, rootItemSearchText, (item) => item.label);
159
+ const firstBlockIndex = ranked.findIndex((item) => block.has(item));
160
+ const local = ranked.filter((item) => !block.has(item));
161
+ if (firstBlockIndex < 0) return [...local, ...blockItems];
162
+ return [...local.slice(0, firstBlockIndex), ...blockItems, ...local.slice(firstBlockIndex)];
163
+ }
164
+
165
+ function rootItemSearchText(item: ResultItem): string {
166
+ return `${item.label} ${item.searchText || ""} ${item.detail} ${item.right || ""}`;
167
+ }
168
+
137
169
  /** Kept short: the empty bar is still a browse list of everything below. */
138
170
  const MAX_RECENT_TICKER_ROWS = 4;
139
171
  const MAX_RECENT_PANE_ROWS = 4;
@@ -211,6 +243,7 @@ export function buildRootResultModel(options: RootResultModelOptions): RootResul
211
243
  rootQuery,
212
244
  rootShortcutIntent,
213
245
  providerResultItems = [],
246
+ providerMatchItems = [],
214
247
  runDirectCommand,
215
248
  runSecurityDescriptionShortcut,
216
249
  state,
@@ -316,6 +349,7 @@ export function buildRootResultModel(options: RootResultModelOptions): RootResul
316
349
  recentTickers: state.recentTickers ?? [],
317
350
  }));
318
351
  }
352
+ items.push(...providerMatchItems);
319
353
  items.push(...paneShortcutItems());
320
354
  for (const command of availableCommands) {
321
355
  const item = commandToItem(command);
@@ -336,13 +370,7 @@ export function buildRootResultModel(options: RootResultModelOptions): RootResul
336
370
  ...tickerActionItems(),
337
371
  ...pluginCommandItems(),
338
372
  ];
339
- const matchedItems = fuzzyFilter(
340
- allItems,
341
- rootQuery,
342
- (item) => `${item.label} ${item.searchText || ""} ${item.detail} ${item.right || ""}`,
343
- (item) => item.label,
344
- );
345
- items.push(...matchedItems);
373
+ items.push(...rankMatchBlock(allItems, providerMatchItems, rootQuery));
346
374
  }
347
375
 
348
376
  const shortcutClaimedQuery = rootShortcutIntent.kind !== "none";
@@ -356,10 +384,12 @@ export function buildRootResultModel(options: RootResultModelOptions): RootResul
356
384
  items.push(buildBindKeyItem(rootShortcutIntent, bindKey));
357
385
  }
358
386
  // A resolved prefix means the user is speaking the command language, so
359
- // free-text providers stay out of the way.
360
- if (!shortcutClaimedQuery) {
361
- items.push(...providerResultItems);
387
+ // only providers that asked to answer after that code add rows, below the
388
+ // code's own.
389
+ if (shortcutClaimedQuery) {
390
+ items.push(...providerMatchItems);
362
391
  }
392
+ items.push(...providerResultItems);
363
393
 
364
394
  // Built from the local matches, then placed above them: the AI turns the
365
395
  // typed sentence into commands, so its answer leads the list. Its Thinking
@@ -14,6 +14,7 @@ import { normalizeCommandTickerSearchText } from "../ticker-search/results";
14
14
  import { useTickerSearchRouteResults } from "../ticker-search/route";
15
15
  import { buildRootResultModel, type RootResultModel } from "./results";
16
16
  import { useRootProviderSearch } from "./provider-search";
17
+ import { isProviderResultItem } from "./search-providers";
17
18
  import { buildRootShortcutFeedback } from "./shortcut-feedback";
18
19
  import type { ShortcutIntent } from "./shortcuts";
19
20
  import { clampIndex } from "../../../../utils/math";
@@ -66,6 +67,7 @@ interface UseCommandBarRootRuntimeOptions {
66
67
  pluginCommandResultItems(command: CommandDef, shortcutArg: string): ResultItem[];
67
68
  pluginInstallItem?: ResultItem | null;
68
69
  providerResultItems?: ResultItem[];
70
+ providerMatchItems?: ResultItem[];
69
71
  providerCategoryPriorities?: CommandBarCategoryPriorities;
70
72
  providerSearching?: boolean;
71
73
  readTickerSearchCache(
@@ -123,6 +125,7 @@ export function useCommandBarRootRuntime({
123
125
  pluginCommandResultItems,
124
126
  pluginInstallItem = null,
125
127
  providerResultItems = [],
128
+ providerMatchItems = [],
126
129
  providerCategoryPriorities,
127
130
  providerSearching = false,
128
131
  readTickerSearchCache,
@@ -202,6 +205,7 @@ export function useCommandBarRootRuntime({
202
205
  rootQuery,
203
206
  rootShortcutIntent,
204
207
  providerResultItems,
208
+ providerMatchItems,
205
209
  runDirectCommand,
206
210
  runSecurityDescriptionShortcut,
207
211
  state,
@@ -234,6 +238,7 @@ export function useCommandBarRootRuntime({
234
238
  rootQuery,
235
239
  rootShortcutIntent,
236
240
  providerResultItems,
241
+ providerMatchItems,
237
242
  runDirectCommand,
238
243
  runSecurityDescriptionShortcut,
239
244
  state,
@@ -261,10 +266,13 @@ export function useCommandBarRootRuntime({
261
266
  const normalizedQuery = normalizeCommandTickerSearchText(symbolQuery);
262
267
  // The install row for "TV" is labelled TV, but it leads on purpose and
263
268
  // must not keep Grupo Televisa out of the list.
269
+ // A plugin's row never stands in for the symbol: a DM with @spy must not
270
+ // keep SPY out of the list.
264
271
  const hasExactLocalRow = rootResultModel.items.some((item) => (
265
272
  item !== pluginInstallItem
266
273
  && item.kind !== "ticker"
267
274
  && item.kind !== "search"
275
+ && !isProviderResultItem(item)
268
276
  && normalizeCommandTickerSearchText(item.label) === normalizedQuery
269
277
  ));
270
278
  return hasExactLocalRow ? null : symbolQuery;
@@ -9,6 +9,29 @@ import type { ResultItem } from "../../list/model";
9
9
 
10
10
  const DEFAULT_MIN_QUERY_LENGTH = 3;
11
11
  const DEFAULT_DEBOUNCE_MS = 300;
12
+ const PROVIDER_ROW_PREFIX = "search-provider:";
13
+
14
+ /** A row a plugin search provider contributed, as opposed to one the bar resolved itself. */
15
+ export function isProviderResultItem(item: Pick<ResultItem, "id">): boolean {
16
+ return item.id.startsWith(PROVIDER_ROW_PREFIX);
17
+ }
18
+
19
+ /**
20
+ * Whether a provider answers the text in the bar: free text, or text a code
21
+ * claimed that the provider asked to keep hearing.
22
+ */
23
+ function answersQuery(provider: CommandBarSearchProvider, claimedShortcut: string | null): boolean {
24
+ if (!claimedShortcut) return true;
25
+ return provider.shortcuts?.some((code) => code.trim().toUpperCase() === claimedShortcut) ?? false;
26
+ }
27
+
28
+ function meetsMinQueryLength(provider: CommandBarSearchProvider, trimmedQuery: string): boolean {
29
+ return trimmedQuery.length >= (provider.minQueryLength ?? DEFAULT_MIN_QUERY_LENGTH);
30
+ }
31
+
32
+ function sortByPriority(providers: readonly CommandBarSearchProvider[]): CommandBarSearchProvider[] {
33
+ return [...providers].sort((left, right) => (left.priority ?? 0) - (right.priority ?? 0));
34
+ }
12
35
 
13
36
  export function getAvailableCommandBarSearchProviders(
14
37
  pluginRegistry: Pick<PluginRegistry, "commandBarSearchProviders" | "getCommandBarSearchProviderPluginId">,
@@ -31,15 +54,16 @@ export function toProviderResultItem(
31
54
  onExecuted: () => void,
32
55
  ): ResultItem {
33
56
  return {
34
- id: `search-provider:${provider.id}:${result.id}`,
57
+ id: `${PROVIDER_ROW_PREFIX}${provider.id}:${result.id}`,
35
58
  label: result.label,
36
59
  detail: result.detail ?? "",
37
60
  category: result.category ?? provider.category,
38
61
  kind: "action",
39
62
  lines: result.lines,
40
63
  badge: result.badge,
64
+ name: result.name,
41
65
  right: result.right,
42
- searchText: [result.label, result.detail ?? "", ...(result.keywords ?? [])].join(" "),
66
+ searchText: [result.label, result.detail ?? "", result.name ?? "", ...(result.keywords ?? [])].join(" "),
43
67
  disabled: result.disabled,
44
68
  action: async () => {
45
69
  if (result.disabled) return;
@@ -58,26 +82,73 @@ interface ProviderResults {
58
82
  interface UseCommandBarSearchProvidersOptions {
59
83
  providers: readonly CommandBarSearchProvider[];
60
84
  query: string;
61
- /** False while a route is open or a prefix already claimed the query. */
85
+ /** False while a route is open or the venue list owns the query. */
62
86
  enabled: boolean;
87
+ /**
88
+ * The code that claimed the query (`CHAT`), upper case, or null for free
89
+ * text. Only providers that list it among their `shortcuts` still answer.
90
+ */
91
+ claimedShortcut?: string | null;
63
92
  context: CommandBarSearchContext;
64
93
  onExecuted: () => void;
65
94
  }
66
95
 
96
+ /**
97
+ * Rows from providers that answer from memory (`match`), worked out as the bar
98
+ * renders so they land with the keystroke instead of a frame behind it. A
99
+ * provider that throws contributes nothing.
100
+ */
101
+ function useProviderMatchItems({
102
+ providers,
103
+ query,
104
+ enabled,
105
+ claimedShortcut,
106
+ context,
107
+ onExecutedRef,
108
+ }: {
109
+ providers: readonly CommandBarSearchProvider[];
110
+ query: string;
111
+ enabled: boolean;
112
+ claimedShortcut: string | null;
113
+ context: CommandBarSearchContext;
114
+ onExecutedRef: { current: () => void };
115
+ }): ResultItem[] {
116
+ return useMemo(() => {
117
+ if (!enabled) return [];
118
+ const trimmed = query.trim();
119
+ return sortByPriority(providers).flatMap((provider) => {
120
+ if (!provider.match || !answersQuery(provider, claimedShortcut) || !meetsMinQueryLength(provider, trimmed)) {
121
+ return [];
122
+ }
123
+ let results: CommandBarResultDef[];
124
+ try {
125
+ results = provider.match(trimmed, context);
126
+ } catch {
127
+ return [];
128
+ }
129
+ return results.map((result) => toProviderResultItem(provider, result, () => onExecutedRef.current()));
130
+ });
131
+ }, [claimedShortcut, context, enabled, onExecutedRef, providers, query]);
132
+ }
133
+
67
134
  /**
68
135
  * Runs plugin search providers for the root list. Each provider debounces and
69
136
  * aborts on its own, answers are memoized for the life of the bar, and rows are
70
137
  * only ever added to the static list: a provider that fails, hangs, or answers
71
138
  * late leaves what the command bar already resolved exactly as it was.
139
+ * Providers that answer from memory skip all of that and come back as
140
+ * `providerMatchItems`, for the bar to rank with its own matches.
72
141
  */
73
142
  export function useCommandBarSearchProviders({
74
143
  providers,
75
144
  query,
76
145
  enabled,
146
+ claimedShortcut = null,
77
147
  context,
78
148
  onExecuted,
79
149
  }: UseCommandBarSearchProvidersOptions): {
80
150
  providerResultItems: ResultItem[];
151
+ providerMatchItems: ResultItem[];
81
152
  providerSearching: boolean;
82
153
  } {
83
154
  const [resultsByProvider, setResultsByProvider] = useState<Record<string, ProviderResults>>({});
@@ -136,8 +207,12 @@ export function useCommandBarSearchProviders({
136
207
  }
137
208
 
138
209
  for (const provider of providers) {
139
- const minLength = provider.minQueryLength ?? DEFAULT_MIN_QUERY_LENGTH;
140
- if (!enabled || trimmed.length < minLength) {
210
+ if (
211
+ !enabled
212
+ || provider.match
213
+ || !answersQuery(provider, claimedShortcut)
214
+ || !meetsMinQueryLength(provider, trimmed)
215
+ ) {
141
216
  cancel(provider.id);
142
217
  setLoading(provider.id, false);
143
218
  continue;
@@ -179,7 +254,7 @@ export function useCommandBarSearchProviders({
179
254
  })();
180
255
  }, provider.debounceMs ?? DEFAULT_DEBOUNCE_MS));
181
256
  }
182
- }, [enabled, providers, query]);
257
+ }, [claimedShortcut, enabled, providers, query]);
183
258
 
184
259
  useEffect(() => () => {
185
260
  for (const timer of timersRef.current.values()) clearTimeout(timer);
@@ -191,15 +266,23 @@ export function useCommandBarSearchProviders({
191
266
  const providerResultItems = useMemo(() => {
192
267
  const trimmed = query.trim();
193
268
  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]);
269
+ return sortByPriority(providers).flatMap((provider) => {
270
+ if (provider.match || !answersQuery(provider, claimedShortcut)) return [];
271
+ const results = resultsByProvider[provider.id];
272
+ return results?.query === trimmed ? results.items : [];
273
+ });
274
+ }, [claimedShortcut, enabled, providers, query, resultsByProvider]);
275
+
276
+ const providerMatchItems = useProviderMatchItems({
277
+ providers,
278
+ query,
279
+ enabled,
280
+ claimedShortcut,
281
+ context,
282
+ onExecutedRef,
283
+ });
201
284
 
202
285
  const providerSearching = enabled && providers.some((provider) => loadingProviderIds.includes(provider.id));
203
286
 
204
- return { providerResultItems, providerSearching };
287
+ return { providerResultItems, providerMatchItems, providerSearching };
205
288
  }
@@ -23,6 +23,7 @@ import { useAppStateRef } from "../../../state/app/context";
23
23
  import { listingChoiceQuery } from "../../../tickers/search";
24
24
  import { parseRootShortcutIntent } from "../routes/root/shortcuts";
25
25
  import { useRootPluginInstallItem } from "../routes/root/plugin-install";
26
+ import { useRootPluginTurnOnItem } from "../routes/root/plugin-turn-on";
26
27
  import { useCommandBarThemePreview } from "../theme-preview";
27
28
  import { matchThemeOptions } from "../theme-picker";
28
29
  import { CommandBarPanel } from "../panel";
@@ -243,6 +244,15 @@ export function CommandBar({
243
244
  dispatch({ type: "SET_COMMAND_BAR", open: true, query, launch: { kind: "run-query", query } });
244
245
  }, [dispatch]);
245
246
  const closeBar = useCallback(() => closeAfterRun({ revertThemePreview: false }), [closeAfterRun]);
247
+ // A code whose plugin is switched off offers to turn it on. The install row
248
+ // never offers a code the app has, so the two never compete.
249
+ const pluginTurnOnItem = useRootPluginTurnOnItem({
250
+ enabled: !currentRoute && rootShortcutIntent.kind === "none",
251
+ query: rootQuery,
252
+ pluginRegistry,
253
+ disabledPlugins: state.config.disabledPlugins,
254
+ rerunQuery,
255
+ });
246
256
  const pluginInstallItem = useRootPluginInstallItem({
247
257
  enabled: !currentRoute && rootShortcutIntent.kind === "none",
248
258
  query: rootQuery,
@@ -374,12 +384,15 @@ export function CommandBar({
374
384
  const closeAfterProviderResult = useCallback(() => {
375
385
  closeAfterRun({ revertThemePreview: false });
376
386
  }, [closeAfterRun]);
377
- const { providerResultItems, providerSearching } = useCommandBarSearchProviders({
387
+ const { providerResultItems, providerMatchItems, providerSearching } = useCommandBarSearchProviders({
378
388
  providers: searchProviders,
379
389
  query: rootQuery,
390
+ // A colon is the venue list.
391
+ enabled: !currentRoute && !listingChoice,
380
392
  // A resolved prefix means the user is running a command, so free-text
381
- // providers neither ask the network nor add rows. A colon is the venue list.
382
- enabled: !currentRoute && rootShortcutIntent.kind === "none" && !listingChoice,
393
+ // providers neither ask the network nor add rows, unless they asked to
394
+ // answer after that code.
395
+ claimedShortcut: rootShortcutIntent.kind === "none" ? null : rootShortcutIntent.prefix,
383
396
  context: searchProviderContext,
384
397
  onExecuted: closeAfterProviderResult,
385
398
  });
@@ -426,8 +439,9 @@ export function CommandBar({
426
439
  paneShortcutItems,
427
440
  pluginCommandItems,
428
441
  pluginCommandResultItems,
429
- pluginInstallItem,
442
+ pluginInstallItem: pluginTurnOnItem ?? pluginInstallItem,
430
443
  providerResultItems,
444
+ providerMatchItems,
431
445
  providerCategoryPriorities,
432
446
  providerSearching,
433
447
  readTickerSearchCache,
@@ -11,7 +11,7 @@ import {
11
11
  import { t } from "../../i18n";
12
12
  import { getThemeIds, isDarkTheme, themes as themeRegistry } from "../../theme/themes";
13
13
  import { Box, Text, TextAttributes } from "../../ui";
14
- import { truncateToDisplayWidth } from "../../utils/format";
14
+ import { displayWidth, truncateToDisplayWidth } from "../../utils/format";
15
15
  import { ListView, type ListViewItem } from "../ui";
16
16
  import type { ListRowState } from "../ui/list-view";
17
17
  import type { ListJump } from "./list/model";
@@ -28,6 +28,8 @@ const THEME_PREVIEW_DEBOUNCE_MS = 120;
28
28
  const DARK_THEME_GLYPH = "☾";
29
29
  /** The glyph plus the space that keeps names on one left edge, dark or light. */
30
30
  const GLYPH_GUTTER_WIDTH = 2;
31
+ /** Between a name and its config id. */
32
+ const ID_GAP = 2;
31
33
 
32
34
  export interface ThemeOption {
33
35
  id: string;
@@ -38,6 +40,32 @@ export interface ThemeOption {
38
40
  const THEME_OPTIONS: ThemeOption[] = getThemeIds()
39
41
  .map((id) => ({ id, name: themeRegistry[id]!.name, dark: isDarkTheme(id) }))
40
42
  .sort((a, b) => a.name.localeCompare(b.name));
43
+ /** Ids line up in one column after the longest name. */
44
+ const LONGEST_THEME_NAME = Math.max(...THEME_OPTIONS.map((theme) => displayWidth(theme.name)));
45
+ const CURRENT_LABEL = "current";
46
+
47
+ interface ThemeRowLayout {
48
+ name: number;
49
+ /** Zero when the id has no room. */
50
+ id: number;
51
+ showCurrent: boolean;
52
+ }
53
+
54
+ /**
55
+ * A row's name, then the id `config set theme` and the docs use, in one
56
+ * column after the longest name. The id is copied, so it never shows cut:
57
+ * the current row gives up its "current" tag first (its name is bold), then
58
+ * the id goes, before any of a name does.
59
+ */
60
+ function themeRowLayout(width: number, id: string, current: boolean): ThemeRowLayout {
61
+ const name = Math.min(LONGEST_THEME_NAME, width);
62
+ const idWidth = displayWidth(id);
63
+ const room = width - name - ID_GAP;
64
+ const currentWidth = displayWidth(CURRENT_LABEL) + 1;
65
+ if (current && room - currentWidth >= idWidth) return { name, id: idWidth, showCurrent: true };
66
+ if (room >= idWidth) return { name, id: idWidth, showCurrent: false };
67
+ return { name, id: 0, showCurrent: current && width - name >= currentWidth };
68
+ }
41
69
 
42
70
  /**
43
71
  * Shared with the panel layout, which sizes the sheet to whatever this returns
@@ -227,10 +255,12 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
227
255
  onCommitRef.current(selected.id);
228
256
  }, [cancelPreview]);
229
257
 
230
- const nameWidth = Math.max(1, labelWidth - GLYPH_GUTTER_WIDTH);
258
+ // The whole row past the glyph: the name, the id and, on the current row, its tag.
259
+ const rowWidth = Math.max(1, labelWidth - GLYPH_GUTTER_WIDTH + trailingWidth);
231
260
  const renderRow = useCallback((item: ListViewItem, state: ListRowState) => {
232
- const label = truncateToDisplayWidth(item.label, nameWidth);
233
- const trailing = item.current ? "current" : "";
261
+ const layout = themeRowLayout(rowWidth, item.id, item.current === true);
262
+ const label = truncateToDisplayWidth(item.label, layout.name);
263
+ const subtle = state.selected ? palette.selectedText : palette.subtle;
234
264
  return (
235
265
  <Box
236
266
  flexDirection="row"
@@ -241,11 +271,11 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
241
271
  style={nativePaneChrome ? { borderRadius: 6 } : undefined}
242
272
  >
243
273
  <Box width={GLYPH_GUTTER_WIDTH}>
244
- <Text fg={state.selected ? palette.selectedText : palette.subtle}>
274
+ <Text fg={subtle}>
245
275
  {isDarkTheme(item.id) ? DARK_THEME_GLYPH : ""}
246
276
  </Text>
247
277
  </Box>
248
- <Box width={nameWidth}>
278
+ <Box width={layout.name}>
249
279
  <Text
250
280
  fg={state.selected ? palette.selectedText : palette.text}
251
281
  attributes={item.current ? TextAttributes.BOLD : undefined}
@@ -253,19 +283,24 @@ export const ThemePicker = memo(forwardRef<ThemePickerHandle, ThemePickerProps>(
253
283
  {label}
254
284
  </Text>
255
285
  </Box>
256
- <Box width={trailingWidth}>
257
- <Text fg={state.selected ? palette.selectedText : palette.subtle}>
258
- {truncateToDisplayWidth(trailing, trailingWidth)}
259
- </Text>
260
- </Box>
286
+ {layout.id > 0 ? (
287
+ <Box width={ID_GAP + layout.id} paddingLeft={ID_GAP}>
288
+ <Text fg={subtle}>{item.id}</Text>
289
+ </Box>
290
+ ) : null}
291
+ <Box flexGrow={1} />
292
+ {layout.showCurrent ? (
293
+ <Box width={displayWidth(CURRENT_LABEL) + 1} flexShrink={0}>
294
+ <Text fg={subtle}>{CURRENT_LABEL}</Text>
295
+ </Box>
296
+ ) : null}
261
297
  </Box>
262
298
  );
263
299
  }, [
264
300
  contentPadding,
265
- nameWidth,
266
301
  nativePaneChrome,
267
302
  palette,
268
- trailingWidth,
303
+ rowWidth,
269
304
  ]);
270
305
 
271
306
  return (