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
package/src/i18n/ja.ts
CHANGED
|
@@ -13,6 +13,7 @@ export const ja: Record<string, string> = {
|
|
|
13
13
|
"Cancel": "キャンセル",
|
|
14
14
|
"Close": "閉じる",
|
|
15
15
|
"Open": "開く",
|
|
16
|
+
"Linked to {source}": "{source} にリンク中",
|
|
16
17
|
"Working…": "処理中…",
|
|
17
18
|
"Searching…": "検索中…",
|
|
18
19
|
"Loading...": "読み込み中...",
|
|
@@ -221,12 +222,12 @@ export const ja: Record<string, string> = {
|
|
|
221
222
|
"Dock Pane": "ペインをドッキング",
|
|
222
223
|
"Settings": "設定",
|
|
223
224
|
"Copy Screenshot": "スクリーンショットをコピー",
|
|
225
|
+
"Share Pane": "ペインを共有",
|
|
224
226
|
"Pop Out": "別ウィンドウで表示",
|
|
225
227
|
"Move Window...": "ウィンドウを移動...",
|
|
226
228
|
"Resize Window...": "ウィンドウのサイズを変更...",
|
|
227
229
|
"Move Window": "ウィンドウを移動",
|
|
228
230
|
"Resize Window": "ウィンドウのサイズを変更",
|
|
229
|
-
"Gridlock All Windows": "すべてのウィンドウをグリッド配置",
|
|
230
231
|
"Change the current saved layout name": "現在保存されているレイアウト名を変更します",
|
|
231
232
|
"Create a copy of the current layout": "現在のレイアウトのコピーを作成します",
|
|
232
233
|
"Create a fresh saved layout": "新しく保存されたレイアウトを作成する",
|
|
@@ -518,7 +519,6 @@ export const ja: Record<string, string> = {
|
|
|
518
519
|
"Pop the focused pane out to a desktop window.": "フォーカスされたペインをデスクトップ ウィンドウにポップアウトします。",
|
|
519
520
|
"Copy a screenshot of the focused pane.": "フォーカスされたペインのスクリーンショットをコピーします。",
|
|
520
521
|
"Open layout actions.": "レイアウト アクションを開きます。",
|
|
521
|
-
"Gridlock all windows.": "すべてのウィンドウをグリッドロックします。",
|
|
522
522
|
"Enter window move mode.": "ウィンドウ移動モードに入ります。",
|
|
523
523
|
"Enter window resize mode.": "ウィンドウサイズ変更モードに入ります。",
|
|
524
524
|
"Cancel an active pane drag.": "アクティブなペインのドラッグをキャンセルします。",
|
|
@@ -912,8 +912,7 @@ export const ja: Record<string, string> = {
|
|
|
912
912
|
"v{version} available — run {command}": "v{version} が利用可能 — {command} を実行",
|
|
913
913
|
"v{version} available — starting download...": "v{version} が利用可能 — ダウンロードを開始しています...",
|
|
914
914
|
"command bar": "コマンド バー",
|
|
915
|
-
"
|
|
916
|
-
"Gridlock All": "すべてをグリッド配置",
|
|
915
|
+
"Tidy Windows": "ウィンドウを整理",
|
|
917
916
|
"Dismiss": "閉じる",
|
|
918
917
|
"Minimize": "最小化",
|
|
919
918
|
"Maximize": "最大化",
|
|
@@ -1064,6 +1063,16 @@ export const ja: Record<string, string> = {
|
|
|
1064
1063
|
"Signed in as {email}": "{email} でログイン中",
|
|
1065
1064
|
"Signed in to Gloom Cloud": "Gloom Cloud にログイン中",
|
|
1066
1065
|
"Signing you in...": "ログイン中...",
|
|
1066
|
+
"Forgot password?": "パスワードをお忘れですか?",
|
|
1067
|
+
"Sending reset link...": "再設定リンクを送信中...",
|
|
1068
|
+
"Reset link sent to {email}. Check your inbox.": "{email} に再設定リンクを送信しました。受信トレイをご確認ください。",
|
|
1069
|
+
"Could not send the reset email.": "再設定メールを送信できませんでした。",
|
|
1070
|
+
"Create Account": "アカウントを作成",
|
|
1071
|
+
"Log in instead": "代わりにログイン",
|
|
1072
|
+
"Sign up instead": "代わりに新規登録",
|
|
1073
|
+
"We'll email you a verification link.": "確認リンクをメールでお送りします。",
|
|
1074
|
+
"show": "表示",
|
|
1075
|
+
"hide": "非表示",
|
|
1067
1076
|
"Signs this terminal in to your account.": "この端末をアカウントにログインさせます。",
|
|
1068
1077
|
"Skip for now": "今はスキップ",
|
|
1069
1078
|
"Scan QR with the mobile app": "モバイルアプリで QR をスキャン",
|
|
@@ -1095,7 +1104,6 @@ export const ja: Record<string, string> = {
|
|
|
1095
1104
|
"signing in...": "サインイン中...",
|
|
1096
1105
|
|
|
1097
1106
|
// ── Onboarding setup ────────────────────────────────────────
|
|
1098
|
-
"$29/mo for real-time market data": "リアルタイム市場データを月額$29で",
|
|
1099
1107
|
"Limited founding offer. Start a free 7-day trial.": "創設記念の期間限定オファー。7日間の無料トライアルを開始できます。",
|
|
1100
1108
|
"Start 7-day free trial": "7日間の無料トライアルを開始",
|
|
1101
1109
|
"Adds quotes, financials, options, research, news, chat and AI commands.": "株価、財務情報、オプション、リサーチ、ニュース、チャット、AIコマンドを追加します。",
|
package/src/i18n/ko.ts
CHANGED
|
@@ -11,6 +11,7 @@ export const ko: Record<string, string> = {
|
|
|
11
11
|
"Cancel": "취소",
|
|
12
12
|
"Close": "닫기",
|
|
13
13
|
"Open": "열기",
|
|
14
|
+
"Linked to {source}": "{source}에 연결됨",
|
|
14
15
|
"Working…": "처리 중…",
|
|
15
16
|
"Searching…": "검색 중…",
|
|
16
17
|
"Loading...": "불러오는 중...",
|
|
@@ -219,12 +220,12 @@ export const ko: Record<string, string> = {
|
|
|
219
220
|
"Dock Pane": "패널 도킹",
|
|
220
221
|
"Settings": "설정",
|
|
221
222
|
"Copy Screenshot": "스크린샷 복사",
|
|
223
|
+
"Share Pane": "창 공유",
|
|
222
224
|
"Pop Out": "별도 창으로 열기",
|
|
223
225
|
"Move Window...": "창 이동...",
|
|
224
226
|
"Resize Window...": "창 크기 조정...",
|
|
225
227
|
"Move Window": "창 이동",
|
|
226
228
|
"Resize Window": "창 크기 조정",
|
|
227
|
-
"Gridlock All Windows": "모든 창 격자 정렬",
|
|
228
229
|
"Change the current saved layout name": "현재 저장된 레이아웃 이름 변경",
|
|
229
230
|
"Create a copy of the current layout": "현재 레이아웃의 복사본 만들기",
|
|
230
231
|
"Create a fresh saved layout": "새로 저장된 레이아웃 만들기",
|
|
@@ -516,7 +517,6 @@ export const ko: Record<string, string> = {
|
|
|
516
517
|
"Pop the focused pane out to a desktop window.": "포커스 패널을 데스크톱 창으로 분리합니다.",
|
|
517
518
|
"Copy a screenshot of the focused pane.": "포커스 패널의 스크린샷을 복사합니다.",
|
|
518
519
|
"Open layout actions.": "레이아웃 작업을 엽니다.",
|
|
519
|
-
"Gridlock all windows.": "모든 창을 격자에 맞춥니다.",
|
|
520
520
|
"Enter window move mode.": "창 이동 모드로 들어갑니다.",
|
|
521
521
|
"Enter window resize mode.": "창 크기 조절 모드로 들어갑니다.",
|
|
522
522
|
"Cancel an active pane drag.": "진행 중인 패널 드래그를 취소합니다.",
|
|
@@ -910,8 +910,7 @@ export const ko: Record<string, string> = {
|
|
|
910
910
|
"v{version} available — run {command}": "v{version} 사용 가능 — {command} 실행",
|
|
911
911
|
"v{version} available — starting download...": "v{version} 사용 가능 — 다운로드 시작 중...",
|
|
912
912
|
"command bar": "명령 팔레트",
|
|
913
|
-
"
|
|
914
|
-
"Gridlock All": "모두 격자 정렬",
|
|
913
|
+
"Tidy Windows": "창 정리",
|
|
915
914
|
"Dismiss": "닫기",
|
|
916
915
|
"Minimize": "최소화",
|
|
917
916
|
"Maximize": "최대화",
|
|
@@ -1062,6 +1061,16 @@ export const ko: Record<string, string> = {
|
|
|
1062
1061
|
"Signed in as {email}": "{email}(으)로 로그인됨",
|
|
1063
1062
|
"Signed in to Gloom Cloud": "Gloom Cloud에 로그인됨",
|
|
1064
1063
|
"Signing you in...": "로그인하는 중...",
|
|
1064
|
+
"Forgot password?": "비밀번호를 잊으셨나요?",
|
|
1065
|
+
"Sending reset link...": "재설정 링크 보내는 중...",
|
|
1066
|
+
"Reset link sent to {email}. Check your inbox.": "{email}(으)로 재설정 링크를 보냈습니다. 받은편지함을 확인하세요.",
|
|
1067
|
+
"Could not send the reset email.": "재설정 이메일을 보내지 못했습니다.",
|
|
1068
|
+
"Create Account": "계정 만들기",
|
|
1069
|
+
"Log in instead": "대신 로그인",
|
|
1070
|
+
"Sign up instead": "대신 가입하기",
|
|
1071
|
+
"We'll email you a verification link.": "인증 링크를 이메일로 보내드립니다.",
|
|
1072
|
+
"show": "표시",
|
|
1073
|
+
"hide": "숨기기",
|
|
1065
1074
|
"Signs this terminal in to your account.": "이 터미널을 계정에 로그인합니다.",
|
|
1066
1075
|
"Skip for now": "지금은 건너뛰기",
|
|
1067
1076
|
"Scan QR with the mobile app": "모바일 앱으로 QR 스캔",
|
|
@@ -1093,7 +1102,6 @@ export const ko: Record<string, string> = {
|
|
|
1093
1102
|
"signing in...": "로그인 중...",
|
|
1094
1103
|
|
|
1095
1104
|
// ── Onboarding setup ────────────────────────────────────────
|
|
1096
|
-
"$29/mo for real-time market data": "실시간 시장 데이터 월 $29",
|
|
1097
1105
|
"Limited founding offer. Start a free 7-day trial.": "창립 한정 혜택. 7일 무료 체험을 시작하세요.",
|
|
1098
1106
|
"Start 7-day free trial": "7일 무료 체험 시작",
|
|
1099
1107
|
"Adds quotes, financials, options, research, news, chat and AI commands.": "시세, 재무 정보, 옵션, 리서치, 뉴스, 채팅, AI 명령을 추가합니다.",
|
package/src/i18n/zh-cn.ts
CHANGED
|
@@ -13,6 +13,7 @@ export const zhCN: Record<string, string> = {
|
|
|
13
13
|
"Cancel": "取消",
|
|
14
14
|
"Close": "关闭",
|
|
15
15
|
"Open": "打开",
|
|
16
|
+
"Linked to {source}": "已链接到 {source}",
|
|
16
17
|
"Working…": "处理中…",
|
|
17
18
|
"Searching…": "搜索中…",
|
|
18
19
|
"Loading...": "加载中...",
|
|
@@ -221,12 +222,12 @@ export const zhCN: Record<string, string> = {
|
|
|
221
222
|
"Dock Pane": "停靠面板",
|
|
222
223
|
"Settings": "设置",
|
|
223
224
|
"Copy Screenshot": "复制截图",
|
|
225
|
+
"Share Pane": "分享窗格",
|
|
224
226
|
"Pop Out": "弹出窗口",
|
|
225
227
|
"Move Window...": "移动窗口...",
|
|
226
228
|
"Resize Window...": "调整窗口大小...",
|
|
227
229
|
"Move Window": "移动窗口",
|
|
228
230
|
"Resize Window": "调整窗口大小",
|
|
229
|
-
"Gridlock All Windows": "窗口网格对齐",
|
|
230
231
|
"Change the current saved layout name": "修改当前已存布局的名称",
|
|
231
232
|
"Create a copy of the current layout": "创建当前布局的副本",
|
|
232
233
|
"Create a fresh saved layout": "创建一个新的已存布局",
|
|
@@ -518,7 +519,6 @@ export const zhCN: Record<string, string> = {
|
|
|
518
519
|
"Pop the focused pane out to a desktop window.": "把聚焦面板弹出为桌面窗口。",
|
|
519
520
|
"Copy a screenshot of the focused pane.": "复制聚焦面板的截图。",
|
|
520
521
|
"Open layout actions.": "打开布局操作。",
|
|
521
|
-
"Gridlock all windows.": "将所有窗口网格对齐。",
|
|
522
522
|
"Enter window move mode.": "进入窗口移动模式。",
|
|
523
523
|
"Enter window resize mode.": "进入窗口缩放模式。",
|
|
524
524
|
"Cancel an active pane drag.": "取消正在进行的面板拖动。",
|
|
@@ -912,8 +912,7 @@ export const zhCN: Record<string, string> = {
|
|
|
912
912
|
"v{version} available — run {command}": "v{version} 可用 — 执行 {command}",
|
|
913
913
|
"v{version} available — starting download...": "v{version} 可用 — 开始下载...",
|
|
914
914
|
"command bar": "命令栏",
|
|
915
|
-
"
|
|
916
|
-
"Gridlock All": "全部网格对齐",
|
|
915
|
+
"Tidy Windows": "整理窗口",
|
|
917
916
|
"Dismiss": "忽略",
|
|
918
917
|
"Minimize": "最小化",
|
|
919
918
|
"Maximize": "最大化",
|
|
@@ -1064,6 +1063,16 @@ export const zhCN: Record<string, string> = {
|
|
|
1064
1063
|
"Signed in as {email}": "已登录:{email}",
|
|
1065
1064
|
"Signed in to Gloom Cloud": "已登录 Gloom 云",
|
|
1066
1065
|
"Signing you in...": "正在登录...",
|
|
1066
|
+
"Forgot password?": "忘记密码?",
|
|
1067
|
+
"Sending reset link...": "正在发送重置链接...",
|
|
1068
|
+
"Reset link sent to {email}. Check your inbox.": "重置链接已发送至 {email}。请查收邮箱。",
|
|
1069
|
+
"Could not send the reset email.": "无法发送重置邮件。",
|
|
1070
|
+
"Create Account": "创建账户",
|
|
1071
|
+
"Log in instead": "改为登录",
|
|
1072
|
+
"Sign up instead": "改为注册",
|
|
1073
|
+
"We'll email you a verification link.": "我们会通过邮件发送验证链接。",
|
|
1074
|
+
"show": "显示",
|
|
1075
|
+
"hide": "隐藏",
|
|
1067
1076
|
"Signs this terminal in to your account.": "将此终端登录到你的账户。",
|
|
1068
1077
|
"Skip for now": "暂时跳过",
|
|
1069
1078
|
"Scan QR with the mobile app": "用手机应用扫描二维码",
|
|
@@ -1095,7 +1104,6 @@ export const zhCN: Record<string, string> = {
|
|
|
1095
1104
|
"signing in...": "登录中...",
|
|
1096
1105
|
|
|
1097
1106
|
// ── Onboarding setup ────────────────────────────────────────
|
|
1098
|
-
"$29/mo for real-time market data": "实时市场数据每月 $29",
|
|
1099
1107
|
"Limited founding offer. Start a free 7-day trial.": "限时创始优惠。开始 7 天免费试用。",
|
|
1100
1108
|
"Start 7-day free trial": "开始 7 天免费试用",
|
|
1101
1109
|
"Adds quotes, financials, options, research, news, chat and AI commands.": "增加行情、财务数据、期权、研报、新闻、聊天和 AI 命令。",
|
package/src/i18n/zh-tw.ts
CHANGED
|
@@ -13,6 +13,7 @@ export const zhTW: Record<string, string> = {
|
|
|
13
13
|
"Cancel": "取消",
|
|
14
14
|
"Close": "關閉",
|
|
15
15
|
"Open": "開啟",
|
|
16
|
+
"Linked to {source}": "已連結至 {source}",
|
|
16
17
|
"Working…": "處理中…",
|
|
17
18
|
"Searching…": "搜尋中…",
|
|
18
19
|
"Loading...": "載入中...",
|
|
@@ -221,12 +222,12 @@ export const zhTW: Record<string, string> = {
|
|
|
221
222
|
"Dock Pane": "停靠面板",
|
|
222
223
|
"Settings": "設定",
|
|
223
224
|
"Copy Screenshot": "複製截圖",
|
|
225
|
+
"Share Pane": "分享窗格",
|
|
224
226
|
"Pop Out": "彈出視窗",
|
|
225
227
|
"Move Window...": "移動視窗...",
|
|
226
228
|
"Resize Window...": "調整視窗大小...",
|
|
227
229
|
"Move Window": "移動視窗",
|
|
228
230
|
"Resize Window": "調整視窗大小",
|
|
229
|
-
"Gridlock All Windows": "視窗網格對齊",
|
|
230
231
|
"Change the current saved layout name": "修改目前版面設定的名稱",
|
|
231
232
|
"Create a copy of the current layout": "建立目前版面設定的副本",
|
|
232
233
|
"Create a fresh saved layout": "建立新的版面設定",
|
|
@@ -518,7 +519,6 @@ export const zhTW: Record<string, string> = {
|
|
|
518
519
|
"Pop the focused pane out to a desktop window.": "把聚焦面板彈出為桌面視窗。",
|
|
519
520
|
"Copy a screenshot of the focused pane.": "複製聚焦面板的截圖。",
|
|
520
521
|
"Open layout actions.": "開啟版面設定操作。",
|
|
521
|
-
"Gridlock all windows.": "將所有視窗網格對齊。",
|
|
522
522
|
"Enter window move mode.": "進入視窗移動模式。",
|
|
523
523
|
"Enter window resize mode.": "進入視窗縮放模式。",
|
|
524
524
|
"Cancel an active pane drag.": "取消正在進行的面板拖曳。",
|
|
@@ -912,8 +912,7 @@ export const zhTW: Record<string, string> = {
|
|
|
912
912
|
"v{version} available — run {command}": "v{version} 可用 — 執行 {command}",
|
|
913
913
|
"v{version} available — starting download...": "v{version} 可用 — 開始下載...",
|
|
914
914
|
"command bar": "命令列",
|
|
915
|
-
"
|
|
916
|
-
"Gridlock All": "全部網格對齊",
|
|
915
|
+
"Tidy Windows": "整理視窗",
|
|
917
916
|
"Dismiss": "忽略",
|
|
918
917
|
"Minimize": "最小化",
|
|
919
918
|
"Maximize": "最大化",
|
|
@@ -1064,6 +1063,16 @@ export const zhTW: Record<string, string> = {
|
|
|
1064
1063
|
"Signed in as {email}": "已登入:{email}",
|
|
1065
1064
|
"Signed in to Gloom Cloud": "已登入 Gloom 雲端",
|
|
1066
1065
|
"Signing you in...": "正在登入...",
|
|
1066
|
+
"Forgot password?": "忘記密碼?",
|
|
1067
|
+
"Sending reset link...": "正在傳送重設連結...",
|
|
1068
|
+
"Reset link sent to {email}. Check your inbox.": "重設連結已傳送至 {email}。請查看收件匣。",
|
|
1069
|
+
"Could not send the reset email.": "無法傳送重設郵件。",
|
|
1070
|
+
"Create Account": "建立帳戶",
|
|
1071
|
+
"Log in instead": "改為登入",
|
|
1072
|
+
"Sign up instead": "改為註冊",
|
|
1073
|
+
"We'll email you a verification link.": "我們會透過電子郵件寄送驗證連結。",
|
|
1074
|
+
"show": "顯示",
|
|
1075
|
+
"hide": "隱藏",
|
|
1067
1076
|
"Signs this terminal in to your account.": "將此終端登入到你的帳戶。",
|
|
1068
1077
|
"Skip for now": "暫時略過",
|
|
1069
1078
|
"Scan QR with the mobile app": "用手機應用程式掃描 QR 碼",
|
|
@@ -1095,7 +1104,6 @@ export const zhTW: Record<string, string> = {
|
|
|
1095
1104
|
"signing in...": "登入中...",
|
|
1096
1105
|
|
|
1097
1106
|
// ── Onboarding setup ────────────────────────────────────────
|
|
1098
|
-
"$29/mo for real-time market data": "即時市場資料每月 $29",
|
|
1099
1107
|
"Limited founding offer. Start a free 7-day trial.": "限時創始優惠。開始 7 天免費試用。",
|
|
1100
1108
|
"Start 7-day free trial": "開始 7 天免費試用",
|
|
1101
1109
|
"Adds quotes, financials, options, research, news, chat and AI commands.": "增加報價、財務資料、選擇權、研究、新聞、聊天與 AI 指令。",
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
getPublicMarketplaceLayout,
|
|
4
|
+
marketplaceLayoutIdFromSearch,
|
|
5
|
+
openLiveMarketplaceLayoutUrl,
|
|
6
|
+
parseMarketplaceLayoutId,
|
|
7
|
+
publicMarketplaceLayoutUrl,
|
|
8
|
+
} from "./api";
|
|
9
|
+
|
|
10
|
+
const id = "0123456789abcdef0123456789abcdef";
|
|
11
|
+
const entry = {
|
|
12
|
+
id,
|
|
13
|
+
name: "Social Desk",
|
|
14
|
+
schemaVersion: 2,
|
|
15
|
+
sourceConfigVersion: 21,
|
|
16
|
+
layout: {
|
|
17
|
+
dockRoot: { kind: "pane", instanceId: "p1" },
|
|
18
|
+
instances: [{
|
|
19
|
+
instanceId: "p1",
|
|
20
|
+
paneId: "twitter-feed",
|
|
21
|
+
params: { query: "$NVDA" },
|
|
22
|
+
}],
|
|
23
|
+
floating: [],
|
|
24
|
+
detached: [],
|
|
25
|
+
},
|
|
26
|
+
paneState: {},
|
|
27
|
+
author: { username: "analyst", displayName: "Analyst" },
|
|
28
|
+
publishedAt: "2026-08-27T00:00:00.000Z",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
describe("public marketplace layout links", () => {
|
|
32
|
+
test("loads and validates an anonymous layout item", async () => {
|
|
33
|
+
const calls: Array<[string, RequestInit | undefined]> = [];
|
|
34
|
+
const loaded = await getPublicMarketplaceLayout(id, async (url, init) => {
|
|
35
|
+
calls.push([url, init]);
|
|
36
|
+
return new Response(JSON.stringify(entry), {
|
|
37
|
+
status: 200,
|
|
38
|
+
headers: { "content-type": "application/json" },
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(loaded).toEqual(entry);
|
|
43
|
+
expect(new URL(calls[0]![0]).pathname).toBe(`/layouts/${id}`);
|
|
44
|
+
expect(calls[0]![1]?.credentials).toBe("include");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("builds durable public and live-install URLs", () => {
|
|
48
|
+
expect(publicMarketplaceLayoutUrl(id, "https://term.gloom.sh")).toBe(`https://term.gloom.sh/l/${id}`);
|
|
49
|
+
expect(openLiveMarketplaceLayoutUrl(id, "https://term.gloom.sh")).toBe(`https://term.gloom.sh/?layout=${id}`);
|
|
50
|
+
expect(marketplaceLayoutIdFromSearch(`?layout=${id}`)).toBe(id);
|
|
51
|
+
expect(parseMarketplaceLayoutId(`/l/${id}`)).toBe(id);
|
|
52
|
+
expect(parseMarketplaceLayoutId("/l/not-an-id")).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PUBLIC_SHARE_ORIGIN,
|
|
3
|
+
SHARE_API_ORIGIN,
|
|
4
|
+
} from "../shares/api";
|
|
5
|
+
import {
|
|
6
|
+
isMarketplaceLayoutId,
|
|
7
|
+
parseMarketplaceLayoutEntry,
|
|
8
|
+
type LayoutMarketplaceEntry,
|
|
9
|
+
} from "./payload";
|
|
10
|
+
|
|
11
|
+
type LayoutFetch = (input: string, init?: RequestInit) => Promise<Response>;
|
|
12
|
+
|
|
13
|
+
export async function getPublicMarketplaceLayout(
|
|
14
|
+
id: string,
|
|
15
|
+
fetchImpl: LayoutFetch = fetch,
|
|
16
|
+
): Promise<LayoutMarketplaceEntry | null> {
|
|
17
|
+
if (!isMarketplaceLayoutId(id)) return null;
|
|
18
|
+
const response = await fetchImpl(
|
|
19
|
+
`${SHARE_API_ORIGIN}/layouts/${encodeURIComponent(id)}`,
|
|
20
|
+
{ credentials: "include" },
|
|
21
|
+
);
|
|
22
|
+
if (response.status === 404) return null;
|
|
23
|
+
if (!response.ok) throw new Error("Could not load shared layout.");
|
|
24
|
+
let body: unknown;
|
|
25
|
+
try {
|
|
26
|
+
body = await response.json();
|
|
27
|
+
} catch {
|
|
28
|
+
throw new Error("The layout service returned an invalid response.");
|
|
29
|
+
}
|
|
30
|
+
return parseMarketplaceLayoutEntry(body);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function publicMarketplaceLayoutUrl(
|
|
34
|
+
id: string,
|
|
35
|
+
origin = PUBLIC_SHARE_ORIGIN,
|
|
36
|
+
): string {
|
|
37
|
+
if (!isMarketplaceLayoutId(id)) throw new Error("Invalid layout id.");
|
|
38
|
+
return new URL(`/l/${encodeURIComponent(id)}`, origin).toString();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function openLiveMarketplaceLayoutUrl(
|
|
42
|
+
id: string,
|
|
43
|
+
origin = PUBLIC_SHARE_ORIGIN,
|
|
44
|
+
): string {
|
|
45
|
+
if (!isMarketplaceLayoutId(id)) throw new Error("Invalid layout id.");
|
|
46
|
+
const url = new URL("/", origin);
|
|
47
|
+
url.searchParams.set("layout", id);
|
|
48
|
+
return url.toString();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function marketplaceLayoutIdFromSearch(search: string): string | null {
|
|
52
|
+
const id = new URLSearchParams(search).get("layout")?.trim() ?? "";
|
|
53
|
+
return isMarketplaceLayoutId(id) ? id : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function parseMarketplaceLayoutId(pathname: string): string | null {
|
|
57
|
+
const match = /^\/l\/([^/]+)\/?$/.exec(pathname);
|
|
58
|
+
if (!match) return null;
|
|
59
|
+
try {
|
|
60
|
+
const id = decodeURIComponent(match[1] ?? "");
|
|
61
|
+
return isMarketplaceLayoutId(id) ? id : null;
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/** @jsxImportSource react */
|
|
2
|
+
import { Window } from "happy-dom";
|
|
3
|
+
|
|
4
|
+
const testWindow = new Window({ url: "http://localhost" });
|
|
5
|
+
const domGlobals = {
|
|
6
|
+
IS_REACT_ACT_ENVIRONMENT: true,
|
|
7
|
+
window: testWindow,
|
|
8
|
+
document: testWindow.document,
|
|
9
|
+
navigator: testWindow.navigator,
|
|
10
|
+
KeyboardEvent: testWindow.KeyboardEvent,
|
|
11
|
+
MouseEvent: testWindow.MouseEvent,
|
|
12
|
+
HTMLElement: testWindow.HTMLElement,
|
|
13
|
+
Node: testWindow.Node,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** Bun shares one process across test files, so the DOM globals must not leak. */
|
|
17
|
+
const priorGlobals = Object.fromEntries(
|
|
18
|
+
Object.keys(domGlobals).map((key) => [key, (globalThis as Record<string, unknown>)[key]]),
|
|
19
|
+
);
|
|
20
|
+
Object.assign(globalThis, domGlobals);
|
|
21
|
+
|
|
22
|
+
import { afterAll, afterEach, expect, test } from "bun:test";
|
|
23
|
+
import { act } from "react";
|
|
24
|
+
import { createRoot } from "react-dom/client";
|
|
25
|
+
import { UiHostProvider, type RendererHost, type UiHost } from "../ui";
|
|
26
|
+
import { WebBox } from "../renderers/electrobun/view/host/box";
|
|
27
|
+
import { WebText, WebSpan } from "../renderers/electrobun/view/host/text";
|
|
28
|
+
import { WebScrollBox } from "../renderers/electrobun/view/host/scroll-box";
|
|
29
|
+
import { WebInput } from "../renderers/electrobun/view/host/input";
|
|
30
|
+
import { WebButton, WebTextField } from "../renderers/electrobun/view/desktop/controls";
|
|
31
|
+
import { cloneLayout, createDefaultConfig } from "../types/config";
|
|
32
|
+
import type { PaneDef } from "../types/plugin";
|
|
33
|
+
import { LayoutGalleryDesktop } from "./gallery-desktop";
|
|
34
|
+
import { buildOwnedEntries, type GalleryEntry } from "./model";
|
|
35
|
+
import type { LayoutGalleryController } from "./gallery";
|
|
36
|
+
|
|
37
|
+
const renderer: RendererHost = {
|
|
38
|
+
requestExit() {},
|
|
39
|
+
async openExternal() {},
|
|
40
|
+
async copyText() {},
|
|
41
|
+
async readText() { return ""; },
|
|
42
|
+
notify() {},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const ui = {
|
|
46
|
+
kind: "desktop-web",
|
|
47
|
+
capabilities: { cellWidthPx: 8, cellHeightPx: 18, fractionalViewport: true },
|
|
48
|
+
Box: WebBox,
|
|
49
|
+
Text: WebText,
|
|
50
|
+
Span: WebSpan,
|
|
51
|
+
ScrollBox: WebScrollBox,
|
|
52
|
+
Button: WebButton,
|
|
53
|
+
Input: WebInput,
|
|
54
|
+
TextField: WebTextField,
|
|
55
|
+
SpinnerMark: () => null,
|
|
56
|
+
} as unknown as UiHost;
|
|
57
|
+
|
|
58
|
+
afterAll(() => {
|
|
59
|
+
for (const [key, value] of Object.entries(priorGlobals)) {
|
|
60
|
+
if (value === undefined) delete (globalThis as Record<string, unknown>)[key];
|
|
61
|
+
else (globalThis as Record<string, unknown>)[key] = value;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
function paneDef(id: string, name: string, icon: string): PaneDef {
|
|
66
|
+
return { id, name, icon, component: () => null, defaultPosition: "left" };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const panes = new Map<string, PaneDef>([
|
|
70
|
+
["portfolio-list", paneDef("portfolio-list", "Portfolio", "P")],
|
|
71
|
+
["ticker-research", paneDef("ticker-research", "Ticker Research", "T")],
|
|
72
|
+
["chat", paneDef("chat", "Chat", "M")],
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
let root: ReturnType<typeof createRoot> | undefined;
|
|
76
|
+
|
|
77
|
+
afterEach(async () => {
|
|
78
|
+
if (root) {
|
|
79
|
+
await act(async () => root!.unmount());
|
|
80
|
+
root = undefined;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
function communityEntry(): GalleryEntry {
|
|
85
|
+
return {
|
|
86
|
+
id: "community:abc",
|
|
87
|
+
marketplaceId: "0123456789abcdef0123456789abcdef",
|
|
88
|
+
kind: "community",
|
|
89
|
+
name: "Earnings War Room",
|
|
90
|
+
layout: cloneLayout(createDefaultConfig("/tmp/gloomberb-gallery-desktop-community").layout),
|
|
91
|
+
paneState: {},
|
|
92
|
+
index: null,
|
|
93
|
+
active: false,
|
|
94
|
+
author: "@analyst",
|
|
95
|
+
publishedAt: "2026-08-26T00:00:00.000Z",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function createController(overrides: Partial<LayoutGalleryController> = {}): {
|
|
100
|
+
controller: LayoutGalleryController;
|
|
101
|
+
activated: GalleryEntry[];
|
|
102
|
+
installed: GalleryEntry[];
|
|
103
|
+
selections: (string | null)[];
|
|
104
|
+
copied: GalleryEntry[];
|
|
105
|
+
} {
|
|
106
|
+
const config = createDefaultConfig("/tmp/gloomberb-gallery-desktop-test");
|
|
107
|
+
const owned = buildOwnedEntries([
|
|
108
|
+
{ name: "Monitor", layout: cloneLayout(config.layout) },
|
|
109
|
+
{ name: "Research Desk", layout: cloneLayout(config.layout) },
|
|
110
|
+
], 1);
|
|
111
|
+
const activated: GalleryEntry[] = [];
|
|
112
|
+
const installed: GalleryEntry[] = [];
|
|
113
|
+
const selections: (string | null)[] = [];
|
|
114
|
+
const copied: GalleryEntry[] = [];
|
|
115
|
+
const community = overrides.community ?? [];
|
|
116
|
+
const controller: LayoutGalleryController = {
|
|
117
|
+
query: "",
|
|
118
|
+
setQuery: () => {},
|
|
119
|
+
owned,
|
|
120
|
+
community,
|
|
121
|
+
entries: [...(overrides.owned ?? owned), ...community],
|
|
122
|
+
selectedId: null,
|
|
123
|
+
select: (id) => selections.push(id),
|
|
124
|
+
detail: null,
|
|
125
|
+
openDetail: () => {},
|
|
126
|
+
closeDetail: () => {},
|
|
127
|
+
activate: (entry) => activated.push(entry),
|
|
128
|
+
install: (entry) => installed.push(entry),
|
|
129
|
+
discover: {
|
|
130
|
+
state: { status: "signed-out", items: [] },
|
|
131
|
+
refresh: () => {},
|
|
132
|
+
publish: async () => { throw new Error("unused"); },
|
|
133
|
+
},
|
|
134
|
+
signedIn: false,
|
|
135
|
+
requestSignIn: () => {},
|
|
136
|
+
publishCurrent: () => {},
|
|
137
|
+
copyLink: (entry) => copied.push(entry),
|
|
138
|
+
publishing: false,
|
|
139
|
+
newLayout: () => {},
|
|
140
|
+
renameLayout: () => {},
|
|
141
|
+
duplicateLayout: () => {},
|
|
142
|
+
deleteLayout: () => {},
|
|
143
|
+
canDelete: true,
|
|
144
|
+
close: () => {},
|
|
145
|
+
panes,
|
|
146
|
+
missingPaneIds: () => [],
|
|
147
|
+
...overrides,
|
|
148
|
+
};
|
|
149
|
+
return { controller, activated, installed, selections, copied };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function renderGallery(controller: LayoutGalleryController) {
|
|
153
|
+
const container = testWindow.document.createElement("div");
|
|
154
|
+
testWindow.document.body.appendChild(container);
|
|
155
|
+
root = createRoot(container as unknown as HTMLElement);
|
|
156
|
+
await act(async () => {
|
|
157
|
+
root!.render(
|
|
158
|
+
<UiHostProvider ui={ui} renderer={renderer}>
|
|
159
|
+
<LayoutGalleryDesktop controller={controller} />
|
|
160
|
+
</UiHostProvider>,
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
return container;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function rows(container: Element) {
|
|
167
|
+
return [...container.querySelectorAll('[data-gloom-role="layout-gallery-row"]')];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function pressButton(container: Element, label: string) {
|
|
171
|
+
const button = [...container.querySelectorAll('[data-gloom-role="desktop-button"]')]
|
|
172
|
+
.find((node) => node.textContent?.includes(label));
|
|
173
|
+
if (!button) throw new Error(`no button labelled ${label}`);
|
|
174
|
+
return act(async () => {
|
|
175
|
+
button.dispatchEvent(new testWindow.MouseEvent("mousedown", { bubbles: true, button: 0 }) as unknown as MouseEvent);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
test("sidebar rows select the preview instead of activating the layout", async () => {
|
|
180
|
+
const { controller, activated, selections } = createController();
|
|
181
|
+
const container = await renderGallery(controller);
|
|
182
|
+
|
|
183
|
+
const text = container.textContent ?? "";
|
|
184
|
+
expect(text.indexOf("YOUR LAYOUTS")).toBeLessThan(text.indexOf("DISCOVER"));
|
|
185
|
+
expect(text).toContain("A Gloom account is required to browse community layouts.");
|
|
186
|
+
|
|
187
|
+
const sidebarRows = rows(container);
|
|
188
|
+
// Two owned layouts plus the account-gate row.
|
|
189
|
+
expect(sidebarRows.length).toBe(3);
|
|
190
|
+
expect(sidebarRows[0]!.getAttribute("role")).toBe("button");
|
|
191
|
+
expect(sidebarRows[0]!.getAttribute("tabindex")).toBe("0");
|
|
192
|
+
expect(sidebarRows[0]!.getAttribute("aria-label")).toContain("panes");
|
|
193
|
+
|
|
194
|
+
await act(async () => {
|
|
195
|
+
sidebarRows[0]!.dispatchEvent(new testWindow.MouseEvent("mousedown", { bubbles: true, button: 0 }) as unknown as MouseEvent);
|
|
196
|
+
});
|
|
197
|
+
await act(async () => {
|
|
198
|
+
sidebarRows[1]!.dispatchEvent(new testWindow.MouseEvent("mouseover", { bubbles: true }) as unknown as MouseEvent);
|
|
199
|
+
});
|
|
200
|
+
await act(async () => {
|
|
201
|
+
(sidebarRows[1] as unknown as HTMLElement).focus();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
expect(selections).toEqual(["owned:0", "owned:1", "owned:1"]);
|
|
205
|
+
expect(activated).toEqual([]);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("preview falls back to the layout in use and runs owned actions", async () => {
|
|
209
|
+
const renamed: GalleryEntry[] = [];
|
|
210
|
+
const { controller, activated } = createController({ renameLayout: (entry) => renamed.push(entry) });
|
|
211
|
+
const container = await renderGallery(controller);
|
|
212
|
+
|
|
213
|
+
const preview = container.querySelector('[data-gloom-role="layout-gallery-preview"]')!;
|
|
214
|
+
const previewText = preview.textContent ?? "";
|
|
215
|
+
// owned:1 is the active layout and there is no selection yet.
|
|
216
|
+
expect(previewText).toContain("Research Desk");
|
|
217
|
+
expect(preview.querySelector("svg")).not.toBeNull();
|
|
218
|
+
|
|
219
|
+
await pressButton(preview, "Rename");
|
|
220
|
+
expect(renamed.map((entry) => entry.name)).toEqual(["Research Desk"]);
|
|
221
|
+
|
|
222
|
+
await pressButton(preview, "Use Layout");
|
|
223
|
+
expect(activated.map((entry) => entry.name)).toEqual(["Research Desk"]);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("a selected community layout installs as an independent copy", async () => {
|
|
227
|
+
const community = [communityEntry()];
|
|
228
|
+
const { controller, installed, copied } = createController({
|
|
229
|
+
community,
|
|
230
|
+
selectedId: "community:abc",
|
|
231
|
+
signedIn: true,
|
|
232
|
+
discover: {
|
|
233
|
+
state: { status: "ready", items: [] },
|
|
234
|
+
refresh: () => {},
|
|
235
|
+
publish: async () => { throw new Error("unused"); },
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
const container = await renderGallery(controller);
|
|
239
|
+
|
|
240
|
+
const preview = container.querySelector('[data-gloom-role="layout-gallery-preview"]')!;
|
|
241
|
+
const previewText = preview.textContent ?? "";
|
|
242
|
+
expect(previewText).toContain("Earnings War Room");
|
|
243
|
+
expect(previewText).toContain("@analyst");
|
|
244
|
+
expect(previewText).toContain("Adds an editable copy");
|
|
245
|
+
expect(previewText).not.toContain("Rename");
|
|
246
|
+
|
|
247
|
+
await pressButton(preview, "Copy Link");
|
|
248
|
+
expect(copied.map((entry) => entry.marketplaceId)).toEqual(["0123456789abcdef0123456789abcdef"]);
|
|
249
|
+
|
|
250
|
+
await pressButton(preview, "Add Layout");
|
|
251
|
+
expect(installed.map((entry) => entry.name)).toEqual(["Earnings War Room"]);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("an empty gallery keeps a preview placeholder instead of a blank pane", async () => {
|
|
255
|
+
const { controller } = createController({ owned: [], entries: [] });
|
|
256
|
+
const container = await renderGallery(controller);
|
|
257
|
+
|
|
258
|
+
expect(rows(container).length).toBe(1); // account gate only
|
|
259
|
+
const empty = container.querySelector('[data-gloom-role="layout-gallery-preview-empty"]')!;
|
|
260
|
+
expect(empty.textContent).toContain("No layout selected.");
|
|
261
|
+
expect(container.querySelector('[data-gloom-role="layout-gallery-preview"]')).toBeNull();
|
|
262
|
+
});
|