gloomberb 0.11.0 → 0.11.2
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 +38 -15
- package/README.zh-CN.md +12 -12
- package/package.json +10 -3
- package/src/api-client/auth.ts +14 -10
- package/src/api-client/data.ts +54 -0
- package/src/api-client/index.test.ts +124 -0
- package/src/api-client/index.ts +139 -4
- package/src/api-client/paths.ts +89 -0
- package/src/api-client/request.ts +15 -2
- package/src/api-client/socket-health.test.ts +1 -0
- package/src/api-client/socket.ts +2 -1
- package/src/api-client/types.ts +163 -0
- package/src/app/pane-runtime/index.tsx +4 -1
- package/src/app/pane-runtime/plugin-bindings.test.ts +117 -0
- package/src/app/pane-runtime/plugin-bindings.ts +25 -4
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +25 -14
- package/src/app/runtime/desktop-deeplink.test.ts +16 -0
- package/src/app/runtime/desktop-deeplink.ts +66 -2
- package/src/app/runtime/desktop-menu.ts +3 -0
- package/src/app/runtime/plugin-bindings.ts +30 -28
- package/src/app/runtime/update.ts +8 -6
- package/src/app.tsx +21 -7
- package/src/architecture/import-boundaries.test.ts +5 -1
- package/src/capabilities/factories.ts +4 -0
- package/src/cli/commands/automation.test.ts +9 -0
- package/src/cli/commands/automation.ts +22 -73
- package/src/cli/index.ts +10 -6
- package/src/cli/registry.ts +1 -1
- package/src/components/chart/composite/composite-chart.test.tsx +45 -2
- package/src/components/chart/composite/composite-chart.tsx +16 -12
- package/src/components/chart/composite/format.test.ts +34 -0
- package/src/components/chart/composite/format.ts +33 -10
- package/src/components/chart/composite/scene.ts +0 -21
- package/src/components/chart/composite/tools.ts +49 -0
- package/src/components/chart/core/axis-format.ts +13 -3
- package/src/components/chart/core/index.test.ts +7 -0
- package/src/components/chart/core/renderer.ts +3 -4
- package/src/components/command-bar/commands/direct/index.ts +4 -0
- package/src/components/command-bar/commands/registry.ts +8 -1
- package/src/components/command-bar/layout-items/current-layout.ts +1 -1
- package/src/components/command-bar/pane-templates/workflow.ts +4 -0
- package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -0
- package/src/components/command-bar/routes/root/shortcuts.test.ts +19 -0
- package/src/components/command-bar/routes/root/shortcuts.ts +3 -1
- package/src/components/command-bar/surface/index.test.tsx +6 -6
- package/src/components/command-bar/surface/index.tsx +4 -1
- package/src/components/command-bar/surface/pane.test.tsx +66 -2
- package/src/components/command-bar/view-model.test.ts +2 -1
- package/src/components/command-bar/workflow/ops.test.ts +54 -0
- package/src/components/command-bar/workflow/ops.ts +1 -1
- package/src/components/company-logo.test.ts +19 -0
- package/src/components/company-logo.tsx +60 -0
- package/src/components/feed-data-table/stack-view.tsx +7 -1
- package/src/components/index.ts +1 -0
- package/src/components/layout/detached-pane-shell.tsx +85 -15
- package/src/components/layout/header.tsx +5 -5
- package/src/components/layout/pane/title.ts +31 -3
- package/src/components/layout/shell/active-drag.ts +4 -11
- package/src/components/layout/shell/drag/index.ts +4 -20
- package/src/components/layout/shell/drag/runtime.ts +3 -4
- package/src/components/layout/shell/index.test.tsx +17 -3
- package/src/components/layout/shell/index.tsx +89 -31
- package/src/components/layout/shell/menu.test.ts +37 -1
- package/src/components/layout/shell/menu.ts +14 -0
- package/src/components/layout/shell/native/pointer-runtime.ts +22 -8
- package/src/components/layout/shell/pane/actions.ts +0 -5
- package/src/components/layout/shell/pane/management-shortcuts.ts +9 -4
- package/src/components/layout/shell/shortcuts.ts +6 -3
- package/src/components/layout/shell/ticker-link-menu.test.ts +82 -0
- package/src/components/layout/shell/ticker-link-menu.ts +78 -0
- package/src/components/layout/status-bar.test.tsx +62 -61
- package/src/components/layout/status-bar.tsx +53 -75
- package/src/components/onboarding/account-step/form-panel.tsx +4 -2
- package/src/components/onboarding/account-step/index.tsx +1 -1
- package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
- package/src/components/onboarding/onboarding-wizard.test.tsx +44 -1
- package/src/components/onboarding/onboarding-wizard.tsx +13 -5
- package/src/components/onboarding/wizard-account.ts +4 -23
- package/src/components/table-view-shared.test.ts +11 -0
- package/src/components/table-view-shared.tsx +26 -0
- package/src/components/ui/data-table/index.tsx +19 -1
- package/src/components/ui/fields.tsx +5 -1
- package/src/core/state/app/layout-reducer.ts +36 -0
- package/src/core/state/app/layout.ts +10 -5
- package/src/core/state/app/state.ts +0 -13
- package/src/core/state/app/types.ts +6 -4
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/config/layout.ts +2 -0
- package/src/data/config/store/index.test.ts +27 -6
- package/src/data/config/store/normalize.ts +3 -5
- package/src/data/plugin-state-store.ts +11 -0
- package/src/data/sqlite/cache.test.ts +10 -0
- package/src/i18n/es.ts +13 -5
- package/src/i18n/ja.ts +13 -5
- package/src/i18n/ko.ts +13 -5
- package/src/i18n/zh-cn.ts +13 -5
- package/src/i18n/zh-tw.ts +13 -5
- package/src/layout-marketplace/api.test.ts +54 -0
- package/src/layout-marketplace/api.ts +65 -0
- package/src/layout-marketplace/gallery-desktop.test.tsx +262 -0
- package/src/layout-marketplace/gallery-desktop.tsx +375 -0
- package/src/layout-marketplace/gallery-terminal.tsx +376 -0
- package/src/layout-marketplace/gallery.test.tsx +84 -0
- package/src/layout-marketplace/gallery.tsx +323 -0
- package/src/layout-marketplace/geometry.test.ts +47 -0
- package/src/layout-marketplace/geometry.ts +80 -0
- package/src/layout-marketplace/mini-workspace.test.tsx +122 -0
- package/src/layout-marketplace/mini-workspace.tsx +428 -0
- package/src/layout-marketplace/model.test.ts +118 -0
- package/src/layout-marketplace/model.ts +171 -0
- package/src/layout-marketplace/name-dialog.tsx +60 -0
- package/src/layout-marketplace/pane-imagery.ts +85 -0
- package/src/layout-marketplace/pane.tsx +20 -0
- package/src/layout-marketplace/payload.test.ts +246 -0
- package/src/layout-marketplace/payload.ts +624 -0
- package/src/layout-marketplace/use-marketplace.ts +63 -0
- package/src/market-data/coordinator/index.test.ts +2 -2
- package/src/market-data/coordinator/index.ts +1 -1
- package/src/market-data/hooks.tsx +0 -41
- package/src/market-data/market/format.test.ts +7 -0
- package/src/market-data/market/freshness.ts +5 -2
- package/src/market-data/quotes/freshness.test.ts +16 -0
- package/src/news/aggregator.test.ts +92 -3
- package/src/news/aggregator.ts +98 -18
- package/src/news/hooks.test.tsx +2 -0
- package/src/news/hooks.ts +27 -1
- package/src/news/news-model.ts +5 -2
- package/src/news/types.ts +7 -0
- package/src/plugins/broker-sync/broker-sync.test.ts +58 -0
- package/src/plugins/broker-sync/normalize.ts +42 -5
- package/src/plugins/builtin/account-management/pane.tsx +5 -5
- package/src/plugins/builtin/ai/index.tsx +18 -2
- package/src/plugins/builtin/ai/screener/pane.test.tsx +47 -4
- package/src/plugins/builtin/ai/screener/pane.tsx +35 -12
- package/src/plugins/builtin/alerts/alert-engine.test.ts +53 -1
- package/src/plugins/builtin/alerts/alert-engine.ts +34 -1
- package/src/plugins/builtin/alerts/index.test.tsx +49 -6
- package/src/plugins/builtin/alerts/index.tsx +11 -6
- package/src/plugins/builtin/alerts/pane.tsx +51 -9
- package/src/plugins/builtin/alerts/quotes.ts +11 -3
- package/src/plugins/builtin/alerts/types.ts +1 -0
- package/src/plugins/builtin/analytics/index.tsx +11 -1
- package/src/plugins/builtin/analytics/pane-model.ts +6 -4
- package/src/plugins/builtin/broker-manager/index.tsx +3 -0
- package/src/plugins/builtin/buildout/model/index.ts +1 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +17 -18
- package/src/plugins/builtin/cds/client.test.ts +83 -0
- package/src/plugins/builtin/cds/client.ts +67 -0
- package/src/plugins/builtin/cds/index.tsx +41 -0
- package/src/plugins/builtin/cds/model.test.ts +232 -0
- package/src/plugins/builtin/cds/model.ts +401 -0
- package/src/plugins/builtin/cds/pane.test.tsx +132 -0
- package/src/plugins/builtin/cds/pane.tsx +358 -0
- package/src/plugins/builtin/chart-composer/chart-spec.ts +31 -0
- package/src/plugins/builtin/chart-composer/index.tsx +88 -2
- package/src/plugins/builtin/chart-composer/pane-share.test.ts +71 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +103 -15
- package/src/plugins/builtin/chat/content/index.tsx +4 -1
- package/src/plugins/builtin/chat/controller/channels.ts +7 -1
- package/src/plugins/builtin/chat/controller/index.ts +14 -9
- package/src/plugins/builtin/chat/controller/notifications.ts +13 -3
- package/src/plugins/builtin/chat/controller/persistence.ts +0 -3
- package/src/plugins/builtin/chat/controller/realtime.ts +3 -3
- package/src/plugins/builtin/chat/controller/send.ts +1 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +18 -11
- package/src/plugins/builtin/chat/controller/state.ts +1 -3
- package/src/plugins/builtin/chat/controller/storage.ts +0 -2
- package/src/plugins/builtin/chat/controller/utils.ts +4 -6
- package/src/plugins/builtin/chat/controller/view.ts +2 -2
- package/src/plugins/builtin/chat/controller.test.ts +142 -14
- package/src/plugins/builtin/chat/message/desktop.test.tsx +88 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +3 -1
- package/src/plugins/builtin/chat/message/profile-popover.test.tsx +1 -29
- package/src/plugins/builtin/chat/profile-popover.ts +53 -22
- package/src/plugins/builtin/chat/sidebar.test.tsx +81 -0
- package/src/plugins/builtin/chat/sidebar.tsx +63 -0
- package/src/plugins/builtin/chat/test-harness.tsx +29 -1
- package/src/plugins/builtin/cloud/auth-actions.tsx +14 -7
- package/src/plugins/builtin/cloud/auth-commands.ts +21 -51
- package/src/plugins/builtin/cloud/auth-dialog.tsx +296 -0
- package/src/{components/onboarding/account-step/model.test.ts → plugins/builtin/cloud/auth-model.test.ts} +1 -1
- package/src/{components/onboarding/account-step/model.ts → plugins/builtin/cloud/auth-model.ts} +37 -3
- package/src/plugins/builtin/cloud/browser.tsx +9 -0
- package/src/plugins/builtin/cloud/buildout-module.tsx +23 -0
- package/src/plugins/builtin/cloud/device-signin.ts +2 -8
- package/src/plugins/builtin/cloud/index.tsx +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +14 -24
- package/src/plugins/builtin/cloud/upgrade-command.ts +13 -6
- package/src/plugins/builtin/cloud-tweets/pane-share.test.ts +56 -0
- package/src/plugins/builtin/cloud-tweets/pane.tsx +20 -5
- package/src/plugins/builtin/cloud-tweets/registration.ts +50 -6
- package/src/plugins/builtin/composite-plugins.ts +3 -1
- package/src/plugins/builtin/congress-trades/detail.tsx +43 -1
- package/src/plugins/builtin/congress-trades/model.test.ts +85 -0
- package/src/plugins/builtin/congress-trades/model.ts +69 -1
- package/src/plugins/builtin/congress-trades/pane.tsx +51 -2
- package/src/plugins/builtin/connections/index.ts +3 -0
- package/src/plugins/builtin/dividend-yield/client.ts +18 -0
- package/src/plugins/builtin/dividend-yield/pane.tsx +7 -6
- package/src/plugins/builtin/earnings/index.tsx +5 -3
- package/src/plugins/builtin/earnings/model.test.ts +19 -1
- package/src/plugins/builtin/earnings/model.ts +17 -15
- package/src/plugins/builtin/econ/calendar-model.ts +0 -5
- package/src/plugins/builtin/econ/index.test.tsx +6 -1
- package/src/plugins/builtin/fear-greed/charts.tsx +12 -8
- package/src/plugins/builtin/help/index.tsx +2 -2
- package/src/plugins/builtin/holders/thirteenf-match.ts +1 -2
- package/src/plugins/builtin/insider/index.tsx +27 -11
- package/src/plugins/builtin/kelly-sizer/index.tsx +4 -1
- package/src/plugins/builtin/layout-manager/index.tsx +17 -4
- package/src/plugins/builtin/news/index.tsx +11 -6
- package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +2 -0
- package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +2 -0
- package/src/plugins/builtin/news/wire/breaking/pane.tsx +4 -1
- package/src/plugins/builtin/news/wire/feed-config.test.ts +15 -1
- package/src/plugins/builtin/news/wire/feed-config.ts +25 -5
- package/src/plugins/builtin/news/wire/index.tsx +3 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +7 -2
- package/src/plugins/builtin/news/wire/news/footer.ts +34 -1
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +4 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +8 -2
- package/src/plugins/builtin/news/wire/rss/source.test.ts +12 -0
- package/src/plugins/builtin/news/wire/rss/source.ts +1 -1
- package/src/plugins/builtin/notes/files.ts +80 -0
- package/src/plugins/builtin/notes/index.tsx +3 -0
- package/src/plugins/builtin/notes/sync.test.ts +49 -0
- package/src/plugins/builtin/notes/sync.ts +98 -0
- package/src/plugins/builtin/options/index.tsx +1 -0
- package/src/plugins/builtin/options/view.test.tsx +25 -3
- package/src/plugins/builtin/options/view.tsx +1 -0
- package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +22 -0
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +12 -7
- package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
- package/src/plugins/builtin/portfolio-list/column-values.ts +15 -4
- package/src/plugins/builtin/portfolio-list/header.tsx +13 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +8 -0
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +31 -1
- package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +1 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +10 -7
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +6 -1
- package/src/plugins/builtin/portfolio-list/position-metrics.ts +25 -6
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +35 -21
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +20 -6
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -12
- package/src/plugins/builtin/research/equity-diagnostic-pane.test.tsx +329 -0
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +582 -0
- package/src/plugins/builtin/research/index.test.ts +14 -0
- package/src/plugins/builtin/research/index.tsx +29 -0
- package/src/plugins/builtin/sec/index.tsx +24 -9
- package/src/plugins/builtin/sectors/index.tsx +14 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +18 -11
- package/src/plugins/builtin/shared/public-pane.test.ts +12 -0
- package/src/plugins/builtin/shared/public-pane.ts +9 -0
- package/src/plugins/builtin/shared/public-share.ts +23 -0
- package/src/plugins/builtin/shared/ticker-source.ts +32 -0
- package/src/plugins/builtin/shared/ticker-surface.test.ts +27 -0
- package/src/plugins/builtin/shared/ticker-surface.ts +17 -0
- package/src/plugins/builtin/substack/api/cache.ts +7 -3
- package/src/plugins/builtin/substack/api/loaders.ts +36 -12
- package/src/plugins/builtin/substack/api/types.ts +2 -0
- package/src/plugins/builtin/substack/pane-state.ts +1 -1
- package/src/plugins/builtin/substack/pane.tsx +55 -12
- package/src/plugins/builtin/thirteenf/api.test.ts +1 -1
- package/src/plugins/builtin/thirteenf/api.ts +40 -18
- package/src/plugins/builtin/thirteenf/data.ts +15 -7
- package/src/plugins/builtin/thirteenf/pane.tsx +45 -10
- package/src/plugins/builtin/ticker-detail/index.tsx +10 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +12 -4
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +8 -2
- package/src/plugins/builtin/ticker-detail/settings.ts +12 -0
- package/src/plugins/builtin/treasury-auctions/index.tsx +2 -0
- package/src/plugins/builtin/world-indices/index.tsx +2 -0
- package/src/plugins/catalog-browser.test.ts +78 -0
- package/src/plugins/catalog-browser.ts +136 -0
- package/src/plugins/gridlock-notification.ts +2 -1
- package/src/plugins/ibkr/flex/index.test.ts +19 -0
- package/src/plugins/ibkr/flex/index.ts +1 -1
- package/src/plugins/pane-manager/gridlock.ts +87 -3
- package/src/plugins/pane-manager.test.ts +30 -0
- package/src/plugins/pane-manager.ts +6 -1
- package/src/plugins/prediction-markets/chart.tsx +2 -2
- package/src/plugins/prediction-markets/controller/catalog.ts +77 -2
- package/src/plugins/prediction-markets/controller/data.ts +7 -1
- package/src/plugins/prediction-markets/controller/index.ts +9 -2
- package/src/plugins/prediction-markets/controller/keyboard.ts +10 -14
- package/src/plugins/prediction-markets/detail/overview.tsx +21 -0
- package/src/plugins/prediction-markets/detail/pane.tsx +43 -34
- package/src/plugins/prediction-markets/detail/trades.tsx +34 -9
- package/src/plugins/prediction-markets/detail.test.tsx +29 -2
- package/src/plugins/prediction-markets/index.tsx +8 -4
- package/src/plugins/prediction-markets/navigation.ts +0 -1
- package/src/plugins/prediction-markets/pane.test.tsx +77 -0
- package/src/plugins/prediction-markets/pane.tsx +12 -0
- package/src/plugins/prediction-markets/plugin.test.tsx +36 -0
- package/src/plugins/prediction-markets/services/kalshi/adapter.ts +49 -9
- package/src/plugins/prediction-markets/services/polymarket/adapter.ts +31 -0
- package/src/plugins/prediction-markets/test-helpers.tsx +9 -0
- package/src/plugins/registry/index.ts +2 -0
- package/src/plugins/ticker-navigation.test.ts +24 -0
- package/src/plugins/ticker-navigation.ts +40 -0
- package/src/remote/schema.ts +1 -1
- package/src/renderers/browser/app-services.ts +61 -0
- package/src/renderers/browser/cloud-transport.test.ts +68 -0
- package/src/renderers/browser/cloud-transport.ts +48 -0
- package/src/renderers/browser/config-host.ts +58 -0
- package/src/renderers/browser/deeplink-bridge.test.ts +41 -0
- package/src/renderers/browser/deeplink-bridge.ts +22 -0
- package/src/renderers/browser/error-boundary.tsx +23 -0
- package/src/renderers/browser/main.tsx +64 -0
- package/src/renderers/browser/persistence.ts +74 -0
- package/src/renderers/browser/storage.test.ts +66 -0
- package/src/renderers/browser/storage.ts +50 -0
- package/src/renderers/browser/ticker-repository.ts +59 -0
- package/src/renderers/browser/ui-host.tsx +40 -0
- package/src/renderers/cloudflare/worker.test.ts +136 -0
- package/src/renderers/cloudflare/worker.ts +116 -0
- package/src/renderers/electrobun/bun/application-menu/index.ts +3 -2
- package/src/renderers/electrobun/bun/desktop/initialization.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/plugin-state.ts +7 -1
- package/src/renderers/electrobun/view/app-services.ts +14 -3
- package/src/renderers/electrobun/view/build-assets.ts +12 -10
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
- package/src/renderers/electrobun/view/data-table/index.tsx +10 -1
- package/src/renderers/electrobun/view/desktop/controls.tsx +2 -0
- package/src/renderers/electrobun/view/dom-ui-host.tsx +143 -0
- package/src/renderers/electrobun/view/host/box.test.tsx +33 -0
- package/src/renderers/electrobun/view/host/box.tsx +11 -6
- package/src/renderers/electrobun/view/host/chart-surface.tsx +7 -1
- package/src/renderers/electrobun/view/host/input.tsx +11 -7
- package/src/renderers/electrobun/view/host/native.ts +0 -5
- package/src/renderers/electrobun/view/main.tsx +2 -0
- package/src/renderers/electrobun/view/remote/persistence.ts +5 -0
- package/src/renderers/electrobun/view/styles.css +107 -7
- package/src/renderers/electrobun/view/toast-host.test.tsx +78 -0
- package/src/renderers/electrobun/view/toast-host.tsx +69 -43
- package/src/renderers/electrobun/view/ui-host.tsx +6 -137
- package/src/renderers/opentui/start.tsx +2 -0
- package/src/renderers/share/layout-view.test.tsx +58 -0
- package/src/renderers/share/layout-view.tsx +133 -0
- package/src/renderers/share/main.tsx +102 -0
- package/src/renderers/share/styles.css +54 -0
- package/src/renderers/share/view.test.tsx +100 -0
- package/src/renderers/share/view.tsx +150 -0
- package/src/shares/api.test.ts +105 -0
- package/src/shares/api.ts +127 -0
- package/src/shares/chart-snapshot.test.ts +34 -0
- package/src/shares/chart-snapshot.ts +28 -0
- package/src/shares/location.test.ts +19 -0
- package/src/shares/location.ts +11 -0
- package/src/shares/pane.test.ts +110 -0
- package/src/shares/pane.ts +54 -0
- package/src/shares/payload.ts +177 -0
- package/src/sources/gloomberb-cloud/index.test.ts +61 -1
- package/src/sources/gloomberb-cloud/index.ts +86 -14
- package/src/sources/gloomberb-cloud/normalizers.test.ts +34 -0
- package/src/sources/gloomberb-cloud/normalizers.ts +5 -5
- package/src/sources/provider-router/history.test.ts +2 -2
- package/src/sources/provider-router/news.test.ts +34 -0
- package/src/sources/provider-router/news.ts +15 -1
- package/src/sources/sec-edgar.test.ts +59 -0
- package/src/sources/sec-edgar.ts +63 -23
- package/src/sources/yahoo-finance/mappers.ts +3 -0
- package/src/sources/yahoo-finance/snapshots.ts +10 -2
- package/src/sources/yahoo-finance/symbols.test.ts +10 -0
- package/src/sources/yahoo-finance/symbols.ts +13 -8
- package/src/sources/yahoo-finance.test.ts +1 -1
- package/src/sources/yahoo-finance.ts +6 -2
- package/src/state/app/bootstrap.ts +6 -7
- package/src/state/app/context/index.test.ts +41 -1
- package/src/state/app/context/index.test.tsx +34 -16
- package/src/state/selectors-ui.ts +0 -8
- package/src/sync/baseline.ts +32 -0
- package/src/sync/controller.test.ts +124 -1
- package/src/sync/controller.ts +54 -12
- package/src/sync/core-contributors.test.ts +44 -0
- package/src/sync/core-contributors.ts +59 -10
- package/src/sync/react.ts +22 -2
- package/src/sync/types.ts +12 -0
- package/src/theme/font-scale.ts +0 -11
- package/src/tickers/search/index.test.ts +27 -0
- package/src/tickers/search/upsert.ts +10 -0
- package/src/types/capability-route-source.ts +2 -1
- package/src/types/config.ts +35 -5
- package/src/types/desktop-menu.ts +1 -0
- package/src/types/plugin.ts +36 -0
- package/src/ui/context-menu.tsx +1 -1
- package/src/ui/host.tsx +4 -0
- package/src/ui/toast.tsx +2 -0
- package/src/utils/back-navigation.ts +0 -4
- package/src/utils/price-history.ts +11 -10
- package/src/utils/sec.test.ts +6 -0
- package/src/utils/sec.ts +1 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
interface StaticAssetsBinding {
|
|
2
|
+
fetch(request: Request): Promise<Response>;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface WorkerEnv {
|
|
6
|
+
ASSETS: StaticAssetsBinding;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const SHARE_PATH = /^\/s\/[a-f0-9]{32}\/?$/;
|
|
10
|
+
const LAYOUT_SHARE_PATH = /^\/l\/[a-f0-9]{32}\/?$/;
|
|
11
|
+
const API_PATH = /^\/api(?:\/|$)/;
|
|
12
|
+
const APPLE_APP_SITE_ASSOCIATION_PATH = "/.well-known/apple-app-site-association";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Lets the iOS app claim `https://term.gloom.sh/s/...` share links.
|
|
16
|
+
*
|
|
17
|
+
* Apple fetches this over HTTPS with its own client, so it has to be a direct
|
|
18
|
+
* 200 with JSON content type: a redirect, an HTML error page, or a 404 all
|
|
19
|
+
* silently disable universal links with no visible failure on device.
|
|
20
|
+
*
|
|
21
|
+
* The path list mirrors `SHARE_PATH` above. Only pane/content shares are
|
|
22
|
+
* claimed. Layout permalinks stay in the browser so their page can preview the
|
|
23
|
+
* workspace and hand it to the hosted terminal as an editable copy.
|
|
24
|
+
*
|
|
25
|
+
* `3XQML3UV65` is the Apple team id that prefixes the app id.
|
|
26
|
+
*/
|
|
27
|
+
const APPLE_APP_SITE_ASSOCIATION = {
|
|
28
|
+
applinks: {
|
|
29
|
+
details: [
|
|
30
|
+
{
|
|
31
|
+
appIDs: ["3XQML3UV65.sh.gloom.companion"],
|
|
32
|
+
components: [{ "/": "/s/*", comment: "Public portfolio shares" }],
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
} as const;
|
|
37
|
+
const API_ORIGIN = "https://api.gloom.sh";
|
|
38
|
+
const API_METHODS = new Set(["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]);
|
|
39
|
+
type ApiFetch = (request: Request) => Promise<Response>;
|
|
40
|
+
|
|
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",
|
|
43
|
+
"cross-origin-opener-policy": "same-origin",
|
|
44
|
+
"cross-origin-resource-policy": "same-origin",
|
|
45
|
+
"permissions-policy": "camera=(), geolocation=(), microphone=(), payment=(), usb=()",
|
|
46
|
+
"referrer-policy": "strict-origin-when-cross-origin",
|
|
47
|
+
"strict-transport-security": "max-age=31536000; includeSubDomains",
|
|
48
|
+
"x-content-type-options": "nosniff",
|
|
49
|
+
"x-frame-options": "DENY",
|
|
50
|
+
} as const;
|
|
51
|
+
|
|
52
|
+
export function withSecurityHeaders(response: Response, options: { share?: boolean } = {}): Response {
|
|
53
|
+
const headers = new Headers(response.headers);
|
|
54
|
+
for (const [name, value] of Object.entries(SECURITY_HEADERS)) headers.set(name, value);
|
|
55
|
+
if (options.share) headers.set("x-robots-tag", "noindex, nofollow, noarchive");
|
|
56
|
+
if ((headers.get("content-type") ?? "").includes("text/html")) {
|
|
57
|
+
headers.set("cache-control", "no-store");
|
|
58
|
+
}
|
|
59
|
+
return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function proxyApi(request: Request, fetchApi: ApiFetch): Promise<Response> {
|
|
63
|
+
const url = new URL(request.url);
|
|
64
|
+
if (!API_METHODS.has(request.method)) {
|
|
65
|
+
return Response.json({ error: "Method not allowed" }, {
|
|
66
|
+
status: 405,
|
|
67
|
+
headers: { Allow: [...API_METHODS].join(", ") },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const origin = request.headers.get("origin");
|
|
71
|
+
if (origin && origin !== url.origin) {
|
|
72
|
+
return Response.json({ error: "Origin not allowed" }, { status: 403 });
|
|
73
|
+
}
|
|
74
|
+
const upstreamUrl = new URL(`${url.pathname.slice(4) || "/"}${url.search}`, API_ORIGIN);
|
|
75
|
+
return fetchApi(new Request(upstreamUrl, request));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function handleRequest(request: Request, env: WorkerEnv, fetchApi: ApiFetch = fetch): Promise<Response> {
|
|
79
|
+
const url = new URL(request.url);
|
|
80
|
+
if (API_PATH.test(url.pathname)) return proxyApi(request, fetchApi);
|
|
81
|
+
|
|
82
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
83
|
+
return withSecurityHeaders(Response.json({ error: "Method not allowed" }, {
|
|
84
|
+
status: 405,
|
|
85
|
+
headers: { Allow: "GET, HEAD" },
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (url.pathname === "/health") {
|
|
90
|
+
return withSecurityHeaders(Response.json({ status: "ok" }));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Served before the asset handler so it cannot be shadowed by a static file
|
|
94
|
+
// or the SPA fallback, either of which would hand Apple HTML and quietly
|
|
95
|
+
// break universal links.
|
|
96
|
+
if (url.pathname === APPLE_APP_SITE_ASSOCIATION_PATH) {
|
|
97
|
+
return withSecurityHeaders(
|
|
98
|
+
Response.json(APPLE_APP_SITE_ASSOCIATION, {
|
|
99
|
+
headers: { "cache-control": "public, max-age=3600" },
|
|
100
|
+
}),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const socialShare = SHARE_PATH.test(url.pathname) || LAYOUT_SHARE_PATH.test(url.pathname);
|
|
104
|
+
if (socialShare) {
|
|
105
|
+
const assetUrl = new URL("/share.html", url.origin);
|
|
106
|
+
const assetRequest = new Request(assetUrl, { method: request.method, headers: request.headers });
|
|
107
|
+
return withSecurityHeaders(await env.ASSETS.fetch(assetRequest), { share: true });
|
|
108
|
+
}
|
|
109
|
+
return withSecurityHeaders(await env.ASSETS.fetch(request));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export default {
|
|
113
|
+
fetch(request: Request, env: WorkerEnv): Promise<Response> {
|
|
114
|
+
return handleRequest(request, env);
|
|
115
|
+
},
|
|
116
|
+
};
|
|
@@ -88,11 +88,12 @@ function buildApplicationMenu(): ApplicationMenuItemConfig[] {
|
|
|
88
88
|
{
|
|
89
89
|
label: "Layout",
|
|
90
90
|
submenu: [
|
|
91
|
-
|
|
91
|
+
commandItem("Layouts...", { type: "open-layout-gallery" }, { accelerator: "CmdOrCtrl+Shift+L" }),
|
|
92
|
+
openCommandBar("Layout Actions...", "LMA "),
|
|
92
93
|
{ type: "divider" },
|
|
93
94
|
commandItem("Undo Layout Change", { type: "layout-undo" }),
|
|
94
95
|
commandItem("Redo Layout Change", { type: "layout-redo" }),
|
|
95
|
-
commandItem("
|
|
96
|
+
commandItem("Tidy Windows", { type: "layout-gridlock" }, { accelerator: "CmdOrCtrl+Shift+G" }),
|
|
96
97
|
{ type: "divider" },
|
|
97
98
|
commandItem("New Layout...", { type: "open-plugin-workflow", commandId: "new-layout" }),
|
|
98
99
|
commandItem("Rename Current Layout...", { type: "open-plugin-workflow", commandId: "rename-layout" }),
|
|
@@ -142,6 +142,7 @@ export async function initializeDesktopBackend<TRpc>(
|
|
|
142
142
|
plugins: getDesktopBackendPlugins(),
|
|
143
143
|
});
|
|
144
144
|
options.setServices(services);
|
|
145
|
+
await services.ready;
|
|
145
146
|
options.syncConfigAccessors();
|
|
146
147
|
options.registerCoreCapabilities();
|
|
147
148
|
options.setDesktopWorkspace(createDesktopWorkspace(config, options.getSessionSnapshot()));
|
|
@@ -76,9 +76,15 @@ export function handleDesktopPluginStateRequest(
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
function pluginStateIds(registry: PluginRegistry): string[] {
|
|
80
|
+
const store = registry.persistence.pluginState as { pluginIds?: () => string[] };
|
|
81
|
+
const storedIds = typeof store.pluginIds === "function" ? store.pluginIds() : [];
|
|
82
|
+
return [...new Set(["gloomberb-cloud", ...registry.allPlugins.keys(), ...storedIds])];
|
|
83
|
+
}
|
|
84
|
+
|
|
79
85
|
export function loadDesktopPluginState(registry: PluginRegistry): Record<string, Record<string, unknown>> {
|
|
80
86
|
const state: Record<string, Record<string, unknown>> = {};
|
|
81
|
-
for (const pluginId of registry
|
|
87
|
+
for (const pluginId of pluginStateIds(registry)) {
|
|
82
88
|
const keys = registry.persistence.pluginState.keys(pluginId);
|
|
83
89
|
if (keys.length === 0) continue;
|
|
84
90
|
state[pluginId] = {};
|
|
@@ -7,17 +7,18 @@ import type { AppRuntimeServices, AppServicesFactoryOptions } from "../../../cor
|
|
|
7
7
|
import { newsProvider } from "../../../capabilities";
|
|
8
8
|
import { debugLog } from "../../../utils/debug-log";
|
|
9
9
|
import { measurePerf, measurePerfAsync } from "../../../utils/perf-marks";
|
|
10
|
-
import { getRendererBuiltinPlugins } from "../../../plugins/catalog-ui";
|
|
11
10
|
import { createRemoteAssetDataClient } from "./remote/asset-data-client";
|
|
12
11
|
import { RemotePersistence } from "./remote/persistence";
|
|
13
12
|
import { RemoteTickerRepository } from "./remote/ticker-repository";
|
|
14
13
|
import { connectBackendConnectionHealth } from "./remote/connection-health-backend";
|
|
15
14
|
import { backendRequest, getElectrobunBackendInitSnapshot } from "./backend-rpc";
|
|
16
15
|
import { createCapabilityInvoker } from "./remote/capability-invoker";
|
|
16
|
+
import { apiClient } from "../../../api-client";
|
|
17
|
+
import { cloudNewsParams, mapCloudNewsArticle } from "../../../sources/gloomberb-cloud/news";
|
|
17
18
|
|
|
18
19
|
const servicesLog = debugLog.createLogger("services");
|
|
19
20
|
|
|
20
|
-
export function createElectrobunAppServices({ config }: AppServicesFactoryOptions): AppRuntimeServices {
|
|
21
|
+
export function createElectrobunAppServices({ config, plugins }: AppServicesFactoryOptions): AppRuntimeServices {
|
|
21
22
|
servicesLog.info("create desktop web services start", {
|
|
22
23
|
brokerInstanceCount: config.brokerInstances.length,
|
|
23
24
|
});
|
|
@@ -54,10 +55,20 @@ export function createElectrobunAppServices({ config }: AppServicesFactoryOption
|
|
|
54
55
|
priority: 0,
|
|
55
56
|
provider: {
|
|
56
57
|
fetchNews: (query) => dataProvider.getNews(query),
|
|
58
|
+
fetchNewsPage: async (query) => {
|
|
59
|
+
try {
|
|
60
|
+
const response = await apiClient.getCloudNews(cloudNewsParams(query));
|
|
61
|
+
return {
|
|
62
|
+
articles: response.items.map((item) => mapCloudNewsArticle(item, query.ticker)),
|
|
63
|
+
nextCursor: response.nextCursor ?? null,
|
|
64
|
+
};
|
|
65
|
+
} catch {
|
|
66
|
+
return { articles: await dataProvider.getNews(query), nextCursor: null };
|
|
67
|
+
}
|
|
68
|
+
},
|
|
57
69
|
},
|
|
58
70
|
}));
|
|
59
71
|
|
|
60
|
-
const plugins = getRendererBuiltinPlugins();
|
|
61
72
|
const pluginReadyPromises: Promise<void>[] = [];
|
|
62
73
|
for (const plugin of plugins) {
|
|
63
74
|
pluginReadyPromises.push(measurePerfAsync("startup.services.register-plugin", () => (
|
|
@@ -2,7 +2,7 @@ import { readFile, writeFile } from "fs/promises";
|
|
|
2
2
|
import { join, relative } from "path";
|
|
3
3
|
import { TITLEBAR_OVERLAY_HEIGHT_PX } from "../../../components/layout/titlebar-overlay";
|
|
4
4
|
|
|
5
|
-
type AliasRule = readonly [string, string] | readonly [string, string, string];
|
|
5
|
+
export type AliasRule = readonly [string, string] | readonly [string, string, string];
|
|
6
6
|
type PageOptions = {
|
|
7
7
|
entrypoint: string;
|
|
8
8
|
outdir: string;
|
|
@@ -61,15 +61,7 @@ async function buildElectrobunViewBundle({
|
|
|
61
61
|
// already reads from the environment is baked in at build time.
|
|
62
62
|
__GLOOMBERB_API_URL__: JSON.stringify(process.env.GLOOMBERB_API_URL ?? ""),
|
|
63
63
|
},
|
|
64
|
-
plugins: [
|
|
65
|
-
{
|
|
66
|
-
name: pluginName,
|
|
67
|
-
setup(build) {
|
|
68
|
-
const aliasRules = [...extraAliasRules, ...COMMON_ALIAS_RULES];
|
|
69
|
-
build.onResolve({ filter: /.*/ }, (args) => resolveAlias(args, aliasRules));
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
],
|
|
64
|
+
plugins: [electrobunViewAliasPlugin(pluginName, extraAliasRules)],
|
|
73
65
|
});
|
|
74
66
|
|
|
75
67
|
if (!result.success) {
|
|
@@ -113,6 +105,16 @@ ${bootstrapScript}
|
|
|
113
105
|
`;
|
|
114
106
|
}
|
|
115
107
|
|
|
108
|
+
export function electrobunViewAliasPlugin(name: string, extraAliasRules: AliasRule[] = []) {
|
|
109
|
+
return {
|
|
110
|
+
name,
|
|
111
|
+
setup(build: { onResolve(options: { filter: RegExp }, callback: (args: { path: string; importer?: string }) => unknown): void }) {
|
|
112
|
+
const aliasRules = [...extraAliasRules, ...COMMON_ALIAS_RULES];
|
|
113
|
+
build.onResolve({ filter: /.*/ }, (args) => resolveAlias(args, aliasRules));
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
116
118
|
function resolveAlias(args: { path: string; importer?: string }, aliasRules: AliasRule[]) {
|
|
117
119
|
const importer = args.importer?.replaceAll("\\", "/");
|
|
118
120
|
for (const rule of aliasRules) {
|
|
@@ -546,7 +546,7 @@ function ShotPane({ payload }: { payload: CliPaneShotPayload }) {
|
|
|
546
546
|
config: payload.config,
|
|
547
547
|
paneState: payload.paneState,
|
|
548
548
|
} as Pick<AppState, "config" | "paneState">;
|
|
549
|
-
const title = getPaneDisplayTitle(titleState, instance, pane);
|
|
549
|
+
const title = getPaneDisplayTitle(titleState, instance, pane, shotRegistry?.panes);
|
|
550
550
|
const width = payload.widthCells;
|
|
551
551
|
const height = payload.heightCells;
|
|
552
552
|
const bodyFrame = resolvePaneBodyFrame({
|
|
@@ -140,6 +140,7 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
140
140
|
}, [emitVisibleRange, onBodyScrollActivity]);
|
|
141
141
|
const scheduleBodyScrollActivity = useRafCallback(handleBodyScrollActivity);
|
|
142
142
|
const scheduleVisibleRangeMeasure = useRafCallback(emitVisibleRange);
|
|
143
|
+
const lastAppliedScrollRequestRef = useRef<string | null>(null);
|
|
143
144
|
|
|
144
145
|
const rowVirtualizer = useVirtualizer({
|
|
145
146
|
count: items.length,
|
|
@@ -203,7 +204,12 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
203
204
|
// then converted to pixels. Letting the virtualizer scroll by pixels landed
|
|
204
205
|
// mid-row and clipped the first and last visible rows into slivers.
|
|
205
206
|
useEffect(() => {
|
|
206
|
-
if (scrollToIndex == null || items.length === 0)
|
|
207
|
+
if (scrollToIndex == null || items.length === 0) {
|
|
208
|
+
lastAppliedScrollRequestRef.current = null;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const scrollRequestKey = `${scrollToIndex}:${scrollToIndexVersion}:${scrollToIndexAlign}`;
|
|
212
|
+
if (lastAppliedScrollRequestRef.current === scrollRequestKey) return;
|
|
207
213
|
const targetIndex = Math.max(0, Math.min(scrollToIndex, items.length - 1));
|
|
208
214
|
const element = bodyElementRef.current;
|
|
209
215
|
if (!element) return;
|
|
@@ -219,13 +225,16 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
219
225
|
}
|
|
220
226
|
if (nextTop !== currentTop) {
|
|
221
227
|
element.scrollTop = nextTop * WEB_CELL_HEIGHT;
|
|
228
|
+
scheduleBodyScrollActivity();
|
|
222
229
|
}
|
|
230
|
+
lastAppliedScrollRequestRef.current = scrollRequestKey;
|
|
223
231
|
scheduleVisibleRangeMeasure();
|
|
224
232
|
}, [
|
|
225
233
|
items.length,
|
|
226
234
|
scrollToIndex,
|
|
227
235
|
scrollToIndexAlign,
|
|
228
236
|
scrollToIndexVersion,
|
|
237
|
+
scheduleBodyScrollActivity,
|
|
229
238
|
scheduleVisibleRangeMeasure,
|
|
230
239
|
]);
|
|
231
240
|
|
|
@@ -216,6 +216,7 @@ export function WebTextField({
|
|
|
216
216
|
onBlur,
|
|
217
217
|
hint,
|
|
218
218
|
type = "text",
|
|
219
|
+
autoComplete,
|
|
219
220
|
variant = "default",
|
|
220
221
|
backgroundColor,
|
|
221
222
|
textColor,
|
|
@@ -273,6 +274,7 @@ export function WebTextField({
|
|
|
273
274
|
width="100%"
|
|
274
275
|
value={value}
|
|
275
276
|
type={type}
|
|
277
|
+
autoComplete={autoComplete}
|
|
276
278
|
placeholder={placeholder}
|
|
277
279
|
focused={focused}
|
|
278
280
|
textColor={resolvedTextColor}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/** @jsxImportSource react */
|
|
2
|
+
import { useEffect, useState, type CSSProperties, type ReactNode } from "react";
|
|
3
|
+
import type { UiHost } from "../../../ui/host";
|
|
4
|
+
import { WEB_CELL_HEIGHT, WEB_CELL_WIDTH } from "./input-host";
|
|
5
|
+
import { WebDataTable } from "./data-table";
|
|
6
|
+
import {
|
|
7
|
+
WebButton,
|
|
8
|
+
WebCheckbox,
|
|
9
|
+
WebDialogFrame,
|
|
10
|
+
WebListView,
|
|
11
|
+
WebMessageComposer,
|
|
12
|
+
WebPageStackView,
|
|
13
|
+
WebSegmentedControl,
|
|
14
|
+
WebTextField,
|
|
15
|
+
} from "./desktop/controls";
|
|
16
|
+
import { WebPopover } from "./desktop/popover";
|
|
17
|
+
import { WebBox } from "./host/box";
|
|
18
|
+
import { WebChartSurface } from "./host/chart-surface";
|
|
19
|
+
import { WebInput, WebTextarea } from "./host/input";
|
|
20
|
+
import { WebMediaSurface } from "./host/media-surface";
|
|
21
|
+
import { WebScrollBox } from "./host/scroll-box";
|
|
22
|
+
import { cleanDomProps, commonStyle } from "./host/style";
|
|
23
|
+
import { WebAsciiText, WebSpan, WebStrong, WebText, WebUnderline } from "./host/text";
|
|
24
|
+
import { WebTabs } from "./host/tabs";
|
|
25
|
+
|
|
26
|
+
function currentDesktopPlatform(): string {
|
|
27
|
+
const navigatorWithUserAgentData = navigator as Navigator & {
|
|
28
|
+
userAgentData?: { platform?: string };
|
|
29
|
+
};
|
|
30
|
+
return [
|
|
31
|
+
navigatorWithUserAgentData.userAgentData?.platform,
|
|
32
|
+
navigator.platform,
|
|
33
|
+
navigator.userAgent,
|
|
34
|
+
].filter((value): value is string => Boolean(value)).join(" ");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const DESKTOP_PLATFORM = currentDesktopPlatform();
|
|
38
|
+
const USES_WINDOWS_CONTROLS = !/(darwin|ipad|iphone|linux|mac)/i.test(DESKTOP_PLATFORM);
|
|
39
|
+
const NON_WINDOWS_DESKTOP_PLATFORMS = /^(darwin|linux|freebsd|openbsd|aix|sunos)$/i;
|
|
40
|
+
|
|
41
|
+
function usesWindowsWindowControls(desktopPlatform?: string): boolean {
|
|
42
|
+
const platform = desktopPlatform?.trim();
|
|
43
|
+
if (!platform) return USES_WINDOWS_CONTROLS;
|
|
44
|
+
if (/^win/i.test(platform)) return true;
|
|
45
|
+
if (NON_WINDOWS_DESKTOP_PLATFORMS.test(platform)) return false;
|
|
46
|
+
return USES_WINDOWS_CONTROLS;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function createDomUiHost(
|
|
50
|
+
desktopPlatform?: string,
|
|
51
|
+
options: {
|
|
52
|
+
nativeContextMenu?: boolean;
|
|
53
|
+
titleBarOverlay?: boolean;
|
|
54
|
+
nativePaneChrome?: boolean;
|
|
55
|
+
nativeWindowChrome?: boolean;
|
|
56
|
+
publicSharing?: boolean;
|
|
57
|
+
} = {},
|
|
58
|
+
): UiHost {
|
|
59
|
+
const usesWindowsControls = options.titleBarOverlay !== false
|
|
60
|
+
&& options.nativeWindowChrome !== false
|
|
61
|
+
&& usesWindowsWindowControls(desktopPlatform);
|
|
62
|
+
return {
|
|
63
|
+
kind: "desktop-web",
|
|
64
|
+
capabilities: {
|
|
65
|
+
nativePaneChrome: options.nativePaneChrome ?? true,
|
|
66
|
+
titleBarOverlay: options.titleBarOverlay ?? true,
|
|
67
|
+
nativeWindowChrome: options.nativeWindowChrome ?? true,
|
|
68
|
+
publicSharing: options.publicSharing ?? false,
|
|
69
|
+
precisePointer: true,
|
|
70
|
+
fractionalViewport: true,
|
|
71
|
+
cellWidthPx: WEB_CELL_WIDTH,
|
|
72
|
+
cellHeightPx: WEB_CELL_HEIGHT,
|
|
73
|
+
pixelRatio: Math.min(window.devicePixelRatio || 1, 2),
|
|
74
|
+
canvasCharts: true,
|
|
75
|
+
nativeContextMenu: options.nativeContextMenu ?? false,
|
|
76
|
+
windowControls: usesWindowsControls ? "windows" : undefined,
|
|
77
|
+
},
|
|
78
|
+
Box: WebBox,
|
|
79
|
+
Text: WebText,
|
|
80
|
+
Span: WebSpan,
|
|
81
|
+
Strong: WebStrong,
|
|
82
|
+
Underline: WebUnderline,
|
|
83
|
+
ScrollBox: WebScrollBox,
|
|
84
|
+
Input: WebInput,
|
|
85
|
+
Textarea: WebTextarea,
|
|
86
|
+
Button: WebButton,
|
|
87
|
+
Checkbox: WebCheckbox,
|
|
88
|
+
Popover: WebPopover,
|
|
89
|
+
TextField: WebTextField,
|
|
90
|
+
MessageComposer: WebMessageComposer,
|
|
91
|
+
ListView: WebListView,
|
|
92
|
+
SegmentedControl: WebSegmentedControl,
|
|
93
|
+
DialogFrame: WebDialogFrame,
|
|
94
|
+
PageStackView: WebPageStackView,
|
|
95
|
+
DataTable: WebDataTable,
|
|
96
|
+
Tabs: WebTabs,
|
|
97
|
+
ChartSurface: WebChartSurface,
|
|
98
|
+
ImageSurface: ({ children, src, alt = "", objectFit = "contain", ...props }) => {
|
|
99
|
+
const imageSrc = typeof src === "string" ? src.trim() : "";
|
|
100
|
+
const [failed, setFailed] = useState(false);
|
|
101
|
+
useEffect(() => setFailed(false), [imageSrc]);
|
|
102
|
+
const baseStyle = commonStyle(props);
|
|
103
|
+
return (
|
|
104
|
+
<div
|
|
105
|
+
{...cleanDomProps(props)}
|
|
106
|
+
style={{
|
|
107
|
+
...baseStyle,
|
|
108
|
+
overflow: baseStyle.overflow ?? "hidden",
|
|
109
|
+
...(props.style as CSSProperties | undefined),
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
{imageSrc && !failed ? (
|
|
113
|
+
<img
|
|
114
|
+
key={imageSrc}
|
|
115
|
+
src={imageSrc}
|
|
116
|
+
alt={alt}
|
|
117
|
+
draggable={false}
|
|
118
|
+
onError={() => setFailed(true)}
|
|
119
|
+
style={{ width: "100%", height: "100%", display: "block", objectFit }}
|
|
120
|
+
/>
|
|
121
|
+
) : children as ReactNode}
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
},
|
|
125
|
+
MediaSurface: WebMediaSurface,
|
|
126
|
+
SpinnerMark: ({ color, ...props }) => (
|
|
127
|
+
<span
|
|
128
|
+
{...cleanDomProps(props)}
|
|
129
|
+
aria-hidden="true"
|
|
130
|
+
style={{
|
|
131
|
+
color,
|
|
132
|
+
display: "inline-block",
|
|
133
|
+
width: "1ch",
|
|
134
|
+
animation: "gloom-spin 0.9s steps(8) infinite",
|
|
135
|
+
...(props.style as CSSProperties | undefined),
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
*
|
|
139
|
+
</span>
|
|
140
|
+
),
|
|
141
|
+
AsciiText: (props) => <WebAsciiText {...props} desktopPlatform={desktopPlatform} />,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -71,6 +71,39 @@ test("reports a drag as a drag, never as a move, and keeps its modifiers", async
|
|
|
71
71
|
expect(modifiers.every((entry) => entry.shift)).toBe(true);
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
+
test("chart surfaces consume browser pan and zoom gestures", async () => {
|
|
75
|
+
const directions: string[] = [];
|
|
76
|
+
const container = testWindow.document.createElement("div");
|
|
77
|
+
testWindow.document.body.appendChild(container);
|
|
78
|
+
const root = createRoot(container as unknown as HTMLElement);
|
|
79
|
+
await act(async () => {
|
|
80
|
+
root.render(
|
|
81
|
+
<WebChartSurface
|
|
82
|
+
width={40}
|
|
83
|
+
height={10}
|
|
84
|
+
onMouseScroll={(event: { scroll?: { direction?: string }; preventDefault: () => void }) => {
|
|
85
|
+
directions.push(event.scroll?.direction ?? "");
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
}}
|
|
88
|
+
/>,
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const surface = container.firstElementChild as unknown as HTMLElement;
|
|
93
|
+
const wheel = new testWindow.WheelEvent("wheel", {
|
|
94
|
+
bubbles: true,
|
|
95
|
+
cancelable: true,
|
|
96
|
+
deltaX: 24,
|
|
97
|
+
});
|
|
98
|
+
expect(surface.dispatchEvent(wheel as never)).toBe(false);
|
|
99
|
+
expect(wheel.defaultPrevented).toBe(true);
|
|
100
|
+
expect(directions).toEqual(["right"]);
|
|
101
|
+
expect(surface.style.touchAction).toBe("none");
|
|
102
|
+
expect(surface.style.overscrollBehavior).toBe("none");
|
|
103
|
+
|
|
104
|
+
await act(async () => root.unmount());
|
|
105
|
+
});
|
|
106
|
+
|
|
74
107
|
test("a tab bar occupies exactly the one row panes reserve for it", async () => {
|
|
75
108
|
const { WebTabs } = await import("./tabs");
|
|
76
109
|
const { WEB_CELL_HEIGHT } = await import("../input-host");
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
type MouseEvent,
|
|
9
9
|
type ReactNode,
|
|
10
10
|
type Ref,
|
|
11
|
-
type WheelEvent as ReactWheelEvent,
|
|
12
11
|
} from "react";
|
|
13
12
|
import {
|
|
14
13
|
type CellMouseEvent,
|
|
@@ -96,6 +95,17 @@ export const WebBox = forwardRef<HTMLDivElement, Record<string, unknown> & { chi
|
|
|
96
95
|
document.body.classList.remove("gloom-dragging");
|
|
97
96
|
}, []);
|
|
98
97
|
|
|
98
|
+
const handlesWheel = typeof props.onMouseScroll === "function";
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
const element = elementRef.current;
|
|
101
|
+
if (!element || !handlesWheel) return;
|
|
102
|
+
const handleWheel = (event: WheelEvent) => {
|
|
103
|
+
callMouseHandler(propsRef.current.onMouseScroll, event, "scroll");
|
|
104
|
+
};
|
|
105
|
+
element.addEventListener("wheel", handleWheel, { passive: false });
|
|
106
|
+
return () => element.removeEventListener("wheel", handleWheel);
|
|
107
|
+
}, [handlesWheel]);
|
|
108
|
+
|
|
99
109
|
const stopDocumentDrag = () => {
|
|
100
110
|
if (!draggingRef.current) return;
|
|
101
111
|
draggingRef.current = false;
|
|
@@ -136,10 +146,6 @@ export const WebBox = forwardRef<HTMLDivElement, Record<string, unknown> & { chi
|
|
|
136
146
|
callMouseHandler(propsRef.current.onMouseDownCapture, event, "down");
|
|
137
147
|
};
|
|
138
148
|
|
|
139
|
-
const handleWheel = (event: ReactWheelEvent<HTMLDivElement>) => {
|
|
140
|
-
callMouseHandler(propsRef.current.onMouseScroll, event, "scroll");
|
|
141
|
-
};
|
|
142
|
-
|
|
143
149
|
const handleMouseOver = (event: MouseEvent) => {
|
|
144
150
|
callMouseHandler(propsRef.current.onMouseOver, event, "over");
|
|
145
151
|
};
|
|
@@ -160,7 +166,6 @@ export const WebBox = forwardRef<HTMLDivElement, Record<string, unknown> & { chi
|
|
|
160
166
|
onMouseMove={(event) => scheduleFrameMouseHandler(event, "move")}
|
|
161
167
|
onMouseUp={(event) => callMouseHandler(propsRef.current.onMouseUp, event, "up")}
|
|
162
168
|
onMouseOut={handleMouseOut}
|
|
163
|
-
onWheel={typeof props.onMouseScroll === "function" ? handleWheel : undefined}
|
|
164
169
|
style={{
|
|
165
170
|
...commonStyle(props),
|
|
166
171
|
"--gloom-box-hover-bg": hoverBackgroundColor,
|
|
@@ -211,7 +211,13 @@ export const WebChartSurface = forwardRef<BoxRenderable, Record<string, unknown>
|
|
|
211
211
|
{...props}
|
|
212
212
|
ref={ref as Ref<HTMLDivElement>}
|
|
213
213
|
data-gloom-role={(props["data-gloom-role"] as string | undefined) ?? "chart-surface"}
|
|
214
|
-
style={{
|
|
214
|
+
style={{
|
|
215
|
+
position: "relative",
|
|
216
|
+
overflow: "hidden",
|
|
217
|
+
touchAction: "none",
|
|
218
|
+
overscrollBehavior: "none",
|
|
219
|
+
...(props.style as CSSProperties | undefined),
|
|
220
|
+
}}
|
|
215
221
|
>
|
|
216
222
|
{layers.length > 0
|
|
217
223
|
? layers.map((layer, index) => (
|
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
type KeyboardEvent,
|
|
11
11
|
type RefObject,
|
|
12
12
|
} from "react";
|
|
13
|
-
import
|
|
13
|
+
import { editableTextContextMenuItems } from "../../../../ui/context-menu";
|
|
14
|
+
import { useRendererHost, useUiCapabilities, type InputRenderable, type TextareaRenderable } from "../../../../ui/host";
|
|
14
15
|
import { WEB_CELL_HEIGHT, WEB_CELL_WIDTH } from "../input-host";
|
|
15
|
-
import { NATIVE_CONTEXT_MENU_SUPPORTED, showEditableTextContextMenu } from "./native";
|
|
16
16
|
import { cellHeight, cellWidth, cleanDomProps, commonStyle } from "./style";
|
|
17
17
|
|
|
18
18
|
function textInputStyle(props: Record<string, unknown>, multiline: boolean): CSSProperties {
|
|
@@ -205,6 +205,8 @@ function textareaMetrics(
|
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
export const WebInput = forwardRef<InputRenderable, Record<string, unknown>>(function WebInput(props, ref) {
|
|
208
|
+
const renderer = useRendererHost();
|
|
209
|
+
const { nativeContextMenu } = useUiCapabilities();
|
|
208
210
|
const elementRef = useRef<HTMLInputElement | null>(null);
|
|
209
211
|
const propsRef = useLatestRef(props);
|
|
210
212
|
const { value, valueRef, setValue } = useEditableValue(props);
|
|
@@ -271,7 +273,7 @@ export const WebInput = forwardRef<InputRenderable, Record<string, unknown>>(fun
|
|
|
271
273
|
value={value}
|
|
272
274
|
autoCorrect="off"
|
|
273
275
|
autoCapitalize="off"
|
|
274
|
-
autoComplete="off"
|
|
276
|
+
autoComplete={getStringProp(props, "autoComplete") ?? "off"}
|
|
275
277
|
spellCheck={false}
|
|
276
278
|
placeholder={getStringProp(props, "placeholder")}
|
|
277
279
|
onInput={(event) => handleValueChange(event.currentTarget.value)}
|
|
@@ -287,11 +289,11 @@ export const WebInput = forwardRef<InputRenderable, Record<string, unknown>>(fun
|
|
|
287
289
|
}}
|
|
288
290
|
onKeyDown={handleKeyDown}
|
|
289
291
|
onContextMenu={(event) => {
|
|
290
|
-
if (!
|
|
292
|
+
if (!nativeContextMenu || !renderer.showContextMenu) return;
|
|
291
293
|
elementRef.current?.focus();
|
|
292
294
|
event.preventDefault();
|
|
293
295
|
event.stopPropagation();
|
|
294
|
-
void
|
|
296
|
+
void renderer.showContextMenu(editableTextContextMenuItems());
|
|
295
297
|
}}
|
|
296
298
|
onSelect={() => {
|
|
297
299
|
setCursorOffset(elementRef.current?.selectionStart ?? valueRef.current.length);
|
|
@@ -303,6 +305,8 @@ export const WebInput = forwardRef<InputRenderable, Record<string, unknown>>(fun
|
|
|
303
305
|
});
|
|
304
306
|
|
|
305
307
|
export const WebTextarea = forwardRef<TextareaRenderable, Record<string, unknown>>(function WebTextarea(props, ref) {
|
|
308
|
+
const renderer = useRendererHost();
|
|
309
|
+
const { nativeContextMenu } = useUiCapabilities();
|
|
306
310
|
const elementRef = useRef<HTMLTextAreaElement | null>(null);
|
|
307
311
|
const propsRef = useLatestRef(props);
|
|
308
312
|
const { value, valueRef, setValue } = useEditableValue(props);
|
|
@@ -400,11 +404,11 @@ export const WebTextarea = forwardRef<TextareaRenderable, Record<string, unknown
|
|
|
400
404
|
}}
|
|
401
405
|
onKeyDown={handleKeyDown}
|
|
402
406
|
onContextMenu={(event) => {
|
|
403
|
-
if (!
|
|
407
|
+
if (!nativeContextMenu || !renderer.showContextMenu) return;
|
|
404
408
|
elementRef.current?.focus();
|
|
405
409
|
event.preventDefault();
|
|
406
410
|
event.stopPropagation();
|
|
407
|
-
void
|
|
411
|
+
void renderer.showContextMenu(editableTextContextMenuItems());
|
|
408
412
|
}}
|
|
409
413
|
onSelect={() => {
|
|
410
414
|
setCursorOffset(elementRef.current?.selectionStart ?? valueRef.current.length);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ContextMenuItem } from "../../../../types/context-menu";
|
|
2
|
-
import { editableTextContextMenuItems } from "../../../../ui/context-menu";
|
|
3
2
|
import { backendRequest, onContextMenuSelect } from "../backend-rpc";
|
|
4
3
|
import {
|
|
5
4
|
DesktopContextMenuActionScope,
|
|
@@ -42,7 +41,3 @@ export async function showDesktopContextMenu(items: ContextMenuItem[]): Promise<
|
|
|
42
41
|
return false;
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
|
-
|
|
46
|
-
export function showEditableTextContextMenu(): Promise<boolean> {
|
|
47
|
-
return showDesktopContextMenu(editableTextContextMenuItems());
|
|
48
|
-
}
|
|
@@ -30,6 +30,7 @@ import { createDesktopDeepLinkBridge } from "./desktop-deeplink-bridge";
|
|
|
30
30
|
import { createDesktopWindowBridge } from "./desktop/window/bridge";
|
|
31
31
|
import { prepareDetachedSnapshot } from "./desktop/window/snapshot";
|
|
32
32
|
import { createElectrobunAppServices } from "./app-services";
|
|
33
|
+
import { getRendererBuiltinPlugins } from "../../../plugins/catalog-ui";
|
|
33
34
|
|
|
34
35
|
const rootElement = document.getElementById("root");
|
|
35
36
|
if (!rootElement) {
|
|
@@ -112,6 +113,7 @@ async function boot() {
|
|
|
112
113
|
<App
|
|
113
114
|
config={config}
|
|
114
115
|
servicesFactory={createElectrobunAppServices}
|
|
116
|
+
plugins={getRendererBuiltinPlugins()}
|
|
115
117
|
desktopWindowBridge={desktopWindowBridge}
|
|
116
118
|
desktopApplicationMenuBridge={desktopApplicationMenuBridge}
|
|
117
119
|
desktopDeepLinkBridge={desktopDeepLinkBridge}
|
|
@@ -77,6 +77,11 @@ class RemotePluginStateStore {
|
|
|
77
77
|
if (!this.state.has(pluginId)) this.state.set(pluginId, new Map());
|
|
78
78
|
this.state.get(pluginId)!.set(key, value);
|
|
79
79
|
this.getScheduler(pluginId, key).schedule({ pluginId, key, value, schemaVersion });
|
|
80
|
+
// Closing the window tears down the RPC before a debounced save can land.
|
|
81
|
+
// Auth has to reach SQLite immediately or the next launch is signed out.
|
|
82
|
+
if (key === "session" || key === "resume:session") {
|
|
83
|
+
void this.flush();
|
|
84
|
+
}
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
delete(pluginId: string, key: string): void {
|