gloomberb 0.10.0 → 0.10.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/package.json +8 -1
- package/src/api-client/auth.ts +1 -0
- package/src/api-client/index.test.ts +359 -108
- package/src/api-client/index.ts +24 -13
- package/src/api-client/socket.ts +70 -35
- package/src/api-client/types.ts +1 -1
- package/src/app/language-observer.test.tsx +82 -0
- package/src/app/language-observer.tsx +13 -0
- package/src/app/pane-runtime/index.tsx +2 -2
- package/src/app/pane-runtime/pane-settings-runtime.tsx +2 -2
- package/src/app/pane-runtime/pane-template-runtime.ts +2 -2
- package/src/app/pane-runtime/plugin-bindings.ts +2 -2
- package/src/app/pane-runtime/ticker-open-runtime.ts +2 -2
- package/src/app/runtime/broker-import.ts +2 -2
- package/src/app/runtime/plugin-bindings.ts +2 -2
- package/src/app/runtime/startup.ts +2 -2
- package/src/app.tsx +16 -7
- package/src/brokers/account-cache.ts +6 -6
- package/src/brokers/broker-ticker-sync.ts +3 -3
- package/src/brokers/remote-broker-adapter.ts +2 -2
- package/src/brokers/sync-broker-instance.ts +5 -6
- package/src/cli/commands/api.ts +3 -6
- package/src/cli/commands/automation.ts +7 -15
- package/src/cli/commands/market.ts +26 -61
- package/src/cli/commands/overview.ts +34 -45
- package/src/cli/commands/search.ts +25 -27
- package/src/cli/commands/system.ts +27 -57
- package/src/cli/commands/ticker.ts +62 -63
- package/src/cli/context.test.ts +63 -0
- package/src/cli/context.ts +15 -1
- package/src/cli/index.ts +1 -1
- package/src/cli/pane-functions/data.ts +2 -19
- package/src/cli/pane-functions/index.ts +26 -25
- package/src/cli/pane-functions/report.ts +1 -1
- package/src/cli/pane-functions/screenshot.ts +3 -3
- package/src/cli/scoped-context.ts +41 -0
- package/src/components/chart/composite/composite-chart.test.tsx +615 -18
- package/src/components/chart/composite/composite-chart.tsx +179 -79
- package/src/components/chart/composite/format.test.ts +20 -7
- package/src/components/chart/composite/index.ts +0 -21
- package/src/components/chart/composite/interactions.test.ts +90 -3
- package/src/components/chart/composite/interactions.ts +189 -24
- package/src/components/chart/composite/price-series.ts +0 -2
- package/src/components/chart/composite/rasterizer.ts +10 -9
- package/src/components/chart/composite/renderers.test.ts +108 -2
- package/src/components/chart/composite/scene.test.ts +36 -0
- package/src/components/chart/composite/scene.ts +5 -2
- package/src/components/chart/composite/text-renderer.ts +37 -25
- package/src/components/chart/composite/time-axis.test.ts +243 -0
- package/src/components/chart/composite/time-axis.ts +608 -0
- package/src/components/chart/composite/types.ts +12 -1
- package/src/components/chart/core/data.ts +2 -159
- package/src/components/chart/core/date-window.ts +6 -225
- package/src/components/chart/core/palette.ts +1 -12
- package/src/components/chart/core/pointer.ts +0 -142
- package/src/components/chart/core/types.ts +1 -58
- package/src/components/chart/cursor-motion.ts +2 -4
- package/src/components/chart/indicators/types.ts +0 -9
- package/src/components/chart/market-session.ts +0 -11
- package/src/components/chart/native/kitty/protocol.ts +1 -1
- package/src/components/chart/native/raster/layout.ts +3 -10
- package/src/components/chart/static/chart/axis-overlays.test.tsx +64 -0
- package/src/components/chart/static/chart/axis-overlays.tsx +59 -14
- package/src/components/chart/static/chart/surface.tsx +1 -2
- package/src/components/command-bar/action-runtime.ts +2 -2
- package/src/components/command-bar/commands/direct/index.ts +1 -1
- package/src/components/command-bar/panel/body.tsx +2 -2
- package/src/components/command-bar/panel/index.tsx +11 -9
- package/src/components/command-bar/routes/ticker-search/actions.ts +2 -2
- package/src/components/command-bar/surface/index.tsx +2 -2
- package/src/components/command-bar/workflow/body.tsx +3 -3
- package/src/components/command-bar/workflow/collection-actions.ts +2 -2
- package/src/components/command-bar/workflow/coordinator.ts +2 -2
- package/src/components/command-bar/workflow/field-row.tsx +25 -11
- package/src/components/command-bar/workflow/tickers.ts +2 -2
- package/src/components/feed-data-table/stack-view.test.tsx +28 -36
- package/src/components/feed-data-table/stack-view.tsx +3 -1
- package/src/components/layout/detached-pane-shell.tsx +6 -6
- package/src/components/layout/header.tsx +13 -9
- package/src/components/layout/pane/content.test.tsx +42 -3
- package/src/components/layout/pane/content.tsx +3 -1
- package/src/components/layout/pane/footer/index.test.tsx +45 -2
- package/src/components/layout/pane/footer/registration.tsx +3 -1
- package/src/components/layout/shell/index.tsx +1 -2
- package/src/components/layout/shell/native/surfaces.ts +1 -1
- package/src/components/layout/shell/native/window-state.ts +1 -1
- package/src/components/layout/status-bar.tsx +12 -8
- package/src/components/layout/window-controls.tsx +8 -6
- package/src/components/onboarding/onboarding-wizard.tsx +3 -1
- package/src/components/pane-settings-dialog/desktop.tsx +0 -2
- package/src/components/pane-settings-dialog.tsx +0 -2
- package/src/components/pane-template-wizard.tsx +10 -9
- package/src/components/price-performance.tsx +2 -113
- package/src/components/price-sparkline/model.ts +15 -17
- package/src/components/price-sparkline/view.tsx +8 -7
- package/src/components/speedometer-gauge/desktop.tsx +2 -1
- package/src/components/speedometer-gauge/index.tsx +0 -2
- package/src/components/speedometer-gauge/terminal.tsx +24 -5
- package/src/components/ticker/badge/index.tsx +1 -18
- package/src/components/ui/data-table/opentui/index.tsx +3 -3
- package/src/components/ui/frame.tsx +1 -2
- package/src/components/ui/page-stack-view.test.tsx +126 -0
- package/src/components/ui/page-stack-view.tsx +24 -12
- package/src/components/ui/tabs.tsx +7 -3
- package/src/components/ui/ui.test.tsx +23 -2
- package/src/components/use-external-link-footer.ts +4 -2
- package/src/core/app-service-ports.ts +46 -0
- package/src/core/app-services.test.ts +66 -0
- package/src/core/app-services.ts +17 -15
- package/src/data/config/chart-settings.ts +3 -10
- package/src/data/config/store/index.test.ts +99 -2
- package/src/data/config/store/migrations.ts +278 -0
- package/src/data/config/store/normalize.ts +44 -186
- package/src/data/resource-store.ts +73 -12
- package/src/data/sqlite/cache.test.ts +104 -15
- package/src/i18n/index.test.ts +17 -0
- package/src/i18n/index.ts +23 -6
- package/src/i18n/ja.ts +13 -0
- package/src/i18n/ko.ts +13 -0
- package/src/i18n/react.ts +6 -0
- package/src/i18n/zh-cn.ts +13 -0
- package/src/i18n/zh-tw.ts +13 -0
- package/src/market-data/coordinator/auxiliary.ts +7 -3
- package/src/market-data/coordinator/chart.ts +1 -1
- package/src/market-data/coordinator/index.test.ts +26 -0
- package/src/market-data/coordinator/index.ts +11 -5
- package/src/market-data/coordinator/quotes.ts +4 -40
- package/src/market-data/coordinator/subscriptions.test.ts +89 -1
- package/src/market-data/hooks.tsx +47 -1
- package/src/market-data/market/status.ts +6 -6
- package/src/market-data/performance.ts +1 -1
- package/src/market-data/quote-subscription-target.test.ts +37 -0
- package/src/market-data/quote-subscription-target.ts +60 -0
- package/src/market-data/quotes/freshness.ts +1 -0
- package/src/market-data/quotes/resolution.ts +4 -0
- package/src/market-data/request-types.ts +3 -3
- package/src/news/aggregator.test.ts +97 -1
- package/src/news/aggregator.ts +100 -57
- package/src/news/hooks.test.tsx +64 -0
- package/src/news/hooks.ts +18 -25
- package/src/plugins/builtin/account-management/footer.ts +4 -2
- package/src/plugins/builtin/account-management/pane.tsx +9 -4
- package/src/plugins/builtin/ai/agent-history.ts +11 -7
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +0 -5
- package/src/plugins/builtin/ai/ask-ai.test.tsx +3 -4
- package/src/plugins/builtin/ai/index.test.ts +2 -2
- package/src/plugins/builtin/ai/index.tsx +10 -11
- package/src/plugins/builtin/ai/pane-settings.ts +1 -1
- package/src/plugins/builtin/ai/pi/runtime.ts +6 -5
- package/src/plugins/builtin/ai/providers.ts +1 -9
- package/src/plugins/builtin/ai/runner.ts +0 -6
- package/src/plugins/builtin/ai/screener/footer.ts +4 -1
- package/src/plugins/builtin/ai/screener/pane.test.tsx +9 -9
- package/src/plugins/builtin/ai/workspace/model.ts +2 -27
- package/src/plugins/builtin/ai/workspace/pane.tsx +2 -2
- package/src/plugins/builtin/analytics/metrics.ts +1 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +3 -2
- package/src/plugins/builtin/broker-manager/index.tsx +4 -2
- package/src/plugins/builtin/chart-composer/editor/controller.ts +643 -0
- package/src/plugins/builtin/chart-composer/editor/desktop-view.tsx +134 -0
- package/src/plugins/builtin/chart-composer/editor/model.test.ts +85 -0
- package/src/plugins/builtin/chart-composer/editor/model.ts +292 -0
- package/src/plugins/builtin/chart-composer/editor/opentui-view.tsx +108 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +65 -947
- package/src/plugins/builtin/chart-composer/index.tsx +0 -6
- package/src/plugins/builtin/chart-composer/pane.tsx +69 -36
- package/src/plugins/builtin/chart-composer/presets.ts +1 -19
- package/src/plugins/builtin/chart-composer/settings.ts +1 -1
- package/src/plugins/builtin/chat/content/composer-runtime.ts +1 -1
- package/src/plugins/builtin/chat/content/shortcuts.ts +0 -1
- package/src/plugins/builtin/chat/controller/fetch.ts +8 -2
- package/src/plugins/builtin/chat/controller/persistence.ts +2 -0
- package/src/plugins/builtin/chat/controller/session.test.ts +25 -0
- package/src/plugins/builtin/chat/controller/session.ts +2 -0
- package/src/plugins/builtin/chat/controller/state.ts +7 -0
- package/src/plugins/builtin/chat/controller.test.ts +73 -20
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
- package/src/plugins/builtin/cloud-tweets/footer.ts +9 -28
- package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -5
- package/src/plugins/builtin/cloud-tweets/table.tsx +12 -17
- package/src/plugins/builtin/correlation/compute.ts +4 -10
- package/src/plugins/builtin/econ/index.tsx +17 -19
- package/src/plugins/builtin/holders/types.ts +0 -1
- package/src/plugins/builtin/insider/index.tsx +6 -31
- package/src/plugins/builtin/news/index.tsx +5 -7
- package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +6 -8
- package/src/plugins/builtin/news/wire/news/footer.ts +10 -11
- package/src/plugins/builtin/news/wire/read-state.test.ts +7 -0
- package/src/plugins/builtin/news/wire/read-state.ts +18 -28
- package/src/plugins/builtin/news/wire/rss/parser.ts +1 -11
- package/src/plugins/builtin/options/footer.test.ts +115 -0
- package/src/plugins/builtin/options/footer.ts +110 -0
- package/src/plugins/builtin/options/live-quotes.test.ts +238 -0
- package/src/plugins/builtin/options/live-quotes.ts +147 -0
- package/src/plugins/builtin/options/view.test.tsx +71 -1
- package/src/plugins/builtin/options/view.tsx +39 -24
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +103 -117
- package/src/plugins/builtin/portfolio-list/cli/render.ts +11 -5
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -135
- package/src/plugins/builtin/portfolio-list/header.tsx +9 -4
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +6 -1
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +4 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +24 -52
- package/src/plugins/builtin/sec/filing-content.test.ts +41 -0
- package/src/plugins/builtin/sec/filing-content.ts +94 -0
- package/src/plugins/builtin/sec/index.tsx +21 -104
- package/src/plugins/builtin/sectors/sector-model.ts +1 -7
- package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -0
- package/src/plugins/builtin/shared/pane-footer.ts +82 -0
- package/src/plugins/builtin/shared/read-state.ts +64 -3
- package/src/plugins/builtin/substack/read-state.ts +16 -41
- package/src/plugins/builtin/thirteenf/pane.tsx +24 -73
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +10 -10
- package/src/plugins/builtin/yield-curve/index.tsx +11 -9
- package/src/plugins/ibkr/dialogs.tsx +3 -3
- package/src/plugins/ibkr/gateway/fundamentals.ts +1 -1
- package/src/plugins/ibkr/gateway/history-requests.ts +2 -2
- package/src/plugins/ibkr/gateway/history.ts +2 -2
- package/src/plugins/ibkr/gateway/selection.ts +2 -2
- package/src/plugins/ibkr/gateway/service/index.ts +2 -2
- package/src/plugins/ibkr/gateway/service/native.ts +2 -2
- package/src/plugins/plugin-persistence.ts +3 -4
- package/src/plugins/prediction-markets/controller/keyboard.ts +1 -1
- package/src/plugins/prediction-markets/controller/live-updates.ts +6 -5
- package/src/plugins/prediction-markets/detail/book.tsx +2 -2
- package/src/plugins/prediction-markets/detail/outcomes.tsx +3 -1
- package/src/plugins/prediction-markets/detail/pane.tsx +3 -1
- package/src/plugins/prediction-markets/detail/trades.tsx +3 -3
- package/src/plugins/registry/context.ts +3 -4
- package/src/plugins/registry/index.ts +5 -6
- package/src/react/input.test.ts +126 -3
- package/src/react/input.ts +120 -1
- package/src/remote/app-host.tsx +2 -13
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +44 -60
- package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +15 -13
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +26 -22
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +5 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +11 -6
- package/src/renderers/electrobun/bun/desktop/media.ts +5 -3
- package/src/renderers/electrobun/bun/desktop/plugin-state.ts +21 -22
- package/src/renderers/electrobun/bun/desktop/window-controls.ts +3 -1
- package/src/renderers/electrobun/bun/desktop/workspace/requests.ts +18 -18
- package/src/renderers/electrobun/bun/index.ts +123 -83
- package/src/renderers/electrobun/shared/protocol.ts +146 -7
- package/src/renderers/electrobun/view/app-services.ts +5 -8
- package/src/renderers/electrobun/view/backend-rpc.ts +18 -8
- package/src/renderers/electrobun/view/broker-remote-client.ts +2 -2
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +5 -22
- package/src/renderers/electrobun/view/config-host.ts +1 -1
- package/src/renderers/electrobun/view/context-menu.ts +1 -17
- package/src/renderers/electrobun/view/data-table/index.tsx +0 -1
- package/src/renderers/electrobun/view/data-table/row.tsx +0 -4
- package/src/renderers/electrobun/view/desktop/control-styles.ts +37 -34
- package/src/renderers/electrobun/view/desktop/controls.tsx +36 -33
- package/src/renderers/electrobun/view/desktop/list-view.tsx +15 -12
- package/src/renderers/electrobun/view/desktop/popover.tsx +2 -3
- package/src/renderers/electrobun/view/dialog-host.tsx +2 -3
- package/src/renderers/electrobun/view/fatal-screen.tsx +0 -1
- package/src/renderers/electrobun/view/host/box.tsx +0 -1
- package/src/renderers/electrobun/view/host/chart-surface.tsx +0 -1
- package/src/renderers/electrobun/view/host/input.tsx +0 -1
- package/src/renderers/electrobun/view/host/media-surface.tsx +0 -1
- package/src/renderers/electrobun/view/host/native.ts +0 -1
- package/src/renderers/electrobun/view/host/scroll-box.tsx +0 -1
- package/src/renderers/electrobun/view/host/tabs.tsx +0 -1
- package/src/renderers/electrobun/view/host/text.tsx +0 -1
- package/src/renderers/electrobun/view/http-fetch.ts +3 -11
- package/src/renderers/electrobun/view/input-host.tsx +10 -53
- package/src/renderers/electrobun/view/key-event.ts +0 -1
- package/src/renderers/electrobun/view/main.tsx +3 -3
- package/src/renderers/electrobun/view/native-renderer.ts +0 -1
- package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +13 -1
- package/src/renderers/electrobun/view/native-stubs/chart/surface-manager.ts +11 -0
- package/src/renderers/electrobun/view/native-stubs/native-stubs.test.ts +47 -0
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +6 -44
- package/src/renderers/electrobun/view/remote/persistence.ts +6 -2
- package/src/renderers/electrobun/view/remote/quote-subscription-registry.test.ts +86 -0
- package/src/renderers/electrobun/view/remote/quote-subscription-registry.ts +72 -0
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +2 -2
- package/src/renderers/electrobun/view/toast-host.tsx +0 -1
- package/src/renderers/electrobun/view/ui-host.tsx +1 -2
- package/src/renderers/electrobun/view/update-host.ts +1 -1
- package/src/renderers/opentui/chart-surface.tsx +2 -1
- package/src/renderers/opentui/dialog-host.tsx +20 -4
- package/src/renderers/opentui/image/loader.ts +12 -2
- package/src/renderers/opentui/input-host.tsx +7 -49
- package/src/renderers/opentui/start.tsx +2 -0
- package/src/sources/gloomberb-cloud/history-quality.ts +44 -0
- package/src/sources/gloomberb-cloud/index.test.ts +173 -0
- package/src/sources/gloomberb-cloud/index.ts +42 -20
- package/src/sources/gloomberb-cloud/normalizers.ts +9 -3
- package/src/sources/provider-router/cache.ts +1 -1
- package/src/sources/provider-router/document-routes.ts +10 -5
- package/src/sources/provider-router/financials.test.ts +14 -0
- package/src/sources/provider-router/financials.ts +6 -1
- package/src/sources/provider-router/history.test.ts +163 -1
- package/src/sources/provider-router/history.ts +278 -347
- package/src/sources/provider-router/index.ts +2 -2
- package/src/sources/provider-router/routing.test.ts +39 -0
- package/src/sources/provider-router/routing.ts +24 -11
- package/src/sources/provider-router/supplemental.ts +37 -17
- package/src/sources/sec-edgar/content.ts +2 -12
- package/src/sources/sec-edgar.ts +2 -13
- package/src/sources/yahoo-finance/history.ts +2 -2
- package/src/sources/yahoo-finance/options.ts +5 -0
- package/src/sources/yahoo-finance.ts +2 -2
- package/src/state/app/bootstrap.ts +2 -2
- package/src/state/app/context/index.tsx +2 -2
- package/src/state/hooks/quote-streaming.ts +66 -1
- package/src/state/hooks/session-snapshot.ts +2 -2
- package/src/state/selectors.ts +0 -9
- package/src/sync/controller.test.ts +98 -0
- package/src/sync/controller.ts +29 -5
- package/src/sync/react.ts +2 -2
- package/src/sync/types.ts +2 -2
- package/src/theme/colors.test.ts +10 -0
- package/src/theme/colors.ts +75 -78
- package/src/theme/theme-context.test.tsx +67 -0
- package/src/theme/theme-context.tsx +9 -5
- package/src/tickers/open-target.ts +2 -2
- package/src/tickers/search/upsert.ts +2 -2
- package/src/time-series/alignment.ts +0 -7
- package/src/time-series/chart-data.ts +145 -0
- package/src/time-series/date-window.ts +44 -0
- package/src/time-series/history-window.ts +21 -0
- package/src/time-series/hooks.test.ts +59 -0
- package/src/time-series/hooks.ts +33 -12
- package/src/time-series/market-resolution.ts +2 -2
- package/src/time-series/market.ts +1 -1
- package/src/time-series/range.ts +9 -0
- package/src/{components/chart/core → time-series}/resolution.ts +3 -102
- package/src/time-series/resolve.test.ts +203 -1
- package/src/time-series/resolve.ts +96 -18
- package/src/time-series/spec-studies.test.ts +15 -0
- package/src/time-series/spec.ts +2 -2
- package/src/time-series/studies.ts +5 -2
- package/src/time-series/types.ts +3 -1
- package/src/time-series/use-chart-resolution.test.tsx +358 -0
- package/src/time-series/use-chart-resolution.ts +46 -4
- package/src/types/broker.ts +2 -2
- package/src/types/data-provider.ts +3 -3
- package/src/types/financials.ts +12 -0
- package/src/types/plugin.ts +2 -2
- package/src/ui/context-menu.tsx +3 -2
- package/src/ui/dialog.tsx +2 -2
- package/src/utils/html-entities.ts +3 -5
- package/src/utils/main-thread-monitor.ts +1 -1
- package/src/utils/price-history.ts +5 -5
- package/src/version.ts +3 -1
- package/src/components/chart/core/viewport.test.ts +0 -8
- package/src/components/chart/core/viewport.ts +0 -24
- package/src/time-series/index.ts +0 -14
- /package/src/{components/chart/core → time-series}/resolution.test.ts +0 -0
package/src/api-client/socket.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "./normalizers";
|
|
12
12
|
import { debugLog } from "../utils/debug-log";
|
|
13
13
|
import { canonicalExchange, normalizeSymbol } from "../utils/exchanges";
|
|
14
|
+
import { mergeQuoteSubscriptionTargets } from "../market-data/quote-subscription-target";
|
|
14
15
|
|
|
15
16
|
const QUOTE_SUBSCRIPTION_FLUSH_MS = 25;
|
|
16
17
|
const cloudApiLog = debugLog.createLogger("cloud-api");
|
|
@@ -19,6 +20,18 @@ type ChannelListener = (message: ChatMessage) => void;
|
|
|
19
20
|
type ChatNotificationListener = (notification: ChatNotification) => void;
|
|
20
21
|
type ChatPresenceListener = (onlineCount: number) => void;
|
|
21
22
|
type QuoteListener = (target: QuoteStreamTarget, quote: CloudQuotePayload) => void;
|
|
23
|
+
type QuoteSubscription = {
|
|
24
|
+
target: QuoteStreamTarget;
|
|
25
|
+
listener: QuoteListener;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function mergeQuoteStreamSubscriptions(
|
|
29
|
+
subscriptions: Iterable<QuoteSubscription>,
|
|
30
|
+
): QuoteStreamTarget | null {
|
|
31
|
+
return mergeQuoteSubscriptionTargets(
|
|
32
|
+
[...subscriptions].map((subscription) => subscription.target),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
22
35
|
|
|
23
36
|
type CloudApiSocketDelegate = {
|
|
24
37
|
getBaseUrl: () => string;
|
|
@@ -49,7 +62,8 @@ export class CloudApiSocket {
|
|
|
49
62
|
private readonly channelListeners = new Map<string, Set<ChannelListener>>();
|
|
50
63
|
private readonly chatNotificationListeners = new Set<ChatNotificationListener>();
|
|
51
64
|
private readonly chatPresenceListeners = new Set<ChatPresenceListener>();
|
|
52
|
-
private
|
|
65
|
+
private nextQuoteSubscriptionId = 1;
|
|
66
|
+
private readonly quoteSubscriptions = new Map<string, Map<number, QuoteSubscription>>();
|
|
53
67
|
private readonly quoteTargets = new Map<string, QuoteStreamTarget>();
|
|
54
68
|
private readonly pendingQuoteSubscribes = new Map<string, QuoteStreamTarget>();
|
|
55
69
|
private readonly pendingQuoteUnsubscribes = new Map<string, QuoteStreamTarget>();
|
|
@@ -57,11 +71,14 @@ export class CloudApiSocket {
|
|
|
57
71
|
|
|
58
72
|
constructor(private readonly delegate: CloudApiSocketDelegate) {}
|
|
59
73
|
|
|
60
|
-
syncAuthState(): void {
|
|
74
|
+
syncAuthState(options: { reconnect?: boolean } = {}): void {
|
|
61
75
|
if (!this.shouldKeepSocketOpen()) {
|
|
62
76
|
this.teardown();
|
|
63
77
|
return;
|
|
64
78
|
}
|
|
79
|
+
if (options.reconnect) {
|
|
80
|
+
this.teardown();
|
|
81
|
+
}
|
|
65
82
|
this.ensureSocket();
|
|
66
83
|
}
|
|
67
84
|
|
|
@@ -150,6 +167,7 @@ export class CloudApiSocket {
|
|
|
150
167
|
targets: QuoteStreamTarget[],
|
|
151
168
|
onQuote: (target: QuoteStreamTarget, quote: CloudQuotePayload) => void,
|
|
152
169
|
): () => void {
|
|
170
|
+
const subscriptionId = this.nextQuoteSubscriptionId++;
|
|
153
171
|
const uniqueTargets = [...new Map(
|
|
154
172
|
targets
|
|
155
173
|
.filter((target) => typeof target.symbol === "string" && target.symbol.trim().length > 0)
|
|
@@ -170,18 +188,17 @@ export class CloudApiSocket {
|
|
|
170
188
|
const updatedSubscriptions: QuoteStreamTarget[] = [];
|
|
171
189
|
for (const target of uniqueTargets) {
|
|
172
190
|
const key = marketKey(target.symbol, target.exchange);
|
|
173
|
-
const
|
|
174
|
-
|
|
191
|
+
const subscriptions = this.quoteSubscriptions.get(key) ?? new Map<number, QuoteSubscription>();
|
|
192
|
+
const previousTarget = this.quoteTargets.get(key);
|
|
193
|
+
subscriptions.set(subscriptionId, { target, listener: onQuote });
|
|
194
|
+
this.quoteSubscriptions.set(key, subscriptions);
|
|
195
|
+
const mergedTarget = mergeQuoteStreamSubscriptions(subscriptions.values()) ?? target;
|
|
196
|
+
if (!previousTarget) {
|
|
175
197
|
newSubscriptions.push(target);
|
|
176
|
-
} else {
|
|
177
|
-
|
|
178
|
-
if (JSON.stringify(this.serializeQuoteStreamTarget(existing ?? target)) !== JSON.stringify(this.serializeQuoteStreamTarget(target))) {
|
|
179
|
-
updatedSubscriptions.push(target);
|
|
180
|
-
}
|
|
198
|
+
} else if (!this.areQuoteStreamTargetsEquivalent(previousTarget, mergedTarget)) {
|
|
199
|
+
updatedSubscriptions.push(mergedTarget);
|
|
181
200
|
}
|
|
182
|
-
|
|
183
|
-
this.quoteListeners.set(key, listeners);
|
|
184
|
-
this.quoteTargets.set(key, target);
|
|
201
|
+
this.quoteTargets.set(key, mergedTarget);
|
|
185
202
|
}
|
|
186
203
|
|
|
187
204
|
this.ensureSocket();
|
|
@@ -196,20 +213,29 @@ export class CloudApiSocket {
|
|
|
196
213
|
|
|
197
214
|
return () => {
|
|
198
215
|
const removedTargets: QuoteStreamTarget[] = [];
|
|
216
|
+
const updatedTargets: QuoteStreamTarget[] = [];
|
|
199
217
|
|
|
200
218
|
for (const target of uniqueTargets) {
|
|
201
219
|
const key = marketKey(target.symbol, target.exchange);
|
|
202
|
-
const
|
|
203
|
-
if (!
|
|
204
|
-
|
|
205
|
-
if (
|
|
206
|
-
this.
|
|
207
|
-
|
|
208
|
-
if (storedTarget) {
|
|
209
|
-
removedTargets.push(storedTarget);
|
|
210
|
-
}
|
|
220
|
+
const subscriptions = this.quoteSubscriptions.get(key);
|
|
221
|
+
if (!subscriptions || !subscriptions.delete(subscriptionId)) continue;
|
|
222
|
+
const previousTarget = this.quoteTargets.get(key);
|
|
223
|
+
if (subscriptions.size === 0) {
|
|
224
|
+
this.quoteSubscriptions.delete(key);
|
|
225
|
+
if (previousTarget) removedTargets.push(previousTarget);
|
|
211
226
|
this.quoteTargets.delete(key);
|
|
227
|
+
continue;
|
|
212
228
|
}
|
|
229
|
+
const mergedTarget = mergeQuoteStreamSubscriptions(subscriptions.values());
|
|
230
|
+
if (!mergedTarget) continue;
|
|
231
|
+
this.quoteTargets.set(key, mergedTarget);
|
|
232
|
+
if (previousTarget && !this.areQuoteStreamTargetsEquivalent(previousTarget, mergedTarget)) {
|
|
233
|
+
updatedTargets.push(mergedTarget);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (updatedTargets.length > 0) {
|
|
238
|
+
this.queueQuoteSubscribes(updatedTargets);
|
|
213
239
|
}
|
|
214
240
|
|
|
215
241
|
if (removedTargets.length > 0) {
|
|
@@ -234,7 +260,7 @@ export class CloudApiSocket {
|
|
|
234
260
|
this.channelListeners.clear();
|
|
235
261
|
this.chatNotificationListeners.clear();
|
|
236
262
|
this.chatPresenceListeners.clear();
|
|
237
|
-
this.
|
|
263
|
+
this.quoteSubscriptions.clear();
|
|
238
264
|
this.quoteTargets.clear();
|
|
239
265
|
this.pendingQuoteSubscribes.clear();
|
|
240
266
|
this.pendingQuoteUnsubscribes.clear();
|
|
@@ -302,12 +328,15 @@ export class CloudApiSocket {
|
|
|
302
328
|
|
|
303
329
|
if (parsed?.type === "market.quote" && parsed.quote && typeof parsed.symbol === "string") {
|
|
304
330
|
const key = marketKey(parsed.symbol, parsed.exchange);
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
331
|
+
const quote: CloudQuotePayload = {
|
|
332
|
+
...(parsed.quote as CloudQuotePayload),
|
|
333
|
+
...(parsed.delivery === "stream" || parsed.delivery === "poll"
|
|
334
|
+
? { delivery: parsed.delivery }
|
|
335
|
+
: {}),
|
|
336
|
+
...(typeof parsed.stale === "boolean" ? { stale: parsed.stale } : {}),
|
|
308
337
|
};
|
|
309
|
-
for (const
|
|
310
|
-
listener(target,
|
|
338
|
+
for (const subscription of this.quoteSubscriptions.get(key)?.values() ?? []) {
|
|
339
|
+
subscription.listener(subscription.target, quote);
|
|
311
340
|
}
|
|
312
341
|
}
|
|
313
342
|
}
|
|
@@ -366,7 +395,8 @@ export class CloudApiSocket {
|
|
|
366
395
|
reason: closeEvent?.reason,
|
|
367
396
|
tokenSource: usingWebSocketToken ? "websocket" : "session",
|
|
368
397
|
});
|
|
369
|
-
if (activeSocket
|
|
398
|
+
if (!activeSocket) return;
|
|
399
|
+
if (usingWebSocketToken && this.delegate.clearWebSocketTokenForFallback()) {
|
|
370
400
|
this.reconnectDelayMs = 1000;
|
|
371
401
|
cloudApiLog.warn("cleared websocket token after socket close; falling back to session token");
|
|
372
402
|
}
|
|
@@ -440,7 +470,7 @@ export class CloudApiSocket {
|
|
|
440
470
|
private queueQuoteUnsubscribes(targets: QuoteStreamTarget[]): void {
|
|
441
471
|
for (const target of targets) {
|
|
442
472
|
const key = marketKey(target.symbol, target.exchange);
|
|
443
|
-
|
|
473
|
+
this.pendingQuoteSubscribes.delete(key);
|
|
444
474
|
this.pendingQuoteUnsubscribes.set(key, target);
|
|
445
475
|
}
|
|
446
476
|
this.scheduleQuoteSubscriptionFlush();
|
|
@@ -456,18 +486,18 @@ export class CloudApiSocket {
|
|
|
456
486
|
const unsubscribes = [...this.pendingQuoteUnsubscribes.values()];
|
|
457
487
|
this.pendingQuoteSubscribes.clear();
|
|
458
488
|
this.pendingQuoteUnsubscribes.clear();
|
|
459
|
-
if (subscribes.length > 0) {
|
|
460
|
-
this.sendSocketMessage({
|
|
461
|
-
type: "market.subscribe",
|
|
462
|
-
symbols: subscribes.map((target) => this.serializeQuoteStreamTarget(target)),
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
489
|
if (unsubscribes.length > 0) {
|
|
466
490
|
this.sendSocketMessage({
|
|
467
491
|
type: "market.unsubscribe",
|
|
468
492
|
symbols: unsubscribes.map((target) => this.serializeQuoteStreamTarget(target)),
|
|
469
493
|
});
|
|
470
494
|
}
|
|
495
|
+
if (subscribes.length > 0) {
|
|
496
|
+
this.sendSocketMessage({
|
|
497
|
+
type: "market.subscribe",
|
|
498
|
+
symbols: subscribes.map((target) => this.serializeQuoteStreamTarget(target)),
|
|
499
|
+
});
|
|
500
|
+
}
|
|
471
501
|
}
|
|
472
502
|
|
|
473
503
|
private serializeQuoteStreamTarget(target: QuoteStreamTarget): QuoteStreamTarget {
|
|
@@ -480,4 +510,9 @@ export class CloudApiSocket {
|
|
|
480
510
|
...(Number.isFinite(target.weight) ? { weight: target.weight } : {}),
|
|
481
511
|
};
|
|
482
512
|
}
|
|
513
|
+
|
|
514
|
+
private areQuoteStreamTargetsEquivalent(left: QuoteStreamTarget, right: QuoteStreamTarget): boolean {
|
|
515
|
+
return JSON.stringify(this.serializeQuoteStreamTarget(left))
|
|
516
|
+
=== JSON.stringify(this.serializeQuoteStreamTarget(right));
|
|
517
|
+
}
|
|
483
518
|
}
|
package/src/api-client/types.ts
CHANGED
|
@@ -530,7 +530,7 @@ export interface CloudVerificationResponse {
|
|
|
530
530
|
export interface QuoteStreamTarget {
|
|
531
531
|
symbol: string;
|
|
532
532
|
exchange?: string;
|
|
533
|
-
surface?: "portfolio" | "watchlist" | "detail" | "monitor" | "inline" | "screener" | "unknown";
|
|
533
|
+
surface?: "portfolio" | "watchlist" | "detail" | "monitor" | "inline" | "options" | "screener" | "unknown";
|
|
534
534
|
visible?: boolean;
|
|
535
535
|
selected?: boolean;
|
|
536
536
|
weight?: number;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { act, type Dispatch } from "react";
|
|
3
|
+
import { getLanguage, setLanguage, t } from "../i18n";
|
|
4
|
+
import { useAppLanguage } from "../i18n/react";
|
|
5
|
+
import { testRender } from "../renderers/opentui/test-utils";
|
|
6
|
+
import {
|
|
7
|
+
AppProvider,
|
|
8
|
+
useAppDispatch,
|
|
9
|
+
type AppAction,
|
|
10
|
+
} from "../state/app/context";
|
|
11
|
+
import { createDefaultConfig } from "../types/config";
|
|
12
|
+
import { AppLanguageConfigObserver } from "./language-observer";
|
|
13
|
+
|
|
14
|
+
let testSetup: Awaited<ReturnType<typeof testRender>> | undefined;
|
|
15
|
+
let dispatch: Dispatch<AppAction> | null = null;
|
|
16
|
+
|
|
17
|
+
function DispatchCapture() {
|
|
18
|
+
dispatch = useAppDispatch();
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function LanguageProbe() {
|
|
23
|
+
useAppLanguage();
|
|
24
|
+
return <text>{t("Done")}</text>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
afterEach(async () => {
|
|
28
|
+
if (testSetup) {
|
|
29
|
+
await act(async () => testSetup?.renderer.destroy());
|
|
30
|
+
}
|
|
31
|
+
testSetup = undefined;
|
|
32
|
+
dispatch = null;
|
|
33
|
+
setLanguage("en");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
async function renderSettled() {
|
|
37
|
+
await act(async () => {
|
|
38
|
+
await testSetup?.renderOnce();
|
|
39
|
+
await testSetup?.renderOnce();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("AppLanguageConfigObserver", () => {
|
|
44
|
+
test("tracks language changes from config replacement and desktop hydration", async () => {
|
|
45
|
+
const config = { ...createDefaultConfig("/tmp/gloomberb-language-observer"), language: "en" as const };
|
|
46
|
+
testSetup = await testRender(
|
|
47
|
+
<AppProvider config={config}>
|
|
48
|
+
<AppLanguageConfigObserver />
|
|
49
|
+
<DispatchCapture />
|
|
50
|
+
<LanguageProbe />
|
|
51
|
+
</AppProvider>,
|
|
52
|
+
{ width: 20, height: 2 },
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
await renderSettled();
|
|
56
|
+
expect(getLanguage()).toBe("en");
|
|
57
|
+
expect(testSetup.captureCharFrame()).toContain("Done");
|
|
58
|
+
|
|
59
|
+
act(() => {
|
|
60
|
+
dispatch?.({ type: "SET_CONFIG", config: { ...config, language: "zh-CN" } });
|
|
61
|
+
});
|
|
62
|
+
await renderSettled();
|
|
63
|
+
expect(getLanguage()).toBe("zh-CN");
|
|
64
|
+
expect(testSetup.captureCharFrame()).toContain("完成");
|
|
65
|
+
|
|
66
|
+
act(() => {
|
|
67
|
+
dispatch?.({
|
|
68
|
+
type: "HYDRATE_DESKTOP_SNAPSHOT",
|
|
69
|
+
snapshot: {
|
|
70
|
+
config: { ...config, language: "ko" },
|
|
71
|
+
paneState: {},
|
|
72
|
+
focusedPaneId: null,
|
|
73
|
+
activePanel: "left",
|
|
74
|
+
statusBarVisible: true,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
await renderSettled();
|
|
79
|
+
expect(getLanguage()).toBe("ko");
|
|
80
|
+
expect(testSetup.captureCharFrame()).toContain("완료");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { applyLanguageFromConfig } from "../i18n";
|
|
3
|
+
import { useAppSelector } from "../state/app/context";
|
|
4
|
+
|
|
5
|
+
export function AppLanguageConfigObserver() {
|
|
6
|
+
const language = useAppSelector((state) => state.config.language);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
applyLanguageFromConfig({ language });
|
|
10
|
+
}, [language]);
|
|
11
|
+
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, type Dispatch } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { AppTickerRepositoryPort } from "../../core/app-service-ports";
|
|
3
3
|
import {
|
|
4
4
|
addPaneFloating,
|
|
5
5
|
addPaneToLayout,
|
|
@@ -54,7 +54,7 @@ interface AppPaneRuntimeArgs {
|
|
|
54
54
|
pluginRegistry: PluginRegistry;
|
|
55
55
|
state: AppState;
|
|
56
56
|
stateRef: { current: AppState };
|
|
57
|
-
tickerRepository:
|
|
57
|
+
tickerRepository: AppTickerRepositoryPort;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export function useAppPaneRuntime({
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
applyPaneSettingFieldValue as applyPaneSettingFieldValueShared,
|
|
4
4
|
} from "../../components/command-bar/workflow/ops";
|
|
5
5
|
import { PaneSettingsDialogContent } from "../../components/pane-settings-dialog";
|
|
6
|
-
import type {
|
|
6
|
+
import type { AppTickerRepositoryPort } from "../../core/app-service-ports";
|
|
7
7
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
8
8
|
import type { AppAction, AppState } from "../../state/app/context";
|
|
9
9
|
import type { LayoutConfig } from "../../types/config";
|
|
@@ -19,7 +19,7 @@ interface UseAppPaneSettingsRuntimeOptions {
|
|
|
19
19
|
pluginRegistry: PluginRegistry;
|
|
20
20
|
resolvePaneTarget: (paneId: string, layout?: LayoutConfig) => string | null;
|
|
21
21
|
stateRef: { current: AppState };
|
|
22
|
-
tickerRepository:
|
|
22
|
+
tickerRepository: AppTickerRepositoryPort;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function useAppPaneSettingsRuntime({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, type Dispatch } from "react";
|
|
2
2
|
import { getPaneTemplateDisplayLabel } from "../../components/command-bar/pane-templates/items";
|
|
3
3
|
import { createPaneTemplateOrThrow } from "../../components/command-bar/workflow/ops";
|
|
4
|
-
import type {
|
|
4
|
+
import type { AppTickerRepositoryPort } from "../../core/app-service-ports";
|
|
5
5
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
6
6
|
import type { AppAction, AppState } from "../../state/app/context";
|
|
7
7
|
import type { PaneBinding, PaneInstanceConfig } from "../../types/config";
|
|
@@ -34,7 +34,7 @@ interface UseAppPaneTemplateRuntimeOptions {
|
|
|
34
34
|
) => void;
|
|
35
35
|
pluginRegistry: PluginRegistry;
|
|
36
36
|
stateRef: { current: AppState };
|
|
37
|
-
tickerRepository:
|
|
37
|
+
tickerRepository: AppTickerRepositoryPort;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export function useAppPaneTemplateRuntime({
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
applyPaneSettingFieldValue as applyPaneSettingFieldValueShared,
|
|
4
4
|
createPaneTemplateOrThrow,
|
|
5
5
|
} from "../../components/command-bar/workflow/ops";
|
|
6
|
-
import type {
|
|
6
|
+
import type { AppTickerRepositoryPort } from "../../core/app-service-ports";
|
|
7
7
|
import { setLayoutManagerDispatch } from "../../plugins/builtin/layout-manager";
|
|
8
8
|
import {
|
|
9
9
|
isPaneInLayout,
|
|
@@ -65,7 +65,7 @@ interface BindAppPanePluginRegistryOptions {
|
|
|
65
65
|
state: AppState;
|
|
66
66
|
stateRef: { current: AppState };
|
|
67
67
|
switchTickerResearchTab: (tabId: string, preferredPaneId?: string | null) => void;
|
|
68
|
-
tickerRepository:
|
|
68
|
+
tickerRepository: AppTickerRepositoryPort;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
export function bindAppPanePluginRegistry({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, type Dispatch } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { AppTickerRepositoryPort } from "../../core/app-service-ports";
|
|
3
3
|
import {
|
|
4
4
|
addPaneFloating,
|
|
5
5
|
addPaneToLayout,
|
|
@@ -37,7 +37,7 @@ interface UseAppTickerOpenRuntimeOptions {
|
|
|
37
37
|
persistLayout: (layout: LayoutConfig, options?: { pushHistory?: boolean }) => void;
|
|
38
38
|
pluginRegistry: PluginRegistry;
|
|
39
39
|
stateRef: { current: AppState };
|
|
40
|
-
tickerRepository:
|
|
40
|
+
tickerRepository: AppTickerRepositoryPort;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export function useAppTickerOpenRuntime({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, type Dispatch } from "react";
|
|
2
2
|
import { restoreBrokerPortfoliosFromTickerPositions, syncBrokerInstance, syncBrokerInstances } from "../../brokers/sync-broker-instance";
|
|
3
3
|
import type { SyncBrokerInstanceResult } from "../../brokers/sync-broker-instance";
|
|
4
|
-
import type {
|
|
4
|
+
import type { AppTickerRepositoryPort } from "../../core/app-service-ports";
|
|
5
5
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
6
6
|
import { saveConfigImmediately } from "../../state/config-save-scheduler";
|
|
7
7
|
import type { AppAction, AppState } from "../../state/app/context";
|
|
@@ -28,7 +28,7 @@ export function useBrokerImportRuntime({
|
|
|
28
28
|
pluginRegistry: PluginRegistry;
|
|
29
29
|
refreshQuote: (symbol: string, exchange?: string, tickerOverride?: TickerRecord | null, priority?: number) => void;
|
|
30
30
|
stateRef: { current: AppState };
|
|
31
|
-
tickerRepository:
|
|
31
|
+
tickerRepository: AppTickerRepositoryPort;
|
|
32
32
|
}): AppBrokerImportRuntime {
|
|
33
33
|
const applyBrokerImportResult = useCallback(async (
|
|
34
34
|
instanceId: string,
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
clearPersistedBrokerAccounts,
|
|
4
4
|
getBrokerAccountCacheSourceKey,
|
|
5
5
|
} from "../../brokers/account-cache";
|
|
6
|
-
import type {
|
|
6
|
+
import type { AppTickerRepositoryPort } from "../../core/app-service-ports";
|
|
7
7
|
import type { MarketDataCoordinator } from "../../market-data/coordinator";
|
|
8
8
|
import { instrumentFromTicker } from "../../market-data/request-types";
|
|
9
9
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
@@ -32,7 +32,7 @@ export function bindPluginRegistryRuntimeAccess({
|
|
|
32
32
|
marketData: MarketDataCoordinator;
|
|
33
33
|
pluginRegistry: PluginRegistry;
|
|
34
34
|
state: AppState;
|
|
35
|
-
tickerRepository:
|
|
35
|
+
tickerRepository: AppTickerRepositoryPort;
|
|
36
36
|
}) {
|
|
37
37
|
pluginRegistry.getTickerFn = (symbol) => state.tickers.get(symbol) ?? null;
|
|
38
38
|
pluginRegistry.getDataFn = (symbol) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, type Dispatch } from "react";
|
|
2
2
|
import { loadPersistedBrokerAccountMap } from "../../brokers/account-cache";
|
|
3
3
|
import type { AppSessionSnapshot } from "../../core/state/session-persistence";
|
|
4
|
-
import type {
|
|
4
|
+
import type { AppTickerRepositoryPort } from "../../core/app-service-ports";
|
|
5
5
|
import type { MarketDataCoordinator } from "../../market-data/coordinator";
|
|
6
6
|
import { instrumentFromTicker } from "../../market-data/request-types";
|
|
7
7
|
import { chatController } from "../../plugins/builtin/chat/controller";
|
|
@@ -36,7 +36,7 @@ interface UseAppStartupRuntimeOptions {
|
|
|
36
36
|
refreshTickersBatch: InitializeAppStateArgs["refreshTickersBatch"];
|
|
37
37
|
sessionSnapshot?: AppSessionSnapshot | null;
|
|
38
38
|
state: AppState;
|
|
39
|
-
tickerRepository:
|
|
39
|
+
tickerRepository: AppTickerRepositoryPort;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export function useAppStartupRuntime({
|
package/src/app.tsx
CHANGED
|
@@ -19,7 +19,8 @@ import { CommandBar } from "./components/command-bar/surface";
|
|
|
19
19
|
import { OnboardingWizard } from "./components/onboarding/onboarding-wizard";
|
|
20
20
|
import { useDialog } from "./ui/dialog";
|
|
21
21
|
import { PluginRegistry } from "./plugins/registry";
|
|
22
|
-
import type {
|
|
22
|
+
import type { LoadedExternalPlugin } from "./plugins/loader";
|
|
23
|
+
import type { AppServicesFactory, AppTickerRepositoryPort } from "./core/app-service-ports";
|
|
23
24
|
import { ThemeProvider, useThemeColors } from "./theme/theme-context";
|
|
24
25
|
import type { AppConfig } from "./types/config";
|
|
25
26
|
import type { DesktopDeepLinkBridge } from "./types/desktop-deeplink";
|
|
@@ -31,7 +32,6 @@ import type { LayoutBounds } from "./plugins/pane-manager";
|
|
|
31
32
|
import type { AppSessionSnapshot } from "./core/state/session-persistence";
|
|
32
33
|
import type { MarketDataCoordinator } from "./market-data/coordinator";
|
|
33
34
|
import { createAppNotifier } from "./notifications/app-notifier";
|
|
34
|
-
import { createAppServices } from "./core/app-services";
|
|
35
35
|
import { getLoadablePlugins } from "./plugins/catalog";
|
|
36
36
|
import { useBrokerImportRuntime } from "./app/runtime/broker-import";
|
|
37
37
|
import { useDesktopDeepLinkRuntime } from "./app/runtime/desktop-deeplink";
|
|
@@ -53,10 +53,14 @@ import {
|
|
|
53
53
|
} from "./app/app-bootstrap-state";
|
|
54
54
|
import { scheduleConfigSave } from "./state/config-save-scheduler";
|
|
55
55
|
import { measurePerf } from "./utils/perf-marks";
|
|
56
|
+
import { useAppLanguage } from "./i18n/react";
|
|
57
|
+
import { AppLanguageConfigObserver } from "./app/language-observer";
|
|
58
|
+
|
|
59
|
+
const EMPTY_EXTERNAL_PLUGINS: LoadedExternalPlugin[] = [];
|
|
56
60
|
|
|
57
61
|
interface AppInnerProps {
|
|
58
62
|
pluginRegistry: PluginRegistry;
|
|
59
|
-
tickerRepository:
|
|
63
|
+
tickerRepository: AppTickerRepositoryPort;
|
|
60
64
|
dataProvider: DataProvider;
|
|
61
65
|
marketData: MarketDataCoordinator;
|
|
62
66
|
sessionSnapshot?: AppSessionSnapshot | null;
|
|
@@ -384,7 +388,8 @@ function AppInner({
|
|
|
384
388
|
|
|
385
389
|
interface AppProps {
|
|
386
390
|
config: AppConfig;
|
|
387
|
-
|
|
391
|
+
servicesFactory: AppServicesFactory;
|
|
392
|
+
externalPlugins?: LoadedExternalPlugin[];
|
|
388
393
|
cliLaunchRequest?: CliLaunchRequest | null;
|
|
389
394
|
desktopWindowBridge?: DesktopWindowBridge;
|
|
390
395
|
desktopApplicationMenuBridge?: DesktopApplicationMenuBridge;
|
|
@@ -396,7 +401,8 @@ interface AppProps {
|
|
|
396
401
|
|
|
397
402
|
export function App({
|
|
398
403
|
config: initialConfig,
|
|
399
|
-
|
|
404
|
+
servicesFactory,
|
|
405
|
+
externalPlugins: providedExternalPlugins,
|
|
400
406
|
cliLaunchRequest = null,
|
|
401
407
|
desktopWindowBridge,
|
|
402
408
|
desktopApplicationMenuBridge,
|
|
@@ -405,6 +411,8 @@ export function App({
|
|
|
405
411
|
desktopThemePreview = null,
|
|
406
412
|
remoteControlAdapter,
|
|
407
413
|
}: AppProps) {
|
|
414
|
+
useAppLanguage();
|
|
415
|
+
const externalPlugins = providedExternalPlugins ?? EMPTY_EXTERNAL_PLUGINS;
|
|
408
416
|
const renderer = useNativeRenderer();
|
|
409
417
|
const effectiveInitialConfig = useMemo(() => {
|
|
410
418
|
return resolveInitialAppConfig({
|
|
@@ -432,7 +440,7 @@ export function App({
|
|
|
432
440
|
|
|
433
441
|
const services = useMemo(() => {
|
|
434
442
|
return measurePerf("startup.app.create-services", () => (
|
|
435
|
-
|
|
443
|
+
servicesFactory({
|
|
436
444
|
config,
|
|
437
445
|
plugins: getLoadablePlugins(externalPlugins),
|
|
438
446
|
})
|
|
@@ -441,7 +449,7 @@ export function App({
|
|
|
441
449
|
disabledPluginCount: config.disabledPlugins.length,
|
|
442
450
|
brokerInstanceCount: config.brokerInstances.length,
|
|
443
451
|
});
|
|
444
|
-
}, [config.dataDir, externalPlugins]);
|
|
452
|
+
}, [config.dataDir, externalPlugins, servicesFactory]);
|
|
445
453
|
|
|
446
454
|
useEffect(() => {
|
|
447
455
|
return () => services.destroy();
|
|
@@ -483,6 +491,7 @@ export function App({
|
|
|
483
491
|
desktopSnapshot={desktopSnapshot}
|
|
484
492
|
initialThemePreview={desktopThemePreview}
|
|
485
493
|
>
|
|
494
|
+
<AppLanguageConfigObserver />
|
|
486
495
|
<AppInner
|
|
487
496
|
pluginRegistry={services.pluginRegistry}
|
|
488
497
|
tickerRepository={services.tickerRepository}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AppResourceStorePort } from "../core/app-service-ports";
|
|
2
2
|
import type { BrokerAdapter } from "../types/broker";
|
|
3
3
|
import type { BrokerInstanceConfig } from "../types/config";
|
|
4
4
|
import type { CachePolicy } from "../types/persistence";
|
|
@@ -42,7 +42,7 @@ function getBrokerAccountCachePolicy(
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function pruneMismatchedSnapshots(
|
|
45
|
-
resources:
|
|
45
|
+
resources: AppResourceStorePort,
|
|
46
46
|
instance: BrokerInstanceConfig,
|
|
47
47
|
sourceKey: string,
|
|
48
48
|
): void {
|
|
@@ -67,7 +67,7 @@ function pruneMismatchedSnapshots(
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
export function loadPersistedBrokerAccounts(
|
|
70
|
-
resources:
|
|
70
|
+
resources: AppResourceStorePort,
|
|
71
71
|
instance: BrokerInstanceConfig,
|
|
72
72
|
broker?: BrokerAdapter | null,
|
|
73
73
|
): BrokerAccount[] | null {
|
|
@@ -85,7 +85,7 @@ export function loadPersistedBrokerAccounts(
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
export function persistBrokerAccounts(
|
|
88
|
-
resources:
|
|
88
|
+
resources: AppResourceStorePort,
|
|
89
89
|
instance: BrokerInstanceConfig,
|
|
90
90
|
broker: BrokerAdapter,
|
|
91
91
|
accounts: BrokerAccount[],
|
|
@@ -108,7 +108,7 @@ export function persistBrokerAccounts(
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
export function loadPersistedBrokerAccountMap(
|
|
111
|
-
resources:
|
|
111
|
+
resources: AppResourceStorePort,
|
|
112
112
|
brokerInstances: BrokerInstanceConfig[],
|
|
113
113
|
brokers: ReadonlyMap<string, BrokerAdapter>,
|
|
114
114
|
): Record<string, BrokerAccount[]> {
|
|
@@ -126,7 +126,7 @@ export function loadPersistedBrokerAccountMap(
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
export function clearPersistedBrokerAccounts(
|
|
129
|
-
resources:
|
|
129
|
+
resources: AppResourceStorePort,
|
|
130
130
|
instance: BrokerInstanceConfig,
|
|
131
131
|
): void {
|
|
132
132
|
const records = resources.list<PersistedBrokerAccountSnapshot>({
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AppTickerRepositoryPort } from "../core/app-service-ports";
|
|
2
2
|
import type { BrokerPosition } from "../types/broker";
|
|
3
3
|
import type { BrokerInstanceConfig } from "../types/config";
|
|
4
4
|
import type { BrokerContractRef } from "../types/instrument";
|
|
5
5
|
import type { TickerMetadata, TickerPosition, TickerRecord } from "../types/ticker";
|
|
6
6
|
|
|
7
7
|
export async function loadTickerMap(
|
|
8
|
-
tickerRepository:
|
|
8
|
+
tickerRepository: AppTickerRepositoryPort,
|
|
9
9
|
existingTickers?: Map<string, TickerRecord>,
|
|
10
10
|
): Promise<Map<string, TickerRecord>> {
|
|
11
11
|
if (existingTickers) {
|
|
@@ -114,7 +114,7 @@ export async function upsertBrokerPositionTicker({
|
|
|
114
114
|
portfolioId,
|
|
115
115
|
position,
|
|
116
116
|
}: {
|
|
117
|
-
tickerRepository:
|
|
117
|
+
tickerRepository: AppTickerRepositoryPort;
|
|
118
118
|
tickers: Map<string, TickerRecord>;
|
|
119
119
|
instance: BrokerInstanceConfig;
|
|
120
120
|
portfolioId: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { TimeRange } from "../
|
|
2
|
-
import type { ManualChartResolution } from "../
|
|
1
|
+
import type { TimeRange } from "../time-series/range";
|
|
2
|
+
import type { ManualChartResolution } from "../time-series/resolution";
|
|
3
3
|
import type { BrokerAdapter, BrokerConnectionStatus } from "../types/broker";
|
|
4
4
|
import type { BrokerInstanceConfig } from "../types/config";
|
|
5
5
|
import type { QuoteSubscriptionTarget } from "../types/data-provider";
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { TickerRepository } from "../data/ticker-repository";
|
|
1
|
+
import type { AppResourceStorePort, AppTickerRepositoryPort } from "../core/app-service-ports";
|
|
3
2
|
import { persistBrokerAccounts } from "./account-cache";
|
|
4
3
|
import type { BrokerAdapter, BrokerPosition } from "../types/broker";
|
|
5
4
|
import type { AppConfig, BrokerInstanceConfig } from "../types/config";
|
|
@@ -23,9 +22,9 @@ export interface SyncBrokerInstanceArgs {
|
|
|
23
22
|
config: AppConfig;
|
|
24
23
|
instanceId: string;
|
|
25
24
|
brokers: ReadonlyMap<string, BrokerAdapter>;
|
|
26
|
-
tickerRepository:
|
|
25
|
+
tickerRepository: AppTickerRepositoryPort;
|
|
27
26
|
existingTickers?: Map<string, TickerRecord>;
|
|
28
|
-
resources?:
|
|
27
|
+
resources?: AppResourceStorePort;
|
|
29
28
|
persistResolvedBrokerConfig?: boolean;
|
|
30
29
|
}
|
|
31
30
|
|
|
@@ -42,9 +41,9 @@ export interface SyncBrokerInstanceResult {
|
|
|
42
41
|
export interface SyncBrokerInstancesArgs {
|
|
43
42
|
config: AppConfig;
|
|
44
43
|
brokers: ReadonlyMap<string, BrokerAdapter>;
|
|
45
|
-
tickerRepository:
|
|
44
|
+
tickerRepository: AppTickerRepositoryPort;
|
|
46
45
|
existingTickers: Map<string, TickerRecord>;
|
|
47
|
-
resources?:
|
|
46
|
+
resources?: AppResourceStorePort;
|
|
48
47
|
persistResolvedBrokerConfig?: boolean;
|
|
49
48
|
onResult?: (result: SyncBrokerInstanceResult, instance: BrokerInstanceConfig, previousConfig: AppConfig) => void | Promise<void>;
|
|
50
49
|
}
|