gloomberb 0.5.1 → 0.6.1
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 +91 -11
- package/package.json +13 -5
- package/src/app.tsx +414 -191
- package/src/architecture/import-boundaries.test.ts +118 -0
- package/src/assets/gloomberb-logo.png +0 -0
- package/src/assets/gloomberb-logo.svg +73 -0
- package/src/brokers/account-cache.ts +157 -0
- package/src/brokers/profile-form.test.ts +73 -0
- package/src/brokers/profile-form.ts +109 -0
- package/src/brokers/remote-broker-adapter.ts +91 -0
- package/src/brokers/sync-broker-instance.test.ts +124 -1
- package/src/brokers/sync-broker-instance.ts +113 -17
- package/src/capabilities/factories.ts +100 -0
- package/src/capabilities/index.ts +4 -0
- package/src/capabilities/registry.test.ts +137 -0
- package/src/capabilities/registry.ts +131 -0
- package/src/capabilities/service-capabilities.ts +27 -0
- package/src/capabilities/types.ts +84 -0
- package/src/cli/commands/ticker.ts +11 -3
- package/src/cli/context.ts +19 -13
- package/src/cli/helpers.ts +1 -0
- package/src/cli/types.ts +2 -2
- package/src/components/chart/chart-controller.ts +4 -24
- package/src/components/chart/chart-data.ts +3 -14
- package/src/components/chart/chart-indicator-selector.tsx +28 -6
- package/src/components/chart/chart-pane-settings.ts +15 -28
- package/src/components/chart/chart-renderer.ts +74 -99
- package/src/components/chart/chart-resolution.ts +10 -10
- package/src/components/chart/chart-scroll.test.ts +6 -5
- package/src/components/chart/chart-scroll.ts +2 -2
- package/src/components/chart/chart-types.ts +0 -1
- package/src/components/chart/chart-viewport.ts +2 -1
- package/src/components/chart/chart.test.ts +37 -0
- package/src/components/chart/comparison-chart-data.ts +3 -17
- package/src/components/chart/comparison-chart-renderer.test.ts +27 -4
- package/src/components/chart/comparison-chart-renderer.ts +1 -1
- package/src/components/chart/comparison-stock-chart.tsx +318 -158
- package/src/components/chart/cursor-motion.ts +3 -2
- package/src/components/chart/indicators/bands.test.ts +0 -14
- package/src/components/chart/indicators/moving-averages.test.ts +0 -15
- package/src/components/chart/indicators/options.ts +3 -3
- package/src/components/chart/indicators/oscillators.test.ts +0 -21
- package/src/components/chart/indicators/types.ts +1 -2
- package/src/components/chart/native/chart-rasterizer.ts +37 -44
- package/src/components/chart/native/kitty-adapter.ts +2 -5
- package/src/components/chart/native/kitty-manager.ts +1 -1
- package/src/components/chart/native/kitty-protocol.ts +0 -1
- package/src/components/chart/native/kitty-support.ts +1 -1
- package/src/components/chart/native/kitty.test.ts +53 -13
- package/src/components/chart/native/renderer-selection.ts +1 -1
- package/src/components/chart/native/surface-manager.ts +1 -1
- package/src/components/chart/native/surface-visibility.ts +48 -0
- package/src/components/chart/static-chart-surface.test.tsx +51 -0
- package/src/components/chart/static-chart-surface.tsx +191 -0
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +242 -35
- package/src/components/chart/stock-chart.tsx +452 -191
- package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
- package/src/components/command-bar/command-bar.test.tsx +269 -20
- package/src/components/command-bar/command-bar.tsx +830 -353
- package/src/components/command-bar/command-registry.ts +10 -20
- package/src/components/command-bar/helpers.test.ts +1 -1
- package/src/components/command-bar/helpers.ts +9 -11
- package/src/components/command-bar/pane-template-display.ts +4 -8
- package/src/components/command-bar/root-shortcuts.ts +12 -11
- package/src/components/command-bar/view-model.test.ts +8 -42
- package/src/components/command-bar/view-model.ts +8 -36
- package/src/components/command-bar/workflow-ops.test.ts +76 -2
- package/src/components/command-bar/workflow-ops.ts +40 -9
- package/src/components/command-bar/workflow-types.ts +2 -1
- package/src/components/data-table-stack-view.test.tsx +206 -0
- package/src/components/data-table-stack-view.tsx +152 -0
- package/src/components/data-table-view.test.tsx +188 -0
- package/src/components/data-table-view.tsx +279 -0
- package/src/components/feed-data-table-stack-view.test.tsx +225 -0
- package/src/components/feed-data-table-stack-view.tsx +365 -0
- package/src/components/index.ts +26 -31
- package/src/components/layout/detached-pane-shell.tsx +185 -0
- package/src/components/layout/floating-pane.tsx +87 -14
- package/src/components/layout/header.tsx +139 -49
- package/src/components/layout/pane-content.tsx +42 -0
- package/src/components/layout/pane-footer.test.tsx +195 -0
- package/src/components/layout/pane-footer.tsx +425 -0
- package/src/components/layout/pane-header.test.tsx +39 -0
- package/src/components/layout/pane-header.tsx +192 -14
- package/src/components/layout/pane-keyboard-scroll.test.tsx +226 -0
- package/src/components/layout/pane-sizing.ts +9 -10
- package/src/components/layout/pane-title.ts +35 -0
- package/src/components/layout/pane.tsx +52 -11
- package/src/components/layout/shell.test.tsx +315 -107
- package/src/components/layout/shell.tsx +1010 -368
- package/src/components/layout/status-bar.test.tsx +17 -10
- package/src/components/layout/status-bar.tsx +367 -100
- package/src/components/layout/titlebar-overlay.ts +2 -0
- package/src/components/markdown-editor.tsx +29 -14
- package/src/components/markdown-text.tsx +25 -24
- package/src/components/onboarding/onboarding-wizard.test.tsx +8 -3
- package/src/components/onboarding/onboarding-wizard.tsx +347 -473
- package/src/components/pane-settings-dialog.tsx +521 -89
- package/src/components/pane-template-wizard.test.tsx +1 -1
- package/src/components/pane-template-wizard.tsx +38 -37
- package/src/components/price-selector-dialog.tsx +22 -21
- package/src/components/ticker-badge-list.tsx +53 -0
- package/src/components/ticker-badge-text.test.tsx +44 -1
- package/src/components/ticker-badge-text.tsx +78 -38
- package/src/components/ticker-badge.tsx +28 -7
- package/src/components/ticker-list-table-view.tsx +248 -0
- package/src/components/ticker-list-table.test.tsx +47 -1
- package/src/components/ticker-list-table.tsx +212 -41
- package/src/components/toggle-list.tsx +114 -8
- package/src/components/ui/button.tsx +24 -16
- package/src/components/ui/choice-dialog.tsx +89 -0
- package/src/components/ui/data-table.tsx +313 -114
- package/src/components/ui/external-link.tsx +52 -13
- package/src/components/ui/fields.tsx +44 -30
- package/src/components/ui/frame.tsx +24 -57
- package/src/components/ui/index.ts +15 -50
- package/src/components/ui/list-view.tsx +65 -25
- package/src/components/ui/loading.tsx +5 -60
- package/src/components/ui/message-composer.test.tsx +81 -0
- package/src/components/ui/message-composer.tsx +139 -0
- package/src/components/ui/multi-select-dialog.tsx +100 -32
- package/src/components/ui/multi-select.ts +112 -0
- package/src/components/ui/native-select.tsx +120 -0
- package/src/components/ui/page-stack-view.tsx +49 -19
- package/src/components/ui/remote-image.tsx +50 -0
- package/src/components/ui/row-value-cache.test.ts +17 -0
- package/src/components/ui/row-value-cache.ts +41 -0
- package/src/components/ui/shortcut-hint.tsx +55 -0
- package/src/components/ui/status.tsx +12 -67
- package/src/components/ui/table-layout.ts +59 -0
- package/src/components/ui/tabs.tsx +249 -27
- package/src/components/ui/toggle.tsx +19 -127
- package/src/components/ui/ui.test.tsx +266 -53
- package/src/components/use-double-click-activation.ts +7 -2
- package/src/components/use-external-link-footer.ts +79 -0
- package/src/core/app-services.ts +95 -0
- package/src/core/state/app-state.ts +886 -0
- package/src/{state → core/state}/session-persistence.ts +7 -6
- package/src/data/config-layout.ts +225 -0
- package/src/data/config-store-node.ts +390 -0
- package/src/data/config-store.test.ts +72 -0
- package/src/data/config-store.ts +35 -478
- package/src/data/sqlite-json.ts +2 -5
- package/src/data/sqlite-retry.ts +1 -0
- package/src/index.tsx +2 -68
- package/src/market-data/coordinator-subscriptions.test.ts +125 -0
- package/src/market-data/coordinator.test.ts +73 -4
- package/src/market-data/coordinator.ts +190 -24
- package/src/market-data/hooks.test.tsx +1 -1
- package/src/market-data/hooks.tsx +109 -87
- package/src/market-data/query-store.ts +3 -3
- package/src/market-data/request-types.test.ts +0 -14
- package/src/market-data/result-types.ts +1 -0
- package/src/news/aggregator.test.ts +267 -0
- package/src/news/aggregator.ts +452 -0
- package/src/news/hooks.ts +39 -0
- package/src/news/ticker-symbols.ts +21 -0
- package/src/news/types.ts +77 -0
- package/src/notifications/app-notifier.ts +21 -10
- package/src/pane-settings.test.ts +0 -2
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +124 -134
- package/src/plugins/builtin/ai/gloomberb-cli.ts +14 -13
- package/src/plugins/builtin/ai/providers.ts +1 -9
- package/src/plugins/builtin/ai/runner.ts +41 -5
- package/src/plugins/builtin/ai/screener-pane.test.tsx +37 -55
- package/src/plugins/builtin/ai/screener-pane.tsx +231 -276
- package/src/plugins/builtin/ai/settings.ts +0 -1
- package/src/plugins/builtin/ai/utils.ts +1 -71
- package/src/plugins/builtin/alerts/alert-engine.test.ts +1 -38
- package/src/plugins/builtin/alerts/index.test.tsx +61 -82
- package/src/plugins/builtin/alerts/index.tsx +263 -155
- package/src/plugins/builtin/alerts/types.ts +7 -0
- package/src/plugins/builtin/analytics/index.test.tsx +13 -8
- package/src/plugins/builtin/analytics/index.tsx +78 -120
- package/src/plugins/builtin/analytics/metrics.test.ts +0 -4
- package/src/plugins/builtin/ask-ai.test.tsx +55 -54
- package/src/plugins/builtin/broker-manager/index.test.tsx +168 -0
- package/src/plugins/builtin/broker-manager/index.tsx +759 -0
- package/src/plugins/builtin/broker-manager/model.test.ts +107 -0
- package/src/plugins/builtin/broker-manager/model.ts +142 -0
- package/src/plugins/builtin/chat-controller.test.ts +129 -0
- package/src/plugins/builtin/chat-controller.ts +85 -14
- package/src/plugins/builtin/chat.test.tsx +283 -36
- package/src/plugins/builtin/chat.tsx +363 -170
- package/src/plugins/builtin/cloud-auth-actions.tsx +72 -0
- package/src/plugins/builtin/cloud-tweets.tsx +896 -0
- package/src/plugins/builtin/comparison-chart.test.tsx +46 -57
- package/src/plugins/builtin/comparison-chart.tsx +11 -11
- package/src/plugins/builtin/correlation/compute.test.ts +0 -17
- package/src/plugins/builtin/correlation/index.tsx +51 -46
- package/src/plugins/builtin/correlation/settings.ts +1 -2
- package/src/plugins/builtin/debug.tsx +99 -70
- package/src/plugins/builtin/earnings/earnings-cache.test.ts +2 -2
- package/src/plugins/builtin/earnings/earnings-cache.ts +12 -3
- package/src/plugins/builtin/earnings/earnings-data.test.ts +42 -11
- package/src/plugins/builtin/earnings/earnings-data.ts +70 -5
- package/src/plugins/builtin/earnings/index.tsx +187 -94
- package/src/plugins/builtin/econ/calendar-source.ts +9 -116
- package/src/plugins/builtin/econ/fred-client.test.ts +1 -14
- package/src/plugins/builtin/econ/fred-series-map.ts +1 -0
- package/src/plugins/builtin/econ/index.tsx +236 -421
- package/src/plugins/builtin/econ/types.ts +1 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +43 -45
- package/src/plugins/builtin/fx-matrix/pairs.ts +1 -0
- package/src/plugins/builtin/help.test.tsx +120 -60
- package/src/plugins/builtin/help.tsx +266 -82
- package/src/plugins/builtin/holders/index.test.ts +45 -0
- package/src/plugins/builtin/holders/index.tsx +949 -0
- package/src/plugins/builtin/insider/index.tsx +207 -253
- package/src/plugins/builtin/insider/insider-data.test.ts +1 -11
- package/src/plugins/builtin/insider/insider-data.ts +1 -0
- package/src/plugins/builtin/layout-manager.tsx +12 -40
- package/src/plugins/builtin/market-movers/index.tsx +70 -144
- package/src/plugins/builtin/market-movers/screener.test.ts +0 -18
- package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +98 -0
- package/src/plugins/builtin/news-wire/breaking-notifications.ts +110 -0
- package/src/plugins/builtin/news-wire/breaking-pane.tsx +157 -0
- package/src/plugins/builtin/news-wire/categories.ts +114 -0
- package/src/plugins/builtin/news-wire/default-feeds.ts +3 -0
- package/src/plugins/builtin/news-wire/digest-store.ts +44 -0
- package/src/plugins/builtin/news-wire/feed-config.test.ts +90 -0
- package/src/plugins/builtin/news-wire/feed-config.ts +216 -0
- package/src/plugins/builtin/news-wire/feed-pane.tsx +21 -0
- package/src/plugins/builtin/news-wire/hash.ts +9 -0
- package/src/plugins/builtin/news-wire/index.tsx +92 -0
- package/src/plugins/builtin/news-wire/industry-pane.tsx +140 -0
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +256 -0
- package/src/plugins/builtin/news-wire/news-footer.ts +28 -0
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +94 -0
- package/src/plugins/builtin/news-wire/news-query-presets.ts +40 -0
- package/src/plugins/builtin/news-wire/news-table.test.tsx +206 -0
- package/src/plugins/builtin/news-wire/news-table.tsx +296 -0
- package/src/plugins/builtin/news-wire/persisted-articles.ts +92 -0
- package/src/plugins/builtin/news-wire/read-state.test.ts +30 -0
- package/src/plugins/builtin/news-wire/read-state.ts +76 -0
- package/src/plugins/builtin/news-wire/rss-parser.test.ts +184 -0
- package/src/plugins/builtin/news-wire/rss-parser.ts +213 -0
- package/src/plugins/builtin/news-wire/rss-source.test.ts +159 -0
- package/src/plugins/builtin/news-wire/rss-source.ts +181 -0
- package/src/plugins/builtin/news-wire/top-pane.tsx +21 -0
- package/src/plugins/builtin/news.tsx +98 -25
- package/src/plugins/builtin/notes-files.ts +51 -10
- package/src/plugins/builtin/notes.tsx +332 -131
- package/src/plugins/builtin/options.test.tsx +185 -0
- package/src/plugins/builtin/options.tsx +346 -206
- package/src/plugins/builtin/plugin-groups.ts +92 -0
- package/src/plugins/builtin/portfolio-list/header.tsx +72 -37
- package/src/plugins/builtin/portfolio-list/index.tsx +25 -42
- package/src/plugins/builtin/portfolio-list/metrics.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane.tsx +246 -164
- package/src/plugins/builtin/portfolio-list/settings.ts +19 -51
- package/src/plugins/builtin/portfolio-list/summary.tsx +21 -16
- package/src/plugins/builtin/portfolio-list/table.tsx +66 -25
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +1 -1
- package/src/plugins/builtin/portfolio-list.test.ts +60 -1
- package/src/plugins/builtin/portfolio-list.test.tsx +122 -55
- package/src/plugins/builtin/quote-monitor.test.tsx +2 -27
- package/src/plugins/builtin/research/index.tsx +724 -0
- package/src/plugins/builtin/sec.tsx +67 -19
- package/src/plugins/builtin/sectors/index.tsx +42 -80
- package/src/plugins/builtin/sectors/sector-data.ts +1 -0
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +25 -39
- package/src/plugins/builtin/ticker-detail/financials-tab.tsx +108 -58
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +383 -271
- package/src/plugins/builtin/ticker-detail/pane.tsx +143 -49
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +43 -27
- package/src/plugins/builtin/ticker-detail/settings.ts +3 -6
- package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +82 -10
- package/src/plugins/builtin/ticker-detail.test.tsx +258 -33
- package/src/plugins/builtin/ticker-surface.ts +68 -0
- package/src/plugins/builtin/world-indices/index.tsx +44 -82
- package/src/plugins/builtin/world-indices/indices.ts +1 -0
- package/src/plugins/builtin/yahoo.ts +55 -1
- package/src/plugins/builtin/yield-curve/index.tsx +87 -145
- package/src/plugins/builtin/yield-curve/treasury-data.ts +6 -77
- package/src/plugins/catalog-ui.test.ts +24 -0
- package/src/plugins/catalog-ui.ts +44 -0
- package/src/plugins/catalog.ts +2 -50
- package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
- package/src/plugins/ibkr/account-cache.test.ts +15 -14
- package/src/plugins/ibkr/account-cache.ts +15 -120
- package/src/plugins/ibkr/broker-adapter.ts +67 -0
- package/src/plugins/ibkr/config.test.ts +0 -18
- package/src/plugins/ibkr/dialogs.tsx +14 -56
- package/src/plugins/ibkr/flex.test.ts +57 -2
- package/src/plugins/ibkr/flex.ts +116 -12
- package/src/plugins/ibkr/gateway-helpers.ts +8 -3
- package/src/plugins/ibkr/gateway-service-native.ts +2381 -0
- package/src/plugins/ibkr/gateway-service.test.ts +47 -9
- package/src/plugins/ibkr/gateway-service.ts +264 -2235
- package/src/plugins/ibkr/gateway-types.ts +27 -0
- package/src/plugins/ibkr/index.test.tsx +3 -11
- package/src/plugins/ibkr/index.tsx +1 -0
- package/src/plugins/ibkr/price-normalization.test.ts +1 -6
- package/src/plugins/ibkr/price-normalization.ts +1 -0
- package/src/plugins/ibkr/trade-badge.tsx +6 -5
- package/src/plugins/ibkr/trade-tab.test.tsx +55 -3
- package/src/plugins/ibkr/trade-tab.tsx +166 -117
- package/src/plugins/ibkr/trading-pane.tsx +117 -83
- package/src/plugins/pane-manager.test.ts +3 -1
- package/src/plugins/pane-manager.ts +93 -19
- package/src/plugins/plugin-runtime.test.tsx +176 -74
- package/src/plugins/plugin-runtime.tsx +228 -10
- package/src/plugins/prediction-markets/cache.ts +49 -0
- package/src/plugins/prediction-markets/categories.ts +0 -10
- package/src/plugins/prediction-markets/chart.test.tsx +1 -1
- package/src/plugins/prediction-markets/chart.tsx +21 -20
- package/src/plugins/prediction-markets/cli.ts +1 -0
- package/src/plugins/prediction-markets/controller-data.ts +135 -89
- package/src/plugins/prediction-markets/controller.ts +37 -108
- package/src/plugins/prediction-markets/detail/book.tsx +23 -22
- package/src/plugins/prediction-markets/detail/outcomes.tsx +33 -32
- package/src/plugins/prediction-markets/detail/overview.tsx +17 -12
- package/src/plugins/prediction-markets/detail/pane.tsx +59 -45
- package/src/plugins/prediction-markets/detail/rules.tsx +22 -7
- package/src/plugins/prediction-markets/detail/shared.tsx +3 -2
- package/src/plugins/prediction-markets/detail/trades.tsx +12 -11
- package/src/plugins/prediction-markets/detail.test.tsx +1 -1
- package/src/plugins/prediction-markets/keyboard.ts +1 -0
- package/src/plugins/prediction-markets/launch.test.ts +1 -1
- package/src/plugins/prediction-markets/launch.ts +1 -1
- package/src/plugins/prediction-markets/metrics.ts +25 -12
- package/src/plugins/prediction-markets/pane.test.tsx +16 -7
- package/src/plugins/prediction-markets/pane.tsx +180 -82
- package/src/plugins/prediction-markets/plugin.test.tsx +37 -45
- package/src/plugins/prediction-markets/services/fetch.ts +27 -10
- package/src/plugins/prediction-markets/services/kalshi-adapter.ts +20 -3
- package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
- package/src/plugins/prediction-markets/settings.ts +0 -5
- package/src/plugins/prediction-markets/test-helpers.tsx +19 -44
- package/src/plugins/registry.test.ts +307 -0
- package/src/plugins/registry.ts +293 -82
- package/src/react/input.ts +61 -0
- package/src/react/plugins/plugin-slot.tsx +21 -0
- package/src/react/use-raf-callback.ts +44 -0
- package/src/renderers/electrobun/bun/application-menu-click.test.ts +41 -0
- package/src/renderers/electrobun/bun/application-menu-click.ts +55 -0
- package/src/renderers/electrobun/bun/application-menu.ts +118 -0
- package/src/renderers/electrobun/bun/context-menu-click.test.ts +67 -0
- package/src/renderers/electrobun/bun/context-menu-click.ts +61 -0
- package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +52 -0
- package/src/renderers/electrobun/bun/context-menu-normalize.ts +67 -0
- package/src/renderers/electrobun/bun/core-capabilities.ts +328 -0
- package/src/renderers/electrobun/bun/desktop-workspace.test.ts +63 -0
- package/src/renderers/electrobun/bun/desktop-workspace.ts +162 -0
- package/src/renderers/electrobun/bun/index.ts +1149 -0
- package/src/renderers/electrobun/bun/window-frame.test.ts +40 -0
- package/src/renderers/electrobun/bun/window-frame.ts +46 -0
- package/src/renderers/electrobun/shared/protocol.ts +72 -0
- package/src/renderers/electrobun/view/ai-host.ts +93 -0
- package/src/renderers/electrobun/view/app-services.ts +348 -0
- package/src/renderers/electrobun/view/application-menu-bridge.ts +12 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +173 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +154 -0
- package/src/renderers/electrobun/view/config-host.ts +35 -0
- package/src/renderers/electrobun/view/context-menu.test.ts +140 -0
- package/src/renderers/electrobun/view/context-menu.ts +164 -0
- package/src/renderers/electrobun/view/data-table.tsx +773 -0
- package/src/renderers/electrobun/view/desktop-controls.tsx +651 -0
- package/src/renderers/electrobun/view/desktop-window-bridge.ts +59 -0
- package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +57 -0
- package/src/renderers/electrobun/view/desktop-window-snapshot.ts +41 -0
- package/src/renderers/electrobun/view/dialog-host.tsx +62 -0
- package/src/renderers/electrobun/view/http-fetch.ts +113 -0
- package/src/renderers/electrobun/view/input-host.tsx +112 -0
- package/src/renderers/electrobun/view/key-event.test.ts +34 -0
- package/src/renderers/electrobun/view/key-event.ts +134 -0
- package/src/renderers/electrobun/view/main.tsx +104 -0
- package/src/renderers/electrobun/view/native-renderer.ts +110 -0
- package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +9 -0
- package/src/renderers/electrobun/view/native-stubs/chart-surface-manager.ts +25 -0
- package/src/renderers/electrobun/view/native-stubs/chart-surface-sync.ts +1 -0
- package/src/renderers/electrobun/view/notes-files.ts +56 -0
- package/src/renderers/electrobun/view/resource-store.test.ts +44 -0
- package/src/renderers/electrobun/view/resource-store.ts +106 -0
- package/src/renderers/electrobun/view/rpc-codec.ts +42 -0
- package/src/renderers/electrobun/view/scrollbar-activity.ts +23 -0
- package/src/renderers/electrobun/view/toast-host.tsx +65 -0
- package/src/renderers/electrobun/view/ui-host.tsx +1509 -0
- package/src/renderers/electrobun/view/update-host.ts +38 -0
- package/src/renderers/opentui/dialog-host.test.tsx +44 -0
- package/src/renderers/opentui/dialog-host.tsx +56 -0
- package/src/renderers/opentui/host.tsx +140 -0
- package/src/renderers/opentui/image-loader.test.ts +25 -0
- package/src/renderers/opentui/image-loader.ts +105 -0
- package/src/renderers/opentui/image-surface.tsx +266 -0
- package/src/renderers/opentui/input-host.tsx +63 -0
- package/src/renderers/opentui/start.tsx +71 -0
- package/src/renderers/opentui/test-utils.tsx +76 -0
- package/src/renderers/opentui/toast-host.tsx +10 -0
- package/src/renderers/opentui/ui-host.tsx +98 -0
- package/src/sources/gloomberb-cloud.test.ts +288 -17
- package/src/sources/gloomberb-cloud.ts +176 -9
- package/src/sources/provider-errors.ts +1 -0
- package/src/sources/provider-router.test.ts +220 -81
- package/src/sources/provider-router.ts +294 -111
- package/src/sources/sec-edgar.ts +19 -6
- package/src/sources/yahoo-finance.test.ts +0 -5
- package/src/sources/yahoo-finance.ts +420 -11
- package/src/state/app-activity.ts +7 -7
- package/src/state/app-bootstrap.ts +114 -40
- package/src/state/app-context-selector.test.tsx +70 -1
- package/src/state/app-context.test.ts +71 -3
- package/src/state/app-context.test.tsx +14 -7
- package/src/state/app-context.tsx +209 -869
- package/src/state/config-save-scheduler.ts +35 -0
- package/src/state/pane-scroll-registry.tsx +254 -0
- package/src/state/persist-scheduler.test.ts +67 -0
- package/src/state/persist-scheduler.ts +69 -0
- package/src/state/selectors-ui.ts +55 -0
- package/src/state/selectors.ts +15 -71
- package/src/state/session-persistence.test.ts +1 -1
- package/src/state/ticker-refresh-queue.ts +52 -3
- package/src/state/use-inline-tickers.ts +18 -16
- package/src/state/use-quote-streaming.test.ts +0 -6
- package/src/state/use-quote-streaming.test.tsx +1 -1
- package/src/state/use-quote-streaming.ts +3 -2
- package/src/state/use-session-snapshot.ts +32 -17
- package/src/test-support/plugin-runtime.ts +149 -0
- package/src/theme/color-utils.ts +1 -0
- package/src/theme/colors.ts +16 -0
- package/src/types/broker.ts +16 -1
- package/src/types/capability-route-source.ts +23 -0
- package/src/types/config.test.ts +23 -0
- package/src/types/config.ts +145 -10
- package/src/types/context-menu.ts +95 -0
- package/src/types/data-provider.ts +34 -3
- package/src/types/desktop-menu.ts +13 -0
- package/src/types/desktop-window.ts +29 -0
- package/src/types/financials.ts +123 -2
- package/src/types/instrument.ts +1 -0
- package/src/types/news-source.ts +9 -0
- package/src/types/persistence.ts +1 -0
- package/src/types/plugin.ts +32 -8
- package/src/ui/ascii-font.ts +85 -0
- package/src/ui/context-menu.test.ts +94 -0
- package/src/ui/context-menu.tsx +433 -0
- package/src/ui/dialog.tsx +55 -0
- package/src/ui/host.tsx +364 -0
- package/src/ui/index.tsx +134 -0
- package/src/ui/toast.tsx +40 -0
- package/src/updater.test.ts +69 -0
- package/src/updater.ts +61 -18
- package/src/utils/api-client.test.ts +99 -9
- package/src/utils/api-client.ts +378 -18
- package/src/utils/back-navigation.ts +6 -0
- package/src/utils/cli-output.ts +1 -0
- package/src/utils/currency-units.test.ts +1 -6
- package/src/utils/currency-units.ts +1 -0
- package/src/utils/debug-log.ts +68 -0
- package/src/utils/dom-screenshot.ts +326 -0
- package/src/utils/exchange-rate-map.ts +1 -0
- package/src/utils/exchanges.ts +214 -0
- package/src/utils/format.test.ts +7 -0
- package/src/utils/format.ts +1 -0
- package/src/utils/fuzzy-search.ts +1 -0
- package/src/utils/html-entities.test.ts +8 -0
- package/src/utils/html-entities.ts +32 -0
- package/src/utils/http-transport.ts +11 -0
- package/src/utils/main-thread-monitor.ts +127 -0
- package/src/utils/perf-marks.ts +54 -0
- package/src/utils/quote-freshness.ts +27 -50
- package/src/utils/quote-resolution.test.ts +5 -5
- package/src/utils/quote-resolution.ts +0 -1
- package/src/utils/quote-time.ts +1 -5
- package/src/utils/sec.test.ts +0 -12
- package/src/utils/selection-clipboard.ts +14 -6
- package/src/utils/shortcut-labels.test.ts +20 -0
- package/src/utils/shortcut-labels.ts +67 -0
- package/src/utils/slugify.ts +1 -0
- package/src/utils/terminal-input-reset.ts +1 -0
- package/src/utils/text-wrap.ts +82 -0
- package/src/utils/throttled-fetch.test.ts +19 -0
- package/src/utils/throttled-fetch.ts +11 -9
- package/src/utils/ticker-list.test.ts +1 -7
- package/src/utils/ticker-list.ts +1 -0
- package/src/utils/timestamp.ts +29 -0
- package/src/utils/tweet-text.test.ts +13 -0
- package/src/utils/tweet-text.ts +18 -0
- package/src/utils/url.ts +11 -0
- package/src/version.ts +1 -1
- package/src/components/chart/chart-control-hint.tsx +0 -39
- package/src/components/detail-feed-view.test.tsx +0 -180
- package/src/components/detail-feed-view.tsx +0 -531
- package/src/components/layout/pixel-mouse-mode.test.ts +0 -122
- package/src/components/layout/pixel-mouse.test.ts +0 -40
- package/src/components/spinner.tsx +0 -2
- package/src/components/tab-bar.tsx +0 -17
- package/src/components/ui/multi-select-chips.tsx +0 -69
- package/src/plugins/builtin/ai/index.test.tsx +0 -139
- package/src/plugins/builtin/ask-ai.tsx +0 -9
- package/src/plugins/builtin/correlation/plugin.test.ts +0 -44
- package/src/plugins/builtin/correlation/settings.test.ts +0 -34
- package/src/plugins/builtin/econ/calendar-source.test.ts +0 -219
- package/src/plugins/builtin/econ/fred-client.ts +0 -79
- package/src/plugins/builtin/fred-settings.ts +0 -10
- package/src/plugins/builtin/options-availability.test.tsx +0 -179
- package/src/plugins/builtin/options-availability.ts +0 -158
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +0 -61
- package/src/plugins/builtin/yield-curve/treasury-data.test.ts +0 -212
- package/src/plugins/prediction-markets/status.ts +0 -24
- package/src/plugins/prediction-markets/table.tsx +0 -87
- package/src/utils/quote-time.test.ts +0 -22
- package/src/utils/selection-clipboard.test.ts +0 -43
- package/src/utils/terminal-input-reset.test.ts +0 -50
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { Box, ScrollBox, Text } from "../../ui";
|
|
2
|
+
import { TextAttributes } from "../../ui";
|
|
3
|
+
import { usePaneFooter } from "../../components";
|
|
4
|
+
import { useShortcut } from "../../react/input";
|
|
5
|
+
import { useDialog } from "../../ui/dialog";
|
|
6
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
5
7
|
import { resolveTickerFinancialsForInstrument } from "../../market-data/coordinator";
|
|
6
8
|
import { instrumentFromTicker } from "../../market-data/request-types";
|
|
7
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
useAppDispatch,
|
|
11
|
+
useAppSelector,
|
|
12
|
+
usePaneCollection,
|
|
13
|
+
usePaneInstanceId,
|
|
14
|
+
} from "../../state/app-context";
|
|
8
15
|
import { colors, priceColor } from "../../theme/colors";
|
|
9
16
|
import type { PaneProps } from "../../types/plugin";
|
|
10
17
|
import type { Quote } from "../../types/financials";
|
|
11
18
|
import { formatCurrency, padTo } from "../../utils/format";
|
|
12
19
|
import { formatMarketPrice, formatMarketQuantity } from "../../utils/market-format";
|
|
13
20
|
import { getBrokerInstance } from "../../utils/broker-instances";
|
|
14
|
-
import {
|
|
21
|
+
import { usePluginPaneActions } from "../plugin-runtime";
|
|
15
22
|
import { isGatewayConfigured } from "./config";
|
|
16
23
|
import { ChoiceDialog } from "./dialogs";
|
|
17
24
|
import { useIbkrGatewaySelection } from "./gateway-selection";
|
|
@@ -32,9 +39,13 @@ import {
|
|
|
32
39
|
} from "./trade-utils";
|
|
33
40
|
|
|
34
41
|
export function TradingPane({ focused, width, height }: PaneProps) {
|
|
35
|
-
const
|
|
42
|
+
const dispatch = useAppDispatch();
|
|
43
|
+
const config = useAppSelector((state) => state.config);
|
|
44
|
+
const brokerAccounts = useAppSelector((state) => state.brokerAccounts);
|
|
45
|
+
const tickers = useAppSelector((state) => state.tickers);
|
|
36
46
|
const paneId = usePaneInstanceId();
|
|
37
47
|
const { collectionId } = usePaneCollection(paneId);
|
|
48
|
+
const { selectTicker, switchTab, switchPanel } = usePluginPaneActions();
|
|
38
49
|
const dialog = useDialog();
|
|
39
50
|
const tradeState = useTradingPaneState();
|
|
40
51
|
const {
|
|
@@ -49,8 +60,8 @@ export function TradingPane({ focused, width, height }: PaneProps) {
|
|
|
49
60
|
availableAccounts,
|
|
50
61
|
gatewayRequiredMessage,
|
|
51
62
|
} = useIbkrGatewaySelection(
|
|
52
|
-
|
|
53
|
-
|
|
63
|
+
config,
|
|
64
|
+
brokerAccounts,
|
|
54
65
|
collectionId,
|
|
55
66
|
tradeState.brokerInstanceId,
|
|
56
67
|
);
|
|
@@ -96,7 +107,7 @@ export function TradingPane({ focused, width, height }: PaneProps) {
|
|
|
96
107
|
useEffect(() => {
|
|
97
108
|
if (!isGatewayMode || availableAccounts.length === 0 || tradeState.accountId || !selectedInstance) return;
|
|
98
109
|
const inferred = inferDraftAccountId(
|
|
99
|
-
|
|
110
|
+
config,
|
|
100
111
|
collectionId,
|
|
101
112
|
availableAccounts,
|
|
102
113
|
selectedInstance.id,
|
|
@@ -105,7 +116,7 @@ export function TradingPane({ focused, width, height }: PaneProps) {
|
|
|
105
116
|
if (inferred) {
|
|
106
117
|
updateTradingPaneState({ accountId: inferred });
|
|
107
118
|
}
|
|
108
|
-
}, [isGatewayMode, availableAccounts, tradeState.accountId,
|
|
119
|
+
}, [isGatewayMode, availableAccounts, tradeState.accountId, config, collectionId, selectedInstance]);
|
|
109
120
|
|
|
110
121
|
const refresh = useCallback(async () => {
|
|
111
122
|
if (!selectedInstance || !normalizedConfig || !isGatewayMode || !isGatewayConfigured(selectedInstance.config)) {
|
|
@@ -117,7 +128,7 @@ export function TradingPane({ focused, width, height }: PaneProps) {
|
|
|
117
128
|
setTradingBusy(true);
|
|
118
129
|
await refreshGatewayData(selectedInstance);
|
|
119
130
|
const inferred = inferDraftAccountId(
|
|
120
|
-
|
|
131
|
+
config,
|
|
121
132
|
collectionId,
|
|
122
133
|
availableAccounts,
|
|
123
134
|
selectedInstance.id,
|
|
@@ -136,7 +147,7 @@ export function TradingPane({ focused, width, height }: PaneProps) {
|
|
|
136
147
|
selectedInstance,
|
|
137
148
|
normalizedConfig,
|
|
138
149
|
isGatewayMode,
|
|
139
|
-
|
|
150
|
+
config,
|
|
140
151
|
collectionId,
|
|
141
152
|
availableAccounts,
|
|
142
153
|
tradeState.accountId,
|
|
@@ -165,13 +176,13 @@ export function TradingPane({ focused, width, height }: PaneProps) {
|
|
|
165
176
|
choices={gatewayInstances.map((instance) => ({
|
|
166
177
|
id: instance.id,
|
|
167
178
|
label: instance.label,
|
|
168
|
-
|
|
179
|
+
description: "Gateway / TWS",
|
|
169
180
|
}))}
|
|
170
181
|
/>
|
|
171
182
|
),
|
|
172
183
|
});
|
|
173
184
|
if (!selected) return;
|
|
174
|
-
const instance = getBrokerInstance(
|
|
185
|
+
const instance = getBrokerInstance(config.brokerInstances, selected);
|
|
175
186
|
if (!instance) return;
|
|
176
187
|
updateTradingPaneState({
|
|
177
188
|
brokerInstanceId: instance.id,
|
|
@@ -181,7 +192,7 @@ export function TradingPane({ focused, width, height }: PaneProps) {
|
|
|
181
192
|
lastError: undefined,
|
|
182
193
|
lastInfo: undefined,
|
|
183
194
|
});
|
|
184
|
-
}, [dialog, gatewayInstances, lockedBrokerInstanceId,
|
|
195
|
+
}, [dialog, gatewayInstances, lockedBrokerInstanceId, config.brokerInstances]);
|
|
185
196
|
|
|
186
197
|
const chooseAccount = useCallback(async () => {
|
|
187
198
|
if (!selectedInstance || !normalizedConfig || !gatewayService || !isGatewayMode) return;
|
|
@@ -190,7 +201,7 @@ export function TradingPane({ focused, width, height }: PaneProps) {
|
|
|
190
201
|
}
|
|
191
202
|
|
|
192
203
|
const nextAccounts = getKnownIbkrAccounts(
|
|
193
|
-
|
|
204
|
+
brokerAccounts,
|
|
194
205
|
selectedInstance.id,
|
|
195
206
|
gatewayService.getSnapshot().accounts,
|
|
196
207
|
);
|
|
@@ -207,14 +218,14 @@ export function TradingPane({ focused, width, height }: PaneProps) {
|
|
|
207
218
|
choices={nextAccounts.map((account) => ({
|
|
208
219
|
id: account.accountId,
|
|
209
220
|
label: `${selectedInstance.label} → ${account.accountId}`,
|
|
210
|
-
|
|
221
|
+
description: `${formatCurrency(account.netLiquidation || 0, account.currency || "USD")} net liq`,
|
|
211
222
|
}))}
|
|
212
223
|
/>
|
|
213
224
|
),
|
|
214
225
|
});
|
|
215
226
|
if (!selected) return;
|
|
216
227
|
updateTradingPaneState({ accountId: selected });
|
|
217
|
-
}, [
|
|
228
|
+
}, [availableAccounts, brokerAccounts, dialog, gatewayService, isGatewayMode, normalizedConfig, refresh, selectedInstance]);
|
|
218
229
|
|
|
219
230
|
const cancelSelectedOrder = useCallback(async () => {
|
|
220
231
|
if (!selectedOrder || !selectedInstance || !normalizedConfig || !gatewayService || !isGatewayMode) return;
|
|
@@ -232,8 +243,7 @@ export function TradingPane({ focused, width, height }: PaneProps) {
|
|
|
232
243
|
|
|
233
244
|
const openSelectedOrder = useCallback(() => {
|
|
234
245
|
if (!selectedOrder) return;
|
|
235
|
-
const
|
|
236
|
-
const ticker = findTickerForOrder(selectedOrder, state.tickers);
|
|
246
|
+
const ticker = findTickerForOrder(selectedOrder, tickers);
|
|
237
247
|
if (!ticker) {
|
|
238
248
|
setTradingMessage(undefined, `No local ticker exists for ${selectedOrder.contract.localSymbol || selectedOrder.contract.symbol}.`);
|
|
239
249
|
return;
|
|
@@ -245,12 +255,43 @@ export function TradingPane({ focused, width, height }: PaneProps) {
|
|
|
245
255
|
lastInfo: `Loaded order ${selectedOrder.orderId} into ${ticker.metadata.ticker}.`,
|
|
246
256
|
lastError: undefined,
|
|
247
257
|
});
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}, [selectedOrder,
|
|
258
|
+
selectTicker(ticker.metadata.ticker, paneId);
|
|
259
|
+
switchTab("ibkr-trade", paneId);
|
|
260
|
+
switchPanel("right");
|
|
261
|
+
}, [selectedOrder, tickers, paneId, selectTicker, switchTab, switchPanel]);
|
|
262
|
+
|
|
263
|
+
const footerActionsRef = useRef<{
|
|
264
|
+
chooseBrokerInstance: () => Promise<void>;
|
|
265
|
+
chooseAccount: () => Promise<void>;
|
|
266
|
+
openSelectedOrder: () => void;
|
|
267
|
+
cancelSelectedOrder: () => Promise<void>;
|
|
268
|
+
refresh: () => Promise<void>;
|
|
269
|
+
}>({
|
|
270
|
+
chooseBrokerInstance: async () => {},
|
|
271
|
+
chooseAccount: async () => {},
|
|
272
|
+
openSelectedOrder: () => {},
|
|
273
|
+
cancelSelectedOrder: async () => {},
|
|
274
|
+
refresh: async () => {},
|
|
275
|
+
});
|
|
276
|
+
footerActionsRef.current = {
|
|
277
|
+
chooseBrokerInstance,
|
|
278
|
+
chooseAccount,
|
|
279
|
+
openSelectedOrder,
|
|
280
|
+
cancelSelectedOrder,
|
|
281
|
+
refresh,
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
usePaneFooter("ibkr-trading-pane", () => ({
|
|
285
|
+
hints: [
|
|
286
|
+
{ id: "profile", key: "i", label: "profile", onPress: () => footerActionsRef.current.chooseBrokerInstance().catch(() => {}) },
|
|
287
|
+
{ id: "account", key: "a", label: "ccount", onPress: () => footerActionsRef.current.chooseAccount().catch(() => {}) },
|
|
288
|
+
{ id: "open", key: "m", label: "open", onPress: () => footerActionsRef.current.openSelectedOrder() },
|
|
289
|
+
{ id: "cancel", key: "c", label: "ancel", onPress: () => footerActionsRef.current.cancelSelectedOrder().catch(() => {}) },
|
|
290
|
+
{ id: "refresh", key: "r", label: "efresh", onPress: () => footerActionsRef.current.refresh().catch(() => {}) },
|
|
291
|
+
],
|
|
292
|
+
}), []);
|
|
252
293
|
|
|
253
|
-
|
|
294
|
+
useShortcut((event) => {
|
|
254
295
|
if (!focused) return;
|
|
255
296
|
event.stopPropagation?.();
|
|
256
297
|
|
|
@@ -290,18 +331,18 @@ export function TradingPane({ focused, width, height }: PaneProps) {
|
|
|
290
331
|
const activeAccount = availableAccounts.find((account) => account.accountId === (tradeState.accountId || ""));
|
|
291
332
|
const orderPanelWidth = Math.max(36, Math.floor(width * 0.6));
|
|
292
333
|
const listPanelWidth = Math.max(24, width - orderPanelWidth - 1);
|
|
293
|
-
const listHeight = Math.max(4, height -
|
|
334
|
+
const listHeight = Math.max(4, height - 4);
|
|
294
335
|
const getOrderQuote = useCallback((symbol: string): Quote | null => {
|
|
295
|
-
const ticker =
|
|
336
|
+
const ticker = tickers.get(symbol) ?? null;
|
|
296
337
|
const instrument = instrumentFromTicker(ticker, symbol);
|
|
297
338
|
return instrument ? resolveTickerFinancialsForInstrument(instrument)?.quote ?? null : null;
|
|
298
|
-
}, [
|
|
339
|
+
}, [tickers]);
|
|
299
340
|
|
|
300
341
|
return (
|
|
301
|
-
<
|
|
302
|
-
<
|
|
303
|
-
<
|
|
304
|
-
<
|
|
342
|
+
<Box flexDirection="column" flexGrow={1} paddingX={1}>
|
|
343
|
+
<Box flexDirection="row" height={1}>
|
|
344
|
+
<Box flexGrow={1}>
|
|
345
|
+
<Text fg={
|
|
305
346
|
displayStatusState === "connected"
|
|
306
347
|
? colors.positive
|
|
307
348
|
: displayStatusState === "error"
|
|
@@ -311,13 +352,13 @@ export function TradingPane({ focused, width, height }: PaneProps) {
|
|
|
311
352
|
{selectedInstance
|
|
312
353
|
? `${selectedInstance.label} · ${isGatewayMode ? "Gateway" : "Flex"} · ${displayStatusState}`
|
|
313
354
|
: "IBKR · no profile selected"}
|
|
314
|
-
</
|
|
315
|
-
</
|
|
316
|
-
{tradeState.busy && <
|
|
317
|
-
</
|
|
355
|
+
</Text>
|
|
356
|
+
</Box>
|
|
357
|
+
{tradeState.busy && <Text fg={colors.textDim}>Working…</Text>}
|
|
358
|
+
</Box>
|
|
318
359
|
|
|
319
|
-
<
|
|
320
|
-
<
|
|
360
|
+
<Box height={1}>
|
|
361
|
+
<Text fg={colors.textDim}>
|
|
321
362
|
{activeAccount
|
|
322
363
|
? `${selectedInstance?.label || "IBKR"} → ${activeAccount.accountId} · ${formatCurrency(activeAccount.netLiquidation || 0, activeAccount.currency || "USD")} net liq`
|
|
323
364
|
: isGatewayMode
|
|
@@ -327,29 +368,29 @@ export function TradingPane({ focused, width, height }: PaneProps) {
|
|
|
327
368
|
: gatewayInstances.length > 0
|
|
328
369
|
? "Choose a Gateway / TWS profile"
|
|
329
370
|
: "Connect an IBKR profile"}
|
|
330
|
-
</
|
|
331
|
-
</
|
|
371
|
+
</Text>
|
|
372
|
+
</Box>
|
|
332
373
|
|
|
333
|
-
<
|
|
334
|
-
<
|
|
374
|
+
<Box height={1}>
|
|
375
|
+
<Text fg={tradeState.lastError ? colors.negative : colors.textDim}>
|
|
335
376
|
{tradeState.lastError
|
|
336
377
|
|| gatewaySnapshot.status.message
|
|
337
378
|
|| gatewaySnapshot.lastError
|
|
338
379
|
|| tradeState.lastInfo
|
|
339
380
|
|| "Use this console for profile status, accounts, open orders, and executions."}
|
|
340
|
-
</
|
|
341
|
-
</
|
|
381
|
+
</Text>
|
|
382
|
+
</Box>
|
|
342
383
|
|
|
343
|
-
<
|
|
344
|
-
<
|
|
345
|
-
</
|
|
384
|
+
<Box height={1}>
|
|
385
|
+
<Text fg={colors.border}>{"─".repeat(Math.max(1, width - 2))}</Text>
|
|
386
|
+
</Box>
|
|
346
387
|
|
|
347
|
-
<
|
|
348
|
-
<
|
|
349
|
-
<
|
|
350
|
-
<
|
|
388
|
+
<Box flexDirection="row" height={listHeight}>
|
|
389
|
+
<Box width={orderPanelWidth} flexDirection="column">
|
|
390
|
+
<Text attributes={TextAttributes.BOLD} fg={colors.textBright}>Open Orders</Text>
|
|
391
|
+
<ScrollBox flexGrow={1} scrollY>
|
|
351
392
|
{gatewaySnapshot.openOrders.length === 0 ? (
|
|
352
|
-
<
|
|
393
|
+
<Text fg={colors.textDim}>No open IBKR orders.</Text>
|
|
353
394
|
) : (
|
|
354
395
|
gatewaySnapshot.openOrders.map((order, index) => {
|
|
355
396
|
const selected = index === tradeState.selectedOpenOrderIndex;
|
|
@@ -363,7 +404,7 @@ export function TradingPane({ focused, width, height }: PaneProps) {
|
|
|
363
404
|
? formatMarketPrice(order.stopPrice, { contractSecType: order.contract.secType, maxWidth: 9 })
|
|
364
405
|
: "MKT";
|
|
365
406
|
return (
|
|
366
|
-
<
|
|
407
|
+
<Box
|
|
367
408
|
key={order.orderId}
|
|
368
409
|
backgroundColor={selected ? colors.selected : colors.bg}
|
|
369
410
|
onMouseDown={() => {
|
|
@@ -374,7 +415,7 @@ export function TradingPane({ focused, width, height }: PaneProps) {
|
|
|
374
415
|
}
|
|
375
416
|
}}
|
|
376
417
|
>
|
|
377
|
-
<
|
|
418
|
+
<Text fg={selected ? colors.text : colors.textDim}>
|
|
378
419
|
{selected ? "▸ " : " "}
|
|
379
420
|
{padTo(String(order.orderId), 6)}
|
|
380
421
|
{padTo(order.action, 5)}
|
|
@@ -385,55 +426,48 @@ export function TradingPane({ focused, width, height }: PaneProps) {
|
|
|
385
426
|
{padTo(orderPrice, 9)}
|
|
386
427
|
{padTo(`B:${bidStr}`, 10)}
|
|
387
428
|
{`A:${askStr}`}
|
|
388
|
-
</
|
|
389
|
-
</
|
|
429
|
+
</Text>
|
|
430
|
+
</Box>
|
|
390
431
|
);
|
|
391
432
|
})
|
|
392
433
|
)}
|
|
393
|
-
</
|
|
394
|
-
</
|
|
434
|
+
</ScrollBox>
|
|
435
|
+
</Box>
|
|
395
436
|
|
|
396
|
-
<
|
|
397
|
-
<
|
|
398
|
-
</
|
|
437
|
+
<Box width={1}>
|
|
438
|
+
<Text fg={colors.border}>│</Text>
|
|
439
|
+
</Box>
|
|
399
440
|
|
|
400
|
-
<
|
|
401
|
-
<
|
|
402
|
-
<
|
|
441
|
+
<Box width={listPanelWidth} flexDirection="column">
|
|
442
|
+
<Text attributes={TextAttributes.BOLD} fg={colors.textBright}>Executions</Text>
|
|
443
|
+
<ScrollBox flexGrow={1} scrollY>
|
|
403
444
|
{gatewaySnapshot.executions.length === 0 ? (
|
|
404
|
-
<
|
|
445
|
+
<Text fg={colors.textDim}>No recent executions.</Text>
|
|
405
446
|
) : (
|
|
406
447
|
gatewaySnapshot.executions.slice(0, 20).map((execution) => (
|
|
407
|
-
<
|
|
448
|
+
<Box
|
|
408
449
|
key={execution.execId}
|
|
409
450
|
onMouseDown={() => {
|
|
410
451
|
const symbol = execution.contract.symbol;
|
|
411
|
-
if (symbol &&
|
|
412
|
-
|
|
452
|
+
if (symbol && tickers.has(symbol)) {
|
|
453
|
+
selectTicker(symbol, paneId);
|
|
413
454
|
}
|
|
414
455
|
}}
|
|
415
456
|
>
|
|
416
|
-
<
|
|
457
|
+
<Text fg={priceColor(execution.side.toUpperCase() === "BOT" ? 1 : -1)}>
|
|
417
458
|
{padTo(execution.side, 5)}
|
|
418
459
|
{padTo(execution.contract.localSymbol || execution.contract.symbol, 18)}
|
|
419
460
|
{padTo(formatMarketQuantity(execution.shares, { contractSecType: execution.contract.secType, maxWidth: 6 }), 6, "right")}
|
|
420
461
|
{" "}
|
|
421
462
|
{formatMarketPrice(execution.price, { contractSecType: execution.contract.secType })}
|
|
422
|
-
</
|
|
423
|
-
</
|
|
463
|
+
</Text>
|
|
464
|
+
</Box>
|
|
424
465
|
))
|
|
425
466
|
)}
|
|
426
|
-
</
|
|
427
|
-
</
|
|
428
|
-
</
|
|
467
|
+
</ScrollBox>
|
|
468
|
+
</Box>
|
|
469
|
+
</Box>
|
|
429
470
|
|
|
430
|
-
|
|
431
|
-
<text fg={colors.textMuted} onMouseDown={() => chooseBrokerInstance().catch(() => {})}>{" [i] Profile "}</text>
|
|
432
|
-
<text fg={colors.textMuted} onMouseDown={() => chooseAccount().catch(() => {})}>{" [a] Account "}</text>
|
|
433
|
-
<text fg={colors.textMuted} onMouseDown={() => openSelectedOrder()}>{" [Enter] Open "}</text>
|
|
434
|
-
<text fg={colors.textMuted} onMouseDown={() => cancelSelectedOrder().catch(() => {})}>{" [c] Cancel "}</text>
|
|
435
|
-
<text fg={colors.textMuted} onMouseDown={() => refresh().catch(() => {})}>{" [r] Refresh "}</text>
|
|
436
|
-
</box>
|
|
437
|
-
</box>
|
|
471
|
+
</Box>
|
|
438
472
|
);
|
|
439
473
|
}
|
|
@@ -19,6 +19,7 @@ describe("pane-manager split-tree drops", () => {
|
|
|
19
19
|
...cloneLayout(config.layout),
|
|
20
20
|
dockRoot: null,
|
|
21
21
|
floating: [],
|
|
22
|
+
detached: [],
|
|
22
23
|
instances: [],
|
|
23
24
|
};
|
|
24
25
|
const floatingOnly = addPaneFloating(layout, createPaneInstance("chat"), 120, 30);
|
|
@@ -71,7 +72,7 @@ describe("pane-manager split-tree drops", () => {
|
|
|
71
72
|
const next = gridlockAllPanes(layout);
|
|
72
73
|
|
|
73
74
|
expect(next.floating).toHaveLength(0);
|
|
74
|
-
expect(getDockedPaneIds(next)).toHaveLength(
|
|
75
|
+
expect(getDockedPaneIds(next)).toHaveLength(layout.instances.length);
|
|
75
76
|
});
|
|
76
77
|
|
|
77
78
|
test("gridlock infers a matching tiled layout from arranged windows", () => {
|
|
@@ -80,6 +81,7 @@ describe("pane-manager split-tree drops", () => {
|
|
|
80
81
|
...cloneLayout(config.layout),
|
|
81
82
|
dockRoot: null,
|
|
82
83
|
floating: [],
|
|
84
|
+
detached: [],
|
|
83
85
|
instances: [],
|
|
84
86
|
};
|
|
85
87
|
const leftPane = createPaneInstance("chat");
|
|
@@ -81,6 +81,11 @@ export interface DockDividerLayout {
|
|
|
81
81
|
ratio: number;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
export interface DockGeometryOptions {
|
|
85
|
+
precise?: boolean;
|
|
86
|
+
dividerSize?: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
84
89
|
export interface LayoutSimulation {
|
|
85
90
|
layout: LayoutConfig;
|
|
86
91
|
previewRect: LayoutBounds | null;
|
|
@@ -202,9 +207,24 @@ function removeNodeAtPath(node: DockLayoutNode | null, path: Array<0 | 1>): Dock
|
|
|
202
207
|
|
|
203
208
|
function normalizeFloatingEntries(layout: LayoutConfig): FloatingPaneEntry[] {
|
|
204
209
|
const dockedIds = new Set(getDockedInstanceIds(layout.dockRoot));
|
|
210
|
+
const detachedIds = new Set((layout.detached ?? []).map((entry) => entry.instanceId));
|
|
205
211
|
const seen = new Set<string>();
|
|
206
212
|
return layout.floating
|
|
207
|
-
.filter((entry) => !dockedIds.has(entry.instanceId))
|
|
213
|
+
.filter((entry) => !dockedIds.has(entry.instanceId) && !detachedIds.has(entry.instanceId))
|
|
214
|
+
.filter((entry) => {
|
|
215
|
+
if (seen.has(entry.instanceId)) return false;
|
|
216
|
+
seen.add(entry.instanceId);
|
|
217
|
+
return true;
|
|
218
|
+
})
|
|
219
|
+
.map((entry) => ({ ...entry }));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function normalizeDetachedEntries(layout: LayoutConfig) {
|
|
223
|
+
const dockedIds = new Set(getDockedInstanceIds(layout.dockRoot));
|
|
224
|
+
const floatingIds = new Set(layout.floating.map((entry) => entry.instanceId));
|
|
225
|
+
const seen = new Set<string>();
|
|
226
|
+
return (layout.detached ?? [])
|
|
227
|
+
.filter((entry) => !dockedIds.has(entry.instanceId) && !floatingIds.has(entry.instanceId))
|
|
208
228
|
.filter((entry) => {
|
|
209
229
|
if (seen.has(entry.instanceId)) return false;
|
|
210
230
|
seen.add(entry.instanceId);
|
|
@@ -249,6 +269,7 @@ function captureDockedMemory(layout: LayoutConfig, dockRoot: DockLayoutNode | nu
|
|
|
249
269
|
function capturePlacementMemory(layout: LayoutConfig): LayoutConfig {
|
|
250
270
|
const dockedMemory = captureDockedMemory(layout, layout.dockRoot);
|
|
251
271
|
const floatingById = new Map(layout.floating.map((entry) => [entry.instanceId, entry] as const));
|
|
272
|
+
const detachedById = new Map((layout.detached ?? []).map((entry) => [entry.instanceId, entry] as const));
|
|
252
273
|
|
|
253
274
|
return {
|
|
254
275
|
...layout,
|
|
@@ -262,12 +283,20 @@ function capturePlacementMemory(layout: LayoutConfig): LayoutConfig {
|
|
|
262
283
|
width: floating.width,
|
|
263
284
|
height: floating.height,
|
|
264
285
|
} : previous.floating;
|
|
286
|
+
const detached = detachedById.get(instance.instanceId);
|
|
287
|
+
const nextDetached = detached ? {
|
|
288
|
+
x: detached.x,
|
|
289
|
+
y: detached.y,
|
|
290
|
+
width: detached.width,
|
|
291
|
+
height: detached.height,
|
|
292
|
+
} : previous.detached;
|
|
265
293
|
|
|
266
294
|
return {
|
|
267
295
|
...instance,
|
|
268
|
-
placementMemory: nextDocked || nextFloating ? {
|
|
296
|
+
placementMemory: nextDocked || nextFloating || nextDetached ? {
|
|
269
297
|
docked: nextDocked,
|
|
270
298
|
floating: nextFloating,
|
|
299
|
+
detached: nextDetached,
|
|
271
300
|
} : undefined,
|
|
272
301
|
};
|
|
273
302
|
}),
|
|
@@ -278,6 +307,7 @@ function finalizeLayout(layout: LayoutConfig): LayoutConfig {
|
|
|
278
307
|
const normalized = normalizePaneLayout({
|
|
279
308
|
...layout,
|
|
280
309
|
floating: normalizeFloatingEntries(layout),
|
|
310
|
+
detached: normalizeDetachedEntries(layout),
|
|
281
311
|
});
|
|
282
312
|
return capturePlacementMemory(normalized);
|
|
283
313
|
}
|
|
@@ -313,6 +343,7 @@ function detachPane(layout: LayoutConfig, instanceId: string): LayoutConfig {
|
|
|
313
343
|
return {
|
|
314
344
|
...removeDockedLeaf(layout, instanceId),
|
|
315
345
|
floating: layout.floating.filter((entry) => entry.instanceId !== instanceId),
|
|
346
|
+
detached: (layout.detached ?? []).filter((entry) => entry.instanceId !== instanceId),
|
|
316
347
|
};
|
|
317
348
|
}
|
|
318
349
|
|
|
@@ -373,7 +404,19 @@ function scoreDirectionalCandidate(
|
|
|
373
404
|
return primaryDelta * 1000 + secondaryDelta;
|
|
374
405
|
}
|
|
375
406
|
|
|
376
|
-
function resolveSplitSizes(total: number, ratio: number, minSize: number): [number, number] {
|
|
407
|
+
function resolveSplitSizes(total: number, ratio: number, minSize: number, precise = false): [number, number] {
|
|
408
|
+
if (precise) {
|
|
409
|
+
if (total <= 0) return [0, 0];
|
|
410
|
+
if (total <= 1) return [total, 0];
|
|
411
|
+
if (total < minSize * 2) {
|
|
412
|
+
const first = total / 2;
|
|
413
|
+
return [first, total - first];
|
|
414
|
+
}
|
|
415
|
+
const preferred = total * clampRatio(ratio);
|
|
416
|
+
const first = Math.max(minSize, Math.min(total - minSize, preferred));
|
|
417
|
+
return [first, total - first];
|
|
418
|
+
}
|
|
419
|
+
|
|
377
420
|
if (total <= 1) return [1, 0];
|
|
378
421
|
if (total < minSize * 2) {
|
|
379
422
|
const first = Math.max(1, Math.floor(total / 2));
|
|
@@ -390,6 +433,7 @@ function collectDockGeometry(
|
|
|
390
433
|
path: Array<0 | 1> = [],
|
|
391
434
|
leaves: DockLeafLayout[] = [],
|
|
392
435
|
dividers: DockDividerLayout[] = [],
|
|
436
|
+
options: DockGeometryOptions = {},
|
|
393
437
|
): { leaves: DockLeafLayout[]; dividers: DockDividerLayout[] } {
|
|
394
438
|
if (!node) return { leaves, dividers };
|
|
395
439
|
if (node.kind === "pane") {
|
|
@@ -397,8 +441,11 @@ function collectDockGeometry(
|
|
|
397
441
|
return { leaves, dividers };
|
|
398
442
|
}
|
|
399
443
|
|
|
444
|
+
const precise = options.precise === true;
|
|
445
|
+
const dividerSize = options.dividerSize ?? 1;
|
|
446
|
+
|
|
400
447
|
if (node.axis === "horizontal") {
|
|
401
|
-
const [firstWidth, secondWidth] = resolveSplitSizes(bounds.width, node.ratio, MIN_PANE_WIDTH);
|
|
448
|
+
const [firstWidth, secondWidth] = resolveSplitSizes(bounds.width, node.ratio, MIN_PANE_WIDTH, precise);
|
|
402
449
|
const firstBounds = { x: bounds.x, y: bounds.y, width: firstWidth, height: bounds.height };
|
|
403
450
|
const secondBounds = { x: bounds.x + firstWidth, y: bounds.y, width: secondWidth, height: bounds.height };
|
|
404
451
|
dividers.push({
|
|
@@ -407,18 +454,18 @@ function collectDockGeometry(
|
|
|
407
454
|
bounds: { ...bounds },
|
|
408
455
|
ratio: node.ratio,
|
|
409
456
|
rect: {
|
|
410
|
-
x: bounds.x + firstWidth - 1,
|
|
457
|
+
x: precise ? bounds.x + firstWidth - (dividerSize / 2) : bounds.x + firstWidth - 1,
|
|
411
458
|
y: bounds.y,
|
|
412
|
-
width:
|
|
459
|
+
width: dividerSize,
|
|
413
460
|
height: bounds.height,
|
|
414
461
|
},
|
|
415
462
|
});
|
|
416
|
-
collectDockGeometry(node.first, firstBounds, [...path, 0], leaves, dividers);
|
|
417
|
-
collectDockGeometry(node.second, secondBounds, [...path, 1], leaves, dividers);
|
|
463
|
+
collectDockGeometry(node.first, firstBounds, [...path, 0], leaves, dividers, options);
|
|
464
|
+
collectDockGeometry(node.second, secondBounds, [...path, 1], leaves, dividers, options);
|
|
418
465
|
return { leaves, dividers };
|
|
419
466
|
}
|
|
420
467
|
|
|
421
|
-
const [firstHeight, secondHeight] = resolveSplitSizes(bounds.height, node.ratio, MIN_DOCKED_HEIGHT);
|
|
468
|
+
const [firstHeight, secondHeight] = resolveSplitSizes(bounds.height, node.ratio, MIN_DOCKED_HEIGHT, precise);
|
|
422
469
|
const firstBounds = { x: bounds.x, y: bounds.y, width: bounds.width, height: firstHeight };
|
|
423
470
|
const secondBounds = { x: bounds.x, y: bounds.y + firstHeight, width: bounds.width, height: secondHeight };
|
|
424
471
|
dividers.push({
|
|
@@ -428,13 +475,13 @@ function collectDockGeometry(
|
|
|
428
475
|
ratio: node.ratio,
|
|
429
476
|
rect: {
|
|
430
477
|
x: bounds.x,
|
|
431
|
-
y: bounds.y + firstHeight - 1,
|
|
478
|
+
y: precise ? bounds.y + firstHeight - (dividerSize / 2) : bounds.y + firstHeight - 1,
|
|
432
479
|
width: bounds.width,
|
|
433
|
-
height:
|
|
480
|
+
height: dividerSize,
|
|
434
481
|
},
|
|
435
482
|
});
|
|
436
|
-
collectDockGeometry(node.first, firstBounds, [...path, 0], leaves, dividers);
|
|
437
|
-
collectDockGeometry(node.second, secondBounds, [...path, 1], leaves, dividers);
|
|
483
|
+
collectDockGeometry(node.first, firstBounds, [...path, 0], leaves, dividers, options);
|
|
484
|
+
collectDockGeometry(node.second, secondBounds, [...path, 1], leaves, dividers, options);
|
|
438
485
|
return { leaves, dividers };
|
|
439
486
|
}
|
|
440
487
|
|
|
@@ -593,12 +640,12 @@ export function findDockLeaf(layout: LayoutConfig, instanceId: string): DockLeaf
|
|
|
593
640
|
return traverseDockLeaves(layout).find((entry) => entry.instanceId === instanceId) ?? null;
|
|
594
641
|
}
|
|
595
642
|
|
|
596
|
-
export function getDockLeafLayouts(layout: LayoutConfig, bounds: LayoutBounds): DockLeafLayout[] {
|
|
597
|
-
return collectDockGeometry(layout.dockRoot, bounds).leaves;
|
|
643
|
+
export function getDockLeafLayouts(layout: LayoutConfig, bounds: LayoutBounds, options?: DockGeometryOptions): DockLeafLayout[] {
|
|
644
|
+
return collectDockGeometry(layout.dockRoot, bounds, [], [], [], options).leaves;
|
|
598
645
|
}
|
|
599
646
|
|
|
600
|
-
export function getDockDividerLayouts(layout: LayoutConfig, bounds: LayoutBounds): DockDividerLayout[] {
|
|
601
|
-
return collectDockGeometry(layout.dockRoot, bounds).dividers;
|
|
647
|
+
export function getDockDividerLayouts(layout: LayoutConfig, bounds: LayoutBounds, options?: DockGeometryOptions): DockDividerLayout[] {
|
|
648
|
+
return collectDockGeometry(layout.dockRoot, bounds, [], [], [], options).dividers;
|
|
602
649
|
}
|
|
603
650
|
|
|
604
651
|
export function getLeafRect(layout: LayoutConfig, instanceId: string, bounds: LayoutBounds): LayoutBounds | null {
|
|
@@ -636,6 +683,27 @@ export function resolveFloating(
|
|
|
636
683
|
return result;
|
|
637
684
|
}
|
|
638
685
|
|
|
686
|
+
export function detachPaneToFrame(
|
|
687
|
+
layout: LayoutConfig,
|
|
688
|
+
instanceId: string,
|
|
689
|
+
rect: Pick<LayoutConfig["detached"][number], "x" | "y" | "width" | "height">,
|
|
690
|
+
): LayoutConfig {
|
|
691
|
+
const base = detachPane(layout, instanceId);
|
|
692
|
+
return finalizeLayout({
|
|
693
|
+
...base,
|
|
694
|
+
detached: [
|
|
695
|
+
...base.detached.filter((entry) => entry.instanceId !== instanceId),
|
|
696
|
+
{
|
|
697
|
+
instanceId,
|
|
698
|
+
x: Math.max(0, Math.round(rect.x)),
|
|
699
|
+
y: Math.max(0, Math.round(rect.y)),
|
|
700
|
+
width: Math.max(1, Math.round(rect.width)),
|
|
701
|
+
height: Math.max(1, Math.round(rect.height)),
|
|
702
|
+
},
|
|
703
|
+
],
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
|
|
639
707
|
export function floatAtRect(layout: LayoutConfig, instanceId: string, rect: FloatingRect): LayoutConfig {
|
|
640
708
|
const base = detachPane(layout, instanceId);
|
|
641
709
|
return finalizeLayout({
|
|
@@ -900,6 +968,7 @@ export function removePane(layout: LayoutConfig, instanceId: string): LayoutConf
|
|
|
900
968
|
...layout,
|
|
901
969
|
dockRoot: removeDockedLeaf(layout, instanceId).dockRoot,
|
|
902
970
|
floating: layout.floating.filter((entry) => entry.instanceId !== instanceId),
|
|
971
|
+
detached: (layout.detached ?? []).filter((entry) => entry.instanceId !== instanceId),
|
|
903
972
|
},
|
|
904
973
|
[instanceId],
|
|
905
974
|
));
|
|
@@ -907,7 +976,12 @@ export function removePane(layout: LayoutConfig, instanceId: string): LayoutConf
|
|
|
907
976
|
|
|
908
977
|
export function isPaneInLayout(layout: LayoutConfig, instanceId: string): boolean {
|
|
909
978
|
return !!findDockLeaf(layout, instanceId)
|
|
910
|
-
|| layout.floating.some((entry) => entry.instanceId === instanceId)
|
|
979
|
+
|| layout.floating.some((entry) => entry.instanceId === instanceId)
|
|
980
|
+
|| (layout.detached ?? []).some((entry) => entry.instanceId === instanceId);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
export function isPaneDetached(layout: LayoutConfig, instanceId: string): boolean {
|
|
984
|
+
return (layout.detached ?? []).some((entry) => entry.instanceId === instanceId);
|
|
911
985
|
}
|
|
912
986
|
|
|
913
987
|
export function isPaneDocked(layout: LayoutConfig, instanceId: string): boolean {
|
|
@@ -933,7 +1007,7 @@ export function bringToFront(layout: LayoutConfig, instanceId: string): LayoutCo
|
|
|
933
1007
|
|
|
934
1008
|
export function getLayoutPreview(layout: LayoutConfig): string {
|
|
935
1009
|
const geometry = getDockLeafLayouts(layout, { x: 0, y: 0, width: 120, height: 40 });
|
|
936
|
-
return `${countColumnsFromGeometry(geometry)}c / ${geometry.length}d / ${layout.floating.length}f`;
|
|
1010
|
+
return `${countColumnsFromGeometry(geometry)}c / ${geometry.length}d / ${layout.floating.length}f / ${(layout.detached ?? []).length}x`;
|
|
937
1011
|
}
|
|
938
1012
|
|
|
939
1013
|
export function gridlockAllPanes(
|