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
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { createThrottledFetch, type ThrottledFetchTransport } from "../../../utils/throttled-fetch";
|
|
2
2
|
import type { PluginPersistence } from "../../../types/plugin";
|
|
3
|
+
import { apiClient } from "../../../api-client";
|
|
4
|
+
import type {
|
|
5
|
+
CloudMarketResponse,
|
|
6
|
+
CloudMarketScreenerCategory,
|
|
7
|
+
CloudMarketScreenerItem,
|
|
8
|
+
CloudMarketScreenerPayload,
|
|
9
|
+
} from "../../../api-client/types";
|
|
10
|
+
import { hasProAccess } from "../shared/plan-access";
|
|
3
11
|
|
|
4
12
|
const YAHOO_FINANCE_HOSTS = [
|
|
5
13
|
"query2.finance.yahoo.com",
|
|
@@ -12,6 +20,7 @@ const CACHE_POLICY = {
|
|
|
12
20
|
staleMs: 5 * 60 * 1000,
|
|
13
21
|
expireMs: 60 * 60 * 1000,
|
|
14
22
|
} as const;
|
|
23
|
+
const YAHOO_METADATA_WAIT_MS = 1_500;
|
|
15
24
|
|
|
16
25
|
const YAHOO_FINANCE_HEADERS = {
|
|
17
26
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
|
|
@@ -101,27 +110,34 @@ function writeCache<T>(key: string, data: T): void {
|
|
|
101
110
|
});
|
|
102
111
|
}
|
|
103
112
|
|
|
113
|
+
/** A cached value plus whether it is a fallback rather than a fresh load. */
|
|
114
|
+
export interface CachedResult<T> {
|
|
115
|
+
data: T;
|
|
116
|
+
stale: boolean;
|
|
117
|
+
}
|
|
118
|
+
|
|
104
119
|
async function loadCached<T>(
|
|
105
120
|
key: string,
|
|
106
121
|
fetcher: () => Promise<T>,
|
|
107
122
|
options?: FetchCacheOptions,
|
|
108
|
-
): Promise<T
|
|
109
|
-
if (options?.cache === false) return fetcher();
|
|
123
|
+
): Promise<CachedResult<T>> {
|
|
124
|
+
if (options?.cache === false) return { data: await fetcher(), stale: false };
|
|
110
125
|
|
|
111
126
|
const cached = readCache<T>(key);
|
|
112
|
-
if (!options?.forceRefresh && cached && !cached.stale) return cached.data;
|
|
127
|
+
if (!options?.forceRefresh && cached && !cached.stale) return { data: cached.data, stale: false };
|
|
113
128
|
|
|
114
|
-
const activeFetch = activeFetches.get(key) as Promise<T
|
|
129
|
+
const activeFetch = activeFetches.get(key) as Promise<CachedResult<T>> | undefined;
|
|
115
130
|
if (activeFetch) return activeFetch;
|
|
116
131
|
|
|
117
132
|
const fallback = cached ?? readCache<T>(key, { allowExpired: true });
|
|
118
133
|
const fetchPromise = fetcher()
|
|
119
134
|
.then((data) => {
|
|
120
135
|
writeCache(key, data);
|
|
121
|
-
return data;
|
|
136
|
+
return { data, stale: false };
|
|
122
137
|
})
|
|
123
138
|
.catch((error) => {
|
|
124
|
-
|
|
139
|
+
// Serving the expired copy is right; hiding that it is expired is not.
|
|
140
|
+
if (fallback) return { data: fallback.data, stale: true };
|
|
125
141
|
throw error;
|
|
126
142
|
})
|
|
127
143
|
.finally(() => {
|
|
@@ -152,6 +168,29 @@ export interface ScreenerQuote {
|
|
|
152
168
|
dayHigh: number | undefined;
|
|
153
169
|
dayLow: number | undefined;
|
|
154
170
|
exchange: string;
|
|
171
|
+
lastUpdated?: number;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export type MarketMoversDataSource = "cloud" | "yahoo";
|
|
175
|
+
|
|
176
|
+
export interface MarketMoversResult {
|
|
177
|
+
quotes: ScreenerQuote[];
|
|
178
|
+
source: MarketMoversDataSource;
|
|
179
|
+
stale: boolean;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface PreferredMarketMoverSources {
|
|
183
|
+
isCloudEligible(): boolean;
|
|
184
|
+
fetchCloud(
|
|
185
|
+
category: CloudMarketScreenerCategory,
|
|
186
|
+
count: number,
|
|
187
|
+
mode: "cache-first" | "refresh",
|
|
188
|
+
): Promise<CloudMarketResponse<CloudMarketScreenerPayload>>;
|
|
189
|
+
fetchYahoo(
|
|
190
|
+
category: ScreenerCategory,
|
|
191
|
+
count: number,
|
|
192
|
+
options?: FetchCacheOptions,
|
|
193
|
+
): Promise<CachedResult<ScreenerQuote[]>>;
|
|
155
194
|
}
|
|
156
195
|
|
|
157
196
|
export interface TrendingSymbol {
|
|
@@ -199,12 +238,12 @@ export function parseScreenerResponse(data: any): ScreenerQuote[] {
|
|
|
199
238
|
}
|
|
200
239
|
}
|
|
201
240
|
|
|
202
|
-
export async function
|
|
241
|
+
export async function fetchScreenerResult(
|
|
203
242
|
category: ScreenerCategory,
|
|
204
243
|
count = 25,
|
|
205
244
|
api: YahooScreenerApi = screenerApi,
|
|
206
245
|
options?: FetchCacheOptions,
|
|
207
|
-
): Promise<ScreenerQuote[]
|
|
246
|
+
): Promise<CachedResult<ScreenerQuote[]>> {
|
|
208
247
|
const load = async () => {
|
|
209
248
|
const data = await api.fetchJson("/v1/finance/screener/predefined/saved", {
|
|
210
249
|
formatted: "false",
|
|
@@ -215,10 +254,129 @@ export async function fetchScreener(
|
|
|
215
254
|
});
|
|
216
255
|
return parseScreenerResponse(data);
|
|
217
256
|
};
|
|
218
|
-
if (!shouldUseCache(api, options)) return load();
|
|
257
|
+
if (!shouldUseCache(api, options)) return { data: await load(), stale: false };
|
|
219
258
|
return loadCached(`screener:${category}:count=${count}`, load, options);
|
|
220
259
|
}
|
|
221
260
|
|
|
261
|
+
export async function fetchScreener(
|
|
262
|
+
category: ScreenerCategory,
|
|
263
|
+
count = 25,
|
|
264
|
+
api: YahooScreenerApi = screenerApi,
|
|
265
|
+
options?: FetchCacheOptions,
|
|
266
|
+
): Promise<ScreenerQuote[]> {
|
|
267
|
+
return (await fetchScreenerResult(category, count, api, options)).data;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function cloudScreenerCategory(category: ScreenerCategory): CloudMarketScreenerCategory {
|
|
271
|
+
if (category === "day_gainers") return "gainers";
|
|
272
|
+
if (category === "day_losers") return "losers";
|
|
273
|
+
return "most-active";
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function isCloudScreenerEligible(): boolean {
|
|
277
|
+
const user = apiClient.getCurrentUser();
|
|
278
|
+
return apiClient.isVerified() && hasProAccess(user);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const defaultPreferredMarketMoverSources: PreferredMarketMoverSources = {
|
|
282
|
+
isCloudEligible: isCloudScreenerEligible,
|
|
283
|
+
fetchCloud: (category, count, mode) => apiClient.getCloudMarketScreener(category, count, mode),
|
|
284
|
+
fetchYahoo: (category, count, options) => fetchScreenerResult(category, count, undefined, options),
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
function mergeCloudScreenerItem(
|
|
288
|
+
item: CloudMarketScreenerItem,
|
|
289
|
+
metadata?: ScreenerQuote,
|
|
290
|
+
): ScreenerQuote {
|
|
291
|
+
return {
|
|
292
|
+
symbol: item.symbol,
|
|
293
|
+
name: item.name && item.name !== item.symbol
|
|
294
|
+
? item.name
|
|
295
|
+
: metadata?.name ?? item.symbol,
|
|
296
|
+
price: item.price,
|
|
297
|
+
change: item.change,
|
|
298
|
+
changePercent: item.changePercent,
|
|
299
|
+
volume: item.volume,
|
|
300
|
+
avgVolume: metadata?.avgVolume ?? 0,
|
|
301
|
+
volumeRatio: metadata?.avgVolume
|
|
302
|
+
? item.volume / metadata.avgVolume
|
|
303
|
+
: 0,
|
|
304
|
+
marketCap: metadata?.marketCap,
|
|
305
|
+
currency: item.currency || metadata?.currency || "USD",
|
|
306
|
+
fiftyTwoWeekHigh: item.high52w ?? metadata?.fiftyTwoWeekHigh,
|
|
307
|
+
fiftyTwoWeekLow: item.low52w ?? metadata?.fiftyTwoWeekLow,
|
|
308
|
+
dayHigh: item.dayHigh ?? metadata?.dayHigh,
|
|
309
|
+
dayLow: item.dayLow ?? metadata?.dayLow,
|
|
310
|
+
exchange: item.exchange || metadata?.exchange || "",
|
|
311
|
+
lastUpdated: item.lastUpdated,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function bestEffortYahooMetadata(
|
|
316
|
+
request: Promise<CachedResult<ScreenerQuote[]>>,
|
|
317
|
+
): Promise<ScreenerQuote[]> {
|
|
318
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
319
|
+
try {
|
|
320
|
+
return await Promise.race([
|
|
321
|
+
request.then((result) => result.data).catch(() => []),
|
|
322
|
+
new Promise<ScreenerQuote[]>((resolve) => {
|
|
323
|
+
timeout = setTimeout(() => resolve([]), YAHOO_METADATA_WAIT_MS);
|
|
324
|
+
}),
|
|
325
|
+
]);
|
|
326
|
+
} finally {
|
|
327
|
+
if (timeout) clearTimeout(timeout);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export async function fetchPreferredMarketMovers(
|
|
332
|
+
category: ScreenerCategory,
|
|
333
|
+
count = 25,
|
|
334
|
+
options?: FetchCacheOptions,
|
|
335
|
+
sources: PreferredMarketMoverSources = defaultPreferredMarketMoverSources,
|
|
336
|
+
): Promise<MarketMoversResult> {
|
|
337
|
+
if (!sources.isCloudEligible()) {
|
|
338
|
+
const result = await sources.fetchYahoo(category, count, options);
|
|
339
|
+
return { quotes: result.data, source: "yahoo", stale: result.stale };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const yahooMetadata = sources.fetchYahoo(
|
|
343
|
+
category,
|
|
344
|
+
Math.max(count, 50),
|
|
345
|
+
options,
|
|
346
|
+
);
|
|
347
|
+
try {
|
|
348
|
+
const response = await sources.fetchCloud(
|
|
349
|
+
cloudScreenerCategory(category),
|
|
350
|
+
count,
|
|
351
|
+
options?.forceRefresh ? "refresh" : "cache-first",
|
|
352
|
+
);
|
|
353
|
+
if (
|
|
354
|
+
(response.status === "success" || response.status === "partial")
|
|
355
|
+
&& response.data
|
|
356
|
+
&& response.data.items.length > 0
|
|
357
|
+
) {
|
|
358
|
+
const metadata = await bestEffortYahooMetadata(yahooMetadata);
|
|
359
|
+
const metadataBySymbol = new Map(metadata.map((quote) => [quote.symbol, quote]));
|
|
360
|
+
return {
|
|
361
|
+
quotes: response.data.items.map((item) => (
|
|
362
|
+
mergeCloudScreenerItem(item, metadataBySymbol.get(item.symbol))
|
|
363
|
+
)),
|
|
364
|
+
source: "cloud",
|
|
365
|
+
stale: response.stale === true || response.data.stale === true,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
} catch {
|
|
369
|
+
// Yahoo remains the resilient fallback when Cloud is unavailable.
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const fallback = await yahooMetadata;
|
|
373
|
+
return {
|
|
374
|
+
quotes: fallback.data,
|
|
375
|
+
source: "yahoo",
|
|
376
|
+
stale: fallback.stale,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
222
380
|
export function parseTrendingResponse(data: any): TrendingSymbol[] {
|
|
223
381
|
try {
|
|
224
382
|
const quotes = data?.finance?.result?.[0]?.quotes;
|
|
@@ -246,7 +404,7 @@ export async function fetchTrending(
|
|
|
246
404
|
return parseTrendingResponse(data);
|
|
247
405
|
};
|
|
248
406
|
if (!shouldUseCache(api, options)) return load();
|
|
249
|
-
return loadCached(`trending:US:count=${count}`, load, options);
|
|
407
|
+
return (await loadCached(`trending:US:count=${count}`, load, options)).data;
|
|
250
408
|
}
|
|
251
409
|
|
|
252
410
|
export const MARKET_SUMMARY_SYMBOLS = ["^GSPC", "^DJI", "^IXIC", "^RUT"] as const;
|
|
@@ -1,61 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useRef, useEffect, useMemo, useState } from "react";
|
|
1
|
+
import { Box } from "../../../ui";
|
|
3
2
|
import { composeBuiltinPlugin, type PluginModule } from "../plugin-module";
|
|
4
3
|
import { usePaneTicker } from "../../../state/app/context";
|
|
5
|
-
import { colors } from "../../../theme/colors";
|
|
6
|
-
import type { NewsArticle } from "../../../types/news-source";
|
|
7
4
|
import { useArticleSummary, useResolvedEntryValue } from "../../../market-data/hooks";
|
|
8
5
|
import { instrumentFromTicker } from "../../../market-data/request-types";
|
|
9
|
-
import { useDebouncedPluginPaneState } from "../../runtime";
|
|
10
|
-
import {
|
|
11
|
-
import { useNewsArticles } from "../../../news/hooks";
|
|
6
|
+
import { useDebouncedPluginPaneState, usePluginPaneState } from "../../runtime";
|
|
7
|
+
import { EmptyState } from "../../../components";
|
|
8
|
+
import { useLoadNewsStory, useNewsArticles } from "../../../news/hooks";
|
|
12
9
|
import { newsWireModule } from "./wire";
|
|
10
|
+
import { NewsDetailView, useNewsArticleDetail } from "./wire/news/detail-view";
|
|
11
|
+
import {
|
|
12
|
+
NewsArticleStackView,
|
|
13
|
+
newsTableStatusContent,
|
|
14
|
+
type NewsSortPreference,
|
|
15
|
+
} from "./wire/news/table";
|
|
13
16
|
import { useNewsArticleFooter } from "./wire/news/footer";
|
|
14
17
|
import { usePersistedNewsArticles } from "./wire/persisted-articles";
|
|
15
18
|
import { useNewsReadState } from "./wire/read-state";
|
|
16
19
|
import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
|
|
17
20
|
|
|
18
21
|
const NEWS_ITEM_LIMIT = 50;
|
|
19
|
-
|
|
20
|
-
function getFeedItems(
|
|
21
|
-
news: NewsArticle[],
|
|
22
|
-
selectedUrl: string | undefined,
|
|
23
|
-
summaryCache: Map<string, string>,
|
|
24
|
-
loadingSummary: boolean,
|
|
25
|
-
): FeedDataTableItem[] {
|
|
26
|
-
return news.map((item) => {
|
|
27
|
-
const preview = summaryCache.get(item.url) ?? item.summary ?? undefined;
|
|
28
|
-
const isSelected = item.url === selectedUrl;
|
|
29
|
-
return {
|
|
30
|
-
id: item.id,
|
|
31
|
-
eyebrow: item.source,
|
|
32
|
-
title: item.title,
|
|
33
|
-
timestamp: item.publishedAt,
|
|
34
|
-
detailTitle: item.title,
|
|
35
|
-
detailMeta: [
|
|
36
|
-
item.source,
|
|
37
|
-
`Published ${item.publishedAt.toLocaleString("en-US", {
|
|
38
|
-
month: "short",
|
|
39
|
-
day: "numeric",
|
|
40
|
-
year: "numeric",
|
|
41
|
-
hour: "numeric",
|
|
42
|
-
minute: "2-digit",
|
|
43
|
-
})}`,
|
|
44
|
-
],
|
|
45
|
-
detailBody: isSelected
|
|
46
|
-
? preview ?? (loadingSummary ? "Loading preview..." : "No preview available.")
|
|
47
|
-
: preview ?? "",
|
|
48
|
-
detailNote: item.url,
|
|
49
|
-
};
|
|
50
|
-
});
|
|
51
|
-
}
|
|
22
|
+
const DEFAULT_SORT: NewsSortPreference = { columnId: "time", direction: "desc" };
|
|
52
23
|
|
|
53
24
|
function TickerNewsView({ width, height, focused }: { width: number; height: number; focused: boolean }) {
|
|
54
25
|
const { ticker } = usePaneTicker();
|
|
55
|
-
const
|
|
56
|
-
const [
|
|
57
|
-
|
|
58
|
-
|
|
26
|
+
const symbol = ticker?.metadata.ticker ?? "none";
|
|
27
|
+
const [selectedArticleId, setSelectedArticleId] = useDebouncedPluginPaneState<string | null>(
|
|
28
|
+
`selectedArticleId:${symbol}`,
|
|
29
|
+
null,
|
|
30
|
+
);
|
|
31
|
+
const [sortPreference, setSortPreference] = usePluginPaneState<NewsSortPreference>(
|
|
32
|
+
"ticker-news:sort",
|
|
33
|
+
DEFAULT_SORT,
|
|
34
|
+
);
|
|
59
35
|
const instrument = instrumentFromTicker(ticker, ticker?.metadata.ticker ?? null);
|
|
60
36
|
const newsState = useNewsArticles(instrument ? {
|
|
61
37
|
feed: "ticker",
|
|
@@ -64,81 +40,84 @@ function TickerNewsView({ width, height, focused }: { width: number; height: num
|
|
|
64
40
|
tickerTier: "primary",
|
|
65
41
|
limit: NEWS_ITEM_LIMIT,
|
|
66
42
|
} : null);
|
|
67
|
-
const liveNews = newsState.articles;
|
|
68
43
|
const news = usePersistedNewsArticles(
|
|
69
44
|
`articles:${instrument?.symbol ?? "none"}:${instrument?.exchange ?? ""}`,
|
|
70
|
-
|
|
45
|
+
newsState.articles,
|
|
71
46
|
);
|
|
72
47
|
const { readArticleIds, markArticleRead } = useNewsReadState();
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
summaryFetchRef.current += 1;
|
|
79
|
-
setSummaryCache(new Map());
|
|
80
|
-
}, [ticker?.metadata.ticker]);
|
|
48
|
+
const loadNewsStory = useLoadNewsStory();
|
|
49
|
+
const { detailArticle, openArticle, closeDetail } = useNewsArticleDetail(news, loadNewsStory);
|
|
50
|
+
const loading = newsState.phase === "loading"
|
|
51
|
+
|| (newsState.phase === "refreshing" && news.length === 0);
|
|
52
|
+
const error = newsState.error;
|
|
81
53
|
|
|
82
|
-
const selected = news[selectedIdx];
|
|
83
|
-
const openArticle = openItemId
|
|
84
|
-
? news.find((article) => article.id === openItemId) ?? null
|
|
85
|
-
: null;
|
|
86
|
-
const cachedSelectedSummary = selected ? summaryCache.get(selected.url) : undefined;
|
|
87
54
|
const articleSummaryEntry = useArticleSummary(
|
|
88
|
-
|
|
55
|
+
detailArticle && !detailArticle.summary ? detailArticle.url : null,
|
|
89
56
|
);
|
|
90
|
-
const
|
|
91
|
-
const loadingSummary = articleSummaryEntry?.phase === "loading"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
setSummaryCache((prev) => prev.has(selected.url) ? prev : new Map(prev).set(selected.url, summary));
|
|
97
|
-
}, [selected?.summary, selected?.url]);
|
|
98
|
-
|
|
99
|
-
useEffect(() => {
|
|
100
|
-
if (!selected?.url || !selectedSummary) return;
|
|
101
|
-
setSummaryCache((prev) => new Map(prev).set(selected.url, selectedSummary));
|
|
102
|
-
}, [selected?.url, selectedSummary]);
|
|
103
|
-
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
if (news.length > 0 && selectedIdx >= news.length) {
|
|
106
|
-
setSelectedIdx(Math.max(0, news.length - 1));
|
|
107
|
-
}
|
|
108
|
-
}, [news.length, selectedIdx, setSelectedIdx]);
|
|
57
|
+
const fetchedSummary = useResolvedEntryValue(articleSummaryEntry);
|
|
58
|
+
const loadingSummary = articleSummaryEntry?.phase === "loading"
|
|
59
|
+
|| articleSummaryEntry?.phase === "refreshing";
|
|
60
|
+
const detailWithSummary = detailArticle && !detailArticle.summary && fetchedSummary
|
|
61
|
+
? { ...detailArticle, summary: fetchedSummary }
|
|
62
|
+
: detailArticle;
|
|
109
63
|
|
|
110
64
|
useNewsArticleFooter({
|
|
111
65
|
registrationId: "news",
|
|
112
66
|
focused,
|
|
113
|
-
article:
|
|
114
|
-
|
|
67
|
+
article: detailArticle,
|
|
68
|
+
// Stale rows stay on screen during a refresh or a failure, so the pane says
|
|
69
|
+
// so in the footer instead of replacing them.
|
|
70
|
+
loading: loading && news.length > 0,
|
|
115
71
|
error,
|
|
116
72
|
info: loadingSummary
|
|
117
73
|
? [{ id: "summary", parts: [{ text: "summary loading", tone: "muted" as const }] }]
|
|
118
74
|
: undefined,
|
|
119
75
|
});
|
|
120
76
|
|
|
121
|
-
if (!ticker)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
77
|
+
if (!ticker) {
|
|
78
|
+
return (
|
|
79
|
+
<Box paddingX={1} paddingY={1}>
|
|
80
|
+
<EmptyState title="No ticker selected." message="Pick a ticker to load its news." />
|
|
81
|
+
</Box>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
127
84
|
|
|
128
85
|
return (
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
height={height}
|
|
86
|
+
<NewsArticleStackView
|
|
87
|
+
articles={news}
|
|
132
88
|
focused={focused}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
89
|
+
width={width}
|
|
90
|
+
rootHeight={height}
|
|
91
|
+
readArticleIds={readArticleIds}
|
|
92
|
+
selectedArticleId={selectedArticleId}
|
|
93
|
+
setSelectedArticleId={setSelectedArticleId}
|
|
94
|
+
sortPreference={sortPreference}
|
|
95
|
+
setSortPreference={setSortPreference}
|
|
96
|
+
onOpenArticle={openArticle}
|
|
97
|
+
onArticleRead={markArticleRead}
|
|
98
|
+
detailOpen={!!detailWithSummary}
|
|
99
|
+
onBack={closeDetail}
|
|
100
|
+
detailContent={detailWithSummary ? (
|
|
101
|
+
<NewsDetailView
|
|
102
|
+
item={detailWithSummary}
|
|
103
|
+
focused={focused}
|
|
104
|
+
width={width}
|
|
105
|
+
showTitle={false}
|
|
106
|
+
/>
|
|
107
|
+
) : (
|
|
108
|
+
<Box flexGrow={1} />
|
|
109
|
+
)}
|
|
110
|
+
detailTitle={detailWithSummary?.title}
|
|
111
|
+
columns={["time", "source", "title", "categories", "sentiment"]}
|
|
112
|
+
emptyContent={newsTableStatusContent({
|
|
113
|
+
loading,
|
|
114
|
+
error,
|
|
115
|
+
subject: "News",
|
|
116
|
+
emptyTitle: `No news for ${ticker.metadata.ticker}`,
|
|
117
|
+
emptyMessage: "Stories appear as sources publish them.",
|
|
118
|
+
})}
|
|
119
|
+
emptyStateTitle={`No news for ${ticker.metadata.ticker}`}
|
|
120
|
+
emptyStateHint="Stories appear as sources publish them."
|
|
142
121
|
/>
|
|
143
122
|
);
|
|
144
123
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { NewsArticle } from "../../../../news/types";
|
|
3
|
+
import {
|
|
4
|
+
looksLikeArticleQuery,
|
|
5
|
+
searchNewsArticles,
|
|
6
|
+
tokenizeArticleQuery,
|
|
7
|
+
} from "./article-search";
|
|
8
|
+
|
|
9
|
+
function article(overrides: Partial<NewsArticle> & Pick<NewsArticle, "id" | "title" | "source">): NewsArticle {
|
|
10
|
+
return {
|
|
11
|
+
url: `https://example.com/${overrides.id}`,
|
|
12
|
+
publishedAt: new Date("2026-08-14T12:00:00Z"),
|
|
13
|
+
topic: "general",
|
|
14
|
+
topics: [],
|
|
15
|
+
sectors: [],
|
|
16
|
+
categories: [],
|
|
17
|
+
tickers: [],
|
|
18
|
+
scores: { importance: 50, urgency: 0, marketImpact: 0, novelty: 0, confidence: 0 },
|
|
19
|
+
isBreaking: false,
|
|
20
|
+
isDeveloping: false,
|
|
21
|
+
importance: 50,
|
|
22
|
+
...overrides,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const hormuz = article({
|
|
27
|
+
id: "hormuz",
|
|
28
|
+
title: "Iran Threatens to Close the Strait of Hormuz",
|
|
29
|
+
source: "Reuters",
|
|
30
|
+
summary: "Shipping risk rises as Tehran warns it may shut the strait.",
|
|
31
|
+
publishedAt: new Date("2026-08-14T18:00:00Z"),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const other = article({
|
|
35
|
+
id: "fed",
|
|
36
|
+
title: "Fed holds rates as inflation cools",
|
|
37
|
+
source: "CNBC Top News",
|
|
38
|
+
publishedAt: new Date("2026-08-13T12:00:00Z"),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("looksLikeArticleQuery", () => {
|
|
42
|
+
test("treats article, news, and ART lookups as article searches", () => {
|
|
43
|
+
expect(looksLikeArticleQuery("article on the strait")).toBe(true);
|
|
44
|
+
expect(looksLikeArticleQuery("ART hormuz")).toBe(true);
|
|
45
|
+
expect(looksLikeArticleQuery("open press story")).toBe(true);
|
|
46
|
+
expect(looksLikeArticleQuery("ADI")).toBe(false);
|
|
47
|
+
expect(looksLikeArticleQuery("nvda")).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("searchNewsArticles", () => {
|
|
52
|
+
test("matches headlines after dropping filler and intent words", () => {
|
|
53
|
+
expect(tokenizeArticleQuery("article on the strait of hormuz")).toEqual(["strait", "hormuz"]);
|
|
54
|
+
const matches = searchNewsArticles([hormuz, other], "article on the strait of hormuz");
|
|
55
|
+
expect(matches.map((item) => item.id)).toEqual(["hormuz"]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("returns most recent articles when the query is only an intent word", () => {
|
|
59
|
+
const matches = searchNewsArticles([other, hormuz], "article");
|
|
60
|
+
expect(matches.map((item) => item.id)).toEqual(["hormuz", "fed"]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("strips the ART command prefix so local headlines match immediately", () => {
|
|
64
|
+
const trump = article({
|
|
65
|
+
id: "trump",
|
|
66
|
+
title: "Trump administration pauses contentious trade talks",
|
|
67
|
+
source: "AP",
|
|
68
|
+
});
|
|
69
|
+
expect(tokenizeArticleQuery("art trum")).toEqual(["trum"]);
|
|
70
|
+
expect(searchNewsArticles([trump, other], "ART trum").map((item) => item.id)).toEqual(["trump"]);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { NewsArticle, NewsQuery } from "../../../../news/types";
|
|
2
|
+
import { getSharedNewsService } from "../../../../news/hooks";
|
|
3
|
+
|
|
4
|
+
const ARTICLE_SEARCH_LIMIT = 8;
|
|
5
|
+
|
|
6
|
+
const STOPWORDS = new Set([
|
|
7
|
+
"a",
|
|
8
|
+
"an",
|
|
9
|
+
"the",
|
|
10
|
+
"on",
|
|
11
|
+
"of",
|
|
12
|
+
"in",
|
|
13
|
+
"to",
|
|
14
|
+
"for",
|
|
15
|
+
"and",
|
|
16
|
+
"or",
|
|
17
|
+
"from",
|
|
18
|
+
"with",
|
|
19
|
+
"about",
|
|
20
|
+
"by",
|
|
21
|
+
"at",
|
|
22
|
+
"as",
|
|
23
|
+
"is",
|
|
24
|
+
"are",
|
|
25
|
+
"was",
|
|
26
|
+
"be",
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
const INTENT_WORDS = new Set([
|
|
30
|
+
"article",
|
|
31
|
+
"articles",
|
|
32
|
+
"news",
|
|
33
|
+
"headline",
|
|
34
|
+
"headlines",
|
|
35
|
+
"story",
|
|
36
|
+
"stories",
|
|
37
|
+
"rss",
|
|
38
|
+
"feed",
|
|
39
|
+
"feeds",
|
|
40
|
+
"press",
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const COMMAND_WORDS = new Set([
|
|
44
|
+
"open",
|
|
45
|
+
"read",
|
|
46
|
+
"show",
|
|
47
|
+
"find",
|
|
48
|
+
"search",
|
|
49
|
+
"please",
|
|
50
|
+
"me",
|
|
51
|
+
"latest",
|
|
52
|
+
"recent",
|
|
53
|
+
"lookup",
|
|
54
|
+
"look",
|
|
55
|
+
"up",
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
/** Pulls the freshest slice of the global feed to search over. */
|
|
59
|
+
export const ARTICLE_SEARCH_QUERY: NewsQuery = { feed: "latest", limit: 200 };
|
|
60
|
+
|
|
61
|
+
export function tokenizeArticleQuery(query: string): string[] {
|
|
62
|
+
// `ART trump` is the command prefix plus a topic — "art" is not a search term.
|
|
63
|
+
const withoutArtPrefix = query.replace(/^\s*art\b/i, " ");
|
|
64
|
+
return withoutArtPrefix
|
|
65
|
+
.toLowerCase()
|
|
66
|
+
.split(/[^a-z0-9]+/)
|
|
67
|
+
.filter((token) => (
|
|
68
|
+
token.length >= 2
|
|
69
|
+
&& !STOPWORDS.has(token)
|
|
70
|
+
&& !INTENT_WORDS.has(token)
|
|
71
|
+
&& !COMMAND_WORDS.has(token)
|
|
72
|
+
));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function looksLikeArticleQuery(query: string): boolean {
|
|
76
|
+
const tokens = query.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
77
|
+
if (tokens.some((token) => INTENT_WORDS.has(token))) return true;
|
|
78
|
+
return /^\s*art\b/i.test(query);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function scoreArticleMatch(article: NewsArticle, tokens: readonly string[]): number {
|
|
82
|
+
if (tokens.length === 0) return 0;
|
|
83
|
+
const title = article.title.toLowerCase();
|
|
84
|
+
const source = article.source.toLowerCase();
|
|
85
|
+
const haystack = [
|
|
86
|
+
title,
|
|
87
|
+
source,
|
|
88
|
+
article.summary ?? "",
|
|
89
|
+
...article.topics,
|
|
90
|
+
...article.categories,
|
|
91
|
+
...article.tickers,
|
|
92
|
+
].join(" ").toLowerCase();
|
|
93
|
+
|
|
94
|
+
let score = 0;
|
|
95
|
+
for (const token of tokens) {
|
|
96
|
+
if (title.includes(token)) score += 10;
|
|
97
|
+
else if (source.includes(token)) score += 6;
|
|
98
|
+
else if (haystack.includes(token)) score += 3;
|
|
99
|
+
else return 0;
|
|
100
|
+
}
|
|
101
|
+
return score;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function searchNewsArticles(
|
|
105
|
+
articles: readonly NewsArticle[],
|
|
106
|
+
query: string,
|
|
107
|
+
limit = ARTICLE_SEARCH_LIMIT,
|
|
108
|
+
): NewsArticle[] {
|
|
109
|
+
const tokens = tokenizeArticleQuery(query);
|
|
110
|
+
if (tokens.length === 0) {
|
|
111
|
+
return [...articles]
|
|
112
|
+
.sort((left, right) => right.publishedAt.getTime() - left.publishedAt.getTime())
|
|
113
|
+
.slice(0, limit);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return articles
|
|
117
|
+
.map((article) => ({ article, score: scoreArticleMatch(article, tokens) }))
|
|
118
|
+
.filter((entry) => entry.score > 0)
|
|
119
|
+
.sort((left, right) => {
|
|
120
|
+
if (right.score !== left.score) return right.score - left.score;
|
|
121
|
+
return right.article.publishedAt.getTime() - left.article.publishedAt.getTime();
|
|
122
|
+
})
|
|
123
|
+
.slice(0, limit)
|
|
124
|
+
.map((entry) => entry.article);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Headlines already sitting in the shared news pool (firehose/RSS/breaking). */
|
|
128
|
+
export function cachedNewsArticles(): NewsArticle[] {
|
|
129
|
+
const service = getSharedNewsService();
|
|
130
|
+
if (!service) return [];
|
|
131
|
+
const pooled = service.getFirehose(undefined, 500);
|
|
132
|
+
if (pooled.length > 0) return pooled;
|
|
133
|
+
return service.getQueryState(ARTICLE_SEARCH_QUERY).articles;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function loadNewsArticles(): Promise<NewsArticle[]> {
|
|
137
|
+
const service = getSharedNewsService();
|
|
138
|
+
if (!service) return [];
|
|
139
|
+
return (await service.load(ARTICLE_SEARCH_QUERY)).articles;
|
|
140
|
+
}
|