gloomberb 0.10.4 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +30 -3
- package/README.zh-CN.md +1 -1
- package/package.json +5 -5
- package/src/api-client/auth.ts +34 -0
- package/src/api-client/data.ts +25 -0
- package/src/api-client/index.test.ts +81 -0
- package/src/api-client/index.ts +37 -0
- package/src/api-client/request.test.ts +30 -0
- package/src/api-client/request.ts +44 -20
- package/src/api-client/socket-health.test.ts +79 -0
- package/src/api-client/socket.ts +108 -5
- package/src/api-client/types.ts +139 -0
- package/src/app/global-shortcuts.test.tsx +144 -9
- package/src/app/global-shortcuts.ts +33 -11
- package/src/app/runtime/broker-import.ts +39 -5
- package/src/app/runtime/startup.ts +7 -1
- package/src/app/runtime/update.ts +26 -1
- package/src/app.tsx +36 -19
- package/src/brokers/broker-ticker-sync.ts +3 -8
- package/src/brokers/sync-broker-instance.test.ts +59 -0
- package/src/brokers/sync-broker-instance.ts +37 -13
- package/src/capabilities/chart-series.ts +318 -0
- package/src/capabilities/factories.ts +46 -0
- package/src/capabilities/index.ts +1 -0
- package/src/capabilities/registry.test.ts +135 -0
- package/src/capabilities/registry.ts +31 -4
- package/src/capabilities/types.ts +52 -0
- package/src/cli/commands/overview.ts +2 -2
- package/src/cli/desktop-pane-shot.ts +2 -0
- package/src/cli/entry.ts +11 -1
- package/src/cli/native-smoke.ts +5 -0
- package/src/cli/pane-functions/catalog.ts +4 -0
- package/src/cli/pane-functions/data.ts +6 -1
- package/src/cli/pane-functions/discovery.ts +76 -10
- package/src/cli/pane-functions/report.test.ts +76 -0
- package/src/cli/pane-functions/report.ts +7 -1
- package/src/cli/pane-functions/resolver.ts +4 -1
- package/src/cli/pane-functions/screenshot.test.ts +56 -3
- package/src/cli/pane-functions/screenshot.ts +68 -5
- package/src/components/chart/composite/composite-chart.test.tsx +54 -0
- package/src/components/chart/composite/composite-chart.tsx +92 -15
- package/src/components/chart/composite/downsample.test.ts +136 -0
- package/src/components/chart/composite/downsample.ts +208 -0
- package/src/components/chart/composite/panel-series.test.ts +66 -0
- package/src/components/chart/composite/panel-series.ts +101 -0
- package/src/components/chart/multi-line/renderer.ts +1 -0
- package/src/components/chart/native/kitty/index.test.ts +25 -3
- package/src/components/chart/native/kitty/support.ts +37 -3
- package/src/components/chart/native/use-chart-text-fallback.ts +19 -0
- package/src/components/chart/static/chart/surface.tsx +43 -12
- package/src/components/chart/static/multi-line-chart-surface.tsx +41 -5
- package/src/components/command-bar/commands/direct/index.ts +18 -0
- package/src/components/command-bar/commands/registry.ts +14 -0
- package/src/components/command-bar/routes/root/article-results.test.ts +83 -0
- package/src/components/command-bar/routes/root/article-results.ts +55 -0
- package/src/components/command-bar/routes/root/command-items.ts +3 -0
- package/src/components/command-bar/routes/root/provider-search.ts +7 -3
- package/src/components/command-bar/routes/root/results.ts +38 -11
- package/src/components/command-bar/routes/root/runtime.ts +6 -2
- package/src/components/command-bar/routes/ticker-search/actions.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/results.test.ts +34 -0
- package/src/components/command-bar/routes/ticker-search/results.ts +4 -6
- package/src/components/command-bar/routes/ticker-search/route.ts +2 -2
- package/src/components/command-bar/routing/list-state.ts +2 -1
- package/src/components/command-bar/surface/assist.test.tsx +13 -6
- package/src/components/command-bar/surface/index.test.tsx +65 -0
- package/src/components/command-bar/surface/index.tsx +35 -0
- package/src/components/command-bar/view-model.test.ts +43 -0
- package/src/components/command-bar/view-model.ts +41 -4
- package/src/components/command-bar/workflow/collection-actions.test.ts +94 -0
- package/src/components/command-bar/workflow/collection-actions.ts +14 -3
- package/src/components/command-bar/workflow/ops.test.ts +136 -0
- package/src/components/command-bar/workflow/ops.ts +71 -1
- package/src/components/index.ts +1 -1
- package/src/components/layout/header.test.tsx +35 -0
- package/src/components/layout/header.tsx +101 -9
- package/src/components/layout/pane/cycle-order.test.ts +0 -1
- package/src/components/layout/pane/footer/index.test.tsx +19 -19
- package/src/components/layout/pane/header.tsx +2 -13
- package/src/components/layout/shell/index.tsx +3 -0
- package/src/components/layout/status-bar.test.tsx +36 -1
- package/src/components/layout/status-bar.tsx +48 -2
- package/src/components/onboarding/account-step/chooser-panel.tsx +27 -50
- package/src/components/onboarding/account-step/form-panel.tsx +85 -29
- package/src/components/onboarding/account-step/index.tsx +10 -1
- package/src/components/onboarding/account-step/model.ts +3 -3
- package/src/components/onboarding/account-step/qr-panel.tsx +68 -0
- package/src/components/onboarding/account-step/signed-in-panel.tsx +4 -7
- package/src/components/onboarding/onboarding-frame.tsx +517 -0
- package/src/components/onboarding/onboarding-steps.tsx +0 -264
- package/src/components/onboarding/onboarding-wizard.test.tsx +626 -200
- package/src/components/onboarding/onboarding-wizard.tsx +848 -266
- package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +81 -51
- package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +14 -20
- package/src/components/onboarding/portfolio-step/broker-sync-panel.tsx +8 -11
- package/src/components/onboarding/portfolio-step/choose-panel.tsx +18 -22
- package/src/components/onboarding/portfolio-step/index.tsx +3 -0
- package/src/components/onboarding/portfolio-step/types.ts +3 -0
- package/src/components/onboarding/wizard-account.ts +38 -3
- package/src/components/onboarding/wizard-broker-sync.ts +45 -67
- package/src/components/onboarding/wizard-model.ts +18 -2
- package/src/components/ui/data-table/opentui/index.tsx +29 -9
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/external-link.tsx +4 -2
- package/src/components/ui/loading.tsx +1 -1
- package/src/components/ui/status.tsx +76 -4
- package/src/components/ui/table-layout.test.ts +24 -0
- package/src/components/ui/table-layout.ts +90 -23
- package/src/components/ui/tabs.tsx +86 -4
- package/src/core/app-services.ts +14 -3
- package/src/core/connection-health.test.ts +143 -0
- package/src/core/connection-health.ts +295 -0
- package/src/core/state/app/layout-reducer.ts +34 -0
- package/src/core/state/app/layout.ts +21 -0
- package/src/core/state/app/state.ts +10 -0
- package/src/core/state/app/types.ts +7 -1
- package/src/data/config/chart-settings.ts +11 -2
- package/src/data/config/store/index.test.ts +83 -9
- package/src/data/config/store/migrations.ts +16 -0
- package/src/data/config/store/normalize.ts +59 -1
- package/src/data/fred-series.ts +8 -2
- package/src/i18n/es.ts +126 -0
- package/src/i18n/ja.ts +126 -0
- package/src/i18n/ko.ts +126 -0
- package/src/i18n/zh-cn.ts +126 -0
- package/src/i18n/zh-tw.ts +126 -0
- package/src/market-data/coordinator/chart.ts +3 -2
- package/src/market-data/hooks.tsx +3 -2
- package/src/market-data/market/status.test.ts +12 -0
- package/src/market-data/market/status.ts +34 -17
- package/src/news/aggregator.test.ts +39 -0
- package/src/news/aggregator.ts +78 -22
- package/src/news/hooks.ts +4 -0
- package/src/notifications/app-notifier.test.ts +28 -6
- package/src/notifications/app-notifier.ts +24 -5
- package/src/plugins/broker-sync/broker-sync.test.ts +130 -0
- package/src/plugins/broker-sync/native-loader.ts +10 -0
- package/src/plugins/broker-sync/normalize.ts +271 -0
- package/src/plugins/broker-sync/public.ts +122 -0
- package/src/plugins/broker-sync/robinhood-native.ts +367 -0
- package/src/plugins/broker-sync/robinhood.ts +114 -0
- package/src/plugins/broker-sync/simplefin-native.ts +201 -0
- package/src/plugins/broker-sync/simplefin.ts +70 -0
- package/src/plugins/builtin/account-management/footer.ts +8 -19
- package/src/plugins/builtin/account-management/pane.tsx +13 -8
- package/src/plugins/builtin/ai/pi/host.ts +6 -5
- package/src/plugins/builtin/ai/providers.ts +1 -1
- package/src/plugins/builtin/ai/screener/footer.ts +0 -7
- package/src/plugins/builtin/ai/screener/pane.test.tsx +2 -1
- package/src/plugins/builtin/ai/screener/pane.tsx +3 -1
- package/src/plugins/builtin/ai/screener/results-view.tsx +8 -0
- package/src/plugins/builtin/ai/screener/runner.ts +7 -3
- package/src/plugins/builtin/ai/workspace/pane.tsx +13 -2
- package/src/plugins/builtin/alerts/alert-engine.ts +13 -0
- package/src/plugins/builtin/alerts/constants.ts +2 -1
- package/src/plugins/builtin/alerts/index.tsx +67 -36
- package/src/plugins/builtin/alerts/pane.tsx +17 -65
- package/src/plugins/builtin/analytics/index.tsx +24 -11
- package/src/plugins/builtin/analytics/pane-model.ts +48 -14
- package/src/plugins/builtin/analytics/portfolio-selection.ts +19 -0
- package/src/plugins/builtin/analytics/view.tsx +5 -3
- package/src/plugins/builtin/broker-manager/detail.tsx +7 -2
- package/src/plugins/builtin/broker-manager/footer.ts +4 -9
- package/src/plugins/builtin/broker-manager/index.tsx +1 -6
- package/src/plugins/builtin/buildout/detail/ui.tsx +15 -4
- package/src/plugins/builtin/buildout/pane/header.tsx +1 -0
- package/src/plugins/builtin/changelog/index.tsx +60 -45
- package/src/plugins/builtin/chart-composer/catalog-inventory.test.ts +97 -0
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +366 -0
- package/src/plugins/builtin/chart-composer/chart-spec.ts +21 -1
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +307 -0
- package/src/plugins/builtin/chart-composer/editor/controller.ts +1 -1
- package/src/plugins/builtin/chart-composer/index.tsx +45 -5
- package/src/plugins/builtin/chart-composer/pane.tsx +7 -88
- package/src/plugins/builtin/chart-composer/presets.test.ts +48 -0
- package/src/plugins/builtin/chart-composer/presets.ts +69 -3
- package/src/plugins/builtin/chart-composer/quick-add.tsx +8 -5
- package/src/plugins/builtin/chart-composer/semantic.ts +18 -14
- package/src/plugins/builtin/chart-composer/series-catalog.test.ts +39 -0
- package/src/plugins/builtin/chart-composer/series-catalog.ts +104 -3
- package/src/plugins/builtin/chart-composer/shortcuts.test.ts +10 -10
- package/src/plugins/builtin/chart-composer/shortcuts.ts +4 -12
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +230 -23
- package/src/plugins/builtin/chat/chat.test.tsx +22 -0
- package/src/plugins/builtin/chat/content/composer.tsx +6 -4
- package/src/plugins/builtin/chat/content/index.tsx +7 -0
- package/src/plugins/builtin/chat/content/snapshot.ts +9 -2
- package/src/plugins/builtin/chat/content/transcript.tsx +10 -0
- package/src/plugins/builtin/chat/controller/channel-actions.ts +11 -1
- package/src/plugins/builtin/chat/controller/channels.ts +13 -3
- package/src/plugins/builtin/chat/controller/index.ts +61 -7
- package/src/plugins/builtin/chat/controller/lifecycle.ts +3 -0
- package/src/plugins/builtin/chat/controller/message-loading.ts +5 -1
- package/src/plugins/builtin/chat/controller/messages.ts +3 -1
- package/src/plugins/builtin/chat/controller/notifications.ts +32 -11
- package/src/plugins/builtin/chat/controller/state.ts +7 -0
- package/src/plugins/builtin/chat/controller/view.ts +1 -0
- package/src/plugins/builtin/chat/controller.test.ts +117 -28
- package/src/plugins/builtin/cloud/auth-commands.ts +18 -0
- package/src/plugins/builtin/cloud/device-signin-dialog.tsx +243 -0
- package/src/plugins/builtin/cloud/device-signin.test.ts +217 -0
- package/src/plugins/builtin/cloud/device-signin.ts +240 -0
- package/src/plugins/builtin/cloud/plugin.tsx +7 -1
- package/src/plugins/builtin/cloud-tweets/table.tsx +57 -10
- package/src/plugins/builtin/composite-plugins.ts +39 -5
- package/src/plugins/builtin/congress-trades/detail.tsx +2 -4
- package/src/plugins/builtin/congress-trades/footer.ts +7 -16
- package/src/plugins/builtin/congress-trades/pane.tsx +12 -20
- package/src/plugins/builtin/connections/index.ts +53 -0
- package/src/plugins/builtin/connections/pane.test.tsx +79 -0
- package/src/plugins/builtin/connections/pane.tsx +258 -0
- package/src/plugins/builtin/correlation/index.test.tsx +2 -1
- package/src/plugins/builtin/correlation/index.tsx +58 -19
- package/src/plugins/builtin/correlation/matrix/model.ts +18 -7
- package/src/plugins/builtin/correlation/relationship/pane.tsx +43 -19
- package/src/plugins/builtin/correlation/settings.ts +1 -1
- package/src/plugins/builtin/credit-conditions/client.test.ts +80 -0
- package/src/plugins/builtin/credit-conditions/client.ts +105 -0
- package/src/plugins/builtin/credit-conditions/index.test.tsx +111 -0
- package/src/plugins/builtin/credit-conditions/index.tsx +227 -0
- package/src/plugins/builtin/credit-conditions/model.test.ts +43 -0
- package/src/plugins/builtin/credit-conditions/model.ts +64 -0
- package/src/plugins/builtin/debug/index.tsx +11 -6
- package/src/plugins/builtin/dividend-yield/client.test.ts +58 -0
- package/src/plugins/builtin/dividend-yield/client.ts +252 -0
- package/src/plugins/builtin/dividend-yield/index.tsx +61 -0
- package/src/plugins/builtin/dividend-yield/model.ts +83 -0
- package/src/plugins/builtin/dividend-yield/pane.tsx +305 -0
- package/src/plugins/builtin/dividend-yield/types.ts +22 -0
- package/src/plugins/builtin/earnings/data/cache.test.ts +10 -7
- package/src/plugins/builtin/earnings/data/cache.ts +23 -9
- package/src/plugins/builtin/earnings/index.tsx +57 -6
- package/src/plugins/builtin/econ/calendar-model.test.ts +37 -3
- package/src/plugins/builtin/econ/calendar-model.ts +32 -26
- package/src/plugins/builtin/econ/fred-cache.test.ts +8 -1
- package/src/plugins/builtin/econ/fred-series-map.ts +18 -0
- package/src/plugins/builtin/econ/index.test.tsx +105 -0
- package/src/plugins/builtin/econ/index.tsx +95 -52
- package/src/plugins/builtin/fear-greed/cache.test.ts +18 -3
- package/src/plugins/builtin/fear-greed/cache.ts +18 -6
- package/src/plugins/builtin/fear-greed/charts.tsx +9 -2
- package/src/plugins/builtin/fear-greed/format.ts +0 -9
- package/src/plugins/builtin/fear-greed/pane.tsx +21 -21
- package/src/plugins/builtin/futures/contracts.ts +100 -0
- package/src/plugins/builtin/futures/index.tsx +298 -0
- package/src/plugins/builtin/futures/model.test.ts +129 -0
- package/src/plugins/builtin/futures/model.ts +136 -0
- package/src/plugins/builtin/futures/pane.test.tsx +239 -0
- package/src/plugins/builtin/futures/table.test.ts +155 -0
- package/src/plugins/builtin/futures/table.tsx +241 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +160 -134
- package/src/plugins/builtin/fx-matrix/pairs.ts +12 -1
- package/src/plugins/builtin/fx-matrix/pane.test.tsx +109 -0
- package/src/plugins/builtin/help/components.tsx +1 -30
- package/src/plugins/builtin/help/index.tsx +5 -25
- package/src/plugins/builtin/holders/pane.tsx +17 -12
- package/src/plugins/builtin/holders/treemap.tsx +4 -2
- package/src/plugins/builtin/insider/index.tsx +20 -10
- package/src/plugins/builtin/ipo-calendar/cache.ts +101 -0
- package/src/plugins/builtin/ipo-calendar/client.test.ts +31 -0
- package/src/plugins/builtin/ipo-calendar/client.ts +296 -0
- package/src/plugins/builtin/ipo-calendar/index.tsx +65 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +145 -0
- package/src/plugins/builtin/ipo-calendar/pane.tsx +328 -0
- package/src/plugins/builtin/ipo-calendar/types.ts +19 -0
- package/src/plugins/builtin/kelly-sizer/index.test.tsx +3 -3
- package/src/plugins/builtin/kelly-sizer/model.ts +8 -1
- package/src/plugins/builtin/kelly-sizer/pane.tsx +33 -15
- package/src/plugins/builtin/kelly-sizer/view.tsx +12 -5
- package/src/plugins/builtin/market-halts/client.ts +114 -0
- package/src/plugins/builtin/market-halts/index.tsx +29 -0
- package/src/plugins/builtin/market-halts/model.test.ts +187 -0
- package/src/plugins/builtin/market-halts/model.ts +291 -0
- package/src/plugins/builtin/market-halts/pane.tsx +256 -0
- package/src/plugins/builtin/market-heatmap/data.test.ts +1 -0
- package/src/plugins/builtin/market-heatmap/data.ts +12 -3
- package/src/plugins/builtin/market-heatmap/index.tsx +131 -46
- package/src/plugins/builtin/market-movers/index.tsx +145 -63
- package/src/plugins/builtin/market-movers/model.ts +19 -3
- package/src/plugins/builtin/market-movers/screener.test.ts +108 -0
- package/src/plugins/builtin/market-movers/screener.ts +168 -10
- package/src/plugins/builtin/news/index.tsx +78 -99
- package/src/plugins/builtin/news/wire/article-search.test.ts +72 -0
- package/src/plugins/builtin/news/wire/article-search.ts +140 -0
- package/src/plugins/builtin/news/wire/breaking/pane.tsx +18 -8
- package/src/plugins/builtin/news/wire/categories.ts +35 -0
- package/src/plugins/builtin/news/wire/default-feeds.ts +47 -1
- package/src/plugins/builtin/news/wire/feed-config.test.ts +10 -5
- package/src/plugins/builtin/news/wire/index.tsx +41 -4
- package/src/plugins/builtin/news/wire/industry-pane.tsx +38 -19
- package/src/plugins/builtin/news/wire/news/detail-view.tsx +10 -5
- package/src/plugins/builtin/news/wire/news/footer.ts +1 -0
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +15 -6
- package/src/plugins/builtin/news/wire/news/query-presets.ts +16 -2
- package/src/plugins/builtin/news/wire/news/table.test.tsx +54 -0
- package/src/plugins/builtin/news/wire/news/table.tsx +111 -17
- package/src/plugins/builtin/notes/files.test.ts +18 -0
- package/src/plugins/builtin/notes/files.ts +10 -4
- package/src/plugins/builtin/notes/index.tsx +4 -1
- package/src/plugins/builtin/notes/quick-notes-pane.test.tsx +182 -0
- package/src/plugins/builtin/notes/quick-notes-pane.tsx +74 -31
- package/src/plugins/builtin/notes/ticker-notes-tab.test.tsx +380 -0
- package/src/plugins/builtin/notes/ticker-notes-tab.tsx +97 -40
- package/src/plugins/builtin/options/calc-seed.test.ts +118 -0
- package/src/plugins/builtin/options/calc-seed.ts +52 -0
- package/src/plugins/builtin/options/footer.ts +4 -1
- package/src/plugins/builtin/options/index.tsx +22 -1
- package/src/plugins/builtin/options/live-quotes.ts +8 -0
- package/src/plugins/builtin/options/table.ts +3 -1
- package/src/plugins/builtin/options/view.test.tsx +5 -1
- package/src/plugins/builtin/options/view.tsx +112 -17
- package/src/plugins/builtin/options-calculator/index.tsx +35 -0
- package/src/plugins/builtin/options-calculator/model.test.ts +172 -0
- package/src/plugins/builtin/options-calculator/model.ts +273 -0
- package/src/plugins/builtin/options-calculator/pane.test.tsx +138 -0
- package/src/plugins/builtin/options-calculator/pane.tsx +218 -0
- package/src/plugins/builtin/plugin-module.test.ts +16 -2
- package/src/plugins/builtin/plugin-module.ts +5 -1
- package/src/plugins/builtin/polls/client.ts +57 -0
- package/src/plugins/builtin/polls/hooks.ts +20 -0
- package/src/plugins/builtin/polls/index.tsx +35 -0
- package/src/plugins/builtin/polls/normalize.test.ts +230 -0
- package/src/plugins/builtin/polls/normalize.ts +319 -0
- package/src/plugins/builtin/polls/pane.tsx +722 -0
- package/src/plugins/builtin/polls/types.ts +77 -0
- package/src/plugins/builtin/portfolio-list/header.tsx +15 -4
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -3
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +1 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -0
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +2 -1
- package/src/plugins/builtin/research/analyst-pane.tsx +40 -67
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +25 -19
- package/src/plugins/builtin/research/index.test.ts +6 -25
- package/src/plugins/builtin/research/index.tsx +11 -5
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +86 -26
- package/src/plugins/builtin/scanner/denied.tsx +31 -0
- package/src/plugins/builtin/scanner/feed.ts +94 -0
- package/src/plugins/builtin/scanner/flow-model.test.ts +85 -0
- package/src/plugins/builtin/scanner/flow-model.ts +133 -0
- package/src/plugins/builtin/scanner/flow-pane.tsx +197 -0
- package/src/plugins/builtin/scanner/hilo-bars.tsx +121 -0
- package/src/plugins/builtin/scanner/hilo-model.test.ts +44 -0
- package/src/plugins/builtin/scanner/hilo-model.ts +75 -0
- package/src/plugins/builtin/scanner/hilo-pane.tsx +165 -0
- package/src/plugins/builtin/scanner/index.ts +109 -0
- package/src/plugins/builtin/scanner/waiting.tsx +11 -0
- package/src/plugins/builtin/sec/index.tsx +11 -7
- package/src/plugins/builtin/sectors/index.tsx +105 -81
- package/src/plugins/builtin/sectors/move-bar.tsx +43 -0
- package/src/plugins/builtin/sectors/sector-data.ts +18 -0
- package/src/plugins/builtin/sectors/sector-model.ts +13 -15
- package/src/plugins/builtin/shared/auto-refresh.ts +51 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +29 -5
- package/src/plugins/builtin/shared/pane-footer.ts +16 -3
- package/src/plugins/builtin/shared/screener-live-quotes.test.ts +91 -0
- package/src/plugins/builtin/shared/screener-live-quotes.ts +134 -0
- package/src/plugins/builtin/shared/table-pane.ts +1 -5
- package/src/plugins/builtin/shared/use-quote-board.test.tsx +211 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +195 -0
- package/src/plugins/builtin/short-interest/client.ts +124 -0
- package/src/plugins/builtin/short-interest/index.tsx +62 -0
- package/src/plugins/builtin/short-interest/model.ts +113 -0
- package/src/plugins/builtin/short-interest/pane.tsx +252 -0
- package/src/plugins/builtin/short-interest/types.ts +9 -0
- package/src/plugins/builtin/substack/article-detail.tsx +15 -1
- package/src/plugins/builtin/substack/pane-footer.ts +3 -22
- package/src/plugins/builtin/substack/pane.tsx +1 -2
- package/src/plugins/builtin/thirteenf/api.ts +4 -1
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +6 -2
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +10 -4
- package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +67 -11
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +15 -8
- package/src/plugins/builtin/ticker-detail/index.tsx +5 -21
- package/src/plugins/builtin/ticker-detail/overview/components.tsx +72 -32
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +0 -10
- package/src/plugins/builtin/ticker-detail/pane.tsx +4 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.test.tsx +70 -0
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +34 -26
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +96 -49
- package/src/plugins/builtin/ticker-detail/research-tabs.ts +32 -0
- package/src/plugins/builtin/ticker-research-backend-plugin.ts +2 -9
- package/src/plugins/builtin/ticker-research-plugin.ts +6 -0
- package/src/plugins/builtin/treasury-auctions/cache.test.ts +173 -0
- package/src/plugins/builtin/treasury-auctions/cache.ts +104 -0
- package/src/plugins/builtin/treasury-auctions/client.test.ts +181 -0
- package/src/plugins/builtin/treasury-auctions/client.ts +151 -0
- package/src/plugins/builtin/treasury-auctions/index.tsx +90 -0
- package/src/plugins/builtin/treasury-auctions/model.test.ts +147 -0
- package/src/plugins/builtin/treasury-auctions/model.ts +188 -0
- package/src/plugins/builtin/treasury-auctions/pane.test.tsx +170 -0
- package/src/plugins/builtin/treasury-auctions/pane.tsx +417 -0
- package/src/plugins/builtin/treasury-auctions/types.ts +59 -0
- package/src/plugins/builtin/tv/pane.tsx +67 -7
- package/src/plugins/builtin/tv/youtube-embed.test.ts +84 -0
- package/src/plugins/builtin/tv/youtube-embed.ts +173 -0
- package/src/plugins/builtin/tv/youtube-stream.ts +27 -2
- package/src/plugins/builtin/volatility/client.ts +87 -0
- package/src/plugins/builtin/volatility/index.test.tsx +107 -0
- package/src/plugins/builtin/volatility/index.tsx +193 -0
- package/src/plugins/builtin/volatility/model.test.ts +66 -0
- package/src/plugins/builtin/volatility/model.ts +113 -0
- package/src/plugins/builtin/world-indices/footer.ts +43 -23
- package/src/plugins/builtin/world-indices/index.tsx +44 -93
- package/src/plugins/builtin/world-indices/indices.ts +11 -2
- package/src/plugins/builtin/world-indices/model.ts +17 -24
- package/src/plugins/builtin/world-indices/pane.test.tsx +116 -0
- package/src/plugins/builtin/world-indices/table.tsx +82 -37
- package/src/plugins/builtin/yield-curve/index.tsx +16 -7
- package/src/plugins/builtin/yield-curve/treasury-data.test.ts +25 -0
- package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -0
- package/src/plugins/catalog-backend.ts +8 -0
- package/src/plugins/catalog-ui.ts +8 -0
- package/src/plugins/event-bus.ts +1 -0
- package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
- package/src/plugins/ibkr/trade/tab/footer.ts +9 -23
- package/src/plugins/ibkr/trade/tab/index.tsx +1 -5
- package/src/plugins/ibkr/trading/pane-view.tsx +149 -87
- package/src/plugins/ibkr/trading/pane.tsx +2 -2
- package/src/plugins/ownership.ts +3 -0
- package/src/plugins/pane-manager/floating.ts +8 -2
- package/src/plugins/pane-manager.test.ts +13 -0
- package/src/plugins/prediction-markets/backend-plugin.ts +2 -0
- package/src/plugins/prediction-markets/capability.test.ts +281 -0
- package/src/plugins/prediction-markets/capability.ts +171 -0
- package/src/plugins/prediction-markets/chart.tsx +18 -26
- package/src/plugins/prediction-markets/columns.ts +3 -2
- package/src/plugins/prediction-markets/controller/catalog.ts +15 -12
- package/src/plugins/prediction-markets/controller/detail.ts +4 -0
- package/src/plugins/prediction-markets/controller/index.ts +0 -15
- package/src/plugins/prediction-markets/detail/book.tsx +3 -23
- package/src/plugins/prediction-markets/detail/overview.tsx +0 -18
- package/src/plugins/prediction-markets/detail/pane.tsx +36 -12
- package/src/plugins/prediction-markets/detail.test.tsx +1 -1
- package/src/plugins/prediction-markets/index.tsx +2 -0
- package/src/plugins/prediction-markets/metrics.ts +28 -18
- package/src/plugins/prediction-markets/navigation.ts +1 -0
- package/src/plugins/prediction-markets/pane.test.tsx +1 -1
- package/src/plugins/prediction-markets/pane.tsx +0 -1
- package/src/plugins/prediction-markets/services/fetch.ts +3 -2
- package/src/plugins/prediction-markets/services/kalshi/adapter.ts +82 -23
- package/src/plugins/prediction-markets/services/kalshi/normalize.ts +3 -1
- package/src/plugins/prediction-markets/services/kalshi/types.ts +14 -0
- package/src/plugins/prediction-markets/services/polymarket/adapter.ts +26 -14
- package/src/plugins/prediction-markets/services/polymarket/detail.ts +67 -18
- package/src/plugins/prediction-markets/test-helpers.tsx +0 -1
- package/src/plugins/prediction-markets/types.ts +3 -9
- package/src/plugins/registry/context.ts +6 -1
- package/src/plugins/registry/index.test.ts +2 -0
- package/src/plugins/registry/index.ts +53 -3
- package/src/plugins/runtime/context.tsx +4 -2
- package/src/plugins/runtime/index.tsx +9 -0
- package/src/remote/controller.test.ts +17 -4
- package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
- package/src/renderers/electrobun/bun/core-capabilities.ts +2 -2
- package/src/renderers/electrobun/bun/desktop/capability-bridge.test.ts +64 -0
- package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +41 -7
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +5 -2
- package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +33 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +53 -6
- package/src/renderers/electrobun/bun/desktop/initialization.test.ts +27 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +6 -1
- package/src/renderers/electrobun/bun/index.ts +1 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/app-services.ts +25 -2
- package/src/renderers/electrobun/view/build-assets.ts +5 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +186 -65
- package/src/renderers/electrobun/view/cli-pane-shot-health.test.ts +25 -0
- package/src/renderers/electrobun/view/cli-pane-shot-health.ts +31 -0
- package/src/renderers/electrobun/view/data-table/index.tsx +10 -11
- package/src/renderers/electrobun/view/data-table/row.tsx +2 -0
- package/src/renderers/electrobun/view/desktop/controls.test.tsx +68 -0
- package/src/renderers/electrobun/view/desktop/controls.tsx +25 -1
- package/src/renderers/electrobun/view/host/box.test.tsx +80 -0
- package/src/renderers/electrobun/view/host/media-surface.tsx +70 -3
- package/src/renderers/electrobun/view/host/tabs.tsx +97 -5
- package/src/renderers/electrobun/view/host/text.tsx +5 -2
- package/src/renderers/electrobun/view/input-host.test.tsx +140 -0
- package/src/renderers/electrobun/view/input-host.tsx +4 -2
- package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +19 -1
- package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +7 -0
- package/src/renderers/electrobun/view/native-stubs/chart/kitty-support.ts +10 -0
- package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +46 -0
- package/src/renderers/electrobun/view/remote/capability-invoker.ts +34 -13
- package/src/renderers/electrobun/view/remote/connection-health-backend.ts +19 -0
- package/src/renderers/electrobun/view/remote/connection-health.test.ts +44 -0
- package/src/renderers/electrobun/view/remote/connection-health.ts +49 -0
- package/src/renderers/electrobun/view/styles.css +5 -0
- package/src/renderers/opentui/dialog-host.tsx +279 -80
- package/src/renderers/opentui/host.tsx +15 -1
- package/src/renderers/opentui/terminal-media.test.ts +102 -0
- package/src/renderers/opentui/terminal-media.ts +142 -0
- package/src/renderers/opentui/test-utils.tsx +9 -6
- package/src/renderers/opentui/toast-host.tsx +136 -7
- package/src/renderers/opentui/ui-host.tsx +19 -3
- package/src/sources/provider-router/connection-health.test.ts +28 -0
- package/src/sources/provider-router/connection-health.ts +47 -0
- package/src/sources/provider-router/history.test.ts +53 -4
- package/src/sources/provider-router/history.ts +119 -14
- package/src/sources/provider-router/index.ts +12 -1
- package/src/sources/yahoo-finance/history.ts +2 -9
- package/src/sources/yahoo-finance/http.ts +1 -1
- package/src/sources/yahoo-finance/symbols.ts +1 -1
- package/src/sources/yahoo-finance/types.ts +19 -0
- package/src/state/app/context/index.test.tsx +101 -0
- package/src/state/app/context/index.tsx +10 -0
- package/src/state/config-save-scheduler.ts +1 -6
- package/src/state/persist-scheduler.test.ts +31 -0
- package/src/state/persist-scheduler.ts +21 -8
- package/src/sync/auth-transition.test.ts +36 -0
- package/src/sync/auth-transition.ts +22 -0
- package/src/sync/core-contributors.test.ts +58 -1
- package/src/sync/core-contributors.ts +39 -2
- package/src/sync/react.ts +9 -0
- package/src/test-support/plugin-runtime.ts +3 -0
- package/src/theme/font-scale.test.ts +14 -0
- package/src/theme/font-scale.ts +69 -0
- package/src/tickers/search/index.test.ts +284 -2
- package/src/tickers/search/index.ts +55 -18
- package/src/tickers/search/ranking.ts +143 -18
- package/src/tickers/search/types.ts +1 -0
- package/src/time-series/alignment.ts +15 -11
- package/src/time-series/field-catalog.ts +10 -0
- package/src/time-series/fundamentals.ts +19 -11
- package/src/time-series/hooks.ts +8 -2
- package/src/time-series/parsed-history-cache.ts +35 -0
- package/src/time-series/resolution.ts +10 -0
- package/src/time-series/resolve.test.ts +332 -3
- package/src/time-series/resolve.ts +225 -25
- package/src/time-series/spec-studies.test.ts +23 -0
- package/src/time-series/spec.ts +21 -2
- package/src/time-series/transforms.test.ts +76 -1
- package/src/time-series/transforms.ts +39 -8
- package/src/time-series/types.ts +9 -2
- package/src/time-series/use-chart-resolution.test.tsx +105 -1
- package/src/time-series/use-chart-resolution.ts +67 -13
- package/src/types/config.ts +30 -27
- package/src/types/media.ts +2 -0
- package/src/types/plugin.ts +8 -1
- package/src/ui/host.tsx +4 -0
- package/src/ui/pointer-drag.ts +12 -0
- package/src/ui/qr.test.ts +79 -0
- package/src/ui/qr.ts +82 -0
- package/src/updater/github-releases.test.ts +3 -3
- package/src/updater/github-releases.ts +1 -1
- package/src/updater.test.ts +117 -16
- package/src/updater.ts +32 -5
- package/src/utils/exchanges.ts +1 -0
- package/src/utils/external-url.test.ts +32 -0
- package/src/utils/external-url.ts +23 -0
- package/src/utils/sort-values.ts +31 -0
- package/src/utils/zoned-date-time.ts +67 -0
- package/src/components/onboarding/wizard-keyboard.ts +0 -306
- package/src/plugins/builtin/chart-composer/date-window-dialog.tsx +0 -120
|
@@ -11,6 +11,9 @@ import { getRendererBuiltinPlugins } from "../../../plugins/catalog-ui";
|
|
|
11
11
|
import { createRemoteAssetDataClient } from "./remote/asset-data-client";
|
|
12
12
|
import { RemotePersistence } from "./remote/persistence";
|
|
13
13
|
import { RemoteTickerRepository } from "./remote/ticker-repository";
|
|
14
|
+
import { connectBackendConnectionHealth } from "./remote/connection-health-backend";
|
|
15
|
+
import { backendRequest, getElectrobunBackendInitSnapshot } from "./backend-rpc";
|
|
16
|
+
import { createCapabilityInvoker } from "./remote/capability-invoker";
|
|
14
17
|
|
|
15
18
|
const servicesLog = debugLog.createLogger("services");
|
|
16
19
|
|
|
@@ -22,11 +25,20 @@ export function createElectrobunAppServices({ config }: AppServicesFactoryOption
|
|
|
22
25
|
const tickerRepository = measurePerf("startup.services.ticker-repository", () => new RemoteTickerRepository());
|
|
23
26
|
const dataProvider = measurePerf("startup.services.data-provider", () => createRemoteAssetDataClient());
|
|
24
27
|
const marketData = new MarketDataCoordinator(dataProvider);
|
|
28
|
+
const invokeCapability = createCapabilityInvoker({
|
|
29
|
+
request: backendRequest,
|
|
30
|
+
shouldApplyDeadline: () => false,
|
|
31
|
+
timeoutMs: 0,
|
|
32
|
+
});
|
|
25
33
|
const pluginRegistry = new PluginRegistry(dataProvider, tickerRepository, persistence, {
|
|
26
34
|
enableCapabilityHandlers: false,
|
|
27
35
|
wrapBrokerAdapter: (broker) => createRemoteBrokerAdapter(broker),
|
|
36
|
+
remoteCapabilityManifests: () => getElectrobunBackendInitSnapshot()?.capabilityManifests ?? [],
|
|
37
|
+
remoteCapabilityInvoke: (capabilityId, operationId, payload, options) => (
|
|
38
|
+
invokeCapability(capabilityId, operationId, payload, options)
|
|
39
|
+
),
|
|
28
40
|
});
|
|
29
|
-
const newsService = new NewsService();
|
|
41
|
+
const newsService = new NewsService({ connectionHealth: pluginRegistry.connectionHealth });
|
|
30
42
|
|
|
31
43
|
pluginRegistry.getConfigFn = () => config;
|
|
32
44
|
pluginRegistry.getLayoutFn = () => config.layout;
|
|
@@ -55,6 +67,14 @@ export function createElectrobunAppServices({ config }: AppServicesFactoryOption
|
|
|
55
67
|
measurePerf("startup.services.news-start", () => {
|
|
56
68
|
newsService.start();
|
|
57
69
|
});
|
|
70
|
+
let destroyed = false;
|
|
71
|
+
let disposeRemoteConnectionHealth: (() => void) | null = null;
|
|
72
|
+
const ready = Promise.all(pluginReadyPromises).then(() => {
|
|
73
|
+
if (destroyed) return;
|
|
74
|
+
const dispose = connectBackendConnectionHealth(pluginRegistry.connectionHealth);
|
|
75
|
+
if (destroyed) dispose();
|
|
76
|
+
else disposeRemoteConnectionHealth = dispose;
|
|
77
|
+
});
|
|
58
78
|
servicesLog.info("create desktop web services complete", { pluginCount: plugins.length });
|
|
59
79
|
|
|
60
80
|
return {
|
|
@@ -63,8 +83,11 @@ export function createElectrobunAppServices({ config }: AppServicesFactoryOption
|
|
|
63
83
|
dataProvider,
|
|
64
84
|
marketData,
|
|
65
85
|
pluginRegistry,
|
|
66
|
-
ready
|
|
86
|
+
ready,
|
|
67
87
|
destroy() {
|
|
88
|
+
destroyed = true;
|
|
89
|
+
disposeRemoteConnectionHealth?.();
|
|
90
|
+
disposeRemoteConnectionHealth = null;
|
|
68
91
|
setSharedMarketDataCoordinator(null);
|
|
69
92
|
setSharedNewsService(null);
|
|
70
93
|
newsService.stop();
|
|
@@ -24,6 +24,8 @@ const COMMON_ALIAS_RULES: AliasRule[] = [
|
|
|
24
24
|
["native/surface/manager", "native-stubs/chart/surface-manager.ts"],
|
|
25
25
|
["native/surface/sync", "native-stubs/chart/surface-sync.ts"],
|
|
26
26
|
["./native-loader", "plugins/ibkr/gateway/service/index.ts", "native-stubs/ibkr-native-loader.ts"],
|
|
27
|
+
["./native-loader", "plugins/broker-sync/robinhood.ts", "native-stubs/broker-sync-native-loader.ts"],
|
|
28
|
+
["./native-loader", "plugins/broker-sync/simplefin.ts", "native-stubs/broker-sync-native-loader.ts"],
|
|
27
29
|
];
|
|
28
30
|
|
|
29
31
|
export function electrobunViewPath(...parts: string[]): string {
|
|
@@ -55,6 +57,9 @@ async function buildElectrobunViewBundle({
|
|
|
55
57
|
minify: true,
|
|
56
58
|
define: {
|
|
57
59
|
"process.env.NODE_ENV": "\"production\"",
|
|
60
|
+
// The webview has no `process`, so the cloud endpoint override the terminal
|
|
61
|
+
// already reads from the environment is baked in at build time.
|
|
62
|
+
__GLOOMBERB_API_URL__: JSON.stringify(process.env.GLOOMBERB_API_URL ?? ""),
|
|
58
63
|
},
|
|
59
64
|
plugins: [
|
|
60
65
|
{
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/** @jsxImportSource react */
|
|
2
2
|
import { createRoot } from "react-dom/client";
|
|
3
|
-
import { useEffect, type
|
|
3
|
+
import { useEffect, type ReactNode } from "react";
|
|
4
4
|
import { AppProvider, useAppDispatch } from "../../../state/app/context";
|
|
5
|
+
import { createCliPaneShotConnectionHealth } from "./cli-pane-shot-health";
|
|
5
6
|
import { MarketDataCoordinator, setSharedMarketDataCoordinator } from "../../../market-data/coordinator";
|
|
6
7
|
import { instrumentFromTicker } from "../../../market-data/request-types";
|
|
7
8
|
import { UiHostProvider, type RendererHost } from "../../../ui/host";
|
|
@@ -11,8 +12,7 @@ import { webNativeRenderer } from "./native-renderer";
|
|
|
11
12
|
import { WebToastHostProvider } from "./toast-host";
|
|
12
13
|
import { webUiHost } from "./ui-host";
|
|
13
14
|
import { getLoadablePlugins } from "../../../plugins/catalog";
|
|
14
|
-
import { setSharedMarketDataForTests } from "../../../plugins/registry";
|
|
15
|
-
import { PluginRenderProvider, type PluginRuntimeAccess } from "../../../plugins/runtime";
|
|
15
|
+
import { PluginRegistry, setSharedMarketDataForTests } from "../../../plugins/registry";
|
|
16
16
|
import {
|
|
17
17
|
RemoteUiRegistryProvider,
|
|
18
18
|
useRemoteUiRegistry,
|
|
@@ -29,6 +29,11 @@ import {
|
|
|
29
29
|
} from "../../../time-series/resolution";
|
|
30
30
|
import type { TimeRange } from "../../../components/chart/core/types";
|
|
31
31
|
import type { AppConfig } from "../../../types/config";
|
|
32
|
+
import type {
|
|
33
|
+
AppPersistencePort,
|
|
34
|
+
AppTickerRepositoryPort,
|
|
35
|
+
} from "../../../core/app-service-ports";
|
|
36
|
+
import type { CachedResourceRecord, ResourceCacheKey, SetResourceOptions } from "../../../data/resource-store";
|
|
32
37
|
import type { CachedFinancialsTarget, DataProvider, QuoteSubscriptionTarget } from "../../../types/data-provider";
|
|
33
38
|
import type { OptionsChain, TickerFinancials } from "../../../types/financials";
|
|
34
39
|
import type { TickerRecord } from "../../../types/ticker";
|
|
@@ -37,6 +42,8 @@ import type { PaneDef } from "../../../types/plugin";
|
|
|
37
42
|
import { canonicalTickerKey, parsePublicTickerKey } from "../../../utils/exchanges";
|
|
38
43
|
import { hydrateFredSeries, type FredSeriesCacheEntry } from "../../../data/fred-series";
|
|
39
44
|
import { clipPriceHistoryToRange } from "../../../time-series/history-window";
|
|
45
|
+
import type { ResolvedSeries } from "../../../time-series/types";
|
|
46
|
+
import { chartSeriesSourceKey } from "../../../capabilities";
|
|
40
47
|
|
|
41
48
|
interface CliPaneShotPayload {
|
|
42
49
|
config: AppConfig;
|
|
@@ -47,6 +54,7 @@ interface CliPaneShotPayload {
|
|
|
47
54
|
financials: Array<[string, TickerFinancials]>;
|
|
48
55
|
optionsChains: Array<[string, OptionsChain]>;
|
|
49
56
|
fredSeries: Array<[string, FredSeriesCacheEntry]>;
|
|
57
|
+
capabilitySeries: Array<[string, ResolvedSeries]>;
|
|
50
58
|
paneState: Record<string, PaneRuntimeState>;
|
|
51
59
|
}
|
|
52
60
|
|
|
@@ -64,7 +72,11 @@ declare global {
|
|
|
64
72
|
// frames can capture the shell before those requests register, leaving a
|
|
65
73
|
// loading chart or stale performance table in an otherwise valid PNG.
|
|
66
74
|
const SHOT_READY_STABLE_FRAMES = 10;
|
|
67
|
-
|
|
75
|
+
// Loading is detected from an explicit marker rendered by Spinner and
|
|
76
|
+
// PaneStatusBody. Matching on body text instead made any pane whose real
|
|
77
|
+
// content contains the word "loading" (a changelog release note, a news
|
|
78
|
+
// headline) wait forever and time out.
|
|
79
|
+
const SHOT_LOADING_SELECTOR = "[data-gloom-status=\"loading\"]";
|
|
68
80
|
const TRACKED_RESPONSE_METHODS = new Set<PropertyKey>([
|
|
69
81
|
"arrayBuffer",
|
|
70
82
|
"blob",
|
|
@@ -76,6 +88,7 @@ const TRACKED_RESPONSE_METHODS = new Set<PropertyKey>([
|
|
|
76
88
|
let pendingShotWork = 0;
|
|
77
89
|
let didInstallShotFetchTracker = false;
|
|
78
90
|
let shotDataProvider: DataProvider | null = null;
|
|
91
|
+
let shotRegistry: PluginRegistry | null = null;
|
|
79
92
|
const SHOT_CHART_RESOLUTION_SUPPORT = normalizeChartResolutionSupport(
|
|
80
93
|
TIME_RANGE_ORDER.map((maxRange) => ({
|
|
81
94
|
resolution: getPresetResolution(maxRange),
|
|
@@ -133,7 +146,23 @@ function resolveShotWork<T>(value: T): Promise<T> {
|
|
|
133
146
|
}
|
|
134
147
|
|
|
135
148
|
function isShotLoadingTextVisible(): boolean {
|
|
136
|
-
return
|
|
149
|
+
return document.querySelector(SHOT_LOADING_SELECTOR) !== null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The payload crosses into the webview as JSON, so every `Date` arrives as a
|
|
154
|
+
* string while the types still claim `Date`. Anything calling `date.getTime()`
|
|
155
|
+
* then throws, which is what made the valuation graph preset fail while the
|
|
156
|
+
* price presets passed.
|
|
157
|
+
*/
|
|
158
|
+
function revivePayloadDates(payload: CliPaneShotPayload): void {
|
|
159
|
+
for (const [, data] of payload.financials) {
|
|
160
|
+
const history = data.priceHistory;
|
|
161
|
+
if (!Array.isArray(history)) continue;
|
|
162
|
+
for (const point of history) {
|
|
163
|
+
if (!(point.date instanceof Date)) point.date = new Date(point.date as unknown as string);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
137
166
|
}
|
|
138
167
|
|
|
139
168
|
function hasUnresolvedChartData(): boolean {
|
|
@@ -248,8 +277,17 @@ function createShotDataProvider(payload: CliPaneShotPayload): DataProvider {
|
|
|
248
277
|
?? financials.get(normalizeSymbol(target.symbol)))?.quote ?? null,
|
|
249
278
|
})));
|
|
250
279
|
},
|
|
251
|
-
getExchangeRate() {
|
|
252
|
-
|
|
280
|
+
getExchangeRate(fromCurrency: string) {
|
|
281
|
+
// Returning 1 for every currency used to render the FX matrix as a grid
|
|
282
|
+
// of 1.0000, which reads as real data. Only the identity conversion is
|
|
283
|
+
// known offline; anything else has to fail so the pane reports it as
|
|
284
|
+
// unavailable instead of inventing a parity rate.
|
|
285
|
+
if (normalizeSymbol(fromCurrency) === normalizeSymbol(payload.config.baseCurrency)) {
|
|
286
|
+
return resolveShotWork(1);
|
|
287
|
+
}
|
|
288
|
+
return trackShotWork(Promise.reject(
|
|
289
|
+
new Error(`No screenshot exchange rate available for ${fromCurrency}.`),
|
|
290
|
+
));
|
|
253
291
|
},
|
|
254
292
|
getOptionsChain(ticker, exchange) {
|
|
255
293
|
return trackShotWork(Promise.resolve().then(() => {
|
|
@@ -313,58 +351,147 @@ function installShotMarketData(payload: CliPaneShotPayload): void {
|
|
|
313
351
|
setSharedMarketDataCoordinator(coordinator);
|
|
314
352
|
}
|
|
315
353
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
354
|
+
/**
|
|
355
|
+
* The screenshot renderer has no database and no Electrobun backend. Plugins
|
|
356
|
+
* still need somewhere to read and write state while the render runs, so state
|
|
357
|
+
* lives in memory for the lifetime of the page and the resource cache always
|
|
358
|
+
* reports a miss instead of pretending something was persisted.
|
|
359
|
+
*/
|
|
360
|
+
function createShotPersistence(): AppPersistencePort {
|
|
361
|
+
const pluginState = new Map<string, { value: unknown; schemaVersion: number; updatedAt: number }>();
|
|
362
|
+
const stateKey = (pluginId: string, key: string) => `${pluginId}:${key}`;
|
|
324
363
|
return {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
getResumeState,
|
|
347
|
-
setResumeState: (_pluginId, key, value) => {
|
|
348
|
-
resumeState.set(key, value);
|
|
364
|
+
pluginState: {
|
|
365
|
+
get: <T,>(pluginId: string, key: string, schemaVersion = 1) => {
|
|
366
|
+
const record = pluginState.get(stateKey(pluginId, key));
|
|
367
|
+
if (!record || record.schemaVersion !== schemaVersion) return null;
|
|
368
|
+
return { value: record.value as T, schemaVersion: record.schemaVersion, updatedAt: record.updatedAt };
|
|
369
|
+
},
|
|
370
|
+
set: (pluginId: string, key: string, value: unknown, schemaVersion = 1) => {
|
|
371
|
+
pluginState.set(stateKey(pluginId, key), { value, schemaVersion, updatedAt: Date.now() });
|
|
372
|
+
},
|
|
373
|
+
delete: (pluginId: string, key: string) => {
|
|
374
|
+
pluginState.delete(stateKey(pluginId, key));
|
|
375
|
+
},
|
|
376
|
+
keys: (pluginId: string) => [...pluginState.keys()]
|
|
377
|
+
.filter((entry) => entry.startsWith(`${pluginId}:`))
|
|
378
|
+
.map((entry) => entry.slice(pluginId.length + 1))
|
|
379
|
+
.sort(),
|
|
380
|
+
clear: (pluginId: string) => {
|
|
381
|
+
for (const entry of [...pluginState.keys()]) {
|
|
382
|
+
if (entry.startsWith(`${pluginId}:`)) pluginState.delete(entry);
|
|
383
|
+
}
|
|
384
|
+
},
|
|
349
385
|
},
|
|
350
|
-
|
|
351
|
-
|
|
386
|
+
resources: {
|
|
387
|
+
get: () => null,
|
|
388
|
+
list: () => [],
|
|
389
|
+
set: <T,>(key: ResourceCacheKey, value: T, options: SetResourceOptions): CachedResourceRecord<T> => {
|
|
390
|
+
const fetchedAt = options.fetchedAt ?? Date.now();
|
|
391
|
+
return {
|
|
392
|
+
namespace: key.namespace,
|
|
393
|
+
kind: key.kind,
|
|
394
|
+
entityKey: key.entityKey,
|
|
395
|
+
variantKey: key.variantKey ?? "",
|
|
396
|
+
sourceKey: key.sourceKey ?? "",
|
|
397
|
+
value,
|
|
398
|
+
fetchedAt,
|
|
399
|
+
staleAt: fetchedAt + options.cachePolicy.staleMs,
|
|
400
|
+
expiresAt: fetchedAt + options.cachePolicy.expireMs,
|
|
401
|
+
schemaVersion: options.schemaVersion ?? 1,
|
|
402
|
+
provenance: options.provenance ?? null,
|
|
403
|
+
lastAccessedAt: fetchedAt,
|
|
404
|
+
sizeBytes: 0,
|
|
405
|
+
};
|
|
406
|
+
},
|
|
407
|
+
delete: () => {},
|
|
352
408
|
},
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
409
|
+
sessions: {
|
|
410
|
+
get: () => null,
|
|
411
|
+
set: () => {},
|
|
412
|
+
delete: () => {},
|
|
413
|
+
},
|
|
414
|
+
close() {},
|
|
358
415
|
};
|
|
359
416
|
}
|
|
360
417
|
|
|
361
|
-
function
|
|
418
|
+
function createShotTickerRepository(tickers: TickerRecord[]): AppTickerRepositoryPort {
|
|
419
|
+
const bySymbol = new Map(tickers.map((ticker) => [ticker.metadata.ticker, ticker]));
|
|
420
|
+
return {
|
|
421
|
+
loadAllTickers: async () => [...bySymbol.values()],
|
|
422
|
+
loadTicker: async (symbol) => bySymbol.get(normalizeSymbol(symbol)) ?? bySymbol.get(symbol) ?? null,
|
|
423
|
+
saveTicker: async () => {},
|
|
424
|
+
createTicker: async (metadata) => ({ metadata }),
|
|
425
|
+
deleteTicker: async () => {},
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Screenshots render the same tree the desktop app renders, so they need the
|
|
431
|
+
* same registry: plugin-contributed tabs, slots, shortcuts, context menus and
|
|
432
|
+
* setup-registered panes all resolve through it. Only the capability surface is
|
|
433
|
+
* replaced, because the offline render can answer chart series from the payload
|
|
434
|
+
* but cannot reach a backend.
|
|
435
|
+
*/
|
|
436
|
+
async function installShotPluginRegistry(payload: CliPaneShotPayload): Promise<PluginRegistry> {
|
|
437
|
+
const capabilitySeries = new Map(payload.capabilitySeries ?? []);
|
|
438
|
+
const capabilityIds = [...new Set(payload.config.layout.instances.flatMap((instance) => {
|
|
439
|
+
const chartSpec = instance.settings?.chartSpec;
|
|
440
|
+
if (!chartSpec || typeof chartSpec !== "object" || !Array.isArray((chartSpec as any).series)) return [];
|
|
441
|
+
return (chartSpec as any).series.flatMap((series: any) => (
|
|
442
|
+
series?.source?.kind === "capability" && typeof series.source.capabilityId === "string"
|
|
443
|
+
? [series.source.capabilityId]
|
|
444
|
+
: []
|
|
445
|
+
));
|
|
446
|
+
}))];
|
|
447
|
+
|
|
448
|
+
const registry = new PluginRegistry(
|
|
449
|
+
shotDataProvider!,
|
|
450
|
+
createShotTickerRepository(payload.tickers),
|
|
451
|
+
createShotPersistence(),
|
|
452
|
+
{
|
|
453
|
+
enableCapabilityHandlers: false,
|
|
454
|
+
connectionHealth: createCliPaneShotConnectionHealth(),
|
|
455
|
+
remoteCapabilityManifests: () => capabilityIds.map((id) => ({
|
|
456
|
+
id,
|
|
457
|
+
kind: "chart-series",
|
|
458
|
+
name: id,
|
|
459
|
+
operations: [{ id: "resolve", kind: "query", rendererSafe: true }],
|
|
460
|
+
})),
|
|
461
|
+
remoteCapabilityInvoke: async <T,>(capabilityId: string, operationId: string, input: unknown) => {
|
|
462
|
+
if (operationId !== "resolve" || !input || typeof input !== "object") {
|
|
463
|
+
throw new Error(`Screenshot capability operation ${capabilityId}.${operationId} is unavailable.`);
|
|
464
|
+
}
|
|
465
|
+
const request = input as { seriesId?: string };
|
|
466
|
+
const value = capabilitySeries.get(chartSeriesSourceKey({
|
|
467
|
+
kind: "capability",
|
|
468
|
+
capabilityId,
|
|
469
|
+
seriesId: request.seriesId ?? "",
|
|
470
|
+
}));
|
|
471
|
+
if (!value) throw new Error(`No screenshot chart series data is available for ${request.seriesId ?? capabilityId}.`);
|
|
472
|
+
return value as T;
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
);
|
|
476
|
+
registry.getConfigFn = () => payload.config;
|
|
477
|
+
registry.getLayoutFn = () => payload.config.layout;
|
|
478
|
+
registry.getPaneRuntimeStateFn = (paneId) => payload.paneState[paneId] ?? null;
|
|
479
|
+
|
|
362
480
|
for (const plugin of getLoadablePlugins()) {
|
|
363
|
-
|
|
364
|
-
|
|
481
|
+
try {
|
|
482
|
+
await registry.register(plugin);
|
|
483
|
+
} catch (error) {
|
|
484
|
+
// One failing plugin must not cost the screenshot every other pane.
|
|
485
|
+
console.error(`[shot] Plugin ${plugin.id} failed to register:`, error);
|
|
365
486
|
}
|
|
366
487
|
}
|
|
367
|
-
|
|
488
|
+
shotRegistry = registry;
|
|
489
|
+
return registry;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function findPaneDef(paneId: string): { pluginId: string; pane: PaneDef } | null {
|
|
493
|
+
const pane = shotRegistry?.panes.get(paneId);
|
|
494
|
+
return pane ? { pluginId: shotRegistry?.getPanePluginId(paneId) ?? "", pane } : null;
|
|
368
495
|
}
|
|
369
496
|
|
|
370
497
|
function HydratePayload({
|
|
@@ -413,16 +540,8 @@ function ShotPane({ payload }: { payload: CliPaneShotPayload }) {
|
|
|
413
540
|
const found = findPaneDef(instance.paneId);
|
|
414
541
|
if (!found) throw new Error(`Pane ${instance.paneId} is not registered in the desktop renderer.`);
|
|
415
542
|
|
|
416
|
-
|
|
417
|
-
const
|
|
418
|
-
const pane: PaneDef = {
|
|
419
|
-
...found.pane,
|
|
420
|
-
component: (props) => (
|
|
421
|
-
<PluginRenderProvider pluginId={found.pluginId} runtime={runtime}>
|
|
422
|
-
<PaneComponent {...props} />
|
|
423
|
-
</PluginRenderProvider>
|
|
424
|
-
),
|
|
425
|
-
};
|
|
543
|
+
// The registry already bound its runtime to this pane component.
|
|
544
|
+
const pane = found.pane;
|
|
426
545
|
const titleState = {
|
|
427
546
|
config: payload.config,
|
|
428
547
|
paneState: payload.paneState,
|
|
@@ -462,12 +581,16 @@ function ShotPane({ payload }: { payload: CliPaneShotPayload }) {
|
|
|
462
581
|
);
|
|
463
582
|
}
|
|
464
583
|
|
|
465
|
-
function render() {
|
|
584
|
+
async function render() {
|
|
466
585
|
const payload = window.__GLOOM_CLI_SHOT_PAYLOAD__;
|
|
467
586
|
if (!payload) throw new Error("Missing CLI pane screenshot payload.");
|
|
587
|
+
revivePayloadDates(payload);
|
|
468
588
|
installShotFetchTracker();
|
|
469
589
|
hydrateFredSeries(payload.fredSeries ?? []);
|
|
470
590
|
installShotMarketData(payload);
|
|
591
|
+
// Panes contributed from an async setup() only exist once every plugin has
|
|
592
|
+
// finished registering, so the tree cannot mount before that resolves.
|
|
593
|
+
await installShotPluginRegistry(payload);
|
|
471
594
|
|
|
472
595
|
const rootElement = document.getElementById("root");
|
|
473
596
|
if (!rootElement) throw new Error("Missing root element.");
|
|
@@ -498,9 +621,7 @@ function render() {
|
|
|
498
621
|
);
|
|
499
622
|
}
|
|
500
623
|
|
|
501
|
-
|
|
502
|
-
render();
|
|
503
|
-
} catch (error) {
|
|
624
|
+
render().catch((error) => {
|
|
504
625
|
window.__GLOOM_CLI_SHOT_ERROR__ = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
505
626
|
throw error;
|
|
506
|
-
}
|
|
627
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { createCliPaneShotConnectionHealth } from "./cli-pane-shot-health";
|
|
3
|
+
|
|
4
|
+
describe("CLI pane shot connection health", () => {
|
|
5
|
+
test("seeds stable request, socket, and error rows", () => {
|
|
6
|
+
const sources = createCliPaneShotConnectionHealth(1_000).getSnapshot().sources;
|
|
7
|
+
|
|
8
|
+
expect(sources.map((source) => source.id)).toEqual([
|
|
9
|
+
"gloom-cloud-http",
|
|
10
|
+
"gloom-cloud-socket",
|
|
11
|
+
"gloom-cloud-fred",
|
|
12
|
+
"asset-data.yahoo",
|
|
13
|
+
]);
|
|
14
|
+
expect(sources.map((source) => ({
|
|
15
|
+
status: source.status,
|
|
16
|
+
operation: source.lastOperation,
|
|
17
|
+
latency: source.lastLatencyMs,
|
|
18
|
+
}))).toEqual([
|
|
19
|
+
{ status: "connected", operation: "GET /market/quotes", latency: 84 },
|
|
20
|
+
{ status: "connected", operation: null, latency: null },
|
|
21
|
+
{ status: "idle", operation: null, latency: null },
|
|
22
|
+
{ status: "error", operation: "getPriceHistory", latency: 240 },
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConnectionHealthRegistry,
|
|
3
|
+
GLOOM_CLOUD_HTTP_CONNECTION_ID,
|
|
4
|
+
GLOOM_CLOUD_SOCKET_CONNECTION_ID,
|
|
5
|
+
registerGloomCloudConnectionSources,
|
|
6
|
+
} from "../../../core/connection-health";
|
|
7
|
+
|
|
8
|
+
export function createCliPaneShotConnectionHealth(now = Date.now()): ConnectionHealthRegistry {
|
|
9
|
+
const health = new ConnectionHealthRegistry({ now: () => now });
|
|
10
|
+
registerGloomCloudConnectionSources(health);
|
|
11
|
+
health.reportRequest(GLOOM_CLOUD_HTTP_CONNECTION_ID, {
|
|
12
|
+
operation: "GET /market/quotes",
|
|
13
|
+
success: true,
|
|
14
|
+
latencyMs: 84,
|
|
15
|
+
});
|
|
16
|
+
health.reportSocketState(GLOOM_CLOUD_SOCKET_CONNECTION_ID, "open", "api.gloom.sh/cloud/ws");
|
|
17
|
+
health.registerSource({
|
|
18
|
+
id: "asset-data.yahoo",
|
|
19
|
+
name: "Yahoo Finance",
|
|
20
|
+
kind: "asset-data",
|
|
21
|
+
ownerId: "market-data",
|
|
22
|
+
priority: 20,
|
|
23
|
+
});
|
|
24
|
+
health.reportRequest("asset-data.yahoo", {
|
|
25
|
+
operation: "getPriceHistory",
|
|
26
|
+
success: false,
|
|
27
|
+
latencyMs: 240,
|
|
28
|
+
error: "Rate limited",
|
|
29
|
+
});
|
|
30
|
+
return health;
|
|
31
|
+
}
|
|
@@ -199,16 +199,12 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
199
199
|
scheduleVisibleRangeMeasure();
|
|
200
200
|
}, [items.length, scheduleVisibleRangeMeasure, visibleRangeKey]);
|
|
201
201
|
|
|
202
|
+
// Rows sit on a whole-cell grid, so the offset is computed in rows and only
|
|
203
|
+
// then converted to pixels. Letting the virtualizer scroll by pixels landed
|
|
204
|
+
// mid-row and clipped the first and last visible rows into slivers.
|
|
202
205
|
useEffect(() => {
|
|
203
206
|
if (scrollToIndex == null || items.length === 0) return;
|
|
204
207
|
const targetIndex = Math.max(0, Math.min(scrollToIndex, items.length - 1));
|
|
205
|
-
if (virtualize) {
|
|
206
|
-
rowVirtualizer.scrollToIndex(targetIndex, {
|
|
207
|
-
align: scrollToIndexAlign === "center" ? "center" : "auto",
|
|
208
|
-
});
|
|
209
|
-
scheduleVisibleRangeMeasure();
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
208
|
const element = bodyElementRef.current;
|
|
213
209
|
if (!element) return;
|
|
214
210
|
const viewportRows = Math.max(1, Math.floor(element.clientHeight / WEB_CELL_HEIGHT) - 1);
|
|
@@ -227,12 +223,10 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
227
223
|
scheduleVisibleRangeMeasure();
|
|
228
224
|
}, [
|
|
229
225
|
items.length,
|
|
230
|
-
rowVirtualizer,
|
|
231
226
|
scrollToIndex,
|
|
232
227
|
scrollToIndexAlign,
|
|
233
228
|
scrollToIndexVersion,
|
|
234
229
|
scheduleVisibleRangeMeasure,
|
|
235
|
-
virtualize,
|
|
236
230
|
]);
|
|
237
231
|
|
|
238
232
|
useScrollBoxHandle(
|
|
@@ -276,6 +270,9 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
276
270
|
overflowX: horizontalScrollEnabled ? "auto" : "hidden",
|
|
277
271
|
overflowY: "auto",
|
|
278
272
|
backgroundColor: CSS_BG,
|
|
273
|
+
// Trim the viewport to whole rows so the bottom row is never a sliver.
|
|
274
|
+
// Browsers without CSS round() drop this and keep the previous behavior.
|
|
275
|
+
maxHeight: "round(down, 100%, var(--cell-h))",
|
|
279
276
|
};
|
|
280
277
|
|
|
281
278
|
return (
|
|
@@ -333,11 +330,13 @@ export function WebDataTable<T, C extends DataTableColumn = DataTableColumn>({
|
|
|
333
330
|
lineHeight: "var(--cell-h)",
|
|
334
331
|
}}
|
|
335
332
|
>
|
|
336
|
-
|
|
333
|
+
{/* cellTextStyle is inline-block for real cells, so the title and
|
|
334
|
+
hint would share one line and read as a single run-on string. */}
|
|
335
|
+
<div style={{ ...cellTextStyle(CSS_TEXT_BRIGHT, TextAttributes.BOLD), display: "block" }}>
|
|
337
336
|
{emptyStateTitle}
|
|
338
337
|
</div>
|
|
339
338
|
{emptyStateHint ? (
|
|
340
|
-
<div style={cellTextStyle(CSS_TEXT_DIM, TextAttributes.NONE)}>
|
|
339
|
+
<div style={{ ...cellTextStyle(CSS_TEXT_DIM, TextAttributes.NONE), display: "block" }}>
|
|
341
340
|
{emptyStateHint}
|
|
342
341
|
</div>
|
|
343
342
|
) : null}
|
|
@@ -208,10 +208,12 @@ function WebDataTableRowInner<
|
|
|
208
208
|
style={{
|
|
209
209
|
...baseRowStyle,
|
|
210
210
|
backgroundColor: sectionHeader.backgroundColor ?? CSS_BG,
|
|
211
|
+
cursor: sectionHeader.onMouseDown ? "pointer" : undefined,
|
|
211
212
|
}}
|
|
212
213
|
onMouseDown={(event) => {
|
|
213
214
|
focusPane();
|
|
214
215
|
onTableMouseDown?.(event);
|
|
216
|
+
sectionHeader.onMouseDown?.(event);
|
|
215
217
|
event.preventDefault();
|
|
216
218
|
}}
|
|
217
219
|
>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** @jsxImportSource react */
|
|
2
|
+
import { Window } from "happy-dom";
|
|
3
|
+
|
|
4
|
+
const testWindow = new Window({ url: "http://localhost" });
|
|
5
|
+
Object.assign(globalThis, {
|
|
6
|
+
IS_REACT_ACT_ENVIRONMENT: true,
|
|
7
|
+
window: testWindow,
|
|
8
|
+
document: testWindow.document,
|
|
9
|
+
navigator: testWindow.navigator,
|
|
10
|
+
KeyboardEvent: testWindow.KeyboardEvent,
|
|
11
|
+
HTMLElement: testWindow.HTMLElement,
|
|
12
|
+
Node: testWindow.Node,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
import { expect, test } from "bun:test";
|
|
16
|
+
import { act } from "react";
|
|
17
|
+
import { createRoot } from "react-dom/client";
|
|
18
|
+
import { UiHostProvider, type RendererHost, type UiHost } from "../../../../ui";
|
|
19
|
+
import { WebBox } from "../host/box";
|
|
20
|
+
import { WebText } from "../host/text";
|
|
21
|
+
import { WebSegmentedControl } from "./controls";
|
|
22
|
+
|
|
23
|
+
const renderer: RendererHost = {
|
|
24
|
+
requestExit() {},
|
|
25
|
+
async openExternal() {},
|
|
26
|
+
async copyText() {},
|
|
27
|
+
async readText() { return ""; },
|
|
28
|
+
notify() {},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const ui = {
|
|
32
|
+
kind: "desktop-web",
|
|
33
|
+
capabilities: { cellWidthPx: 8, fractionalViewport: true },
|
|
34
|
+
Box: WebBox,
|
|
35
|
+
Text: WebText,
|
|
36
|
+
} as unknown as UiHost;
|
|
37
|
+
|
|
38
|
+
test("desktop segmented controls expose radio semantics and keyboard selection", async () => {
|
|
39
|
+
const selected: string[] = [];
|
|
40
|
+
const container = testWindow.document.createElement("div");
|
|
41
|
+
testWindow.document.body.appendChild(container);
|
|
42
|
+
const root = createRoot(container as unknown as HTMLElement);
|
|
43
|
+
await act(async () => {
|
|
44
|
+
root.render(
|
|
45
|
+
<UiHostProvider ui={ui} renderer={renderer}>
|
|
46
|
+
<WebSegmentedControl
|
|
47
|
+
options={[{ label: "Call", value: "call" }, { label: "Put", value: "put" }]}
|
|
48
|
+
value="call"
|
|
49
|
+
onChange={(value) => selected.push(value)}
|
|
50
|
+
/>
|
|
51
|
+
</UiHostProvider>,
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const group = container.querySelector('[role="radiogroup"]');
|
|
56
|
+
const radios = [...container.querySelectorAll('[role="radio"]')] as unknown as HTMLElement[];
|
|
57
|
+
expect(group).not.toBeNull();
|
|
58
|
+
expect(radios.map((radio) => radio.getAttribute("aria-checked"))).toEqual(["true", "false"]);
|
|
59
|
+
|
|
60
|
+
await act(async () => {
|
|
61
|
+
radios[0]!.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowRight", bubbles: true }));
|
|
62
|
+
radios[1]!.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
|
|
63
|
+
});
|
|
64
|
+
expect(selected).toEqual(["put", "put"]);
|
|
65
|
+
|
|
66
|
+
await act(async () => root.unmount());
|
|
67
|
+
container.remove();
|
|
68
|
+
});
|