@gloom-sh/terminal 0.18.5 → 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/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +140 -7
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +17 -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 +11 -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 +1 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/research-activity.ts +3 -2
- package/src/api-client/types.ts +70 -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/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 +72 -26
- package/src/cli/commands/options-chain.ts +48 -7
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +61 -12
- package/src/cli/commands/search.ts +21 -1
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +55 -34
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +8 -0
- package/src/cli/glossary.ts +25 -2
- package/src/cli/history-rows.ts +57 -9
- package/src/cli/index.ts +2 -0
- package/src/cli/listing-arg.ts +180 -52
- 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 +86 -3
- package/src/cli/pane-functions/function-help.ts +13 -10
- package/src/cli/pane-functions/headless.ts +17 -7
- package/src/cli/pane-functions/index.ts +49 -14
- 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/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/pane-templates/items.ts +3 -2
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- 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 +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/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/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 +71 -137
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -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 +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/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/plugins/builtin/alerts/alert-engine.ts +20 -1
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +336 -77
- package/src/plugins/builtin/alerts/command.ts +25 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- 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 +24 -6
- 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/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/headless-schema.ts +7 -1
- package/src/plugins/builtin/chart-composer/headless.ts +6 -1
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- 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/pane-links/commands.ts +7 -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/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/types.ts +2 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +6 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +36 -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 +1 -0
- 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 +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/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/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 +2 -1
- 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 +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 +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/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- 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 +7 -5
- 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/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 +27 -3
- 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 +2 -1
- package/src/plugins/builtin/series-inventory.ts +2 -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/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/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/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-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/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 +2 -2
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- 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/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 +12 -0
- 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/update.ts +115 -14
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/shared/app-name.ts +13 -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/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/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/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.ts +44 -3
- package/src/tickers/listing-venues.ts +15 -3
- package/src/tickers/terminal-codes.ts +19 -0
- 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/financials.ts +32 -1
- package/src/types/headless.ts +21 -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 +44 -0
- 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/price-history.ts +7 -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
|
@@ -4,7 +4,7 @@ import { useAsyncResource } from "../../../react/async-resource";
|
|
|
4
4
|
import { Box } from "../../../ui";
|
|
5
5
|
import { t } from "../../../i18n";
|
|
6
6
|
import type { FundFactsPayload } from "../../../api-client/fund-facts";
|
|
7
|
-
import { cachedFundFacts, fundFactItems, fundFactsEmptyText, fundFactsErrorLine, fundFactsStaleText, loadFundFacts } from "./fund-facts";
|
|
7
|
+
import { cachedFundFacts, fundFactItems, fundFactsEmptyText, fundFactsErrorLine, fundFactsNavReasonText, fundFactsStaleText, loadFundFacts } from "./fund-facts";
|
|
8
8
|
import type { CloudResource } from "../shared/cloud-resource";
|
|
9
9
|
|
|
10
10
|
/** A fund's facts, served from the cache first and refreshed behind it; null asks for nothing. */
|
|
@@ -18,7 +18,8 @@ export function useFundFacts(symbol: string | null | undefined) {
|
|
|
18
18
|
/**
|
|
19
19
|
* The fund's facts as the summary sheet over its filings, or one line saying
|
|
20
20
|
* why there are none. Loading is one muted line, so the filings never wait.
|
|
21
|
-
* A stale NAV adds the line saying since when, under the marked figures
|
|
21
|
+
* A stale NAV adds the line saying since when, under the marked figures; a
|
|
22
|
+
* fund without a NAV, the line saying why.
|
|
22
23
|
*/
|
|
23
24
|
export function FundFactsSummary({ symbol, width, facts, loading, error }: {
|
|
24
25
|
symbol: string;
|
|
@@ -29,10 +30,12 @@ export function FundFactsSummary({ symbol, width, facts, loading, error }: {
|
|
|
29
30
|
}) {
|
|
30
31
|
if (facts?.covered) {
|
|
31
32
|
const stale = fundFactsStaleText(symbol, facts);
|
|
33
|
+
const noNav = fundFactsNavReasonText(symbol, facts);
|
|
32
34
|
return (
|
|
33
35
|
<>
|
|
34
36
|
<StatGrid width={width} items={fundFactItems(facts)} />
|
|
35
37
|
{stale ? <FundViewNotice lines={[stale]} tone="warning" /> : null}
|
|
38
|
+
{noNav ? <FundViewNotice lines={[noNav]} /> : null}
|
|
36
39
|
</>
|
|
37
40
|
);
|
|
38
41
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { apiClient } from "../../../api-client";
|
|
2
|
-
import type { FundFactsPayload, FundLeverage, FundMetalHoldings } from "../../../api-client/fund-facts";
|
|
2
|
+
import type { FundFactsPayload, FundLeverage, FundMetalHoldings, FundStructure } from "../../../api-client/fund-facts";
|
|
3
3
|
import type { StatItem } from "../../../components/ui/stat-grid";
|
|
4
4
|
import { leverageFactorLabel } from "../../../time-series/leverage";
|
|
5
5
|
import { t, tf } from "../../../i18n";
|
|
@@ -7,7 +7,8 @@ import { createPluginCache } from "../../../data/plugin-cache";
|
|
|
7
7
|
import type { HeadlessPaneEntry } from "../../../types/plugin";
|
|
8
8
|
import { errorMessage } from "../../../utils/errors";
|
|
9
9
|
import { formatCompact } from "../../../utils/format";
|
|
10
|
-
import { bondClassText, bondFigures
|
|
10
|
+
import { bondClassText, bondFigures } from "../../../market-data/bond-funds";
|
|
11
|
+
import { readFundFacts, validateFundFacts } from "../../../market-data/fund-facts";
|
|
11
12
|
import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
12
13
|
|
|
13
14
|
/** Sponsors publish once a day, in the US evening; a copy younger than this is not asked for again. */
|
|
@@ -17,28 +18,6 @@ export const fundFactsCache = createPluginCache<FundFactsPayload>({
|
|
|
17
18
|
|
|
18
19
|
type Client = Pick<typeof apiClient, "getFundFacts">;
|
|
19
20
|
|
|
20
|
-
const fact = (value: unknown) => value === null || (typeof value === "object" && value !== null && "value" in value && "source" in value);
|
|
21
|
-
const finite = (value: unknown): value is number => typeof value === "number" && Number.isFinite(value);
|
|
22
|
-
|
|
23
|
-
function isMetalHoldingsShape(value: FundFactsPayload["metalHoldings"]): boolean {
|
|
24
|
-
return value == null || ((value.metal === "gold" || value.metal === "silver") && typeof value.asOf === "string" && finite(value.ounces)
|
|
25
|
-
&& (value.tonnes === null || finite(value.tonnes)) && typeof value.source?.publisher === "string"
|
|
26
|
-
&& (value.change === null || (typeof value.change?.since === "string" && finite(value.change.ounces))));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Absent from servers before it; otherwise a dated factor with its reset period. */
|
|
30
|
-
const leverageShape = (value: FundFactsPayload["leverage"]) => value === undefined || value === null
|
|
31
|
-
|| (fact(value) && typeof value.value === "number" && Number.isFinite(value.value) && (value.reset === "daily" || value.reset === "monthly"));
|
|
32
|
-
|
|
33
|
-
function validateFundFacts(data: FundFactsPayload): FundFactsPayload {
|
|
34
|
-
if (!data || typeof data.ticker !== "string" || typeof data.covered !== "boolean"
|
|
35
|
-
|| ![data.expenseRatio, data.nav, data.premiumDiscount, data.netAssets, data.sponsor, data.inception, data.index].every(fact)
|
|
36
|
-
|| !isBondFactsShape(data.bond) || !leverageShape(data.leverage) || !isMetalHoldingsShape(data.metalHoldings)) {
|
|
37
|
-
throw new Error("Unreadable fund facts response");
|
|
38
|
-
}
|
|
39
|
-
return data;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
21
|
export const FUND_FACTS_UNAVAILABLE = "Fund facts are not available on this server yet.";
|
|
43
22
|
|
|
44
23
|
/** A failed load as one line: the server-not-ready message as it is, anything else after "Fund facts unavailable:". */
|
|
@@ -54,7 +33,7 @@ export function fundFactsErrorLine(error: unknown): string {
|
|
|
54
33
|
}
|
|
55
34
|
|
|
56
35
|
export async function fetchFundFacts(ticker: string, client: Client = apiClient, signal?: AbortSignal): Promise<FundFactsPayload> {
|
|
57
|
-
try { return
|
|
36
|
+
try { return await readFundFacts(ticker, client, signal); } catch (error) { throw unavailableOnServer(error, FUND_FACTS_UNAVAILABLE); }
|
|
58
37
|
}
|
|
59
38
|
const cacheKey = (ticker: string) => ticker.trim().toUpperCase();
|
|
60
39
|
export const cachedFundFacts = (ticker: string) => cachedCloudResource(fundFactsCache, cacheKey(ticker), validateFundFacts);
|
|
@@ -80,6 +59,40 @@ function formatNetAssets(value: number): string {
|
|
|
80
59
|
return `$${formatCompact(value)}`;
|
|
81
60
|
}
|
|
82
61
|
|
|
62
|
+
/** A NAV the sponsor published as a range: `$4.12 to $4.22`. */
|
|
63
|
+
const estimateRange = (estimate: { low: number; high: number }) => `${formatNav(estimate.low)} to ${formatNav(estimate.high)}`;
|
|
64
|
+
|
|
65
|
+
/** Shares created less redeemed, signed: `+12.35M`, `-3.1M`. */
|
|
66
|
+
const signedCompact = (value: number) => `${value > 0 ? "+" : value < 0 ? "-" : ""}${formatCompact(Math.abs(value))}`;
|
|
67
|
+
|
|
68
|
+
/** A month's flows in dollars, when every day had a NAV to value them at. */
|
|
69
|
+
const flowsValue = (value: number) => `${value < 0 ? "-" : value > 0 ? "+" : ""}$${formatCompact(Math.abs(value))}`;
|
|
70
|
+
|
|
71
|
+
/** A closed-end fund's NAV is a month end's (a sponsor's estimate, or the filing's period end): its normal cadence, not a lag. */
|
|
72
|
+
const monthEndNav = (facts: FundFactsPayload) => facts.nav?.period === "month-end";
|
|
73
|
+
|
|
74
|
+
/** The session of the close a premium was computed from, when it is before the NAV's date: a month end on a weekend. */
|
|
75
|
+
const closeSession = (facts: FundFactsPayload) => {
|
|
76
|
+
const close = facts.closingPrice?.asOf;
|
|
77
|
+
const premium = facts.premiumDiscount?.asOf;
|
|
78
|
+
return close && premium && close < premium ? close : null;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** A registered closed-end fund or a BDC: its NAV is a month end's or a filing's, and its price trades at its own premium to it. */
|
|
82
|
+
const closedEnd = (structure: FundFactsPayload["structure"]) => structure === "closed-end" || structure === "bdc";
|
|
83
|
+
|
|
84
|
+
/** Each structure by its own name in a report; one a newer server adds, by its own key rather than another's name. */
|
|
85
|
+
const STRUCTURE_LABELS: Record<FundStructure, string> = {
|
|
86
|
+
registered: "Registered fund",
|
|
87
|
+
trust: "Trust or pool, not an open-end fund",
|
|
88
|
+
"closed-end": "Closed-end fund",
|
|
89
|
+
bdc: "Business development company (BDC)",
|
|
90
|
+
};
|
|
91
|
+
const structureLabel = (structure: FundStructure) => STRUCTURE_LABELS[structure] ?? String(structure);
|
|
92
|
+
|
|
93
|
+
/** The pane's shorter names, for the funds whose figures read unlike an ETF's. */
|
|
94
|
+
const PANE_STRUCTURE: Partial<Record<FundStructure, string>> = { "closed-end": "Closed-end fund", bdc: "BDC" };
|
|
95
|
+
|
|
83
96
|
/** Ounces and tonnes as the sponsor publishes them, to its own decimals (34,085,936.5; 1,060.18). */
|
|
84
97
|
const metalFigure = (value: number, signed = false) =>
|
|
85
98
|
`${signed && value > 0 ? "+" : ""}${value.toLocaleString("en-US", { maximumFractionDigits: 3 })}`;
|
|
@@ -156,9 +169,19 @@ function datedFigures(facts: FundFactsPayload): Record<"nav" | "premium" | "fee"
|
|
|
156
169
|
const prospectus = facts.expenseRatio?.document === "prospectus";
|
|
157
170
|
const nport = assets?.document === "N-PORT";
|
|
158
171
|
return {
|
|
159
|
-
nav: {
|
|
172
|
+
nav: {
|
|
173
|
+
detail: nav
|
|
174
|
+
? facts.stale ? tf("{date} stale", { date: nav }) : monthEndNav(facts) ? tf("{date} month-end", { date: nav }) : nav
|
|
175
|
+
: undefined,
|
|
176
|
+
marked: facts.stale,
|
|
177
|
+
},
|
|
160
178
|
premium: {
|
|
161
|
-
|
|
179
|
+
// A computed premium is dated by its close's session (a weekend month end's Friday), and says when it is an estimate's.
|
|
180
|
+
detail: premium?.asOf
|
|
181
|
+
? premium.basis === "computed"
|
|
182
|
+
? `${tf("{date} close", { date: closeSession(facts) ?? premium.asOf })}${premium.estimate ? ` · ${t("est.")}` : ""}`
|
|
183
|
+
: premium.asOf
|
|
184
|
+
: undefined,
|
|
162
185
|
marked: facts.stale || (!!premium?.asOf && !!nav && premium.asOf !== nav),
|
|
163
186
|
},
|
|
164
187
|
fee: {
|
|
@@ -192,10 +215,29 @@ export function fundFactItems(facts: FundFactsPayload): StatItem[] {
|
|
|
192
215
|
const cell = (id: string, label: string, value: string, figure: DatedFigure): StatItem => ({
|
|
193
216
|
id, label, value, ...(figure.detail ? { detail: figure.detail } : {}), ...(figure.marked ? { tone: "warning" as const } : {}),
|
|
194
217
|
});
|
|
195
|
-
if (facts.nav)
|
|
218
|
+
if (facts.nav) {
|
|
219
|
+
// A sponsor's estimate is a range: its midpoint is the figure, the range follows its date.
|
|
220
|
+
const estimate = facts.nav.estimate;
|
|
221
|
+
const nav = estimate && dated.nav.detail
|
|
222
|
+
? { ...dated.nav, detail: `${dated.nav.detail} · ${tf("est. {range}", { range: estimateRange(estimate) })}` } : dated.nav;
|
|
223
|
+
items.push(cell("nav", "NAV", formatNav(facts.nav.value), nav));
|
|
224
|
+
}
|
|
196
225
|
if (facts.premiumDiscount) items.push(cell("premium", "Premium/discount", formatPremiumPercent(facts.premiumDiscount.value), dated.premium));
|
|
197
226
|
if (facts.expenseRatio) items.push(cell("fee", expenseRatioLabel(facts), formatFeePercent(facts.expenseRatio.value), dated.fee));
|
|
198
227
|
if (facts.netAssets) items.push(cell("assets", wholeFund(facts) ? "Fund net assets" : "Net assets", formatNetAssets(facts.netAssets.value), dated.netAssets));
|
|
228
|
+
// Shares outstanding as the sponsor states them, then the month's creations less redemptions.
|
|
229
|
+
if (facts.sharesOutstanding) {
|
|
230
|
+
items.push(cell("shares", "Shares outstanding", formatCompact(facts.sharesOutstanding.value),
|
|
231
|
+
{ detail: facts.sharesOutstanding.asOf ?? undefined, marked: facts.stale }));
|
|
232
|
+
}
|
|
233
|
+
if (facts.flows) {
|
|
234
|
+
// In dollars with the shares beside them; in shares alone when a day had no NAV to value them at.
|
|
235
|
+
const flows = facts.flows;
|
|
236
|
+
const shares = tf("{count} shares", { count: signedCompact(flows.shares) });
|
|
237
|
+
const since = tf("since {date}", { date: flows.from });
|
|
238
|
+
items.push(cell("flows", "1M flows", flows.value === null ? shares : flowsValue(flows.value),
|
|
239
|
+
{ detail: flows.value === null ? since : `${shares} · ${since}`, marked: facts.stale }));
|
|
240
|
+
}
|
|
199
241
|
// A bond fund's yields and duration, each dated by its sponsor.
|
|
200
242
|
for (const figure of facts.bond ? bondFigures(facts.bond) : []) {
|
|
201
243
|
items.push(cell(figure.key, figure.label, figure.text, { detail: figure.date, marked: facts.stale }));
|
|
@@ -212,6 +254,9 @@ export function fundFactItems(facts: FundFactsPayload): StatItem[] {
|
|
|
212
254
|
{ detail: metal.change.tonnes == null ? tf("since {date}", { date: metal.change.since }) : `${change} · ${tf("since {date}", { date: metal.change.since })}`, marked: metal.stale }));
|
|
213
255
|
}
|
|
214
256
|
}
|
|
257
|
+
// A closed-end fund or BDC says which: its figures read unlike an ETF's.
|
|
258
|
+
const structure = facts.structure ? PANE_STRUCTURE[facts.structure] : undefined;
|
|
259
|
+
if (structure) items.push({ id: "structure", label: "Structure", value: t(structure) });
|
|
215
260
|
if (facts.sponsor) items.push({ id: "sponsor", label: "Sponsor", value: facts.sponsor.value });
|
|
216
261
|
if (facts.inception) items.push({ id: "inception", label: "Inception", value: facts.inception.value });
|
|
217
262
|
// A leveraged or inverse fund's objective, dated by the sponsor page it was read from, beside its benchmark.
|
|
@@ -276,19 +321,23 @@ export function fundFactEntries(facts: FundFactsPayload): HeadlessPaneEntry[] {
|
|
|
276
321
|
const add = (key: string, label: string, value: unknown, formatted: string, unit?: string) =>
|
|
277
322
|
entries.push({ key, label, value, formatted, ...(unit ? { unit } : {}) });
|
|
278
323
|
if (facts.name) add("name", "Name", facts.name, facts.name);
|
|
279
|
-
if (facts.structure) add("structure", "Structure", facts.structure,
|
|
280
|
-
facts.structure === "trust" ? "Trust or pool, not an open-end fund" : "Registered fund");
|
|
324
|
+
if (facts.structure) add("structure", "Structure", facts.structure, structureLabel(facts.structure));
|
|
281
325
|
if (facts.expenseRatio) {
|
|
282
326
|
const kind = feeBasis(facts);
|
|
283
327
|
const date = facts.expenseRatio.asOf && facts.expenseRatio.document === "prospectus" ? `${facts.expenseRatio.asOf} prospectus` : facts.expenseRatio.asOf;
|
|
284
328
|
add("expenseRatioPercent", expenseRatioLabel(facts), facts.expenseRatio.value,
|
|
285
329
|
provenance(`${formatFeePercent(facts.expenseRatio.value)}${kind ? ` ${kind}` : ""}`, date, facts.expenseRatio.source), "%");
|
|
286
330
|
}
|
|
287
|
-
if (facts.nav)
|
|
331
|
+
if (facts.nav) {
|
|
332
|
+
const date = facts.nav.asOf && monthEndNav(facts) ? `${facts.nav.asOf} month-end` : facts.nav.asOf;
|
|
333
|
+
const estimate = facts.nav.estimate ? `estimate ${estimateRange(facts.nav.estimate)}` : null;
|
|
334
|
+
add("nav", "NAV", facts.nav.value, provenance(formatNav(facts.nav.value), date, estimate, facts.nav.source), "USD");
|
|
335
|
+
}
|
|
288
336
|
if (facts.closingPrice) add("closingPrice", "Closing price", facts.closingPrice.value,
|
|
289
337
|
provenance(formatNav(facts.closingPrice.value), facts.closingPrice.asOf, facts.closingPrice.source), "USD");
|
|
290
338
|
if (facts.premiumDiscount) {
|
|
291
|
-
const basis = facts.premiumDiscount.basis
|
|
339
|
+
const basis = facts.premiumDiscount.basis !== "computed" ? null
|
|
340
|
+
: facts.premiumDiscount.estimate ? "closing price vs NAV estimate midpoint" : "closing price vs NAV";
|
|
292
341
|
add("premiumDiscountPercent", "Premium/discount", facts.premiumDiscount.value,
|
|
293
342
|
provenance(formatPremiumPercent(facts.premiumDiscount.value), facts.premiumDiscount.asOf, basis, facts.premiumDiscount.source), "%");
|
|
294
343
|
}
|
|
@@ -298,6 +347,16 @@ export function fundFactEntries(facts: FundFactsPayload): HeadlessPaneEntry[] {
|
|
|
298
347
|
? `${facts.netAssets.asOf} month-end${facts.netAssets.document ? ` ${facts.netAssets.document}` : ""}` : facts.netAssets.asOf;
|
|
299
348
|
add("netAssets", "Net assets", facts.netAssets.value, provenance(formatNetAssets(facts.netAssets.value), date, scope, facts.netAssets.source), "USD");
|
|
300
349
|
}
|
|
350
|
+
if (facts.sharesOutstanding) {
|
|
351
|
+
add("sharesOutstanding", "Shares outstanding", facts.sharesOutstanding.value,
|
|
352
|
+
provenance(facts.sharesOutstanding.value.toLocaleString("en-US"), facts.sharesOutstanding.asOf, facts.sharesOutstanding.source), "shares");
|
|
353
|
+
}
|
|
354
|
+
if (facts.flows) {
|
|
355
|
+
// JSON keeps the dollar figure (null when a day had no NAV); `fundFacts.flows` has the shares.
|
|
356
|
+
const flows = facts.flows;
|
|
357
|
+
add("flows1M", "1M flows", flows.value, provenance(flows.value === null ? null : flowsValue(flows.value),
|
|
358
|
+
`${signedCompact(flows.shares)} shares`, `${flows.from} to ${flows.asOf}`, flows.source), "USD");
|
|
359
|
+
}
|
|
301
360
|
const metal = facts.metalHoldings;
|
|
302
361
|
if (metal) {
|
|
303
362
|
// The trust's own figures, as its sponsor's daily holdings file states them; JSON has the tonnes in `fundFacts`.
|
|
@@ -323,7 +382,8 @@ export function fundFactEntries(facts: FundFactsPayload): HeadlessPaneEntry[] {
|
|
|
323
382
|
}
|
|
324
383
|
if (facts.index) add("index", BENCHMARK_LABEL, facts.index.value, provenance(facts.index.value, facts.index.source));
|
|
325
384
|
const url = facts.nav?.url ?? facts.expenseRatio?.url ?? facts.netAssets?.url;
|
|
326
|
-
|
|
385
|
+
// The figures' own page: the sponsor's, or the SEC filing they were read from (a prospectus, an N-PORT, an 8-K).
|
|
386
|
+
if (url) add("url", /^https:\/\/www\.sec\.gov\//.test(url) ? "SEC filing" : "Sponsor page", url, url);
|
|
327
387
|
return entries;
|
|
328
388
|
}
|
|
329
389
|
|
|
@@ -338,7 +398,7 @@ export function listNames(names: readonly string[]): string {
|
|
|
338
398
|
* the consolidated close cites Gloom Cloud on its own line, not here.
|
|
339
399
|
*/
|
|
340
400
|
export function fundFactPublishers(facts: FundFactsPayload): string[] {
|
|
341
|
-
return [...new Set([...[facts.nav, facts.expenseRatio, facts.netAssets, facts.leverage, facts.index, facts.bond?.secYield, facts.bond?.yieldToWorst, facts.bond?.duration]
|
|
401
|
+
return [...new Set([...[facts.nav, facts.expenseRatio, facts.netAssets, facts.sharesOutstanding, facts.flows, facts.leverage, facts.index, facts.bond?.secYield, facts.bond?.yieldToWorst, facts.bond?.duration]
|
|
342
402
|
.flatMap((value) => value ? [value.source] : []), ...facts.metalHoldings ? [facts.metalHoldings.source.publisher] : []])];
|
|
343
403
|
}
|
|
344
404
|
|
|
@@ -352,6 +412,25 @@ export function fundFactsStaleText(symbol: string, facts: FundFactsPayload): str
|
|
|
352
412
|
return fundFactsStaleNote(symbol, facts) ? tf("{symbol}'s newest NAV is from {date}; its sponsor has not published one since.", { symbol, date: facts.nav!.asOf! }) : null;
|
|
353
413
|
}
|
|
354
414
|
|
|
415
|
+
/**
|
|
416
|
+
* Why a covered fund has no NAV, in one line, translated for the pane: what
|
|
417
|
+
* Gloom reads for it and what that leaves out, as the server's `navReason`
|
|
418
|
+
* says it. Null when it has a NAV, and from servers before the reason.
|
|
419
|
+
*/
|
|
420
|
+
export function fundFactsNavReasonText(symbol: string, facts: FundFactsPayload): string | null {
|
|
421
|
+
if (!facts.covered || facts.nav || !facts.navReason) return null;
|
|
422
|
+
// A sponsor that publishes the fund's other figures.
|
|
423
|
+
const sponsor = fundFactPublishers(facts).find((publisher) => publisher !== "SEC EDGAR");
|
|
424
|
+
if (sponsor) return tf("No NAV for {symbol}: {publisher} publishes figures for it, but no NAV.", { symbol, publisher: sponsor });
|
|
425
|
+
if (closedEnd(facts.structure)) {
|
|
426
|
+
return tf("No NAV for {symbol}: its SEC filings state net assets but no NAV per share, and its sponsor's NAV is not one Gloom reads.", { symbol });
|
|
427
|
+
}
|
|
428
|
+
if (facts.structure === "trust") {
|
|
429
|
+
return tf("No NAV for {symbol}: its sponsor's site is not one Gloom reads, and a trust files no NAV with the SEC's fund data sets.", { symbol });
|
|
430
|
+
}
|
|
431
|
+
return tf("No NAV for {symbol}: its sponsor's site is not one Gloom reads, and the SEC's prospectus and N-PORT data sets state no NAV.", { symbol });
|
|
432
|
+
}
|
|
433
|
+
|
|
355
434
|
/**
|
|
356
435
|
* Why a fund has no facts, in one line, and where to look instead: its SEC
|
|
357
436
|
* filings, unless the caller already points there.
|
|
@@ -376,6 +455,9 @@ export function fundFactsEmptyText(symbol: string, facts: FundFactsPayload | nul
|
|
|
376
455
|
if (facts.structure === "registered") {
|
|
377
456
|
return tf("No figures for {symbol}: its sponsor's site is not one Gloom reads, and the SEC's prospectus and N-PORT data sets list none for it yet.", { symbol });
|
|
378
457
|
}
|
|
458
|
+
if (closedEnd(facts.structure)) {
|
|
459
|
+
return tf("No figures for {symbol}: it files with the SEC as a closed-end fund or BDC, but neither the filings Gloom reads nor its sponsor's site state any for it yet.", { symbol });
|
|
460
|
+
}
|
|
379
461
|
return tf("No figures for {symbol}: it lists as a fund, but the SEC's lists do not name it yet and its sponsor's site is not one Gloom reads.", { symbol });
|
|
380
462
|
}
|
|
381
463
|
if (facts.status === "not-a-fund") return tf("No fund facts for {symbol}: it is not on the SEC's lists of US-listed funds.", { symbol });
|
|
@@ -268,7 +268,8 @@ export function createFundHeadless(dependencies: FundHeadlessDependencies = defa
|
|
|
268
268
|
...(factsResult.status === "rejected" ? [fundFactsErrorText(factsResult.reason)] : []),
|
|
269
269
|
...(filingsResult.status === "rejected" ? [`Fund filings: ${errorMessage(filingsResult.reason)}`] : []),
|
|
270
270
|
],
|
|
271
|
-
|
|
271
|
+
// A stale NAV says since when; a covered fund without one says why, as the server words it.
|
|
272
|
+
notes: facts ? [fundFactsStaleNote(symbol, facts), facts.covered && !facts.nav ? facts.navReason : null].filter((note): note is string => !!note) : [],
|
|
272
273
|
// The facts' publisher is the source, named the same way for every fund; without facts it is the SEC.
|
|
273
274
|
freshness: {
|
|
274
275
|
source: listNames(publishers) || "SEC EDGAR",
|
|
@@ -64,9 +64,11 @@ export const SERIES_INVENTORY: readonly SeriesInventoryEntry[] = [
|
|
|
64
64
|
{ module: "perps", chart: "Perpetual funding, open interest, premium and long/short account share", series: ["perps:*"] },
|
|
65
65
|
{ module: "crypto-etf-flows", chart: "The running net flow of the US spot bitcoin or ether ETFs since launch", notInCatalog: "not-yet", reason: "Daily and dated: the running total and each fund's daily flow could be crypto ETF series, and none is registered for the composer yet." },
|
|
66
66
|
{ module: "perps", chart: "Perpetual mark price", notInCatalog: "not-yet", reason: "The pair's price is in the catalog; the perpetual's own mark price could be a crypto series." },
|
|
67
|
+
{ module: "treasury-daily", chart: "The Treasury General Account's daily closing balance", notInCatalog: "not-yet", reason: "Daily and dated, from the Daily Treasury Statement's operating cash balance table; the pane reads it with the day's cash lines, and none is registered for the composer yet." },
|
|
67
68
|
{ module: "primary-dealers", chart: "A primary dealer position's weekly net level, the sum of the release's series behind the row", notInCatalog: "not-yet", reason: "Weekly and dated, one series per row of the board; the pane reads each only when its row is selected, and none is registered for the composer yet." },
|
|
68
69
|
{ module: "short-interest", chart: "A ticker's short interest and days to cover", series: ["short-interest:*"] },
|
|
69
70
|
{ module: "short-volume", chart: "A ticker's daily short volume ratio", series: ["short-volume:*"] },
|
|
71
|
+
{ module: "scanner", chart: "Net options premium through the session, for the market or one ticker (FLOW's Tide)", notInCatalog: "per-item", reason: "One per ticker, filter set and session, summed on request from the recorded option prints." },
|
|
70
72
|
{ module: "social-mentions", chart: "A ticker's daily posts", series: ["social-mentions:*"] },
|
|
71
73
|
{ module: "jobs", chart: "A company's open job postings", series: ["jobs:*"] },
|
|
72
74
|
{ module: "attention", chart: "Research hours on a ticker, hour by hour", series: ["attention:*"] },
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
1
2
|
import { ApiRequestError, isAccessDenied } from "../../../api-client/errors";
|
|
2
3
|
import type { PluginCacheResult } from "../../../data/plugin-cache";
|
|
3
4
|
|
|
@@ -26,8 +27,8 @@ export function cachedCloudResource<T>(
|
|
|
26
27
|
if (!cached) return null;
|
|
27
28
|
try {
|
|
28
29
|
return { payload: validate(cached.data), stale: false, refreshError: null };
|
|
29
|
-
} catch {
|
|
30
|
-
return null;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
return logFailure("shared.cloud-resource", "Validate cached cloud payload failed", null)(error);
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
1
2
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
3
|
import { apiClient } from "../../../api-client";
|
|
3
4
|
import { recordResearchActivity, researchUpgradeUrl } from "../../../api-client/research-activity";
|
|
@@ -159,7 +160,7 @@ async function openCheckoutWithHandoff(
|
|
|
159
160
|
.then(({ url, link }): CheckoutHandoffTarget => ({
|
|
160
161
|
url,
|
|
161
162
|
link: link ?? null,
|
|
162
|
-
opened: rendererHost.openExternal(url).then((result) => result !== false, (
|
|
163
|
+
opened: rendererHost.openExternal(url).then((result) => result !== false, logFailure("shared.cloud-upgrade", "Open checkout in the browser failed", false)),
|
|
163
164
|
}));
|
|
164
165
|
void promptCheckoutHandoff(dialog, target);
|
|
165
166
|
await (await target).opened;
|
|
@@ -221,10 +222,10 @@ export function useCloudUpgradeAction(placement: string): (options?: unknown) =>
|
|
|
221
222
|
openCloudUpgradePage("cloud", async () => {
|
|
222
223
|
const url = await apiClient.createBrowserHandoff()
|
|
223
224
|
.then((handoff) => handoff.url)
|
|
224
|
-
.catch((
|
|
225
|
+
.catch(logFailure("shared.cloud-upgrade", "Create Cloud page handoff failed", CLOUD_UPGRADE_URL));
|
|
225
226
|
await rendererHost.openExternal(url);
|
|
226
227
|
})
|
|
227
|
-
)).catch((
|
|
228
|
+
)).catch(logFailure("shared.cloud-upgrade", "Open Cloud upgrade page failed"));
|
|
228
229
|
};
|
|
229
230
|
if (
|
|
230
231
|
checkout.sheet === false ||
|
|
@@ -55,7 +55,7 @@ function calendarDateWins(calendar: EarningsDateStatus | null, other: EarningsDa
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/** The calendar's report as a quarter on record takes it. */
|
|
58
|
-
interface CalendarReport {
|
|
58
|
+
export interface CalendarReport {
|
|
59
59
|
date: string;
|
|
60
60
|
timing: "bmo" | "amc" | null;
|
|
61
61
|
dateStatus: EarningsDateStatus | null;
|
|
@@ -86,7 +86,11 @@ function consensusIsReports(event: EarningsEvent, dateStatus: EarningsDateStatus
|
|
|
86
86
|
return periodEnd != null && periodEnd < date;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
/**
|
|
90
|
+
* A calendar event read as its report, as every surface reads it: reported
|
|
91
|
+
* once out, and with a consensus only when it is that report's.
|
|
92
|
+
*/
|
|
93
|
+
export function calendarReport(event: EarningsEvent, today: string): CalendarReport {
|
|
90
94
|
const date = calendarDay(event);
|
|
91
95
|
const dateStatus = calendarStatus(event, today);
|
|
92
96
|
const ownConsensus = consensusIsReports(event, dateStatus, date);
|
|
@@ -54,8 +54,8 @@ export function epsBasisShort(basis: EarningsEpsBasis | null): string {
|
|
|
54
54
|
return basis === "gaap" ? "GAAP" : basis === "adjusted" ? "adj" : "?";
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
/**
|
|
58
|
-
const BASIS_TEXT: Record<EpsBasisName, string> = { GAAP: "GAAP", adjusted: "adj", unknown: "" };
|
|
57
|
+
/** A reported row's detail names its basis as ERN's Basis column does, unknown included. */
|
|
58
|
+
const BASIS_TEXT: Record<EpsBasisName, string> = { GAAP: "GAAP", adjusted: "adj", unknown: "unknown" };
|
|
59
59
|
|
|
60
60
|
type EarningsRecord = CorporateActionsData["earnings"][number];
|
|
61
61
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
1
2
|
import { ChoiceDialog, TextPromptDialog, usePaneMenuItems } from "../../../components";
|
|
2
3
|
import { createFormCollectionActions } from "../../../components/form-modal/deps";
|
|
3
4
|
import { t, tf } from "../../../i18n";
|
|
@@ -195,7 +196,7 @@ export function useMembersMenu(registrationId: string, list: MemberList | null)
|
|
|
195
196
|
const choice = choices.find((item) => item.id === templateId && !item.disabled);
|
|
196
197
|
// CORR and RIPL resolve `arg` like the command bar; RRG and SIW read `symbols`.
|
|
197
198
|
if (choice) createPaneFromTemplate(choice.id, { symbols: choice.symbols, arg: formatTickerListInput(choice.symbols) });
|
|
198
|
-
}).catch((
|
|
199
|
+
}).catch(logFailure("shared.members-menu", "Open members in a function failed"));
|
|
199
200
|
};
|
|
200
201
|
const saveAsWatchlist = async (requested: string) => {
|
|
201
202
|
// The writes hold the app for about a second with a hundred names; let the dialog close on screen first.
|
|
@@ -253,7 +254,7 @@ export function useMembersMenu(registrationId: string, list: MemberList | null)
|
|
|
253
254
|
saveAsWatchlist(name).catch((error: unknown) => {
|
|
254
255
|
notify({ type: "error", body: error instanceof Error ? error.message : t("The watchlist was not saved.") });
|
|
255
256
|
});
|
|
256
|
-
}).catch((
|
|
257
|
+
}).catch(logFailure("shared.members-menu", "Open save members as watchlist prompt failed"));
|
|
257
258
|
} },
|
|
258
259
|
];
|
|
259
260
|
}, [createPaneFromTemplate, dialog, disabledPlugins, dispatch, getState, list, notify]);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createTtlCache, type TtlCache } from "../../../data/ttl-cache";
|
|
1
2
|
import type { HeadlessPaneContext } from "../../../types/headless";
|
|
2
3
|
import type { PaneLock } from "../../../types/plugin";
|
|
3
4
|
import { settleWithin, signalWithTimeout } from "../../../utils/async-deadline";
|
|
@@ -35,8 +36,7 @@ const ANSWER_TTL_MS = 5 * 60_000;
|
|
|
35
36
|
*/
|
|
36
37
|
const ROUTE_CHECK_WAIT_MS = 3_000;
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
const answers = new WeakMap<FreeRoute, Map<string, KeptAnswer>>();
|
|
39
|
+
const answers = new WeakMap<FreeRoute, TtlCache<boolean>>();
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* Whether the route has data for the symbol, null when its check failed or was
|
|
@@ -45,20 +45,20 @@ const answers = new WeakMap<FreeRoute, Map<string, KeptAnswer>>();
|
|
|
45
45
|
* rather than hiding the route for minutes over one slow read.
|
|
46
46
|
*/
|
|
47
47
|
async function routeAnswers(route: FreeRoute, check: NonNullable<FreeRoute["answers"]>, symbol: string, context: HeadlessPaneContext, deadline: number): Promise<boolean | null> {
|
|
48
|
-
const bySymbol = answers.get(route) ??
|
|
48
|
+
const bySymbol = answers.get(route) ?? createTtlCache<boolean>({ ttlMs: ANSWER_TTL_MS });
|
|
49
49
|
answers.set(route, bySymbol);
|
|
50
50
|
const key = symbol.trim().toUpperCase();
|
|
51
|
-
|
|
52
|
-
if (!kept || Date.now() - kept.at >= ANSWER_TTL_MS) {
|
|
51
|
+
const kept = bySymbol.load(key, () => {
|
|
53
52
|
// The check's own reads stop at the deadline where they take a signal.
|
|
54
53
|
const signal = signalWithTimeout(context.signal, Math.max(0, deadline - Date.now()));
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
const answer = await settleWithin(kept
|
|
61
|
-
|
|
54
|
+
return check(symbol, { ...context, signal }).catch((error: unknown) => {
|
|
55
|
+
logFailure("plugins.pro-lock-note", "Check a free route's data failed")(error);
|
|
56
|
+
throw error;
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
const answer = await settleWithin(kept, Math.max(0, deadline - Date.now()));
|
|
60
|
+
// Only this check is dropped: a newer one for the symbol stays.
|
|
61
|
+
if (answer === null) bySymbol.delete(key, kept);
|
|
62
62
|
return answer;
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -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";
|
|
@@ -129,8 +130,8 @@ export async function cloudRealtimeAccess(): Promise<boolean | null> {
|
|
|
129
130
|
if (!apiClient.isSignedIn()) return false;
|
|
130
131
|
try {
|
|
131
132
|
return hasProAccess(apiClient.getCurrentUser() ?? await apiClient.getSession());
|
|
132
|
-
} catch {
|
|
133
|
-
return null;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return logFailure("shared.report-freshness", "Read Cloud plan for report freshness failed", null)(error);
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
|
|
@@ -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. */
|