gloomberb 0.11.3 → 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 +6 -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 +103 -1
- package/src/api-client/index.test.ts +127 -0
- package/src/api-client/index.ts +129 -1
- package/src/api-client/normalizers.ts +39 -0
- package/src/api-client/paths.ts +85 -1
- package/src/api-client/types.ts +196 -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/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 +58 -28
- 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/restore-plugins.ts +34 -0
- 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/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 +3 -2
- 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/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/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/pane.tsx +22 -8
- 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 +14 -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-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 +2 -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/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/kelly-sizer/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
- 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/index.tsx +1 -0
- package/src/plugins/builtin/options/view.test.tsx +29 -1
- package/src/plugins/builtin/options/view.tsx +1 -22
- 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.ts +9 -1
- 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/sec/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/index.tsx +0 -14
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
- package/src/plugins/builtin/volatility/index.tsx +1 -1
- package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +213 -69
- package/src/plugins/builtin/world-venue-map/model.test.ts +32 -0
- package/src/plugins/builtin/world-venue-map/model.ts +134 -13
- 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-browser.test.ts +1 -0
- package/src/plugins/catalog-browser.ts +10 -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.ts +6 -13
- 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/cloudflare/worker.test.ts +19 -0
- package/src/renderers/cloudflare/worker.ts +1 -1
- package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
- package/src/renderers/electrobun/bun/index.ts +5 -2
- package/src/renderers/electrobun/shared/protocol.ts +21 -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/opentui/chart-surface.tsx +5 -1
- package/src/renderers/opentui/host.tsx +16 -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 +19 -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/resolution.test.ts +25 -26
- package/src/time-series/resolution.ts +41 -10
- package/src/time-series/resolve.ts +7 -0
- 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 +102 -0
- package/src/ui/host.tsx +12 -1
- package/src/ui/index.tsx +1 -0
- package/src/ui/toast.tsx +8 -4
- 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/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
|
@@ -11,8 +11,49 @@ export interface WorldVenueCluster extends WorldMapPoint {
|
|
|
11
11
|
isOpen: boolean;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export interface WorldMapViewport {
|
|
15
|
+
zoom: number;
|
|
16
|
+
centerLongitude: number;
|
|
17
|
+
centerLatitude: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
const MAX_LATITUDE = 85;
|
|
15
21
|
const MIN_LATITUDE = -60;
|
|
22
|
+
const LONGITUDE_SPAN = 360;
|
|
23
|
+
export const MAX_WORLD_MAP_ZOOM = 18;
|
|
24
|
+
|
|
25
|
+
export const DEFAULT_WORLD_MAP_VIEWPORT: WorldMapViewport = {
|
|
26
|
+
zoom: 1,
|
|
27
|
+
centerLongitude: 0,
|
|
28
|
+
centerLatitude: (MAX_LATITUDE + MIN_LATITUDE) / 2,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function clamp(value: number, min: number, max: number): number {
|
|
32
|
+
return Math.max(min, Math.min(max, value));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function mapExtent(width: number, height: number, yUnitAspect = 1) {
|
|
36
|
+
const availableWidth = Math.max(width - 1, 0);
|
|
37
|
+
const availableHeight = Math.max(height - 1, 0);
|
|
38
|
+
const unitAspect = Math.max(yUnitAspect, Number.EPSILON);
|
|
39
|
+
const effectiveHeight = availableHeight * unitAspect;
|
|
40
|
+
const latitudeSpan = MAX_LATITUDE - MIN_LATITUDE;
|
|
41
|
+
return { availableWidth, availableHeight, unitAspect, effectiveHeight, latitudeSpan };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function fittedWorldMapScale(width: number, height: number, yUnitAspect = 1): number {
|
|
45
|
+
const { availableWidth, effectiveHeight, latitudeSpan } = mapExtent(width, height, yUnitAspect);
|
|
46
|
+
return Math.min(availableWidth / LONGITUDE_SPAN, effectiveHeight / latitudeSpan);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function worldMapScale(
|
|
50
|
+
width: number,
|
|
51
|
+
height: number,
|
|
52
|
+
yUnitAspect: number,
|
|
53
|
+
viewport: WorldMapViewport,
|
|
54
|
+
): number {
|
|
55
|
+
return fittedWorldMapScale(width, height, yUnitAspect) * Math.max(viewport.zoom, 1);
|
|
56
|
+
}
|
|
16
57
|
|
|
17
58
|
export function projectWorldPoint(
|
|
18
59
|
longitude: number,
|
|
@@ -20,35 +61,115 @@ export function projectWorldPoint(
|
|
|
20
61
|
width: number,
|
|
21
62
|
height: number,
|
|
22
63
|
yUnitAspect = 1,
|
|
64
|
+
viewport: WorldMapViewport = DEFAULT_WORLD_MAP_VIEWPORT,
|
|
23
65
|
): WorldMapPoint {
|
|
24
|
-
const availableWidth =
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const effectiveHeight = availableHeight * unitAspect;
|
|
28
|
-
const latitudeSpan = MAX_LATITUDE - MIN_LATITUDE;
|
|
29
|
-
const scale = Math.min(availableWidth / 360, effectiveHeight / latitudeSpan);
|
|
30
|
-
const mapWidth = 360 * scale;
|
|
31
|
-
const mapHeight = latitudeSpan * scale;
|
|
32
|
-
const clampedLatitude = Math.max(MIN_LATITUDE, Math.min(MAX_LATITUDE, latitude));
|
|
66
|
+
const { availableWidth, unitAspect, effectiveHeight } = mapExtent(width, height, yUnitAspect);
|
|
67
|
+
const scale = worldMapScale(width, height, yUnitAspect, viewport);
|
|
68
|
+
const clampedLatitude = clamp(latitude, MIN_LATITUDE, MAX_LATITUDE);
|
|
33
69
|
|
|
34
70
|
return {
|
|
35
|
-
x:
|
|
36
|
-
y: (
|
|
71
|
+
x: availableWidth / 2 + (longitude - viewport.centerLongitude) * scale,
|
|
72
|
+
y: (effectiveHeight / 2 - (clampedLatitude - viewport.centerLatitude) * scale) / unitAspect,
|
|
37
73
|
};
|
|
38
74
|
}
|
|
39
75
|
|
|
76
|
+
export function unprojectWorldPoint(
|
|
77
|
+
x: number,
|
|
78
|
+
y: number,
|
|
79
|
+
width: number,
|
|
80
|
+
height: number,
|
|
81
|
+
yUnitAspect = 1,
|
|
82
|
+
viewport: WorldMapViewport = DEFAULT_WORLD_MAP_VIEWPORT,
|
|
83
|
+
): { longitude: number; latitude: number } {
|
|
84
|
+
const { availableWidth, unitAspect, effectiveHeight } = mapExtent(width, height, yUnitAspect);
|
|
85
|
+
const scale = worldMapScale(width, height, yUnitAspect, viewport);
|
|
86
|
+
return {
|
|
87
|
+
longitude: viewport.centerLongitude + (x - availableWidth / 2) / scale,
|
|
88
|
+
latitude: viewport.centerLatitude - (y * unitAspect - effectiveHeight / 2) / scale,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function clampWorldMapViewport(
|
|
93
|
+
viewport: WorldMapViewport,
|
|
94
|
+
width: number,
|
|
95
|
+
height: number,
|
|
96
|
+
yUnitAspect = 1,
|
|
97
|
+
): WorldMapViewport {
|
|
98
|
+
const zoom = clamp(viewport.zoom, 1, MAX_WORLD_MAP_ZOOM);
|
|
99
|
+
if (zoom <= 1) return DEFAULT_WORLD_MAP_VIEWPORT;
|
|
100
|
+
|
|
101
|
+
const { availableWidth, effectiveHeight, latitudeSpan } = mapExtent(width, height, yUnitAspect);
|
|
102
|
+
const scale = fittedWorldMapScale(width, height, yUnitAspect) * zoom;
|
|
103
|
+
const halfLongitude = availableWidth / (2 * scale);
|
|
104
|
+
const halfLatitude = effectiveHeight / (2 * scale);
|
|
105
|
+
const minLongitude = -180 + halfLongitude;
|
|
106
|
+
const maxLongitude = 180 - halfLongitude;
|
|
107
|
+
const minLatitude = MIN_LATITUDE + halfLatitude;
|
|
108
|
+
const maxLatitude = MAX_LATITUDE - halfLatitude;
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
zoom,
|
|
112
|
+
centerLongitude: minLongitude < maxLongitude
|
|
113
|
+
? clamp(viewport.centerLongitude, minLongitude, maxLongitude)
|
|
114
|
+
: DEFAULT_WORLD_MAP_VIEWPORT.centerLongitude,
|
|
115
|
+
centerLatitude: halfLatitude < latitudeSpan / 2
|
|
116
|
+
? clamp(viewport.centerLatitude, minLatitude, maxLatitude)
|
|
117
|
+
: DEFAULT_WORLD_MAP_VIEWPORT.centerLatitude,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function zoomWorldMapViewport(
|
|
122
|
+
viewport: WorldMapViewport,
|
|
123
|
+
width: number,
|
|
124
|
+
height: number,
|
|
125
|
+
point: WorldMapPoint,
|
|
126
|
+
zoomFactor: number,
|
|
127
|
+
yUnitAspect = 1,
|
|
128
|
+
): WorldMapViewport {
|
|
129
|
+
const nextZoom = clamp(viewport.zoom * zoomFactor, 1, MAX_WORLD_MAP_ZOOM);
|
|
130
|
+
if (nextZoom <= 1) return DEFAULT_WORLD_MAP_VIEWPORT;
|
|
131
|
+
|
|
132
|
+
const geographic = unprojectWorldPoint(point.x, point.y, width, height, yUnitAspect, viewport);
|
|
133
|
+
const { availableWidth, unitAspect, effectiveHeight } = mapExtent(width, height, yUnitAspect);
|
|
134
|
+
const scale = fittedWorldMapScale(width, height, yUnitAspect) * nextZoom;
|
|
135
|
+
return clampWorldMapViewport({
|
|
136
|
+
zoom: nextZoom,
|
|
137
|
+
centerLongitude: geographic.longitude - (point.x - availableWidth / 2) / scale,
|
|
138
|
+
centerLatitude: geographic.latitude + (point.y * unitAspect - effectiveHeight / 2) / scale,
|
|
139
|
+
}, width, height, yUnitAspect);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function panWorldMapViewport(
|
|
143
|
+
viewport: WorldMapViewport,
|
|
144
|
+
width: number,
|
|
145
|
+
height: number,
|
|
146
|
+
deltaX: number,
|
|
147
|
+
deltaY: number,
|
|
148
|
+
yUnitAspect = 1,
|
|
149
|
+
): WorldMapViewport {
|
|
150
|
+
if (viewport.zoom <= 1) return DEFAULT_WORLD_MAP_VIEWPORT;
|
|
151
|
+
const scale = worldMapScale(width, height, yUnitAspect, viewport);
|
|
152
|
+
const unitAspect = Math.max(yUnitAspect, Number.EPSILON);
|
|
153
|
+
return clampWorldMapViewport({
|
|
154
|
+
zoom: viewport.zoom,
|
|
155
|
+
centerLongitude: viewport.centerLongitude - deltaX / scale,
|
|
156
|
+
centerLatitude: viewport.centerLatitude + (deltaY * unitAspect) / scale,
|
|
157
|
+
}, width, height, yUnitAspect);
|
|
158
|
+
}
|
|
159
|
+
|
|
40
160
|
export function clusterWorldVenues(
|
|
41
161
|
venues: readonly CloudWorldVenuePayload[],
|
|
42
162
|
width: number,
|
|
43
163
|
height: number,
|
|
44
164
|
yUnitAspect = 1,
|
|
165
|
+
viewport: WorldMapViewport = DEFAULT_WORLD_MAP_VIEWPORT,
|
|
45
166
|
): WorldVenueCluster[] {
|
|
46
167
|
const buckets = new Map<string, CloudWorldVenuePayload[]>();
|
|
47
168
|
const cellWidth = Math.max(4, Math.round(width / 18));
|
|
48
169
|
const cellHeight = Math.max(2, Math.round(height / 12));
|
|
49
170
|
|
|
50
171
|
for (const venue of venues) {
|
|
51
|
-
const point = projectWorldPoint(venue.longitude, venue.latitude, width, height, yUnitAspect);
|
|
172
|
+
const point = projectWorldPoint(venue.longitude, venue.latitude, width, height, yUnitAspect, viewport);
|
|
52
173
|
const key = `${Math.floor(point.x / cellWidth)}:${Math.floor(point.y / cellHeight)}`;
|
|
53
174
|
const bucket = buckets.get(key) ?? [];
|
|
54
175
|
bucket.push(venue);
|
|
@@ -58,7 +179,7 @@ export function clusterWorldVenues(
|
|
|
58
179
|
return [...buckets.entries()].map(([id, grouped]) => {
|
|
59
180
|
const point = grouped.reduce(
|
|
60
181
|
(sum, venue) => {
|
|
61
|
-
const projected = projectWorldPoint(venue.longitude, venue.latitude, width, height, yUnitAspect);
|
|
182
|
+
const projected = projectWorldPoint(venue.longitude, venue.latitude, width, height, yUnitAspect, viewport);
|
|
62
183
|
return { x: sum.x + projected.x, y: sum.y + projected.y };
|
|
63
184
|
},
|
|
64
185
|
{ x: 0, y: 0 },
|
|
@@ -5,7 +5,7 @@ import { Button, EmptyState, Spinner, StaticChartSurface, type PaneFooterSegment
|
|
|
5
5
|
import type { PaneProps } from "../../../types/plugin";
|
|
6
6
|
import type { PluginModule } from "../plugin-module";
|
|
7
7
|
import { colors } from "../../../theme/colors";
|
|
8
|
-
import { resolveChartPalette } from "../../../components/chart/core/
|
|
8
|
+
import { resolveChartPalette } from "../../../components/chart/core/palette";
|
|
9
9
|
import type { ProjectedChartPoint } from "../../../components/chart/core/data";
|
|
10
10
|
import {
|
|
11
11
|
curveAsOf,
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "fs";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
|
|
6
|
+
import { bundleExternalPlugin, buildSharedModuleSource } from "./bundle";
|
|
7
|
+
import { PLUGIN_HOST_GLOBAL } from "./host-contract";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* This is the seam that lets a plugin on disk run inside the desktop and
|
|
11
|
+
* browser renderers, and the failure it prevents is nasty: if `react` or a
|
|
12
|
+
* `gloomberb/*` module gets bundled into the plugin instead of shared with the
|
|
13
|
+
* host, the plugin loads fine and then throws on its first hook, or silently
|
|
14
|
+
* renders against a second copy of the theme. That is worth pinning down.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
function scratchPlugin(source: string): string {
|
|
18
|
+
const dir = mkdtempSync(join(tmpdir(), "gloom-bundle-"));
|
|
19
|
+
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "scratch", main: "index.tsx" }));
|
|
20
|
+
writeFileSync(join(dir, "index.tsx"), source);
|
|
21
|
+
return dir;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const fakeExports = async (specifier: string) => (
|
|
25
|
+
specifier === "gloomberb/ui" ? ["Box", "Text"] : specifier === "react" ? ["useState"] : []
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
describe("buildSharedModuleSource", () => {
|
|
29
|
+
test("re-exports each name from the host registry", () => {
|
|
30
|
+
const source = buildSharedModuleSource("gloomberb/ui", ["Box", "Text"]);
|
|
31
|
+
|
|
32
|
+
expect(source).toContain(PLUGIN_HOST_GLOBAL);
|
|
33
|
+
expect(source).toContain('export const Box = mod["Box"];');
|
|
34
|
+
expect(source).toContain('export const Text = mod["Text"];');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("throws a directed error when the host registry is missing", () => {
|
|
38
|
+
// A plugin bundle loaded before the host installs its modules should say so,
|
|
39
|
+
// not fail later with an undefined property read deep in a render.
|
|
40
|
+
const source = buildSharedModuleSource("gloomberb/ui", []);
|
|
41
|
+
expect(source).toContain("was not installed before this plugin loaded");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("skips a default export, which the module already provides", () => {
|
|
45
|
+
expect(buildSharedModuleSource("react", ["default", "useState"]))
|
|
46
|
+
.not.toContain("export const default");
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("bundleExternalPlugin", () => {
|
|
51
|
+
test("shares react and gloomberb modules instead of bundling them", async () => {
|
|
52
|
+
const dir = scratchPlugin(`
|
|
53
|
+
import { useState } from "react";
|
|
54
|
+
import { Box } from "gloomberb/ui";
|
|
55
|
+
export default { id: "scratch", name: "Scratch", version: "1.0.0", useState, Box };
|
|
56
|
+
`);
|
|
57
|
+
const out = join(dir, "out");
|
|
58
|
+
try {
|
|
59
|
+
const result = await bundleExternalPlugin(dir, out, { exportNamesFor: fakeExports });
|
|
60
|
+
const code = await Bun.file(result.outputPath).text();
|
|
61
|
+
|
|
62
|
+
expect(result.shared).toEqual(["gloomberb/ui", "react"]);
|
|
63
|
+
expect(code).toContain(PLUGIN_HOST_GLOBAL);
|
|
64
|
+
// The giveaway that React got inlined rather than shared.
|
|
65
|
+
expect(code).not.toContain("react-dom/client");
|
|
66
|
+
expect(code).not.toContain("Invalid hook call");
|
|
67
|
+
} finally {
|
|
68
|
+
rmSync(dir, { recursive: true, force: true });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("bundles a plugin's own dependencies rather than sharing them", async () => {
|
|
73
|
+
const dir = scratchPlugin(`
|
|
74
|
+
import { double } from "./helper";
|
|
75
|
+
export default { id: "scratch", name: "Scratch", version: "1.0.0", value: double(21) };
|
|
76
|
+
`);
|
|
77
|
+
writeFileSync(join(dir, "helper.ts"), "export function double(n: number) { return n * 2; }");
|
|
78
|
+
const out = join(dir, "out");
|
|
79
|
+
try {
|
|
80
|
+
const result = await bundleExternalPlugin(dir, out, { exportNamesFor: fakeExports });
|
|
81
|
+
const code = await Bun.file(result.outputPath).text();
|
|
82
|
+
|
|
83
|
+
expect(result.shared).toEqual([]);
|
|
84
|
+
expect(code).toContain("* 2");
|
|
85
|
+
} finally {
|
|
86
|
+
rmSync(dir, { recursive: true, force: true });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("reports a compile error against the plugin instead of throwing something opaque", async () => {
|
|
91
|
+
const dir = scratchPlugin(`import { missing } from "./nope"; export default missing;`);
|
|
92
|
+
try {
|
|
93
|
+
await expect(bundleExternalPlugin(dir, join(dir, "out"), { exportNamesFor: fakeExports }))
|
|
94
|
+
.rejects.toThrow();
|
|
95
|
+
} finally {
|
|
96
|
+
rmSync(dir, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("rejects a directory with no entry file", async () => {
|
|
101
|
+
const dir = mkdtempSync(join(tmpdir(), "gloom-bundle-empty-"));
|
|
102
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
103
|
+
try {
|
|
104
|
+
await expect(bundleExternalPlugin(dir, join(dir, "out"))).rejects.toThrow("No plugin entry file");
|
|
105
|
+
} finally {
|
|
106
|
+
rmSync(dir, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { mkdir } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
|
|
4
|
+
import { resolvePluginEntry } from "./loader";
|
|
5
|
+
import { PLUGIN_HOST_GLOBAL, SHARED_SPECIFIERS } from "./host-contract";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Compiles an external plugin for a renderer that cannot read the filesystem.
|
|
9
|
+
*
|
|
10
|
+
* The terminal renderer runs in Bun and can `import()` a plugin directly from
|
|
11
|
+
* `~/.gloomberb/plugins`. The desktop view and the hosted browser app cannot:
|
|
12
|
+
* they are browser contexts running a sealed bundle, so plugin source has to be
|
|
13
|
+
* compiled to an ES module they can fetch.
|
|
14
|
+
*
|
|
15
|
+
* The hard part is not compiling, it is *sharing*. A plugin bundle that carries
|
|
16
|
+
* its own React would give the process a second React instance and throw on the
|
|
17
|
+
* first hook, and it has no way to reach the host's `gloomberb/*` modules at
|
|
18
|
+
* all. So those specifiers are not bundled — they are rewritten to read from a
|
|
19
|
+
* registry the host publishes on `globalThis` before it loads any plugin. One
|
|
20
|
+
* instance of every shared module, exactly as on the terminal side where the
|
|
21
|
+
* host is symlinked in.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Source for a module that re-exports one shared specifier from the host
|
|
28
|
+
* registry. Named exports have to be listed statically — `export *` cannot
|
|
29
|
+
* forward from a runtime object — so the caller passes the names it found by
|
|
30
|
+
* importing the real module.
|
|
31
|
+
*/
|
|
32
|
+
export function buildSharedModuleSource(specifier: string, exportNames: readonly string[]): string {
|
|
33
|
+
const lines = [
|
|
34
|
+
`const mod = globalThis[${JSON.stringify(PLUGIN_HOST_GLOBAL)}]?.[${JSON.stringify(specifier)}];`,
|
|
35
|
+
`if (!mod) throw new Error(${JSON.stringify(`Gloomberb host module "${specifier}" is unavailable. The plugin host was not installed before this plugin loaded.`)});`,
|
|
36
|
+
"export default mod;",
|
|
37
|
+
];
|
|
38
|
+
for (const name of exportNames) {
|
|
39
|
+
if (name === "default") continue;
|
|
40
|
+
// Not `export const`: a getter keeps live bindings working and avoids
|
|
41
|
+
// capturing a value the host may replace on a theme or locale change.
|
|
42
|
+
lines.push(`export const ${name} = mod[${JSON.stringify(name)}];`);
|
|
43
|
+
}
|
|
44
|
+
return lines.join("\n");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface BundlePluginResult {
|
|
48
|
+
/** Absolute path of the emitted ES module. */
|
|
49
|
+
outputPath: string;
|
|
50
|
+
/** Shared specifiers this plugin actually imported. */
|
|
51
|
+
shared: string[];
|
|
52
|
+
warnings: string[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Bun plugin that redirects the shared specifiers to generated modules.
|
|
57
|
+
*
|
|
58
|
+
* `exportNamesFor` is injected so this stays testable and so the caller decides
|
|
59
|
+
* how names are discovered — the bundler runs in Bun and can simply import the
|
|
60
|
+
* real module, but a test should not have to.
|
|
61
|
+
*/
|
|
62
|
+
export function createSharedModuleResolver(
|
|
63
|
+
exportNamesFor: (specifier: string) => Promise<readonly string[]>,
|
|
64
|
+
onShared?: (specifier: string) => void,
|
|
65
|
+
): import("bun").BunPlugin {
|
|
66
|
+
const namespace = "gloom-host";
|
|
67
|
+
const shared = new Set<string>(SHARED_SPECIFIERS);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
name: "gloomberb-host-modules",
|
|
71
|
+
setup(build) {
|
|
72
|
+
build.onResolve({ filter: /.*/ }, (args) => {
|
|
73
|
+
if (!shared.has(args.path)) return undefined;
|
|
74
|
+
onShared?.(args.path);
|
|
75
|
+
return { path: args.path, namespace };
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
build.onLoad({ filter: /.*/, namespace }, async (args) => ({
|
|
79
|
+
contents: buildSharedModuleSource(args.path, await exportNamesFor(args.path)),
|
|
80
|
+
loader: "js",
|
|
81
|
+
}));
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function hostExportNames(specifier: string): Promise<readonly string[]> {
|
|
87
|
+
const mod = await import(specifier);
|
|
88
|
+
return Object.keys(mod).sort();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Bundles one plugin directory to `outDir`, returning the emitted module path.
|
|
93
|
+
* Throws on a compile error so the caller can surface it against the plugin
|
|
94
|
+
* rather than failing the whole renderer.
|
|
95
|
+
*/
|
|
96
|
+
export async function bundleExternalPlugin(
|
|
97
|
+
pluginDir: string,
|
|
98
|
+
outDir: string,
|
|
99
|
+
options: { exportNamesFor?: (specifier: string) => Promise<readonly string[]> } = {},
|
|
100
|
+
): Promise<BundlePluginResult> {
|
|
101
|
+
const entry = await resolvePluginEntry(pluginDir);
|
|
102
|
+
if (!entry) throw new Error(`No plugin entry file in ${pluginDir}`);
|
|
103
|
+
|
|
104
|
+
await mkdir(outDir, { recursive: true });
|
|
105
|
+
|
|
106
|
+
const shared = new Set<string>();
|
|
107
|
+
const result = await Bun.build({
|
|
108
|
+
entrypoints: [entry],
|
|
109
|
+
outdir: outDir,
|
|
110
|
+
target: "browser",
|
|
111
|
+
format: "esm",
|
|
112
|
+
splitting: false,
|
|
113
|
+
minify: false,
|
|
114
|
+
sourcemap: "none",
|
|
115
|
+
plugins: [
|
|
116
|
+
createSharedModuleResolver(
|
|
117
|
+
options.exportNamesFor ?? hostExportNames,
|
|
118
|
+
(specifier) => shared.add(specifier),
|
|
119
|
+
),
|
|
120
|
+
],
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (!result.success) {
|
|
124
|
+
throw new Error(result.logs.map((log) => log.message).join("\n"));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const output = result.outputs.find((entryOutput) => entryOutput.kind === "entry-point");
|
|
128
|
+
if (!output) throw new Error(`Bundling ${pluginDir} produced no entry point`);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
outputPath: output.path,
|
|
132
|
+
shared: [...shared].sort(),
|
|
133
|
+
warnings: result.logs.filter((log) => log.level === "warning").map((log) => log.message),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function pluginBundleCacheDir(baseDir: string): string {
|
|
138
|
+
return join(baseDir, "bundles");
|
|
139
|
+
}
|
|
@@ -12,7 +12,13 @@ import { connectionsModule } from "./builtin/connections";
|
|
|
12
12
|
import { correlationModule } from "./builtin/correlation";
|
|
13
13
|
import { cdsModule } from "./builtin/cds";
|
|
14
14
|
import { creditConditionsModule } from "./builtin/credit-conditions";
|
|
15
|
+
import { marketValuationModule } from "./builtin/market-valuation";
|
|
16
|
+
import {
|
|
17
|
+
attachValuationPersistence,
|
|
18
|
+
resetValuationPersistence,
|
|
19
|
+
} from "./builtin/market-valuation/cache";
|
|
15
20
|
import { economicCalendarModule } from "./builtin/econ";
|
|
21
|
+
import { econStatisticsModule } from "./builtin/econ-statistics";
|
|
16
22
|
import { futuresModule } from "./builtin/futures";
|
|
17
23
|
import { fxMatrixModule } from "./builtin/fx-matrix";
|
|
18
24
|
import { helpModule } from "./builtin/help";
|
|
@@ -94,9 +100,11 @@ const browserMarketOverviewPlugin = composeBuiltinPlugin({
|
|
|
94
100
|
const browserFredResourcesModule: PluginModule = {
|
|
95
101
|
setup(ctx) {
|
|
96
102
|
attachFredSeriesPersistence(ctx.persistence);
|
|
103
|
+
attachValuationPersistence(ctx.persistence);
|
|
97
104
|
},
|
|
98
105
|
dispose() {
|
|
99
106
|
resetFredSeriesPersistence();
|
|
107
|
+
resetValuationPersistence();
|
|
100
108
|
},
|
|
101
109
|
};
|
|
102
110
|
|
|
@@ -109,9 +117,11 @@ const browserMacroPlugin = composeBuiltinPlugin({
|
|
|
109
117
|
modules: [
|
|
110
118
|
browserFredResourcesModule,
|
|
111
119
|
economicCalendarModule,
|
|
120
|
+
econStatisticsModule,
|
|
112
121
|
yieldCurveModule,
|
|
113
122
|
volatilityModule,
|
|
114
123
|
creditConditionsModule,
|
|
124
|
+
marketValuationModule,
|
|
115
125
|
cdsModule,
|
|
116
126
|
treasuryAuctionsModule,
|
|
117
127
|
],
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { buildBuiltinManifest } from "./catalog-manifest";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The published manifest is the built-in half of the plugin directory. It used
|
|
7
|
+
* to be hand-maintained in a separate repository, where it drifted from the
|
|
8
|
+
* real catalog with nothing to catch it: a built-in could rename itself, gain a
|
|
9
|
+
* pane, or be added outright, and the directory would keep showing the old
|
|
10
|
+
* description indefinitely.
|
|
11
|
+
*
|
|
12
|
+
* These tests exist so that cannot recur. Drift now fails here rather than
|
|
13
|
+
* being discovered by a user reading a stale listing.
|
|
14
|
+
*/
|
|
15
|
+
describe("built-in plugin manifest", () => {
|
|
16
|
+
test("matches the committed file, so the directory cannot serve a stale catalog", async () => {
|
|
17
|
+
const committed = JSON.parse(
|
|
18
|
+
await Bun.file(new URL("../../plugin-manifest.json", import.meta.url)).text(),
|
|
19
|
+
);
|
|
20
|
+
const { uncategorised: _drop, ...generated } = buildBuiltinManifest();
|
|
21
|
+
|
|
22
|
+
expect(generated).toEqual(committed);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("every built-in has editorial metadata", () => {
|
|
26
|
+
// A new built-in with no categories would appear unfiltered and
|
|
27
|
+
// uncategorised in the directory, so generation refuses instead.
|
|
28
|
+
expect(buildBuiltinManifest().uncategorised).toEqual([]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("exactly one plugin is featured", () => {
|
|
32
|
+
const featured = buildBuiltinManifest().plugins.filter((plugin) => plugin.featured);
|
|
33
|
+
|
|
34
|
+
expect(featured.map((plugin) => plugin.id)).toEqual(["gloomberb-cloud"]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("describes what each plugin contributes, which is what the directory renders", () => {
|
|
38
|
+
const cloud = buildBuiltinManifest().plugins.find((plugin) => plugin.id === "gloomberb-cloud");
|
|
39
|
+
|
|
40
|
+
expect(cloud?.contributes.panes).toContain("chat");
|
|
41
|
+
expect(cloud?.contributes.capabilities.length).toBeGreaterThan(0);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { getPluginCatalog } from "./catalog";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The public description of the plugins that ship inside Gloomberb.
|
|
5
|
+
*
|
|
6
|
+
* The plugin directory has to list built-ins alongside installable ones, but a
|
|
7
|
+
* built-in has no repository to read metadata from. Hand-maintaining that half
|
|
8
|
+
* elsewhere meant it silently drifted every time a built-in changed its panes
|
|
9
|
+
* or description, so it is derived from the live catalog here instead and
|
|
10
|
+
* checked against a committed snapshot in CI.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface BuiltinPluginManifestEntry {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
/** Curated, since the catalog has no notion of categories. */
|
|
18
|
+
categories: string[];
|
|
19
|
+
toggleable: boolean;
|
|
20
|
+
featured?: true;
|
|
21
|
+
contributes: {
|
|
22
|
+
panes: string[];
|
|
23
|
+
capabilities: string[];
|
|
24
|
+
broker: boolean;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Editorial metadata the runtime catalog does not carry. Anything missing here
|
|
30
|
+
* is reported by `generate-plugin-manifest.ts` rather than silently defaulted,
|
|
31
|
+
* so a new built-in cannot slip into the directory uncategorised.
|
|
32
|
+
*/
|
|
33
|
+
const EDITORIAL: Record<string, { categories: string[]; featured?: true }> = {
|
|
34
|
+
"gloomberb-cloud": { categories: ["data", "cloud"], featured: true },
|
|
35
|
+
ai: { categories: ["ai"] },
|
|
36
|
+
alerts: { categories: ["alerts"] },
|
|
37
|
+
application: { categories: ["core"] },
|
|
38
|
+
broker: { categories: ["broker"] },
|
|
39
|
+
debug: { categories: ["developer"] },
|
|
40
|
+
macro: { categories: ["macro"] },
|
|
41
|
+
"market-overview": { categories: ["markets"] },
|
|
42
|
+
news: { categories: ["news"] },
|
|
43
|
+
notes: { categories: ["productivity"] },
|
|
44
|
+
polls: { categories: ["data"] },
|
|
45
|
+
portfolio: { categories: ["portfolio"] },
|
|
46
|
+
"prediction-markets": { categories: ["markets"] },
|
|
47
|
+
public: { categories: ["broker"] },
|
|
48
|
+
"research-search": { categories: ["research", "news"] },
|
|
49
|
+
robinhood: { categories: ["broker"] },
|
|
50
|
+
simplefin: { categories: ["broker"] },
|
|
51
|
+
"ticker-research": { categories: ["research"] },
|
|
52
|
+
yahoo: { categories: ["data"] },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export interface BuiltinManifest {
|
|
56
|
+
version: 1;
|
|
57
|
+
plugins: BuiltinPluginManifestEntry[];
|
|
58
|
+
/** Built-in ids with no editorial entry; a non-empty list fails generation. */
|
|
59
|
+
uncategorised: string[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function buildBuiltinManifest(): BuiltinManifest {
|
|
63
|
+
const uncategorised: string[] = [];
|
|
64
|
+
|
|
65
|
+
const plugins = getPluginCatalog()
|
|
66
|
+
.map(({ plugin }) => {
|
|
67
|
+
const editorial = EDITORIAL[plugin.id];
|
|
68
|
+
if (!editorial) uncategorised.push(plugin.id);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
id: plugin.id,
|
|
72
|
+
name: plugin.name,
|
|
73
|
+
description: plugin.description ?? "",
|
|
74
|
+
categories: editorial?.categories ?? [],
|
|
75
|
+
toggleable: plugin.toggleable === true,
|
|
76
|
+
...(editorial?.featured ? { featured: true as const } : {}),
|
|
77
|
+
contributes: {
|
|
78
|
+
panes: (plugin.panes ?? []).map((pane) => pane.id).sort(),
|
|
79
|
+
capabilities: (plugin.capabilities ?? [])
|
|
80
|
+
.map((capability) => {
|
|
81
|
+
const value = capability as { id?: string; kind?: string };
|
|
82
|
+
return value.id ?? value.kind ?? "capability";
|
|
83
|
+
})
|
|
84
|
+
.sort(),
|
|
85
|
+
broker: !!plugin.broker,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
})
|
|
89
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
90
|
+
|
|
91
|
+
return { version: 1, plugins, uncategorised: uncategorised.sort() };
|
|
92
|
+
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import type { GloomPlugin } from "../types/plugin";
|
|
2
|
+
import type { LoadedExternalPlugin } from "./loader";
|
|
2
3
|
import { newsPlugin } from "./builtin/news";
|
|
3
4
|
import { notesPlugin } from "./builtin/notes";
|
|
4
|
-
import { substackPlugin } from "./builtin/substack";
|
|
5
5
|
import { aiPlugin } from "./builtin/ai";
|
|
6
6
|
import { gloomberbCloudPlugin } from "./builtin/cloud";
|
|
7
|
-
import { ibkrPlugin } from "./ibkr";
|
|
8
7
|
import { publicPlugin } from "./broker-sync/public";
|
|
9
8
|
import { robinhoodPlugin } from "./broker-sync/robinhood";
|
|
10
9
|
import { simpleFinPlugin } from "./broker-sync/simplefin";
|
|
11
10
|
import { predictionMarketsPlugin } from "./prediction-markets";
|
|
12
11
|
import { pollsPlugin } from "./builtin/polls";
|
|
13
12
|
import { alertsPlugin } from "./builtin/alerts";
|
|
13
|
+
import { researchSearchPlugin } from "./builtin/research-search";
|
|
14
14
|
import {
|
|
15
15
|
applicationPlugin,
|
|
16
16
|
brokerPlugin,
|
|
@@ -25,13 +25,11 @@ export const uiBuiltinPlugins: GloomPlugin[] = [
|
|
|
25
25
|
portfolioPlugin,
|
|
26
26
|
tickerResearchPlugin,
|
|
27
27
|
brokerPlugin,
|
|
28
|
-
ibkrPlugin,
|
|
29
28
|
publicPlugin,
|
|
30
29
|
robinhoodPlugin,
|
|
31
30
|
simpleFinPlugin,
|
|
32
31
|
applicationPlugin,
|
|
33
32
|
newsPlugin,
|
|
34
|
-
substackPlugin,
|
|
35
33
|
notesPlugin,
|
|
36
34
|
aiPlugin,
|
|
37
35
|
predictionMarketsPlugin,
|
|
@@ -39,8 +37,26 @@ export const uiBuiltinPlugins: GloomPlugin[] = [
|
|
|
39
37
|
marketOverviewPlugin,
|
|
40
38
|
macroPlugin,
|
|
41
39
|
alertsPlugin,
|
|
40
|
+
researchSearchPlugin,
|
|
42
41
|
];
|
|
43
42
|
|
|
44
43
|
export function getRendererBuiltinPlugins(): GloomPlugin[] {
|
|
45
44
|
return uiBuiltinPlugins;
|
|
46
45
|
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The plugin list for a UI renderer: the built-ins it ships with, plus any
|
|
49
|
+
* external plugins that loaded and support this renderer.
|
|
50
|
+
*
|
|
51
|
+
* Deliberately not `getLoadablePlugins`, which is the CLI catalog and also
|
|
52
|
+
* carries the Yahoo fallback provider and the debug plugin. Routing the desktop
|
|
53
|
+
* through it would quietly change which plugins the app runs.
|
|
54
|
+
*/
|
|
55
|
+
export function getRendererPlugins(externalPlugins: readonly LoadedExternalPlugin[] = []): GloomPlugin[] {
|
|
56
|
+
return [
|
|
57
|
+
...uiBuiltinPlugins,
|
|
58
|
+
...externalPlugins
|
|
59
|
+
.filter((entry) => !entry.error && !entry.unsupportedTarget)
|
|
60
|
+
.map((entry) => entry.plugin),
|
|
61
|
+
];
|
|
62
|
+
}
|