@gloom-sh/terminal 0.18.5 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +163 -8
- package/src/api-client/app-attention.ts +3 -0
- package/src/api-client/beneficial-owners.ts +3 -0
- package/src/api-client/company-kpis.ts +3 -0
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +19 -7
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/hiring.ts +3 -0
- package/src/api-client/index.ts +15 -0
- package/src/api-client/ipo.ts +24 -1
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +1 -1
- package/src/api-client/mna.ts +3 -0
- package/src/api-client/paths.ts +9 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/research-activity.ts +3 -2
- package/src/api-client/roadmap.ts +85 -0
- package/src/api-client/types.ts +82 -2
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +0 -2
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +2 -2
- 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/market.ts +107 -38
- package/src/cli/commands/news-search.ts +22 -0
- package/src/cli/commands/options-chain.ts +48 -7
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +147 -32
- package/src/cli/commands/search.ts +21 -1
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +87 -36
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +25 -2
- package/src/cli/glossary.ts +31 -2
- package/src/cli/history-rows.ts +57 -9
- package/src/cli/index-roots.ts +0 -7
- package/src/cli/index.ts +2 -0
- package/src/cli/listing-arg.ts +235 -60
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +4 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/discovery.ts +21 -1
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +93 -4
- package/src/cli/pane-functions/function-help.ts +20 -10
- package/src/cli/pane-functions/headless.ts +17 -7
- package/src/cli/pane-functions/index.ts +54 -16
- package/src/cli/pane-functions/market-session.ts +66 -4
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/resolver.ts +19 -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 +22 -11
- package/src/cli/saved-copy-persistence.ts +88 -0
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +4 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-surface.tsx +52 -3
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +39 -4
- 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/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/list/model.ts +5 -0
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/routes/root/runtime.ts +6 -7
- package/src/components/command-bar/selection-runtime.ts +52 -3
- package/src/components/command-bar/surface/index.tsx +2 -0
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/pane/capture-stamp.ts +2 -2
- package/src/components/layout/pane/footer/index.tsx +22 -6
- package/src/components/layout/pane/footer/model.ts +83 -15
- package/src/components/layout/pane/status-footer.ts +35 -2
- package/src/components/layout/status-widget-space.ts +9 -0
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
- 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 +8 -21
- 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 +18 -3
- package/src/components/ui/icon.tsx +4 -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/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/plugin-cache.ts +5 -3
- package/src/data/saved-copies.ts +8 -0
- 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 +103 -142
- package/src/i18n/es.ts +20 -6
- package/src/i18n/ja.ts +20 -6
- package/src/i18n/ko.ts +20 -6
- package/src/i18n/zh-cn.ts +20 -6
- package/src/i18n/zh-tw.ts +20 -6
- package/src/market-data/depositary-pair.ts +27 -3
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +16 -3
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/options-alternatives.ts +81 -4
- package/src/market-data/quotes/metadata.ts +7 -0
- package/src/news/aggregator.ts +119 -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 +17 -0
- package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +362 -80
- package/src/plugins/builtin/alerts/command.ts +25 -1
- package/src/plugins/builtin/alerts/event-rows.ts +24 -3
- package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
- package/src/plugins/builtin/alerts/fired-log.ts +73 -9
- package/src/plugins/builtin/alerts/format.ts +75 -5
- package/src/plugins/builtin/alerts/headless.ts +34 -12
- package/src/plugins/builtin/alerts/history-rows.ts +3 -2
- package/src/plugins/builtin/alerts/history.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +79 -16
- package/src/plugins/builtin/alerts/metric-command.ts +77 -4
- package/src/plugins/builtin/alerts/metric-data.ts +54 -13
- package/src/plugins/builtin/alerts/metric-model.ts +163 -14
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
- package/src/plugins/builtin/alerts/pane.tsx +61 -23
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +12 -8
- package/src/plugins/builtin/alerts/types.ts +4 -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/broker-manager/index.tsx +7 -3
- 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 +6 -2
- package/src/plugins/builtin/cds/client.ts +142 -30
- package/src/plugins/builtin/cds/headless.ts +35 -8
- package/src/plugins/builtin/cds/model.ts +145 -18
- package/src/plugins/builtin/cds/pane.tsx +16 -5
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
- package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
- package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
- package/src/plugins/builtin/chart-composer/headless.ts +6 -1
- package/src/plugins/builtin/chart-composer/index.tsx +5 -6
- package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
- package/src/plugins/builtin/chat/controller/index.ts +5 -1
- package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
- package/src/plugins/builtin/chat/controller/state.ts +25 -2
- package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
- package/src/plugins/builtin/chat/controller/utils.ts +7 -2
- package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
- package/src/plugins/builtin/chat/status-preview.ts +79 -0
- package/src/plugins/builtin/chat/status-widget.tsx +147 -10
- package/src/plugins/builtin/chat/unread-badge.ts +51 -10
- package/src/plugins/builtin/clinical-trials/client.ts +49 -4
- package/src/plugins/builtin/clinical-trials/model.ts +86 -31
- package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
- package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
- package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- 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/headless.ts +2 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +12 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/company-kpis/headless.ts +8 -3
- package/src/plugins/builtin/company-kpis/model.ts +73 -5
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/composite-plugins.ts +2 -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 +10 -10
- 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/doe/headless.ts +3 -2
- package/src/plugins/builtin/doe/pane.tsx +13 -3
- 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/data.ts +18 -6
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
- package/src/plugins/builtin/econ/calendar-model.ts +48 -1
- package/src/plugins/builtin/econ/calendar-source.ts +4 -2
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/figures.ts +48 -0
- package/src/plugins/builtin/econ/index.tsx +14 -5
- package/src/plugins/builtin/econ/print-watch.ts +3 -1
- package/src/plugins/builtin/econ/types.ts +2 -0
- package/src/plugins/builtin/econ-statistics/client.ts +11 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
- package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +39 -9
- 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 +68 -13
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- 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 +6 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
- package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
- package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -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 +62 -59
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
- package/src/plugins/builtin/help/table-cells.tsx +13 -2
- package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +22 -3
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/holders/report-rows.ts +4 -1
- package/src/plugins/builtin/insider/headless.ts +2 -2
- package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
- package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
- package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
- 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 +33 -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/headless.ts +12 -5
- package/src/plugins/builtin/macro-day/index.tsx +1 -1
- package/src/plugins/builtin/macro-day/model.ts +39 -5
- package/src/plugins/builtin/macro-day/pane.tsx +27 -19
- 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 +27 -7
- package/src/plugins/builtin/market-movers/headless.ts +34 -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 +22 -4
- 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 +2 -1
- package/src/plugins/builtin/mna/headless.ts +2 -0
- package/src/plugins/builtin/mna/pane.tsx +3 -1
- package/src/plugins/builtin/news/headless.ts +3 -1
- package/src/plugins/builtin/news/index.tsx +4 -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 +29 -5
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/table.ts +5 -2
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +17 -4
- package/src/plugins/builtin/options-positioning/client.ts +11 -0
- package/src/plugins/builtin/options-positioning/headless.ts +21 -8
- package/src/plugins/builtin/options-positioning/index.tsx +1 -1
- package/src/plugins/builtin/options-positioning/model.ts +78 -9
- package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
- 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/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +19 -22
- 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 +7 -5
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -7
- 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/index.tsx +0 -14
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
- package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/realized-vol/client.ts +13 -5
- package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
- package/src/plugins/builtin/realized-vol/headless.ts +8 -4
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/model.ts +37 -6
- package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
- 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 +40 -12
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/index.tsx +7 -3
- package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
- package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
- package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
- 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/risk-factors/data.ts +4 -2
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/roadmap/index.tsx +28 -0
- package/src/plugins/builtin/roadmap/model.ts +67 -0
- package/src/plugins/builtin/roadmap/pane.tsx +244 -0
- 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 +16 -5
- package/src/plugins/builtin/scanner/live-headless.ts +48 -5
- package/src/plugins/builtin/scanner/live-model.ts +11 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
- 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 +4 -2
- package/src/plugins/builtin/sec/index.tsx +3 -0
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
- 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/headless-market-data.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -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 +11 -2
- package/src/plugins/builtin/shared/research-cells.tsx +1 -1
- package/src/plugins/builtin/shared/series-cache.ts +4 -4
- 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 +2 -1
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/model.ts +16 -7
- package/src/plugins/builtin/themes/pane.tsx +5 -5
- package/src/plugins/builtin/thirteenf/api.ts +12 -1
- package/src/plugins/builtin/thirteenf/data.ts +7 -7
- package/src/plugins/builtin/thirteenf/headless.ts +31 -4
- 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 +11 -3
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
- 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-auctions/headless.ts +21 -12
- package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
- 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/client.ts +3 -13
- package/src/plugins/builtin/vol-surface/headless.ts +3 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +7 -4
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/builtin/yield-curve/client.ts +61 -1
- package/src/plugins/builtin/yield-curve/headless.ts +9 -6
- package/src/plugins/builtin/yield-curve/index.tsx +34 -10
- package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/host-link.ts +1 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +11 -7
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/remote/resources.ts +2 -1
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/icons.tsx +7 -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 +3 -3
- package/src/renderers/electrobun/bun/desktop/backend.ts +21 -10
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
- package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
- package/src/renderers/electrobun/bun/index.ts +26 -2
- package/src/renderers/electrobun/bun/window/create.ts +43 -4
- package/src/renderers/electrobun/bun/window/events.ts +20 -0
- package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
- package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
- package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
- package/src/renderers/electrobun/shared/app-name.ts +13 -0
- package/src/renderers/electrobun/shared/protocol.ts +12 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +5 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/ui-host.tsx +8 -0
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- 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/fred-series.ts +3 -1
- package/src/sources/gloomberb-cloud/index.ts +43 -8
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/index.ts +8 -5
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/controller.ts +4 -3
- package/src/sync/core-contributors.ts +90 -31
- 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/test-support/preload.ts +14 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name-listing.ts +53 -13
- package/src/tickers/company-name.ts +50 -4
- package/src/tickers/index-roots.ts +7 -0
- package/src/tickers/listing-fate.ts +2 -2
- package/src/tickers/listing-venues.ts +15 -3
- package/src/tickers/search/index.ts +6 -2
- package/src/tickers/search/ranking.ts +5 -1
- package/src/tickers/search/upsert.ts +13 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/tickers/ticker-changes.ts +8 -4
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +1 -1
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/fundamentals.ts +44 -1
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/resolve-context.ts +3 -1
- package/src/time-series/resolve.ts +56 -4
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +3 -2
- package/src/time-series/studies.ts +8 -2
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +2 -3
- package/src/time-series/types.ts +22 -1
- package/src/types/company-from.ts +40 -0
- package/src/types/data-provider.ts +7 -0
- package/src/types/financials.ts +59 -1
- package/src/types/headless.ts +27 -3
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +9 -0
- package/src/ui/host.tsx +3 -0
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +165 -7
- package/src/utils/declared-dividends.ts +1 -1
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +60 -8
- 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/price-history.ts +7 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/version.ts +2 -2
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
1
2
|
import type { HeadlessPaneFreshness } from "../../../types/headless";
|
|
2
3
|
import type { Quote, QuoteIndexSession } from "../../../types/financials";
|
|
3
4
|
import { isQuoteBeforeLatestSession, isQuoteStaleForCurrentSession } from "../../../market-data/quotes/freshness";
|
|
@@ -46,6 +47,14 @@ export function barResolutionFromDates(dates: readonly (string | number | Date)[
|
|
|
46
47
|
/** Company filings read from EDGAR. Old filings are not stale; there is no schedule to miss. */
|
|
47
48
|
export const SEC_FILINGS: HeadlessPaneFreshness = { source: "SEC EDGAR", status: "not-a-feed", basis: "filed data", oldest: null };
|
|
48
49
|
|
|
50
|
+
/**
|
|
51
|
+
* A list of filings, dated by its newest one's filing date and by when it was
|
|
52
|
+
* read: `newest filing Tue 27 Oct 2026 · retrieved Wed 28 Oct 2026 19:07 UTC`.
|
|
53
|
+
*/
|
|
54
|
+
export const SEC_FILING_ROWS: HeadlessPaneFreshness = {
|
|
55
|
+
...SEC_FILINGS, observedKey: "filingDate", asOfLabel: "newest filing", retrievedToo: true,
|
|
56
|
+
};
|
|
57
|
+
|
|
49
58
|
/**
|
|
50
59
|
* What a filings view opened inside a pane that hosts others (Issuer Debt in
|
|
51
60
|
* Fixed Income) declares as its template's `reportFreshness`, so a capture
|
|
@@ -129,8 +138,8 @@ export async function cloudRealtimeAccess(): Promise<boolean | null> {
|
|
|
129
138
|
if (!apiClient.isSignedIn()) return false;
|
|
130
139
|
try {
|
|
131
140
|
return hasProAccess(apiClient.getCurrentUser() ?? await apiClient.getSession());
|
|
132
|
-
} catch {
|
|
133
|
-
return null;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
return logFailure("shared.report-freshness", "Read Cloud plan for report freshness failed", null)(error);
|
|
134
143
|
}
|
|
135
144
|
}
|
|
136
145
|
|
|
@@ -14,7 +14,7 @@ import { displayWidth } from "../../../utils/format";
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
/** The one placeholder for a value the source does not give. */
|
|
17
|
-
const NO_VALUE = "--";
|
|
17
|
+
export const NO_VALUE = "--";
|
|
18
18
|
|
|
19
19
|
export const missingCell = (colors: ThemeColors): DataTableCell => ({ text: NO_VALUE, value: null, color: colors.textMuted });
|
|
20
20
|
|
|
@@ -74,9 +74,9 @@ export function createSeriesCache(kind: string, staleMs: number): SeriesCache {
|
|
|
74
74
|
let generation = 0;
|
|
75
75
|
let persistence: PluginPersistence | null = null;
|
|
76
76
|
const withFailure = <T extends SeriesCacheLoadResult>(key: string, result: T): T => {
|
|
77
|
-
|
|
78
|
-
return
|
|
79
|
-
? { ...result, stale: true, source: "stale-fallback", refreshError:
|
|
77
|
+
// A failure is one whatever it said: an Error without a message still leaves the copy a saved one.
|
|
78
|
+
return failures.has(key)
|
|
79
|
+
? { ...result, stale: true, source: "stale-fallback", refreshError: failures.get(key) }
|
|
80
80
|
: result;
|
|
81
81
|
};
|
|
82
82
|
const loadEntry: SeriesCache["loadEntry"] = async (key, loader, options) => {
|
|
@@ -103,7 +103,7 @@ export function createSeriesCache(kind: string, staleMs: number): SeriesCache {
|
|
|
103
103
|
if (owner === generation && requests.get(key) === request) requests.delete(key);
|
|
104
104
|
}
|
|
105
105
|
}, options);
|
|
106
|
-
return withFailure(key, { ...data, fetchedAt, stale, source, ...(
|
|
106
|
+
return withFailure(key, { ...data, fetchedAt, stale, source, ...(source === "stale-fallback" ? { refreshError } : {}) });
|
|
107
107
|
};
|
|
108
108
|
const reset = () => {
|
|
109
109
|
generation += 1;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
1
2
|
import { useCallback } from "react";
|
|
2
3
|
import { ChoiceDialog } from "../../../components/ui/choice-dialog";
|
|
3
4
|
import { t } from "../../../i18n";
|
|
@@ -29,6 +30,6 @@ export function useOpenTickerChoice(): (symbols: readonly string[]) => void {
|
|
|
29
30
|
),
|
|
30
31
|
}).then((symbol) => {
|
|
31
32
|
if (symbol) openTicker(symbol);
|
|
32
|
-
}).catch((
|
|
33
|
+
}).catch(logFailure("shared.ticker-choice", "Open ticker choice failed"));
|
|
33
34
|
}, [dialog, openTicker]);
|
|
34
35
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { isNoSourceAnswer } from "../../../sources/provider-errors";
|
|
3
|
+
import type { DataProvider } from "../../../types/data-provider";
|
|
4
|
+
import { shortCompanyName } from "../../../tickers/company-name";
|
|
5
|
+
import { classifyInstrumentKind } from "../../../tickers/search/ranking";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A search typed as a ticker (`TRIAL VKTX`, `FDA AZN`): one to five letters,
|
|
9
|
+
* with a share class. Registries know companies by name, so a symbol no record
|
|
10
|
+
* mentions is looked up as its company.
|
|
11
|
+
*/
|
|
12
|
+
export function looksLikeTicker(term: string): boolean {
|
|
13
|
+
return /^[A-Za-z]{1,5}(?:[.-][A-Za-z])?$/.test(term.trim());
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The company a ticker is, by the name a registry files its records under:
|
|
18
|
+
* VKTX is "Viking Therapeutics", AZN "AstraZeneca". Read from the listing's
|
|
19
|
+
* quote, which names it; a fund or a contract is no company. Null when no
|
|
20
|
+
* share trades under it. A quote that could not be read (unreachable, busy,
|
|
21
|
+
* timed out) rejects: it says nothing about the company.
|
|
22
|
+
*/
|
|
23
|
+
export async function tickerCompanyName(dataProvider: Pick<DataProvider, "getQuote"> | null, ticker: string): Promise<string | null> {
|
|
24
|
+
const symbol = ticker.trim().toUpperCase();
|
|
25
|
+
if (!dataProvider || !symbol) return null;
|
|
26
|
+
const quote = await dataProvider.getQuote(symbol).catch((error: unknown) => {
|
|
27
|
+
if (isNoSourceAnswer(error)) return null;
|
|
28
|
+
throw error;
|
|
29
|
+
});
|
|
30
|
+
const name = quote?.name?.trim();
|
|
31
|
+
const kind = classifyInstrumentKind(quote?.instrumentType);
|
|
32
|
+
return name && kind !== "fund" && kind !== "derivative" ? shortCompanyName(name) : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface TickerCompanySearch {
|
|
36
|
+
/** The company is being looked up: the pane shows it loading. */
|
|
37
|
+
resolving: boolean;
|
|
38
|
+
/** The lookup failed, for the pane's unavailable state; null otherwise. */
|
|
39
|
+
error: string | null;
|
|
40
|
+
/** Looks again after a failure: the pane's refresh calls it. */
|
|
41
|
+
retry: () => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A search for a ticker that found nothing is searched again as its company's
|
|
46
|
+
* name: `onCompany` runs with it, once per `key` (the term as the pane compares
|
|
47
|
+
* it). `eligible` is true once the search has finished empty and the term reads
|
|
48
|
+
* as a ticker. A lookup that failed is no answer: it reports `error` until
|
|
49
|
+
* `retry`, or until the term changes and comes back.
|
|
50
|
+
*/
|
|
51
|
+
export function useTickerCompanySearch(
|
|
52
|
+
dataProvider: Pick<DataProvider, "getQuote"> | null,
|
|
53
|
+
term: string,
|
|
54
|
+
{ eligible, key, onCompany }: { eligible: boolean; key: string; onCompany: (name: string) => void },
|
|
55
|
+
): TickerCompanySearch {
|
|
56
|
+
const [looked, setLooked] = useState<ReadonlySet<string>>(() => new Set());
|
|
57
|
+
const [failed, setFailed] = useState<string | null>(null);
|
|
58
|
+
// A failure belongs to its term: another search clears it, so coming back looks again.
|
|
59
|
+
if (failed !== null && failed !== key) setFailed(null);
|
|
60
|
+
const onCompanyRef = useRef(onCompany);
|
|
61
|
+
onCompanyRef.current = onCompany;
|
|
62
|
+
const resolving = eligible && !looked.has(key) && failed !== key;
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!resolving) return;
|
|
65
|
+
let live = true;
|
|
66
|
+
tickerCompanyName(dataProvider, term).then((name) => {
|
|
67
|
+
if (!live) return;
|
|
68
|
+
setLooked((current) => new Set([...current, key]));
|
|
69
|
+
if (name) onCompanyRef.current(name);
|
|
70
|
+
}, () => {
|
|
71
|
+
if (live) setFailed(key);
|
|
72
|
+
});
|
|
73
|
+
return () => { live = false; };
|
|
74
|
+
}, [dataProvider, key, resolving, term]);
|
|
75
|
+
const retry = useCallback(() => setFailed(null), []);
|
|
76
|
+
return { resolving, error: eligible && failed === key ? `Couldn't look up ${term}'s company.` : null, retry };
|
|
77
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
1
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
3
|
import type { DataTableCell, DataTableVisibleRange, PaneFooterSegment } from "../../../components";
|
|
3
4
|
import { savedCopyFooterSegment } from "../../../components/layout/pane/footer/model";
|
|
@@ -341,7 +342,7 @@ export function useQuoteBoard(symbols: string[], options: QuoteBoardOptions = {}
|
|
|
341
342
|
if (fetchGenRef.current !== gen) return;
|
|
342
343
|
setSnapshots((prev) => paintCachedQuotes(prev, cached));
|
|
343
344
|
})
|
|
344
|
-
.catch((
|
|
345
|
+
.catch(logFailure("shared.use-quote-board", "Paint cached board quotes failed"));
|
|
345
346
|
}, [dataProvider]);
|
|
346
347
|
|
|
347
348
|
/** The cache-first full load: the listed symbols' cached quotes at once, then from the network. */
|
|
@@ -112,6 +112,21 @@ export function useOptionsSessionOpen(enabled = true): boolean {
|
|
|
112
112
|
return state.nextChangeAt != null && Date.now() >= state.nextChangeAt ? usOptionsSession(Date.now()).open : state.open;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
export interface LiveSessionRefresh {
|
|
116
|
+
/** The refresh cycle is running, which is what "live" means. */
|
|
117
|
+
live: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Why the last refresh failed, until one succeeds or the cycle stops. The
|
|
120
|
+
* data it would have replaced stays on screen, so the pane shows this where
|
|
121
|
+
* it shows its own failed refreshes.
|
|
122
|
+
*/
|
|
123
|
+
error: string | null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isCancellation(error: unknown): boolean {
|
|
127
|
+
return !!error && typeof error === "object" && (error as { name?: unknown }).name === "AbortError";
|
|
128
|
+
}
|
|
129
|
+
|
|
115
130
|
/**
|
|
116
131
|
* Calls `refresh` every `intervalMs` while enabled, the pane can be seen (the
|
|
117
132
|
* app is visible and the pane is not covered) and the options market is open.
|
|
@@ -119,18 +134,19 @@ export function useOptionsSessionOpen(enabled = true): boolean {
|
|
|
119
134
|
* after the caller's own load (`loadedAt`); returning after a hidden or
|
|
120
135
|
* covered stretch, or reaching the open, more than an interval after the last
|
|
121
136
|
* data fires straight away. A refresh still in flight is never overlapped by
|
|
122
|
-
* the next.
|
|
123
|
-
*
|
|
137
|
+
* the next. A refresh that throws or rejects is reported as `error` until the
|
|
138
|
+
* next one succeeds; a cancelled one (an AbortError) is no failure.
|
|
124
139
|
*/
|
|
125
140
|
export function useLiveSessionRefresh(
|
|
126
141
|
refresh: () => void | Promise<unknown>,
|
|
127
142
|
intervalMs: number,
|
|
128
143
|
enabled: boolean,
|
|
129
144
|
loadedAt?: number | null,
|
|
130
|
-
):
|
|
145
|
+
): LiveSessionRefresh {
|
|
131
146
|
const visible = usePaneVisible();
|
|
132
147
|
const open = useOptionsSessionOpen(enabled);
|
|
133
148
|
const active = enabled && visible && open;
|
|
149
|
+
const [error, setError] = useState<string | null>(null);
|
|
134
150
|
const refreshRef = useRef(refresh);
|
|
135
151
|
refreshRef.current = refresh;
|
|
136
152
|
const lastRef = useRef(Date.now());
|
|
@@ -142,21 +158,31 @@ export function useLiveSessionRefresh(
|
|
|
142
158
|
let cancelled = false;
|
|
143
159
|
// A load the caller finished itself counts as the latest refresh.
|
|
144
160
|
if (loadedAtRef.current != null && loadedAtRef.current > lastRef.current) lastRef.current = loadedAtRef.current;
|
|
161
|
+
const succeeded = () => { if (!cancelled) setError(null); };
|
|
162
|
+
const failed = (failure: unknown) => {
|
|
163
|
+
if (cancelled || isCancellation(failure)) return;
|
|
164
|
+
const message = (failure instanceof Error ? failure.message : String(failure ?? "")).trim();
|
|
165
|
+
setError(message || "Live refresh failed");
|
|
166
|
+
};
|
|
145
167
|
const run = () => {
|
|
146
168
|
if (inFlight || cancelled) return;
|
|
147
169
|
lastRef.current = Date.now();
|
|
148
170
|
let pending: void | Promise<unknown>;
|
|
149
|
-
try { pending = refreshRef.current(); } catch {
|
|
171
|
+
try { pending = refreshRef.current(); } catch (failure) {
|
|
172
|
+
failed(failure);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
150
175
|
if (pending && typeof (pending as Promise<unknown>).then === "function") {
|
|
151
176
|
inFlight = true;
|
|
152
|
-
void (pending as Promise<unknown>).
|
|
153
|
-
}
|
|
177
|
+
void (pending as Promise<unknown>).then(succeeded, failed).finally(() => { inFlight = false; });
|
|
178
|
+
} else succeeded();
|
|
154
179
|
};
|
|
155
180
|
if (Date.now() - lastRef.current >= intervalMs) run();
|
|
156
181
|
const timer = setInterval(run, intervalMs);
|
|
157
|
-
|
|
182
|
+
// A stopped cycle's failure is old news: the pane's own loads take over.
|
|
183
|
+
return () => { cancelled = true; clearInterval(timer); setError(null); };
|
|
158
184
|
}, [active, intervalMs]);
|
|
159
|
-
return active;
|
|
185
|
+
return { live: active, error: active ? error : null };
|
|
160
186
|
}
|
|
161
187
|
|
|
162
188
|
/**
|
|
@@ -134,6 +134,25 @@ export function valueOption(input: OptionPricingInput): OptionValuation {
|
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
/**
|
|
138
|
+
* The chance the underlying closes above `strike` at expiry under the same
|
|
139
|
+
* Black-Scholes-Merton model: N(d2), the risk-neutral probability a call there
|
|
140
|
+
* finishes in the money. 1 or 0 when nothing is left uncertain (expiry, zero
|
|
141
|
+
* volatility), by where the discounted forward sits.
|
|
142
|
+
*/
|
|
143
|
+
export function probabilityAbove(input: Omit<OptionPricingInput, "side">): number {
|
|
144
|
+
const spot = Math.max(0, finite(input.spot));
|
|
145
|
+
const strike = Math.max(0, finite(input.strike));
|
|
146
|
+
const years = Math.max(0, finite(input.daysToExpiry)) / DAYS_PER_YEAR;
|
|
147
|
+
const rate = finite(input.rate);
|
|
148
|
+
const dividendYield = finite(input.dividendYield);
|
|
149
|
+
const volatility = Math.max(0, finite(input.volatility));
|
|
150
|
+
if (strike <= 0) return 1;
|
|
151
|
+
if (years <= 0 || volatility <= 0 || spot <= 0) return spot * Math.exp((rate - dividendYield) * years) > strike ? 1 : 0;
|
|
152
|
+
const d2 = (Math.log(spot / strike) + (rate - dividendYield - volatility * volatility / 2) * years) / (volatility * Math.sqrt(years));
|
|
153
|
+
return normalCdf(d2);
|
|
154
|
+
}
|
|
155
|
+
|
|
137
156
|
export interface ImpliedVolatilityResult {
|
|
138
157
|
volatility: number | null;
|
|
139
158
|
/** Why no volatility could be solved, phrased for the pane body. */
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
2
|
+
|
|
1
3
|
type Arm = "control" | "teaser" | null;
|
|
2
4
|
type TeaserKind = "summary" | "sample" | "none";
|
|
3
5
|
const active = new Map<string, Set<object>>();
|
|
@@ -17,7 +19,7 @@ export function trackWallView({ identity, placement, exposure, currentIdentity,
|
|
|
17
19
|
active.set(key, copies);
|
|
18
20
|
let released = false;
|
|
19
21
|
let reported = false;
|
|
20
|
-
const answer = exposure.catch((
|
|
22
|
+
const answer = exposure.catch(logFailure("shared.wall-view-lifecycle", "Resolve wall experiment arm failed", null));
|
|
21
23
|
|
|
22
24
|
return {
|
|
23
25
|
exposure: answer,
|
|
@@ -17,14 +17,16 @@ export function boardYtdBase(payload: { asOf?: unknown; regularSessionDate?: unk
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* A period's header. YTD
|
|
21
|
-
*
|
|
20
|
+
* A period's header. YTD says what it is, a return against the close it is
|
|
21
|
+
* measured from, as long as `room` allows: `YTD vs 31 Dec close`, `YTD vs 31
|
|
22
|
+
* Dec`, then `YTD`.
|
|
22
23
|
*/
|
|
23
24
|
export function periodLabel(period: ThemePeriod, ytdBase: string | null, room = Infinity): string {
|
|
24
25
|
if (period !== "returnYtdPercent" || !ytdBase) return PERIOD_LABELS[period];
|
|
25
|
-
const labels = [`YTD vs ${ytdBase} close`, `YTD vs ${ytdBase}`,
|
|
26
|
+
const labels = [`YTD vs ${ytdBase} close`, `YTD vs ${ytdBase}`, "YTD"];
|
|
26
27
|
return labels.find((label) => label.length <= room) ?? labels.at(-1)!;
|
|
27
28
|
}
|
|
29
|
+
|
|
28
30
|
export interface ThemeSort { columnId: string; direction: SortDirection }
|
|
29
31
|
export const DEFAULT_SORT: ThemeSort = { columnId: "changePercent", direction: "desc" };
|
|
30
32
|
export function percent(value: number | null): string {
|
|
@@ -32,12 +34,17 @@ export function percent(value: number | null): string {
|
|
|
32
34
|
const rounded = Number(value.toFixed(2));
|
|
33
35
|
return `${rounded > 0 ? "+" : ""}${rounded.toFixed(2)}%`;
|
|
34
36
|
}
|
|
37
|
+
/** How many of a theme's names a figure is drawn from, when not all of them: `7 of 8`. Empty when every name is in it. */
|
|
38
|
+
export function coverageText(metric: ThemeAggregate): string {
|
|
39
|
+
return metric.covered < metric.total ? `${metric.covered} of ${metric.total}` : "";
|
|
40
|
+
}
|
|
35
41
|
export function aggregateText(metric: ThemeAggregate, breadth = false): string {
|
|
36
42
|
const value = breadth && metric.value !== null ? `${Math.round(metric.value)}%` : percent(metric.value);
|
|
37
|
-
|
|
43
|
+
const coverage = coverageText(metric);
|
|
44
|
+
return `${value}${coverage ? ` ${coverage}` : ""}`;
|
|
38
45
|
}
|
|
39
46
|
export const memberPrice = (value: number | null) => value === null ? "--" : value.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
40
|
-
export const coverageWidth = (metrics: readonly ThemeAggregate[]) => Math.max(0, ...metrics.map((metric) => metric
|
|
47
|
+
export const coverageWidth = (metrics: readonly ThemeAggregate[]) => Math.max(0, ...metrics.map((metric) => coverageText(metric) ? coverageText(metric).length + 1 : 0));
|
|
41
48
|
export function sortThemes(rows: readonly ThemeSummary[], sort: ThemeSort): ThemeSummary[] {
|
|
42
49
|
const value = (row: ThemeSummary) => {
|
|
43
50
|
if (sort.columnId === "name") return row.name;
|
|
@@ -152,9 +159,11 @@ export function themeColumns(width: number, rows: readonly ThemeSummary[], ytdBa
|
|
|
152
159
|
const metricWidth = (period: ThemePeriod) => Math.max(8, ...rows.map((row) => percent(row.returns[period].value).length)) + suffixWidth(period);
|
|
153
160
|
// The same trailing space is reserved in every cell and its header. A note
|
|
154
161
|
// occupies that space without moving the number away from its label.
|
|
155
|
-
const label = (period: ThemePeriod, room
|
|
162
|
+
const label = (period: ThemePeriod, room: number) => periodLabel(period, ytdBase, room) + " ".repeat(suffixWidth(period));
|
|
163
|
+
// The board always keeps room for `YTD vs 31 Dec`, so even a narrow one says what its YTD is measured against.
|
|
164
|
+
const leastRoom = ytdBase ? `YTD vs ${ytdBase}`.length : 0;
|
|
156
165
|
const columns = [column("name", "Theme", 26, "left"), column("memberCount", "N", 3),
|
|
157
|
-
...THEME_PERIODS.map((period) => column(period, label(period,
|
|
166
|
+
...THEME_PERIODS.map((period) => column(period, label(period, leastRoom), Math.max(metricWidth(period), label(period, leastRoom).length + HEADER_ROOM))),
|
|
158
167
|
column("breadth", "Breadth" + " ".repeat(coverageWidth(rows.map((row) => row.breadth))), 7 + coverageWidth(rows.map((row) => row.breadth))),
|
|
159
168
|
column("best", "Best 1D", 15), column("worst", "Worst 1D", 15)];
|
|
160
169
|
const fitted = fitColumns(columns, width, ["worst", "best", "return3MPercent", "returnYtdPercent", "return1MPercent", "return1WPercent", "memberCount"]);
|
|
@@ -5,14 +5,14 @@ import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
|
5
5
|
import type { ExtendedSession } from "../../../market-data/market/status";
|
|
6
6
|
import { useAsyncResource, useAutoRefresh, usePluginPaneState, usePluginTickerActions, useUpdatedAgo } from "../../../public/react";
|
|
7
7
|
import { usePaneInstance } from "../../../state/app/context";
|
|
8
|
-
import {
|
|
8
|
+
import { shownPriceColor } from "../../../theme/colors";
|
|
9
9
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
10
10
|
import { Box, Text } from "../../../ui";
|
|
11
11
|
import { nextHeaderSort } from "../../../utils/sort-values";
|
|
12
12
|
import { useMembersMenu } from "../shared/members-menu";
|
|
13
13
|
import { cachedMembers, cachedThemes, loadMembers, loadThemes } from "./client";
|
|
14
14
|
import {
|
|
15
|
-
aggregateText, boardYtdBase, coverageWidth, DEFAULT_SORT, extendedMove, matchTheme, memberColumns, memberExtendedSessions, memberPrice, percent,
|
|
15
|
+
aggregateText, boardYtdBase, coverageText, coverageWidth, DEFAULT_SORT, extendedMove, matchTheme, memberColumns, memberExtendedSessions, memberPrice, percent,
|
|
16
16
|
isLiveBoard, LIVE_BOARD_REFRESH_MS, SNAPSHOT_STALE_MS, snapshotFooterText, sortMembers, sortThemes, themeColumns, type ThemeSort,
|
|
17
17
|
} from "./model";
|
|
18
18
|
|
|
@@ -135,14 +135,14 @@ export function ThemesBoard({ width, height, focused, tabStrip, tabRows = 0 }: T
|
|
|
135
135
|
if (column.id === "best" || column.id === "worst") {
|
|
136
136
|
const member = row[column.id];
|
|
137
137
|
return { text: member ? `${member.symbol} ${percent(member.changePercent)}` : "--", value: member?.changePercent ?? null,
|
|
138
|
-
color: member ?
|
|
138
|
+
color: member ? shownPriceColor(member.changePercent, 2, colors) : colors.textMuted };
|
|
139
139
|
}
|
|
140
140
|
const breadth = column.id === "breadth";
|
|
141
141
|
const metric = breadth ? row.breadth : row.returns[column.id as ThemePeriod];
|
|
142
142
|
const suffixWidth = coverageWidth((data?.themes ?? []).map((theme) => breadth ? theme.breadth : theme.returns[column.id as ThemePeriod]));
|
|
143
|
-
const color = metric.value === null || breadth ? colors.textMuted :
|
|
143
|
+
const color = metric.value === null || breadth ? colors.textMuted : shownPriceColor(metric.value, 2, colors);
|
|
144
144
|
const valueText = breadth && metric.value !== null ? `${Math.round(metric.value)}%` : percent(metric.value);
|
|
145
|
-
const suffix = metric
|
|
145
|
+
const suffix = coverageText(metric);
|
|
146
146
|
return { text: aggregateText(metric, breadth), value: metric.value,
|
|
147
147
|
color, content: suffixWidth ? <Box flexDirection="row" width={column.width} justifyContent="flex-end">
|
|
148
148
|
<Text fg={color}>{valueText}</Text><Text fg={colors.textMuted}>{suffix.padStart(suffixWidth)}</Text>
|
|
@@ -31,6 +31,12 @@ const FORMS_13F_CACHE_POLICY = {
|
|
|
31
31
|
|
|
32
32
|
export interface Forms13FReadOptions {
|
|
33
33
|
onWarning?: (warning: string) => void;
|
|
34
|
+
/**
|
|
35
|
+
* A read answered from the copy saved at `savedAt` (ms) because its refresh
|
|
36
|
+
* failed, said by when it was saved (a command's closing line) instead of
|
|
37
|
+
* as an `onWarning` line.
|
|
38
|
+
*/
|
|
39
|
+
onSavedCopy?: (savedAt: number) => void;
|
|
34
40
|
forceRefresh?: boolean;
|
|
35
41
|
}
|
|
36
42
|
|
|
@@ -157,7 +163,12 @@ async function fetchForms13F<T>(
|
|
|
157
163
|
const retained = options.cache !== false
|
|
158
164
|
? store?.getResource<T>(CACHE_KIND, key, { ...cacheOptions, allowExpired: true }) : null;
|
|
159
165
|
// A caller may retain old research only when it can surface its provenance.
|
|
160
|
-
if (!retained
|
|
166
|
+
if (!retained) throw error;
|
|
167
|
+
if (options.onSavedCopy && Number.isFinite(retained.fetchedAt)) {
|
|
168
|
+
options.onSavedCopy(retained.fetchedAt);
|
|
169
|
+
return retained.value;
|
|
170
|
+
}
|
|
171
|
+
if (!options.onWarning) throw error;
|
|
161
172
|
const retrieved = new Date(retained.fetchedAt);
|
|
162
173
|
const age = Number.isFinite(retrieved.getTime()) ? `retrieved ${retrieved.toISOString()}` : "with unavailable retrieval time";
|
|
163
174
|
const reason = (error instanceof Error ? error.message : String(error)).trim() || "Request failed";
|
|
@@ -115,7 +115,7 @@ export async function loadBrowserRows(
|
|
|
115
115
|
tab: ThirteenFBrowserTab,
|
|
116
116
|
query: string,
|
|
117
117
|
signal?: AbortSignal,
|
|
118
|
-
options:
|
|
118
|
+
options: Pick<Forms13FReadOptions, "forceRefresh" | "onSavedCopy"> & { offset?: number; limit?: number } = {},
|
|
119
119
|
): Promise<BrowserLoadResult> {
|
|
120
120
|
const warnings: string[] = [];
|
|
121
121
|
const result = await loadBrowserPage(tab, query, signal, { ...options, onWarning: warning => warnings.push(warning) });
|
|
@@ -136,13 +136,13 @@ async function loadBrowserPage(
|
|
|
136
136
|
const offset = Math.max(0, options.offset ?? 0);
|
|
137
137
|
const browserLimit = Math.max(1, options.limit ?? BROWSER_PAGE_LIMIT);
|
|
138
138
|
const latestLimit = Math.max(1, options.limit ?? LATEST_FILINGS_PAGE_LIMIT);
|
|
139
|
-
const apiOptions = { forceRefresh: options.forceRefresh, onWarning: options.onWarning };
|
|
139
|
+
const apiOptions = { forceRefresh: options.forceRefresh, onWarning: options.onWarning, onSavedCopy: options.onSavedCopy };
|
|
140
140
|
const pageApiOptions = { ...apiOptions, offset };
|
|
141
141
|
|
|
142
142
|
if (tab === "performance") {
|
|
143
143
|
const topFunds = await listTopThirteenFFunds(quarter, browserLimit, signal, pageApiOptions);
|
|
144
144
|
const quarters = priorQuarters(quarter);
|
|
145
|
-
const history = await loadRankingHistory(quarters, signal,
|
|
145
|
+
const history = await loadRankingHistory(quarters, signal, apiOptions);
|
|
146
146
|
const funds: ThirteenFFund[] = topFunds.map((fund) => ({ cik: fund.cik, name: fund.name }));
|
|
147
147
|
const rows = buildBrowserRows({ topFunds, source: "performance" }).map(row => ({ ...row, priorReturns: quarters.map((previous, index) => ({ quarter: previous, value: history[index]?.get(row.cik) ?? null })) }));
|
|
148
148
|
const result: BrowserLoadResult = {
|
|
@@ -437,10 +437,10 @@ export async function loadFundPerformance(
|
|
|
437
437
|
cik: string,
|
|
438
438
|
fallbackName: string,
|
|
439
439
|
signal?: AbortSignal,
|
|
440
|
-
options:
|
|
440
|
+
options: Pick<Forms13FReadOptions, "forceRefresh" | "onSavedCopy"> & { splitFactor?: SplitFactorLookup } = {},
|
|
441
441
|
): Promise<FundPerformanceResult> {
|
|
442
442
|
const warnings: string[] = [];
|
|
443
|
-
const apiOptions = { forceRefresh: options.forceRefresh, onWarning: (warning: string) => warnings.push(warning) };
|
|
443
|
+
const apiOptions = { forceRefresh: options.forceRefresh, onSavedCopy: options.onSavedCopy, onWarning: (warning: string) => warnings.push(warning) };
|
|
444
444
|
const detail = await loadFundDetail(cik, fallbackName, signal, options);
|
|
445
445
|
const period = detail.latestForm?.periodOfReport;
|
|
446
446
|
const quarter = period ? periodToQuarter(period) : null;
|
|
@@ -496,11 +496,11 @@ export async function loadFundDetail(
|
|
|
496
496
|
fallbackName: string,
|
|
497
497
|
signal?: AbortSignal,
|
|
498
498
|
/** `filingGap: false` skips EDGAR's filing list, for a load that never shows it (an overlap). */
|
|
499
|
-
options:
|
|
499
|
+
options: Pick<Forms13FReadOptions, "forceRefresh" | "onSavedCopy"> & { filingGap?: boolean } = {},
|
|
500
500
|
): Promise<FundDetailData> {
|
|
501
501
|
const now = new Date();
|
|
502
502
|
const warnings: string[] = [];
|
|
503
|
-
const apiOptions = { forceRefresh: options.forceRefresh, onWarning: (warning: string) => warnings.push(warning) };
|
|
503
|
+
const apiOptions = { forceRefresh: options.forceRefresh, onSavedCopy: options.onSavedCopy, onWarning: (warning: string) => warnings.push(warning) };
|
|
504
504
|
|
|
505
505
|
const forms = await listThirteenFForms(
|
|
506
506
|
cik,
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
HeadlessShotState,
|
|
7
7
|
} from "../../../types/plugin";
|
|
8
8
|
import { formatShortDate } from "../../../utils/datetime-format";
|
|
9
|
+
import { servedFromSavedCopy } from "../../../data/saved-copies";
|
|
9
10
|
import { normalizeCik, searchThirteenFFunds, TOP_FUNDS_MAX_LIMIT } from "./api";
|
|
10
11
|
import { ApiRequestError } from "../../../api-client/errors";
|
|
11
12
|
import { REPORT_PAGE, REPORT_ROWS } from "../shared/headless-options";
|
|
@@ -229,15 +230,40 @@ export interface ThirteenFHeadlessDependencies {
|
|
|
229
230
|
loadEtfHoldings?(ticker: string, ctx: HeadlessPaneContext): Promise<FundMembersPayload>;
|
|
230
231
|
}
|
|
231
232
|
|
|
233
|
+
/** The oldest copy a load's 13F reads answered from because their refresh failed, by the load's context. */
|
|
234
|
+
const savedCopies = new WeakMap<HeadlessPaneContext, { savedAt: number | null }>();
|
|
235
|
+
|
|
236
|
+
/** How a 13F read for the load `ctx` says it answered from the copy saved at `savedAt`. */
|
|
237
|
+
const noteSavedCopy = (ctx: HeadlessPaneContext) => (savedAt: number) => {
|
|
238
|
+
const entry = savedCopies.get(ctx);
|
|
239
|
+
if (entry) entry.savedAt = Math.min(entry.savedAt ?? savedAt, savedAt);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* One load, whose report ends with when the oldest saved copy it answered
|
|
244
|
+
* from was saved (`couldn't refresh; saved copy from <time>`), as every
|
|
245
|
+
* command says it, instead of listing each failed refresh as an error.
|
|
246
|
+
*/
|
|
247
|
+
async function readingSavedCopies<T extends object>(ctx: HeadlessPaneContext, run: () => Promise<T>): Promise<T> {
|
|
248
|
+
const entry: { savedAt: number | null } = { savedAt: null };
|
|
249
|
+
savedCopies.set(ctx, entry);
|
|
250
|
+
try {
|
|
251
|
+
const result = await run();
|
|
252
|
+
return entry.savedAt == null ? result : servedFromSavedCopy(result, entry.savedAt);
|
|
253
|
+
} finally {
|
|
254
|
+
if (savedCopies.get(ctx) === entry) savedCopies.delete(ctx);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
232
258
|
const defaultDependencies: Required<ThirteenFHeadlessDependencies> = {
|
|
233
259
|
loadBrowser: (tab, query, limit, _args, ctx) => loadBrowserRows(
|
|
234
260
|
tab,
|
|
235
261
|
query,
|
|
236
262
|
ctx.signal,
|
|
237
|
-
{ limit },
|
|
263
|
+
{ limit, onSavedCopy: noteSavedCopy(ctx) },
|
|
238
264
|
),
|
|
239
265
|
// An overlap never shows a fund's missed quarter, so it does not ask EDGAR.
|
|
240
|
-
loadDetail: (cik, name, args, ctx) => loadFundDetail(cik, name, ctx.signal, { filingGap: !isOverlapLoad(args) }),
|
|
266
|
+
loadDetail: (cik, name, args, ctx) => loadFundDetail(cik, name, ctx.signal, { filingGap: !isOverlapLoad(args), onSavedCopy: noteSavedCopy(ctx) }),
|
|
241
267
|
loadSplitFactors: (detail, ctx) => loadSplitFactors(
|
|
242
268
|
detail,
|
|
243
269
|
(ticker, after, through) => corporateSplitFactor(ctx.marketData, ticker, after, through),
|
|
@@ -247,6 +273,7 @@ const defaultDependencies: Required<ThirteenFHeadlessDependencies> = {
|
|
|
247
273
|
searchFunds: (query, ctx) => searchThirteenFFunds(query, THIRTEENF_MAX_LIMIT, ctx.signal),
|
|
248
274
|
loadPerformance: (cik, name, ctx) => loadFundPerformance(cik, name, ctx.signal, {
|
|
249
275
|
splitFactor: (ticker, after, through) => corporateSplitFactor(ctx.marketData, ticker, after, through),
|
|
276
|
+
onSavedCopy: noteSavedCopy(ctx),
|
|
250
277
|
}),
|
|
251
278
|
loadEtfFunds: async (ctx) => (await fetchFunds(ctx.apiClient)).funds,
|
|
252
279
|
loadEtfHoldings: (ticker, ctx) => fetchMembers(ticker, ctx.apiClient),
|
|
@@ -541,7 +568,7 @@ export function createThirteenFHeadless(
|
|
|
541
568
|
],
|
|
542
569
|
describe: (args, result) => thirteenFTitle(args, result?.metadata),
|
|
543
570
|
shotState: async (args, load) => thirteenFShotState(args, (await load()).metadata ?? {}),
|
|
544
|
-
|
|
571
|
+
load: (args, ctx) => readingSavedCopies(ctx, async () => {
|
|
545
572
|
const query = typeof args.argument === "string" ? args.argument.trim() : "";
|
|
546
573
|
const requestedView = String(args.options.view) as HeadlessThirteenFView;
|
|
547
574
|
const compare = typeof args.options.compare === "string" ? args.options.compare.trim() : "";
|
|
@@ -920,7 +947,7 @@ export function createThirteenFHeadless(
|
|
|
920
947
|
],
|
|
921
948
|
},
|
|
922
949
|
};
|
|
923
|
-
},
|
|
950
|
+
}),
|
|
924
951
|
};
|
|
925
952
|
}
|
|
926
953
|
|