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
|
@@ -5,9 +5,13 @@ import { resetTerminalInputState } from "../../utils/terminal-input-reset";
|
|
|
5
5
|
import type { KeyEventLike } from "../../react/input";
|
|
6
6
|
import type { NativeRendererHost, PixelResolution, RendererHost } from "../../ui/host";
|
|
7
7
|
import { colors } from "../../theme/colors";
|
|
8
|
+
import { safeExternalUrl } from "../../utils/external-url";
|
|
9
|
+
import { createTerminalMediaReaper, terminalMediaStateFile } from "./terminal-media";
|
|
8
10
|
|
|
9
11
|
export { useKeyboard, useTerminalDimensions };
|
|
10
12
|
|
|
13
|
+
const terminalMedia = createTerminalMediaReaper({ stateFile: terminalMediaStateFile() });
|
|
14
|
+
|
|
11
15
|
export interface OpenTuiHost {
|
|
12
16
|
renderer: CliRenderer;
|
|
13
17
|
rendererHost: RendererHost;
|
|
@@ -95,7 +99,9 @@ export async function createOpenTuiHost(): Promise<OpenTuiHost> {
|
|
|
95
99
|
|
|
96
100
|
const rendererHost: RendererHost = {
|
|
97
101
|
requestExit: () => renderer.destroy(),
|
|
98
|
-
async openExternal(
|
|
102
|
+
async openExternal(rawUrl) {
|
|
103
|
+
const url = safeExternalUrl(rawUrl);
|
|
104
|
+
if (!url) return;
|
|
99
105
|
const command = process.platform === "darwin"
|
|
100
106
|
? ["open", url]
|
|
101
107
|
: process.platform === "win32"
|
|
@@ -137,6 +143,9 @@ export async function createOpenTuiHost(): Promise<OpenTuiHost> {
|
|
|
137
143
|
throw new Error("mpv is required for terminal TV playback. Install mpv and try again.");
|
|
138
144
|
}
|
|
139
145
|
|
|
146
|
+
// A player stranded by a previous run keeps decoding video, so clear it
|
|
147
|
+
// before adding another one.
|
|
148
|
+
terminalMedia.reapStale();
|
|
140
149
|
renderer.suspend();
|
|
141
150
|
try {
|
|
142
151
|
const proc = Bun.spawn([
|
|
@@ -158,6 +167,7 @@ export async function createOpenTuiHost(): Promise<OpenTuiHost> {
|
|
|
158
167
|
stdout: "inherit",
|
|
159
168
|
stderr: "pipe",
|
|
160
169
|
});
|
|
170
|
+
terminalMedia.track(proc);
|
|
161
171
|
const stderrPromise = new Response(proc.stderr).text();
|
|
162
172
|
const exitCode = await proc.exited;
|
|
163
173
|
const stderr = await stderrPromise;
|
|
@@ -166,10 +176,14 @@ export async function createOpenTuiHost(): Promise<OpenTuiHost> {
|
|
|
166
176
|
throw new Error(detail || `mpv exited with status ${exitCode}`);
|
|
167
177
|
}
|
|
168
178
|
} finally {
|
|
179
|
+
terminalMedia.stopActive();
|
|
169
180
|
renderer.resume();
|
|
170
181
|
renderer.requestRender();
|
|
171
182
|
}
|
|
172
183
|
},
|
|
184
|
+
stopTerminalMedia() {
|
|
185
|
+
terminalMedia.stopActive();
|
|
186
|
+
},
|
|
173
187
|
};
|
|
174
188
|
|
|
175
189
|
const nativeRenderer: NativeRendererHost = {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { afterEach, expect, test } from "bun:test";
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { createTerminalMediaReaper, type TerminalMediaChild } from "./terminal-media";
|
|
6
|
+
|
|
7
|
+
const dirs: string[] = [];
|
|
8
|
+
|
|
9
|
+
function stateFile(): string {
|
|
10
|
+
const dir = mkdtempSync(join(tmpdir(), "gloom-media-"));
|
|
11
|
+
dirs.push(dir);
|
|
12
|
+
return join(dir, "terminal-media.pid");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function fakeChild(pid: number): TerminalMediaChild & { killed: boolean; exit(): void } {
|
|
16
|
+
let resolveExit: (code: number) => void = () => {};
|
|
17
|
+
const exited = new Promise<number>((resolve) => { resolveExit = resolve; });
|
|
18
|
+
return {
|
|
19
|
+
pid,
|
|
20
|
+
killed: false,
|
|
21
|
+
kill() { this.killed = true; },
|
|
22
|
+
exited,
|
|
23
|
+
exit() { resolveExit(0); },
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("kills the player left behind by a run that was killed without warning", () => {
|
|
32
|
+
const file = stateFile();
|
|
33
|
+
writeFileSync(file, "4242", "utf8");
|
|
34
|
+
const killed: number[] = [];
|
|
35
|
+
const reaper = createTerminalMediaReaper({
|
|
36
|
+
stateFile: file,
|
|
37
|
+
isPlayerProcess: (pid) => pid === 4242,
|
|
38
|
+
killProcess: (pid) => killed.push(pid),
|
|
39
|
+
installExitHooks: false,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
reaper.reapStale();
|
|
43
|
+
|
|
44
|
+
expect(killed).toEqual([4242]);
|
|
45
|
+
// The pid must not linger, or a later run kills whatever inherits it.
|
|
46
|
+
expect(existsSync(file)).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("leaves a recycled pid alone when it is not our player", () => {
|
|
50
|
+
const file = stateFile();
|
|
51
|
+
writeFileSync(file, "4242", "utf8");
|
|
52
|
+
const killed: number[] = [];
|
|
53
|
+
const reaper = createTerminalMediaReaper({
|
|
54
|
+
stateFile: file,
|
|
55
|
+
isPlayerProcess: () => false,
|
|
56
|
+
killProcess: (pid) => killed.push(pid),
|
|
57
|
+
installExitHooks: false,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
reaper.reapStale();
|
|
61
|
+
|
|
62
|
+
expect(killed).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("replacing a player kills the previous one and records the new pid", () => {
|
|
66
|
+
const file = stateFile();
|
|
67
|
+
const reaper = createTerminalMediaReaper({
|
|
68
|
+
stateFile: file,
|
|
69
|
+
isPlayerProcess: () => false,
|
|
70
|
+
killProcess: () => {},
|
|
71
|
+
installExitHooks: false,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const first = fakeChild(11);
|
|
75
|
+
const second = fakeChild(22);
|
|
76
|
+
reaper.track(first);
|
|
77
|
+
expect(readFileSync(file, "utf8")).toBe("11");
|
|
78
|
+
|
|
79
|
+
reaper.track(second);
|
|
80
|
+
|
|
81
|
+
expect(first.killed).toBe(true);
|
|
82
|
+
expect(second.killed).toBe(false);
|
|
83
|
+
expect(readFileSync(file, "utf8")).toBe("22");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("a player that exits on its own clears the recorded pid", async () => {
|
|
87
|
+
const file = stateFile();
|
|
88
|
+
const reaper = createTerminalMediaReaper({
|
|
89
|
+
stateFile: file,
|
|
90
|
+
isPlayerProcess: () => false,
|
|
91
|
+
killProcess: () => {},
|
|
92
|
+
installExitHooks: false,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const child = fakeChild(33);
|
|
96
|
+
reaper.track(child);
|
|
97
|
+
child.exit();
|
|
98
|
+
await child.exited;
|
|
99
|
+
await Promise.resolve();
|
|
100
|
+
|
|
101
|
+
expect(existsSync(file)).toBe(false);
|
|
102
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
4
|
+
|
|
5
|
+
/** The subset of `Bun.Subprocess` this module needs, so tests can supply a fake. */
|
|
6
|
+
export interface TerminalMediaChild {
|
|
7
|
+
readonly pid: number;
|
|
8
|
+
kill(): void;
|
|
9
|
+
readonly exited: Promise<number>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface TerminalMediaReaperOptions {
|
|
13
|
+
/** File holding the pid of the player started by the most recent run. */
|
|
14
|
+
stateFile: string;
|
|
15
|
+
/** True when the pid is still running and is actually our media player. */
|
|
16
|
+
isPlayerProcess?(pid: number): boolean;
|
|
17
|
+
killProcess?(pid: number): void;
|
|
18
|
+
/** Install process exit hooks. Off in tests so the suite keeps its handlers. */
|
|
19
|
+
installExitHooks?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface TerminalMediaReaper {
|
|
23
|
+
/** Kill a player left behind by a previous run of the app. */
|
|
24
|
+
reapStale(): void;
|
|
25
|
+
/** Adopt a freshly spawned player, replacing and killing any current one. */
|
|
26
|
+
track(child: TerminalMediaChild): void;
|
|
27
|
+
/** Kill the player this run started, if it is still going. */
|
|
28
|
+
stopActive(): void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function terminalMediaStateFile(): string {
|
|
32
|
+
return join(process.env.HOME || homedir(), ".gloomberb", "terminal-media.pid");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function defaultIsPlayerProcess(pid: number): boolean {
|
|
36
|
+
if (!Number.isInteger(pid) || pid <= 1) return false;
|
|
37
|
+
try {
|
|
38
|
+
// A recycled pid could belong to anything, so confirm the command first.
|
|
39
|
+
const probe = Bun.spawnSync(["ps", "-p", String(pid), "-o", "comm="]);
|
|
40
|
+
if (probe.exitCode !== 0) return false;
|
|
41
|
+
return probe.stdout.toString().toLowerCase().includes("mpv");
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function defaultKillProcess(pid: number): void {
|
|
48
|
+
try {
|
|
49
|
+
process.kill(pid, "SIGTERM");
|
|
50
|
+
} catch {
|
|
51
|
+
// Already gone, or not ours to kill.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* `mpv` outlives the app: it is a plain child process, and `bun run --watch`
|
|
57
|
+
* restarts by SIGKILLing the parent, so no exit handler ever runs. Every reload
|
|
58
|
+
* with the TV pane open used to strand another player decoding video forever.
|
|
59
|
+
*
|
|
60
|
+
* Recording the pid on disk lets the next run kill the previous player even
|
|
61
|
+
* though that run was killed without warning.
|
|
62
|
+
*/
|
|
63
|
+
export function createTerminalMediaReaper(options: TerminalMediaReaperOptions): TerminalMediaReaper {
|
|
64
|
+
const {
|
|
65
|
+
stateFile,
|
|
66
|
+
isPlayerProcess = defaultIsPlayerProcess,
|
|
67
|
+
killProcess = defaultKillProcess,
|
|
68
|
+
installExitHooks = true,
|
|
69
|
+
} = options;
|
|
70
|
+
|
|
71
|
+
let active: TerminalMediaChild | null = null;
|
|
72
|
+
let hooksInstalled = false;
|
|
73
|
+
|
|
74
|
+
function forgetState(): void {
|
|
75
|
+
try {
|
|
76
|
+
rmSync(stateFile, { force: true });
|
|
77
|
+
} catch {
|
|
78
|
+
// A stale pid file is harmless; the next reap validates it anyway.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function rememberState(pid: number): void {
|
|
83
|
+
try {
|
|
84
|
+
writeFileSync(stateFile, String(pid), "utf8");
|
|
85
|
+
} catch {
|
|
86
|
+
// Losing the pid only costs us cross-restart reaping, never correctness.
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function stopActive(): void {
|
|
91
|
+
const child = active;
|
|
92
|
+
active = null;
|
|
93
|
+
if (child) {
|
|
94
|
+
try {
|
|
95
|
+
child.kill();
|
|
96
|
+
} catch {
|
|
97
|
+
// Already exited.
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
forgetState();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function installExitCleanup(): void {
|
|
104
|
+
if (hooksInstalled || !installExitHooks) return;
|
|
105
|
+
hooksInstalled = true;
|
|
106
|
+
// Covers orderly shutdown. A SIGKILLed parent is handled by reapStale.
|
|
107
|
+
process.on("exit", stopActive);
|
|
108
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"] as const) {
|
|
109
|
+
process.on(signal, () => {
|
|
110
|
+
stopActive();
|
|
111
|
+
process.exit(0);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
reapStale() {
|
|
118
|
+
if (!existsSync(stateFile)) return;
|
|
119
|
+
let pid = 0;
|
|
120
|
+
try {
|
|
121
|
+
pid = Number.parseInt(readFileSync(stateFile, "utf8").trim(), 10);
|
|
122
|
+
} catch {
|
|
123
|
+
pid = 0;
|
|
124
|
+
}
|
|
125
|
+
forgetState();
|
|
126
|
+
if (Number.isFinite(pid) && pid > 0 && isPlayerProcess(pid)) killProcess(pid);
|
|
127
|
+
},
|
|
128
|
+
track(child) {
|
|
129
|
+
stopActive();
|
|
130
|
+
active = child;
|
|
131
|
+
rememberState(child.pid);
|
|
132
|
+
installExitCleanup();
|
|
133
|
+
void child.exited.then(() => {
|
|
134
|
+
if (active === child) {
|
|
135
|
+
active = null;
|
|
136
|
+
forgetState();
|
|
137
|
+
}
|
|
138
|
+
}).catch(() => {});
|
|
139
|
+
},
|
|
140
|
+
stopActive,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useMemo, type ReactNode } from "react";
|
|
2
|
-
import { DialogProvider } from "@opentui-ui/dialog/react";
|
|
3
2
|
import { testRender as openTuiTestRender } from "@opentui/react/test-utils";
|
|
4
3
|
import { createRoot as openTuiCreateRoot, useRenderer } from "@opentui/react";
|
|
5
4
|
import { UiHostProvider, type NativeRendererHost, type RendererHost } from "../../ui";
|
|
@@ -12,7 +11,7 @@ import { openTuiToastHost } from "./toast-host";
|
|
|
12
11
|
|
|
13
12
|
export function TestDialogProvider({ children }: { children: ReactNode }) {
|
|
14
13
|
return (
|
|
15
|
-
<
|
|
14
|
+
<OpenTuiDialogHostProvider
|
|
16
15
|
dialogOptions={{
|
|
17
16
|
style: {
|
|
18
17
|
backgroundColor: colors.bg,
|
|
@@ -22,7 +21,7 @@ export function TestDialogProvider({ children }: { children: ReactNode }) {
|
|
|
22
21
|
}}
|
|
23
22
|
>
|
|
24
23
|
{children}
|
|
25
|
-
</
|
|
24
|
+
</OpenTuiDialogHostProvider>
|
|
26
25
|
);
|
|
27
26
|
}
|
|
28
27
|
|
|
@@ -55,9 +54,13 @@ function OpenTuiTestProviders({ children }: { children: ReactNode }) {
|
|
|
55
54
|
<OpenTuiInputHostProvider>
|
|
56
55
|
<ToastHostProvider host={openTuiToastHost}>
|
|
57
56
|
<OpenTuiDialogHostProvider
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
dialogOptions={{
|
|
58
|
+
style: {
|
|
59
|
+
backgroundColor: colors.bg,
|
|
60
|
+
borderColor: colors.borderFocused,
|
|
61
|
+
borderStyle: "single",
|
|
62
|
+
},
|
|
63
|
+
}}
|
|
61
64
|
>
|
|
62
65
|
{children}
|
|
63
66
|
</OpenTuiDialogHostProvider>
|
|
@@ -1,10 +1,139 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
1
|
+
/** @jsxImportSource @opentui/react */
|
|
2
|
+
import { useTerminalDimensions } from "@opentui/react";
|
|
3
|
+
import { useSyncExternalStore } from "react";
|
|
4
|
+
import { colors } from "../../theme/colors";
|
|
5
|
+
import type { ToastHost, ToastOptions } from "../../ui/toast";
|
|
6
|
+
|
|
7
|
+
type ToastTone = "success" | "error" | "info";
|
|
8
|
+
|
|
9
|
+
interface ToastRecord {
|
|
10
|
+
id: number;
|
|
11
|
+
body: string;
|
|
12
|
+
tone: ToastTone;
|
|
13
|
+
options?: ToastOptions;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let nextToastId = 1;
|
|
17
|
+
let toasts: ToastRecord[] = [];
|
|
18
|
+
const listeners = new Set<() => void>();
|
|
19
|
+
const timers = new Map<number, ReturnType<typeof setTimeout>>();
|
|
20
|
+
|
|
21
|
+
function publish(): void {
|
|
22
|
+
for (const listener of listeners) listener();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function dismissToast(id: string | number): void {
|
|
26
|
+
const numericId = typeof id === "number" ? id : Number(id);
|
|
27
|
+
if (!Number.isFinite(numericId)) return;
|
|
28
|
+
const timer = timers.get(numericId);
|
|
29
|
+
if (timer) clearTimeout(timer);
|
|
30
|
+
timers.delete(numericId);
|
|
31
|
+
const next = toasts.filter((toast) => toast.id !== numericId);
|
|
32
|
+
if (next.length === toasts.length) return;
|
|
33
|
+
toasts = next;
|
|
34
|
+
publish();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function addToast(tone: ToastTone, body: string, options?: ToastOptions): number {
|
|
38
|
+
const id = nextToastId++;
|
|
39
|
+
toasts = [...toasts, { id, body, tone, options }];
|
|
40
|
+
publish();
|
|
41
|
+
const duration = options?.duration ?? 4_000;
|
|
42
|
+
if (Number.isFinite(duration) && duration > 0) {
|
|
43
|
+
timers.set(id, setTimeout(() => dismissToast(id), duration));
|
|
44
|
+
}
|
|
45
|
+
return id;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function subscribe(listener: () => void): () => void {
|
|
49
|
+
listeners.add(listener);
|
|
50
|
+
return () => listeners.delete(listener);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getSnapshot(): ToastRecord[] {
|
|
54
|
+
return toasts;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function toneColor(tone: ToastTone): string {
|
|
58
|
+
if (tone === "success") return colors.positive;
|
|
59
|
+
if (tone === "error") return colors.negative;
|
|
60
|
+
return colors.neutral;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function toneIcon(tone: ToastTone): string {
|
|
64
|
+
if (tone === "success") return "✓";
|
|
65
|
+
if (tone === "error") return "!";
|
|
66
|
+
return "i";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function ToastViewport({ position = "bottom-right" }: { position?: string }) {
|
|
70
|
+
const dimensions = useTerminalDimensions();
|
|
71
|
+
const visibleToasts = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
72
|
+
const maxWidth = Math.max(1, Math.min(60, dimensions.width - 4));
|
|
73
|
+
const placement = position.startsWith("top") ? { top: 1 } : { bottom: 1 };
|
|
74
|
+
const horizontal = position.endsWith("left")
|
|
75
|
+
? { left: 2 }
|
|
76
|
+
: position.endsWith("center")
|
|
77
|
+
? { left: Math.max(0, Math.floor((dimensions.width - maxWidth) / 2)) }
|
|
78
|
+
: { right: 2 };
|
|
79
|
+
|
|
80
|
+
if (visibleToasts.length === 0) return null;
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<box
|
|
84
|
+
position="absolute"
|
|
85
|
+
{...placement}
|
|
86
|
+
{...horizontal}
|
|
87
|
+
width={maxWidth}
|
|
88
|
+
zIndex={9_900}
|
|
89
|
+
flexDirection="column"
|
|
90
|
+
gap={1}
|
|
91
|
+
>
|
|
92
|
+
{visibleToasts.slice(-4).map((toast) => (
|
|
93
|
+
<box
|
|
94
|
+
key={toast.id}
|
|
95
|
+
width="100%"
|
|
96
|
+
border
|
|
97
|
+
borderStyle="single"
|
|
98
|
+
borderColor={toneColor(toast.tone)}
|
|
99
|
+
backgroundColor={colors.panel}
|
|
100
|
+
paddingX={1}
|
|
101
|
+
flexDirection="row"
|
|
102
|
+
gap={1}
|
|
103
|
+
>
|
|
104
|
+
<text fg={toneColor(toast.tone)}>{toneIcon(toast.tone)}</text>
|
|
105
|
+
<text fg={colors.text} flexGrow={1} wrapMode="word">{toast.body}</text>
|
|
106
|
+
{toast.options?.action && (
|
|
107
|
+
<text
|
|
108
|
+
fg={colors.textBright}
|
|
109
|
+
attributes={1}
|
|
110
|
+
onMouseDown={(event) => {
|
|
111
|
+
event.stopPropagation();
|
|
112
|
+
toast.options?.action?.onClick();
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
{`[${toast.options.action.label}]`}
|
|
116
|
+
</text>
|
|
117
|
+
)}
|
|
118
|
+
<text
|
|
119
|
+
fg={colors.textMuted}
|
|
120
|
+
onMouseDown={(event) => {
|
|
121
|
+
event.stopPropagation();
|
|
122
|
+
dismissToast(toast.id);
|
|
123
|
+
}}
|
|
124
|
+
>
|
|
125
|
+
×
|
|
126
|
+
</text>
|
|
127
|
+
</box>
|
|
128
|
+
))}
|
|
129
|
+
</box>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
3
132
|
|
|
4
133
|
export const openTuiToastHost: ToastHost = {
|
|
5
|
-
Viewport:
|
|
6
|
-
success:
|
|
7
|
-
error:
|
|
8
|
-
info:
|
|
9
|
-
dismiss:
|
|
134
|
+
Viewport: ToastViewport,
|
|
135
|
+
success: (body, options) => addToast("success", body, options),
|
|
136
|
+
error: (body, options) => addToast("error", body, options),
|
|
137
|
+
info: (body, options) => addToast("info", body, options),
|
|
138
|
+
dismiss: dismissToast,
|
|
10
139
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { RGBA, StyledText as OpenTuiStyledText, SyntaxStyle, TextAttributes as OpenTuiTextAttributes } from "@opentui/core";
|
|
2
|
-
import { createElement, forwardRef, type ReactNode } from "react";
|
|
3
|
-
import "opentui-spinner/react";
|
|
2
|
+
import { createElement, forwardRef, useEffect, useState, type ReactNode } from "react";
|
|
4
3
|
import { TextAttributes, type UiHost, type TextProps } from "../../ui/host";
|
|
5
4
|
import { renderAsciiText } from "../../ui/ascii-font";
|
|
6
5
|
import { OpenTuiImageSurface } from "./image/surface";
|
|
@@ -60,9 +59,26 @@ const OpenTuiBox = createOpenTuiPrimitive("box");
|
|
|
60
59
|
const OpenTuiScrollBox = createOpenTuiPrimitive("scrollbox");
|
|
61
60
|
const OpenTuiInput = createOpenTuiPrimitive("input");
|
|
62
61
|
const OpenTuiTextarea = createOpenTuiPrimitive("textarea");
|
|
63
|
-
const OpenTuiSpinnerMark = createOpenTuiPrimitive("spinner");
|
|
64
62
|
const OpenTuiMediaSurface = createOpenTuiPrimitive("box");
|
|
65
63
|
|
|
64
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
65
|
+
|
|
66
|
+
const OpenTuiSpinnerMark = forwardRef<unknown, OpenTuiPrimitiveProps & { name?: string; color?: string }>(
|
|
67
|
+
function OpenTuiSpinnerMark({ name: _name, color, ...props }, ref) {
|
|
68
|
+
const [frame, setFrame] = useState(0);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
const timer = setInterval(() => setFrame((current) => (current + 1) % SPINNER_FRAMES.length), 80);
|
|
71
|
+
return () => clearInterval(timer);
|
|
72
|
+
}, []);
|
|
73
|
+
return createElement("text" as any, {
|
|
74
|
+
...props,
|
|
75
|
+
ref,
|
|
76
|
+
fg: color,
|
|
77
|
+
content: SPINNER_FRAMES[frame],
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
|
|
66
82
|
const OpenTuiText = forwardRef<unknown, TextProps>(function OpenTuiText({ children, ...props }, ref) {
|
|
67
83
|
const textProps = stripTextProps(props);
|
|
68
84
|
return createElement("text" as any, {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { ConnectionHealthRegistry } from "../../core/connection-health";
|
|
3
|
+
import type { DataProvider } from "../../types/data-provider";
|
|
4
|
+
import { withProviderConnectionHealth } from "./connection-health";
|
|
5
|
+
|
|
6
|
+
describe("withProviderConnectionHealth", () => {
|
|
7
|
+
test("attributes network calls without reporting cached or static checks", async () => {
|
|
8
|
+
const health = new ConnectionHealthRegistry();
|
|
9
|
+
health.registerSource({ id: "asset-data.yahoo", name: "Yahoo", kind: "asset-data" });
|
|
10
|
+
const provider = withProviderConnectionHealth({
|
|
11
|
+
id: "yahoo",
|
|
12
|
+
name: "Yahoo",
|
|
13
|
+
canProvide: () => true,
|
|
14
|
+
getCachedFinancialsForTargets: () => new Map(),
|
|
15
|
+
getChartResolutionSupport: () => [],
|
|
16
|
+
getQuote: async () => ({ price: 1 }),
|
|
17
|
+
} as unknown as DataProvider, health);
|
|
18
|
+
|
|
19
|
+
await provider.canProvide?.("AAPL");
|
|
20
|
+
provider.getCachedFinancialsForTargets?.([]);
|
|
21
|
+
await provider.getChartResolutionSupport?.("AAPL");
|
|
22
|
+
await provider.getQuote("AAPL");
|
|
23
|
+
|
|
24
|
+
const state = health.getSnapshot().sources[0]!;
|
|
25
|
+
expect(state.lastOperation).toBe("getQuote");
|
|
26
|
+
expect(state.recentRequests).toHaveLength(1);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ConnectionHealthRegistry } from "../../core/connection-health";
|
|
2
|
+
import type { DataProvider } from "../../types/data-provider";
|
|
3
|
+
|
|
4
|
+
const REQUEST_METHODS = new Set([
|
|
5
|
+
"getQuotesBatch",
|
|
6
|
+
"getTickerFinancialsBatch",
|
|
7
|
+
"getTickerFinancials",
|
|
8
|
+
"getQuote",
|
|
9
|
+
"getExchangeRate",
|
|
10
|
+
"search",
|
|
11
|
+
"getNews",
|
|
12
|
+
"getHolders",
|
|
13
|
+
"getAnalystResearch",
|
|
14
|
+
"getCorporateActions",
|
|
15
|
+
"getEarningsCalendar",
|
|
16
|
+
"getSecFilings",
|
|
17
|
+
"getSecFilingDocuments",
|
|
18
|
+
"getSecFilingContent",
|
|
19
|
+
"getArticleSummary",
|
|
20
|
+
"getPriceHistory",
|
|
21
|
+
"getPriceHistoryForResolution",
|
|
22
|
+
"getDetailedPriceHistory",
|
|
23
|
+
"getOptionsChain",
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
/** Wraps provider calls without changing the provider object or treating router cache reads as requests. */
|
|
27
|
+
export function withProviderConnectionHealth(
|
|
28
|
+
provider: DataProvider,
|
|
29
|
+
health: ConnectionHealthRegistry,
|
|
30
|
+
): DataProvider {
|
|
31
|
+
const methods = new Map<PropertyKey, unknown>();
|
|
32
|
+
return new Proxy(provider, {
|
|
33
|
+
get(target, property, receiver) {
|
|
34
|
+
const value = Reflect.get(target, property, receiver);
|
|
35
|
+
if (typeof property !== "string" || typeof value !== "function") return value;
|
|
36
|
+
if (!REQUEST_METHODS.has(property)) return value.bind(target);
|
|
37
|
+
if (!methods.has(property)) {
|
|
38
|
+
methods.set(property, (...args: unknown[]) => health.track(
|
|
39
|
+
`asset-data.${provider.id}`,
|
|
40
|
+
property,
|
|
41
|
+
() => Promise.resolve(value.apply(target, args)),
|
|
42
|
+
));
|
|
43
|
+
}
|
|
44
|
+
return methods.get(property);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -283,8 +283,8 @@ describe("AssetDataRouter chart history", () => {
|
|
|
283
283
|
persistence.close();
|
|
284
284
|
});
|
|
285
285
|
|
|
286
|
-
test("
|
|
287
|
-
const dbPath = createTempDbPath("stale-chart-
|
|
286
|
+
test("returns stale unexpired chart history immediately and refreshes in the background", async () => {
|
|
287
|
+
const dbPath = createTempDbPath("stale-chart-hit");
|
|
288
288
|
const persistence = new AppPersistence(dbPath);
|
|
289
289
|
|
|
290
290
|
const seedRouter = new AssetDataRouter({
|
|
@@ -299,19 +299,68 @@ describe("AssetDataRouter chart history", () => {
|
|
|
299
299
|
.query("UPDATE resource_cache SET stale_at = ? WHERE namespace = ? AND kind = ? AND entity_key = ?")
|
|
300
300
|
.run(Date.now() - 1, "market", "price-history", "AAPL");
|
|
301
301
|
|
|
302
|
+
let resolveFresh!: (points: Array<{ date: Date; close: number }>) => void;
|
|
303
|
+
const freshHistory = new Promise<Array<{ date: Date; close: number }>>((resolve) => {
|
|
304
|
+
resolveFresh = resolve;
|
|
305
|
+
});
|
|
302
306
|
let providerCalls = 0;
|
|
303
307
|
const refreshRouter = new AssetDataRouter({
|
|
304
308
|
...fallbackProvider,
|
|
305
309
|
async getPriceHistory() {
|
|
306
310
|
providerCalls += 1;
|
|
307
|
-
return
|
|
311
|
+
return freshHistory;
|
|
308
312
|
},
|
|
309
313
|
}, [], persistence.resources);
|
|
310
314
|
|
|
311
315
|
const history = await refreshRouter.getPriceHistory("AAPL", "NASDAQ", "1Y");
|
|
312
316
|
|
|
317
|
+
expect(history[0]?.close).toBe(101);
|
|
318
|
+
await Promise.resolve();
|
|
313
319
|
expect(providerCalls).toBe(1);
|
|
314
|
-
|
|
320
|
+
|
|
321
|
+
resolveFresh([{ date: new Date("2026-03-28T00:00:00Z"), close: 202 }]);
|
|
322
|
+
await freshHistory;
|
|
323
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
324
|
+
|
|
325
|
+
const refreshed = await refreshRouter.getPriceHistory("AAPL", "NASDAQ", "1Y");
|
|
326
|
+
expect(refreshed[0]?.close).toBe(202);
|
|
327
|
+
|
|
328
|
+
persistence.close();
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("clips a shorter resolution range from a wider cached series", async () => {
|
|
332
|
+
const dbPath = createTempDbPath("clip-wider-chart-cache");
|
|
333
|
+
const persistence = new AppPersistence(dbPath);
|
|
334
|
+
const now = Date.parse("2026-03-28T00:00:00Z");
|
|
335
|
+
const sixYearsAgo = new Date(now - 6 * 365 * 24 * 60 * 60_000);
|
|
336
|
+
const twoYearsAgo = new Date(now - 2 * 365 * 24 * 60 * 60_000);
|
|
337
|
+
const latest = new Date(now);
|
|
338
|
+
|
|
339
|
+
const seedRouter = new AssetDataRouter({
|
|
340
|
+
...fallbackProvider,
|
|
341
|
+
async getPriceHistoryForResolution() {
|
|
342
|
+
return [
|
|
343
|
+
{ date: sixYearsAgo, close: 50 },
|
|
344
|
+
{ date: twoYearsAgo, close: 101 },
|
|
345
|
+
{ date: latest, close: 110 },
|
|
346
|
+
];
|
|
347
|
+
},
|
|
348
|
+
}, [], persistence.resources);
|
|
349
|
+
await seedRouter.getPriceHistoryForResolution("AAPL", "NASDAQ", "ALL", "1wk");
|
|
350
|
+
|
|
351
|
+
let providerCalls = 0;
|
|
352
|
+
const clipRouter = new AssetDataRouter({
|
|
353
|
+
...fallbackProvider,
|
|
354
|
+
async getPriceHistoryForResolution() {
|
|
355
|
+
providerCalls += 1;
|
|
356
|
+
return [{ date: latest, close: 999 }];
|
|
357
|
+
},
|
|
358
|
+
}, [], persistence.resources);
|
|
359
|
+
|
|
360
|
+
const history = await clipRouter.getPriceHistoryForResolution("AAPL", "NASDAQ", "5Y", "1wk");
|
|
361
|
+
|
|
362
|
+
expect(providerCalls).toBe(0);
|
|
363
|
+
expect(history.map((point) => point.close)).toEqual([101, 110]);
|
|
315
364
|
|
|
316
365
|
persistence.close();
|
|
317
366
|
});
|