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.
- package/README.md +7 -4
- package/README.zh-CN.md +4 -2
- package/package.json +28 -1
- package/src/api-client/auth.ts +10 -0
- package/src/api-client/data.ts +108 -1
- package/src/api-client/index.test.ts +127 -0
- package/src/api-client/index.ts +134 -1
- package/src/api-client/normalizers.ts +39 -0
- package/src/api-client/paths.ts +85 -1
- package/src/api-client/types.ts +220 -0
- package/src/app/pane-runtime/index.tsx +15 -12
- package/src/app/pane-runtime/layout-placement.test.ts +27 -0
- package/src/app/pane-runtime/layout-placement.ts +13 -0
- package/src/app/pane-runtime/plugin-bindings.ts +62 -0
- package/src/app.tsx +60 -18
- package/src/architecture/browser-entry-graph.test.ts +96 -0
- package/src/brokers/profile-form.test.ts +25 -27
- package/src/brokers/test-broker.ts +68 -0
- package/src/capabilities/chart-series.ts +3 -2
- package/src/cli/commands/market.ts +2 -2
- package/src/cli/commands/plugins.ts +39 -11
- package/src/cli/desktop-pane-shot.ts +285 -16
- package/src/cli/index.ts +1 -1
- package/src/cli/pane-functions/capabilities.ts +61 -31
- package/src/cli/pane-functions/catalog.ts +16 -4
- package/src/cli/pane-functions/discovery.ts +1 -0
- package/src/cli/pane-functions/headless.test.ts +247 -0
- package/src/cli/pane-functions/headless.ts +423 -0
- package/src/cli/pane-functions/index.test.ts +9 -0
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +27 -2
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +26 -8
- package/src/cli/pane-functions/screenshot.test.ts +76 -0
- package/src/cli/pane-functions/screenshot.ts +236 -20
- package/src/cli/registry.ts +0 -22
- package/src/cli/restore-plugins.ts +34 -0
- package/src/cli/result.ts +7 -10
- package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
- package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
- package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
- package/src/components/chart/composite/composite-chart.test.tsx +68 -20
- package/src/components/chart/composite/composite-chart.tsx +301 -204
- package/src/components/chart/composite/format.ts +8 -2
- package/src/components/chart/composite/interactions.test.ts +68 -102
- package/src/components/chart/composite/interactions.ts +214 -264
- package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
- package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
- package/src/components/chart/composite/scene.ts +57 -3
- package/src/components/chart/composite/text-renderer.ts +3 -2
- package/src/components/chart/composite/time-scale.ts +138 -57
- package/src/components/chart/composite/types.ts +25 -1
- package/src/components/chart/core/data.ts +9 -13
- package/src/components/chart/core/date-window.ts +7 -1
- package/src/components/chart/core/pointer.ts +3 -0
- package/src/components/chart/core/types.ts +0 -45
- package/src/components/chart/native/chart-rasterizer.ts +0 -1
- package/src/components/chart/native/kitty/index.test.ts +0 -188
- package/src/components/chart/native/raster/crosshair.ts +6 -5
- package/src/components/chart/native/raster/primitives.ts +0 -23
- package/src/components/chart/native/renderer-selection.ts +81 -31
- package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
- package/src/components/chart/static/chart-surface.tsx +150 -0
- package/src/components/chart/static/index.ts +8 -0
- package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
- package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
- package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
- package/src/components/chart/static/series.ts +49 -0
- package/src/components/command-bar/action-runtime.ts +1 -3
- package/src/components/command-bar/assist/model.test.ts +17 -2
- package/src/components/command-bar/assist/model.ts +40 -9
- package/src/components/command-bar/commands/direct/index.ts +1 -1
- package/src/components/command-bar/commands/direct/runtime.ts +1 -1
- package/src/components/command-bar/commands/registry.ts +0 -11
- package/src/components/command-bar/helpers.test.ts +2 -2
- package/src/components/command-bar/helpers.ts +2 -6
- package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
- package/src/components/command-bar/list/badge.test.ts +54 -0
- package/src/components/command-bar/list/badge.ts +62 -0
- package/src/components/command-bar/list/model.test.ts +103 -0
- package/src/components/command-bar/list/model.ts +61 -4
- package/src/components/command-bar/list/view.test.tsx +113 -0
- package/src/components/command-bar/list/view.tsx +147 -131
- package/src/components/command-bar/pane-templates/workflow.ts +1 -1
- package/src/components/command-bar/panel/body.tsx +51 -65
- package/src/components/command-bar/panel/index.tsx +48 -13
- package/src/components/command-bar/panel/layout.test.ts +139 -0
- package/src/components/command-bar/panel/layout.ts +65 -28
- package/src/components/command-bar/panel/native-surface.ts +24 -0
- package/src/components/command-bar/panel/prompt-binding.ts +54 -0
- package/src/components/command-bar/panel/runtime.ts +6 -3
- package/src/components/command-bar/panel/state.ts +41 -7
- package/src/components/command-bar/panel/types.ts +7 -1
- package/src/components/command-bar/routes/root/provider-search.ts +34 -15
- package/src/components/command-bar/routes/root/results.test.ts +161 -0
- package/src/components/command-bar/routes/root/results.ts +30 -28
- package/src/components/command-bar/routes/root/runtime.ts +37 -20
- package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
- package/src/components/command-bar/routes/root/search-providers.ts +205 -0
- package/src/components/command-bar/routes/root/selection.ts +1 -12
- package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
- package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
- package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
- package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
- package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
- package/src/components/command-bar/routing/actions.ts +1 -81
- package/src/components/command-bar/routing/launch-request.ts +1 -1
- package/src/components/command-bar/routing/list-state.ts +6 -21
- package/src/components/command-bar/selection-runtime.ts +1 -1
- package/src/components/command-bar/surface/assist.test.tsx +14 -10
- package/src/components/command-bar/surface/environment.ts +8 -1
- package/src/components/command-bar/surface/index.test.tsx +84 -68
- package/src/components/command-bar/surface/index.tsx +33 -36
- package/src/components/command-bar/surface/test-harness.tsx +15 -3
- package/src/components/command-bar/theme-picker.tsx +43 -15
- package/src/components/command-bar/view-model.test.ts +53 -8
- package/src/components/command-bar/view-model.ts +43 -14
- package/src/components/command-bar/workflow/tickers.ts +1 -0
- package/src/components/command-bar/workflow/types.ts +1 -1
- package/src/components/data-table/export.test.ts +21 -0
- package/src/components/data-table/export.ts +21 -0
- package/src/components/data-table/view.test.tsx +45 -6
- package/src/components/data-table/view.tsx +55 -9
- package/src/components/index.ts +10 -2
- package/src/components/input-search-bar.test.tsx +36 -2
- package/src/components/input-search-bar.tsx +18 -1
- package/src/components/layout/header.test.tsx +57 -10
- package/src/components/layout/header.tsx +292 -149
- package/src/components/layout/market-summary.test.ts +81 -0
- package/src/components/layout/market-summary.ts +109 -0
- package/src/components/layout/shell/chrome.ts +96 -1
- package/src/components/layout/shell/index.tsx +11 -3
- package/src/components/layout/shell/menu.ts +9 -0
- package/src/components/layout/shell/pane/actions.ts +29 -0
- package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
- package/src/components/layout/shell/shortcuts.ts +3 -0
- package/src/components/layout/status-bar.test.tsx +9 -8
- package/src/components/layout/status-bar.tsx +85 -54
- package/src/components/onboarding/onboarding-frame.tsx +2 -26
- package/src/components/price-sparkline/index.ts +61 -34
- package/src/components/price-sparkline/view.tsx +1 -1
- package/src/components/sign-in-gate.test.tsx +128 -0
- package/src/components/sign-in-gate.tsx +149 -0
- package/src/components/speedometer-gauge/desktop.tsx +2 -12
- package/src/components/ticker/badge/list.tsx +17 -3
- package/src/components/ticker/list-table-view.tsx +6 -3
- package/src/components/ui/data-table/index.tsx +22 -4
- package/src/components/ui/data-table/opentui/index.tsx +165 -120
- package/src/components/ui/frame.tsx +28 -0
- package/src/components/ui/index.ts +3 -0
- package/src/components/ui/native-select.tsx +20 -10
- package/src/components/ui/select-button.tsx +138 -0
- package/src/components/ui/status.tsx +2 -2
- package/src/components/ui/table-layout.ts +1 -1
- package/src/components/use-double-click-activation.ts +7 -3
- package/src/core/app-services.ts +3 -3
- package/src/data/config/chart-settings.ts +4 -4
- package/src/data/config/layout.ts +12 -6
- package/src/data/config/store/index.test.ts +97 -0
- package/src/data/config/store/migrations.ts +41 -0
- package/src/data/config/store/normalize.ts +5 -3
- package/src/i18n/es.ts +6 -2
- package/src/i18n/ja.ts +6 -2
- package/src/i18n/ko.ts +6 -2
- package/src/i18n/zh-cn.ts +6 -2
- package/src/i18n/zh-tw.ts +6 -2
- package/src/layout-marketplace/gallery-terminal.tsx +126 -96
- package/src/layout-marketplace/gallery.test.tsx +65 -1
- package/src/layout-marketplace/pane-imagery.ts +1 -0
- package/src/layout-marketplace/payload.test.ts +17 -0
- package/src/layout-marketplace/payload.ts +40 -5
- package/src/market-data/coordinator/index.ts +3 -2
- package/src/market-data/coordinator/quotes.ts +173 -66
- package/src/market-data/coordinator/subscriptions.test.ts +215 -37
- package/src/pane-settings.ts +1 -1
- package/src/plugins/builtin/analytics/pane-model.ts +1 -1
- package/src/plugins/builtin/analytics/view.tsx +1 -1
- package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
- package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
- package/src/plugins/builtin/chart-composer/cli-options.ts +12 -9
- package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
- package/src/plugins/builtin/chart-composer/settings.ts +2 -13
- package/src/plugins/builtin/chat/content/composer.tsx +8 -4
- package/src/plugins/builtin/cloud/auth-dialog.tsx +14 -228
- package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
- package/src/plugins/builtin/cloud/plugin.tsx +1 -0
- package/src/plugins/builtin/composite-plugins.ts +16 -1
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
- package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
- package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
- package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -21
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings/index.tsx +1 -0
- package/src/plugins/builtin/earnings-calls/data.ts +223 -0
- package/src/plugins/builtin/earnings-calls/format.ts +49 -0
- package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +756 -0
- package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
- package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
- package/src/plugins/builtin/earnings-calls/transcript-view.tsx +334 -0
- package/src/plugins/builtin/econ/detail-view.tsx +1 -2
- package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
- package/src/plugins/builtin/econ/index.tsx +3 -1
- package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
- package/src/plugins/builtin/econ-statistics/client.ts +92 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
- package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
- package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
- package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
- package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
- package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
- package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
- package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
- package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
- package/src/plugins/builtin/econ-statistics/view.ts +90 -0
- package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
- package/src/plugins/builtin/fear-greed/data.ts +6 -1
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +1 -0
- package/src/plugins/builtin/help/index.tsx +4 -0
- package/src/plugins/builtin/holders/index.tsx +1 -0
- package/src/plugins/builtin/insider/index.tsx +1 -0
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
- package/src/plugins/builtin/market-halts/index.tsx +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +1 -0
- package/src/plugins/builtin/market-valuation/align.ts +122 -0
- package/src/plugins/builtin/market-valuation/cache.ts +32 -0
- package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
- package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
- package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
- package/src/plugins/builtin/market-valuation/client.ts +116 -0
- package/src/plugins/builtin/market-valuation/defs.ts +251 -0
- package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
- package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
- package/src/plugins/builtin/market-valuation/headless.ts +170 -0
- package/src/plugins/builtin/market-valuation/index.tsx +64 -0
- package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
- package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
- package/src/plugins/builtin/market-valuation/model.ts +8 -0
- package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
- package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
- package/src/plugins/builtin/market-valuation/series.ts +12 -0
- package/src/plugins/builtin/market-valuation/settings.ts +38 -0
- package/src/plugins/builtin/market-valuation/sources.ts +130 -0
- package/src/plugins/builtin/market-valuation/trend.ts +89 -0
- package/src/plugins/builtin/market-valuation/view.ts +137 -0
- package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
- package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
- package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
- package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
- package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
- package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
- package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
- package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
- package/src/plugins/builtin/news/wire/index.tsx +31 -44
- package/src/plugins/builtin/options/analytics.test.ts +83 -0
- package/src/plugins/builtin/options/analytics.ts +110 -0
- package/src/plugins/builtin/options/index.tsx +18 -2
- package/src/plugins/builtin/options/table.ts +121 -51
- package/src/plugins/builtin/options/types.ts +22 -15
- package/src/plugins/builtin/options/view.test.tsx +53 -1
- package/src/plugins/builtin/options/view.tsx +92 -41
- package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
- package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
- package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
- package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
- package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
- package/src/plugins/builtin/plugin-module.test.ts +2 -4
- package/src/plugins/builtin/plugin-module.ts +9 -8
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
- package/src/plugins/builtin/research/index.tsx +4 -0
- package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
- package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
- package/src/plugins/builtin/research-search/data.ts +109 -0
- package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
- package/src/plugins/builtin/research-search/document-view.tsx +254 -0
- package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
- package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
- package/src/plugins/builtin/research-search/index.tsx +85 -0
- package/src/plugins/builtin/research-search/model.test.ts +40 -0
- package/src/plugins/builtin/research-search/model.ts +291 -0
- package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
- package/src/plugins/builtin/research-search/pane.tsx +678 -0
- package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
- package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
- package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
- package/src/plugins/builtin/research-search/snippet.ts +134 -0
- package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
- package/src/plugins/builtin/scanner/flow-model.ts +29 -1
- package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
- package/src/plugins/builtin/scanner/index.ts +2 -0
- package/src/plugins/builtin/sec/index.tsx +1 -0
- package/src/plugins/builtin/sectors/index.tsx +1 -0
- package/src/plugins/builtin/shared/series-cache.ts +93 -0
- package/src/plugins/builtin/short-interest/index.tsx +1 -0
- package/src/plugins/builtin/short-interest/pane.tsx +1 -1
- package/src/plugins/builtin/thirteenf/index.tsx +1 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +3 -5
- package/src/plugins/builtin/ticker-detail/index.tsx +2 -14
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
- package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
- package/src/plugins/builtin/volatility/index.tsx +1 -1
- package/src/plugins/builtin/world-indices/index.tsx +1 -0
- package/src/plugins/builtin/world-venue-map/index.ts +27 -0
- package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +481 -0
- package/src/plugins/builtin/world-venue-map/model.test.ts +85 -0
- package/src/plugins/builtin/world-venue-map/model.ts +268 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +300 -0
- package/src/plugins/builtin/world-venue-map/world-outlines.ts +2 -0
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/bundle.test.ts +109 -0
- package/src/plugins/bundle.ts +139 -0
- package/src/plugins/catalog-backend.ts +6 -44
- package/src/plugins/catalog-browser.test.ts +1 -0
- package/src/plugins/catalog-browser.ts +12 -0
- package/src/plugins/catalog-manifest.test.ts +43 -0
- package/src/plugins/catalog-manifest.ts +92 -0
- package/src/plugins/catalog-ui.ts +20 -4
- package/src/plugins/catalog.ts +9 -2
- package/src/plugins/current-target.ts +28 -0
- package/src/plugins/host-contract.ts +37 -0
- package/src/plugins/host-link.ts +158 -0
- package/src/plugins/host-modules.ts +61 -0
- package/src/plugins/loader.ts +41 -22
- package/src/plugins/ownership.ts +1 -0
- package/src/plugins/prediction-markets/index.tsx +1 -0
- package/src/plugins/registry/context.ts +3 -0
- package/src/plugins/registry/contributions.ts +27 -0
- package/src/plugins/registry/index.test.ts +25 -0
- package/src/plugins/registry/index.ts +8 -0
- package/src/plugins/registry/pane-settings.test.ts +53 -0
- package/src/plugins/registry/pane-settings.ts +29 -3
- package/src/plugins/seed.test.ts +83 -0
- package/src/plugins/seed.ts +80 -0
- package/src/public/broker.ts +31 -0
- package/src/public/market-data.ts +9 -0
- package/src/public/pane-hooks.ts +57 -0
- package/src/public/public-api.test.ts +285 -0
- package/src/public/react.ts +62 -0
- package/src/public/test-support.ts +23 -0
- package/src/public/time-series.ts +8 -0
- package/src/public/utils.ts +77 -0
- package/src/remote/layout-helpers.ts +6 -8
- package/src/remote/resources.ts +2 -0
- package/src/remote/schema.ts +1 -0
- package/src/remote/semantic-tree.test.tsx +105 -0
- package/src/remote/semantic-tree.tsx +53 -35
- package/src/renderers/browser/main.tsx +6 -0
- package/src/renderers/browser/ui-host.tsx +11 -0
- package/src/renderers/cloudflare/worker.test.ts +19 -0
- package/src/renderers/cloudflare/worker.ts +1 -1
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -2
- package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
- package/src/renderers/electrobun/bun/index.ts +6 -2
- package/src/renderers/electrobun/shared/protocol.ts +25 -0
- package/src/renderers/electrobun/view/build-assets.ts +0 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +51 -1
- package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
- package/src/renderers/electrobun/view/external-plugins.ts +63 -0
- package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
- package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
- package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
- package/src/renderers/electrobun/view/host/mouse.ts +16 -2
- package/src/renderers/electrobun/view/main.tsx +27 -2
- package/src/renderers/electrobun/view/styles.css +5 -0
- package/src/renderers/electrobun/view/toast-host.tsx +18 -0
- package/src/renderers/electrobun/view/ui-host.tsx +3 -0
- package/src/renderers/opentui/chart-surface.tsx +5 -1
- package/src/renderers/opentui/host.tsx +20 -2
- package/src/renderers/opentui/interaction-performance.test.ts +79 -0
- package/src/renderers/opentui/interaction-performance.ts +188 -0
- package/src/renderers/opentui/start.tsx +26 -1
- package/src/renderers/opentui/test-utils.tsx +13 -0
- package/src/renderers/opentui/toast-host.tsx +26 -2
- package/src/renderers/opentui/ui-host.tsx +1 -0
- package/src/sources/provider-router/search.test.ts +191 -0
- package/src/sources/provider-router/search.ts +92 -39
- package/src/state/app/context/index.tsx +2 -0
- package/src/state/app/context/selector.test.tsx +33 -1
- package/src/state/hooks/quote-streaming.test.tsx +93 -0
- package/src/state/hooks/quote-streaming.ts +106 -30
- package/src/state/pane-table-export-registry.ts +50 -0
- package/src/theme/color-utils.ts +1 -1
- package/src/theme/colors.ts +50 -0
- package/src/theme/themes.test.ts +15 -2
- package/src/theme/themes.ts +19 -1
- package/src/tickers/search/index.test.ts +26 -0
- package/src/tickers/search/index.ts +42 -17
- package/src/tickers/search/ranking.ts +14 -0
- package/src/time-series/chart-data.test.ts +216 -0
- package/src/time-series/range.ts +12 -0
- package/src/time-series/resolution.test.ts +25 -26
- package/src/time-series/resolution.ts +44 -25
- package/src/time-series/resolve.ts +7 -0
- package/src/time-series/spec.ts +5 -5
- package/src/time-series/types.ts +3 -1
- package/src/time-series/use-chart-resolution.ts +5 -0
- package/src/types/config.ts +123 -3
- package/src/types/data-provider.ts +11 -0
- package/src/types/headless.ts +161 -0
- package/src/types/plugin.ts +104 -20
- package/src/ui/host.tsx +19 -1
- package/src/ui/index.tsx +1 -0
- package/src/ui/toast.tsx +8 -4
- package/src/utils/csv.test.ts +13 -0
- package/src/utils/csv.ts +37 -0
- package/src/utils/format.ts +35 -0
- package/src/utils/fuzzy-search.test.ts +94 -0
- package/src/utils/fuzzy-search.ts +81 -22
- package/src/utils/save-text-file.test.ts +24 -0
- package/src/utils/save-text-file.ts +36 -0
- package/src/utils/shortcut-labels.ts +14 -0
- package/src/components/chart/core/axis-format.ts +0 -157
- package/src/components/chart/core/index.test.ts +0 -1075
- package/src/components/chart/core/projection.ts +0 -357
- package/src/components/chart/core/render-utils.ts +0 -4
- package/src/components/chart/core/renderer.ts +0 -244
- package/src/components/chart/core/scene.ts +0 -180
- package/src/components/chart/core/time-axis.ts +0 -330
- package/src/components/chart/cursor-motion.ts +0 -47
- package/src/components/chart/indicators/types.ts +0 -26
- package/src/components/chart/market-session.ts +0 -176
- package/src/components/chart/multi-line/renderer.test.ts +0 -83
- package/src/components/chart/multi-line/renderer.ts +0 -161
- package/src/components/chart/multi-line/scene.ts +0 -188
- package/src/components/chart/native/raster/layout.ts +0 -129
- package/src/components/chart/native/raster/price-chart.ts +0 -244
- package/src/components/chart/rendering/chart-draw.ts +0 -270
- package/src/components/chart/rendering/geometry.ts +0 -95
- package/src/components/chart/rendering/pixel-buffer.ts +0 -267
- package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
- package/src/components/chart/scatter-chart-renderer.ts +0 -223
- package/src/components/chart/static/chart/surface.tsx +0 -358
- package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
- package/src/components/command-bar/routes/root/article-results.ts +0 -55
- package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
- package/src/plugins/builtin/news/wire/article-search.ts +0 -140
- package/src/plugins/builtin/substack/api/auth.ts +0 -185
- package/src/plugins/builtin/substack/api/cache.ts +0 -82
- package/src/plugins/builtin/substack/api/loaders.ts +0 -170
- package/src/plugins/builtin/substack/api/store.ts +0 -186
- package/src/plugins/builtin/substack/api/types.ts +0 -42
- package/src/plugins/builtin/substack/api.test.ts +0 -298
- package/src/plugins/builtin/substack/article-detail.tsx +0 -397
- package/src/plugins/builtin/substack/article-stack.tsx +0 -133
- package/src/plugins/builtin/substack/content.ts +0 -478
- package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
- package/src/plugins/builtin/substack/index.tsx +0 -49
- package/src/plugins/builtin/substack/login-view.tsx +0 -126
- package/src/plugins/builtin/substack/model.test.ts +0 -155
- package/src/plugins/builtin/substack/normalize.ts +0 -343
- package/src/plugins/builtin/substack/pane-footer.ts +0 -52
- package/src/plugins/builtin/substack/pane-state.ts +0 -170
- package/src/plugins/builtin/substack/pane.tsx +0 -547
- package/src/plugins/builtin/substack/read-state.ts +0 -30
- package/src/plugins/builtin/substack/table.ts +0 -111
- package/src/plugins/builtin/substack/types.ts +0 -71
- package/src/plugins/builtin/substack/utils.ts +0 -131
- package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
- package/src/plugins/ibkr/account-cache.test.ts +0 -102
- package/src/plugins/ibkr/account-cache.ts +0 -24
- package/src/plugins/ibkr/broker-adapter.ts +0 -286
- package/src/plugins/ibkr/config.test.ts +0 -79
- package/src/plugins/ibkr/config.ts +0 -250
- package/src/plugins/ibkr/dialogs.tsx +0 -40
- package/src/plugins/ibkr/flex/index.test.ts +0 -264
- package/src/plugins/ibkr/flex/index.ts +0 -367
- package/src/plugins/ibkr/flex/statement-client.ts +0 -235
- package/src/plugins/ibkr/fundamental-parser.ts +0 -149
- package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
- package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
- package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
- package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
- package/src/plugins/ibkr/gateway/connection.ts +0 -152
- package/src/plugins/ibkr/gateway/contracts.ts +0 -132
- package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
- package/src/plugins/ibkr/gateway/helpers.ts +0 -33
- package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
- package/src/plugins/ibkr/gateway/history.ts +0 -120
- package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
- package/src/plugins/ibkr/gateway/market-data.ts +0 -345
- package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
- package/src/plugins/ibkr/gateway/orders.ts +0 -63
- package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
- package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
- package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
- package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
- package/src/plugins/ibkr/gateway/selection.ts +0 -51
- package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
- package/src/plugins/ibkr/gateway/service/index.ts +0 -429
- package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
- package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
- package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
- package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
- package/src/plugins/ibkr/gateway/service/native.ts +0 -543
- package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
- package/src/plugins/ibkr/gateway/types.ts +0 -27
- package/src/plugins/ibkr/index.test.tsx +0 -162
- package/src/plugins/ibkr/index.tsx +0 -159
- package/src/plugins/ibkr/instance-selection.test.ts +0 -120
- package/src/plugins/ibkr/instance-selection.ts +0 -36
- package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
- package/src/plugins/ibkr/portfolio-performance.ts +0 -129
- package/src/plugins/ibkr/trade/badge.tsx +0 -28
- package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
- package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
- package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
- package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
- package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
- package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
- package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
- package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
- package/src/plugins/ibkr/trade/tab/model.ts +0 -159
- package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
- package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
- package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
- package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
- package/src/plugins/ibkr/trade/utils.test.ts +0 -175
- package/src/plugins/ibkr/trade/utils.ts +0 -144
- package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
- package/src/plugins/ibkr/trading/pane.tsx +0 -332
- package/src/plugins/ibkr/trading/state.test.ts +0 -109
- package/src/plugins/ibkr/trading/state.ts +0 -335
|
@@ -8,7 +8,9 @@ import type { TickerSearchCandidate } from "../../../../tickers/search";
|
|
|
8
8
|
import type { AssistRowHandlers } from "../../assist/model";
|
|
9
9
|
import { matchPrefix, type Command } from "../../commands/registry";
|
|
10
10
|
import type { ResultItem } from "../../list/model";
|
|
11
|
+
import type { CommandBarCategoryPriorities } from "../../view-model";
|
|
11
12
|
import type { CommandBarRoute } from "../../workflow/types";
|
|
13
|
+
import { normalizeCommandTickerSearchText } from "../ticker-search/results";
|
|
12
14
|
import { useTickerSearchRouteResults } from "../ticker-search/route";
|
|
13
15
|
import { buildRootResultModel, type RootResultModel } from "./results";
|
|
14
16
|
import { useRootProviderSearch } from "./provider-search";
|
|
@@ -33,7 +35,6 @@ interface UseCommandBarRootRuntimeOptions {
|
|
|
33
35
|
availableCommands: Command[];
|
|
34
36
|
buildLayoutItems(query: string, options?: { confirmDangerousActions?: boolean }): ResultItem[];
|
|
35
37
|
buildPaneSettingItems(paneId: string | null, query: string): ResultItem[];
|
|
36
|
-
buildPluginItems(query: string): ResultItem[];
|
|
37
38
|
buildTickerSearchResultItems(candidates: TickerSearchCandidate[], query: string): ResultItem[];
|
|
38
39
|
buildWindowModeItems(arg: string): ResultItem[];
|
|
39
40
|
createPaneTemplateItem(template: PaneTemplateDef, options?: {
|
|
@@ -55,7 +56,7 @@ interface UseCommandBarRootRuntimeOptions {
|
|
|
55
56
|
localTickerSearchResultItems(query?: string, options?: { category?: string; limit?: number }): ResultItem[];
|
|
56
57
|
nativeListScrollRef: RefObject<ScrollBoxRenderable | null>;
|
|
57
58
|
nonShortcutPaneTemplateItems(filterQuery?: string): ResultItem[];
|
|
58
|
-
openModeRoute(screen: "ticker-search" | "
|
|
59
|
+
openModeRoute(screen: "ticker-search" | "layout", initialQuery?: string): void;
|
|
59
60
|
paneShortcutItems(options?: {
|
|
60
61
|
filterQuery?: string;
|
|
61
62
|
createOptions?: PaneTemplateCreateOptions;
|
|
@@ -63,7 +64,9 @@ interface UseCommandBarRootRuntimeOptions {
|
|
|
63
64
|
}): ResultItem[];
|
|
64
65
|
pluginCommandItems(): ResultItem[];
|
|
65
66
|
pluginCommandResultItems(command: CommandDef, shortcutArg: string): ResultItem[];
|
|
66
|
-
|
|
67
|
+
providerResultItems?: ResultItem[];
|
|
68
|
+
providerCategoryPriorities?: CommandBarCategoryPriorities;
|
|
69
|
+
providerSearching?: boolean;
|
|
67
70
|
readTickerSearchCache(
|
|
68
71
|
query: string,
|
|
69
72
|
brokerId?: string | null,
|
|
@@ -97,7 +100,6 @@ export function useCommandBarRootRuntime({
|
|
|
97
100
|
availableCommands,
|
|
98
101
|
buildLayoutItems,
|
|
99
102
|
buildPaneSettingItems,
|
|
100
|
-
buildPluginItems,
|
|
101
103
|
buildTickerSearchResultItems,
|
|
102
104
|
buildWindowModeItems,
|
|
103
105
|
createPaneTemplateItem,
|
|
@@ -115,7 +117,9 @@ export function useCommandBarRootRuntime({
|
|
|
115
117
|
paneShortcutItems,
|
|
116
118
|
pluginCommandItems,
|
|
117
119
|
pluginCommandResultItems,
|
|
118
|
-
|
|
120
|
+
providerResultItems = [],
|
|
121
|
+
providerCategoryPriorities,
|
|
122
|
+
providerSearching = false,
|
|
119
123
|
readTickerSearchCache,
|
|
120
124
|
rootModeKind,
|
|
121
125
|
rootQuery,
|
|
@@ -173,7 +177,6 @@ export function useCommandBarRootRuntime({
|
|
|
173
177
|
availableCommands,
|
|
174
178
|
buildLayoutItems,
|
|
175
179
|
buildPaneSettingItems,
|
|
176
|
-
buildPluginItems,
|
|
177
180
|
buildWindowModeItems,
|
|
178
181
|
createPaneTemplateItem,
|
|
179
182
|
createPluginCommandItem,
|
|
@@ -189,7 +192,7 @@ export function useCommandBarRootRuntime({
|
|
|
189
192
|
pluginCommandResultItems,
|
|
190
193
|
rootQuery,
|
|
191
194
|
rootShortcutIntent,
|
|
192
|
-
|
|
195
|
+
providerResultItems,
|
|
193
196
|
runDirectCommand,
|
|
194
197
|
runSecurityDescriptionShortcut,
|
|
195
198
|
state,
|
|
@@ -202,7 +205,6 @@ export function useCommandBarRootRuntime({
|
|
|
202
205
|
availableCommands,
|
|
203
206
|
buildLayoutItems,
|
|
204
207
|
buildPaneSettingItems,
|
|
205
|
-
buildPluginItems,
|
|
206
208
|
buildWindowModeItems,
|
|
207
209
|
createPaneTemplateItem,
|
|
208
210
|
createPluginCommandItem,
|
|
@@ -218,7 +220,7 @@ export function useCommandBarRootRuntime({
|
|
|
218
220
|
pluginCommandResultItems,
|
|
219
221
|
rootQuery,
|
|
220
222
|
rootShortcutIntent,
|
|
221
|
-
|
|
223
|
+
providerResultItems,
|
|
222
224
|
runDirectCommand,
|
|
223
225
|
runSecurityDescriptionShortcut,
|
|
224
226
|
state,
|
|
@@ -228,27 +230,44 @@ export function useCommandBarRootRuntime({
|
|
|
228
230
|
const rootSecurityDescriptionArg = activeMatch?.command.id === "security-description" && activeMatch.arg.length >= 1
|
|
229
231
|
? activeMatch.arg
|
|
230
232
|
: null;
|
|
231
|
-
|
|
233
|
+
// Free text that no prefix claims also goes to symbol search, so "nvidia"
|
|
234
|
+
// finds NVDA without the backtick. Skipped when a local row already carries
|
|
235
|
+
// that exact name, since an "Exact Match" symbol would otherwise outrank it.
|
|
236
|
+
const rootPlainTickerSearchArg = useMemo(() => {
|
|
237
|
+
if (currentRoute || activeMatch || rootShortcutIntent.kind !== "none") return null;
|
|
238
|
+
const trimmed = rootQuery.trim();
|
|
239
|
+
if (trimmed.length < 2) return null;
|
|
240
|
+
const normalizedQuery = normalizeCommandTickerSearchText(trimmed);
|
|
241
|
+
const hasExactLocalRow = rootResultModel.items.some((item) => (
|
|
242
|
+
item.kind !== "ticker"
|
|
243
|
+
&& item.kind !== "search"
|
|
244
|
+
&& normalizeCommandTickerSearchText(item.label) === normalizedQuery
|
|
245
|
+
));
|
|
246
|
+
return hasExactLocalRow ? null : trimmed;
|
|
247
|
+
}, [activeMatch, currentRoute, rootQuery, rootResultModel.items, rootShortcutIntent.kind]);
|
|
248
|
+
const rootTickerSearchArg = rootSecurityDescriptionArg ?? rootPlainTickerSearchArg;
|
|
232
249
|
|
|
233
250
|
const {
|
|
234
251
|
activeRootProviderResultsKey,
|
|
235
252
|
orderedRootResults,
|
|
236
|
-
rootSearching,
|
|
253
|
+
rootSearching: tickerSearching,
|
|
237
254
|
rootSectionOrder,
|
|
238
255
|
} = useRootProviderSearch({
|
|
239
256
|
activeCollectionId,
|
|
240
257
|
buildTickerSearchResultItems,
|
|
258
|
+
categoryPriorities: providerCategoryPriorities,
|
|
241
259
|
currentRoute,
|
|
242
260
|
dataProvider,
|
|
243
261
|
localTickerSearchResultItems,
|
|
244
262
|
portfolios: state.config.portfolios,
|
|
245
263
|
readTickerSearchCache,
|
|
246
|
-
rootPlainTickerSearchArg
|
|
264
|
+
rootPlainTickerSearchArg,
|
|
247
265
|
rootResultItems: rootResultModel.items,
|
|
248
266
|
rootTickerSearchArg,
|
|
249
267
|
tickers: state.tickers,
|
|
250
268
|
writeTickerSearchCache,
|
|
251
269
|
});
|
|
270
|
+
const rootSearching = tickerSearching || providerSearching;
|
|
252
271
|
|
|
253
272
|
useLayoutEffect(() => {
|
|
254
273
|
if (currentRoute) return;
|
|
@@ -263,23 +282,21 @@ export function useCommandBarRootRuntime({
|
|
|
263
282
|
previousRootSelectionContextRef.current?.query !== rootQuery
|
|
264
283
|
|| previousRootSelectionContextRef.current?.mode !== rootModeKind;
|
|
265
284
|
previousRootSelectionContextRef.current = { query: rootQuery, mode: rootModeKind };
|
|
266
|
-
|
|
267
|
-
const keepSelectionAcrossQueries = activeMatch?.command.id === "plugins";
|
|
268
|
-
if (selectionContextChanged && !keepSelectionAcrossQueries) {
|
|
285
|
+
if (selectionContextChanged) {
|
|
269
286
|
rootSelectionNavigatedRef.current = false;
|
|
270
287
|
}
|
|
271
288
|
|
|
272
289
|
setRootSelectedIdx((current) => {
|
|
273
|
-
if (rootSelectionNavigatedRef.current
|
|
274
|
-
// The user picked this row
|
|
275
|
-
//
|
|
290
|
+
if (rootSelectionNavigatedRef.current) {
|
|
291
|
+
// The user picked this row. Async sections append below it, but an
|
|
292
|
+
// exact symbol match still lands above, so the row is tracked by id
|
|
293
|
+
// rather than by index.
|
|
276
294
|
const selectedId = previousResultIds[current];
|
|
277
295
|
const shiftedIdx = selectedId ? resultIds.indexOf(selectedId) : -1;
|
|
278
296
|
if (shiftedIdx >= 0) return shiftedIdx;
|
|
279
297
|
return clampSelectedIdx(current, resultIds.length);
|
|
280
298
|
}
|
|
281
|
-
// Untouched, the selection follows the best row on offer
|
|
282
|
-
// an AI answer becomes the moment it lands at the top of the list.
|
|
299
|
+
// Untouched, the selection follows the best row on offer.
|
|
283
300
|
const defaultIdx = orderedRootResults.findIndex(isDefaultSelectable);
|
|
284
301
|
return clampSelectedIdx(Math.max(rootResultModel.initialIdx, defaultIdx), resultIds.length);
|
|
285
302
|
});
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { act, useState } from "react";
|
|
3
|
+
import { testRender } from "../../../../renderers/opentui/test-utils";
|
|
4
|
+
import type { CommandBarResultDef, CommandBarSearchProvider } from "../../../../types/plugin";
|
|
5
|
+
import type { ResultItem } from "../../list/model";
|
|
6
|
+
import { toProviderResultItem, useCommandBarSearchProviders } from "./search-providers";
|
|
7
|
+
|
|
8
|
+
let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
|
|
9
|
+
let setHarnessQuery: ((query: string) => void) | null = null;
|
|
10
|
+
let latestItems: ResultItem[] = [];
|
|
11
|
+
let latestSearching = false;
|
|
12
|
+
|
|
13
|
+
function ProvidersHarness({
|
|
14
|
+
providers,
|
|
15
|
+
initialQuery,
|
|
16
|
+
}: {
|
|
17
|
+
providers: CommandBarSearchProvider[];
|
|
18
|
+
initialQuery: string;
|
|
19
|
+
}) {
|
|
20
|
+
const [query, setQuery] = useState(initialQuery);
|
|
21
|
+
setHarnessQuery = setQuery;
|
|
22
|
+
const { providerResultItems, providerSearching } = useCommandBarSearchProviders({
|
|
23
|
+
providers,
|
|
24
|
+
query,
|
|
25
|
+
enabled: true,
|
|
26
|
+
context: { activeTicker: null, activeCollectionId: null },
|
|
27
|
+
onExecuted: () => {},
|
|
28
|
+
});
|
|
29
|
+
latestItems = providerResultItems;
|
|
30
|
+
latestSearching = providerSearching;
|
|
31
|
+
return <text>{String(providerResultItems.length)}</text>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function renderHarness(providers: CommandBarSearchProvider[], initialQuery = ""): Promise<void> {
|
|
35
|
+
testSetup = await testRender(
|
|
36
|
+
<ProvidersHarness providers={providers} initialQuery={initialQuery} />,
|
|
37
|
+
{ width: 20, height: 1 },
|
|
38
|
+
);
|
|
39
|
+
await act(async () => {
|
|
40
|
+
await testSetup!.renderOnce();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function typeQuery(query: string): Promise<void> {
|
|
45
|
+
await act(async () => {
|
|
46
|
+
setHarnessQuery?.(query);
|
|
47
|
+
await testSetup!.renderOnce();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function settle(ms = 20): Promise<void> {
|
|
52
|
+
await act(async () => {
|
|
53
|
+
await Bun.sleep(ms);
|
|
54
|
+
await testSetup!.renderOnce();
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function makeResult(id: string): CommandBarResultDef {
|
|
59
|
+
return { id, label: id, execute: () => {} };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
afterEach(async () => {
|
|
63
|
+
if (testSetup) {
|
|
64
|
+
await act(async () => {
|
|
65
|
+
testSetup!.renderer.destroy();
|
|
66
|
+
});
|
|
67
|
+
testSetup = undefined;
|
|
68
|
+
}
|
|
69
|
+
setHarnessQuery = null;
|
|
70
|
+
latestItems = [];
|
|
71
|
+
latestSearching = false;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("command bar search providers", () => {
|
|
75
|
+
test("shows only the answer to the query still in the bar, and aborts the rest", async () => {
|
|
76
|
+
const asked: string[] = [];
|
|
77
|
+
const aborted: string[] = [];
|
|
78
|
+
const provider: CommandBarSearchProvider = {
|
|
79
|
+
id: "docs",
|
|
80
|
+
category: "Documents",
|
|
81
|
+
debounceMs: 0,
|
|
82
|
+
async provide(query, _context, signal) {
|
|
83
|
+
asked.push(query);
|
|
84
|
+
signal.addEventListener("abort", () => aborted.push(query));
|
|
85
|
+
// The slower first request would otherwise land on top of the newer one.
|
|
86
|
+
await Bun.sleep(query === "margin" ? 40 : 0);
|
|
87
|
+
return [makeResult(query)];
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
await renderHarness([provider], "margin");
|
|
92
|
+
await settle(5);
|
|
93
|
+
await typeQuery("margin pressure");
|
|
94
|
+
await settle(60);
|
|
95
|
+
|
|
96
|
+
expect(asked).toEqual(["margin", "margin pressure"]);
|
|
97
|
+
expect(aborted).toEqual(["margin"]);
|
|
98
|
+
expect(latestItems.map((item) => item.label)).toEqual(["margin pressure"]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("does not ask again for a query it has already answered", async () => {
|
|
102
|
+
let calls = 0;
|
|
103
|
+
const provider: CommandBarSearchProvider = {
|
|
104
|
+
id: "docs",
|
|
105
|
+
category: "Documents",
|
|
106
|
+
debounceMs: 0,
|
|
107
|
+
async provide(query) {
|
|
108
|
+
calls += 1;
|
|
109
|
+
return [makeResult(query)];
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
await renderHarness([provider], "margin");
|
|
114
|
+
await settle();
|
|
115
|
+
await typeQuery("margin pressure");
|
|
116
|
+
await settle();
|
|
117
|
+
await typeQuery("margin");
|
|
118
|
+
await settle();
|
|
119
|
+
|
|
120
|
+
expect(calls).toBe(2);
|
|
121
|
+
expect(latestItems.map((item) => item.label)).toEqual(["margin"]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("contributes nothing when it fails, and stops reporting itself as loading", async () => {
|
|
125
|
+
const provider: CommandBarSearchProvider = {
|
|
126
|
+
id: "docs",
|
|
127
|
+
category: "Documents",
|
|
128
|
+
debounceMs: 0,
|
|
129
|
+
async provide() {
|
|
130
|
+
throw new Error("402");
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
await renderHarness([provider], "margin");
|
|
135
|
+
await settle();
|
|
136
|
+
|
|
137
|
+
expect(latestItems).toEqual([]);
|
|
138
|
+
expect(latestSearching).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("stays quiet below the provider's minimum query length", async () => {
|
|
142
|
+
let calls = 0;
|
|
143
|
+
const provider: CommandBarSearchProvider = {
|
|
144
|
+
id: "docs",
|
|
145
|
+
category: "Documents",
|
|
146
|
+
debounceMs: 0,
|
|
147
|
+
minQueryLength: 4,
|
|
148
|
+
async provide(query) {
|
|
149
|
+
calls += 1;
|
|
150
|
+
return [makeResult(query)];
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
await renderHarness([provider], "mar");
|
|
155
|
+
await settle();
|
|
156
|
+
expect(calls).toBe(0);
|
|
157
|
+
expect(latestSearching).toBe(false);
|
|
158
|
+
|
|
159
|
+
await typeQuery("marg");
|
|
160
|
+
await settle();
|
|
161
|
+
expect(calls).toBe(1);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("orders sections by provider priority", async () => {
|
|
165
|
+
const provider = (id: string, priority: number): CommandBarSearchProvider => ({
|
|
166
|
+
id,
|
|
167
|
+
category: id,
|
|
168
|
+
priority,
|
|
169
|
+
debounceMs: 0,
|
|
170
|
+
async provide() {
|
|
171
|
+
return [makeResult(id)];
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
await renderHarness([provider("late", 50), provider("early", 10)], "margin");
|
|
176
|
+
await settle();
|
|
177
|
+
|
|
178
|
+
expect(latestItems.map((item) => item.category)).toEqual(["early", "late"]);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe("toProviderResultItem", () => {
|
|
183
|
+
test("namespaces the row, keeps its lines, and closes the bar after it runs", async () => {
|
|
184
|
+
let executed = 0;
|
|
185
|
+
let closed = 0;
|
|
186
|
+
const item = toProviderResultItem(
|
|
187
|
+
{ id: "docs", category: "Documents", provide: async () => [] },
|
|
188
|
+
{
|
|
189
|
+
id: "hit-1",
|
|
190
|
+
label: "Q3 call",
|
|
191
|
+
detail: "CALL · Feb 02, 26",
|
|
192
|
+
right: "AAPL",
|
|
193
|
+
keywords: ["transcript"],
|
|
194
|
+
lines: [{ segments: [{ text: "margin ", emphasis: "muted" }, { text: "pressure", emphasis: "match" }] }],
|
|
195
|
+
execute: () => { executed += 1; },
|
|
196
|
+
},
|
|
197
|
+
() => { closed += 1; },
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
expect(item.id).toBe("search-provider:docs:hit-1");
|
|
201
|
+
expect(item.category).toBe("Documents");
|
|
202
|
+
expect(item.right).toBe("AAPL");
|
|
203
|
+
expect(item.lines?.[0]?.segments[1]?.emphasis).toBe("match");
|
|
204
|
+
expect(item.searchText).toContain("transcript");
|
|
205
|
+
|
|
206
|
+
await item.action();
|
|
207
|
+
expect(executed).toBe(1);
|
|
208
|
+
expect(closed).toBe(1);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("does not run or close for a disabled row", async () => {
|
|
212
|
+
let executed = 0;
|
|
213
|
+
let closed = 0;
|
|
214
|
+
const item = toProviderResultItem(
|
|
215
|
+
{ id: "docs", category: "Documents", provide: async () => [] },
|
|
216
|
+
{ id: "gate", label: "Upgrade", disabled: true, execute: () => { executed += 1; } },
|
|
217
|
+
() => { closed += 1; },
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
await item.action();
|
|
221
|
+
expect(executed).toBe(0);
|
|
222
|
+
expect(closed).toBe(0);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import type { PluginRegistry } from "../../../../plugins/registry";
|
|
3
|
+
import type {
|
|
4
|
+
CommandBarResultDef,
|
|
5
|
+
CommandBarSearchContext,
|
|
6
|
+
CommandBarSearchProvider,
|
|
7
|
+
} from "../../../../types/plugin";
|
|
8
|
+
import type { ResultItem } from "../../list/model";
|
|
9
|
+
|
|
10
|
+
const DEFAULT_MIN_QUERY_LENGTH = 3;
|
|
11
|
+
const DEFAULT_DEBOUNCE_MS = 300;
|
|
12
|
+
|
|
13
|
+
export function getAvailableCommandBarSearchProviders(
|
|
14
|
+
pluginRegistry: Pick<PluginRegistry, "commandBarSearchProviders" | "getCommandBarSearchProviderPluginId">,
|
|
15
|
+
disabledPlugins: readonly string[],
|
|
16
|
+
): CommandBarSearchProvider[] {
|
|
17
|
+
const disabledPluginIds = new Set(disabledPlugins);
|
|
18
|
+
return [...pluginRegistry.commandBarSearchProviders.values()].filter((provider) => {
|
|
19
|
+
const pluginId = pluginRegistry.getCommandBarSearchProviderPluginId(provider.id);
|
|
20
|
+
return !pluginId || !disabledPluginIds.has(pluginId);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Turns one provider row into a list row. The command bar owns the row model, so
|
|
26
|
+
* a provider never gets to decide selection, kind, or what closing means.
|
|
27
|
+
*/
|
|
28
|
+
export function toProviderResultItem(
|
|
29
|
+
provider: CommandBarSearchProvider,
|
|
30
|
+
result: CommandBarResultDef,
|
|
31
|
+
onExecuted: () => void,
|
|
32
|
+
): ResultItem {
|
|
33
|
+
return {
|
|
34
|
+
id: `search-provider:${provider.id}:${result.id}`,
|
|
35
|
+
label: result.label,
|
|
36
|
+
detail: result.detail ?? "",
|
|
37
|
+
category: result.category ?? provider.category,
|
|
38
|
+
kind: "action",
|
|
39
|
+
lines: result.lines,
|
|
40
|
+
badge: result.badge,
|
|
41
|
+
right: result.right,
|
|
42
|
+
searchText: [result.label, result.detail ?? "", ...(result.keywords ?? [])].join(" "),
|
|
43
|
+
disabled: result.disabled,
|
|
44
|
+
action: async () => {
|
|
45
|
+
if (result.disabled) return;
|
|
46
|
+
await result.execute();
|
|
47
|
+
onExecuted();
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface ProviderResults {
|
|
53
|
+
/** The exact query these rows answer; anything else on screen is stale. */
|
|
54
|
+
query: string;
|
|
55
|
+
items: ResultItem[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface UseCommandBarSearchProvidersOptions {
|
|
59
|
+
providers: readonly CommandBarSearchProvider[];
|
|
60
|
+
query: string;
|
|
61
|
+
/** False while a route is open or a prefix already claimed the query. */
|
|
62
|
+
enabled: boolean;
|
|
63
|
+
context: CommandBarSearchContext;
|
|
64
|
+
onExecuted: () => void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Runs plugin search providers for the root list. Each provider debounces and
|
|
69
|
+
* aborts on its own, answers are memoized for the life of the bar, and rows are
|
|
70
|
+
* only ever added to the static list: a provider that fails, hangs, or answers
|
|
71
|
+
* late leaves what the command bar already resolved exactly as it was.
|
|
72
|
+
*/
|
|
73
|
+
export function useCommandBarSearchProviders({
|
|
74
|
+
providers,
|
|
75
|
+
query,
|
|
76
|
+
enabled,
|
|
77
|
+
context,
|
|
78
|
+
onExecuted,
|
|
79
|
+
}: UseCommandBarSearchProvidersOptions): {
|
|
80
|
+
providerResultItems: ResultItem[];
|
|
81
|
+
providerSearching: boolean;
|
|
82
|
+
} {
|
|
83
|
+
const [resultsByProvider, setResultsByProvider] = useState<Record<string, ProviderResults>>({});
|
|
84
|
+
const [loadingProviderIds, setLoadingProviderIds] = useState<readonly string[]>([]);
|
|
85
|
+
const cacheRef = useRef(new Map<string, CommandBarResultDef[]>());
|
|
86
|
+
const timersRef = useRef(new Map<string, ReturnType<typeof setTimeout>>());
|
|
87
|
+
const abortsRef = useRef(new Map<string, AbortController>());
|
|
88
|
+
/**
|
|
89
|
+
* Query each provider has already been asked for. Rendered state lags a tick
|
|
90
|
+
* behind, so reading it instead would let a re-render start a second request
|
|
91
|
+
* for text that is already in flight.
|
|
92
|
+
*/
|
|
93
|
+
const handledQueriesRef = useRef(new Map<string, string>());
|
|
94
|
+
/** Read at request time only: selecting a ticker is not a new question. */
|
|
95
|
+
const contextRef = useRef(context);
|
|
96
|
+
contextRef.current = context;
|
|
97
|
+
const onExecutedRef = useRef(onExecuted);
|
|
98
|
+
onExecutedRef.current = onExecuted;
|
|
99
|
+
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
const trimmed = query.trim();
|
|
102
|
+
const cancel = (providerId: string) => {
|
|
103
|
+
const timer = timersRef.current.get(providerId);
|
|
104
|
+
if (timer) {
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
timersRef.current.delete(providerId);
|
|
107
|
+
}
|
|
108
|
+
abortsRef.current.get(providerId)?.abort();
|
|
109
|
+
abortsRef.current.delete(providerId);
|
|
110
|
+
handledQueriesRef.current.delete(providerId);
|
|
111
|
+
};
|
|
112
|
+
const setLoading = (providerId: string, loading: boolean) => {
|
|
113
|
+
setLoadingProviderIds((current) => {
|
|
114
|
+
const isLoading = current.includes(providerId);
|
|
115
|
+
if (loading === isLoading) return current;
|
|
116
|
+
return loading ? [...current, providerId] : current.filter((id) => id !== providerId);
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
const publish = (provider: CommandBarSearchProvider, forQuery: string, results: CommandBarResultDef[]) => {
|
|
120
|
+
setResultsByProvider((current) => ({
|
|
121
|
+
...current,
|
|
122
|
+
[provider.id]: {
|
|
123
|
+
query: forQuery,
|
|
124
|
+
items: results.map((result) => toProviderResultItem(
|
|
125
|
+
provider,
|
|
126
|
+
result,
|
|
127
|
+
() => onExecutedRef.current(),
|
|
128
|
+
)),
|
|
129
|
+
},
|
|
130
|
+
}));
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const activeProviderIds = new Set(providers.map((provider) => provider.id));
|
|
134
|
+
for (const providerId of [...handledQueriesRef.current.keys()]) {
|
|
135
|
+
if (!activeProviderIds.has(providerId)) cancel(providerId);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (const provider of providers) {
|
|
139
|
+
const minLength = provider.minQueryLength ?? DEFAULT_MIN_QUERY_LENGTH;
|
|
140
|
+
if (!enabled || trimmed.length < minLength) {
|
|
141
|
+
cancel(provider.id);
|
|
142
|
+
setLoading(provider.id, false);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (handledQueriesRef.current.get(provider.id) === trimmed) continue;
|
|
146
|
+
|
|
147
|
+
cancel(provider.id);
|
|
148
|
+
handledQueriesRef.current.set(provider.id, trimmed);
|
|
149
|
+
|
|
150
|
+
const cached = cacheRef.current.get(`${provider.id}\n${trimmed}`);
|
|
151
|
+
if (cached) {
|
|
152
|
+
setLoading(provider.id, false);
|
|
153
|
+
publish(provider, trimmed, cached);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
setLoading(provider.id, true);
|
|
158
|
+
timersRef.current.set(provider.id, setTimeout(() => {
|
|
159
|
+
timersRef.current.delete(provider.id);
|
|
160
|
+
const controller = new AbortController();
|
|
161
|
+
abortsRef.current.set(provider.id, controller);
|
|
162
|
+
void (async () => {
|
|
163
|
+
try {
|
|
164
|
+
const results = await provider.provide(trimmed, contextRef.current, controller.signal);
|
|
165
|
+
if (controller.signal.aborted) return;
|
|
166
|
+
cacheRef.current.set(`${provider.id}\n${trimmed}`, results);
|
|
167
|
+
publish(provider, trimmed, results);
|
|
168
|
+
} catch {
|
|
169
|
+
// A provider is an extra, never a precondition: its failure leaves
|
|
170
|
+
// the rows the command bar resolved locally untouched.
|
|
171
|
+
if (controller.signal.aborted) return;
|
|
172
|
+
publish(provider, trimmed, []);
|
|
173
|
+
} finally {
|
|
174
|
+
if (abortsRef.current.get(provider.id) === controller) {
|
|
175
|
+
abortsRef.current.delete(provider.id);
|
|
176
|
+
setLoading(provider.id, false);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
})();
|
|
180
|
+
}, provider.debounceMs ?? DEFAULT_DEBOUNCE_MS));
|
|
181
|
+
}
|
|
182
|
+
}, [enabled, providers, query]);
|
|
183
|
+
|
|
184
|
+
useEffect(() => () => {
|
|
185
|
+
for (const timer of timersRef.current.values()) clearTimeout(timer);
|
|
186
|
+
timersRef.current.clear();
|
|
187
|
+
for (const controller of abortsRef.current.values()) controller.abort();
|
|
188
|
+
abortsRef.current.clear();
|
|
189
|
+
}, []);
|
|
190
|
+
|
|
191
|
+
const providerResultItems = useMemo(() => {
|
|
192
|
+
const trimmed = query.trim();
|
|
193
|
+
if (!enabled) return [];
|
|
194
|
+
return [...providers]
|
|
195
|
+
.sort((left, right) => (left.priority ?? 0) - (right.priority ?? 0))
|
|
196
|
+
.flatMap((provider) => {
|
|
197
|
+
const results = resultsByProvider[provider.id];
|
|
198
|
+
return results?.query === trimmed ? results.items : [];
|
|
199
|
+
});
|
|
200
|
+
}, [enabled, providers, query, resultsByProvider]);
|
|
201
|
+
|
|
202
|
+
const providerSearching = enabled && providers.some((provider) => loadingProviderIds.includes(provider.id));
|
|
203
|
+
|
|
204
|
+
return { providerResultItems, providerSearching };
|
|
205
|
+
}
|
|
@@ -32,7 +32,7 @@ interface RootSelectionCommandOptions {
|
|
|
32
32
|
getAvailablePaneShortcutTemplates: (query: string) => PaneTemplateDef[];
|
|
33
33
|
getAvailablePluginCommands: () => CommandDef[];
|
|
34
34
|
openModeRoute: (
|
|
35
|
-
screen: "ticker-search" | "
|
|
35
|
+
screen: "ticker-search" | "layout",
|
|
36
36
|
initialQuery?: string,
|
|
37
37
|
payload?: Record<string, unknown>,
|
|
38
38
|
) => void;
|
|
@@ -185,17 +185,6 @@ export function buildImmediateRootSelection(options: RootSelectionCommandOptions
|
|
|
185
185
|
};
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
if (match.command.id === "plugins") {
|
|
189
|
-
return {
|
|
190
|
-
id: "plugins-route",
|
|
191
|
-
label: "Manage Plugins",
|
|
192
|
-
detail: "Toggle optional plugins without leaving the command bar",
|
|
193
|
-
category: "Plugins",
|
|
194
|
-
kind: "command",
|
|
195
|
-
action: () => options.openModeRoute("plugins", match.arg),
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
188
|
if (match.command.id === "layout") {
|
|
200
189
|
return {
|
|
201
190
|
id: "layout-route",
|
|
@@ -40,11 +40,11 @@ const paneTemplates: PaneTemplateDef[] = [
|
|
|
40
40
|
},
|
|
41
41
|
tickerTemplate("earnings-estimates-pane", "EE"),
|
|
42
42
|
{
|
|
43
|
-
id: "
|
|
44
|
-
paneId: "
|
|
45
|
-
label: "
|
|
46
|
-
description: "
|
|
47
|
-
shortcut: { prefix: "SRCH", argPlaceholder: "query", argKind: "text" },
|
|
43
|
+
id: "research-search-pane",
|
|
44
|
+
paneId: "research-search",
|
|
45
|
+
label: "Research Search",
|
|
46
|
+
description: "Full-text document search",
|
|
47
|
+
shortcut: { prefix: "SRCH", argPlaceholder: "query", argKind: "text", argOptional: true },
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
id: "cds-pane",
|
|
@@ -125,19 +125,19 @@ describe("ticker data root shortcuts", () => {
|
|
|
125
125
|
if (intent.kind === "none") throw new Error("Expected shortcut intent");
|
|
126
126
|
expect(intent.source).toBe("pane-template");
|
|
127
127
|
if (intent.source === "pane-template") {
|
|
128
|
-
expect(intent.template.id).toBe("
|
|
128
|
+
expect(intent.template.id).toBe("research-search-pane");
|
|
129
129
|
expect(intent.argText).toBe("apple inc");
|
|
130
130
|
expect(intent.completionQuery).toBeNull();
|
|
131
131
|
}
|
|
132
132
|
});
|
|
133
133
|
|
|
134
|
-
test("SRCH without a query remains a
|
|
134
|
+
test("SRCH without a query remains a document search shortcut", () => {
|
|
135
135
|
const intent = parse("SRCH");
|
|
136
136
|
expect(intent.kind).toBe("partial");
|
|
137
137
|
if (intent.kind === "none") throw new Error("Expected shortcut intent");
|
|
138
138
|
expect(intent.source).toBe("pane-template");
|
|
139
139
|
if (intent.source === "pane-template") {
|
|
140
|
-
expect(intent.template.id).toBe("
|
|
140
|
+
expect(intent.template.id).toBe("research-search-pane");
|
|
141
141
|
expect(intent.argText).toBe("");
|
|
142
142
|
}
|
|
143
143
|
});
|