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
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public React runtime surface for external plugins (`gloomberb/react`).
|
|
3
|
+
*
|
|
4
|
+
* These hooks are the renderer-neutral way for plugin panes to reach app
|
|
5
|
+
* services. Plugin render code must use them instead of importing OpenTUI,
|
|
6
|
+
* Electrobun, or DOM APIs directly — the renderer decides how each one is
|
|
7
|
+
* fulfilled, which is what lets the same plugin run in the terminal, on the
|
|
8
|
+
* desktop, and (for web-capable plugins) in the browser.
|
|
9
|
+
*
|
|
10
|
+
* Compatibility commitment: see the note in `./utils.ts`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
deletePluginPaneStateValue,
|
|
15
|
+
getPluginPaneStateValue,
|
|
16
|
+
setPluginPaneStateValue,
|
|
17
|
+
useCapabilityInvoker,
|
|
18
|
+
useAssetData,
|
|
19
|
+
useConnectionHealth,
|
|
20
|
+
useDebouncedPluginPaneState,
|
|
21
|
+
useMarketData,
|
|
22
|
+
usePluginAppActions,
|
|
23
|
+
usePluginBrokerActions,
|
|
24
|
+
usePluginConfigState,
|
|
25
|
+
usePluginPaneActions,
|
|
26
|
+
usePluginPaneState,
|
|
27
|
+
usePluginState,
|
|
28
|
+
usePluginTickerActions,
|
|
29
|
+
useSetPluginConfigStates,
|
|
30
|
+
} from "../plugins/runtime";
|
|
31
|
+
export type { PluginRuntimeAccess } from "../plugins/runtime";
|
|
32
|
+
|
|
33
|
+
export { useInlineTickerOpener, useInlineTickers } from "../state/hooks/inline-tickers";
|
|
34
|
+
|
|
35
|
+
// Any feed plugin needs to remember which items have been read, persisted and
|
|
36
|
+
// capped. Substack and the news wire both use this; a third-party feed plugin
|
|
37
|
+
// would otherwise reimplement it or copy it and drift.
|
|
38
|
+
export {
|
|
39
|
+
DEFAULT_MAX_READ_IDS,
|
|
40
|
+
markPersistedReadId,
|
|
41
|
+
normalizePersistedReadIdState,
|
|
42
|
+
usePersistedReadIds,
|
|
43
|
+
} from "../plugins/builtin/shared/read-state";
|
|
44
|
+
export type { PersistedReadIdAdapter } from "../plugins/builtin/shared/read-state";
|
|
45
|
+
export type { InlineTickerCatalogEntry, UseInlineTickersOptions } from "../state/hooks/inline-tickers";
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
AppContext,
|
|
49
|
+
PaneInstanceProvider,
|
|
50
|
+
useAppConfig,
|
|
51
|
+
useAppDispatch,
|
|
52
|
+
useAppSelector,
|
|
53
|
+
useBrokerAccounts,
|
|
54
|
+
useInputCapture,
|
|
55
|
+
usePaneCollection,
|
|
56
|
+
usePaneInstanceId,
|
|
57
|
+
usePaneTicker,
|
|
58
|
+
useTickers,
|
|
59
|
+
} from "./pane-hooks";
|
|
60
|
+
|
|
61
|
+
// Keyboard handling for plugin panes; the renderer decides how events arrive.
|
|
62
|
+
export { useShortcut } from "../react/input";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test helpers for external plugin repositories (`gloomberb/test-support`).
|
|
3
|
+
*
|
|
4
|
+
* Plugins live in their own repos and run their own `bun test`, so the fakes and
|
|
5
|
+
* render harness they need have to ship with the host rather than being copied
|
|
6
|
+
* per plugin and drifting.
|
|
7
|
+
*
|
|
8
|
+
* The harness is OpenTUI-backed. That is the host's business, not the plugin's:
|
|
9
|
+
* a plugin importing a renderer package directly stops working on the other
|
|
10
|
+
* renderers, which is exactly what going through this module avoids.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export { MemoryPluginPersistence } from "../test-support/plugin-persistence";
|
|
14
|
+
|
|
15
|
+
export { TestDialogProvider, testRender } from "../renderers/opentui/test-utils";
|
|
16
|
+
|
|
17
|
+
export { AppContext, PaneInstanceProvider } from "../state/app/context";
|
|
18
|
+
export { createInitialState } from "../core/state/app/state";
|
|
19
|
+
|
|
20
|
+
// Broker plugins test against the real account cache and persistence rather
|
|
21
|
+
// than a hand-rolled double that drifts from how the app actually stores rows.
|
|
22
|
+
export { AppPersistence } from "../data/app-persistence";
|
|
23
|
+
export * from "../brokers/account-cache";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chart range and resolution vocabulary (`gloomberb/time-series`).
|
|
3
|
+
*
|
|
4
|
+
* Shared so a plugin producing history speaks the same range and resolution
|
|
5
|
+
* language as the charts that render it, instead of inventing a parallel one.
|
|
6
|
+
*/
|
|
7
|
+
export * from "../time-series/range";
|
|
8
|
+
export * from "../time-series/resolution";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public utility surface for external plugins (`gloomberb/utils`).
|
|
3
|
+
*
|
|
4
|
+
* Everything re-exported here is a compatibility commitment: plugins in other
|
|
5
|
+
* repositories import it, so removing or changing a signature is a breaking
|
|
6
|
+
* change for them. Internal helpers stay internal — add to this barrel only
|
|
7
|
+
* when a real plugin needs it, and prefer widening later over exporting
|
|
8
|
+
* speculatively.
|
|
9
|
+
*
|
|
10
|
+
* `scripts/check-public-api.ts` pins the exported names so the surface cannot
|
|
11
|
+
* grow by accident during an unrelated refactor.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export { createThrottledFetch } from "../utils/throttled-fetch";
|
|
15
|
+
export type {
|
|
16
|
+
ThrottledFetchClient,
|
|
17
|
+
ThrottledFetchOptions,
|
|
18
|
+
ThrottledFetchTransport,
|
|
19
|
+
} from "../utils/throttled-fetch";
|
|
20
|
+
|
|
21
|
+
export { normalizedHttpUrl } from "../utils/url";
|
|
22
|
+
|
|
23
|
+
export { formatRelativeAge } from "../utils/relative-time";
|
|
24
|
+
|
|
25
|
+
export { decodeHtmlEntities } from "../utils/html-entities";
|
|
26
|
+
|
|
27
|
+
export { isPlainKey, isPlainKeyboardEvent } from "../utils/keyboard";
|
|
28
|
+
export type { KeyboardModifierEventLike } from "../utils/keyboard";
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
displayWidth,
|
|
32
|
+
formatCompact,
|
|
33
|
+
formatCompactCurrency,
|
|
34
|
+
formatCurrency,
|
|
35
|
+
formatGrowthShort,
|
|
36
|
+
formatNumber,
|
|
37
|
+
formatPercent,
|
|
38
|
+
formatPercentRaw,
|
|
39
|
+
formatTimeAgo,
|
|
40
|
+
formatWithDivisor,
|
|
41
|
+
padTo,
|
|
42
|
+
pickUnit,
|
|
43
|
+
truncateToDisplayWidth,
|
|
44
|
+
} from "../utils/format";
|
|
45
|
+
|
|
46
|
+
// Broker and instrument helpers. IBKR needs all of these, and any broker plugin
|
|
47
|
+
// will: instance lookup, currency minor units, venue normalization, and stable
|
|
48
|
+
// hashing for cache keys.
|
|
49
|
+
export {
|
|
50
|
+
buildBrokerPortfolioId,
|
|
51
|
+
createBrokerInstanceId,
|
|
52
|
+
getBrokerInstance,
|
|
53
|
+
getBrokerInstancesByType,
|
|
54
|
+
isBrokerPortfolioId,
|
|
55
|
+
} from "../utils/broker-instances";
|
|
56
|
+
export {
|
|
57
|
+
hasLikelyQuoteUnitMismatch,
|
|
58
|
+
normalizePriceValueByDivisor,
|
|
59
|
+
resolveCurrencyUnit,
|
|
60
|
+
resolveExchangeSubUnitCurrencyUnit,
|
|
61
|
+
resolvePriceHistoryCurrencyUnit,
|
|
62
|
+
} from "../utils/currency-units";
|
|
63
|
+
export type { CurrencyUnitInfo } from "../utils/currency-units";
|
|
64
|
+
export {
|
|
65
|
+
canonicalExchange,
|
|
66
|
+
canonicalTickerKey,
|
|
67
|
+
normalizeSymbol,
|
|
68
|
+
parsePublicTickerKey,
|
|
69
|
+
publicExchange,
|
|
70
|
+
publicTickerKey,
|
|
71
|
+
resolveExchangeTimeZone,
|
|
72
|
+
} from "../utils/exchanges";
|
|
73
|
+
export { fnv1aHashString } from "../utils/hash";
|
|
74
|
+
export { splitLongTextSegmentByDisplayWidth, truncateWithEllipsis, wrapTextLines } from "../utils/text-wrap";
|
|
75
|
+
export { httpFetch, setHttpFetchTransport } from "../utils/http-transport";
|
|
76
|
+
export type { HttpFetchTransport } from "../utils/http-transport";
|
|
77
|
+
export { debugLog } from "../utils/debug-log";
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
getPlacedPaneInstanceIds,
|
|
3
|
+
type DockLayoutNode,
|
|
4
|
+
type LayoutConfig,
|
|
5
|
+
type PaneInstanceConfig,
|
|
6
|
+
} from "../types/config";
|
|
5
7
|
|
|
6
8
|
export function requirePaneInstance(layout: LayoutConfig, paneId: string): PaneInstanceConfig {
|
|
7
9
|
const instance = layout.instances.find((entry) => entry.instanceId === paneId)
|
|
@@ -27,11 +29,7 @@ export function buildGridDockRoot(paneIds: string[], columns?: number): DockLayo
|
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export function visiblePaneIds(layout: LayoutConfig): string[] {
|
|
30
|
-
|
|
31
|
-
getDockedPaneIds(layout).forEach((id) => ids.add(id));
|
|
32
|
-
layout.floating.forEach((entry) => ids.add(entry.instanceId));
|
|
33
|
-
(layout.detached ?? []).forEach((entry) => ids.add(entry.instanceId));
|
|
34
|
-
return [...ids];
|
|
32
|
+
return getPlacedPaneInstanceIds(layout);
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
export function regionToDockPosition(region: string): "left" | "right" | "above" | "below" {
|
package/src/remote/resources.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Dispatch } from "react";
|
|
2
2
|
import type { PluginRegistry } from "../plugins/registry";
|
|
3
|
+
import { apiClient } from "../api-client";
|
|
3
4
|
import type { AppAction, AppState } from "../state/app/context";
|
|
4
5
|
import type { PaneRuntimeState } from "../core/state/app/state";
|
|
5
6
|
import { setPaneSettings } from "../pane-settings";
|
|
@@ -99,6 +100,7 @@ export function createRemoteResources({
|
|
|
99
100
|
if (resource === "app://command-bar") return commandBarSnapshot(state, uiNodes);
|
|
100
101
|
if (resource === "app://command-bar/results") return commandBarResultsFromNodes(uiNodes);
|
|
101
102
|
if (resource === "app://capabilities") return pluginRegistry.capabilities.manifests();
|
|
103
|
+
if (resource === "app://auth") return apiClient.describeAuthState();
|
|
102
104
|
if (resource === "app://remote/help") return REMOTE_AGENT_HELP;
|
|
103
105
|
if (resource === "ui://tree") return uiNodes;
|
|
104
106
|
throw new Error(`Unknown remote resource "${resource}".`);
|
package/src/remote/schema.ts
CHANGED
|
@@ -14,6 +14,7 @@ export const REMOTE_RESOURCES: RemoteResourceSchema[] = [
|
|
|
14
14
|
{ uri: "app://command-bar", description: "Current command-bar state and semantic result rows." },
|
|
15
15
|
{ uri: "app://command-bar/results", description: "Current semantic command-bar result rows." },
|
|
16
16
|
{ uri: "app://capabilities", description: "Registered plugin capability manifests." },
|
|
17
|
+
{ uri: "app://auth", description: "What the client believes about its cloud session: credential present, checked, cached user plan and verification. No secrets." },
|
|
17
18
|
{ uri: "app://remote/help", description: "Agent-oriented remote usage guide with efficient recipes and caveats." },
|
|
18
19
|
{ uri: "ui://tree", description: "Live semantic UI node tree populated by shared controls and interactive primitives." },
|
|
19
20
|
];
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { afterEach, expect, test } from "bun:test";
|
|
2
|
+
import { act, useState } from "react";
|
|
3
|
+
import { testRender } from "../renderers/opentui/test-utils";
|
|
4
|
+
import {
|
|
5
|
+
RemoteUiRegistryProvider,
|
|
6
|
+
createRemoteUiRegistry,
|
|
7
|
+
useRemoteUiNode,
|
|
8
|
+
} from "./semantic-tree";
|
|
9
|
+
|
|
10
|
+
let setup: Awaited<ReturnType<typeof testRender>> | undefined;
|
|
11
|
+
let setValue: ((value: number) => void) | null = null;
|
|
12
|
+
let metadataReads = 0;
|
|
13
|
+
|
|
14
|
+
afterEach(async () => {
|
|
15
|
+
if (!setup) return;
|
|
16
|
+
await act(async () => setup?.renderer.destroy());
|
|
17
|
+
setup = undefined;
|
|
18
|
+
setValue = null;
|
|
19
|
+
metadataReads = 0;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
function LazyMetadataNode() {
|
|
23
|
+
useRemoteUiNode({
|
|
24
|
+
role: "table",
|
|
25
|
+
getMetadata: () => {
|
|
26
|
+
metadataReads += 1;
|
|
27
|
+
return { rowCount: 200 };
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
return <text>table</text>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function DynamicNode() {
|
|
34
|
+
const [value, updateValue] = useState(1);
|
|
35
|
+
setValue = updateValue;
|
|
36
|
+
useRemoteUiNode({
|
|
37
|
+
role: "button",
|
|
38
|
+
label: `Value ${value}`,
|
|
39
|
+
actions: { read: () => value },
|
|
40
|
+
metadata: { value },
|
|
41
|
+
});
|
|
42
|
+
return <text>{value}</text>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
test("semantic registry rejects non-function actions consistently", async () => {
|
|
46
|
+
const registry = createRemoteUiRegistry();
|
|
47
|
+
registry.register("invalid", {
|
|
48
|
+
role: "button",
|
|
49
|
+
actions: { broken: "not-a-function" as unknown as () => void },
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(registry.snapshot()[0]?.actions).toEqual([]);
|
|
53
|
+
await expect(registry.invoke("invalid", "broken")).rejects.toThrow(
|
|
54
|
+
'UI node "invalid" does not expose action "broken".',
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("semantic metadata is projected only when a remote snapshot requests it", async () => {
|
|
59
|
+
const registry = createRemoteUiRegistry();
|
|
60
|
+
setup = await testRender(
|
|
61
|
+
<RemoteUiRegistryProvider registry={registry}>
|
|
62
|
+
<LazyMetadataNode />
|
|
63
|
+
</RemoteUiRegistryProvider>,
|
|
64
|
+
{ width: 20, height: 4 },
|
|
65
|
+
);
|
|
66
|
+
await setup.renderOnce();
|
|
67
|
+
|
|
68
|
+
expect(metadataReads).toBe(0);
|
|
69
|
+
expect(registry.snapshot()[0]?.metadata).toEqual({ rowCount: 200 });
|
|
70
|
+
expect(metadataReads).toBe(1);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("semantic nodes keep current behavior without re-registering on every render", async () => {
|
|
74
|
+
const registry = createRemoteUiRegistry();
|
|
75
|
+
const register = registry.register.bind(registry);
|
|
76
|
+
let registrations = 0;
|
|
77
|
+
registry.register = (id, registration) => {
|
|
78
|
+
registrations += 1;
|
|
79
|
+
register(id, registration);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
setup = await testRender(
|
|
83
|
+
<RemoteUiRegistryProvider registry={registry}>
|
|
84
|
+
<DynamicNode />
|
|
85
|
+
</RemoteUiRegistryProvider>,
|
|
86
|
+
{ width: 20, height: 4 },
|
|
87
|
+
);
|
|
88
|
+
await setup.renderOnce();
|
|
89
|
+
|
|
90
|
+
const nodeId = registry.snapshot()[0]!.id;
|
|
91
|
+
expect(registrations).toBe(1);
|
|
92
|
+
expect(await registry.invoke(nodeId, "read")).toBe(1);
|
|
93
|
+
|
|
94
|
+
await act(async () => {
|
|
95
|
+
setValue?.(2);
|
|
96
|
+
await setup!.renderOnce();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(registrations).toBe(1);
|
|
100
|
+
expect(registry.snapshot()[0]).toMatchObject({
|
|
101
|
+
label: "Value 2",
|
|
102
|
+
metadata: { value: 2 },
|
|
103
|
+
});
|
|
104
|
+
expect(await registry.invoke(nodeId, "read")).toBe(2);
|
|
105
|
+
});
|
|
@@ -17,11 +17,13 @@ export interface RemoteUiNodeRegistration {
|
|
|
17
17
|
disabled?: boolean;
|
|
18
18
|
actions?: Record<string, RemoteUiAction | undefined>;
|
|
19
19
|
metadata?: Record<string, unknown>;
|
|
20
|
+
/** Defers expensive metadata projection until a remote snapshot requests it. */
|
|
21
|
+
getMetadata?: () => Record<string, unknown>;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
interface RegisteredRemoteUiNode
|
|
24
|
+
interface RegisteredRemoteUiNode {
|
|
23
25
|
id: string;
|
|
24
|
-
|
|
26
|
+
registration: RemoteUiNodeRegistration;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
export interface RemoteUiRegistry {
|
|
@@ -33,46 +35,54 @@ export interface RemoteUiRegistry {
|
|
|
33
35
|
|
|
34
36
|
const RemoteUiRegistryContext = createContext<RemoteUiRegistry | null>(null);
|
|
35
37
|
|
|
36
|
-
function createRemoteUiRegistry(): RemoteUiRegistry {
|
|
38
|
+
export function createRemoteUiRegistry(): RemoteUiRegistry {
|
|
37
39
|
const nodes = new Map<string, RegisteredRemoteUiNode>();
|
|
38
40
|
|
|
39
41
|
return {
|
|
40
42
|
register(id, registration) {
|
|
41
|
-
nodes.set(id, {
|
|
42
|
-
...registration,
|
|
43
|
-
id,
|
|
44
|
-
actions: Object.fromEntries(
|
|
45
|
-
Object.entries(registration.actions ?? {})
|
|
46
|
-
.filter((entry): entry is [string, RemoteUiAction] => typeof entry[1] === "function"),
|
|
47
|
-
),
|
|
48
|
-
});
|
|
43
|
+
nodes.set(id, { id, registration });
|
|
49
44
|
},
|
|
50
45
|
unregister(id) {
|
|
51
46
|
nodes.delete(id);
|
|
52
47
|
},
|
|
53
48
|
snapshot() {
|
|
54
|
-
return [...nodes.values()].map((node) =>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
49
|
+
return [...nodes.values()].map((node) => {
|
|
50
|
+
const registration = node.registration;
|
|
51
|
+
return {
|
|
52
|
+
id: node.id,
|
|
53
|
+
role: registration.role,
|
|
54
|
+
label: registration.label,
|
|
55
|
+
disabled: registration.disabled,
|
|
56
|
+
actions: Object.entries(registration.actions ?? {})
|
|
57
|
+
.filter((entry): entry is [string, RemoteUiAction] => typeof entry[1] === "function")
|
|
58
|
+
.map(([action]) => action)
|
|
59
|
+
.sort(),
|
|
60
|
+
metadata: registration.getMetadata?.() ?? registration.metadata,
|
|
61
|
+
};
|
|
62
|
+
});
|
|
62
63
|
},
|
|
63
64
|
async invoke(nodeId, action, input) {
|
|
64
65
|
const node = nodes.get(nodeId);
|
|
65
66
|
if (!node) throw new Error(`Unknown UI node "${nodeId}".`);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
const registration = node.registration;
|
|
68
|
+
if (registration.disabled) throw new Error(`UI node "${nodeId}" is disabled.`);
|
|
69
|
+
const handler = registration.actions?.[action];
|
|
70
|
+
if (typeof handler !== "function") {
|
|
71
|
+
throw new Error(`UI node "${nodeId}" does not expose action "${action}".`);
|
|
72
|
+
}
|
|
69
73
|
return await handler(input);
|
|
70
74
|
},
|
|
71
75
|
};
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
export function RemoteUiRegistryProvider({
|
|
75
|
-
|
|
78
|
+
export function RemoteUiRegistryProvider({
|
|
79
|
+
children,
|
|
80
|
+
registry,
|
|
81
|
+
}: {
|
|
82
|
+
children: ReactNode;
|
|
83
|
+
registry?: RemoteUiRegistry;
|
|
84
|
+
}) {
|
|
85
|
+
const registryRef = useRef<RemoteUiRegistry | null>(registry ?? null);
|
|
76
86
|
if (!registryRef.current) {
|
|
77
87
|
registryRef.current = createRemoteUiRegistry();
|
|
78
88
|
}
|
|
@@ -91,22 +101,30 @@ export function useRemoteUiNode(registration: RemoteUiNodeRegistration | null |
|
|
|
91
101
|
const registry = useRemoteUiRegistry();
|
|
92
102
|
const generatedId = useId();
|
|
93
103
|
const nodeId = useMemo(() => `ui:${generatedId.replace(/:/g, "")}`, [generatedId]);
|
|
104
|
+
const registrationRef = useRef(registration);
|
|
105
|
+
registrationRef.current = registration;
|
|
106
|
+
const dynamicRegistrationRef = useRef<RemoteUiNodeRegistration | null>(null);
|
|
107
|
+
if (!dynamicRegistrationRef.current) {
|
|
108
|
+
dynamicRegistrationRef.current = {
|
|
109
|
+
get role() { return registrationRef.current?.role ?? "unknown"; },
|
|
110
|
+
get label() { return registrationRef.current?.label; },
|
|
111
|
+
get disabled() { return registrationRef.current?.disabled; },
|
|
112
|
+
get actions() { return registrationRef.current?.actions; },
|
|
113
|
+
get metadata() { return registrationRef.current?.metadata; },
|
|
114
|
+
get getMetadata() { return registrationRef.current?.getMetadata; },
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const registered = registration != null;
|
|
94
118
|
|
|
95
119
|
useEffect(() => {
|
|
96
120
|
if (!registry) return;
|
|
97
|
-
if (!
|
|
121
|
+
if (!registered) {
|
|
98
122
|
registry.unregister(nodeId);
|
|
99
123
|
return;
|
|
100
124
|
}
|
|
101
|
-
registry.register(nodeId,
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
return () => registry?.unregister(nodeId);
|
|
106
|
-
}, [
|
|
107
|
-
nodeId,
|
|
108
|
-
registry,
|
|
109
|
-
]);
|
|
125
|
+
registry.register(nodeId, dynamicRegistrationRef.current!);
|
|
126
|
+
return () => registry.unregister(nodeId);
|
|
127
|
+
}, [nodeId, registered, registry]);
|
|
110
128
|
|
|
111
|
-
return registry &&
|
|
129
|
+
return registry && registered ? nodeId : null;
|
|
112
130
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** @jsxImportSource react */
|
|
2
|
+
import { setCurrentPluginTarget } from "../../plugins/current-target";
|
|
2
3
|
import "../electrobun/view/styles.css";
|
|
3
4
|
import { createRoot } from "react-dom/client";
|
|
4
5
|
import { App } from "../../app";
|
|
@@ -21,6 +22,10 @@ import { BROWSER_DATA_DIR, installBrowserConfigStore } from "./config-host";
|
|
|
21
22
|
import { browserRendererHost, browserUiHost } from "./ui-host";
|
|
22
23
|
import { createBrowserDeepLinkBridge } from "./deeplink-bridge";
|
|
23
24
|
|
|
25
|
+
// Declared here rather than sniffed: the desktop view and the hosted browser
|
|
26
|
+
// app are both browser contexts but differ in what plugins may do.
|
|
27
|
+
setCurrentPluginTarget("web");
|
|
28
|
+
|
|
24
29
|
const rootElement = document.getElementById("root");
|
|
25
30
|
if (!rootElement) throw new Error("Missing root element");
|
|
26
31
|
const appRootElement = rootElement;
|
|
@@ -48,6 +53,7 @@ async function boot(): Promise<void> {
|
|
|
48
53
|
plugins={getBrowserBuiltinPlugins()}
|
|
49
54
|
desktopDeepLinkBridge={deepLinkBridge}
|
|
50
55
|
updatesEnabled={false}
|
|
56
|
+
requireSignIn
|
|
51
57
|
/>
|
|
52
58
|
</WebDialogHostProvider>
|
|
53
59
|
</WebToastHostProvider>
|
|
@@ -27,6 +27,17 @@ export const browserRendererHost: RendererHost = {
|
|
|
27
27
|
async readText() {
|
|
28
28
|
return navigator.clipboard.readText();
|
|
29
29
|
},
|
|
30
|
+
async saveTextFile({ name, text, mimeType }) {
|
|
31
|
+
const url = URL.createObjectURL(new Blob([text], { type: mimeType }));
|
|
32
|
+
const anchor = document.createElement("a");
|
|
33
|
+
anchor.href = url;
|
|
34
|
+
anchor.download = name;
|
|
35
|
+
document.body.appendChild(anchor);
|
|
36
|
+
anchor.click();
|
|
37
|
+
anchor.remove();
|
|
38
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
39
|
+
return `Downloads/${name}`;
|
|
40
|
+
},
|
|
30
41
|
async copyPngImage(pngBase64) {
|
|
31
42
|
const ClipboardItemCtor = globalThis.ClipboardItem;
|
|
32
43
|
if (!ClipboardItemCtor || !navigator.clipboard.write) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { handleRequest, SECURITY_HEADERS, type WorkerEnv } from "./worker";
|
|
3
|
+
import { REGISTRY_ORIGIN } from "../../plugins/builtin/plugin-marketplace/feed";
|
|
3
4
|
|
|
4
5
|
function fixture() {
|
|
5
6
|
const requests: Request[] = [];
|
|
@@ -134,3 +135,21 @@ describe("static Cloudflare host", () => {
|
|
|
134
135
|
expect(requests).toHaveLength(0);
|
|
135
136
|
});
|
|
136
137
|
});
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The hosted build can only reach origins listed in `connect-src`. A blocked
|
|
141
|
+
* fetch is not an error the user can act on: the plugin marketplace just shows
|
|
142
|
+
* an empty catalog. This shipped that way once, because the pane was verified
|
|
143
|
+
* on the terminal and desktop but not on the web build, so the two values are
|
|
144
|
+
* pinned to each other here.
|
|
145
|
+
*/
|
|
146
|
+
describe("content security policy", () => {
|
|
147
|
+
test("allows the plugin registry the marketplace fetches", () => {
|
|
148
|
+
const connectSrc = SECURITY_HEADERS["content-security-policy"]
|
|
149
|
+
.split(";")
|
|
150
|
+
.map((directive) => directive.trim())
|
|
151
|
+
.find((directive) => directive.startsWith("connect-src"));
|
|
152
|
+
|
|
153
|
+
expect(connectSrc).toContain(REGISTRY_ORIGIN);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -39,7 +39,7 @@ const API_METHODS = new Set(["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "O
|
|
|
39
39
|
type ApiFetch = (request: Request) => Promise<Response>;
|
|
40
40
|
|
|
41
41
|
export const SECURITY_HEADERS = {
|
|
42
|
-
"content-security-policy": "default-src 'self'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; media-src 'self' https:; connect-src 'self' https://api.github.com https://api.fiscaldata.treasury.gov; form-action 'self'; upgrade-insecure-requests",
|
|
42
|
+
"content-security-policy": "default-src 'self'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; media-src 'self' https:; connect-src 'self' https://api.github.com https://api.fiscaldata.treasury.gov https://plugins.gloom.sh; form-action 'self'; upgrade-insecure-requests",
|
|
43
43
|
"cross-origin-opener-policy": "same-origin",
|
|
44
44
|
"cross-origin-resource-policy": "same-origin",
|
|
45
45
|
"permissions-policy": "camera=(), geolocation=(), microphone=(), payment=(), usb=()",
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
import { safeExternalUrl } from "../../../../utils/external-url";
|
|
11
11
|
import { getContextMenuRequestId, normalizeContextMenuItems } from "../context-menu/normalize";
|
|
12
12
|
import { MAIN_WINDOW_RPC_KEY } from "../window/focus";
|
|
13
|
+
import { saveTextFileToDownloads } from "../../../../utils/save-text-file";
|
|
13
14
|
|
|
14
15
|
interface DesktopHostRequestOptions<TRpc> {
|
|
15
16
|
clearMainWindow: () => void;
|
|
@@ -51,7 +52,7 @@ function normalizeWindowControlAction(action: unknown): DesktopWindowControlActi
|
|
|
51
52
|
throw new Error("host.windowControl requires a valid action.");
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
export function handleDesktopHostRequest<TRpc>({
|
|
55
|
+
export async function handleDesktopHostRequest<TRpc>({
|
|
55
56
|
clearMainWindow,
|
|
56
57
|
closeAllDetachedWindows,
|
|
57
58
|
controlWindowForRpcKey,
|
|
@@ -63,7 +64,7 @@ export function handleDesktopHostRequest<TRpc>({
|
|
|
63
64
|
rpc,
|
|
64
65
|
teardownServices,
|
|
65
66
|
trackContextMenuRequest,
|
|
66
|
-
}: DesktopHostRequestOptions<TRpc>): DesktopBackendRequestResponse<DesktopHostRequest["method"]
|
|
67
|
+
}: DesktopHostRequestOptions<TRpc>): Promise<DesktopBackendRequestResponse<DesktopHostRequest["method"]>> {
|
|
67
68
|
switch (request.method) {
|
|
68
69
|
case "host.restart":
|
|
69
70
|
restartDesktopApp({
|
|
@@ -127,6 +128,12 @@ export function handleDesktopHostRequest<TRpc>({
|
|
|
127
128
|
}
|
|
128
129
|
case "host.readText":
|
|
129
130
|
return Utils.clipboardReadText() ?? "";
|
|
131
|
+
case "host.saveTextFile": {
|
|
132
|
+
if (typeof request.payload.name !== "string" || typeof request.payload.text !== "string") {
|
|
133
|
+
throw new Error("host.saveTextFile requires a name and text.");
|
|
134
|
+
}
|
|
135
|
+
return saveTextFileToDownloads(request.payload.name, request.payload.text);
|
|
136
|
+
}
|
|
130
137
|
case "host.notify":
|
|
131
138
|
playNotificationSound(normalizeText(request.payload.sound));
|
|
132
139
|
Utils.showNotification({
|