gloomberb 0.11.2 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/README.zh-CN.md +4 -2
- package/package.json +28 -1
- package/src/api-client/auth.ts +10 -0
- package/src/api-client/data.ts +108 -1
- package/src/api-client/index.test.ts +127 -0
- package/src/api-client/index.ts +134 -1
- package/src/api-client/normalizers.ts +39 -0
- package/src/api-client/paths.ts +85 -1
- package/src/api-client/types.ts +220 -0
- package/src/app/pane-runtime/index.tsx +15 -12
- package/src/app/pane-runtime/layout-placement.test.ts +27 -0
- package/src/app/pane-runtime/layout-placement.ts +13 -0
- package/src/app/pane-runtime/plugin-bindings.ts +62 -0
- package/src/app.tsx +60 -18
- package/src/architecture/browser-entry-graph.test.ts +96 -0
- package/src/brokers/profile-form.test.ts +25 -27
- package/src/brokers/test-broker.ts +68 -0
- package/src/capabilities/chart-series.ts +3 -2
- package/src/cli/commands/market.ts +2 -2
- package/src/cli/commands/plugins.ts +39 -11
- package/src/cli/desktop-pane-shot.ts +285 -16
- package/src/cli/index.ts +1 -1
- package/src/cli/pane-functions/capabilities.ts +61 -31
- package/src/cli/pane-functions/catalog.ts +16 -4
- package/src/cli/pane-functions/discovery.ts +1 -0
- package/src/cli/pane-functions/headless.test.ts +247 -0
- package/src/cli/pane-functions/headless.ts +423 -0
- package/src/cli/pane-functions/index.test.ts +9 -0
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +27 -2
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +26 -8
- package/src/cli/pane-functions/screenshot.test.ts +76 -0
- package/src/cli/pane-functions/screenshot.ts +236 -20
- package/src/cli/registry.ts +0 -22
- package/src/cli/restore-plugins.ts +34 -0
- package/src/cli/result.ts +7 -10
- package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
- package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
- package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
- package/src/components/chart/composite/composite-chart.test.tsx +68 -20
- package/src/components/chart/composite/composite-chart.tsx +301 -204
- package/src/components/chart/composite/format.ts +8 -2
- package/src/components/chart/composite/interactions.test.ts +68 -102
- package/src/components/chart/composite/interactions.ts +214 -264
- package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
- package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
- package/src/components/chart/composite/scene.ts +57 -3
- package/src/components/chart/composite/text-renderer.ts +3 -2
- package/src/components/chart/composite/time-scale.ts +138 -57
- package/src/components/chart/composite/types.ts +25 -1
- package/src/components/chart/core/data.ts +9 -13
- package/src/components/chart/core/date-window.ts +7 -1
- package/src/components/chart/core/pointer.ts +3 -0
- package/src/components/chart/core/types.ts +0 -45
- package/src/components/chart/native/chart-rasterizer.ts +0 -1
- package/src/components/chart/native/kitty/index.test.ts +0 -188
- package/src/components/chart/native/raster/crosshair.ts +6 -5
- package/src/components/chart/native/raster/primitives.ts +0 -23
- package/src/components/chart/native/renderer-selection.ts +81 -31
- package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
- package/src/components/chart/static/chart-surface.tsx +150 -0
- package/src/components/chart/static/index.ts +8 -0
- package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
- package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
- package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
- package/src/components/chart/static/series.ts +49 -0
- package/src/components/command-bar/action-runtime.ts +1 -3
- package/src/components/command-bar/assist/model.test.ts +17 -2
- package/src/components/command-bar/assist/model.ts +40 -9
- package/src/components/command-bar/commands/direct/index.ts +1 -1
- package/src/components/command-bar/commands/direct/runtime.ts +1 -1
- package/src/components/command-bar/commands/registry.ts +0 -11
- package/src/components/command-bar/helpers.test.ts +2 -2
- package/src/components/command-bar/helpers.ts +2 -6
- package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
- package/src/components/command-bar/list/badge.test.ts +54 -0
- package/src/components/command-bar/list/badge.ts +62 -0
- package/src/components/command-bar/list/model.test.ts +103 -0
- package/src/components/command-bar/list/model.ts +61 -4
- package/src/components/command-bar/list/view.test.tsx +113 -0
- package/src/components/command-bar/list/view.tsx +147 -131
- package/src/components/command-bar/pane-templates/workflow.ts +1 -1
- package/src/components/command-bar/panel/body.tsx +51 -65
- package/src/components/command-bar/panel/index.tsx +48 -13
- package/src/components/command-bar/panel/layout.test.ts +139 -0
- package/src/components/command-bar/panel/layout.ts +65 -28
- package/src/components/command-bar/panel/native-surface.ts +24 -0
- package/src/components/command-bar/panel/prompt-binding.ts +54 -0
- package/src/components/command-bar/panel/runtime.ts +6 -3
- package/src/components/command-bar/panel/state.ts +41 -7
- package/src/components/command-bar/panel/types.ts +7 -1
- package/src/components/command-bar/routes/root/provider-search.ts +34 -15
- package/src/components/command-bar/routes/root/results.test.ts +161 -0
- package/src/components/command-bar/routes/root/results.ts +30 -28
- package/src/components/command-bar/routes/root/runtime.ts +37 -20
- package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
- package/src/components/command-bar/routes/root/search-providers.ts +205 -0
- package/src/components/command-bar/routes/root/selection.ts +1 -12
- package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
- package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
- package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
- package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
- package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
- package/src/components/command-bar/routing/actions.ts +1 -81
- package/src/components/command-bar/routing/launch-request.ts +1 -1
- package/src/components/command-bar/routing/list-state.ts +6 -21
- package/src/components/command-bar/selection-runtime.ts +1 -1
- package/src/components/command-bar/surface/assist.test.tsx +14 -10
- package/src/components/command-bar/surface/environment.ts +8 -1
- package/src/components/command-bar/surface/index.test.tsx +84 -68
- package/src/components/command-bar/surface/index.tsx +33 -36
- package/src/components/command-bar/surface/test-harness.tsx +15 -3
- package/src/components/command-bar/theme-picker.tsx +43 -15
- package/src/components/command-bar/view-model.test.ts +53 -8
- package/src/components/command-bar/view-model.ts +43 -14
- package/src/components/command-bar/workflow/tickers.ts +1 -0
- package/src/components/command-bar/workflow/types.ts +1 -1
- package/src/components/data-table/export.test.ts +21 -0
- package/src/components/data-table/export.ts +21 -0
- package/src/components/data-table/view.test.tsx +45 -6
- package/src/components/data-table/view.tsx +55 -9
- package/src/components/index.ts +10 -2
- package/src/components/input-search-bar.test.tsx +36 -2
- package/src/components/input-search-bar.tsx +18 -1
- package/src/components/layout/header.test.tsx +57 -10
- package/src/components/layout/header.tsx +292 -149
- package/src/components/layout/market-summary.test.ts +81 -0
- package/src/components/layout/market-summary.ts +109 -0
- package/src/components/layout/shell/chrome.ts +96 -1
- package/src/components/layout/shell/index.tsx +11 -3
- package/src/components/layout/shell/menu.ts +9 -0
- package/src/components/layout/shell/pane/actions.ts +29 -0
- package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
- package/src/components/layout/shell/shortcuts.ts +3 -0
- package/src/components/layout/status-bar.test.tsx +9 -8
- package/src/components/layout/status-bar.tsx +85 -54
- package/src/components/onboarding/onboarding-frame.tsx +2 -26
- package/src/components/price-sparkline/index.ts +61 -34
- package/src/components/price-sparkline/view.tsx +1 -1
- package/src/components/sign-in-gate.test.tsx +128 -0
- package/src/components/sign-in-gate.tsx +149 -0
- package/src/components/speedometer-gauge/desktop.tsx +2 -12
- package/src/components/ticker/badge/list.tsx +17 -3
- package/src/components/ticker/list-table-view.tsx +6 -3
- package/src/components/ui/data-table/index.tsx +22 -4
- package/src/components/ui/data-table/opentui/index.tsx +165 -120
- package/src/components/ui/frame.tsx +28 -0
- package/src/components/ui/index.ts +3 -0
- package/src/components/ui/native-select.tsx +20 -10
- package/src/components/ui/select-button.tsx +138 -0
- package/src/components/ui/status.tsx +2 -2
- package/src/components/ui/table-layout.ts +1 -1
- package/src/components/use-double-click-activation.ts +7 -3
- package/src/core/app-services.ts +3 -3
- package/src/data/config/chart-settings.ts +4 -4
- package/src/data/config/layout.ts +12 -6
- package/src/data/config/store/index.test.ts +97 -0
- package/src/data/config/store/migrations.ts +41 -0
- package/src/data/config/store/normalize.ts +5 -3
- package/src/i18n/es.ts +6 -2
- package/src/i18n/ja.ts +6 -2
- package/src/i18n/ko.ts +6 -2
- package/src/i18n/zh-cn.ts +6 -2
- package/src/i18n/zh-tw.ts +6 -2
- package/src/layout-marketplace/gallery-terminal.tsx +126 -96
- package/src/layout-marketplace/gallery.test.tsx +65 -1
- package/src/layout-marketplace/pane-imagery.ts +1 -0
- package/src/layout-marketplace/payload.test.ts +17 -0
- package/src/layout-marketplace/payload.ts +40 -5
- package/src/market-data/coordinator/index.ts +3 -2
- package/src/market-data/coordinator/quotes.ts +173 -66
- package/src/market-data/coordinator/subscriptions.test.ts +215 -37
- package/src/pane-settings.ts +1 -1
- package/src/plugins/builtin/analytics/pane-model.ts +1 -1
- package/src/plugins/builtin/analytics/view.tsx +1 -1
- package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
- package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
- package/src/plugins/builtin/chart-composer/cli-options.ts +12 -9
- package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
- package/src/plugins/builtin/chart-composer/settings.ts +2 -13
- package/src/plugins/builtin/chat/content/composer.tsx +8 -4
- package/src/plugins/builtin/cloud/auth-dialog.tsx +14 -228
- package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
- package/src/plugins/builtin/cloud/plugin.tsx +1 -0
- package/src/plugins/builtin/composite-plugins.ts +16 -1
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
- package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
- package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
- package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -21
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings/index.tsx +1 -0
- package/src/plugins/builtin/earnings-calls/data.ts +223 -0
- package/src/plugins/builtin/earnings-calls/format.ts +49 -0
- package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +756 -0
- package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
- package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
- package/src/plugins/builtin/earnings-calls/transcript-view.tsx +334 -0
- package/src/plugins/builtin/econ/detail-view.tsx +1 -2
- package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
- package/src/plugins/builtin/econ/index.tsx +3 -1
- package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
- package/src/plugins/builtin/econ-statistics/client.ts +92 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
- package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
- package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
- package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
- package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
- package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
- package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
- package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
- package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
- package/src/plugins/builtin/econ-statistics/view.ts +90 -0
- package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
- package/src/plugins/builtin/fear-greed/data.ts +6 -1
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +1 -0
- package/src/plugins/builtin/help/index.tsx +4 -0
- package/src/plugins/builtin/holders/index.tsx +1 -0
- package/src/plugins/builtin/insider/index.tsx +1 -0
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
- package/src/plugins/builtin/market-halts/index.tsx +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +1 -0
- package/src/plugins/builtin/market-valuation/align.ts +122 -0
- package/src/plugins/builtin/market-valuation/cache.ts +32 -0
- package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
- package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
- package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
- package/src/plugins/builtin/market-valuation/client.ts +116 -0
- package/src/plugins/builtin/market-valuation/defs.ts +251 -0
- package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
- package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
- package/src/plugins/builtin/market-valuation/headless.ts +170 -0
- package/src/plugins/builtin/market-valuation/index.tsx +64 -0
- package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
- package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
- package/src/plugins/builtin/market-valuation/model.ts +8 -0
- package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
- package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
- package/src/plugins/builtin/market-valuation/series.ts +12 -0
- package/src/plugins/builtin/market-valuation/settings.ts +38 -0
- package/src/plugins/builtin/market-valuation/sources.ts +130 -0
- package/src/plugins/builtin/market-valuation/trend.ts +89 -0
- package/src/plugins/builtin/market-valuation/view.ts +137 -0
- package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
- package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
- package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
- package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
- package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
- package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
- package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
- package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
- package/src/plugins/builtin/news/wire/index.tsx +31 -44
- package/src/plugins/builtin/options/analytics.test.ts +83 -0
- package/src/plugins/builtin/options/analytics.ts +110 -0
- package/src/plugins/builtin/options/index.tsx +18 -2
- package/src/plugins/builtin/options/table.ts +121 -51
- package/src/plugins/builtin/options/types.ts +22 -15
- package/src/plugins/builtin/options/view.test.tsx +53 -1
- package/src/plugins/builtin/options/view.tsx +92 -41
- package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
- package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
- package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
- package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
- package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
- package/src/plugins/builtin/plugin-module.test.ts +2 -4
- package/src/plugins/builtin/plugin-module.ts +9 -8
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
- package/src/plugins/builtin/research/index.tsx +4 -0
- package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
- package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
- package/src/plugins/builtin/research-search/data.ts +109 -0
- package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
- package/src/plugins/builtin/research-search/document-view.tsx +254 -0
- package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
- package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
- package/src/plugins/builtin/research-search/index.tsx +85 -0
- package/src/plugins/builtin/research-search/model.test.ts +40 -0
- package/src/plugins/builtin/research-search/model.ts +291 -0
- package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
- package/src/plugins/builtin/research-search/pane.tsx +678 -0
- package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
- package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
- package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
- package/src/plugins/builtin/research-search/snippet.ts +134 -0
- package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
- package/src/plugins/builtin/scanner/flow-model.ts +29 -1
- package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
- package/src/plugins/builtin/scanner/index.ts +2 -0
- package/src/plugins/builtin/sec/index.tsx +1 -0
- package/src/plugins/builtin/sectors/index.tsx +1 -0
- package/src/plugins/builtin/shared/series-cache.ts +93 -0
- package/src/plugins/builtin/short-interest/index.tsx +1 -0
- package/src/plugins/builtin/short-interest/pane.tsx +1 -1
- package/src/plugins/builtin/thirteenf/index.tsx +1 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +3 -5
- package/src/plugins/builtin/ticker-detail/index.tsx +2 -14
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
- package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
- package/src/plugins/builtin/volatility/index.tsx +1 -1
- package/src/plugins/builtin/world-indices/index.tsx +1 -0
- package/src/plugins/builtin/world-venue-map/index.ts +27 -0
- package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +481 -0
- package/src/plugins/builtin/world-venue-map/model.test.ts +85 -0
- package/src/plugins/builtin/world-venue-map/model.ts +268 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +300 -0
- package/src/plugins/builtin/world-venue-map/world-outlines.ts +2 -0
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/bundle.test.ts +109 -0
- package/src/plugins/bundle.ts +139 -0
- package/src/plugins/catalog-backend.ts +6 -44
- package/src/plugins/catalog-browser.test.ts +1 -0
- package/src/plugins/catalog-browser.ts +12 -0
- package/src/plugins/catalog-manifest.test.ts +43 -0
- package/src/plugins/catalog-manifest.ts +92 -0
- package/src/plugins/catalog-ui.ts +20 -4
- package/src/plugins/catalog.ts +9 -2
- package/src/plugins/current-target.ts +28 -0
- package/src/plugins/host-contract.ts +37 -0
- package/src/plugins/host-link.ts +158 -0
- package/src/plugins/host-modules.ts +61 -0
- package/src/plugins/loader.ts +41 -22
- package/src/plugins/ownership.ts +1 -0
- package/src/plugins/prediction-markets/index.tsx +1 -0
- package/src/plugins/registry/context.ts +3 -0
- package/src/plugins/registry/contributions.ts +27 -0
- package/src/plugins/registry/index.test.ts +25 -0
- package/src/plugins/registry/index.ts +8 -0
- package/src/plugins/registry/pane-settings.test.ts +53 -0
- package/src/plugins/registry/pane-settings.ts +29 -3
- package/src/plugins/seed.test.ts +83 -0
- package/src/plugins/seed.ts +80 -0
- package/src/public/broker.ts +31 -0
- package/src/public/market-data.ts +9 -0
- package/src/public/pane-hooks.ts +57 -0
- package/src/public/public-api.test.ts +285 -0
- package/src/public/react.ts +62 -0
- package/src/public/test-support.ts +23 -0
- package/src/public/time-series.ts +8 -0
- package/src/public/utils.ts +77 -0
- package/src/remote/layout-helpers.ts +6 -8
- package/src/remote/resources.ts +2 -0
- package/src/remote/schema.ts +1 -0
- package/src/remote/semantic-tree.test.tsx +105 -0
- package/src/remote/semantic-tree.tsx +53 -35
- package/src/renderers/browser/main.tsx +6 -0
- package/src/renderers/browser/ui-host.tsx +11 -0
- package/src/renderers/cloudflare/worker.test.ts +19 -0
- package/src/renderers/cloudflare/worker.ts +1 -1
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -2
- package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
- package/src/renderers/electrobun/bun/index.ts +6 -2
- package/src/renderers/electrobun/shared/protocol.ts +25 -0
- package/src/renderers/electrobun/view/build-assets.ts +0 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +51 -1
- package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
- package/src/renderers/electrobun/view/external-plugins.ts +63 -0
- package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
- package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
- package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
- package/src/renderers/electrobun/view/host/mouse.ts +16 -2
- package/src/renderers/electrobun/view/main.tsx +27 -2
- package/src/renderers/electrobun/view/styles.css +5 -0
- package/src/renderers/electrobun/view/toast-host.tsx +18 -0
- package/src/renderers/electrobun/view/ui-host.tsx +3 -0
- package/src/renderers/opentui/chart-surface.tsx +5 -1
- package/src/renderers/opentui/host.tsx +20 -2
- package/src/renderers/opentui/interaction-performance.test.ts +79 -0
- package/src/renderers/opentui/interaction-performance.ts +188 -0
- package/src/renderers/opentui/start.tsx +26 -1
- package/src/renderers/opentui/test-utils.tsx +13 -0
- package/src/renderers/opentui/toast-host.tsx +26 -2
- package/src/renderers/opentui/ui-host.tsx +1 -0
- package/src/sources/provider-router/search.test.ts +191 -0
- package/src/sources/provider-router/search.ts +92 -39
- package/src/state/app/context/index.tsx +2 -0
- package/src/state/app/context/selector.test.tsx +33 -1
- package/src/state/hooks/quote-streaming.test.tsx +93 -0
- package/src/state/hooks/quote-streaming.ts +106 -30
- package/src/state/pane-table-export-registry.ts +50 -0
- package/src/theme/color-utils.ts +1 -1
- package/src/theme/colors.ts +50 -0
- package/src/theme/themes.test.ts +15 -2
- package/src/theme/themes.ts +19 -1
- package/src/tickers/search/index.test.ts +26 -0
- package/src/tickers/search/index.ts +42 -17
- package/src/tickers/search/ranking.ts +14 -0
- package/src/time-series/chart-data.test.ts +216 -0
- package/src/time-series/range.ts +12 -0
- package/src/time-series/resolution.test.ts +25 -26
- package/src/time-series/resolution.ts +44 -25
- package/src/time-series/resolve.ts +7 -0
- package/src/time-series/spec.ts +5 -5
- package/src/time-series/types.ts +3 -1
- package/src/time-series/use-chart-resolution.ts +5 -0
- package/src/types/config.ts +123 -3
- package/src/types/data-provider.ts +11 -0
- package/src/types/headless.ts +161 -0
- package/src/types/plugin.ts +104 -20
- package/src/ui/host.tsx +19 -1
- package/src/ui/index.tsx +1 -0
- package/src/ui/toast.tsx +8 -4
- package/src/utils/csv.test.ts +13 -0
- package/src/utils/csv.ts +37 -0
- package/src/utils/format.ts +35 -0
- package/src/utils/fuzzy-search.test.ts +94 -0
- package/src/utils/fuzzy-search.ts +81 -22
- package/src/utils/save-text-file.test.ts +24 -0
- package/src/utils/save-text-file.ts +36 -0
- package/src/utils/shortcut-labels.ts +14 -0
- package/src/components/chart/core/axis-format.ts +0 -157
- package/src/components/chart/core/index.test.ts +0 -1075
- package/src/components/chart/core/projection.ts +0 -357
- package/src/components/chart/core/render-utils.ts +0 -4
- package/src/components/chart/core/renderer.ts +0 -244
- package/src/components/chart/core/scene.ts +0 -180
- package/src/components/chart/core/time-axis.ts +0 -330
- package/src/components/chart/cursor-motion.ts +0 -47
- package/src/components/chart/indicators/types.ts +0 -26
- package/src/components/chart/market-session.ts +0 -176
- package/src/components/chart/multi-line/renderer.test.ts +0 -83
- package/src/components/chart/multi-line/renderer.ts +0 -161
- package/src/components/chart/multi-line/scene.ts +0 -188
- package/src/components/chart/native/raster/layout.ts +0 -129
- package/src/components/chart/native/raster/price-chart.ts +0 -244
- package/src/components/chart/rendering/chart-draw.ts +0 -270
- package/src/components/chart/rendering/geometry.ts +0 -95
- package/src/components/chart/rendering/pixel-buffer.ts +0 -267
- package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
- package/src/components/chart/scatter-chart-renderer.ts +0 -223
- package/src/components/chart/static/chart/surface.tsx +0 -358
- package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
- package/src/components/command-bar/routes/root/article-results.ts +0 -55
- package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
- package/src/plugins/builtin/news/wire/article-search.ts +0 -140
- package/src/plugins/builtin/substack/api/auth.ts +0 -185
- package/src/plugins/builtin/substack/api/cache.ts +0 -82
- package/src/plugins/builtin/substack/api/loaders.ts +0 -170
- package/src/plugins/builtin/substack/api/store.ts +0 -186
- package/src/plugins/builtin/substack/api/types.ts +0 -42
- package/src/plugins/builtin/substack/api.test.ts +0 -298
- package/src/plugins/builtin/substack/article-detail.tsx +0 -397
- package/src/plugins/builtin/substack/article-stack.tsx +0 -133
- package/src/plugins/builtin/substack/content.ts +0 -478
- package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
- package/src/plugins/builtin/substack/index.tsx +0 -49
- package/src/plugins/builtin/substack/login-view.tsx +0 -126
- package/src/plugins/builtin/substack/model.test.ts +0 -155
- package/src/plugins/builtin/substack/normalize.ts +0 -343
- package/src/plugins/builtin/substack/pane-footer.ts +0 -52
- package/src/plugins/builtin/substack/pane-state.ts +0 -170
- package/src/plugins/builtin/substack/pane.tsx +0 -547
- package/src/plugins/builtin/substack/read-state.ts +0 -30
- package/src/plugins/builtin/substack/table.ts +0 -111
- package/src/plugins/builtin/substack/types.ts +0 -71
- package/src/plugins/builtin/substack/utils.ts +0 -131
- package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
- package/src/plugins/ibkr/account-cache.test.ts +0 -102
- package/src/plugins/ibkr/account-cache.ts +0 -24
- package/src/plugins/ibkr/broker-adapter.ts +0 -286
- package/src/plugins/ibkr/config.test.ts +0 -79
- package/src/plugins/ibkr/config.ts +0 -250
- package/src/plugins/ibkr/dialogs.tsx +0 -40
- package/src/plugins/ibkr/flex/index.test.ts +0 -264
- package/src/plugins/ibkr/flex/index.ts +0 -367
- package/src/plugins/ibkr/flex/statement-client.ts +0 -235
- package/src/plugins/ibkr/fundamental-parser.ts +0 -149
- package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
- package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
- package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
- package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
- package/src/plugins/ibkr/gateway/connection.ts +0 -152
- package/src/plugins/ibkr/gateway/contracts.ts +0 -132
- package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
- package/src/plugins/ibkr/gateway/helpers.ts +0 -33
- package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
- package/src/plugins/ibkr/gateway/history.ts +0 -120
- package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
- package/src/plugins/ibkr/gateway/market-data.ts +0 -345
- package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
- package/src/plugins/ibkr/gateway/orders.ts +0 -63
- package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
- package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
- package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
- package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
- package/src/plugins/ibkr/gateway/selection.ts +0 -51
- package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
- package/src/plugins/ibkr/gateway/service/index.ts +0 -429
- package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
- package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
- package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
- package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
- package/src/plugins/ibkr/gateway/service/native.ts +0 -543
- package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
- package/src/plugins/ibkr/gateway/types.ts +0 -27
- package/src/plugins/ibkr/index.test.tsx +0 -162
- package/src/plugins/ibkr/index.tsx +0 -159
- package/src/plugins/ibkr/instance-selection.test.ts +0 -120
- package/src/plugins/ibkr/instance-selection.ts +0 -36
- package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
- package/src/plugins/ibkr/portfolio-performance.ts +0 -129
- package/src/plugins/ibkr/trade/badge.tsx +0 -28
- package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
- package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
- package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
- package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
- package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
- package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
- package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
- package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
- package/src/plugins/ibkr/trade/tab/model.ts +0 -159
- package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
- package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
- package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
- package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
- package/src/plugins/ibkr/trade/utils.test.ts +0 -175
- package/src/plugins/ibkr/trade/utils.ts +0 -144
- package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
- package/src/plugins/ibkr/trading/pane.tsx +0 -332
- package/src/plugins/ibkr/trading/state.test.ts +0 -109
- package/src/plugins/ibkr/trading/state.ts +0 -335
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { type CSSProperties } from "react";
|
|
4
4
|
import { Box } from "../../ui";
|
|
5
5
|
import { blendHex, colors } from "../../theme/colors";
|
|
6
|
+
import { WEB_CELL_HEIGHT } from "../../theme/font-scale";
|
|
6
7
|
|
|
7
8
|
export type NativeSelectElement = HTMLSelectElement & { showPicker?: () => void };
|
|
8
9
|
|
|
@@ -18,6 +19,11 @@ export interface NativeSelectProps {
|
|
|
18
19
|
options: NativeSelectOption[];
|
|
19
20
|
width?: number | string;
|
|
20
21
|
height?: number;
|
|
22
|
+
/**
|
|
23
|
+
* `field` is the settings-dialog control. `inline` drops the box so a dense
|
|
24
|
+
* pane row reads as text while staying a real, keyboard-accessible select.
|
|
25
|
+
*/
|
|
26
|
+
variant?: "field" | "inline";
|
|
21
27
|
includeUnsetOption?: boolean;
|
|
22
28
|
selectRef?: (element: NativeSelectElement | null) => void;
|
|
23
29
|
onFocus?: () => void;
|
|
@@ -41,23 +47,27 @@ export function openNativeSelect(element: NativeSelectElement | null | undefined
|
|
|
41
47
|
export function NativeSelect({
|
|
42
48
|
value,
|
|
43
49
|
options,
|
|
44
|
-
width
|
|
45
|
-
height
|
|
50
|
+
width,
|
|
51
|
+
height,
|
|
52
|
+
variant = "field",
|
|
46
53
|
includeUnsetOption = false,
|
|
47
54
|
selectRef,
|
|
48
55
|
onFocus,
|
|
49
56
|
onChange,
|
|
50
57
|
}: NativeSelectProps) {
|
|
58
|
+
const inline = variant === "inline";
|
|
51
59
|
const hasCurrentValue = options.some((option) => option.value === value);
|
|
60
|
+
const resolvedWidth = width ?? (inline ? "auto" : 184);
|
|
61
|
+
const resolvedHeight = height ?? (inline ? WEB_CELL_HEIGHT : 28);
|
|
52
62
|
const style: CSSProperties = {
|
|
53
|
-
width,
|
|
54
|
-
height,
|
|
63
|
+
width: resolvedWidth,
|
|
64
|
+
height: resolvedHeight,
|
|
55
65
|
color: colors.text,
|
|
56
|
-
backgroundColor: blendHex(colors.panel, colors.textBright, 0.06),
|
|
57
|
-
border: `1px solid ${colors.border}`,
|
|
58
|
-
borderRadius: 6,
|
|
59
|
-
padding: "0 8px",
|
|
60
|
-
boxShadow: `inset 0 1px 0 ${blendHex(colors.bg, colors.textBright, 0.05)}`,
|
|
66
|
+
backgroundColor: inline ? "transparent" : blendHex(colors.panel, colors.textBright, 0.06),
|
|
67
|
+
border: inline ? "none" : `1px solid ${colors.border}`,
|
|
68
|
+
borderRadius: inline ? 0 : 6,
|
|
69
|
+
padding: inline ? 0 : "0 8px",
|
|
70
|
+
boxShadow: inline ? "none" : `inset 0 1px 0 ${blendHex(colors.bg, colors.textBright, 0.05)}`,
|
|
61
71
|
cursor: "pointer",
|
|
62
72
|
font: "inherit",
|
|
63
73
|
letterSpacing: 0,
|
|
@@ -68,7 +78,7 @@ export function NativeSelect({
|
|
|
68
78
|
|
|
69
79
|
return (
|
|
70
80
|
<Box
|
|
71
|
-
height={`${
|
|
81
|
+
height={`${resolvedHeight}px`}
|
|
72
82
|
flexDirection="row"
|
|
73
83
|
alignItems="center"
|
|
74
84
|
onMouseDown={(event: any) => {
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { Box, Text, TextAttributes, useUiHost } from "../../ui";
|
|
3
|
+
import { type PromptContext, useDialog } from "../../ui/dialog";
|
|
4
|
+
import { useRemoteUiNode } from "../../remote/semantic-tree";
|
|
5
|
+
import { colors } from "../../theme/colors";
|
|
6
|
+
import { ChoiceDialog } from "./choice-dialog";
|
|
7
|
+
import { NativeSelect } from "./native-select";
|
|
8
|
+
|
|
9
|
+
export interface SelectButtonOption<T extends string = string> {
|
|
10
|
+
value: T;
|
|
11
|
+
label: string;
|
|
12
|
+
/** Trigger wording when the full label is too wide for a pane header. */
|
|
13
|
+
short?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SelectButtonProps<T extends string = string> {
|
|
19
|
+
/** Names the dimension, e.g. "Prem". Shown before the value on the trigger. */
|
|
20
|
+
label: string;
|
|
21
|
+
value: T;
|
|
22
|
+
options: readonly SelectButtonOption<T>[];
|
|
23
|
+
onChange: (value: T) => void;
|
|
24
|
+
/** Dialog title. Defaults to `label`. */
|
|
25
|
+
title?: string;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Draws the value as active. Panes pass `value !== default` so a row of
|
|
29
|
+
* selects shows at a glance which ones are narrowing what you are looking at.
|
|
30
|
+
*/
|
|
31
|
+
emphasized?: boolean;
|
|
32
|
+
idPrefix?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Single-select control usable inside a pane body, not just the settings dialog.
|
|
37
|
+
* Desktop gets a real `<select>`; the terminal has no dropdown primitive, so it
|
|
38
|
+
* opens the standard choice dialog, which shows the full option labels instead
|
|
39
|
+
* of making people click a chip repeatedly to discover what the values are.
|
|
40
|
+
*/
|
|
41
|
+
export function SelectButton<T extends string = string>({
|
|
42
|
+
label,
|
|
43
|
+
value,
|
|
44
|
+
options,
|
|
45
|
+
onChange,
|
|
46
|
+
title,
|
|
47
|
+
disabled = false,
|
|
48
|
+
emphasized = false,
|
|
49
|
+
idPrefix,
|
|
50
|
+
}: SelectButtonProps<T>) {
|
|
51
|
+
const isDesktopWeb = useUiHost().kind === "desktop-web";
|
|
52
|
+
const dialog = useDialog();
|
|
53
|
+
const current = options.find((option) => option.value === value);
|
|
54
|
+
|
|
55
|
+
const selectValue = useCallback((next: unknown) => {
|
|
56
|
+
const candidate = typeof next === "string" ? next : (next as { value?: string })?.value;
|
|
57
|
+
const match = options.find((option) => option.value === candidate);
|
|
58
|
+
if (!match || match.disabled || match.value === value) return;
|
|
59
|
+
onChange(match.value);
|
|
60
|
+
}, [onChange, options, value]);
|
|
61
|
+
|
|
62
|
+
// Same parity as Button and Tabs: the control has to be drivable without a
|
|
63
|
+
// mouse for remote control and UI automation.
|
|
64
|
+
useRemoteUiNode({
|
|
65
|
+
role: "select",
|
|
66
|
+
label,
|
|
67
|
+
disabled,
|
|
68
|
+
actions: { select: selectValue },
|
|
69
|
+
metadata: {
|
|
70
|
+
value,
|
|
71
|
+
options: options.map((option) => ({ value: option.value, label: option.label })),
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const openDialog = useCallback((event?: { stopPropagation?: () => void; preventDefault?: () => void }) => {
|
|
76
|
+
event?.stopPropagation?.();
|
|
77
|
+
event?.preventDefault?.();
|
|
78
|
+
if (disabled) return;
|
|
79
|
+
void dialog.prompt<string>({
|
|
80
|
+
closeOnClickOutside: true,
|
|
81
|
+
content: (context: PromptContext<string>) => (
|
|
82
|
+
<ChoiceDialog
|
|
83
|
+
{...context}
|
|
84
|
+
title={title ?? label}
|
|
85
|
+
selectedChoiceId={value}
|
|
86
|
+
choices={options.map((option) => ({
|
|
87
|
+
id: option.value,
|
|
88
|
+
label: option.label,
|
|
89
|
+
description: option.description,
|
|
90
|
+
disabled: option.disabled,
|
|
91
|
+
}))}
|
|
92
|
+
/>
|
|
93
|
+
),
|
|
94
|
+
}).then((next) => {
|
|
95
|
+
if (!next) return;
|
|
96
|
+
const match = options.find((option) => option.value === next);
|
|
97
|
+
if (match && !match.disabled) onChange(match.value);
|
|
98
|
+
}).catch(() => {});
|
|
99
|
+
}, [dialog, disabled, label, onChange, options, title, value]);
|
|
100
|
+
|
|
101
|
+
if (isDesktopWeb) {
|
|
102
|
+
return (
|
|
103
|
+
<Box flexDirection="row" alignItems="center" gap={1} id={idPrefix ? `${idPrefix}:select` : undefined}>
|
|
104
|
+
<Text fg={colors.textMuted}>{label}</Text>
|
|
105
|
+
<NativeSelect
|
|
106
|
+
variant="inline"
|
|
107
|
+
value={value}
|
|
108
|
+
options={options.map((option) => ({
|
|
109
|
+
value: option.value,
|
|
110
|
+
label: option.label,
|
|
111
|
+
disabled: option.disabled,
|
|
112
|
+
}))}
|
|
113
|
+
onChange={(next) => {
|
|
114
|
+
if (next !== value) onChange(next as T);
|
|
115
|
+
}}
|
|
116
|
+
/>
|
|
117
|
+
</Box>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<Box
|
|
123
|
+
id={idPrefix ? `${idPrefix}:select` : undefined}
|
|
124
|
+
height={1}
|
|
125
|
+
flexDirection="row"
|
|
126
|
+
onMouseDown={openDialog}
|
|
127
|
+
>
|
|
128
|
+
<Text fg={colors.textMuted} onMouseDown={openDialog}>{`${label} `}</Text>
|
|
129
|
+
<Text
|
|
130
|
+
fg={disabled ? colors.textMuted : emphasized ? colors.textBright : colors.text}
|
|
131
|
+
attributes={emphasized ? TextAttributes.BOLD : undefined}
|
|
132
|
+
onMouseDown={openDialog}
|
|
133
|
+
>
|
|
134
|
+
{current?.short ?? current?.label ?? ""}
|
|
135
|
+
</Text>
|
|
136
|
+
</Box>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
@@ -70,7 +70,7 @@ export function PaneStatusBody({
|
|
|
70
70
|
}: PaneStatusBodyProps) {
|
|
71
71
|
if (error) {
|
|
72
72
|
return (
|
|
73
|
-
<Box paddingX={1} paddingY={1}>
|
|
73
|
+
<Box paddingX={1} paddingY={1} data-gloom-status="error">
|
|
74
74
|
<EmptyState
|
|
75
75
|
title={subject ? unavailableText(subject) : error}
|
|
76
76
|
message={subject ? error : undefined}
|
|
@@ -89,7 +89,7 @@ export function PaneStatusBody({
|
|
|
89
89
|
}
|
|
90
90
|
if (empty) {
|
|
91
91
|
return (
|
|
92
|
-
<Box paddingX={1} paddingY={1}>
|
|
92
|
+
<Box paddingX={1} paddingY={1} data-gloom-status="empty">
|
|
93
93
|
<EmptyState
|
|
94
94
|
title={emptyTitle ?? t("Nothing to show yet.")}
|
|
95
95
|
message={emptyMessage}
|
|
@@ -177,7 +177,7 @@ export function useMeasuredTableContentWidth(
|
|
|
177
177
|
queueMicrotask(measureContentWidth);
|
|
178
178
|
}, [measureContentWidth]);
|
|
179
179
|
|
|
180
|
-
useEffect(scheduleContentWidthMeasure);
|
|
180
|
+
useEffect(scheduleContentWidthMeasure, [scheduleContentWidthMeasure]);
|
|
181
181
|
|
|
182
182
|
return {
|
|
183
183
|
contentWidth: Math.max(tableWidth, viewportWidth),
|
|
@@ -16,21 +16,25 @@ export function useDoubleClickActivation<T>({
|
|
|
16
16
|
onActivate?: (value: T) => void;
|
|
17
17
|
}) {
|
|
18
18
|
const lastClickRef = useRef<LastClickState | null>(null);
|
|
19
|
+
const onSelectRef = useRef(onSelect);
|
|
20
|
+
const onActivateRef = useRef(onActivate);
|
|
21
|
+
onSelectRef.current = onSelect;
|
|
22
|
+
onActivateRef.current = onActivate;
|
|
19
23
|
|
|
20
24
|
return useCallback((targetKey: string, value: T, event?: ClickEventLike) => {
|
|
21
25
|
const lastClick = lastClickRef.current;
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
onSelectRef.current?.(value);
|
|
24
28
|
|
|
25
29
|
if ((typeof event?.detail === "number" && event.detail >= 2)
|
|
26
30
|
|| (lastClick && lastClick.targetKey === targetKey)) {
|
|
27
31
|
lastClickRef.current = null;
|
|
28
|
-
|
|
32
|
+
onActivateRef.current?.(value);
|
|
29
33
|
return;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
lastClickRef.current = {
|
|
33
37
|
targetKey,
|
|
34
38
|
};
|
|
35
|
-
}, [
|
|
39
|
+
}, []);
|
|
36
40
|
}
|
package/src/core/app-services.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { assetDataProvider, newsProvider } from "../capabilities";
|
|
|
14
14
|
import type { DataProvider } from "../types/data-provider";
|
|
15
15
|
import { debugLog } from "../utils/debug-log";
|
|
16
16
|
import { measurePerf, measurePerfAsync } from "../utils/perf-marks";
|
|
17
|
-
import {
|
|
17
|
+
import { setPluginResourceStore } from "../public/broker";
|
|
18
18
|
import type { AppRuntimeServices, AppServicesFactoryOptions } from "./app-service-ports";
|
|
19
19
|
|
|
20
20
|
const servicesLog = debugLog.createLogger("services");
|
|
@@ -41,7 +41,7 @@ export function createAppServices({
|
|
|
41
41
|
});
|
|
42
42
|
const dbPath = join(config.dataDir, ".gloomberb-cache.db");
|
|
43
43
|
const persistence = measurePerf("startup.services.persistence", () => new AppPersistence(dbPath));
|
|
44
|
-
|
|
44
|
+
setPluginResourceStore(persistence.resources);
|
|
45
45
|
const tickerRepository = measurePerf("startup.services.ticker-repository", () => new TickerRepository(persistence.tickers));
|
|
46
46
|
const disposeCloudConnectionSources = registerGloomCloudConnectionSources(connectionHealth);
|
|
47
47
|
const providerRouter = measurePerf("startup.services.asset-data-router", () => (
|
|
@@ -105,7 +105,7 @@ export function createAppServices({
|
|
|
105
105
|
newsService.stop();
|
|
106
106
|
pluginRegistry.destroy();
|
|
107
107
|
disposeCloudConnectionSources();
|
|
108
|
-
|
|
108
|
+
setPluginResourceStore(null);
|
|
109
109
|
persistence.close();
|
|
110
110
|
},
|
|
111
111
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { CHART_RESOLUTIONS, TIME_RANGES, type ChartResolution, type TimeRange } from "../../time-series/range";
|
|
2
2
|
import type { LayoutConfig, PaneBinding } from "../../types/config";
|
|
3
3
|
import type {
|
|
4
4
|
ChartPanelSpec,
|
|
@@ -16,8 +16,8 @@ import {
|
|
|
16
16
|
validateChartSpec,
|
|
17
17
|
} from "../../time-series/spec";
|
|
18
18
|
|
|
19
|
-
const CHART_RANGES = new Set<TimeRange>(
|
|
20
|
-
const
|
|
19
|
+
const CHART_RANGES = new Set<TimeRange>(TIME_RANGES);
|
|
20
|
+
const CHART_RESOLUTION_SET = new Set<ChartResolution>(CHART_RESOLUTIONS);
|
|
21
21
|
const PRICE_RENDER_MODES = new Set<SeriesStyle>(["area", "line", "candles", "ohlc", "hlc"]);
|
|
22
22
|
const LEGACY_INDICATOR_IDS = new Set<LegacyChartIndicatorId>([
|
|
23
23
|
"volume",
|
|
@@ -94,7 +94,7 @@ function range(value: unknown, fallback: TimeRange): TimeRange {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
function resolution(value: unknown, fallback: ChartResolution): ChartResolution {
|
|
97
|
-
return
|
|
97
|
+
return CHART_RESOLUTION_SET.has(value as ChartResolution) ? value as ChartResolution : fallback;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
function makeSpec(
|
|
@@ -10,8 +10,10 @@ import {
|
|
|
10
10
|
cloneLayout,
|
|
11
11
|
clonePaneSettings,
|
|
12
12
|
createPaneInstanceId,
|
|
13
|
+
getPlacedPaneInstanceIds,
|
|
13
14
|
normalizePaneLayout,
|
|
14
15
|
normalizePaneId,
|
|
16
|
+
removeUnreachablePaneInstances,
|
|
15
17
|
} from "../../types/config";
|
|
16
18
|
import {
|
|
17
19
|
migrateChartPaneSettings,
|
|
@@ -155,11 +157,14 @@ function sanitizePaneInstances(
|
|
|
155
157
|
placementMemory: sanitizePlacementMemory(entry.placementMemory),
|
|
156
158
|
};
|
|
157
159
|
});
|
|
158
|
-
return instances
|
|
160
|
+
return instances;
|
|
159
161
|
}
|
|
160
162
|
|
|
161
|
-
function getDefaultFollowSourceInstanceId(
|
|
162
|
-
|
|
163
|
+
function getDefaultFollowSourceInstanceId(layout: LayoutConfig): string | null {
|
|
164
|
+
const placedPaneIds = new Set(getPlacedPaneInstanceIds(layout));
|
|
165
|
+
return layout.instances.find((instance) => (
|
|
166
|
+
instance.paneId === "portfolio-list" && placedPaneIds.has(instance.instanceId)
|
|
167
|
+
))?.instanceId ?? null;
|
|
163
168
|
}
|
|
164
169
|
|
|
165
170
|
function sanitizeFloatingEntries(value: unknown, validInstanceIds: Set<string>): LayoutConfig["floating"] {
|
|
@@ -219,7 +224,7 @@ export function sanitizeLayout(
|
|
|
219
224
|
if (!Array.isArray((value as LayoutConfig & { instances?: unknown }).instances)) {
|
|
220
225
|
const layout = cloneLayout(fallback);
|
|
221
226
|
return normalizePaneLayout(layout, {
|
|
222
|
-
defaultFollowSourceInstanceId: getDefaultFollowSourceInstanceId(layout
|
|
227
|
+
defaultFollowSourceInstanceId: getDefaultFollowSourceInstanceId(layout),
|
|
223
228
|
resolveOrphanSymbol: () => null,
|
|
224
229
|
});
|
|
225
230
|
}
|
|
@@ -241,8 +246,9 @@ export function sanitizeLayout(
|
|
|
241
246
|
detached,
|
|
242
247
|
};
|
|
243
248
|
|
|
244
|
-
|
|
245
|
-
defaultFollowSourceInstanceId: getDefaultFollowSourceInstanceId(layout
|
|
249
|
+
const normalized = normalizePaneLayout(layout, {
|
|
250
|
+
defaultFollowSourceInstanceId: getDefaultFollowSourceInstanceId(layout),
|
|
246
251
|
resolveOrphanSymbol: () => null,
|
|
247
252
|
});
|
|
253
|
+
return removeUnreachablePaneInstances(normalized);
|
|
248
254
|
}
|
|
@@ -49,6 +49,50 @@ async function writeConfigJson(dataDir: string, config: Record<string, unknown>)
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
describe("sanitizeLayout", () => {
|
|
52
|
+
test("preserves an intentionally blank layout", () => {
|
|
53
|
+
expect(sanitizeLayout({
|
|
54
|
+
dockRoot: null,
|
|
55
|
+
instances: [],
|
|
56
|
+
floating: [],
|
|
57
|
+
detached: [],
|
|
58
|
+
}, DEFAULT_LAYOUT)).toEqual({
|
|
59
|
+
dockRoot: null,
|
|
60
|
+
instances: [],
|
|
61
|
+
floating: [],
|
|
62
|
+
detached: [],
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("prunes abandoned panes while retaining a hidden follow source", () => {
|
|
67
|
+
const layout = sanitizeLayout({
|
|
68
|
+
dockRoot: { kind: "pane", instanceId: "ticker-research:visible" },
|
|
69
|
+
instances: [
|
|
70
|
+
{
|
|
71
|
+
instanceId: "portfolio-list:source",
|
|
72
|
+
paneId: "portfolio-list",
|
|
73
|
+
binding: { kind: "none" },
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
instanceId: "ticker-research:visible",
|
|
77
|
+
paneId: "ticker-research",
|
|
78
|
+
binding: { kind: "follow", sourceInstanceId: "portfolio-list:source" },
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
instanceId: "ticker-research:closed",
|
|
82
|
+
paneId: "ticker-research",
|
|
83
|
+
binding: { kind: "fixed", symbol: "NVDA" },
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
floating: [],
|
|
87
|
+
detached: [],
|
|
88
|
+
}, DEFAULT_LAYOUT);
|
|
89
|
+
|
|
90
|
+
expect(layout.instances.map((instance) => instance.instanceId)).toEqual([
|
|
91
|
+
"portfolio-list:source",
|
|
92
|
+
"ticker-research:visible",
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
|
|
52
96
|
test("ships a focused Home layout with portfolio, chat, and following research", () => {
|
|
53
97
|
expect(DEFAULT_LAYOUT.instances.map((instance) => instance.instanceId)).toEqual([
|
|
54
98
|
"portfolio-list:main",
|
|
@@ -318,6 +362,59 @@ describe("sanitizeLayout", () => {
|
|
|
318
362
|
});
|
|
319
363
|
|
|
320
364
|
describe("loadConfig", () => {
|
|
365
|
+
test("migrates unreachable pane instances and their saved state", async () => {
|
|
366
|
+
const dataDir = await createTempConfigDir();
|
|
367
|
+
const hiddenPaneId = "ticker-research:closed";
|
|
368
|
+
const legacyLayout = {
|
|
369
|
+
...DEFAULT_LAYOUT,
|
|
370
|
+
instances: [
|
|
371
|
+
...DEFAULT_LAYOUT.instances.map((instance) => instance.instanceId === "chat:main"
|
|
372
|
+
? {
|
|
373
|
+
...instance,
|
|
374
|
+
placementMemory: {
|
|
375
|
+
docked: {
|
|
376
|
+
anchorInstanceId: hiddenPaneId,
|
|
377
|
+
position: "right" as const,
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
}
|
|
381
|
+
: instance),
|
|
382
|
+
{
|
|
383
|
+
instanceId: hiddenPaneId,
|
|
384
|
+
paneId: "ticker-research",
|
|
385
|
+
binding: { kind: "fixed" as const, symbol: "NVDA" },
|
|
386
|
+
placementMemory: { floating: { x: 12, y: 4, width: 90, height: 30 } },
|
|
387
|
+
},
|
|
388
|
+
],
|
|
389
|
+
};
|
|
390
|
+
await writeConfigJson(dataDir, createSavedConfig({
|
|
391
|
+
configVersion: 21,
|
|
392
|
+
layout: legacyLayout,
|
|
393
|
+
layouts: [{
|
|
394
|
+
name: "Default",
|
|
395
|
+
layout: legacyLayout,
|
|
396
|
+
paneState: {
|
|
397
|
+
"chat:main": { draft: "keep" },
|
|
398
|
+
[hiddenPaneId]: { activeTabId: "overview" },
|
|
399
|
+
},
|
|
400
|
+
focusedPaneId: hiddenPaneId,
|
|
401
|
+
}],
|
|
402
|
+
}));
|
|
403
|
+
|
|
404
|
+
const config = await loadConfig(dataDir);
|
|
405
|
+
|
|
406
|
+
expect(config.configVersion).toBe(CURRENT_CONFIG_VERSION);
|
|
407
|
+
expect(config.layout.instances.some((instance) => instance.instanceId === hiddenPaneId)).toBe(false);
|
|
408
|
+
expect(config.layouts[0]?.layout.instances.some((instance) => instance.instanceId === hiddenPaneId)).toBe(false);
|
|
409
|
+
expect(config.layouts[0]?.paneState).toEqual({ "chat:main": { draft: "keep" } });
|
|
410
|
+
expect(config.layouts[0]?.focusedPaneId).toBeNull();
|
|
411
|
+
expect(findPaneInstance(config.layout, "chat:main")?.placementMemory?.docked).toEqual({
|
|
412
|
+
anchorInstanceId: undefined,
|
|
413
|
+
path: undefined,
|
|
414
|
+
position: "right",
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
321
418
|
test("folds saved graph plugin state into composer specs and removes only chart-owned state", async () => {
|
|
322
419
|
const dataDir = await createTempConfigDir();
|
|
323
420
|
const legacyLayout = {
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
CURRENT_CONFIG_VERSION,
|
|
5
5
|
DEFAULT_COLUMNS,
|
|
6
6
|
DEFAULT_PORTFOLIO_COLUMN_IDS,
|
|
7
|
+
getPlacedPaneInstanceIds,
|
|
7
8
|
type LayoutConfig,
|
|
8
9
|
} from "../../../types/config";
|
|
9
10
|
import {
|
|
@@ -24,6 +25,7 @@ const CLOUD_MACRO_SPLIT_CONFIG_VERSION = 15;
|
|
|
24
25
|
const PORTFOLIO_DEFAULT_COLUMNS_CONFIG_VERSION = 17;
|
|
25
26
|
const BUILTIN_OWNERSHIP_AND_CHART_CONFIG_VERSION = 20;
|
|
26
27
|
const ONBOARDING_BACKFILL_CONFIG_VERSION = 21;
|
|
28
|
+
const UNREACHABLE_PANE_CLEANUP_CONFIG_VERSION = 22;
|
|
27
29
|
|
|
28
30
|
const LEGACY_MAIN_PORTFOLIO_COLUMN_IDS = DEFAULT_COLUMNS.map((column) => column.id);
|
|
29
31
|
const PRE_SPARKLINE_PORTFOLIO_COLUMN_IDS = [
|
|
@@ -85,6 +87,11 @@ const CONFIG_MIGRATIONS: readonly ConfigMigration[] = [
|
|
|
85
87
|
toVersion: ONBOARDING_BACKFILL_CONFIG_VERSION,
|
|
86
88
|
migrate: migrateOnboardingComplete,
|
|
87
89
|
},
|
|
90
|
+
{
|
|
91
|
+
name: "prune-unreachable-pane-instances",
|
|
92
|
+
toVersion: UNREACHABLE_PANE_CLEANUP_CONFIG_VERSION,
|
|
93
|
+
migrate: migrateUnreachablePaneInstances,
|
|
94
|
+
},
|
|
88
95
|
];
|
|
89
96
|
|
|
90
97
|
export function migrateSavedConfig(saved: Record<string, unknown>, dataDir: string): ConfigMigrationResult {
|
|
@@ -139,6 +146,40 @@ function migrateOnboardingComplete(saved: Record<string, unknown>): Record<strin
|
|
|
139
146
|
};
|
|
140
147
|
}
|
|
141
148
|
|
|
149
|
+
function migrateUnreachablePaneInstances(
|
|
150
|
+
saved: Record<string, unknown>,
|
|
151
|
+
dataDir: string,
|
|
152
|
+
): Record<string, unknown> {
|
|
153
|
+
const defaults = createDefaultConfig(dataDir);
|
|
154
|
+
const layout = sanitizeLayout(saved.layout, defaults.layout);
|
|
155
|
+
const layouts = Array.isArray(saved.layouts)
|
|
156
|
+
? saved.layouts.map((entry) => {
|
|
157
|
+
if (!isPlainRecord(entry) || typeof entry.name !== "string") return entry;
|
|
158
|
+
const entryLayout = sanitizeLayout(entry.layout, layout);
|
|
159
|
+
const instanceIds = new Set(entryLayout.instances.map((instance) => instance.instanceId));
|
|
160
|
+
const placedIds = new Set(getPlacedPaneInstanceIds(entryLayout));
|
|
161
|
+
const paneState = isPlainRecord(entry.paneState)
|
|
162
|
+
? Object.fromEntries(Object.entries(entry.paneState).filter(([instanceId]) => instanceIds.has(instanceId)))
|
|
163
|
+
: entry.paneState;
|
|
164
|
+
const focusedPaneId = typeof entry.focusedPaneId === "string" && !placedIds.has(entry.focusedPaneId)
|
|
165
|
+
? null
|
|
166
|
+
: entry.focusedPaneId;
|
|
167
|
+
return {
|
|
168
|
+
...entry,
|
|
169
|
+
layout: entryLayout,
|
|
170
|
+
paneState,
|
|
171
|
+
focusedPaneId,
|
|
172
|
+
};
|
|
173
|
+
})
|
|
174
|
+
: saved.layouts;
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
...saved,
|
|
178
|
+
layout,
|
|
179
|
+
layouts,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
142
183
|
function migrateCloudDefault(saved: Record<string, unknown>): Record<string, unknown> {
|
|
143
184
|
return {
|
|
144
185
|
...saved,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
cloneLayout,
|
|
11
11
|
createDefaultConfig,
|
|
12
12
|
CURRENT_CONFIG_VERSION,
|
|
13
|
+
getPlacedPaneInstanceIds,
|
|
13
14
|
} from "../../../types/config";
|
|
14
15
|
import type { Portfolio, Watchlist } from "../../../types/ticker";
|
|
15
16
|
import { isLanguagePreference } from "../../../i18n/languages";
|
|
@@ -305,15 +306,16 @@ function sanitizeSavedLayouts(
|
|
|
305
306
|
)
|
|
306
307
|
.map((entry) => {
|
|
307
308
|
const layout = sanitizeLayout(entry.layout, fallbackLayout);
|
|
309
|
+
const placedPaneIds = new Set(getPlacedPaneInstanceIds(layout));
|
|
308
310
|
const paneState = sanitizeSavedPaneState((entry as { paneState?: unknown }).paneState, layout);
|
|
309
311
|
return {
|
|
310
312
|
id: typeof entry.id === "string" ? entry.id : undefined,
|
|
311
313
|
name: entry.name,
|
|
312
314
|
layout,
|
|
313
315
|
paneState,
|
|
314
|
-
focusedPaneId: typeof entry.focusedPaneId === "string"
|
|
315
|
-
? entry.focusedPaneId
|
|
316
|
-
: undefined,
|
|
316
|
+
focusedPaneId: typeof entry.focusedPaneId === "string"
|
|
317
|
+
? placedPaneIds.has(entry.focusedPaneId) ? entry.focusedPaneId : null
|
|
318
|
+
: entry.focusedPaneId === null ? null : undefined,
|
|
317
319
|
activePanel: entry.activePanel === "right" || entry.activePanel === "left"
|
|
318
320
|
? entry.activePanel
|
|
319
321
|
: undefined,
|
package/src/i18n/es.ts
CHANGED
|
@@ -572,6 +572,7 @@ export const es: Record<string, string> = {
|
|
|
572
572
|
"Open the help window": "Abrir la ventana de ayuda",
|
|
573
573
|
"Set up a portfolio": "Configura una cartera",
|
|
574
574
|
"How would you like to get started?": "¿Cómo quieres empezar?",
|
|
575
|
+
"{shortcut} to get started.": "{shortcut} para empezar.",
|
|
575
576
|
"Edits later from the command bar.": "Se edita luego desde la barra de comandos.",
|
|
576
577
|
"Create Manual Portfolio": "Crear cartera manual",
|
|
577
578
|
"Add tickers and positions by hand": "Agrega tickers y posiciones a mano",
|
|
@@ -760,7 +761,7 @@ export const es: Record<string, string> = {
|
|
|
760
761
|
"Verify your email to send messages.": "Verifica tu correo para enviar mensajes.",
|
|
761
762
|
"Verify your email to use Cloud tweets.": "Verifica tu correo para usar Cloud tweets.",
|
|
762
763
|
"Resend Verification Email": "Reenviar correo de verificación",
|
|
763
|
-
"
|
|
764
|
+
"{shortcut}, then Resend Verification Email": "{shortcut} y luego Reenviar correo de verificación",
|
|
764
765
|
"editing": "editando",
|
|
765
766
|
"replying to": "respondiendo a",
|
|
766
767
|
"Reply": "Responder",
|
|
@@ -998,7 +999,7 @@ export const es: Record<string, string> = {
|
|
|
998
999
|
"No watchlist tickers": "Sin tickers en seguimiento",
|
|
999
1000
|
"No tickers in {name}.": "No hay tickers en {name}.",
|
|
1000
1001
|
"this collection": "esta colección",
|
|
1001
|
-
"Press
|
|
1002
|
+
"Press {shortcut} to add one.": "Pulsa {shortcut} para agregar uno.",
|
|
1002
1003
|
"PRE-MKT": "PRE-MKT",
|
|
1003
1004
|
"OPEN": "ABIERTO",
|
|
1004
1005
|
"AFTER-HRS": "POST-MKT",
|
|
@@ -1092,6 +1093,9 @@ export const es: Record<string, string> = {
|
|
|
1092
1093
|
"Approve from your phone, no typing": "Aprueba desde tu teléfono, sin escribir",
|
|
1093
1094
|
"Scan with the Gloom app": "Escanea con la app Gloom",
|
|
1094
1095
|
"Open the Gloom app on your phone and approve this workspace.": "Abre la app Gloom en tu teléfono y aprueba este espacio de trabajo.",
|
|
1096
|
+
"Approve the code from a phone that is already signed in.": "Aprueba el código desde un teléfono que ya tenga la sesión iniciada.",
|
|
1097
|
+
"Layouts, watchlists and portfolios sync to your account.": "Los diseños, las listas de seguimiento y las carteras se sincronizan con tu cuenta.",
|
|
1098
|
+
"Use email instead": "Usar el correo",
|
|
1095
1099
|
"Scan with the Gloom app to sign in": "Escanea con la app Gloom para iniciar sesión",
|
|
1096
1100
|
"Waiting for approval...": "Esperando aprobación...",
|
|
1097
1101
|
"Approved as {email}": "Aprobado como {email}",
|
package/src/i18n/ja.ts
CHANGED
|
@@ -573,6 +573,7 @@ export const ja: Record<string, string> = {
|
|
|
573
573
|
"Open the help window": "ヘルプ ウィンドウを開く",
|
|
574
574
|
"Set up a portfolio": "ポートフォリオを設定する",
|
|
575
575
|
"How would you like to get started?": "どの方法で始めますか?",
|
|
576
|
+
"{shortcut} to get started.": "{shortcut} で開始します。",
|
|
576
577
|
"Edits later from the command bar.": "後でコマンド バーから編集します。",
|
|
577
578
|
"Create Manual Portfolio": "手動ポートフォリオを作成",
|
|
578
579
|
"Add tickers and positions by hand": "銘柄とポジションを手動で追加",
|
|
@@ -775,7 +776,7 @@ export const ja: Record<string, string> = {
|
|
|
775
776
|
"Verify your email to send messages.": "メッセージを送信するにはメールを確認してください。",
|
|
776
777
|
"Verify your email to use Cloud tweets.": "Cloud tweets を使用するにはメールを確認してください。",
|
|
777
778
|
"Resend Verification Email": "確認メールを再送信",
|
|
778
|
-
"
|
|
779
|
+
"{shortcut}, then Resend Verification Email": "{shortcut} から「確認メールを再送信」",
|
|
779
780
|
"editing": "編集",
|
|
780
781
|
"replying to": "返信先",
|
|
781
782
|
"Reply": "返信",
|
|
@@ -1013,7 +1014,7 @@ export const ja: Record<string, string> = {
|
|
|
1013
1014
|
"No watchlist tickers": "ウォッチリストに銘柄がありません",
|
|
1014
1015
|
"No tickers in {name}.": "{name} に銘柄がありません。",
|
|
1015
1016
|
"this collection": "このコレクション",
|
|
1016
|
-
"Press
|
|
1017
|
+
"Press {shortcut} to add one.": "{shortcut} を押して追加します。",
|
|
1017
1018
|
"PRE-MKT": "PRE-MKT",
|
|
1018
1019
|
"OPEN": "取引中",
|
|
1019
1020
|
"AFTER-HRS": "時間外",
|
|
@@ -1079,6 +1080,9 @@ export const ja: Record<string, string> = {
|
|
|
1079
1080
|
"Approve from your phone, no typing": "スマホから承認、入力不要",
|
|
1080
1081
|
"Scan with the Gloom app": "Gloom アプリでスキャン",
|
|
1081
1082
|
"Open the Gloom app on your phone and approve this workspace.": "スマホで Gloom アプリを開き、このワークスペースを承認してください。",
|
|
1083
|
+
"Approve the code from a phone that is already signed in.": "すでにログイン済みのスマートフォンでコードを承認してください。",
|
|
1084
|
+
"Layouts, watchlists and portfolios sync to your account.": "レイアウト、ウォッチリスト、ポートフォリオがアカウントに同期されます。",
|
|
1085
|
+
"Use email instead": "メールを使う",
|
|
1082
1086
|
"Scan with the Gloom app to sign in": "Gloom アプリでスキャンしてログイン",
|
|
1083
1087
|
"Waiting for approval...": "承認を待っています...",
|
|
1084
1088
|
"Approved as {email}": "{email} として承認されました",
|