gloomberb 0.18.4 → 0.19.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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +169 -5
- package/src/account/auth-model.ts +7 -3
- package/src/api-client/auth.ts +9 -4
- package/src/api-client/catalysts.ts +2 -1
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +31 -6
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/index.ts +171 -11
- package/src/api-client/live-scan.ts +91 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +13 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/request.ts +222 -41
- package/src/api-client/research-activity.ts +6 -12
- package/src/api-client/types.ts +74 -0
- package/src/app/keybindings/index.ts +3 -0
- package/src/app/keybindings/labels.ts +18 -1
- package/src/app/keybindings/resolve.ts +14 -0
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +42 -27
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +1 -1
- package/src/architecture/pane-tabs-allowlist.json +3 -0
- package/src/architecture/ttl-caches-allowlist.json +19 -0
- package/src/brokers/sync-broker-instance.ts +2 -8
- package/src/capabilities/chart-series.ts +1 -1
- package/src/cli/commands/crypto-hints.ts +40 -24
- package/src/cli/commands/fred-series.ts +5 -0
- package/src/cli/commands/keybindings.ts +23 -0
- package/src/cli/commands/market.ts +138 -46
- package/src/cli/commands/options-chain.ts +107 -10
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +176 -39
- package/src/cli/commands/search.ts +39 -3
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +57 -35
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +10 -2
- package/src/cli/export-source.ts +29 -1
- package/src/cli/glossary.ts +73 -1
- package/src/cli/history-rows.ts +87 -16
- package/src/cli/index.ts +2 -0
- package/src/cli/known-gaps.ts +5 -2
- package/src/cli/listing-arg.ts +195 -56
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +3 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +111 -16
- package/src/cli/pane-functions/function-help.ts +23 -13
- package/src/cli/pane-functions/headless.ts +56 -12
- package/src/cli/pane-functions/index.ts +58 -16
- package/src/cli/pane-functions/market-session.ts +81 -9
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/report.ts +5 -1
- package/src/cli/pane-functions/resolver.ts +35 -8
- package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
- package/src/cli/pane-functions/screenshot-types.ts +5 -0
- package/src/cli/quote-answer.ts +30 -13
- package/src/cli/report-tables.ts +18 -7
- package/src/cli/restore-plugins.ts +13 -1
- package/src/cli/result.ts +57 -5
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +26 -4
- package/src/components/chart/composite/format.ts +27 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/levels.ts +2 -0
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +61 -5
- package/src/components/chart/composite/range-statistics.ts +62 -0
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +46 -7
- package/src/components/chart/composite/series-axis-tags.tsx +16 -9
- package/src/components/chart/composite/series-cues.ts +14 -0
- package/src/components/chart/composite/text-renderer.ts +9 -2
- package/src/components/chart/composite/tool-catalog.ts +36 -0
- package/src/components/chart/composite/tools.ts +203 -44
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
- package/src/components/command-bar/assist/inventory.ts +2 -1
- package/src/components/command-bar/commands/direct/index.ts +28 -13
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/panel/prompt-binding.ts +0 -8
- package/src/components/command-bar/workflow/alert.ts +1 -1
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/header.tsx +26 -40
- package/src/components/layout/pane/footer/index.tsx +1 -0
- package/src/components/layout/pane/footer/model.ts +27 -0
- package/src/components/onboarding/onboarding-frame.tsx +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
- package/src/components/onboarding/wizard-broker-sync.ts +0 -1
- package/src/components/onboarding/wizard-positions.ts +3 -3
- package/src/components/ticker/list-table-view.tsx +4 -1
- package/src/components/ui/choice-dialog.tsx +23 -18
- package/src/components/ui/choice-groups.ts +18 -0
- package/src/components/ui/data-table/opentui/index.tsx +9 -7
- package/src/components/ui/data-table/opentui/model.ts +26 -1
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/display.tsx +5 -1
- package/src/components/ui/list-view.tsx +8 -4
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +1 -0
- package/src/components/ui/select-button.tsx +5 -1
- package/src/components/ui/select-field.tsx +1 -0
- package/src/components/ui/table-layout.ts +8 -5
- package/src/core/app-runtime.ts +2 -0
- package/src/core/app-services.ts +2 -2
- package/src/core/state/app/layout.ts +2 -0
- package/src/core/state/app/state.ts +9 -0
- package/src/core/state/app/types.ts +3 -0
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/saved-copies.ts +55 -16
- package/src/data/ticker-repository.ts +16 -9
- package/src/data/ticker-writes.ts +81 -0
- package/src/data/ttl-cache.ts +165 -0
- package/src/docs/snapshot.json +427 -216
- package/src/i18n/es.ts +19 -3
- package/src/i18n/ja.ts +19 -3
- package/src/i18n/ko.ts +19 -3
- package/src/i18n/zh-cn.ts +19 -3
- package/src/i18n/zh-tw.ts +19 -3
- package/src/market-data/coordinator/financials.ts +10 -2
- package/src/market-data/coordinator/index.ts +3 -1
- package/src/market-data/coordinator/quotes.ts +4 -2
- package/src/market-data/depositary-pair.ts +2 -2
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +48 -7
- package/src/market-data/market/dated-previous-close.ts +203 -0
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/market/futures-week.ts +68 -0
- package/src/market-data/market/status.ts +16 -4
- package/src/market-data/options-alternatives.ts +141 -4
- package/src/market-data/quotes/contributions.ts +20 -2
- package/src/market-data/quotes/resolution.ts +3 -0
- package/src/market-data/selectors.ts +3 -0
- package/src/news/aggregator.ts +113 -9
- package/src/news/cloud-session.ts +21 -0
- package/src/news/hooks.ts +1 -0
- package/src/news/news-model.ts +1 -0
- package/src/news/types.ts +11 -0
- package/src/notifications/notification-log.ts +6 -4
- package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +380 -82
- package/src/plugins/builtin/alerts/command.ts +31 -0
- package/src/plugins/builtin/alerts/constants.ts +1 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- package/src/plugins/builtin/alerts/fired-log.ts +113 -8
- package/src/plugins/builtin/alerts/format.ts +80 -4
- package/src/plugins/builtin/alerts/headless.ts +30 -7
- package/src/plugins/builtin/alerts/history-rows.ts +7 -3
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +135 -22
- package/src/plugins/builtin/alerts/live.ts +3 -3
- package/src/plugins/builtin/alerts/metric-command.ts +134 -0
- package/src/plugins/builtin/alerts/metric-data.ts +111 -0
- package/src/plugins/builtin/alerts/metric-model.ts +324 -0
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
- package/src/plugins/builtin/alerts/pane.tsx +90 -39
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +37 -10
- package/src/plugins/builtin/alerts/types.ts +7 -0
- package/src/plugins/builtin/analytics/index.tsx +2 -1
- package/src/plugins/builtin/analytics/risk-client.ts +16 -5
- package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
- package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
- package/src/plugins/builtin/analytics/risk-model.ts +55 -9
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/attention/index.tsx +2 -1
- package/src/plugins/builtin/attention/listing.ts +14 -0
- package/src/plugins/builtin/backtest/headless.ts +10 -5
- package/src/plugins/builtin/backtest/pane.tsx +9 -4
- package/src/plugins/builtin/backtest/presets.ts +29 -3
- package/src/plugins/builtin/broker-manager/index.tsx +10 -5
- package/src/plugins/builtin/broker-manager/model.ts +6 -1
- package/src/plugins/builtin/broker-trading/controller.ts +22 -12
- package/src/plugins/builtin/broker-trading/index.tsx +13 -1
- package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
- package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +12 -4
- package/src/plugins/builtin/buildout/format.ts +38 -0
- package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
- package/src/plugins/builtin/buildout/table-model.ts +23 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +7 -3
- package/src/plugins/builtin/catalysts/model.ts +2 -1
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
- package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
- package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
- package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
- package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
- package/src/plugins/builtin/chart-composer/formula.ts +71 -18
- package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
- package/src/plugins/builtin/chart-composer/headless.ts +19 -3
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
- package/src/plugins/builtin/chart-composer/presets.ts +50 -15
- package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
- package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
- package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
- package/src/plugins/builtin/chat/content/composer.tsx +13 -6
- package/src/plugins/builtin/chat/content/footer.tsx +6 -3
- package/src/plugins/builtin/chat/content/index.tsx +50 -21
- package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
- package/src/plugins/builtin/chat/index.tsx +23 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
- package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
- package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
- package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
- package/src/plugins/builtin/clinical-trials/model.ts +0 -9
- package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +1 -1
- package/src/plugins/builtin/company-attention/building.tsx +5 -1
- package/src/plugins/builtin/company-attention/client.ts +8 -9
- package/src/plugins/builtin/company-attention/evidence.ts +4 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +9 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/correlation/headless.ts +13 -5
- package/src/plugins/builtin/correlation/history.ts +22 -0
- package/src/plugins/builtin/cot/client.ts +22 -18
- package/src/plugins/builtin/cot/headless.ts +15 -10
- package/src/plugins/builtin/cot/model.ts +14 -1
- package/src/plugins/builtin/cot/pane.tsx +22 -14
- package/src/plugins/builtin/credit-boards/client.ts +6 -10
- package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
- package/src/plugins/builtin/credit-conditions/model.ts +4 -0
- package/src/plugins/builtin/credit-conditions/series.ts +3 -1
- package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
- package/src/plugins/builtin/credit-documents/export.ts +173 -0
- package/src/plugins/builtin/credit-documents/headless.ts +7 -7
- package/src/plugins/builtin/credit-documents/model.ts +104 -14
- package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
- package/src/plugins/builtin/custom-view/sources.ts +2 -0
- package/src/plugins/builtin/dividend-yield/client.ts +131 -23
- package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
- package/src/plugins/builtin/dividend-yield/model.ts +8 -5
- package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
- package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
- package/src/plugins/builtin/dividend-yield/types.ts +20 -2
- package/src/plugins/builtin/dividend-yield/view.ts +30 -2
- package/src/plugins/builtin/docs/targets.ts +5 -1
- package/src/plugins/builtin/earnings/board-model.ts +16 -15
- package/src/plugins/builtin/earnings/board.tsx +62 -71
- package/src/plugins/builtin/earnings/headless.ts +40 -4
- package/src/plugins/builtin/earnings/scope.ts +31 -43
- package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
- package/src/plugins/builtin/econ/calendar-model.ts +14 -9
- package/src/plugins/builtin/econ/figures.ts +72 -0
- package/src/plugins/builtin/econ/index.tsx +33 -3
- package/src/plugins/builtin/econ/types.ts +4 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
- package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
- package/src/plugins/builtin/econ-statistics/view.ts +3 -0
- package/src/plugins/builtin/equity-screener/categories.ts +179 -0
- package/src/plugins/builtin/equity-screener/client.ts +4 -2
- package/src/plugins/builtin/equity-screener/headless.ts +144 -13
- package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
- package/src/plugins/builtin/equity-screener/model.ts +99 -10
- package/src/plugins/builtin/equity-screener/pane.tsx +70 -15
- package/src/plugins/builtin/exposure/charts.tsx +2 -2
- package/src/plugins/builtin/exposure/model.ts +13 -1
- package/src/plugins/builtin/exposure/pane.tsx +4 -4
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/futures/model.ts +2 -1
- package/src/plugins/builtin/futures/table.tsx +2 -2
- package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
- package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
- package/src/plugins/builtin/fx-matrix/client.ts +11 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
- package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
- package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -3
- package/src/plugins/builtin/gpu/headless.ts +8 -2
- package/src/plugins/builtin/help/function-index.ts +4 -1
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +18 -1
- package/src/plugins/builtin/iv-history/client.ts +52 -11
- package/src/plugins/builtin/iv-history/format.ts +36 -23
- package/src/plugins/builtin/iv-history/headless.ts +36 -18
- package/src/plugins/builtin/iv-history/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/model.ts +163 -27
- package/src/plugins/builtin/iv-history/pane.tsx +10 -6
- package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
- package/src/plugins/builtin/iv-history/universe.ts +5 -1
- package/src/plugins/builtin/jobs/client.ts +25 -29
- package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
- package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
- package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
- package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
- package/src/plugins/builtin/macro-day/pane.tsx +7 -5
- package/src/plugins/builtin/macro-day/release-table.ts +2 -2
- package/src/plugins/builtin/market-heatmap/data.ts +8 -33
- package/src/plugins/builtin/market-movers/client.ts +25 -7
- package/src/plugins/builtin/market-movers/footer.ts +2 -2
- package/src/plugins/builtin/market-movers/headless.ts +32 -12
- package/src/plugins/builtin/market-movers/index.tsx +26 -14
- package/src/plugins/builtin/market-movers/min-float.ts +74 -0
- package/src/plugins/builtin/market-movers/model.ts +4 -1
- package/src/plugins/builtin/market-movers/screener.ts +14 -0
- package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
- package/src/plugins/builtin/market-movers/session-table.ts +4 -3
- package/src/plugins/builtin/market-movers/table.ts +17 -5
- package/src/plugins/builtin/members/breadth-pane.tsx +82 -0
- package/src/plugins/builtin/members/client.ts +19 -4
- package/src/plugins/builtin/members/headless.ts +20 -7
- package/src/plugins/builtin/members/index.tsx +9 -9
- package/src/plugins/builtin/members/model.ts +23 -4
- package/src/plugins/builtin/members/pane.tsx +25 -15
- package/src/plugins/builtin/news/index.tsx +1 -0
- package/src/plugins/builtin/news/wire/categories.ts +19 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
- package/src/plugins/builtin/openfda/client.ts +14 -2
- package/src/plugins/builtin/openfda/index.tsx +3 -2
- package/src/plugins/builtin/openfda/model.ts +9 -0
- package/src/plugins/builtin/openfda/pane.tsx +42 -6
- package/src/plugins/builtin/options/analytics.ts +54 -6
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/model-spot.ts +5 -2
- package/src/plugins/builtin/options/strike-window.ts +2 -1
- package/src/plugins/builtin/options/table.ts +6 -9
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +42 -12
- package/src/plugins/builtin/options-positioning/client.ts +30 -0
- package/src/plugins/builtin/options-positioning/headless.ts +74 -21
- package/src/plugins/builtin/options-positioning/index.tsx +4 -4
- package/src/plugins/builtin/options-positioning/model.ts +122 -14
- package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
- package/src/plugins/builtin/options-scenario/client.ts +116 -47
- package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
- package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
- package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
- package/src/plugins/builtin/options-scenario/csp.ts +8 -2
- package/src/plugins/builtin/options-scenario/index.tsx +9 -7
- package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
- package/src/plugins/builtin/pe-band/model.ts +12 -49
- package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
- package/src/plugins/builtin/perps/cascade.ts +236 -0
- package/src/plugins/builtin/perps/client.ts +56 -2
- package/src/plugins/builtin/perps/freshness.ts +12 -10
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/live-rows.ts +10 -3
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +32 -31
- package/src/plugins/builtin/perps/status-line.ts +25 -0
- package/src/plugins/builtin/plugin-module.ts +5 -1
- package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +12 -7
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/rate-path/pane.tsx +2 -2
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/client.ts +18 -4
- package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
- package/src/plugins/builtin/relative-rotation/model.ts +16 -40
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
- package/src/plugins/builtin/research/event-model.ts +10 -8
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
- package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
- package/src/plugins/builtin/scanner/feed.ts +2 -2
- package/src/plugins/builtin/scanner/flow-model.ts +50 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
- package/src/plugins/builtin/scanner/flow-session.ts +146 -0
- package/src/plugins/builtin/scanner/headless.ts +204 -35
- package/src/plugins/builtin/scanner/index.ts +40 -6
- package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
- package/src/plugins/builtin/scanner/live-feed.ts +168 -0
- package/src/plugins/builtin/scanner/live-headless.ts +138 -0
- package/src/plugins/builtin/scanner/live-model.ts +362 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
- package/src/plugins/builtin/scanner/live-track.ts +186 -0
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/headless.ts +10 -1
- package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
- package/src/plugins/builtin/sec/fund-facts.ts +116 -34
- package/src/plugins/builtin/sec/headless.ts +2 -1
- package/src/plugins/builtin/series-inventory.ts +3 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
- package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
- package/src/plugins/builtin/shared/earnings-report.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/macro-releases.ts +65 -0
- package/src/plugins/builtin/shared/members-menu.tsx +3 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
- package/src/plugins/builtin/shared/report-freshness.ts +3 -2
- package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
- package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
- package/src/plugins/builtin/shared/ticker-company.ts +77 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +10 -3
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/headless.ts +6 -4
- package/src/plugins/builtin/themes/model.ts +17 -8
- package/src/plugins/builtin/themes/pane.tsx +7 -7
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
- package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
- package/src/plugins/builtin/time-sales/pane.tsx +31 -19
- package/src/plugins/builtin/treasury-daily/client.ts +41 -5
- package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
- package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
- package/src/plugins/builtin/treasury-daily/model.ts +50 -3
- package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
- package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
- package/src/plugins/builtin/vol-surface/client.ts +9 -17
- package/src/plugins/builtin/vol-surface/headless.ts +63 -14
- package/src/plugins/builtin/vol-surface/index.tsx +2 -2
- package/src/plugins/builtin/vol-surface/model.ts +22 -7
- package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
- package/src/plugins/builtin/vol-surface/tables.ts +31 -3
- package/src/plugins/builtin/volatility/dvol.ts +46 -0
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +4 -4
- package/src/plugins/builtin/volatility/series.ts +26 -0
- package/src/plugins/builtin/world-indices/footer.ts +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-browser.ts +13 -8
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +5 -5
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
- package/src/remote/resources.ts +2 -1
- package/src/renderers/browser/app-services.ts +1 -7
- package/src/renderers/browser/config-host.ts +2 -55
- package/src/renderers/browser/main.tsx +7 -57
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/list-view.tsx +13 -10
- package/src/renderers/dom/desktop/query-bar.tsx +4 -2
- package/src/renderers/dom/desktop/select-field.tsx +4 -2
- package/src/renderers/dom/host/chart-surface.tsx +21 -0
- package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
- package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
- package/src/renderers/electrobun/bun/index.ts +25 -1
- package/src/renderers/electrobun/shared/app-name.ts +30 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +3 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/renderers/opentui/toast-host.tsx +71 -43
- package/src/series/registry.ts +1 -0
- package/src/series/types.ts +6 -0
- package/src/series/validate.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +1 -0
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
- package/src/sources/provider-router/batches.ts +10 -2
- package/src/sources/provider-router/financial-routes.ts +3 -3
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/history.ts +28 -7
- package/src/sources/provider-router/index.ts +149 -10
- package/src/sources/provider-router/search.ts +13 -2
- package/src/state/app/context/index.tsx +3 -0
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/auth-transition.ts +19 -9
- package/src/sync/baseline.ts +10 -4
- package/src/sync/controller.ts +70 -19
- package/src/sync/core-contributors.ts +90 -31
- package/src/sync/react.ts +4 -1
- package/src/sync/types.ts +3 -0
- package/src/telemetry/stall-reports-dom.ts +38 -0
- package/src/telemetry/stall-reports-node.ts +75 -0
- package/src/telemetry/stall-reports.ts +214 -0
- package/src/telemetry/usage-counts.ts +0 -18
- package/src/test-support/preload.ts +14 -0
- package/src/theme/colors.ts +8 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name.ts +103 -3
- package/src/tickers/listing-venues.ts +38 -3
- package/src/tickers/search/bare-symbol.ts +36 -0
- package/src/tickers/search/index.ts +36 -7
- package/src/tickers/search/ranking.ts +10 -1
- package/src/tickers/search/types.ts +5 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/base-series.ts +13 -5
- package/src/time-series/daily-valuation.ts +62 -0
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +2 -4
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/forward-valuation.ts +32 -0
- package/src/time-series/fundamentals.ts +198 -7
- package/src/time-series/hooks.ts +1 -0
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/price-units.ts +72 -2
- package/src/time-series/resolve-context.ts +7 -1
- package/src/time-series/resolve.ts +116 -15
- package/src/time-series/series-presentation.ts +40 -6
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +5 -3
- package/src/time-series/studies.ts +278 -74
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +3 -3
- package/src/time-series/types.ts +27 -1
- package/src/time-series/valuation-releases.ts +39 -0
- package/src/types/data-provider.ts +9 -1
- package/src/types/desktop-window.ts +2 -0
- package/src/types/financials.ts +73 -2
- package/src/types/headless.ts +61 -5
- package/src/types/instrument.ts +3 -0
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +10 -1
- package/src/ui/host.tsx +3 -0
- package/src/updater/command-names.ts +12 -7
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +44 -0
- package/src/utils/financial-statements.ts +6 -1
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +24 -3
- package/src/utils/fx-market-hours.ts +26 -3
- package/src/utils/main-thread-stall.ts +35 -2
- package/src/utils/market-capitalization.ts +24 -1
- package/src/utils/network-failure.ts +9 -6
- package/src/utils/option-expiry.ts +48 -1
- package/src/utils/options.ts +10 -0
- package/src/utils/plugin-proxy-hosts.json +1 -0
- package/src/utils/price-history.ts +12 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
- package/src/renderers/browser/first-screen.ts +0 -193
package/src/news/aggregator.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { logFailure } from "../utils/log-failure";
|
|
2
2
|
import type { NewsCapability } from "../capabilities";
|
|
3
3
|
import type { ConnectionHealthRegistry } from "../core/connection-health";
|
|
4
|
-
import type { NewsArticle, NewsQuery, NewsQueryState } from "./types";
|
|
4
|
+
import type { NewsAccess, NewsArticle, NewsQuery, NewsQueryState } from "./types";
|
|
5
5
|
import { isAppVisible, subscribeAppVisibility } from "../state/app/activity";
|
|
6
6
|
import {
|
|
7
7
|
DEFAULT_GLOBAL_QUERY,
|
|
@@ -28,6 +28,18 @@ export interface NewsServiceOptions {
|
|
|
28
28
|
connectionHealth?: ConnectionHealthRegistry;
|
|
29
29
|
/** Defaults to the app's visibility; injectable for tests. */
|
|
30
30
|
visibility?: { isVisible(): boolean; subscribe(listener: () => void): () => void };
|
|
31
|
+
/**
|
|
32
|
+
* The session the sources read news with. Its key changes when what the
|
|
33
|
+
* service answers changes: a session restored at launch, a sign-in or
|
|
34
|
+
* sign-out, an upgrade. Every open query is then read again and its stories
|
|
35
|
+
* replaced, so a feed read without the session is not left on screen.
|
|
36
|
+
*/
|
|
37
|
+
session?: NewsSessionSignal;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface NewsSessionSignal {
|
|
41
|
+
key(): string;
|
|
42
|
+
subscribe(listener: () => void): () => void;
|
|
31
43
|
}
|
|
32
44
|
|
|
33
45
|
export type NewsQueryListener = (state: NewsQueryState) => void;
|
|
@@ -50,17 +62,30 @@ interface SourceFetchResult {
|
|
|
50
62
|
failedSourceIds: string[];
|
|
51
63
|
errors: string[];
|
|
52
64
|
nextCursor: string | null;
|
|
65
|
+
access: NewsAccess | null;
|
|
53
66
|
}
|
|
54
67
|
|
|
55
68
|
interface NewsQueryEntry {
|
|
56
69
|
query: NewsQuery;
|
|
57
70
|
state: NewsQueryState;
|
|
58
71
|
inFlight: Promise<NewsQueryState> | null;
|
|
72
|
+
/** The session generation the read in flight went out under. */
|
|
73
|
+
inFlightGeneration: number;
|
|
59
74
|
loadMoreInFlight: Promise<void> | null;
|
|
75
|
+
loadMoreGeneration: number;
|
|
76
|
+
/** The session generation the stories on screen were read under. */
|
|
77
|
+
generation: number;
|
|
60
78
|
refs: number;
|
|
61
79
|
lastAccessedAt: number;
|
|
62
80
|
}
|
|
63
81
|
|
|
82
|
+
/** Held back beats live: one source the service held back is enough to say the list is not the live wire. */
|
|
83
|
+
function combineNewsAccess(values: ReadonlyArray<NewsAccess | null | undefined>): NewsAccess | null {
|
|
84
|
+
if (values.includes("delayed")) return "delayed";
|
|
85
|
+
if (values.includes("realtime")) return "realtime";
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
64
89
|
function newsCapabilityPriority(source: NewsCapability): number {
|
|
65
90
|
return source.priority ?? 1000;
|
|
66
91
|
}
|
|
@@ -85,6 +110,12 @@ export class NewsService {
|
|
|
85
110
|
private readonly visibility: NonNullable<NewsServiceOptions["visibility"]>;
|
|
86
111
|
private lastPollAt = 0;
|
|
87
112
|
private unsubscribeVisibility: (() => void) | null = null;
|
|
113
|
+
private readonly session?: NewsSessionSignal;
|
|
114
|
+
private sessionKey: string | null = null;
|
|
115
|
+
/** Bumped when the session changes; a read from an older generation is dropped. */
|
|
116
|
+
private sessionGeneration = 0;
|
|
117
|
+
private sessionRefetchQueued = false;
|
|
118
|
+
private unsubscribeSession: (() => void) | null = null;
|
|
88
119
|
|
|
89
120
|
constructor(options: NewsServiceOptions = {}) {
|
|
90
121
|
const pollInterval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
@@ -94,6 +125,7 @@ export class NewsService {
|
|
|
94
125
|
this.now = options.now ?? Date.now;
|
|
95
126
|
this.connectionHealth = options.connectionHealth;
|
|
96
127
|
this.visibility = options.visibility ?? { isVisible: isAppVisible, subscribe: subscribeAppVisibility };
|
|
128
|
+
this.session = options.session;
|
|
97
129
|
}
|
|
98
130
|
|
|
99
131
|
register(source: NewsCapability): () => void {
|
|
@@ -125,6 +157,10 @@ export class NewsService {
|
|
|
125
157
|
this.clearPollTimer();
|
|
126
158
|
this.scheduleNextPoll();
|
|
127
159
|
});
|
|
160
|
+
if (this.session) {
|
|
161
|
+
this.sessionKey = this.session.key();
|
|
162
|
+
this.unsubscribeSession = this.session.subscribe(() => this.handleSessionChange());
|
|
163
|
+
}
|
|
128
164
|
this.scheduleNextPoll();
|
|
129
165
|
}
|
|
130
166
|
|
|
@@ -132,9 +168,46 @@ export class NewsService {
|
|
|
132
168
|
this.polling = false;
|
|
133
169
|
this.unsubscribeVisibility?.();
|
|
134
170
|
this.unsubscribeVisibility = null;
|
|
171
|
+
this.unsubscribeSession?.();
|
|
172
|
+
this.unsubscribeSession = null;
|
|
135
173
|
this.clearPollTimer();
|
|
136
174
|
}
|
|
137
175
|
|
|
176
|
+
/**
|
|
177
|
+
* A new session answers with other news: the live wire once a Pro session
|
|
178
|
+
* is in place, the held-back one after signing out. Reads still out under
|
|
179
|
+
* the old session are dropped, open queries are read again and replace
|
|
180
|
+
* their stories rather than merge into them, and closed ones are forgotten.
|
|
181
|
+
* A token and its user arrive as separate changes, so the reads wait for
|
|
182
|
+
* the end of the tick and go out once.
|
|
183
|
+
*/
|
|
184
|
+
private handleSessionChange(): void {
|
|
185
|
+
if (!this.session) return;
|
|
186
|
+
const key = this.session.key();
|
|
187
|
+
if (key === this.sessionKey) return;
|
|
188
|
+
this.sessionKey = key;
|
|
189
|
+
this.sessionGeneration++;
|
|
190
|
+
if (this.sessionRefetchQueued) return;
|
|
191
|
+
this.sessionRefetchQueued = true;
|
|
192
|
+
queueMicrotask(() => {
|
|
193
|
+
this.sessionRefetchQueued = false;
|
|
194
|
+
if (!this.polling) return;
|
|
195
|
+
let dropped = false;
|
|
196
|
+
for (const [queryKey, entry] of this.queries) {
|
|
197
|
+
if (entry.refs > 0) continue;
|
|
198
|
+
this.queries.delete(queryKey);
|
|
199
|
+
dropped = true;
|
|
200
|
+
}
|
|
201
|
+
if (dropped) {
|
|
202
|
+
this.rebuildArticlePool();
|
|
203
|
+
this.notify();
|
|
204
|
+
}
|
|
205
|
+
for (const entry of this.queries.values()) {
|
|
206
|
+
void this.refreshQuery(entry.query, true);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
138
211
|
private pollInFlight = false;
|
|
139
212
|
|
|
140
213
|
private clearPollTimer(): void {
|
|
@@ -226,6 +299,7 @@ export class NewsService {
|
|
|
226
299
|
return;
|
|
227
300
|
}
|
|
228
301
|
|
|
302
|
+
const generation = this.sessionGeneration;
|
|
229
303
|
const request = (async () => {
|
|
230
304
|
entry.state = { ...entry.state, loadingMore: true };
|
|
231
305
|
this.notify();
|
|
@@ -234,6 +308,12 @@ export class NewsService {
|
|
|
234
308
|
...normalized,
|
|
235
309
|
cursor: entry.state.nextCursor ?? undefined,
|
|
236
310
|
});
|
|
311
|
+
// An older page from another session must not join the list the new session read.
|
|
312
|
+
if (generation !== this.sessionGeneration || entry.generation !== generation) {
|
|
313
|
+
entry.state = { ...entry.state, loadingMore: false };
|
|
314
|
+
this.notify();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
237
317
|
entry.state = {
|
|
238
318
|
...entry.state,
|
|
239
319
|
loadingMore: false,
|
|
@@ -253,6 +333,7 @@ export class NewsService {
|
|
|
253
333
|
}
|
|
254
334
|
})();
|
|
255
335
|
entry.loadMoreInFlight = request;
|
|
336
|
+
entry.loadMoreGeneration = generation;
|
|
256
337
|
await request;
|
|
257
338
|
}
|
|
258
339
|
|
|
@@ -318,7 +399,9 @@ export class NewsService {
|
|
|
318
399
|
showLoading: boolean,
|
|
319
400
|
): Promise<NewsQueryState> {
|
|
320
401
|
const entry = this.getOrCreateQueryEntry(query);
|
|
321
|
-
|
|
402
|
+
const generation = this.sessionGeneration;
|
|
403
|
+
// A read that went out under an older session answers for that session: a new one goes out beside it.
|
|
404
|
+
if (entry.inFlight && entry.inFlightGeneration === generation) return entry.inFlight;
|
|
322
405
|
|
|
323
406
|
const current = entry.state;
|
|
324
407
|
if (showLoading) {
|
|
@@ -333,14 +416,17 @@ export class NewsService {
|
|
|
333
416
|
const promise = (async () => {
|
|
334
417
|
try {
|
|
335
418
|
const result = await this.fetchFromSources(query);
|
|
419
|
+
if (generation !== this.sessionGeneration) return this.currentRead(entry);
|
|
336
420
|
if (result.sourceIds.length === 0 && result.failedSourceIds.length > 0) {
|
|
337
421
|
throw new Error("News sources unavailable.");
|
|
338
422
|
}
|
|
339
|
-
if (entry.loadMoreInFlight) return entry.state;
|
|
423
|
+
if (entry.loadMoreInFlight && entry.loadMoreGeneration === generation) return entry.state;
|
|
340
424
|
const incoming = filterNewsArticlesForQuery(dedupeNewsArticles(result.articles), query);
|
|
425
|
+
// Stories read under another session are another feed (held back, or live): the new read replaces them.
|
|
426
|
+
const sameSession = entry.generation === generation;
|
|
341
427
|
// The stories a coin's fallback found are not the pair's own: once stories are filed under it, they replace them.
|
|
342
|
-
const existing = incoming.length > 0 && entry.state.articles.length > 0
|
|
343
|
-
&& (incoming[0]!.searchedFor ?? null) !== (entry.state.articles[0]!.searchedFor ?? null)
|
|
428
|
+
const existing = !sameSession || (incoming.length > 0 && entry.state.articles.length > 0
|
|
429
|
+
&& (incoming[0]!.searchedFor ?? null) !== (entry.state.articles[0]!.searchedFor ?? null))
|
|
344
430
|
? []
|
|
345
431
|
: entry.state.articles;
|
|
346
432
|
const incomingIds = new Set(incoming.map((article) => article.id));
|
|
@@ -363,13 +449,17 @@ export class NewsService {
|
|
|
363
449
|
sourceIds: result.sourceIds,
|
|
364
450
|
nextCursor: hasOlderPages ? entry.state.nextCursor : result.nextCursor,
|
|
365
451
|
loadingMore: entry.state.loadingMore,
|
|
452
|
+
// A page that does not say keeps what this session's earlier answer said.
|
|
453
|
+
access: result.access ?? (sameSession ? entry.state.access ?? null : null),
|
|
366
454
|
};
|
|
367
455
|
entry.state = state;
|
|
456
|
+
entry.generation = generation;
|
|
368
457
|
entry.lastAccessedAt = this.now();
|
|
369
458
|
this.rebuildArticlePool();
|
|
370
459
|
this.notify();
|
|
371
460
|
return state;
|
|
372
461
|
} catch (error) {
|
|
462
|
+
if (generation !== this.sessionGeneration) return this.currentRead(entry);
|
|
373
463
|
const state: NewsQueryState = {
|
|
374
464
|
...current,
|
|
375
465
|
phase: "error",
|
|
@@ -380,15 +470,21 @@ export class NewsService {
|
|
|
380
470
|
this.notify();
|
|
381
471
|
return state;
|
|
382
472
|
} finally {
|
|
383
|
-
entry.inFlight = null;
|
|
473
|
+
if (entry.inFlightGeneration === generation) entry.inFlight = null;
|
|
384
474
|
this.pruneInactiveQueries();
|
|
385
475
|
}
|
|
386
476
|
})();
|
|
387
477
|
|
|
388
478
|
entry.inFlight = promise;
|
|
479
|
+
entry.inFlightGeneration = generation;
|
|
389
480
|
return promise;
|
|
390
481
|
}
|
|
391
482
|
|
|
483
|
+
/** What a dropped read answers its caller: the current session's read when one is out, else the state as it is. */
|
|
484
|
+
private currentRead(entry: NewsQueryEntry): Promise<NewsQueryState> | NewsQueryState {
|
|
485
|
+
return entry.inFlight && entry.inFlightGeneration === this.sessionGeneration ? entry.inFlight : entry.state;
|
|
486
|
+
}
|
|
487
|
+
|
|
392
488
|
private getOrCreateQueryEntry(query: NewsQuery): NewsQueryEntry {
|
|
393
489
|
const key = buildNewsQueryKey(query);
|
|
394
490
|
const now = this.now();
|
|
@@ -402,7 +498,10 @@ export class NewsService {
|
|
|
402
498
|
query,
|
|
403
499
|
state: createIdleNewsQueryState(),
|
|
404
500
|
inFlight: null,
|
|
501
|
+
inFlightGeneration: this.sessionGeneration,
|
|
405
502
|
loadMoreInFlight: null,
|
|
503
|
+
loadMoreGeneration: this.sessionGeneration,
|
|
504
|
+
generation: this.sessionGeneration,
|
|
406
505
|
refs: 0,
|
|
407
506
|
lastAccessedAt: now,
|
|
408
507
|
};
|
|
@@ -451,7 +550,7 @@ export class NewsService {
|
|
|
451
550
|
private async readSourcePage(
|
|
452
551
|
source: NewsCapability,
|
|
453
552
|
query: NewsQuery,
|
|
454
|
-
): Promise<{ articles: NewsArticle[]; nextCursor: string | null; error?: string | null }> {
|
|
553
|
+
): Promise<{ articles: NewsArticle[]; nextCursor: string | null; error?: string | null; access?: NewsAccess | null }> {
|
|
455
554
|
if (source.provider.fetchNewsPage) {
|
|
456
555
|
const page = await this.trackSourceRequest(
|
|
457
556
|
source,
|
|
@@ -462,6 +561,7 @@ export class NewsService {
|
|
|
462
561
|
articles: page.articles.map((article) => markDetailCapableArticle(source, article)),
|
|
463
562
|
nextCursor: page.nextCursor ?? null,
|
|
464
563
|
error: page.error,
|
|
564
|
+
access: page.access,
|
|
465
565
|
};
|
|
466
566
|
}
|
|
467
567
|
const articles = (await this.trackSourceRequest(
|
|
@@ -484,6 +584,7 @@ export class NewsService {
|
|
|
484
584
|
failedSourceIds,
|
|
485
585
|
errors: page.error ? [page.error] : [],
|
|
486
586
|
nextCursor: page.nextCursor,
|
|
587
|
+
access: page.access ?? null,
|
|
487
588
|
};
|
|
488
589
|
if (page.articles.length > 0) return result;
|
|
489
590
|
firstEmpty ??= result;
|
|
@@ -491,7 +592,7 @@ export class NewsService {
|
|
|
491
592
|
failedSourceIds.push(newsCapabilitySourceId(source));
|
|
492
593
|
}
|
|
493
594
|
}
|
|
494
|
-
return firstEmpty ?? { articles: [], sourceIds: [], failedSourceIds, errors: [], nextCursor: null };
|
|
595
|
+
return firstEmpty ?? { articles: [], sourceIds: [], failedSourceIds, errors: [], nextCursor: null, access: null };
|
|
495
596
|
}
|
|
496
597
|
|
|
497
598
|
private async fetchMergedNews(query: NewsQuery, sources: NewsCapability[]): Promise<SourceFetchResult> {
|
|
@@ -505,6 +606,7 @@ export class NewsService {
|
|
|
505
606
|
const errors: string[] = [];
|
|
506
607
|
const sourceIds: string[] = [];
|
|
507
608
|
const failedSourceIds: string[] = [];
|
|
609
|
+
const access: Array<NewsAccess | null | undefined> = [];
|
|
508
610
|
let nextCursor: string | null = null;
|
|
509
611
|
settled.forEach((result, index) => {
|
|
510
612
|
if (result.status !== "fulfilled") {
|
|
@@ -515,9 +617,10 @@ export class NewsService {
|
|
|
515
617
|
articles.push(...result.value.page.articles);
|
|
516
618
|
if (result.value.page.error) errors.push(result.value.page.error);
|
|
517
619
|
sourceIds.push(newsCapabilitySourceId(result.value.source));
|
|
620
|
+
access.push(result.value.page.access);
|
|
518
621
|
nextCursor ??= result.value.page.nextCursor;
|
|
519
622
|
});
|
|
520
|
-
return { articles, sourceIds, failedSourceIds, errors, nextCursor };
|
|
623
|
+
return { articles, sourceIds, failedSourceIds, errors, nextCursor, access: combineNewsAccess(access) };
|
|
521
624
|
}
|
|
522
625
|
|
|
523
626
|
private trackSourceRequest<T>(
|
|
@@ -550,6 +653,7 @@ export class NewsService {
|
|
|
550
653
|
sourceIds: [...new Set([...entry.state.sourceIds, newsCapabilitySourceId(source)])],
|
|
551
654
|
nextCursor: entry.state.nextCursor,
|
|
552
655
|
loadingMore: false,
|
|
656
|
+
access: entry.state.access ?? null,
|
|
553
657
|
};
|
|
554
658
|
changed = true;
|
|
555
659
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { apiClient } from "../api-client";
|
|
2
|
+
import { hasProAccess } from "../api-client/plan-rules";
|
|
3
|
+
import type { NewsSessionSignal } from "./aggregator";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* What decides which news Gloom Cloud answers with: whether requests carry a
|
|
7
|
+
* session, whose it is, and whether that account has real-time news. Changes
|
|
8
|
+
* when the session is restored at launch, on sign-in and sign-out, and when
|
|
9
|
+
* the plan changes.
|
|
10
|
+
*/
|
|
11
|
+
export const cloudNewsSession: NewsSessionSignal = {
|
|
12
|
+
key() {
|
|
13
|
+
const user = apiClient.getCurrentUser();
|
|
14
|
+
return [
|
|
15
|
+
apiClient.hasSessionCredential() ? "session" : "none",
|
|
16
|
+
user?.id ?? "",
|
|
17
|
+
hasProAccess(user) ? "realtime" : "delayed",
|
|
18
|
+
].join(":");
|
|
19
|
+
},
|
|
20
|
+
subscribe: (listener) => apiClient.subscribeCurrentUser(listener),
|
|
21
|
+
};
|
package/src/news/hooks.ts
CHANGED
package/src/news/news-model.ts
CHANGED
package/src/news/types.ts
CHANGED
|
@@ -81,11 +81,20 @@ export interface NewsQuery {
|
|
|
81
81
|
|
|
82
82
|
type NewsQueryPhase = "idle" | "loading" | "ready" | "refreshing" | "error";
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Which news a source answered with: the live wire, or the one held back for
|
|
86
|
+
* an account without real-time news. Read from the answer itself, never from
|
|
87
|
+
* the plan the app remembers.
|
|
88
|
+
*/
|
|
89
|
+
export type NewsAccess = "realtime" | "delayed";
|
|
90
|
+
|
|
84
91
|
export interface NewsPage {
|
|
85
92
|
/** Active source failure, including a partial failure with usable articles. */
|
|
86
93
|
error?: string | null;
|
|
87
94
|
articles: NewsArticle[];
|
|
88
95
|
nextCursor?: string | null;
|
|
96
|
+
/** Which news the source answered with; null or absent when the answer does not say. */
|
|
97
|
+
access?: NewsAccess | null;
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
export interface NewsQueryState {
|
|
@@ -96,4 +105,6 @@ export interface NewsQueryState {
|
|
|
96
105
|
sourceIds: string[];
|
|
97
106
|
nextCursor: string | null;
|
|
98
107
|
loadingMore: boolean;
|
|
108
|
+
/** Which news the stories are, from the last answer; null or absent when it did not say. */
|
|
109
|
+
access?: NewsAccess | null;
|
|
99
110
|
}
|
|
@@ -6,22 +6,24 @@ export const NOTIFICATION_LOG_LIMIT = 300;
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The notices worth finding again after their toast is gone: a price alert or
|
|
9
|
-
* a catalyst that fired, a high-impact economic print,
|
|
9
|
+
* a catalyst that fired, a high-impact economic print, a team card, and new
|
|
10
|
+
* matches of a live scan with its alerts on. Chat
|
|
10
11
|
* keeps its own unread state on the account, and plain info, success and
|
|
11
12
|
* error toasts are not logged.
|
|
12
13
|
*/
|
|
13
|
-
export type MissedNoticeKind = "price" | "catalyst" | "econ" | "team";
|
|
14
|
+
export type MissedNoticeKind = "price" | "catalyst" | "econ" | "team" | "scan";
|
|
14
15
|
|
|
15
16
|
const KIND_PREFIXES: ReadonlyArray<readonly [MissedNoticeKind, string]> = [
|
|
16
17
|
["price", "price:"],
|
|
17
18
|
["catalyst", "catalyst:"],
|
|
18
19
|
["econ", "econ:"],
|
|
19
20
|
["team", "team:"],
|
|
21
|
+
["scan", "scan:"],
|
|
20
22
|
];
|
|
21
23
|
|
|
22
24
|
export interface NotificationLogEntry {
|
|
23
25
|
id: string;
|
|
24
|
-
/** `price:<alertId>`, `catalyst:<eventId>`, `econ:<eventId>` or `
|
|
26
|
+
/** `price:<alertId>`, `catalyst:<eventId>`, `econ:<eventId>`, `team:<cardId>` or `scan:<paneId>`. A later notice with the same ref updates this row. */
|
|
25
27
|
refId: string;
|
|
26
28
|
title?: string;
|
|
27
29
|
body: string;
|
|
@@ -137,7 +139,7 @@ export function unreadNotificationLogCount(log: readonly NotificationLogEntry[]
|
|
|
137
139
|
|
|
138
140
|
/**
|
|
139
141
|
* Records a notice whose `refId` names a price alert, a catalyst, an economic
|
|
140
|
-
* release or a
|
|
142
|
+
* release, a team card or a live scan; anything else returns null and is not kept. The same ref replaces its
|
|
141
143
|
* row: a price alert that fires again is new and unread again.
|
|
142
144
|
*/
|
|
143
145
|
export function appendNotificationLog(
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { metricDescription, readAlertMetric, validMetricRule } from "./metric-model";
|
|
1
2
|
import type { AlertCondition, AlertRule } from "./types";
|
|
3
|
+
import { alertListing } from "./format";
|
|
4
|
+
import { canonicalExchange } from "../../../utils/exchanges";
|
|
2
5
|
import { logFailure } from "../../../utils/log-failure";
|
|
3
6
|
|
|
4
7
|
export type { AlertRule };
|
|
@@ -32,16 +35,21 @@ export function editAlert(
|
|
|
32
35
|
targetPrice: number,
|
|
33
36
|
): AlertRule {
|
|
34
37
|
const nextSymbol = symbol.trim().toUpperCase();
|
|
38
|
+
const sameListing = nextSymbol === alert.symbol.trim().toUpperCase();
|
|
35
39
|
return {
|
|
36
40
|
id: alert.id,
|
|
37
41
|
symbol: nextSymbol,
|
|
38
|
-
exchange:
|
|
42
|
+
exchange: sameListing ? alert.exchange : undefined,
|
|
43
|
+
// The listing's currency and venue stay with it; a new symbol learns its own at its first check.
|
|
44
|
+
...(sameListing && alert.lastQuoteCurrency ? { lastQuoteCurrency: alert.lastQuoteCurrency } : {}),
|
|
45
|
+
...(sameListing && alert.lastQuoteExchange ? { lastQuoteExchange: alert.lastQuoteExchange } : {}),
|
|
39
46
|
condition,
|
|
40
47
|
targetPrice,
|
|
41
48
|
createdAt: alert.createdAt,
|
|
42
49
|
armedAt: Date.now(),
|
|
43
50
|
status: "active",
|
|
44
51
|
message: alert.message,
|
|
52
|
+
...(alert.metric ? { metric: alert.metric } : {}),
|
|
45
53
|
};
|
|
46
54
|
}
|
|
47
55
|
|
|
@@ -49,8 +57,21 @@ export function rearmAlert(alert: AlertRule): AlertRule {
|
|
|
49
57
|
return editAlert(alert, alert.symbol, alert.condition, alert.targetPrice);
|
|
50
58
|
}
|
|
51
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Whether two level alerts are one rule: the same measure, condition and
|
|
62
|
+
* target on the same listing, whether it was named with its exchange or read
|
|
63
|
+
* from the symbol alone. A second add of one names the first and adds nothing.
|
|
64
|
+
*/
|
|
65
|
+
export function sameLevelRule(a: AlertRule, b: AlertRule): boolean {
|
|
66
|
+
const venue = (alert: AlertRule) => canonicalExchange(alert.exchange ?? "");
|
|
67
|
+
return a.symbol.toUpperCase() === b.symbol.toUpperCase()
|
|
68
|
+
&& (venue(a) === venue(b) || alertListing(a) === alertListing(b))
|
|
69
|
+
&& a.condition === b.condition && a.targetPrice === b.targetPrice
|
|
70
|
+
&& JSON.stringify(readAlertMetric(a.metric)) === JSON.stringify(readAlertMetric(b.metric));
|
|
71
|
+
}
|
|
72
|
+
|
|
52
73
|
export function evaluateAlert(alert: AlertRule, currentPrice: number): boolean {
|
|
53
|
-
if (alert.status !== "active") return false;
|
|
74
|
+
if (alert.metric || alert.status !== "active") return false;
|
|
54
75
|
|
|
55
76
|
switch (alert.condition) {
|
|
56
77
|
case "above":
|
|
@@ -69,6 +90,7 @@ export function evaluateAlert(alert: AlertRule, currentPrice: number): boolean {
|
|
|
69
90
|
}
|
|
70
91
|
|
|
71
92
|
export function formatAlertDescription(alert: AlertRule): string {
|
|
93
|
+
if (alert.metric) return metricDescription(alert);
|
|
72
94
|
const prefix = alert.condition === "above" ? ">"
|
|
73
95
|
: alert.condition === "below" ? "<" : "↕";
|
|
74
96
|
return `${alert.symbol} ${prefix} ${alert.targetPrice}`;
|
|
@@ -94,6 +116,7 @@ function readAlerts(json: unknown): { alerts: AlertRule[]; error: string | null
|
|
|
94
116
|
a != null && typeof a.id === "string" && !!a.id && typeof a.symbol === "string" && !!a.symbol
|
|
95
117
|
&& typeof a.condition === "string" && !!a.condition && typeof a.targetPrice === "number"
|
|
96
118
|
&& (a.exchange == null || typeof a.exchange === "string")
|
|
119
|
+
&& (a.metric == null || (readAlertMetric(a.metric) != null && validMetricRule(a)))
|
|
97
120
|
));
|
|
98
121
|
return {
|
|
99
122
|
alerts,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { metricDescription } from "./metric-model";
|
|
2
|
+
import { eventAlertTarget, type EventAlertRule } from "./events";
|
|
3
|
+
import { alertChangeEntry, appendFiredAlert, readFiredAlerts, type AlertLogAction, type FiredAlert } from "./fired-log";
|
|
4
|
+
import { alertListing } from "./format";
|
|
5
|
+
import { isResearchAlertKind, readResearchRule } from "./research-rules";
|
|
6
|
+
import { readScheduledRule, scheduledEventName } from "./scheduled";
|
|
7
|
+
import type { AlertRule } from "./types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A rule as the alert log names it: its kind, the rule in words (`EWG:ARCA
|
|
11
|
+
* below 40`, as a fired price alert reads) and the symbol it follows.
|
|
12
|
+
*/
|
|
13
|
+
export function alertLogRule(rule: AlertRule | EventAlertRule) {
|
|
14
|
+
if ("symbol" in rule) {
|
|
15
|
+
return {
|
|
16
|
+
kind: rule.metric?.kind ?? "price", ruleId: rule.id,
|
|
17
|
+
rule: rule.metric ? metricDescription(rule) : `${alertListing(rule)} ${rule.condition} ${rule.targetPrice}`,
|
|
18
|
+
symbol: rule.symbol, ...rule.exchange ? { exchange: rule.exchange } : {},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const scheduled = readScheduledRule(rule);
|
|
22
|
+
const symbol = scheduled?.target.kind === "earnings" ? scheduled.target.symbol
|
|
23
|
+
: isResearchAlertKind(rule.kind) ? readResearchRule(rule.kind, rule.value)?.symbol : undefined;
|
|
24
|
+
return {
|
|
25
|
+
kind: rule.kind, ruleId: rule.id, rule: scheduled ? scheduledEventName(scheduled.target) : eventAlertTarget(rule),
|
|
26
|
+
...symbol ? { symbol } : {},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The saved log with one change more: what the pane's `usePluginState(FIRED_LOG_KEY)` setter takes. */
|
|
31
|
+
export function withAlertChange(saved: unknown, action: AlertLogAction, rule: AlertRule | EventAlertRule, at = Date.now()): FiredAlert[] {
|
|
32
|
+
return appendFiredAlert(readFiredAlerts(saved), alertChangeEntry(action, alertLogRule(rule), at));
|
|
33
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// two cases of the same alert, whose dates come from their own calendars.
|
|
6
6
|
import { dateKey, eventCountry, matchesCountry, type CountryFilter } from "../econ/calendar-model";
|
|
7
7
|
import { isAllDayEvent, hasNoReleaseTime, type EconEvent, type EconImpact } from "../econ/types";
|
|
8
|
+
import { RENAMED_RELEASES } from "../shared/macro-releases";
|
|
8
9
|
import { MACRO_EVENT_NAMES } from "./research-builder";
|
|
9
10
|
import type { MacroAlertEvent } from "./research-rules";
|
|
10
11
|
|
|
@@ -52,6 +53,39 @@ export function calendarNameWords(name: string): string[] {
|
|
|
52
53
|
|
|
53
54
|
const nameKey = (name: string) => calendarNameWords(name).join(" ");
|
|
54
55
|
|
|
56
|
+
/** A release series' former name a typed name used, read as its current one. */
|
|
57
|
+
export interface ReleaseRename {
|
|
58
|
+
former: string;
|
|
59
|
+
current: string;
|
|
60
|
+
since: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A name's words with a renamed series' former name put as its current one
|
|
65
|
+
* (`caixin pmi` as `ratingdog pmi`), and the rename; null for a name that
|
|
66
|
+
* carries none.
|
|
67
|
+
*/
|
|
68
|
+
function withCurrentName(words: readonly string[]): { words: string[]; rename: ReleaseRename } | null {
|
|
69
|
+
for (const { former, current, since } of RENAMED_RELEASES) {
|
|
70
|
+
for (const name of former) {
|
|
71
|
+
const old = calendarNameWords(name);
|
|
72
|
+
for (let at = 0; at + old.length <= words.length; at += 1) {
|
|
73
|
+
if (!old.every((word, index) => words[at + index] === word)) continue;
|
|
74
|
+
return {
|
|
75
|
+
words: [...words.slice(0, at), ...calendarNameWords(current), ...words.slice(at + old.length)],
|
|
76
|
+
rename: { former: name, current, since },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** "Caixin releases were renamed RatingDog from the August 2025 survey." */
|
|
85
|
+
export function releaseRenameNote({ former, current, since }: ReleaseRename): string {
|
|
86
|
+
return `${former} releases were renamed ${current} from ${since}.`;
|
|
87
|
+
}
|
|
88
|
+
|
|
55
89
|
/** The UTC day the calendar lists a row under. */
|
|
56
90
|
const utcDay = (time: number) => new Date(time).toISOString().slice(0, 10);
|
|
57
91
|
|
|
@@ -121,13 +155,17 @@ function calendarChoices(events: readonly EconEvent[], now: number): RankedChoic
|
|
|
121
155
|
}
|
|
122
156
|
|
|
123
157
|
/** What a typed release names in the calendar. */
|
|
124
|
-
export type CalendarEventMatch =
|
|
158
|
+
export type CalendarEventMatch = (
|
|
125
159
|
/** One release, the best of those the words name, and the others they name in the same country. */
|
|
126
160
|
| { kind: "match"; choice: CalendarEventChoice; others: CalendarEventChoice[] }
|
|
127
161
|
/** The words name releases in several countries: the best of each. */
|
|
128
162
|
| { kind: "countries"; choices: CalendarEventChoice[] }
|
|
129
163
|
/** Nothing the words name; the nearest names, best first. */
|
|
130
|
-
| { kind: "none"; suggestions: CalendarEventChoice[] }
|
|
164
|
+
| { kind: "none"; suggestions: CalendarEventChoice[] }
|
|
165
|
+
) & {
|
|
166
|
+
/** The former name the words used, read as the series' current one (`Caixin PMI` as RatingDog's). */
|
|
167
|
+
renamed?: ReleaseRename;
|
|
168
|
+
};
|
|
131
169
|
|
|
132
170
|
/** Character pairs of a name, for how near a slip is to it. */
|
|
133
171
|
function pairs(text: string): Map<string, number> {
|
|
@@ -186,7 +224,8 @@ function oneSlipApart(left: string, right: string): boolean {
|
|
|
186
224
|
* then the closest name and the soonest date, so `NBS PMI` is the headline
|
|
187
225
|
* NBS Manufacturing PMI and an old name listed only in the past never beats
|
|
188
226
|
* the live one. Several countries ask which; no match offers the nearest
|
|
189
|
-
* names.
|
|
227
|
+
* names. A series' former name reads as its current one (`Caixin PMI` is
|
|
228
|
+
* RatingDog's), and the match says so.
|
|
190
229
|
*/
|
|
191
230
|
export function matchCalendarEvent(
|
|
192
231
|
query: string,
|
|
@@ -194,7 +233,18 @@ export function matchCalendarEvent(
|
|
|
194
233
|
events: readonly EconEvent[],
|
|
195
234
|
now: number,
|
|
196
235
|
): CalendarEventMatch {
|
|
197
|
-
const
|
|
236
|
+
const typedWords = calendarNameWords(query);
|
|
237
|
+
const current = withCurrentName(typedWords);
|
|
238
|
+
const match = matchCalendarWords(current?.words ?? typedWords, country, events, now);
|
|
239
|
+
return current ? { ...match, renamed: current.rename } : match;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function matchCalendarWords(
|
|
243
|
+
words: readonly string[],
|
|
244
|
+
country: CountryFilter | null,
|
|
245
|
+
events: readonly EconEvent[],
|
|
246
|
+
now: number,
|
|
247
|
+
): CalendarEventMatch {
|
|
198
248
|
const choices = calendarChoices(country ? events.filter((event) => matchesCountry(event, country)) : events, now);
|
|
199
249
|
const exact = choices.filter((choice) => choice.words.join(" ") === words.join(" "));
|
|
200
250
|
const naming = (typed: readonly string[], loose: boolean) => typed.length === 0 ? [] : choices.filter((choice) =>
|
|
@@ -266,9 +316,10 @@ function renamedBy(words: readonly string[], other: readonly string[]): number |
|
|
|
266
316
|
*/
|
|
267
317
|
function releaseRows(target: CalendarReleaseTarget, events: readonly EconEvent[]): EconEvent[] {
|
|
268
318
|
const words = calendarNameWords(target.event);
|
|
269
|
-
|
|
319
|
+
// An alert kept under a series' former name follows the rows under its current one.
|
|
320
|
+
const keys = new Set([words.join(" "), withCurrentName(words)?.words.join(" ")]);
|
|
270
321
|
const own = events.filter((event) => eventCountry(event) === target.country);
|
|
271
|
-
const named = own.filter((event) => nameKey(event.event)
|
|
322
|
+
const named = own.filter((event) => keys.has(nameKey(event.event)));
|
|
272
323
|
if (!target.publisher) return named;
|
|
273
324
|
const namedDays = new Set(named.map((event) => dateKey(event.date)));
|
|
274
325
|
const renamed = own.flatMap((event) => {
|