@gloom-sh/terminal 0.19.1 → 0.19.3
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 +7 -1
- package/package.json +2 -1
- package/patches/electrobun@1.18.1.patch +108 -1
- package/src/account/upgrade-dialog.tsx +4 -23
- package/src/api-client/coalesce.ts +118 -0
- package/src/api-client/credit-boards.ts +13 -0
- package/src/api-client/crypto-etf-flows.ts +6 -0
- package/src/api-client/crypto-markets.ts +40 -0
- package/src/api-client/data.ts +201 -14
- package/src/api-client/equity-screener.ts +10 -0
- package/src/api-client/errors.ts +58 -0
- package/src/api-client/gpu.ts +29 -0
- package/src/api-client/index.ts +5 -2
- package/src/api-client/plan-access.ts +10 -1
- package/src/api-client/plan-rules.ts +35 -0
- package/src/api-client/research-activity.ts +1 -32
- package/src/api-client/types.ts +21 -1
- package/src/app/attention-notice.tsx +94 -0
- package/src/app/runtime/startup.ts +3 -1
- package/src/app.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +2 -1
- package/src/capabilities/chart-series.ts +2 -1
- package/src/capabilities/factories.ts +1 -0
- package/src/cli/ascii-output.ts +42 -0
- package/src/cli/base-converter.ts +10 -2
- package/src/cli/command-options.ts +16 -1
- package/src/cli/commands/crypto-hints.ts +23 -4
- package/src/cli/commands/market.ts +101 -27
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +14 -10
- package/src/cli/commands/search.ts +84 -25
- package/src/cli/commands/system.ts +22 -30
- package/src/cli/commands/ticker.ts +49 -9
- package/src/cli/commands/version.ts +33 -0
- package/src/cli/context.ts +6 -9
- package/src/cli/data-dir.ts +7 -0
- package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
- package/src/cli/desktop-pane-shot-browser.ts +320 -0
- package/src/cli/desktop-pane-shot-routes.ts +26 -1
- package/src/cli/desktop-pane-shot.ts +196 -256
- package/src/cli/early-command.ts +42 -0
- package/src/cli/earnings-calendar.ts +9 -0
- package/src/cli/entry.ts +19 -13
- package/src/cli/errors.ts +12 -2
- package/src/cli/glossary.ts +2 -1
- package/src/cli/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +189 -11
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +51 -5
- package/src/cli/listing-arg.ts +63 -10
- package/src/cli/options.ts +1 -1
- package/src/cli/output-file.ts +60 -13
- package/src/cli/pane-functions/catalog.ts +14 -3
- package/src/cli/pane-functions/data.ts +1 -1
- package/src/cli/pane-functions/dom.ts +45 -0
- package/src/cli/pane-functions/freshness.ts +46 -9
- package/src/cli/pane-functions/function-help.ts +17 -9
- package/src/cli/pane-functions/headless.ts +22 -14
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +6 -2
- package/src/cli/pane-functions/resolver.ts +27 -5
- package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
- package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
- package/src/cli/pane-functions/screenshot-http.ts +70 -0
- package/src/cli/pane-functions/screenshot-render.ts +19 -5
- package/src/cli/quote-answer.ts +49 -6
- package/src/cli/registry.ts +41 -13
- package/src/cli/result.ts +12 -6
- package/src/cli/ticker-resolution.ts +2 -1
- package/src/cli/types.ts +2 -0
- package/src/components/chart/composite/format.ts +3 -0
- package/src/components/chart-table/header.tsx +1 -1
- package/src/components/chart-table/layout.ts +28 -11
- package/src/components/command-bar/action-runtime.ts +4 -0
- package/src/components/command-bar/commands/direct/index.ts +18 -33
- package/src/components/command-bar/commands/direct/runtime.ts +5 -0
- package/src/components/command-bar/commands/registry.ts +10 -1
- package/src/components/command-bar/routes/root/command-items.ts +9 -4
- package/src/components/command-bar/routes/root/runtime.ts +51 -6
- package/src/components/command-bar/selection-runtime.ts +12 -29
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/header.tsx +10 -5
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +8 -2
- package/src/components/layout/pane/footer/model.ts +73 -6
- package/src/components/layout/pane/header.tsx +2 -2
- package/src/components/layout/pane/status-footer.ts +2 -1
- package/src/components/market-board/index.tsx +3 -2
- package/src/components/onboarding/desks-step.tsx +12 -4
- package/src/components/onboarding/first-run-workspace.ts +81 -4
- package/src/components/onboarding/onboarding-frame.tsx +4 -2
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +60 -26
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
- package/src/components/onboarding/run-setup-again.ts +24 -0
- package/src/components/onboarding/use-onboarding-progress.ts +33 -15
- package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
- package/src/components/onboarding/wizard-model.ts +57 -3
- package/src/components/onboarding/wizard-positions.ts +1 -1
- package/src/components/ui/delayed-mark.tsx +56 -0
- package/src/components/ui/display.tsx +16 -5
- package/src/components/ui/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +16 -3
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/core/app-services.ts +11 -2
- package/src/data/app-persistence.ts +9 -7
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/plugin-cache.ts +15 -0
- package/src/data/plugin-state-store.ts +81 -24
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +165 -90
- package/src/data/session-store.ts +31 -8
- package/src/data/sqlite/access.ts +324 -0
- package/src/data/sqlite/checkpointer.ts +285 -0
- package/src/data/sqlite/database.ts +80 -4
- package/src/data/sqlite/retry.ts +50 -9
- package/src/data/ticker-store.ts +57 -23
- package/src/docs/snapshot.json +191 -78
- 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/layout/desk-keys.ts +4 -1
- package/src/layout/desks.ts +38 -5
- package/src/layout-marketplace/gallery-desktop.tsx +45 -16
- package/src/market-data/batched-reads.ts +16 -0
- package/src/market-data/coordinator/auxiliary.ts +4 -2
- package/src/market-data/coordinator/index.ts +11 -2
- package/src/market-data/futures-board.ts +1 -1
- package/src/market-data/futures-contract.ts +3 -1
- package/src/market-data/market/index-level.ts +6 -4
- package/src/market-data/published-apac-sessions.ts +6 -0
- package/src/market-data/realized-volatility.ts +4 -2
- package/src/news/aggregator.ts +7 -4
- package/src/news/news-model.ts +11 -0
- package/src/news/story-id.ts +22 -0
- package/src/news/ticker-symbols.ts +17 -1
- package/src/notifications/app-notifier.ts +35 -14
- package/src/plugins/auto-update-node.ts +10 -6
- package/src/plugins/builtin/account-management/form-components.tsx +1 -1
- package/src/plugins/builtin/account-management/index.tsx +2 -3
- package/src/plugins/builtin/account-management/model.ts +1 -1
- package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
- package/src/plugins/builtin/alerts/events.ts +10 -2
- package/src/plugins/builtin/alerts/index.tsx +5 -5
- package/src/plugins/builtin/alerts/research-builder.ts +5 -1
- package/src/plugins/builtin/alerts/research-rules.ts +18 -0
- package/src/plugins/builtin/alerts/targets.ts +4 -1
- package/src/plugins/builtin/attention/headless.ts +1 -1
- package/src/plugins/builtin/attention/index.tsx +2 -4
- package/src/plugins/builtin/attention/listing.ts +1 -1
- package/src/plugins/builtin/attention/series.ts +1 -1
- package/src/plugins/builtin/awards/index.tsx +1 -2
- package/src/plugins/builtin/backtest/client.ts +1 -0
- package/src/plugins/builtin/backtest/index.tsx +1 -2
- package/src/plugins/builtin/backtest/rules.ts +3 -1
- package/src/plugins/builtin/backtest/window.ts +8 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
- package/src/plugins/builtin/broker-manager/index.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +5 -0
- package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
- package/src/plugins/builtin/buildout/company/id.ts +2 -0
- package/src/plugins/builtin/buildout/company/model.ts +247 -0
- package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
- package/src/plugins/builtin/buildout/facilities.ts +309 -0
- package/src/plugins/builtin/buildout/format.ts +9 -0
- package/src/plugins/builtin/buildout/index.tsx +51 -5
- package/src/plugins/builtin/buildout/model/index.ts +62 -10
- package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
- package/src/plugins/builtin/buildout/model/types.ts +11 -2
- package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
- package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
- package/src/plugins/builtin/buildout/table-model.ts +26 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
- package/src/plugins/builtin/cds/headless.ts +10 -2
- package/src/plugins/builtin/cds/model.ts +7 -3
- package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
- package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
- package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
- package/src/plugins/builtin/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
- package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
- package/src/plugins/builtin/chart-composer/headless.ts +88 -21
- package/src/plugins/builtin/chart-composer/index.tsx +2 -3
- package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
- package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/controller/index.ts +1 -1
- package/src/plugins/builtin/chat/controller/messages.ts +15 -4
- package/src/plugins/builtin/chat/ghost-user.ts +8 -0
- package/src/plugins/builtin/chat/index.tsx +5 -5
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/index.tsx +1 -2
- package/src/plugins/builtin/cloud/plugin.tsx +6 -4
- package/src/plugins/builtin/cloud/team/module.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
- package/src/plugins/builtin/comment-letters/index.tsx +1 -2
- package/src/plugins/builtin/commodity-balances/client.ts +4 -1
- package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
- package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
- package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
- package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
- package/src/plugins/builtin/congress-trades/headless.ts +2 -0
- package/src/plugins/builtin/congress-trades/model.ts +92 -64
- package/src/plugins/builtin/congress-trades/table.tsx +14 -2
- package/src/plugins/builtin/connections/index.ts +2 -3
- package/src/plugins/builtin/cot/client.ts +28 -2
- package/src/plugins/builtin/cot/headless.ts +4 -1
- package/src/plugins/builtin/cot/index.tsx +5 -4
- package/src/plugins/builtin/cot/model.ts +58 -7
- package/src/plugins/builtin/cot/pane.tsx +6 -2
- package/src/plugins/builtin/cpi/client.ts +6 -5
- package/src/plugins/builtin/cpi/index.tsx +1 -2
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/client.ts +7 -2
- package/src/plugins/builtin/credit-boards/headless.ts +25 -12
- package/src/plugins/builtin/credit-boards/model.ts +29 -0
- package/src/plugins/builtin/credit-boards/series.ts +3 -4
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/credit-conditions/client.ts +22 -14
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- package/src/plugins/builtin/crypto-board/client.ts +5 -5
- package/src/plugins/builtin/crypto-board/headless.ts +4 -3
- package/src/plugins/builtin/crypto-board/index.tsx +4 -2
- package/src/plugins/builtin/crypto-board/series.ts +6 -4
- package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
- package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
- package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
- package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
- package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
- package/src/plugins/builtin/debt-maturities/model.ts +17 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
- package/src/plugins/builtin/debt-maturities/series.ts +3 -3
- package/src/plugins/builtin/debug/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/client.ts +32 -2
- package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
- package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
- package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
- package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
- package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
- package/src/plugins/builtin/dividend-yield/view.ts +21 -0
- package/src/plugins/builtin/docs/index.tsx +2 -3
- package/src/plugins/builtin/doe/client.ts +6 -5
- package/src/plugins/builtin/doe/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/doe/prices.ts +2 -3
- package/src/plugins/builtin/doe/series.ts +4 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
- package/src/plugins/builtin/earnings-calls/data.ts +19 -11
- package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
- package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
- package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
- package/src/plugins/builtin/econ/calendar-model.ts +29 -6
- package/src/plugins/builtin/econ/detail-view.tsx +16 -2
- package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +1 -0
- package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
- package/src/plugins/builtin/equity-screener/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/model.ts +28 -4
- package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
- package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
- package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
- package/src/plugins/builtin/executives/data.ts +6 -1
- package/src/plugins/builtin/executives/pane.tsx +9 -21
- package/src/plugins/builtin/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/cache.ts +4 -0
- package/src/plugins/builtin/fear-greed/cli.ts +44 -0
- package/src/plugins/builtin/fear-greed/index.tsx +5 -53
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
- package/src/plugins/builtin/fear-greed/series.ts +6 -4
- package/src/plugins/builtin/fixed-income/index.tsx +1 -2
- package/src/plugins/builtin/futures/board-reads.ts +25 -0
- package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
- package/src/plugins/builtin/futures/index.tsx +15 -0
- package/src/plugins/builtin/futures/model.ts +14 -0
- package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
- package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
- package/src/plugins/builtin/futures-curve/headless.ts +6 -1
- package/src/plugins/builtin/futures-curve/index.tsx +1 -2
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +51 -12
- package/src/plugins/builtin/gpu/client.ts +19 -3
- package/src/plugins/builtin/gpu/equities.tsx +5 -2
- package/src/plugins/builtin/gpu/headless.ts +14 -2
- package/src/plugins/builtin/gpu/index.tsx +1 -2
- package/src/plugins/builtin/gpu/model.ts +20 -2
- package/src/plugins/builtin/gpu/pane.tsx +1 -1
- package/src/plugins/builtin/gpu/series.ts +2 -3
- package/src/plugins/builtin/help/index.tsx +1 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
- package/src/plugins/builtin/holders/index-holders.ts +24 -12
- package/src/plugins/builtin/holders/index.tsx +2 -3
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
- package/src/plugins/builtin/iv-history/index.tsx +1 -2
- package/src/plugins/builtin/jodi/client.ts +4 -1
- package/src/plugins/builtin/jodi/index.tsx +1 -2
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
- package/src/plugins/builtin/layout-manager/index.tsx +9 -8
- package/src/plugins/builtin/macro-day/pane.tsx +4 -2
- package/src/plugins/builtin/market-halts/index.tsx +1 -2
- package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
- package/src/plugins/builtin/market-movers/client.ts +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +16 -5
- package/src/plugins/builtin/market-movers/screener.ts +23 -6
- package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
- package/src/plugins/builtin/market-valuation/index.tsx +1 -2
- package/src/plugins/builtin/members/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/pane.tsx +2 -1
- package/src/plugins/builtin/money-markets/series.ts +3 -5
- package/src/plugins/builtin/news/headless.ts +7 -0
- package/src/plugins/builtin/news/wire/index.tsx +1 -1
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
- package/src/plugins/builtin/notes/index.tsx +3 -6
- package/src/plugins/builtin/openfda/index.tsx +1 -2
- package/src/plugins/builtin/options/view.tsx +6 -5
- package/src/plugins/builtin/options-calculator/index.tsx +1 -2
- package/src/plugins/builtin/options-positioning/index.tsx +1 -2
- package/src/plugins/builtin/options-scenario/index.tsx +2 -4
- package/src/plugins/builtin/perps/index.tsx +1 -2
- package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
- package/src/plugins/builtin/plugin-module.ts +16 -2
- package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
- package/src/plugins/builtin/power/gas-client.ts +2 -1
- package/src/plugins/builtin/power/index.tsx +1 -2
- package/src/plugins/builtin/power/prices-client.ts +3 -2
- package/src/plugins/builtin/rate-path/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/model.ts +16 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
- package/src/plugins/builtin/research/analyst-headless.ts +33 -6
- package/src/plugins/builtin/research/analyst-model.ts +37 -2
- package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
- package/src/plugins/builtin/research/index.tsx +5 -8
- package/src/plugins/builtin/research-search/index.tsx +1 -2
- package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
- package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
- package/src/plugins/builtin/risk-factors/data.ts +8 -4
- package/src/plugins/builtin/risk-factors/index.tsx +3 -1
- package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
- package/src/plugins/builtin/roadmap/index.tsx +2 -3
- package/src/plugins/builtin/roadmap/pane.tsx +49 -2
- package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
- package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
- package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
- package/src/plugins/builtin/scanner/lock-note.ts +26 -3
- package/src/plugins/builtin/sec/filing-content.ts +38 -6
- package/src/plugins/builtin/sec/headless.ts +5 -1
- package/src/plugins/builtin/sec/index.tsx +4 -2
- package/src/plugins/builtin/sec/model.ts +2 -0
- package/src/plugins/builtin/sectors/index.tsx +11 -5
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
- package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
- package/src/plugins/builtin/shared/forming-bars.ts +29 -0
- package/src/plugins/builtin/shared/fred-points.ts +6 -1
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- package/src/plugins/builtin/shared/series-warning.ts +13 -4
- package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
- package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/series.ts +2 -3
- package/src/plugins/builtin/social-mentions/client.ts +15 -10
- package/src/plugins/builtin/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- package/src/plugins/builtin/social-mentions/series.ts +2 -3
- package/src/plugins/builtin/supply-chain/client.ts +22 -1
- package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
- package/src/plugins/builtin/supply-chain/index.tsx +1 -2
- package/src/plugins/builtin/thirteenf/api.ts +127 -29
- package/src/plugins/builtin/thirteenf/data.ts +165 -35
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
- package/src/plugins/builtin/thirteenf/types.ts +14 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
- package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
- package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
- package/src/plugins/builtin/time-sales/index.tsx +1 -2
- package/src/plugins/builtin/time-sales/pane.tsx +3 -1
- package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
- package/src/plugins/builtin/trace-bonds/model.ts +44 -10
- package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
- package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
- package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
- package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
- package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
- package/src/plugins/builtin/vol-surface/client.ts +18 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -2
- package/src/plugins/builtin/volatility/client.ts +43 -16
- package/src/plugins/builtin/volatility/headless.ts +5 -2
- package/src/plugins/builtin/volatility/index.tsx +1 -2
- package/src/plugins/builtin/volatility/model.ts +17 -6
- package/src/plugins/builtin/volatility/pane.tsx +6 -2
- package/src/plugins/builtin/volatility/series.ts +2 -1
- package/src/plugins/builtin/world-indices/footer.ts +6 -1
- package/src/plugins/builtin/world-venue-map/client.ts +10 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -2
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
- package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
- package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
- package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
- package/src/plugins/builtin/yield-curve/client.ts +4 -6
- package/src/plugins/builtin/yield-curve/curves.ts +16 -2
- package/src/plugins/builtin/yield-curve/headless.ts +3 -3
- package/src/plugins/bundle.ts +52 -2
- package/src/plugins/catalog-browser.ts +2 -2
- package/src/plugins/catalog-cli.ts +20 -0
- package/src/plugins/host-link.ts +13 -1
- package/src/plugins/installer.ts +64 -42
- package/src/plugins/loader.ts +43 -11
- package/src/plugins/peer-imports.ts +201 -0
- package/src/plugins/peer-retry.ts +26 -29
- package/src/plugins/plugin-process.ts +128 -0
- package/src/plugins/redirection-guard.ts +45 -0
- package/src/remote/endpoint.ts +16 -3
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/http-proxy.ts +7 -123
- package/src/renderers/dom/desktop/icons.tsx +5 -2
- package/src/renderers/dom/host/image-surface.tsx +46 -21
- package/src/renderers/dom/styles.css +7 -0
- package/src/renderers/dom/toast-host.tsx +2 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
- package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
- package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
- package/src/renderers/electrobun/bun/window/attention.ts +54 -0
- package/src/renderers/electrobun/bun/window/create.ts +6 -2
- package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
- package/src/renderers/electrobun/view/main.tsx +7 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/renderers/electrobun/view/ui-host.tsx +9 -2
- package/src/renderers/opentui/browser-open.ts +10 -4
- package/src/renderers/opentui/image/surface.tsx +1 -1
- package/src/renderers/opentui/start.tsx +12 -6
- package/src/renderers/opentui/toast-host.tsx +2 -1
- package/src/series/registry.ts +18 -0
- package/src/series/resolve.ts +2 -1
- package/src/series/standalone.ts +10 -1
- package/src/series/types.ts +12 -0
- package/src/series/validate.ts +11 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
- package/src/sources/gloomberb-cloud/index.ts +28 -5
- package/src/sources/gloomberb-cloud/news.ts +24 -3
- package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
- package/src/sources/provider-errors.ts +26 -3
- package/src/sources/provider-router/history.ts +5 -0
- package/src/sources/provider-router/index.ts +13 -0
- package/src/sync/core-contributors.ts +32 -0
- package/src/telemetry/attention-counts.ts +64 -6
- package/src/telemetry/attention-setting.ts +33 -0
- package/src/telemetry/crash-reports-node.ts +3 -1
- package/src/telemetry/stall-clocks.ts +93 -0
- package/src/telemetry/stall-reports-node.ts +25 -9
- package/src/telemetry/stall-reports.ts +218 -13
- package/src/test-support/outage.ts +8 -0
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name-listing.ts +59 -29
- package/src/tickers/company-name.ts +84 -1
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +104 -8
- package/src/tickers/search/index.ts +5 -2
- package/src/tickers/search/types.ts +6 -1
- package/src/time-series/bar-aggregation.ts +7 -1
- package/src/time-series/base-series.ts +3 -3
- package/src/time-series/chart-bounds.ts +1 -1
- package/src/time-series/chart-data.ts +5 -2
- package/src/time-series/chart-seed.ts +1 -1
- package/src/time-series/coin-session-close.ts +211 -0
- package/src/time-series/live-bars.ts +13 -2
- package/src/time-series/live-quotes.ts +1 -1
- package/src/time-series/market.ts +5 -3
- package/src/time-series/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +75 -8
- package/src/time-series/resolve.ts +32 -4
- package/src/time-series/series-presentation.ts +1 -1
- package/src/time-series/studies/formula.ts +205 -0
- package/src/time-series/studies/indicators.ts +299 -0
- package/src/time-series/studies/leverage.ts +75 -0
- package/src/time-series/studies/pair.ts +138 -0
- package/src/time-series/studies/resolve.ts +333 -0
- package/src/time-series/studies/shared.ts +141 -0
- package/src/time-series/studies/units.ts +243 -0
- package/src/time-series/studies/vwap.ts +274 -0
- package/src/time-series/types.ts +7 -0
- package/src/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +40 -3
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +17 -1
- package/src/ui/toast.tsx +26 -1
- package/src/utils/ascii-text.ts +140 -0
- package/src/utils/cli-output.ts +91 -18
- package/src/utils/concurrency.ts +24 -0
- package/src/utils/exchanges.ts +6 -0
- package/src/utils/external-url.ts +20 -7
- package/src/utils/financial-statements.ts +73 -16
- package/src/utils/fundamentals.ts +17 -5
- package/src/utils/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/lease-liabilities.ts +54 -0
- package/src/utils/main-thread-stall.ts +122 -5
- package/src/utils/perf-marks.ts +58 -12
- package/src/utils/price-history-integrity.ts +47 -1
- package/src/utils/private-files.ts +141 -0
- package/src/utils/public-address.ts +99 -0
- package/src/account/upgrade-personalization.ts +0 -57
- package/src/account/upgrade-tickers.ts +0 -124
- package/src/api-client/upgrade-experiment.ts +0 -91
- package/src/plugins/builtin/cloud/browser.tsx +0 -3
- package/src/time-series/studies.ts +0 -1628
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { useCallback, useMemo, useRef } from "react";
|
|
2
|
+
import { apiClient } from "../../../api-client";
|
|
3
|
+
import {
|
|
4
|
+
DataTableView,
|
|
5
|
+
PaneStatusBody,
|
|
6
|
+
usePaneFooter,
|
|
7
|
+
type DataTableCell,
|
|
8
|
+
type DataTableColumn,
|
|
9
|
+
} from "../../../components";
|
|
10
|
+
import { loadingErrorFooterInfo, usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
11
|
+
import { useAsyncResource } from "../../../react/async-resource";
|
|
12
|
+
import { useAssetData, usePluginAppActions, usePluginPaneState } from "../../../public/react";
|
|
13
|
+
import { useAppSelector } from "../../../state/app/context";
|
|
14
|
+
import { resolveSavedInstrument } from "../../../tickers/saved-instrument";
|
|
15
|
+
import { colors } from "../../../theme/colors";
|
|
16
|
+
import type { HeadlessPaneColumn } from "../../../types/headless";
|
|
17
|
+
import { TextAttributes } from "../../../ui";
|
|
18
|
+
import { compareSortValues, nextHeaderSort, type SortComparableValue, type SortPreference } from "../../../utils/sort-values";
|
|
19
|
+
import { SignInWall } from "../cloud/auth-actions";
|
|
20
|
+
import { isCloudSessionRequired, useResearchCloudSession } from "../shared/research-cloud-session";
|
|
21
|
+
import { fetchDividendData } from "./client";
|
|
22
|
+
import { dividendRankingReads, type DividendReadContext } from "./headless";
|
|
23
|
+
import { projectDividendRanking, type DividendRankingEntry } from "./ranking";
|
|
24
|
+
import { readDividendRanking, type DividendRankingReads } from "./ranking-reads";
|
|
25
|
+
|
|
26
|
+
/** The pane setting a ranking of several tickers is opened with (`shot DVD KO JNJ`). */
|
|
27
|
+
export const RANK_SYMBOLS_SETTING = "rankSymbols";
|
|
28
|
+
|
|
29
|
+
/** The tickers a pane ranks, from its settings: two or more, or none for a pane of one ticker. */
|
|
30
|
+
export function rankSymbolsOf(settings: Readonly<Record<string, unknown>> | undefined): string[] {
|
|
31
|
+
const value = settings?.[RANK_SYMBOLS_SETTING];
|
|
32
|
+
const symbols = Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === "string" && entry.trim() !== "") : [];
|
|
33
|
+
return symbols.length > 1 ? symbols : [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** How wide each of the ranking's columns starts, in characters; a column not named fits its header. */
|
|
37
|
+
const COLUMN_WIDTHS: Record<string, number> = { symbol: 10, lastExDate: 10, payDate: 10, note: 24, yieldBasis: 24 };
|
|
38
|
+
/** Growth figures read by their sign, as the single-ticker pane colors them. */
|
|
39
|
+
const SIGNED_COLUMNS = new Set(["growth1Y", "growth3Y"]);
|
|
40
|
+
const DATE_COLUMNS = new Set(["lastExDate", "payDate", "secYieldAsOf"]);
|
|
41
|
+
|
|
42
|
+
type RankingRow = Record<string, unknown> & { symbol: string };
|
|
43
|
+
type RankingColumn = DataTableColumn & { source: HeadlessPaneColumn };
|
|
44
|
+
/**
|
|
45
|
+
* The order `fn DVD` ranks in: trailing yield, highest first. A stable sort
|
|
46
|
+
* of the ranked rows keeps a ticker not read last among those with no yield.
|
|
47
|
+
*/
|
|
48
|
+
const RANKED_ORDER: SortPreference<string> = { columnId: "trailingYield", direction: "desc" };
|
|
49
|
+
|
|
50
|
+
function sortRankingRows(rows: RankingRow[], sort: SortPreference<string>): RankingRow[] {
|
|
51
|
+
const { columnId, direction } = sort;
|
|
52
|
+
if (!columnId) return rows;
|
|
53
|
+
return [...rows].sort((left, right) => compareSortValues(left[columnId] as SortComparableValue, right[columnId] as SortComparableValue, direction));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Signed out, every read is refused for the account: the pane shows the wall the single-ticker DVD shows. */
|
|
57
|
+
function needsSignIn(entries: readonly DividendRankingEntry[] | null): boolean {
|
|
58
|
+
return !!entries?.length && entries.every((entry) => !("data" in entry))
|
|
59
|
+
&& entries.some((entry) => "error" in entry && isCloudSessionRequired(entry.error));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cellText(column: HeadlessPaneColumn, row: RankingRow): string {
|
|
63
|
+
const value = row[column.key];
|
|
64
|
+
const format = column.textFormat ?? column.format;
|
|
65
|
+
return format ? format(value, row) : value == null ? "" : String(value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Several tickers ranked by trailing yield in one table, with the columns and
|
|
70
|
+
* figures `fn DVD A B C` prints; Enter opens a ticker's own dividend pane.
|
|
71
|
+
*/
|
|
72
|
+
export function DividendRankingView({ symbols, focused, width, height, loadData }: {
|
|
73
|
+
symbols: readonly string[];
|
|
74
|
+
focused: boolean;
|
|
75
|
+
width: number;
|
|
76
|
+
height: number;
|
|
77
|
+
/** One listing's history alone, without the quote `fn DVD` prices it at; for tests. */
|
|
78
|
+
loadData?: typeof fetchDividendData;
|
|
79
|
+
}) {
|
|
80
|
+
const key = symbols.join(",");
|
|
81
|
+
const marketData = useAssetData();
|
|
82
|
+
const cloudSession = useResearchCloudSession();
|
|
83
|
+
// A key reads the listing `fn DVD` reads for it: its own venue, else the one its saved ticker is on.
|
|
84
|
+
// Through a ref, so a quote that updates a saved ticker does not read the ranking again.
|
|
85
|
+
const tickers = useAppSelector((state) => state.tickers);
|
|
86
|
+
const tickersRef = useRef(tickers);
|
|
87
|
+
tickersRef.current = tickers;
|
|
88
|
+
const reads = useMemo<DividendRankingReads>(() => {
|
|
89
|
+
const resolveInstrument = (typed: string) => resolveSavedInstrument(typed, (saved) => tickersRef.current.get(saved));
|
|
90
|
+
// `fn DVD`'s own reads: the listing's quote for the price, the stated forward rate, a fund's bond facts.
|
|
91
|
+
if (marketData && !loadData) return dividendRankingReads({ marketData, apiClient, resolveInstrument } satisfies DividendReadContext);
|
|
92
|
+
const history = loadData ?? fetchDividendData;
|
|
93
|
+
return { loadData: async (typed) => {
|
|
94
|
+
const listing = await resolveInstrument(typed);
|
|
95
|
+
return history(listing.symbol, null, listing.exchange ?? "");
|
|
96
|
+
} };
|
|
97
|
+
}, [loadData, marketData]);
|
|
98
|
+
// A sign-in or a verified email reads the ranking again.
|
|
99
|
+
const request = useCallback(() => readDividendRanking(key.split(","), reads), [key, reads, cloudSession.requestKey]);
|
|
100
|
+
const resource = useAsyncResource(request, { keepPreviousData: true });
|
|
101
|
+
const authWall = needsSignIn(resource.data);
|
|
102
|
+
const { createPaneFromTemplate } = usePluginAppActions();
|
|
103
|
+
const section = useMemo(() => resource.data ? projectDividendRanking(resource.data).sections[0] ?? null : null, [resource.data]);
|
|
104
|
+
const [sort, setSort] = usePluginPaneState<SortPreference<string>>("dividend-ranking:sort", RANKED_ORDER);
|
|
105
|
+
const rows = useMemo(() => sortRankingRows((section?.rows ?? []) as RankingRow[], sort), [section, sort]);
|
|
106
|
+
const columns = useMemo<RankingColumn[]>(() => (section?.columns ?? []).map((column) => ({
|
|
107
|
+
id: column.key,
|
|
108
|
+
label: column.header,
|
|
109
|
+
width: Math.max(column.header.length, COLUMN_WIDTHS[column.key] ?? 0),
|
|
110
|
+
align: column.align === "right" ? "right" : "left",
|
|
111
|
+
source: column,
|
|
112
|
+
})), [section]);
|
|
113
|
+
const [selected, setSelected] = usePluginPaneState<string | null>("dividend-ranking:selected", null);
|
|
114
|
+
|
|
115
|
+
usePaneFooter("dividend-ranking", () => ({
|
|
116
|
+
info: loadingErrorFooterInfo(resource.loading && !!resource.data, resource.data ? null : resource.error),
|
|
117
|
+
}), [resource.loading, resource.data, resource.error]);
|
|
118
|
+
usePaneRefreshKey(resource.reload, { focused, enabled: !authWall });
|
|
119
|
+
|
|
120
|
+
const renderCell = useCallback((row: RankingRow, column: RankingColumn): DataTableCell => {
|
|
121
|
+
const text = cellText(column.source, row);
|
|
122
|
+
const value = row[column.id];
|
|
123
|
+
if (column.id === "symbol") return { text, color: colors.textBright, attributes: TextAttributes.BOLD };
|
|
124
|
+
if (column.id === "note") return { text, color: colors.warning };
|
|
125
|
+
if (DATE_COLUMNS.has(column.id) || column.id === "yieldBasis") return { text, color: colors.textDim };
|
|
126
|
+
if (SIGNED_COLUMNS.has(column.id) && typeof value === "number") {
|
|
127
|
+
return { text, color: value > 0 ? colors.positive : value < 0 ? colors.negative : colors.text };
|
|
128
|
+
}
|
|
129
|
+
return { text };
|
|
130
|
+
}, []);
|
|
131
|
+
|
|
132
|
+
if (authWall) return <SignInWall placement="dividend-yield-signin" action="view dividend history" needsVerification={cloudSession.needsVerification} />;
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<PaneStatusBody
|
|
136
|
+
subject="dividends"
|
|
137
|
+
loading={!resource.data && resource.loading}
|
|
138
|
+
error={!resource.data ? resource.error : null}
|
|
139
|
+
empty={false}
|
|
140
|
+
>
|
|
141
|
+
<DataTableView<RankingRow, RankingColumn>
|
|
142
|
+
focused={focused}
|
|
143
|
+
columns={columns}
|
|
144
|
+
items={rows}
|
|
145
|
+
rootWidth={width}
|
|
146
|
+
rootHeight={height}
|
|
147
|
+
freezeFirstColumn
|
|
148
|
+
sortColumnId={sort.columnId}
|
|
149
|
+
sortDirection={sort.direction}
|
|
150
|
+
// A third click on a column returns to the ranking.
|
|
151
|
+
onHeaderClick={(id) => setSort((current) => nextHeaderSort(current, id, {
|
|
152
|
+
firstDirection: id === "symbol" || id === "note" || id === "yieldBasis" ? "asc" : "desc", resetTo: RANKED_ORDER }))}
|
|
153
|
+
getItemKey={(row) => row.symbol}
|
|
154
|
+
selection={{ kind: "id", selectedId: rows.some((row) => row.symbol === selected) ? selected! : rows[0]?.symbol ?? "",
|
|
155
|
+
getId: (row) => row.symbol, onChange: (id) => setSelected(id) }}
|
|
156
|
+
onActivate={(row) => createPaneFromTemplate("dividend-yield-pane", { symbol: row.symbol })}
|
|
157
|
+
renderCell={renderCell}
|
|
158
|
+
selectedTextOverridesCellColor
|
|
159
|
+
emptyStateTitle="No tickers"
|
|
160
|
+
/>
|
|
161
|
+
</PaneStatusBody>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
@@ -146,7 +146,7 @@ const RANKING_COLUMNS: HeadlessPaneColumn[] = [
|
|
|
146
146
|
{ key: "growth3Y", header: "3Y CAGR", align: "right", format: growth("growth3Y"),
|
|
147
147
|
description: "Compound annual growth of trailing twelve-month cash per share over three years; n/a when either year holds fewer payments than the cadence pays." },
|
|
148
148
|
{ key: "lastExDate", header: "Last ex", format: day, shrink: false },
|
|
149
|
-
{ key: "payDate", header: "
|
|
149
|
+
{ key: "payDate", header: "Next pay", format: day, shrink: false, stackPriority: 2,
|
|
150
150
|
description: "Pay date of the next declared distribution; past distributions carry ex-dates only." },
|
|
151
151
|
];
|
|
152
152
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { DividendPayment, GrowthPayments } from "./types";
|
|
2
|
+
import { formatCurrency } from "../../../utils/format";
|
|
3
|
+
import { currencyMinorDigits } from "../../../market-data/market/format";
|
|
4
|
+
import { resolveCurrencyUnit } from "../../../utils/currency-units";
|
|
2
5
|
import type { ProjectedChartPoint } from "../../../components/chart/core/data";
|
|
3
6
|
import { calendarMonthsBefore } from "../../../utils/calendar-date";
|
|
4
7
|
import { NOT_MEANINGFUL, formatPriceEarnings } from "../../../utils/price-earnings";
|
|
@@ -6,6 +9,24 @@ import { LATE_GRACE_DAYS, trailingCashAt, type TrailingPayments } from "../../..
|
|
|
6
9
|
|
|
7
10
|
const DAY = 24 * 60 * 60 * 1000;
|
|
8
11
|
|
|
12
|
+
/**
|
|
13
|
+
* A cash amount the listing pays as a conversion of the currency its company
|
|
14
|
+
* declares in (`DividendData.convertedFrom`), to two decimals of the unit its
|
|
15
|
+
* source states it in: pence stay pence (BP's 6.4059p is `6.41p`, never
|
|
16
|
+
* `£0.06`), and a major unit keeps its currency's decimals (`ZAR 0.87`, not
|
|
17
|
+
* `ZAR 0.866087`), as the conversion gives it no more.
|
|
18
|
+
*/
|
|
19
|
+
export function formatConvertedAmount(value: number | null | undefined, currency: string, subUnit?: string): string {
|
|
20
|
+
if (value == null || !Number.isFinite(value)) return "—";
|
|
21
|
+
const unit = subUnit ? resolveCurrencyUnit(subUnit) : null;
|
|
22
|
+
return unit && unit.divisor !== 1 && unit.currency === currency
|
|
23
|
+
? formatCurrency(value * unit.divisor, subUnit!, 2)
|
|
24
|
+
: formatCurrency(value, currency, currencyMinorDigits(currency));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** What a converted amount says beside it: `converted from USD`. */
|
|
28
|
+
export const convertedFromText = (currency: string) => `converted from ${currency}`;
|
|
29
|
+
|
|
9
30
|
export function formatDividendYield(value: number | null): string {
|
|
10
31
|
return value != null && Number.isFinite(value) ? `${(value * 100).toFixed(2)}%` : "—";
|
|
11
32
|
}
|
|
@@ -6,7 +6,6 @@ import type { PaneTemplateContext, PaneTemplateCreateOptions } from "../../../ty
|
|
|
6
6
|
import { getSharedRegistry } from "../../registry";
|
|
7
7
|
import { buildRegistryHelpIndex } from "../help/function-index";
|
|
8
8
|
import type { PluginModule } from "../plugin-module";
|
|
9
|
-
import { DocsPane } from "./pane";
|
|
10
9
|
import { docsTopicForArgument, docsTopicForPane } from "./targets";
|
|
11
10
|
|
|
12
11
|
/** One reader: every request lands in the same pane, on top of its history. */
|
|
@@ -38,8 +37,8 @@ export const docsModule: PluginModule = {
|
|
|
38
37
|
id: "docs",
|
|
39
38
|
name: "Docs",
|
|
40
39
|
icon: "?",
|
|
41
|
-
component: DocsPane,
|
|
42
|
-
reportFreshness: { status: "not-a-feed", basis: "documentation" },
|
|
40
|
+
get component(): typeof import("./pane").DocsPane { const { DocsPane } = require("./pane"); return DocsPane; },
|
|
41
|
+
reportFreshness: { status: "not-a-feed", basis: "documentation", local: true },
|
|
43
42
|
// A page reads whole in a report, not cut at the first screen.
|
|
44
43
|
shotFitsReader: true,
|
|
45
44
|
defaultPosition: "right",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { apiClient } from "../../../api-client";
|
|
2
|
+
import { sourceDidNotAnswer } from "../../../api-client/errors";
|
|
2
3
|
import type { DoeBoardPayload, DoeSeriesRow } from "../../../api-client/doe";
|
|
3
|
-
import { ApiRequestError } from "../../../api-client/errors";
|
|
4
4
|
import { createPluginCache } from "../../../data/plugin-cache";
|
|
5
|
-
import { cachedCloudResource, loadCloudResource } from "../shared/cloud-resource";
|
|
5
|
+
import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
6
6
|
|
|
7
7
|
/** What the pane says while the server has no EIA weeks to serve: before the backend ships, or before its first read. */
|
|
8
8
|
export const DOE_NOT_AVAILABLE = "Oil and gas inventories are not available yet.";
|
|
@@ -65,11 +65,12 @@ export async function fetchDoeBoard(client: Pick<typeof apiClient, "getCloudDoeB
|
|
|
65
65
|
try {
|
|
66
66
|
payload = await client.getCloudDoeBoard();
|
|
67
67
|
} catch (error) {
|
|
68
|
-
|
|
69
|
-
throw error;
|
|
68
|
+
throw unavailableOnServer(error, DOE_NOT_AVAILABLE, [404, 503]);
|
|
70
69
|
}
|
|
71
70
|
const board = validateDoeBoard(payload);
|
|
72
|
-
|
|
71
|
+
// The server's source did not answer this time: a saved copy answers, as it does a 5xx.
|
|
72
|
+
if (board.status === "unavailable") throw sourceDidNotAnswer(DOE_NOT_AVAILABLE);
|
|
73
|
+
if (!board.series.some((row) => row.value != null)) throw new Error(DOE_NOT_AVAILABLE);
|
|
73
74
|
return board;
|
|
74
75
|
}
|
|
75
76
|
|
|
@@ -49,6 +49,14 @@ function seasonalRows(row: DoeSeriesRow) {
|
|
|
49
49
|
fiveYearLow: level(min), fiveYearAverage: level(average), fiveYearHigh: level(max) }));
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/** Words for natural gas storage, which `NGS` reads, rather than a series of the petroleum tables. */
|
|
53
|
+
const GAS_WORDS = new Set(["ngs", "ng", "gas", "natgas", "naturalgas", "natgasstorage", "naturalgasstorage", "gasstorage", "storage", "workinggas"]);
|
|
54
|
+
|
|
55
|
+
/** Whether `DOE` was given a word for gas storage (`NGS`, `natural gas`) rather than a series. */
|
|
56
|
+
export function doeGasWord(argument: string): boolean {
|
|
57
|
+
return GAS_WORDS.has(argument.toLowerCase().replace(/[^a-z]/g, ""));
|
|
58
|
+
}
|
|
59
|
+
|
|
52
60
|
/** `DOE` reads every tab unless one is named; `NGS` starts on gas storage; a series argument adds its year. */
|
|
53
61
|
export function doeHeadless(defaultTab: DoeTab | "all"): HeadlessPaneDefinition<"bundle"> {
|
|
54
62
|
return {
|
|
@@ -77,10 +85,13 @@ export function doeHeadless(defaultTab: DoeTab | "all"): HeadlessPaneDefinition<
|
|
|
77
85
|
nextExpectedAt: oldestReportTime(reports.map((report) => report.nextReleaseAt)) };
|
|
78
86
|
};
|
|
79
87
|
const argument = Array.isArray(args.argument) ? args.argument.join(" ") : args.argument;
|
|
80
|
-
|
|
88
|
+
// `DOE NGS` or `DOE natural gas` is the gas storage tables `NGS` opens on, and says so; `NGS gas` is them already.
|
|
89
|
+
const gasWord = argument ? doeGasWord(argument) : false;
|
|
90
|
+
const ngs = defaultTab === "gas";
|
|
91
|
+
if (argument && !gasWord) {
|
|
81
92
|
const option = doeSeriesOption(argument);
|
|
82
93
|
const row = option ? data.series.find((entry) => entry.id === option.value) : null;
|
|
83
|
-
if (!row) throw new Error(
|
|
94
|
+
if (!row) throw new Error(`Use a series such as cushing, gasoline, distillate or east${ngs ? "" : "; for gas storage, gloom fn NGS"}`);
|
|
84
95
|
return {
|
|
85
96
|
sections: [
|
|
86
97
|
{ title: doeHeaderLine(doeReport(data, row.tab)) ?? row.label, columns: SUMMARY_COLUMNS, rows: [reportRow(row)] },
|
|
@@ -95,8 +106,10 @@ export function doeHeadless(defaultTab: DoeTab | "all"): HeadlessPaneDefinition<
|
|
|
95
106
|
metadata: { ...data, series: [row], complete: data.status === "available" },
|
|
96
107
|
};
|
|
97
108
|
}
|
|
98
|
-
const tabs =
|
|
109
|
+
const tabs = gasWord ? ["gas" as const]
|
|
110
|
+
: args.options.tab === "all" || args.options.tab == null ? DOE_TABS.map(({ value }) => value) : [doeTab(args.options.tab)];
|
|
99
111
|
return {
|
|
112
|
+
...(gasWord && !ngs ? { warnings: [`Gas storage is its own function: gloom fn NGS (one region: gloom fn NGS east)`] } : {}),
|
|
100
113
|
sections: tabs.map((tab) => ({
|
|
101
114
|
title: `${DOE_TABS.find((entry) => entry.value === tab)!.label}${doeHeaderLine(doeReport(data, tab)) ? `: ${doeHeaderLine(doeReport(data, tab))}` : ""}`,
|
|
102
115
|
columns: SUMMARY_COLUMNS,
|
|
@@ -4,15 +4,16 @@ import type { PluginModule } from "../plugin-module";
|
|
|
4
4
|
import { DOE_CATALOG_SERIES } from "./series";
|
|
5
5
|
import { doeBoardCache } from "./client";
|
|
6
6
|
import { doePriceCache } from "./prices";
|
|
7
|
-
import { doeHeadless } from "./headless";
|
|
7
|
+
import { doeGasWord, doeHeadless } from "./headless";
|
|
8
8
|
import { DOE_SERIES_OPTIONS, doeSeriesOption } from "./model";
|
|
9
|
-
import { DoePane } from "./pane";
|
|
10
9
|
|
|
11
10
|
/** Opens on a tab, or on the series typed after the mnemonic and its tab. */
|
|
12
11
|
function doeInstance(tab: DoeTab, options?: PaneTemplateCreateOptions): PaneTemplateInstanceConfig {
|
|
13
12
|
const input = options?.arg?.trim();
|
|
13
|
+
// `DOE NGS` or `DOE natural gas` opens on gas storage, as `NGS` does.
|
|
14
|
+
if (input && doeGasWord(input)) return { placement: "floating", settings: { tab: "gas" } };
|
|
14
15
|
const series = doeSeriesOption(input);
|
|
15
|
-
if (input && !series) throw new Error(
|
|
16
|
+
if (input && !series) throw new Error(`Use a series such as cushing, gasoline, distillate or east${tab === "gas" ? "" : "; for gas storage, gloom fn NGS"}`);
|
|
16
17
|
return { placement: "floating", settings: { tab: series?.tab ?? tab }, ...(series ? { params: { series: series.value } } : {}) };
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -23,7 +24,7 @@ const shortcut = (prefix: string, tab: DoeTab | null) => ({
|
|
|
23
24
|
|
|
24
25
|
export const doeModule: PluginModule = {
|
|
25
26
|
series: DOE_CATALOG_SERIES,
|
|
26
|
-
panes: [{ id: "doe", name: "Oil and Gas Inventories", icon: "D", component: DoePane,
|
|
27
|
+
panes: [{ id: "doe", name: "Oil and Gas Inventories", icon: "D", get component(): typeof import("./pane").DoePane { const { DoePane } = require("./pane"); return DoePane; },
|
|
27
28
|
defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 96, height: 34 },
|
|
28
29
|
tableExport: true, headless: doeHeadless("all") }],
|
|
29
30
|
paneTemplates: [{
|
|
@@ -2,7 +2,7 @@ import { apiClient } from "../../../api-client";
|
|
|
2
2
|
import type { DoePricesPayload } from "../../../api-client/doe";
|
|
3
3
|
import { createPluginCache } from "../../../data/plugin-cache";
|
|
4
4
|
import type { SeriesDefinition } from "../../../series/types";
|
|
5
|
-
import {
|
|
5
|
+
import { cachedSeriesNote } from "../shared/series-warning";
|
|
6
6
|
import { loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -73,10 +73,9 @@ export const DOE_PRICE_SERIES: readonly SeriesDefinition[] = PRICE_SERIES.map((s
|
|
|
73
73
|
const resource = await loadCloudResource(doePriceCache, "prices", () => fetchDoePrices(), { validate: validatePrices });
|
|
74
74
|
const row = resource.payload.series.find((entry) => entry.id === series.id);
|
|
75
75
|
if (!row?.points.length) throw new Error(`${series.name} is not available yet.`);
|
|
76
|
-
const warning = cachedSeriesWarning(resource);
|
|
77
76
|
return {
|
|
78
77
|
points: row.points.map(([date, value]) => ({ date, value })),
|
|
79
|
-
...(
|
|
78
|
+
...cachedSeriesNote(resource),
|
|
80
79
|
// Cited as the EIA series it is, not as the route that served it.
|
|
81
80
|
attribution: { source: `EIA series ${series.key}`, publisher: "EIA" },
|
|
82
81
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DoeSeriesRow, DoeUnit } from "../../../api-client/doe";
|
|
2
2
|
import type { SeriesDefinition, SeriesPoint } from "../../../series/types";
|
|
3
|
-
import {
|
|
3
|
+
import { cachedSeriesNote } from "../shared/series-warning";
|
|
4
4
|
import { loadDoeBoard } from "./client";
|
|
5
5
|
import { DOE_SERIES_OPTIONS } from "./model";
|
|
6
6
|
import { DOE_PRICE_SERIES } from "./prices";
|
|
@@ -50,14 +50,14 @@ const DOE_SERIES: readonly SeriesDefinition[] = DOE_SERIES_OPTIONS.map((option):
|
|
|
50
50
|
unit: UNITS[unit].unit,
|
|
51
51
|
frequency: "weekly",
|
|
52
52
|
group: "Energy",
|
|
53
|
-
|
|
53
|
+
// Jet fuel is kerosene, the word a reader may search it by.
|
|
54
|
+
tags: gas ? ["natural gas", "storage", "inventories"] : ["oil", "petroleum", "inventories", "US", ...option.value === "jet" ? ["kerosene"] : []],
|
|
54
55
|
publisher: "EIA",
|
|
55
56
|
load: async () => {
|
|
56
57
|
const resource = await loadDoeBoard();
|
|
57
58
|
const row = resource.payload.series.find((entry) => entry.id === option.value);
|
|
58
59
|
if (!row) throw new Error(`${option.label} is not in this week's report.`);
|
|
59
|
-
|
|
60
|
-
return { points: doeRowPoints(row), ...(warning ? { warning } : {}) };
|
|
60
|
+
return { points: doeRowPoints(row), ...cachedSeriesNote(resource) };
|
|
61
61
|
},
|
|
62
62
|
};
|
|
63
63
|
});
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type QueryBarFilter,
|
|
11
11
|
type QueryBarView,
|
|
12
12
|
} from "../../../components";
|
|
13
|
+
import { ageFooterSegment } from "../../../components/layout/pane/footer";
|
|
13
14
|
import { handleRefreshKey, usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
14
15
|
import { openFormModal } from "../../../components/form-modal";
|
|
15
16
|
import { FILL_REST } from "../../../components/ui/fill";
|
|
@@ -305,7 +306,7 @@ export function EarningsBoard({ focused, width, height, scopedSymbols, highlight
|
|
|
305
306
|
loading: resource.loading,
|
|
306
307
|
error: resource.error ?? data?.refreshError ?? null,
|
|
307
308
|
stale: !!data?.stale,
|
|
308
|
-
info: data && updatedAgo ? [
|
|
309
|
+
info: data && updatedAgo ? [ageFooterSegment(updatedAgo)] : [],
|
|
309
310
|
});
|
|
310
311
|
const symbol = open?.symbol ?? selectedReport?.symbol ?? null;
|
|
311
312
|
// The desktop and the web only: the terminal's footer stays as it was.
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
usePaneStatusFooter,
|
|
9
9
|
type ChartTableChart,
|
|
10
10
|
} from "../../../components";
|
|
11
|
+
import { ageFooterSegment } from "../../../components/layout/pane/footer";
|
|
11
12
|
import { staticSeries } from "../../../components/chart/static/series";
|
|
12
13
|
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
13
14
|
import { useAsyncResource, useAutoRefresh, usePluginPaneState, useUpdatedAgo } from "../../../public/react";
|
|
@@ -107,7 +108,7 @@ export function EarningsHistoryView({ symbol, width, height, focused, registrati
|
|
|
107
108
|
error: historyError,
|
|
108
109
|
stale: !!resource.data?.stale,
|
|
109
110
|
info: abroad ? [{ id: "us-listings", parts: [{ text: "past reports: US listings only", tone: "muted" as const }] }]
|
|
110
|
-
: payload && updatedAgo ? [
|
|
111
|
+
: payload && updatedAgo ? [ageFooterSegment(updatedAgo)] : [],
|
|
111
112
|
});
|
|
112
113
|
|
|
113
114
|
return (
|
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
type SectionedRow,
|
|
20
20
|
} from "../../../components";
|
|
21
21
|
import { handleRefreshKey } from "../../../components/data-table/table-pane";
|
|
22
|
+
import { savedCopyFooterInfo } from "../../../components/layout/pane/footer";
|
|
23
|
+
import { isPermanentClientError } from "../../../api-client/errors";
|
|
22
24
|
import { TABLE_COLUMN_GAP, tableColumnWidth } from "../../../components/ui/table-layout";
|
|
23
25
|
import { useAsyncResource } from "../../../react/async-resource";
|
|
24
26
|
import { useAgeClock, useAutoRefresh } from "../../../react/auto-refresh";
|
|
@@ -162,6 +164,8 @@ export function UpcomingCallsView({ focused, width, height, view, renderCompany
|
|
|
162
164
|
}, [range]);
|
|
163
165
|
const resource = useAsyncResource(entitled ? loader : null, {
|
|
164
166
|
keepPreviousData: true,
|
|
167
|
+
// A refusal asking again will not change takes the calendar off screen, for its wall or its error.
|
|
168
|
+
clearOnError: isPermanentClientError,
|
|
165
169
|
initialData: () => cachedUpcomingCalls(range),
|
|
166
170
|
});
|
|
167
171
|
const data = resource.data;
|
|
@@ -204,12 +208,16 @@ export function UpcomingCallsView({ focused, width, height, view, renderCompany
|
|
|
204
208
|
const proRequired = !signInRequired && !verificationRequired && (!access.hasProAccess || (!data && refused === 402));
|
|
205
209
|
|
|
206
210
|
const hints = useMemo<PaneHint[]>(() => [{ id: "search", key: "/", label: "search", onPress: focusSearch }], [focusSearch]);
|
|
211
|
+
// A refresh that failed shows the calendar an earlier read saved, and says when that copy is from.
|
|
212
|
+
const savedAt = data?.savedAt ?? null;
|
|
213
|
+
const savedInfo = useMemo(() => savedCopyFooterInfo([savedAt]), [savedAt]);
|
|
207
214
|
usePaneStatusFooter({
|
|
208
215
|
registrationId: "earnings-calls:calendar",
|
|
209
216
|
enabled: !detailOpen && entitled,
|
|
210
217
|
loading: resource.loading && !!data,
|
|
211
|
-
error: data ? data.refreshError ?? resource.error : null,
|
|
212
|
-
stale: !!data && (data.stale || !!resource.error),
|
|
218
|
+
error: data ? (savedAt == null ? data.refreshError : null) ?? resource.error : null,
|
|
219
|
+
stale: !!data && savedAt == null && (data.stale || !!resource.error),
|
|
220
|
+
info: savedInfo,
|
|
213
221
|
hints,
|
|
214
222
|
});
|
|
215
223
|
// A calendar cut at the server's limit says so, rather than end the week early.
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
type CloudEarningsCallPayload,
|
|
6
6
|
type CloudEarningsTranscriptPayload,
|
|
7
7
|
} from "../../../api-client";
|
|
8
|
-
import { ApiRequestError } from "../../../api-client/errors";
|
|
8
|
+
import { ApiRequestError, isTransientFailure } from "../../../api-client/errors";
|
|
9
9
|
import { listingAbroad, type IssuerListingParams, type ListingAbroad } from "../../../api-client/paths";
|
|
10
10
|
import { servedFromSavedCopy } from "../../../data/saved-copies";
|
|
11
11
|
import { areDifferentCompanies } from "../../../sources/sec-registrant";
|
|
@@ -45,6 +45,8 @@ export interface EarningsCallsResult {
|
|
|
45
45
|
calls: CloudEarningsCallPayload[];
|
|
46
46
|
fetchedAt: number;
|
|
47
47
|
stale: boolean;
|
|
48
|
+
/** Its refresh failed and the copy saved at this time (ms) answered instead. */
|
|
49
|
+
savedAt?: number;
|
|
48
50
|
refreshError?: string;
|
|
49
51
|
/** HTTP status when the request failed, used to pick the right gate. */
|
|
50
52
|
errorStatus?: number;
|
|
@@ -122,6 +124,8 @@ export interface UpcomingCallsResult {
|
|
|
122
124
|
stale: boolean;
|
|
123
125
|
/** The server answered its limit's worth: calls later on these days may be missing. */
|
|
124
126
|
truncated?: boolean;
|
|
127
|
+
/** Its refresh failed and the copy saved at this time (ms) answered instead. */
|
|
128
|
+
savedAt?: number;
|
|
125
129
|
refreshError?: string;
|
|
126
130
|
errorStatus?: number;
|
|
127
131
|
}
|
|
@@ -134,7 +138,7 @@ export interface UpcomingDays {
|
|
|
134
138
|
let persistence: PluginPersistence | null = null;
|
|
135
139
|
const activeUpcomingFetches = new Map<string, Promise<UpcomingCallsResult>>();
|
|
136
140
|
const activeListFetches = new Map<string, Promise<EarningsCallsResult>>();
|
|
137
|
-
const activeTranscriptFetches = new Map<string, Promise<
|
|
141
|
+
const activeTranscriptFetches = new Map<string, Promise<EarningsTranscriptResult>>();
|
|
138
142
|
|
|
139
143
|
export function attachEarningsCallsPersistence(value: PluginPersistence): void {
|
|
140
144
|
persistence = value;
|
|
@@ -253,20 +257,21 @@ export async function loadEarningsCallsWithClient(
|
|
|
253
257
|
.catch((error: unknown) => {
|
|
254
258
|
const refreshError = error instanceof Error ? error.message : String(error);
|
|
255
259
|
const errorStatus = statusOf(error);
|
|
256
|
-
//
|
|
257
|
-
//
|
|
260
|
+
// A refusal (signed out, no Pro, a route the server stopped serving) or a reply that fails
|
|
261
|
+
// validation is said rather than covered by a cached list.
|
|
258
262
|
const expired = store?.getResource<CloudEarningsCallListPayload>(LIST_KIND, key, {
|
|
259
263
|
sourceKey: CACHE_SOURCE,
|
|
260
264
|
schemaVersion: LIST_CACHE_SCHEMA_VERSION,
|
|
261
265
|
allowExpired: true,
|
|
262
266
|
});
|
|
263
|
-
if (expired &&
|
|
267
|
+
if (expired && isTransientFailure(error)) {
|
|
264
268
|
// A command that shows it ends with when the copy was saved, not with the failure.
|
|
265
269
|
return servedFromSavedCopy({
|
|
266
270
|
...expired.value,
|
|
267
271
|
...sourceScope(expired.value.calls),
|
|
268
272
|
fetchedAt: expired.fetchedAt,
|
|
269
273
|
stale: true,
|
|
274
|
+
savedAt: expired.fetchedAt,
|
|
270
275
|
refreshError,
|
|
271
276
|
errorStatus,
|
|
272
277
|
}, expired.fetchedAt);
|
|
@@ -367,9 +372,10 @@ export async function loadUpcomingCallsWithClient(
|
|
|
367
372
|
const errorStatus = statusOf(error);
|
|
368
373
|
// Losing Pro access re-gates the calendar, as it does the list.
|
|
369
374
|
const expired = read(true);
|
|
370
|
-
if (expired &&
|
|
375
|
+
if (expired && isTransientFailure(error)) {
|
|
371
376
|
return servedFromSavedCopy({
|
|
372
377
|
...upcomingFromCache(expired.value, expired.fetchedAt, true),
|
|
378
|
+
savedAt: expired.fetchedAt,
|
|
373
379
|
refreshError: error instanceof Error ? error.message : String(error),
|
|
374
380
|
errorStatus,
|
|
375
381
|
}, expired.fetchedAt);
|
|
@@ -409,11 +415,14 @@ export function isPendingTranscript(
|
|
|
409
415
|
return record.status === "pending" || !Array.isArray(record.turns);
|
|
410
416
|
}
|
|
411
417
|
|
|
418
|
+
/** A transcript, and when it was saved when its refresh failed and the saved copy answered. */
|
|
419
|
+
export type EarningsTranscriptResult = CloudEarningsTranscriptPayload & { savedAt?: number };
|
|
420
|
+
|
|
412
421
|
export async function loadTranscriptWithClient(
|
|
413
422
|
client: EarningsCallsApiClient,
|
|
414
423
|
callId: string,
|
|
415
424
|
options?: { force?: boolean },
|
|
416
|
-
): Promise<
|
|
425
|
+
): Promise<EarningsTranscriptResult> {
|
|
417
426
|
const force = options?.force ?? false;
|
|
418
427
|
const store = persistence;
|
|
419
428
|
|
|
@@ -444,7 +453,6 @@ export async function loadTranscriptWithClient(
|
|
|
444
453
|
return transcript;
|
|
445
454
|
})
|
|
446
455
|
.catch((error: unknown) => {
|
|
447
|
-
const status = statusOf(error);
|
|
448
456
|
const expired = store?.getResource<CloudEarningsTranscriptPayload>(
|
|
449
457
|
TRANSCRIPT_KIND,
|
|
450
458
|
callId,
|
|
@@ -455,8 +463,8 @@ export async function loadTranscriptWithClient(
|
|
|
455
463
|
},
|
|
456
464
|
);
|
|
457
465
|
// Losing Pro access must re-gate the transcript, not serve it from cache.
|
|
458
|
-
if (expired &&
|
|
459
|
-
return expired.value;
|
|
466
|
+
if (expired && isTransientFailure(error)) {
|
|
467
|
+
return servedFromSavedCopy({ ...expired.value, savedAt: expired.fetchedAt }, expired.fetchedAt);
|
|
460
468
|
}
|
|
461
469
|
throw error;
|
|
462
470
|
})
|
|
@@ -473,7 +481,7 @@ export async function loadTranscriptWithClient(
|
|
|
473
481
|
export function loadTranscript(
|
|
474
482
|
callId: string,
|
|
475
483
|
options?: { force?: boolean },
|
|
476
|
-
): Promise<
|
|
484
|
+
): Promise<EarningsTranscriptResult> {
|
|
477
485
|
return loadTranscriptWithClient(apiClient, callId, options);
|
|
478
486
|
}
|
|
479
487
|
|