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
|
@@ -4,7 +4,7 @@ import type { QuoteSubscriptionTarget } from "../../types/data-provider";
|
|
|
4
4
|
import type { Quote } from "../../types/financials";
|
|
5
5
|
import { debugLog } from "../../utils/debug-log";
|
|
6
6
|
import { normalizeSymbol } from "../../utils/exchanges";
|
|
7
|
-
import { getSharedMarketDataCoordinator } from "../../market-data/coordinator";
|
|
7
|
+
import { getSharedMarketDataCoordinator, type MarketDataCoordinator } from "../../market-data/coordinator";
|
|
8
8
|
import { useQuoteEntries } from "../../market-data/hooks";
|
|
9
9
|
import type { InstrumentRef } from "../../market-data/request-types";
|
|
10
10
|
import type { QueryEntry } from "../../market-data/result-types";
|
|
@@ -33,12 +33,11 @@ export function normalizeQuoteStreamSubscriptionTarget(target: QuoteSubscription
|
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export function
|
|
36
|
+
export function buildQuoteStreamSubscriptionIdentityKey(target: QuoteSubscriptionTarget): string {
|
|
37
37
|
const contractKey = target.context?.instrument?.conId
|
|
38
38
|
?? target.context?.instrument?.localSymbol
|
|
39
39
|
?? target.context?.instrument?.symbol
|
|
40
40
|
?? "";
|
|
41
|
-
const weight = Number.isFinite(target.weight) ? String(target.weight) : "";
|
|
42
41
|
return [
|
|
43
42
|
target.symbol,
|
|
44
43
|
target.exchange ?? "",
|
|
@@ -46,6 +45,13 @@ export function buildQuoteStreamSubscriptionKey(target: QuoteSubscriptionTarget)
|
|
|
46
45
|
target.context?.brokerInstanceId ?? "",
|
|
47
46
|
contractKey,
|
|
48
47
|
target.route ?? "auto",
|
|
48
|
+
].join("|");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function buildQuoteStreamSubscriptionKey(target: QuoteSubscriptionTarget): string {
|
|
52
|
+
const weight = Number.isFinite(target.weight) ? String(target.weight) : "";
|
|
53
|
+
return [
|
|
54
|
+
buildQuoteStreamSubscriptionIdentityKey(target),
|
|
49
55
|
target.surface ?? "",
|
|
50
56
|
target.visible ? "visible" : "",
|
|
51
57
|
target.selected ? "selected" : "",
|
|
@@ -53,6 +59,37 @@ export function buildQuoteStreamSubscriptionKey(target: QuoteSubscriptionTarget)
|
|
|
53
59
|
].join("|");
|
|
54
60
|
}
|
|
55
61
|
|
|
62
|
+
type CoordinatorQuoteTargets = Parameters<MarketDataCoordinator["subscribeQuotes"]>[0];
|
|
63
|
+
type UpdateableQuoteSubscription = (() => void) & {
|
|
64
|
+
update?: (targets: CoordinatorQuoteTargets) => void;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
interface ActiveQuoteSubscription {
|
|
68
|
+
identityKey: string;
|
|
69
|
+
subscriptionKey: string;
|
|
70
|
+
count: number;
|
|
71
|
+
dispose: UpdateableQuoteSubscription;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function toCoordinatorQuoteTargets(targets: QuoteSubscriptionTarget[]): CoordinatorQuoteTargets {
|
|
75
|
+
return targets.map((target) => ({
|
|
76
|
+
instrument: {
|
|
77
|
+
symbol: target.symbol,
|
|
78
|
+
exchange: target.exchange,
|
|
79
|
+
brokerId: target.context?.brokerId,
|
|
80
|
+
brokerInstanceId: target.context?.brokerInstanceId,
|
|
81
|
+
instrument: target.context?.instrument ?? null,
|
|
82
|
+
},
|
|
83
|
+
priority: {
|
|
84
|
+
route: target.route,
|
|
85
|
+
surface: target.surface,
|
|
86
|
+
visible: target.visible,
|
|
87
|
+
selected: target.selected,
|
|
88
|
+
weight: target.weight,
|
|
89
|
+
},
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
|
|
56
93
|
export function useQuoteStreaming(
|
|
57
94
|
targets: QuoteSubscriptionTarget[],
|
|
58
95
|
{ enabled = true }: QuoteStreamingOptions = {},
|
|
@@ -70,48 +107,86 @@ export function useQuoteStreaming(
|
|
|
70
107
|
const sortedEntries = [...normalizedEntries.entries()].sort(([left], [right]) => left.localeCompare(right));
|
|
71
108
|
const normalizedTargets = sortedEntries.map(([, target]) => target);
|
|
72
109
|
const subscriptionKey = sortedEntries.map(([key]) => key).join("|");
|
|
110
|
+
const identityKey = [...new Set(normalizedTargets.map(buildQuoteStreamSubscriptionIdentityKey))]
|
|
111
|
+
.sort()
|
|
112
|
+
.join("\u001f");
|
|
113
|
+
const coordinatorTargets = toCoordinatorQuoteTargets(normalizedTargets);
|
|
114
|
+
const latestSubscriptionRef = useRef({
|
|
115
|
+
identityKey,
|
|
116
|
+
subscriptionKey,
|
|
117
|
+
targets: coordinatorTargets,
|
|
118
|
+
});
|
|
119
|
+
latestSubscriptionRef.current = {
|
|
120
|
+
identityKey,
|
|
121
|
+
subscriptionKey,
|
|
122
|
+
targets: coordinatorTargets,
|
|
123
|
+
};
|
|
124
|
+
const activeSubscriptionRef = useRef<ActiveQuoteSubscription | null>(null);
|
|
73
125
|
|
|
74
126
|
useEffect(() => {
|
|
127
|
+
const latest = latestSubscriptionRef.current;
|
|
75
128
|
if (!enabled || !appActive) {
|
|
76
|
-
if (
|
|
129
|
+
if (latest.targets.length > 0) {
|
|
77
130
|
quoteStreamLog.info("skipping subscription", {
|
|
78
131
|
reason: enabled ? "inactive" : "disabled",
|
|
79
|
-
targets: subscriptionKey,
|
|
132
|
+
targets: latest.subscriptionKey,
|
|
80
133
|
});
|
|
81
134
|
}
|
|
82
135
|
return;
|
|
83
136
|
}
|
|
84
|
-
if (!coordinator ||
|
|
137
|
+
if (!coordinator || latest.targets.length === 0) return;
|
|
85
138
|
quoteStreamLog.info("subscribe", {
|
|
86
139
|
providerId: "market-data",
|
|
87
|
-
count:
|
|
88
|
-
targets: subscriptionKey,
|
|
140
|
+
count: latest.targets.length,
|
|
141
|
+
targets: latest.subscriptionKey,
|
|
89
142
|
});
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
},
|
|
98
|
-
priority: {
|
|
99
|
-
route: target.route,
|
|
100
|
-
surface: target.surface,
|
|
101
|
-
visible: target.visible,
|
|
102
|
-
selected: target.selected,
|
|
103
|
-
weight: target.weight,
|
|
104
|
-
},
|
|
105
|
-
})));
|
|
143
|
+
const active: ActiveQuoteSubscription = {
|
|
144
|
+
identityKey: latest.identityKey,
|
|
145
|
+
subscriptionKey: latest.subscriptionKey,
|
|
146
|
+
count: latest.targets.length,
|
|
147
|
+
dispose: coordinator.subscribeQuotes(latest.targets) as UpdateableQuoteSubscription,
|
|
148
|
+
};
|
|
149
|
+
activeSubscriptionRef.current = active;
|
|
106
150
|
return () => {
|
|
151
|
+
if (activeSubscriptionRef.current === active) {
|
|
152
|
+
activeSubscriptionRef.current = null;
|
|
153
|
+
}
|
|
107
154
|
quoteStreamLog.info("unsubscribe", {
|
|
108
155
|
providerId: "market-data",
|
|
109
|
-
count:
|
|
110
|
-
targets: subscriptionKey,
|
|
156
|
+
count: active.count,
|
|
157
|
+
targets: active.subscriptionKey,
|
|
111
158
|
});
|
|
112
|
-
|
|
159
|
+
active.dispose();
|
|
113
160
|
};
|
|
114
|
-
}, [appActive, coordinator, enabled,
|
|
161
|
+
}, [appActive, coordinator, enabled, identityKey]);
|
|
162
|
+
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (!enabled || !appActive || !coordinator) return;
|
|
165
|
+
const active = activeSubscriptionRef.current;
|
|
166
|
+
const latest = latestSubscriptionRef.current;
|
|
167
|
+
if (
|
|
168
|
+
!active
|
|
169
|
+
|| active.identityKey !== latest.identityKey
|
|
170
|
+
|| active.subscriptionKey === latest.subscriptionKey
|
|
171
|
+
) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
quoteStreamLog.info("update subscription", {
|
|
176
|
+
providerId: "market-data",
|
|
177
|
+
count: latest.targets.length,
|
|
178
|
+
targets: latest.subscriptionKey,
|
|
179
|
+
});
|
|
180
|
+
if (typeof active.dispose.update === "function") {
|
|
181
|
+
active.dispose.update(latest.targets);
|
|
182
|
+
} else {
|
|
183
|
+
const previousDispose = active.dispose;
|
|
184
|
+
active.dispose = coordinator.subscribeQuotes(latest.targets) as UpdateableQuoteSubscription;
|
|
185
|
+
previousDispose();
|
|
186
|
+
}
|
|
187
|
+
active.subscriptionKey = latest.subscriptionKey;
|
|
188
|
+
active.count = latest.targets.length;
|
|
189
|
+
}, [appActive, coordinator, enabled, identityKey, subscriptionKey]);
|
|
115
190
|
}
|
|
116
191
|
|
|
117
192
|
export function useQuoteUpdates(
|
|
@@ -195,8 +270,9 @@ export function useLiveQuoteEntries(
|
|
|
195
270
|
const normalized = normalizeQuoteStreamSubscriptionTarget(target);
|
|
196
271
|
return normalized ? [normalized] : [];
|
|
197
272
|
});
|
|
198
|
-
const targetKey =
|
|
199
|
-
.map((target) =>
|
|
273
|
+
const targetKey = [...new Set(
|
|
274
|
+
normalizedTargets.map((target) => buildQuoteStreamSubscriptionIdentityKey(target)),
|
|
275
|
+
)]
|
|
200
276
|
.sort()
|
|
201
277
|
.join("\u001f");
|
|
202
278
|
const liveStreaming = options.liveStreaming !== false;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createCsvExportFilename } from "../utils/csv";
|
|
2
|
+
|
|
3
|
+
export type PaneTableExporter = (filename: string) => Promise<string>;
|
|
4
|
+
|
|
5
|
+
const exporters = new Map<string, Map<symbol, PaneTableExporter>>();
|
|
6
|
+
|
|
7
|
+
export function registerPaneTableExporter(
|
|
8
|
+
paneId: string,
|
|
9
|
+
exporter: PaneTableExporter,
|
|
10
|
+
): () => void {
|
|
11
|
+
const token = Symbol(paneId);
|
|
12
|
+
const paneExporters = exporters.get(paneId) ?? new Map<symbol, PaneTableExporter>();
|
|
13
|
+
paneExporters.set(token, exporter);
|
|
14
|
+
exporters.set(paneId, paneExporters);
|
|
15
|
+
|
|
16
|
+
return () => {
|
|
17
|
+
const current = exporters.get(paneId);
|
|
18
|
+
current?.delete(token);
|
|
19
|
+
if (current?.size === 0) exporters.delete(paneId);
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function hasPaneTableExporter(paneId: string): boolean {
|
|
24
|
+
return exporters.get(paneId)?.size === 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function exportPaneTable(paneId: string, filename: string): Promise<string> {
|
|
28
|
+
const paneExporters = exporters.get(paneId);
|
|
29
|
+
if (paneExporters?.size !== 1) {
|
|
30
|
+
throw new Error("This pane does not have one active exportable table.");
|
|
31
|
+
}
|
|
32
|
+
return [...paneExporters.values()][0]!(filename);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Shared entry point for the pane menu, the keyboard shortcut, and pane settings so
|
|
37
|
+
* every surface produces the same filename and the same success/failure notification.
|
|
38
|
+
*/
|
|
39
|
+
export async function exportPaneTableCsv(
|
|
40
|
+
paneId: string,
|
|
41
|
+
title: string,
|
|
42
|
+
notify: (notification: { body: string; type: "success" | "error" }) => void,
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
try {
|
|
45
|
+
const location = await exportPaneTable(paneId, createCsvExportFilename(title));
|
|
46
|
+
notify({ body: `Exported to ${location}`, type: "success" });
|
|
47
|
+
} catch {
|
|
48
|
+
notify({ body: "Failed to export CSV", type: "error" });
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/theme/color-utils.ts
CHANGED
|
@@ -10,7 +10,7 @@ export function blendHex(a: string, b: string, ratio: number): string {
|
|
|
10
10
|
return `#${mix(ar, br)}${mix(ag, bg)}${mix(ab, bb)}`;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
function relativeLuminance(hex: string): number {
|
|
13
|
+
export function relativeLuminance(hex: string): number {
|
|
14
14
|
const h = hex.replace("#", "");
|
|
15
15
|
const toLinear = (value: string) => {
|
|
16
16
|
const normalized = parseInt(value, 16) / 255;
|
package/src/theme/colors.ts
CHANGED
|
@@ -193,6 +193,56 @@ export function commandBarAccentText(palette: ThemeColors = colors): string {
|
|
|
193
193
|
);
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Matched terms inside a result row's snippet. It has to stay legible on the
|
|
198
|
+
* selected row too, so contrast is checked against the selection background as
|
|
199
|
+
* well as the two panel surfaces.
|
|
200
|
+
*/
|
|
201
|
+
export function commandBarMatchText(palette: ThemeColors = colors): string {
|
|
202
|
+
const surfaces = [
|
|
203
|
+
commandBarBg(palette),
|
|
204
|
+
commandBarPanelBg(palette),
|
|
205
|
+
commandBarSelectedBg(palette),
|
|
206
|
+
] as const;
|
|
207
|
+
return blendForContrastOnSurfaces(
|
|
208
|
+
palette.warning,
|
|
209
|
+
surfaces,
|
|
210
|
+
higherContrast("#ffffff", "#000000", surfaces[0]),
|
|
211
|
+
3.6,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export type CommandBarBadgeTone = "command" | "instrument" | "document" | "assist";
|
|
216
|
+
|
|
217
|
+
function commandBarBadgeHue(tone: CommandBarBadgeTone, palette: ThemeColors): string {
|
|
218
|
+
if (tone === "instrument") return palette.warning;
|
|
219
|
+
if (tone === "document") return palette.neutral;
|
|
220
|
+
if (tone === "assist") return ASSIST_ACCENT;
|
|
221
|
+
return palette.borderFocused;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Text-only tag left of a result label. One hue per family so commands,
|
|
226
|
+
* instruments, documents and AI answers scan apart without a filled box, pulled
|
|
227
|
+
* towards the subtle text so the tag stays quieter than the label, then pushed
|
|
228
|
+
* only until it clears every surface it can land on, the selected row included.
|
|
229
|
+
*/
|
|
230
|
+
export function commandBarBadgeText(tone: CommandBarBadgeTone, palette: ThemeColors = colors): string {
|
|
231
|
+
const surfaces = [
|
|
232
|
+
commandBarBg(palette),
|
|
233
|
+
commandBarPanelBg(palette),
|
|
234
|
+
commandBarHoverBg(palette),
|
|
235
|
+
commandBarSelectedBg(palette),
|
|
236
|
+
] as const;
|
|
237
|
+
const muted = blendHex(commandBarBadgeHue(tone, palette), commandBarSubtleText(palette), 0.4);
|
|
238
|
+
return blendForContrastOnSurfaces(
|
|
239
|
+
muted,
|
|
240
|
+
surfaces,
|
|
241
|
+
higherContrast("#ffffff", "#000000", surfaces[0]),
|
|
242
|
+
3.6,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
196
246
|
export function commandBarSelectedText(palette: ThemeColors = colors): string {
|
|
197
247
|
const base = commandBarSelectedBg(palette);
|
|
198
248
|
const preferred = higherContrast(palette.selectedText, palette.text, base);
|
package/src/theme/themes.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterEach, describe, test } from "bun:test";
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
2
|
import {
|
|
3
3
|
applyTheme,
|
|
4
4
|
commandBarAccentText,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
paneTitleText,
|
|
17
17
|
} from "./colors";
|
|
18
18
|
import { contrastRatio } from "./color-utils";
|
|
19
|
-
import { DEFAULT_THEME, themes } from "./themes";
|
|
19
|
+
import { DEFAULT_THEME, getThemeIds, isDarkTheme, themes } from "./themes";
|
|
20
20
|
|
|
21
21
|
const BODY_TEXT_MIN = 4.5;
|
|
22
22
|
const SUBTLE_TEXT_MIN = 3.6;
|
|
@@ -34,6 +34,19 @@ afterEach(() => {
|
|
|
34
34
|
applyTheme(DEFAULT_THEME);
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* The theme picker marks the dark half of its list with a moon, so a palette
|
|
39
|
+
* that lands on the wrong side of this shows the wrong glyph, and the whole
|
|
40
|
+
* gutter stops meaning anything. Pinning the light set catches a palette drifting
|
|
41
|
+
* across the line when someone retunes its colours.
|
|
42
|
+
*/
|
|
43
|
+
describe("theme appearance", () => {
|
|
44
|
+
test("reads every light theme as light and everything else as dark", () => {
|
|
45
|
+
const light = getThemeIds().filter((id) => !isDarkTheme(id)).sort();
|
|
46
|
+
expect(light).toEqual(["github-light", "gruvbox-light", "nord-light", "paper", "solarized-light"]);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
37
50
|
describe("theme contrast", () => {
|
|
38
51
|
test("keeps shared text roles readable on body surfaces", () => {
|
|
39
52
|
for (const [id, theme] of Object.entries(themes)) {
|
package/src/theme/themes.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
blendForContrast,
|
|
3
|
+
blendForContrastOnSurfaces,
|
|
4
|
+
blendHex,
|
|
5
|
+
contrastRatio,
|
|
6
|
+
higherContrast,
|
|
7
|
+
relativeLuminance,
|
|
8
|
+
} from "./color-utils";
|
|
2
9
|
|
|
3
10
|
export interface Theme {
|
|
4
11
|
name: string;
|
|
@@ -638,6 +645,17 @@ export function getThemeIds(): string[] {
|
|
|
638
645
|
return Object.keys(themes);
|
|
639
646
|
}
|
|
640
647
|
|
|
648
|
+
/**
|
|
649
|
+
* A theme is dark when its text sits lighter than its background. That is the
|
|
650
|
+
* definition, and it beats thresholding the background on its own, which
|
|
651
|
+
* mislabels the mid-tone palettes sitting near whatever cutoff you pick.
|
|
652
|
+
*/
|
|
653
|
+
export function isDarkTheme(themeId: string): boolean {
|
|
654
|
+
const theme = themes[themeId];
|
|
655
|
+
if (!theme) return true;
|
|
656
|
+
return relativeLuminance(theme.text) > relativeLuminance(theme.bg);
|
|
657
|
+
}
|
|
658
|
+
|
|
641
659
|
export function getTheme(id: string): Theme {
|
|
642
660
|
return themes[id] ?? themes[DEFAULT_THEME]!;
|
|
643
661
|
}
|
|
@@ -359,6 +359,32 @@ describe("ticker-search utilities", () => {
|
|
|
359
359
|
expect(rankTickerSearchItems([...candidates].reverse(), "Apple").map((item) => item.symbol)).toEqual(expected);
|
|
360
360
|
});
|
|
361
361
|
|
|
362
|
+
test("ranks the primary listing above leveraged and depositary look-alikes", () => {
|
|
363
|
+
const make = (symbol: string, right: string, providerRank: number) => ({
|
|
364
|
+
id: symbol,
|
|
365
|
+
label: symbol,
|
|
366
|
+
symbol,
|
|
367
|
+
detail: "NVIDIA Corporation",
|
|
368
|
+
right,
|
|
369
|
+
category: "Other Listings",
|
|
370
|
+
kind: "search" as const,
|
|
371
|
+
instrumentClass: "equity" as const,
|
|
372
|
+
providerRank,
|
|
373
|
+
});
|
|
374
|
+
// Provider order puts the Milan 4x product first; every text signal ties.
|
|
375
|
+
const candidates = [make("4NVDA", "MTA", 0), make("NVDA", "NASDAQ", 1), make("NVDC34", "BOVESPA", 2)];
|
|
376
|
+
|
|
377
|
+
expect(rankTickerSearchItems(candidates, "nvidia").map((item) => item.symbol))
|
|
378
|
+
.toEqual(["NVDA", "4NVDA", "NVDC34"]);
|
|
379
|
+
|
|
380
|
+
// All-digit symbols are Tokyo, Shanghai and Hong Kong primaries, not look-alikes.
|
|
381
|
+
const toyota = [
|
|
382
|
+
{ ...make("7203", "TSE", 0), detail: "Toyota Motor Corporation" },
|
|
383
|
+
{ ...make("TM", "NYSE", 1), detail: "Toyota Motor Corporation" },
|
|
384
|
+
];
|
|
385
|
+
expect(rankTickerSearchItems(toyota, "toyota").map((item) => item.symbol)).toEqual(["7203", "TM"]);
|
|
386
|
+
});
|
|
387
|
+
|
|
362
388
|
test("normalizes punctuated legal suffixes before applying same-issuer provider order", () => {
|
|
363
389
|
const results = buildTickerSearchCandidates({
|
|
364
390
|
query: "Acme",
|
|
@@ -117,6 +117,7 @@ export async function searchTickerCandidates({
|
|
|
117
117
|
localLimit = 6,
|
|
118
118
|
totalLimit = 8,
|
|
119
119
|
includeOptionContracts = true,
|
|
120
|
+
onPartial,
|
|
120
121
|
}: {
|
|
121
122
|
query: string;
|
|
122
123
|
tickers: ReadonlyMap<string, TickerRecord>;
|
|
@@ -125,15 +126,23 @@ export async function searchTickerCandidates({
|
|
|
125
126
|
localLimit?: number;
|
|
126
127
|
totalLimit?: number;
|
|
127
128
|
includeOptionContracts?: boolean;
|
|
129
|
+
/** Called when a slower, richer source improves results already returned. */
|
|
130
|
+
onPartial?: (candidates: TickerSearchCandidate[]) => void;
|
|
128
131
|
}): Promise<TickerSearchCandidate[]> {
|
|
129
|
-
|
|
132
|
+
const assemble = (providerResults: InstrumentSearchResult[]) => buildTickerSearchCandidates({
|
|
130
133
|
query,
|
|
131
134
|
tickers,
|
|
132
|
-
providerResults
|
|
135
|
+
providerResults,
|
|
133
136
|
localLimit,
|
|
134
137
|
totalLimit,
|
|
135
138
|
includeOptionContracts,
|
|
136
139
|
});
|
|
140
|
+
return assemble(await searchProviderResults(
|
|
141
|
+
dataProvider,
|
|
142
|
+
query,
|
|
143
|
+
searchContext,
|
|
144
|
+
onPartial ? (results) => onPartial(assemble(results)) : undefined,
|
|
145
|
+
));
|
|
137
146
|
}
|
|
138
147
|
|
|
139
148
|
export function buildTickerSearchCandidates({
|
|
@@ -233,27 +242,43 @@ async function searchProviderResults(
|
|
|
233
242
|
dataProvider: DataProvider,
|
|
234
243
|
query: string,
|
|
235
244
|
searchContext?: SearchRequestContext,
|
|
245
|
+
onPartial?: (results: InstrumentSearchResult[]) => void,
|
|
236
246
|
): Promise<InstrumentSearchResult[]> {
|
|
237
|
-
|
|
238
|
-
|
|
247
|
+
// A Map rather than a list plus a seen set, because a later source can send
|
|
248
|
+
// back a richer version of a symbol already recorded. Overwriting a key keeps
|
|
249
|
+
// its original position, so an upgrade does not reorder the list.
|
|
250
|
+
const byKey = new Map<string, InstrumentSearchResult>();
|
|
251
|
+
const add = (results: InstrumentSearchResult[], upgrade = false) => {
|
|
252
|
+
for (const result of results) {
|
|
253
|
+
const key = buildProviderSearchResultKey(result);
|
|
254
|
+
if (!upgrade && byKey.has(key)) continue;
|
|
255
|
+
byKey.set(key, result);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
239
258
|
|
|
240
|
-
|
|
241
|
-
|
|
259
|
+
// The variants are independent lookups of the same words, so they run
|
|
260
|
+
// together. Awaited in turn they multiplied every per-source timeout by the
|
|
261
|
+
// number of spellings tried.
|
|
262
|
+
await Promise.all(buildProviderSearchQueries(query).map(async (searchQuery) => {
|
|
242
263
|
try {
|
|
243
|
-
results = await dataProvider.search(searchQuery,
|
|
264
|
+
const results = await dataProvider.search(searchQuery, {
|
|
265
|
+
...searchContext,
|
|
266
|
+
...(onPartial
|
|
267
|
+
? {
|
|
268
|
+
onPartial: (upgraded: InstrumentSearchResult[]) => {
|
|
269
|
+
add(upgraded, true);
|
|
270
|
+
onPartial([...byKey.values()]);
|
|
271
|
+
},
|
|
272
|
+
}
|
|
273
|
+
: {}),
|
|
274
|
+
});
|
|
275
|
+
add(results);
|
|
244
276
|
} catch {
|
|
245
|
-
|
|
277
|
+
// One spelling failing must not lose the others.
|
|
246
278
|
}
|
|
279
|
+
}));
|
|
247
280
|
|
|
248
|
-
|
|
249
|
-
const key = buildProviderSearchResultKey(result);
|
|
250
|
-
if (seen.has(key)) continue;
|
|
251
|
-
seen.add(key);
|
|
252
|
-
merged.push(result);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return merged;
|
|
281
|
+
return [...byKey.values()];
|
|
257
282
|
}
|
|
258
283
|
|
|
259
284
|
function buildProviderSearchQueries(query: string): string[] {
|
|
@@ -225,6 +225,15 @@ export function rankTickerSearchItems<T extends Pick<TickerSearchRankableItem, "
|
|
|
225
225
|
a.issuerGroupKey
|
|
226
226
|
&& a.issuerGroupKey === b.issuerGroupKey
|
|
227
227
|
) {
|
|
228
|
+
// Letters mixed with digits (4NVDA, NVDC34, 0R1I) mark a leveraged
|
|
229
|
+
// product, a depositary receipt or a secondary venue's code filed under
|
|
230
|
+
// the issuer's name, never its primary listing. All-digit symbols are
|
|
231
|
+
// Tokyo, Shanghai and Hong Kong primaries and must not be touched.
|
|
232
|
+
// Provider order is trusted for real listings of one issuer, but it put
|
|
233
|
+
// a Milan 4x product above NVDA, so mixed codes yield first.
|
|
234
|
+
const aSynthetic = isMixedCode(a.item.label);
|
|
235
|
+
const bSynthetic = isMixedCode(b.item.label);
|
|
236
|
+
if (aSynthetic !== bSynthetic) return aSynthetic ? 1 : -1;
|
|
228
237
|
const aProviderRank = a.item.providerRank ?? Number.POSITIVE_INFINITY;
|
|
229
238
|
const bProviderRank = b.item.providerRank ?? Number.POSITIVE_INFINITY;
|
|
230
239
|
if (aProviderRank !== bProviderRank) return aProviderRank - bProviderRank;
|
|
@@ -438,6 +447,11 @@ function scoreExchangePreference(
|
|
|
438
447
|
return matchesHint ? 2_000 : -800;
|
|
439
448
|
}
|
|
440
449
|
|
|
450
|
+
function isMixedCode(label: string): boolean {
|
|
451
|
+
const symbol = label.split(".")[0] ?? label;
|
|
452
|
+
return /\d/.test(symbol) && /[A-Za-z]/.test(symbol);
|
|
453
|
+
}
|
|
454
|
+
|
|
441
455
|
function scoreListingPriority(item: Pick<TickerSearchRankableItem, "label"> & Partial<TickerSearchRankableItem>): number {
|
|
442
456
|
const instrumentClass = item.instrumentClass || "other";
|
|
443
457
|
if (instrumentClass !== "equity") return 0;
|