@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
|
@@ -1,12 +1,34 @@
|
|
|
1
1
|
import type { PaneLock } from "../../../types/plugin";
|
|
2
|
-
import {
|
|
2
|
+
import { isOptionsUnavailableError } from "../../../market-data/options-alternatives";
|
|
3
|
+
import { resolveHeadlessInstrument } from "../shared/headless-market-data";
|
|
4
|
+
import { proLockNote, type FreeRoute } from "../shared/pro-lock-note";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The listing's option chain, which needs no account, where options are
|
|
8
|
+
* served for it: LHA:XETRA has none, and `options LHA:XETRA` says so.
|
|
9
|
+
*/
|
|
10
|
+
const OPTIONS_ROUTE: FreeRoute = {
|
|
11
|
+
text: (ticker, company) => `gloom options ${ticker} lists ${company}'s option chain, each contract with its volume and open interest.`,
|
|
12
|
+
answers: async (symbol, context) => {
|
|
13
|
+
const { symbol: ticker, exchange } = await resolveHeadlessInstrument(context, symbol);
|
|
14
|
+
if (!context.marketData.getOptionsChain) return false;
|
|
15
|
+
try {
|
|
16
|
+
const chain = await context.marketData.getOptionsChain(ticker, exchange ?? "");
|
|
17
|
+
return chain.expirationDates.length + chain.calls.length + chain.puts.length > 0;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (isOptionsUnavailableError(error)) return false;
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
};
|
|
3
24
|
|
|
4
25
|
/**
|
|
5
26
|
* FLOW is Pro whole: the one scanner with no delayed tier to fall back to.
|
|
6
27
|
* The options chain needs no account and lists each contract's day volume and
|
|
7
28
|
* open interest, the raw counts behind a sweep or a block, without the prints.
|
|
8
29
|
* FLOW takes a ticker only to narrow the market to one name, so the note
|
|
9
|
-
* repeats it only when the reader gave one
|
|
30
|
+
* repeats it only when the reader gave one, and only when the listing has
|
|
31
|
+
* options served: FLOW's prints are of the same US options.
|
|
10
32
|
*/
|
|
11
33
|
export function flowLockNote(lock: PaneLock): Promise<string> {
|
|
12
34
|
return proLockNote(lock, {
|
|
@@ -14,6 +36,7 @@ export function flowLockNote(lock: PaneLock): Promise<string> {
|
|
|
14
36
|
argument: "optional",
|
|
15
37
|
does: "it streams real-time sweeps, blocks and large premium prints across US options exchanges",
|
|
16
38
|
shows: "it",
|
|
17
|
-
free: [
|
|
39
|
+
free: [OPTIONS_ROUTE],
|
|
40
|
+
noneFor: (ticker) => `Options are not available for ${ticker}, so FLOW has no prints of it.`,
|
|
18
41
|
});
|
|
19
42
|
}
|
|
@@ -3,6 +3,7 @@ import { useSecFilingContent } from "../../../market-data/hooks";
|
|
|
3
3
|
import { getSharedMarketDataCoordinator, resolveEntryValue } from "../../../market-data/coordinator";
|
|
4
4
|
import type { QueryEntry } from "../../../market-data/result-types";
|
|
5
5
|
import type { SecFilingDocument, SecFilingItem } from "../../../types/data-provider";
|
|
6
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
6
7
|
import { documentContentTarget, isInlineExhibitDocument } from "./filing-documents";
|
|
7
8
|
|
|
8
9
|
interface ScopedFilingContentCache {
|
|
@@ -41,18 +42,32 @@ export function buildInlineFilingContentTargets(
|
|
|
41
42
|
.map((document) => documentContentTarget(filing, document));
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
/** Filings whose content is read at once: the queue's head and the next ones read ahead. */
|
|
46
|
+
const CONTENT_READS_AT_ONCE = 4;
|
|
47
|
+
|
|
48
|
+
/** The first `count` targets not cached yet, once each, in the caller's priority order. */
|
|
49
|
+
export function nextUncachedFilingContentTargets(
|
|
45
50
|
targets: readonly SecFilingItem[],
|
|
46
51
|
cache: ReadonlyMap<string, string | null>,
|
|
47
|
-
|
|
52
|
+
count: number,
|
|
53
|
+
): SecFilingItem[] {
|
|
48
54
|
const seen = new Set<string>();
|
|
55
|
+
const next: SecFilingItem[] = [];
|
|
49
56
|
for (const target of targets) {
|
|
57
|
+
if (next.length >= count) break;
|
|
50
58
|
const key = target.accessionNumber;
|
|
51
59
|
if (seen.has(key)) continue;
|
|
52
60
|
seen.add(key);
|
|
53
|
-
if (!cache.has(key))
|
|
61
|
+
if (!cache.has(key)) next.push(target);
|
|
54
62
|
}
|
|
55
|
-
return
|
|
63
|
+
return next;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function nextUncachedFilingContentTarget(
|
|
67
|
+
targets: readonly SecFilingItem[],
|
|
68
|
+
cache: ReadonlyMap<string, string | null>,
|
|
69
|
+
): SecFilingItem | null {
|
|
70
|
+
return nextUncachedFilingContentTargets(targets, cache, 1)[0] ?? null;
|
|
56
71
|
}
|
|
57
72
|
|
|
58
73
|
export function useSecFilingContentCache({
|
|
@@ -81,12 +96,29 @@ export function useSecFilingContentCache({
|
|
|
81
96
|
return () => { retryGeneration.current += 1; };
|
|
82
97
|
}, [scopeKey]);
|
|
83
98
|
|
|
84
|
-
const
|
|
85
|
-
() =>
|
|
99
|
+
const upcoming = useMemo(
|
|
100
|
+
() => nextUncachedFilingContentTargets(targets, contentCache, CONTENT_READS_AT_ONCE),
|
|
86
101
|
[contentCache, targets],
|
|
87
102
|
);
|
|
103
|
+
const nextTarget = upcoming[0] ?? null;
|
|
88
104
|
const contentEntry = useSecFilingContent(nextTarget);
|
|
89
105
|
|
|
106
|
+
// The queue takes one filing at a time; the next few are read beside it, and
|
|
107
|
+
// taken from the coordinator once the head settles (below). Each is asked
|
|
108
|
+
// once per scope: the coordinator shares a read in flight and keeps it.
|
|
109
|
+
const readAhead = useRef<{ scopeKey: string; keys: Set<string> }>({ scopeKey, keys: new Set() });
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
const coordinator = getSharedMarketDataCoordinator();
|
|
112
|
+
if (!coordinator) return;
|
|
113
|
+
if (readAhead.current.scopeKey !== scopeKey) readAhead.current = { scopeKey, keys: new Set() };
|
|
114
|
+
const asked = readAhead.current.keys;
|
|
115
|
+
for (const target of upcoming.slice(1)) {
|
|
116
|
+
if (asked.has(target.accessionNumber)) continue;
|
|
117
|
+
asked.add(target.accessionNumber);
|
|
118
|
+
void coordinator.loadSecFilingContent(target).catch(logFailure("sec.filing-content", "Reading filing content ahead failed"));
|
|
119
|
+
}
|
|
120
|
+
}, [scopeKey, upcoming]);
|
|
121
|
+
|
|
90
122
|
useEffect(() => {
|
|
91
123
|
if (!nextTarget) return;
|
|
92
124
|
const first = settledFilingContent(contentEntry);
|
|
@@ -39,8 +39,12 @@ const SEC_COLUMNS: HeadlessPaneColumn[] = [
|
|
|
39
39
|
/** Some row has 8-K item codes; a column every row leaves `-` takes no room in the text table (exports keep it). */
|
|
40
40
|
const hasItems = (rows: readonly HeadlessPaneRow[]) => rows.some((row) => typeof row.items === "string" && row.items !== "");
|
|
41
41
|
|
|
42
|
+
/** A current report's press release exhibit, printed only when some row has one; exports keep it with the filing's link. */
|
|
43
|
+
const PRESS_RELEASE_COLUMN: HeadlessPaneColumn = { key: "pressReleaseUrl", header: "Press release" };
|
|
44
|
+
|
|
42
45
|
function secTextColumns(rows: readonly HeadlessPaneRow[]): HeadlessPaneColumn[] {
|
|
43
|
-
|
|
46
|
+
const columns = hasItems(rows) ? SEC_COLUMNS : SEC_COLUMNS.filter((column) => column.key !== "items");
|
|
47
|
+
return rows.some((row) => typeof row.pressReleaseUrl === "string" && row.pressReleaseUrl) ? [...columns, PRESS_RELEASE_COLUMN] : columns;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
/**
|
|
@@ -457,9 +457,11 @@ function SecView({ width, height, focused, paneType }: { width: number; height:
|
|
|
457
457
|
usePaneStatusLinkFooter({
|
|
458
458
|
registrationId: "sec",
|
|
459
459
|
focused,
|
|
460
|
-
|
|
460
|
+
// A current report links its press release, where its figures are, when its index names one.
|
|
461
|
+
url: openFiling ? openFiling.pressReleaseUrl ?? openFiling.filingUrl : factsUrl ?? abroadPage ?? announcements?.url,
|
|
461
462
|
source: openFiling ? openFiling.form : facts?.covered ? listNames(fundFactPublishers(facts)) : announcements?.name ?? null,
|
|
462
|
-
label: openFiling ?
|
|
463
|
+
label: openFiling ? openFiling.pressReleaseUrl ? "press release" : "filing"
|
|
464
|
+
: factsUrl ? "source" : abroadPage ? "issuer page" : announcements ? "announcements" : "filing",
|
|
463
465
|
// Rows read in the background for their insiders do not hold up the pane.
|
|
464
466
|
loading: loading || loadingDocuments || (!!loadingKey && viewedTargets.some((target) => target.accessionNumber === loadingKey)),
|
|
465
467
|
error: authWall ? null : error ?? detailError,
|
|
@@ -227,6 +227,8 @@ export function buildSecFilingRows(filings: readonly SecFilingItem[]) {
|
|
|
227
227
|
cik: filing.cik,
|
|
228
228
|
companyName: filing.companyName ?? null,
|
|
229
229
|
url: filing.filingUrl,
|
|
230
|
+
// A current report's press release exhibit, where its index names one: the page its figures are on.
|
|
231
|
+
...(filing.pressReleaseUrl ? { pressReleaseUrl: filing.pressReleaseUrl } : {}),
|
|
230
232
|
};
|
|
231
233
|
});
|
|
232
234
|
}
|
|
@@ -16,6 +16,7 @@ import type { ExtendedSession } from "../../../market-data/market/status";
|
|
|
16
16
|
import { useAutoRefresh, useUpdatedAgo } from "../../../react/auto-refresh";
|
|
17
17
|
import { useLiveStreamingSetting } from "../../../state/hooks/live-streaming";
|
|
18
18
|
import { isStreamCarryingQuote, useVisibleBoardSymbols } from "../shared/use-quote-board";
|
|
19
|
+
import { useQuoteDelayFooter } from "../shared/cloud-upgrade";
|
|
19
20
|
import { SectorMoveBar } from "./move-bar";
|
|
20
21
|
import {
|
|
21
22
|
SECTOR_COLLECTIONS,
|
|
@@ -345,16 +346,21 @@ function EtfBoard({ collectionId, focused, width, height, rootBefore }: EtfBoard
|
|
|
345
346
|
.map((row) => `${row.etf}: ${sectorRowIssues(row).join(" · ")}`);
|
|
346
347
|
usePaneNoticeFooter({ registrationId: "sectors:row-issues", notices: rowIssueNotices, focused });
|
|
347
348
|
|
|
349
|
+
// Signed out or free, the ETFs' quotes are delayed ones.
|
|
350
|
+
const quoteDelay = useQuoteDelayFooter(rows.map((row) => (row.loading ? null : { dataSource: row.quoteDataSource })), "sectors-delay");
|
|
348
351
|
usePaneFooter("sectors", () => {
|
|
349
352
|
const info = loadingErrorFooterInfo(loading, loadError);
|
|
350
353
|
if (returnAsOfDate) info.push({ id: "return-as-of", parts: [{ text: `returns as of ${returnAsOfDate}`, tone: "muted" }] });
|
|
351
354
|
if (updatedAgo) info.push({ id: "updated", parts: [{ text: `checked ${updatedAgo}`, tone: "muted" }] });
|
|
352
355
|
// Keep the leading current failure readable when the pane is narrow; separate
|
|
353
356
|
// flex children would each shrink it to a few characters beside routine status.
|
|
354
|
-
return { info:
|
|
355
|
-
...(
|
|
356
|
-
|
|
357
|
-
|
|
357
|
+
return { info: [
|
|
358
|
+
...(info.length > 0 ? [{ id: "status", parts: info.flatMap((segment, index) => [
|
|
359
|
+
...(index > 0 ? [{ text: "·", tone: "muted" as const }] : []), ...segment.parts,
|
|
360
|
+
]) }] : []),
|
|
361
|
+
...(quoteDelay ? [quoteDelay] : []),
|
|
362
|
+
] };
|
|
363
|
+
}, [loadError, loading, quoteDelay, returnAsOfDate, updatedAgo]);
|
|
358
364
|
|
|
359
365
|
return (
|
|
360
366
|
<DataTableView<SectorRow, SectorColumn>
|
|
@@ -442,7 +448,7 @@ export const sectorsModule: PluginModule = {
|
|
|
442
448
|
paneId: "sectors",
|
|
443
449
|
label: "Thematic Baskets",
|
|
444
450
|
description: "Curated themes with equal-weight returns, breadth and member leaders and laggards.",
|
|
445
|
-
keywords: ["themes", "thematic", "baskets", "AI compute", "datacenters", "electrification", "power grid", "nuclear", "uranium", "defense", "aerospace", "cybersecurity", "robotics", "automation", "critical minerals", "crypto", "bitcoin", "solar", "clean energy", "gold", "silver", "homebuilders", "memory", "chip equipment", "cloud software"],
|
|
451
|
+
keywords: ["themes", "thematic", "baskets", "AI compute", "datacenters", "electrification", "power grid", "nuclear", "uranium", "defense", "aerospace", "cybersecurity", "robotics", "automation", "critical minerals", "crypto", "bitcoin", "bitcoin miners", "miners", "bitcoin mining", "solar", "clean energy", "gold", "silver", "homebuilders", "memory", "chip equipment", "cloud software"],
|
|
446
452
|
shortcut: { prefix: "THEM", argKind: "text", argPlaceholder: "theme", argOptional: true },
|
|
447
453
|
headless: themesHeadless,
|
|
448
454
|
// The theme param opens the Themes tab, and the named theme's members.
|
|
@@ -61,6 +61,7 @@ export const SERIES_INVENTORY: readonly SeriesInventoryEntry[] = [
|
|
|
61
61
|
{ module: "futures", chart: "Front-month futures board, charted from the board", series: ["fut:*"] },
|
|
62
62
|
{ module: "futures-curve", chart: "Futures curve by contract month", notInCatalog: "not-a-time-series", reason: "Plotted by delivery month; each front month is in the catalog as FUT:code." },
|
|
63
63
|
{ module: "crypto-board", chart: "Price sparklines for the large coins", series: ["crypto:*"] },
|
|
64
|
+
{ module: "crypto-board", chart: "Bitcoin network hash rate, difficulty, next retarget estimate and hashprice", series: ["bitcoin:*"] },
|
|
64
65
|
{ module: "perps", chart: "Perpetual funding, open interest, premium and long/short account share", series: ["perps:*"] },
|
|
65
66
|
{ 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
67
|
{ 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." },
|
|
@@ -96,6 +97,7 @@ export const SERIES_INVENTORY: readonly SeriesInventoryEntry[] = [
|
|
|
96
97
|
{ module: "analytics", chart: "Portfolio value, drawdown and factor exposure", notInCatalog: "account", reason: "Built from the user's own portfolio; the factor legs are tickers in the catalog." },
|
|
97
98
|
{ module: "holders", chart: "Holders treemap", notInCatalog: "not-a-time-series", reason: "A treemap of one filing date." },
|
|
98
99
|
{ module: "market-heatmap", chart: "Market heat map", notInCatalog: "not-a-time-series", reason: "A treemap of today's moves." },
|
|
100
|
+
{ module: "buildout", chart: "TheBuildout's theme board", notInCatalog: "not-a-time-series", reason: "A treemap of the themes' market caps, coloured by their median 1Y return today." },
|
|
99
101
|
{ module: "portfolio-list", chart: "Portfolio treemap", notInCatalog: "not-a-time-series", reason: "A treemap of current positions." },
|
|
100
102
|
{ module: "world-venue-map", chart: "Chokepoint transits and the map's place series", series: ["geo:*"] },
|
|
101
103
|
];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { logFailure } from "../../../utils/log-failure";
|
|
2
|
-
import { ApiRequestError
|
|
3
|
-
import
|
|
2
|
+
import { ApiRequestError } from "../../../api-client/errors";
|
|
3
|
+
import { createMissingRouteLatch } from "../../../api-client/coalesce";
|
|
4
|
+
import { savedCopyForTransientFailure, type PluginCacheResult } from "../../../data/plugin-cache";
|
|
4
5
|
|
|
5
6
|
interface CloudCache<T> {
|
|
6
7
|
get(key: string, options?: { allowExpired?: boolean }): PluginCacheResult<T> | null;
|
|
@@ -38,9 +39,11 @@ const isAbort = (error: unknown) => !!error && typeof error === "object" && (err
|
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
* Loads through the cache, falling back to the cached copy when a refresh
|
|
41
|
-
* fails
|
|
42
|
-
* session, so the pane shows its sign-in wall
|
|
43
|
-
* no longer see,
|
|
42
|
+
* fails for a reason asking again may get past (`isTransientFailure`). A
|
|
43
|
+
* refusal still throws: a refused session, so the pane shows its sign-in wall
|
|
44
|
+
* instead of data the account can no longer see, any other permanent client
|
|
45
|
+
* error (an unknown series, a route not served), and a reply that fails
|
|
46
|
+
* validation.
|
|
44
47
|
*
|
|
45
48
|
* Callers of one key share the request in flight, which runs on the first
|
|
46
49
|
* caller's `signal`. When that caller goes away (a tab left while it loads,
|
|
@@ -60,8 +63,7 @@ export async function loadCloudResource<T>(
|
|
|
60
63
|
throw error;
|
|
61
64
|
});
|
|
62
65
|
if (!result || cancelledByOther(result.error)) result = await cache.load(key, fetch, { force });
|
|
63
|
-
const saved = result.source === "stale-fallback";
|
|
64
|
-
if (saved && isPermanentClientError(result.error)) throw result.error;
|
|
66
|
+
const saved = savedCopyForTransientFailure(result).source === "stale-fallback";
|
|
65
67
|
// A copy served because its refresh failed is one by where it came from, whatever the failure said (or did not).
|
|
66
68
|
return {
|
|
67
69
|
payload: validate(result.data), stale: result.stale, refreshError: result.refreshError ?? null,
|
|
@@ -72,7 +74,70 @@ export async function loadCloudResource<T>(
|
|
|
72
74
|
/**
|
|
73
75
|
* Turns a route the server does not serve yet (404, or the given statuses)
|
|
74
76
|
* into a message that says what is unavailable; other errors pass through.
|
|
77
|
+
* The status stays, so a 404 is still a refusal no saved copy answers for and
|
|
78
|
+
* a 503 still a failure one may.
|
|
75
79
|
*/
|
|
76
80
|
export function unavailableOnServer(error: unknown, message: string, statuses: readonly number[] = [404]): unknown {
|
|
77
|
-
return error instanceof ApiRequestError && statuses.includes(error.status ?? 0)
|
|
81
|
+
return error instanceof ApiRequestError && statuses.includes(error.status ?? 0)
|
|
82
|
+
? new ApiRequestError(message, error.status, error.retryAfterMs, error.code, error.details)
|
|
83
|
+
: error;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Reads ahead many items' payloads in batch requests (`read`, at most `max`
|
|
88
|
+
* ids each, sent side by side) into the cache their own loads read under
|
|
89
|
+
* `key`, skipping an item cached fresh unless `force`. Answers the ids stored, which a forced load can
|
|
90
|
+
* take as fresh; an item the batch failed or left out stays for its own load
|
|
91
|
+
* to ask, which says why. A batch route the server does not serve (404) is
|
|
92
|
+
* left alone until it is asked again (BATCH_ROUTE_REPROBE_MS).
|
|
93
|
+
*/
|
|
94
|
+
export function createCloudBatchAhead<T>(
|
|
95
|
+
cache: CloudCache<T>,
|
|
96
|
+
options: {
|
|
97
|
+
max: number;
|
|
98
|
+
validate: (payload: T) => T;
|
|
99
|
+
read: (ids: string[]) => Promise<Array<{ id: string; payload: T }>>;
|
|
100
|
+
},
|
|
101
|
+
): (items: ReadonlyArray<{ id: string; key: string }>, force?: boolean) => Promise<Set<string>> {
|
|
102
|
+
const route = createMissingRouteLatch();
|
|
103
|
+
return async (items, force = false) => {
|
|
104
|
+
const stored = new Set<string>();
|
|
105
|
+
const keys = new Map<string, string>();
|
|
106
|
+
for (const { id, key } of items) {
|
|
107
|
+
const cached = cache.get(key);
|
|
108
|
+
if (force || !cached || cached.stale) keys.set(id, key);
|
|
109
|
+
}
|
|
110
|
+
const ids = [...keys.keys()];
|
|
111
|
+
if (route.missing() || ids.length < 2) return stored;
|
|
112
|
+
const chunks = Array.from({ length: Math.ceil(ids.length / options.max) }, (_, index) => ids.slice(index * options.max, (index + 1) * options.max));
|
|
113
|
+
await Promise.all(chunks.map(async (chunk) => {
|
|
114
|
+
const read = await options.read(chunk).catch((error: unknown) => {
|
|
115
|
+
route.refused(error);
|
|
116
|
+
if (!(error instanceof ApiRequestError && error.status === 404)) logFailure("shared.cloud-resource", "Read a batch of cloud payloads ahead failed")(error);
|
|
117
|
+
return [];
|
|
118
|
+
});
|
|
119
|
+
await Promise.all(read.map(async ({ id, payload }) => {
|
|
120
|
+
const key = keys.get(id);
|
|
121
|
+
if (!key) return;
|
|
122
|
+
try {
|
|
123
|
+
const valid = options.validate(payload);
|
|
124
|
+
await cache.load(key, async () => valid, { force: true });
|
|
125
|
+
stored.add(id);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
logFailure("shared.cloud-resource", "Store a batched cloud payload failed")(error);
|
|
128
|
+
}
|
|
129
|
+
}));
|
|
130
|
+
}));
|
|
131
|
+
return stored;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* A batch reply's items. A reply without them is a service that read the
|
|
137
|
+
* batch path as something else (an older one's `/:symbol` route, for a symbol
|
|
138
|
+
* named "batch"), which is a service without the route: a 404.
|
|
139
|
+
*/
|
|
140
|
+
export function batchReplyItems<T>(reply: { items?: T[] } | null | undefined): T[] {
|
|
141
|
+
if (!Array.isArray(reply?.items)) throw new ApiRequestError("This service does not read these in batches", 404);
|
|
142
|
+
return reply.items;
|
|
78
143
|
}
|
|
@@ -11,7 +11,7 @@ import { useRendererHost } from "../../../ui";
|
|
|
11
11
|
import type { RendererHost } from "../../../ui";
|
|
12
12
|
import { getSharedRegistry } from "../../registry";
|
|
13
13
|
import { requestAccountManagementTab } from "../account-management/navigation";
|
|
14
|
-
import { resolvePlanAccess, usePlanAccess, type PlanAccess } from "../../../api-client/plan-access";
|
|
14
|
+
import { freeQuotesDelayMinutes, resolvePlanAccess, usePlanAccess, type PlanAccess, type QuoteDelayFacts } from "../../../api-client/plan-access";
|
|
15
15
|
import { useViewport } from "../../../react/input";
|
|
16
16
|
import { type DialogApi, useOptionalDialog } from "../../../ui/dialog";
|
|
17
17
|
import { promptCloudUpgrade } from "../../../account/upgrade-dialog";
|
|
@@ -365,3 +365,26 @@ export function useCloudAccessFooter({
|
|
|
365
365
|
|
|
366
366
|
return { access, hint, openUpgrade, segment };
|
|
367
367
|
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* The footer's clock for a pane whose prices this account sees late: the free
|
|
371
|
+
* tier's delay (`freeQuotesDelayMinutes`) on any of `quotes`, pressed for the
|
|
372
|
+
* upgrade, as the quote footer's clock is. Null with Pro or a trial, and while
|
|
373
|
+
* every price shown is live, so those accounts see nothing new. A pane whose
|
|
374
|
+
* data comes as one board says so with one `{ dataSource }` for the board.
|
|
375
|
+
*/
|
|
376
|
+
export function useQuoteDelayFooter(
|
|
377
|
+
quotes: Iterable<QuoteDelayFacts | null | undefined>,
|
|
378
|
+
placement: string,
|
|
379
|
+
): PaneFooterSegment | null {
|
|
380
|
+
const language = useAppLanguage();
|
|
381
|
+
const minutes = freeQuotesDelayMinutes(usePlanAccess(), quotes);
|
|
382
|
+
const openUpgrade = useCloudUpgradeAction(placement);
|
|
383
|
+
return useMemo(() => (minutes == null ? null : delayedFooterSegment({
|
|
384
|
+
id: "quote-delay",
|
|
385
|
+
detail: tf("{delay} delayed · try Pro live", { delay: `${minutes}m` }),
|
|
386
|
+
delay: `${minutes}m`,
|
|
387
|
+
onPress: openUpgrade,
|
|
388
|
+
quoteStatus: true,
|
|
389
|
+
})), [language, minutes, openUpgrade]);
|
|
390
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The step a daily study takes before it reads closes (BT's run, HVG's
|
|
3
|
+
* estimates): the newest session is left out while it is not final, and the
|
|
4
|
+
* report says so. Not final is a session still trading (`partial`: its close
|
|
5
|
+
* is the latest price) or a future's closed session whose settlement is not
|
|
6
|
+
* in yet (`unsettled`: its close is the last trade). Either close moves
|
|
7
|
+
* before the day is done, so a study that kept it would give another answer
|
|
8
|
+
* read again.
|
|
9
|
+
*/
|
|
10
|
+
interface DatedBar {
|
|
11
|
+
partial?: boolean;
|
|
12
|
+
unsettled?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isFormingBar(bar: DatedBar): boolean {
|
|
16
|
+
return bar.partial === true || bar.unsettled === true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* What the bars left out are, after their dates: "is still forming
|
|
21
|
+
* (partial)", "is not settled yet (unsettled)", or for several of both kinds
|
|
22
|
+
* "are still forming or not settled yet".
|
|
23
|
+
*/
|
|
24
|
+
export function formingBarsState(bars: readonly DatedBar[]): string {
|
|
25
|
+
const verb = bars.length === 1 ? "is" : "are";
|
|
26
|
+
if (bars.every((bar) => bar.partial === true)) return `${verb} still forming (partial)`;
|
|
27
|
+
if (bars.every((bar) => bar.unsettled === true && bar.partial !== true)) return `${verb} not settled yet (unsettled)`;
|
|
28
|
+
return `${verb} still forming or not settled yet`;
|
|
29
|
+
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { apiClient } from "../../../api-client";
|
|
2
2
|
import { loadCachedFredSeries } from "../../../sources/gloomberb-cloud/fred-series";
|
|
3
3
|
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* A FRED series from `start` on, oldest first, through the shared FRED cache;
|
|
6
|
+
* `savedAt` when a failed refresh left the copy saved then answering.
|
|
7
|
+
*/
|
|
5
8
|
export async function loadFredPoints(seriesId: string, start?: Date): Promise<{
|
|
6
9
|
points: Array<{ date: string; value: number | null }>;
|
|
7
10
|
stale: boolean;
|
|
8
11
|
refreshError?: string;
|
|
12
|
+
savedAt?: number;
|
|
9
13
|
}> {
|
|
10
14
|
const startDate = start ? start.toISOString().slice(0, 10) : "1900-01-01";
|
|
11
15
|
const result = await loadCachedFredSeries(
|
|
@@ -16,5 +20,6 @@ export async function loadFredPoints(seriesId: string, start?: Date): Promise<{
|
|
|
16
20
|
points: (result.data.observations ?? []).map((row) => ({ date: row.date, value: row.value })),
|
|
17
21
|
stale: result.stale,
|
|
18
22
|
...(result.refreshError ? { refreshError: result.refreshError } : {}),
|
|
23
|
+
...(result.source === "stale-fallback" ? { savedAt: result.fetchedAt } : {}),
|
|
19
24
|
};
|
|
20
25
|
}
|
|
@@ -2,6 +2,7 @@ import { intradayBarText } from "../../../market-data/quotes/intraday-bar";
|
|
|
2
2
|
import type { Quote, QuoteIntradayBar } from "../../../types/financials";
|
|
3
3
|
import { isFxMarketOpen } from "../../../utils/fx-market-hours";
|
|
4
4
|
import { quoteFreshnessFields } from "./report-freshness";
|
|
5
|
+
import { formatUtcDayTime } from "../../../utils/utc-time";
|
|
5
6
|
|
|
6
7
|
/** The venue FX pairs trade on, so a report can place the FX market. */
|
|
7
8
|
const FX_VENUE = "CCY";
|
|
@@ -56,6 +57,22 @@ export function fxRateNotes(legs: ReadonlyMap<string, FxRateTiming>, now = Date.
|
|
|
56
57
|
});
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
/**
|
|
61
|
+
* What `fx --refresh` says of a rate it read again and found still stale with
|
|
62
|
+
* no reason given (`ZAR/USD: --refresh found no newer rate than Tue 27 Oct
|
|
63
|
+
* 14:00 UTC.`): the refresh did run, and the data service has nothing newer.
|
|
64
|
+
* A rate whose refresh failed says that instead, and one with a stated reason
|
|
65
|
+
* says the reason (fxRateNotes).
|
|
66
|
+
*/
|
|
67
|
+
export function refreshedFxRateNotes(legs: ReadonlyMap<string, FxRateTiming>, now = Date.now()): string[] {
|
|
68
|
+
return [...legs].flatMap(([currency, timing]) => {
|
|
69
|
+
if (currency === "USD" || timing.error != null || timing.staleReason || timing.staleAt == null || timing.staleAt > now) return [];
|
|
70
|
+
const since = timing.asOf != null && Number.isFinite(timing.asOf)
|
|
71
|
+
? ` than ${formatUtcDayTime({ time: timing.asOf, dateOnly: false }, now)} UTC` : "";
|
|
72
|
+
return [`${currency}/USD: --refresh found no newer rate${since}.`];
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
59
76
|
function isoTime(time: number | undefined | null): string | undefined {
|
|
60
77
|
return time != null && Number.isFinite(time) ? new Date(time).toISOString() : undefined;
|
|
61
78
|
}
|
|
@@ -70,19 +70,23 @@ async function routeAnswers(route: FreeRoute, check: NonNullable<FreeRoute["answ
|
|
|
70
70
|
* symbol the routes read `<ticker>` and the first is named; without a way to
|
|
71
71
|
* read data, only a route that answers for every symbol is.
|
|
72
72
|
*/
|
|
73
|
-
async function answeringFreeRoute(lock: PaneLock, routes: readonly FreeRoute[]): Promise<FreeRoute | null> {
|
|
73
|
+
async function answeringFreeRoute(lock: PaneLock, routes: readonly FreeRoute[]): Promise<{ route: FreeRoute | null; noneHasData: boolean }> {
|
|
74
74
|
const { symbol, context } = lock;
|
|
75
|
-
if (!symbol) return routes[0] ?? null;
|
|
75
|
+
if (!symbol) return { route: routes[0] ?? null, noneHasData: false };
|
|
76
76
|
// Routes past the first that needs no check are never named, so they are not checked.
|
|
77
77
|
const unchecked = routes.findIndex((route) => !route.answers);
|
|
78
78
|
const candidates = unchecked < 0 ? routes : routes.slice(0, unchecked + 1);
|
|
79
79
|
const deadline = Date.now() + ROUTE_CHECK_WAIT_MS;
|
|
80
80
|
const checks = candidates.map((route) => !route.answers ? Promise.resolve(true)
|
|
81
|
-
: context ? routeAnswers(route, route.answers, symbol, context, deadline) : Promise.resolve(
|
|
81
|
+
: context ? routeAnswers(route, route.answers, symbol, context, deadline) : Promise.resolve(null));
|
|
82
|
+
const answered: Array<boolean | null> = [];
|
|
82
83
|
for (const [index, check] of checks.entries()) {
|
|
83
|
-
|
|
84
|
+
const answer = await check;
|
|
85
|
+
if (answer) return { route: candidates[index]!, noneHasData: false };
|
|
86
|
+
answered.push(answer);
|
|
84
87
|
}
|
|
85
|
-
|
|
88
|
+
// Every check answered no: a failed or stalled one is not known to.
|
|
89
|
+
return { route: null, noneHasData: candidates.length > 0 && answered.every((answer) => answer === false) };
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
/**
|
|
@@ -114,6 +118,14 @@ export async function proLockNote(
|
|
|
114
118
|
* command repeats the ticker only when the reader gave one. Default `ticker`.
|
|
115
119
|
*/
|
|
116
120
|
argument?: "ticker" | "optional" | "none";
|
|
121
|
+
/**
|
|
122
|
+
* For a function that reads what its free routes read (RISK the SEC's
|
|
123
|
+
* 10-Ks and 20-Fs, FLOW the listing's options): the sentence the note
|
|
124
|
+
* says when every route answered that the symbol has none. The note then
|
|
125
|
+
* names no command for the symbol, which would read nothing once unlocked
|
|
126
|
+
* either: IAG:LSE files neither form, and LHA:XETRA has no options served.
|
|
127
|
+
*/
|
|
128
|
+
noneFor?: (ticker: string) => string;
|
|
117
129
|
},
|
|
118
130
|
): Promise<string> {
|
|
119
131
|
const { gate, symbol, token: typed, verifyEmail } = lock;
|
|
@@ -129,12 +141,15 @@ export async function proLockNote(
|
|
|
129
141
|
const unlock = gate === "pro"
|
|
130
142
|
? `To read ${pane.shows} in ${token}, upgrade in the app (gloom, then Ctrl+P, UPGRADE)`
|
|
131
143
|
: `To read ${pane.shows} in ${token}, open the app (gloom), ${account}${pro ? ", then upgrade (Ctrl+P, UPGRADE)" : ""}`;
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
const
|
|
144
|
+
const { route: free, noneHasData } = pane.free ? await answeringFreeRoute(lock, pane.free) : { route: null, noneHasData: false };
|
|
145
|
+
// Unlocking reads nothing for a symbol none of the function's own data covers: it is named only where it has some.
|
|
146
|
+
const uncovered = noneHasData && !!pane.noneFor;
|
|
147
|
+
const bare = pane.argument === "none" || (pane.argument === "optional" && (!symbol || uncovered));
|
|
148
|
+
const command = bare ? `gloom fn ${token}` : `gloom fn ${token} ${uncovered ? "<ticker>" : ticker}`;
|
|
135
149
|
return [
|
|
136
150
|
`${token} ${what}: ${pane.does}. ${without} it shows none of ${pane.shows}.`,
|
|
137
151
|
...free ? [`Free, with no sign-in: ${free.text(ticker, symbol ?? "the company")}`] : [],
|
|
152
|
+
...uncovered ? [pane.noneFor!(ticker)] : [],
|
|
138
153
|
`${unlock}; ${command} then reads with that session.`,
|
|
139
154
|
].join("\n");
|
|
140
155
|
}
|
|
@@ -111,7 +111,9 @@ export function SeriesDetailChart({
|
|
|
111
111
|
}) {
|
|
112
112
|
const [userViewport, setUserViewport] = useState<{ start: Date; end: Date } | null>(null);
|
|
113
113
|
const chartWidth = Math.max(24, width);
|
|
114
|
-
|
|
114
|
+
// A figure wraps only into rows the chart's least leaves, so the pane never spills.
|
|
115
|
+
const statMaxLines = height - MIN_CHART_ROWS - aboveChartRows - belowChartRows;
|
|
116
|
+
const statRows = statGridRows(stats, width, undefined, { maxLines: statMaxLines });
|
|
115
117
|
const chartHeight = Math.max(MIN_CHART_ROWS, height - statRows - aboveChartRows - belowChartRows);
|
|
116
118
|
const { unit, unitGroup, nativeFrequency } = axis;
|
|
117
119
|
|
|
@@ -139,7 +141,7 @@ export function SeriesDetailChart({
|
|
|
139
141
|
|
|
140
142
|
return (
|
|
141
143
|
<Box flexDirection="column" width={width}>
|
|
142
|
-
<StatGrid items={stats} width={width} />
|
|
144
|
+
<StatGrid items={stats} width={width} maxLines={statMaxLines} />
|
|
143
145
|
{drawable ? (
|
|
144
146
|
<>
|
|
145
147
|
{aboveChart?.(chartWidth)}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* What a loaded catalog series says of an old copy. A copy that answered
|
|
3
|
+
* because its refresh failed carries when it was saved (`savedAt`), which the
|
|
4
|
+
* chart's footer shows as "saved copy from …", the note every pane gives it.
|
|
5
|
+
* A cached copy the source has not updated since is a note on the chart.
|
|
6
|
+
*/
|
|
7
|
+
export function cachedSeriesNote(resource: { stale?: boolean; refreshError?: string | null; savedAt?: number | null }): {
|
|
8
|
+
warning?: string;
|
|
9
|
+
savedAt?: number;
|
|
10
|
+
} {
|
|
11
|
+
if (resource.savedAt != null && Number.isFinite(resource.savedAt)) return { savedAt: resource.savedAt };
|
|
12
|
+
if (resource.refreshError) return { warning: `Refresh failed (${resource.refreshError}); showing cached data.` };
|
|
13
|
+
return resource.stale ? { warning: "Showing cached data; the source has not updated since." } : {};
|
|
5
14
|
}
|
|
@@ -520,6 +520,11 @@ export function quoteBoardStatus(quotes: BoardQuoteMap): QuoteBoardStatus {
|
|
|
520
520
|
return { loading, stale, unavailable, latestTs, savedAt: oldestSavedCopyAt(Array.from(quotes.values(), (state) => state.quote)) };
|
|
521
521
|
}
|
|
522
522
|
|
|
523
|
+
/** The quotes a board shows, for the footer's delayed clock (`useQuoteDelayFooter`). */
|
|
524
|
+
export function* boardQuotes(quotes: BoardQuoteMap): Generator<Quote | null> {
|
|
525
|
+
for (const state of quotes.values()) yield state.quote;
|
|
526
|
+
}
|
|
527
|
+
|
|
523
528
|
/** Board footer status: everything here changes as loads succeed or fail. */
|
|
524
529
|
export function quoteBoardFooterInfo(status: QuoteBoardStatus): PaneFooterSegment[] {
|
|
525
530
|
const info: PaneFooterSegment[] = [];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useMemo } from "react";
|
|
2
2
|
import { DataTableStackView, EmptyState, PaneStatusBody, usePaneNoticeFooter, usePaneStatusLinkFooter, type DataTableCell } from "../../../components";
|
|
3
|
+
import { ageFooterSegment } from "../../../components/layout/pane/footer";
|
|
3
4
|
import { DetailReader } from "../../../components/reader/detail-reader";
|
|
4
5
|
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
5
6
|
import { useAsyncResource, useAutoRefresh, usePluginPaneState, useUpdatedAgo } from "../../../public/react";
|
|
@@ -40,7 +41,7 @@ export function ShortSidePublicationPane({ kind, width, height, focused }: Pick<
|
|
|
40
41
|
notices: [...(data?.warnings ?? []), ...(resource.data?.refreshError ? [resource.data.refreshError] : [])] });
|
|
41
42
|
usePaneStatusLinkFooter({ registrationId: kind, focused, loading: resource.loading, error: resource.error,
|
|
42
43
|
url: selected?.sourceUrl ?? data?.source.url ?? null, showOpenHint: true,
|
|
43
|
-
info: data && updatedAgo ? [
|
|
44
|
+
info: data && updatedAgo ? [ageFooterSegment(updatedAgo)] : [],
|
|
44
45
|
stale: !!data && resource.data?.stale,
|
|
45
46
|
});
|
|
46
47
|
if (!symbol) return <EmptyState title="No ticker selected." message={`Select a US stock to view ${subject}.`} />;
|
|
@@ -3,6 +3,7 @@ import { listingIdentity } from "../shared/ticker-request";
|
|
|
3
3
|
import { Box, ScrollBox, useUiCapabilities } from "../../../ui";
|
|
4
4
|
import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginPaneState, useUpdatedAgo } from "../../../public/react";
|
|
5
5
|
import { ChartTableHeader, DataTableStackView, EmptyState, formatPercentAxis, KeyValueRow, PaneStatusBody, useChartTableSelection, usePaneNoticeFooter, usePaneStatusLinkFooter, type DataTableCell, type StatItem } from "../../../components";
|
|
6
|
+
import { ageFooterSegment } from "../../../components/layout/pane/footer";
|
|
6
7
|
import { colors } from "../../../theme/colors";
|
|
7
8
|
import type { ShortVolumeObservation } from "../../../api-client/short-volume";
|
|
8
9
|
import { isAccessDenied } from "../../../api-client/errors";
|
|
@@ -83,7 +84,7 @@ export function ShortVolumePane({ width, height, focused }: Pick<PaneProps, "wid
|
|
|
83
84
|
notices: [...(data?.warnings ?? []), ...(resource.data?.refreshError ? [resource.data.refreshError] : [])] });
|
|
84
85
|
usePaneStatusLinkFooter({ registrationId: "short-volume", focused, loading: resource.loading, error: resource.error,
|
|
85
86
|
url: selected?.sourceUrl ?? data?.source.url ?? null, showOpenHint: true,
|
|
86
|
-
info: data && updatedAgo ? [
|
|
87
|
+
info: data && updatedAgo ? [ageFooterSegment(updatedAgo)] : [],
|
|
87
88
|
stale: !!data && resource.data?.stale,
|
|
88
89
|
});
|
|
89
90
|
if (!data && isCloudSessionRequired(resource.error)) return <SignInWall placement="short-volume-signin" action="view daily short volume" needsVerification={session.needsVerification} />;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SeriesDefinition } from "../../../series/types";
|
|
2
|
-
import {
|
|
2
|
+
import { cachedSeriesNote } from "../shared/series-warning";
|
|
3
3
|
import { loadShortVolume } from "./client";
|
|
4
4
|
|
|
5
5
|
/** The share of a ticker's daily on-exchange volume reported as short sales. */
|
|
@@ -14,10 +14,9 @@ export const SHORT_VOLUME_SERIES: readonly SeriesDefinition[] = [{
|
|
|
14
14
|
input: "ticker",
|
|
15
15
|
load: async ({ ticker }) => {
|
|
16
16
|
const resource = await loadShortVolume(ticker!, "nms");
|
|
17
|
-
const warning = cachedSeriesWarning(resource);
|
|
18
17
|
return {
|
|
19
18
|
points: resource.payload.history.map((point) => ({ date: point.date, value: point.ratioPercent })),
|
|
20
|
-
...(
|
|
19
|
+
...cachedSeriesNote(resource),
|
|
21
20
|
};
|
|
22
21
|
},
|
|
23
22
|
}];
|