@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,143 @@
|
|
|
1
|
+
import { apiClient } from "../../../api-client";
|
|
2
|
+
import { ApiRequestError } from "../../../api-client/errors";
|
|
3
|
+
import type { BitcoinNetworkDailyValue, BitcoinNetworkPayload } from "../../../api-client/crypto-markets";
|
|
4
|
+
import { createPluginCache } from "../../../data/plugin-cache";
|
|
5
|
+
import type { SeriesAttribution, SeriesDefinition, SeriesPoint } from "../../../series/types";
|
|
6
|
+
import { isFiniteNumber } from "../../../utils/guards";
|
|
7
|
+
import { loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
8
|
+
import { cachedSeriesNote } from "../shared/series-warning";
|
|
9
|
+
|
|
10
|
+
const bitcoinNetworkCache = createPluginCache<BitcoinNetworkPayload>({
|
|
11
|
+
kind: "bitcoin-network",
|
|
12
|
+
source: "gloom-cloud",
|
|
13
|
+
schemaVersion: 1,
|
|
14
|
+
// The server reads the estimate every half hour and the daily figures every six hours.
|
|
15
|
+
policy: { staleMs: 30 * 60_000, expireMs: 30 * 86_400_000 },
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const BITCOIN_NETWORK_CACHES = [bitcoinNetworkCache] as const;
|
|
19
|
+
|
|
20
|
+
const MAX_DAYS = 20_000;
|
|
21
|
+
const date = (value: unknown): value is string => typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value);
|
|
22
|
+
const instant = (value: unknown): value is string => typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
23
|
+
const daily = (rows: unknown): rows is BitcoinNetworkDailyValue[] => Array.isArray(rows) && rows.length <= MAX_DAYS
|
|
24
|
+
&& rows.every((row) => !!row && date((row as BitcoinNetworkDailyValue).date) && isFiniteNumber((row as BitcoinNetworkDailyValue).value));
|
|
25
|
+
|
|
26
|
+
function validateBitcoinNetwork(data: BitcoinNetworkPayload): BitcoinNetworkPayload {
|
|
27
|
+
const retarget = data?.retarget;
|
|
28
|
+
if (
|
|
29
|
+
!data
|
|
30
|
+
|| data.version !== 1
|
|
31
|
+
|| !instant(data.generatedAt)
|
|
32
|
+
|| typeof data.source?.name !== "string" || !data.source.name.trim()
|
|
33
|
+
|| !["available", "stale", "unavailable"].includes(data.status)
|
|
34
|
+
|| !daily(data.hashrate)
|
|
35
|
+
|| !daily(data.hashprice)
|
|
36
|
+
|| !daily(data.retargetHistory)
|
|
37
|
+
|| !Array.isArray(data.difficulty)
|
|
38
|
+
|| !data.difficulty.every((row) => !!row && date(row.date) && isFiniteNumber(row.difficulty) && row.difficulty > 0)
|
|
39
|
+
|| (retarget !== null && (!retarget || !isFiniteNumber(retarget.changePercent) || !instant(retarget.estimatedAt) || !instant(retarget.readAt)))
|
|
40
|
+
|| !Array.isArray(data.warnings)
|
|
41
|
+
) {
|
|
42
|
+
throw new Error("The server returned invalid bitcoin network data");
|
|
43
|
+
}
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function fetchBitcoinNetwork(client: Pick<typeof apiClient, "getCloudBitcoinNetwork"> = apiClient): Promise<BitcoinNetworkPayload> {
|
|
48
|
+
try {
|
|
49
|
+
return validateBitcoinNetwork(await client.getCloudBitcoinNetwork());
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error instanceof ApiRequestError && error.status === 503) throw unavailableOnServer(error, "Bitcoin network data is temporarily unavailable", [503]);
|
|
52
|
+
throw unavailableOnServer(error, "Bitcoin network data is not available yet.");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function loadBitcoinNetwork() {
|
|
57
|
+
return loadCloudResource(bitcoinNetworkCache, "all", () => fetchBitcoinNetwork(), { validate: validateBitcoinNetwork });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const points = (rows: readonly BitcoinNetworkDailyValue[]): SeriesPoint[] =>
|
|
61
|
+
rows.map((row) => ({ date: Date.parse(`${row.date}T00:00:00Z`), value: row.value }));
|
|
62
|
+
|
|
63
|
+
const TAGS = ["bitcoin", "BTC", "BTC-USD", "mining", "miner", "miners", "bitcoin mining", "network", "on-chain"];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A series of the network board: its points, cited as the server names its
|
|
67
|
+
* source, with the board's warnings about this figure (`concerns`: "Hash
|
|
68
|
+
* rate, difficulty and hashprice could not refresh" is three series', "The
|
|
69
|
+
* retarget estimate is unavailable" the estimate's alone) and the
|
|
70
|
+
* cached-copy note.
|
|
71
|
+
*/
|
|
72
|
+
function networkSeries(
|
|
73
|
+
definition: Omit<Extract<SeriesDefinition, { load: unknown }>, "load" | "frequency" | "group">
|
|
74
|
+
& { frequency?: SeriesDefinition["frequency"] },
|
|
75
|
+
read: (payload: BitcoinNetworkPayload) => SeriesPoint[],
|
|
76
|
+
{ concerns, derived }: { concerns: RegExp; derived?: string },
|
|
77
|
+
): SeriesDefinition {
|
|
78
|
+
return {
|
|
79
|
+
frequency: "daily",
|
|
80
|
+
group: "Crypto",
|
|
81
|
+
refreshMs: 30 * 60_000,
|
|
82
|
+
...definition,
|
|
83
|
+
load: async () => {
|
|
84
|
+
const resource = await loadBitcoinNetwork();
|
|
85
|
+
const found = read(resource.payload);
|
|
86
|
+
if (!found.length) throw new Error(`${definition.name} is not available yet.`);
|
|
87
|
+
const note = cachedSeriesNote(resource);
|
|
88
|
+
const own = resource.payload.warnings.filter((line) => concerns.test(line)).join(" ");
|
|
89
|
+
const warning = note.warning ?? (own || undefined);
|
|
90
|
+
const source = resource.payload.source.name.trim();
|
|
91
|
+
const attribution: SeriesAttribution = { source: derived ? `${source}, ${derived}` : source };
|
|
92
|
+
return { points: found, attribution, ...note, ...warning ? { warning } : {} };
|
|
93
|
+
},
|
|
94
|
+
} as SeriesDefinition;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The bitcoin network for `fn G`: hash rate, difficulty, the next
|
|
99
|
+
* retarget's estimate and hashprice. `fn G hashrate`, `difficulty`,
|
|
100
|
+
* `retarget` and `hashprice` chart them by their aliases; `catalog
|
|
101
|
+
* hashrate`, `hashprice`, `difficulty`, `mining` and `miner` find them by
|
|
102
|
+
* their tags.
|
|
103
|
+
*/
|
|
104
|
+
export const BITCOIN_NETWORK_SERIES: readonly SeriesDefinition[] = [
|
|
105
|
+
networkSeries({
|
|
106
|
+
id: "bitcoin:hashrate",
|
|
107
|
+
name: "Bitcoin network hash rate",
|
|
108
|
+
unit: "EH/s",
|
|
109
|
+
tags: [...TAGS, "hashrate", "hash rate"],
|
|
110
|
+
aliases: ["hashrate"],
|
|
111
|
+
description: "The bitcoin network's hash rate in exahashes per second, each UTC day's average, estimated from the blocks found; the newest day is the day so far.",
|
|
112
|
+
}, (payload) => points(payload.hashrate), { concerns: /\bhash rate\b/i }),
|
|
113
|
+
networkSeries({
|
|
114
|
+
id: "bitcoin:difficulty",
|
|
115
|
+
name: "Bitcoin mining difficulty (trillions)",
|
|
116
|
+
unit: "number",
|
|
117
|
+
frequency: "irregular",
|
|
118
|
+
style: "step",
|
|
119
|
+
tags: [...TAGS, "difficulty", "retarget", "difficulty adjustment"],
|
|
120
|
+
aliases: ["difficulty"],
|
|
121
|
+
description: "The difficulty each retarget set, every 2,016 blocks, in trillions, dated by the block that set it.",
|
|
122
|
+
}, (payload) => payload.difficulty.map((row) => ({ date: Date.parse(`${row.date}T00:00:00Z`), value: Number((row.difficulty / 1e12).toFixed(4)) })),
|
|
123
|
+
{ concerns: /\bdifficulty\b/i }),
|
|
124
|
+
networkSeries({
|
|
125
|
+
id: "bitcoin:retarget-estimate",
|
|
126
|
+
name: "Bitcoin next difficulty retarget estimate",
|
|
127
|
+
unit: "%",
|
|
128
|
+
tags: [...TAGS, "difficulty", "retarget", "difficulty adjustment", "estimate"],
|
|
129
|
+
aliases: ["retarget"],
|
|
130
|
+
description: "The estimated change in difficulty at the next retarget, in percent, as read each UTC day since Gloom began keeping it in October 2026.",
|
|
131
|
+
}, (payload) => points(payload.retargetHistory), { concerns: /\bretarget\b/i }),
|
|
132
|
+
networkSeries({
|
|
133
|
+
id: "bitcoin:hashprice",
|
|
134
|
+
name: "Bitcoin hashprice (derived)",
|
|
135
|
+
unit: "USD/PH/s/day",
|
|
136
|
+
tags: [...TAGS, "hashprice", "hash price", "miner revenue"],
|
|
137
|
+
aliases: ["hashprice"],
|
|
138
|
+
description: "Derived, not quoted: what 1 PH/s is expected to earn in a day, in USD, at the day's difficulty, average block reward (subsidy and fees) and BTC price.",
|
|
139
|
+
}, (payload) => points(payload.hashprice), {
|
|
140
|
+
concerns: /\bhashprice\b/i,
|
|
141
|
+
derived: "derived from the block rewards, the BTC price and the difficulty",
|
|
142
|
+
}),
|
|
143
|
+
];
|
|
@@ -145,10 +145,10 @@ export async function fetchCryptoMarkets(
|
|
|
145
145
|
try {
|
|
146
146
|
return validateCryptoMarkets(await client.getCloudCryptoMarkets());
|
|
147
147
|
} catch (error) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
// Each keeps its status: a 503 passes with time and a saved copy answers it; a 404 does not.
|
|
149
|
+
throw error instanceof ApiRequestError && error.status === 503
|
|
150
|
+
? unavailableOnServer(error, "Crypto prices are temporarily unavailable", [503])
|
|
151
|
+
: unavailableOnServer(error, "Crypto markets are not available yet.");
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
@@ -248,7 +248,7 @@ function validateStablecoinSupplyHistory(data: StablecoinSupplyHistoryPayload):
|
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
function supplyError(error: unknown, what: string): unknown {
|
|
251
|
-
if (error instanceof ApiRequestError && error.status === 503) return
|
|
251
|
+
if (error instanceof ApiRequestError && error.status === 503) return unavailableOnServer(error, `${what} is temporarily unavailable`, [503]);
|
|
252
252
|
return unavailableOnServer(error, `${what} is not available yet.`);
|
|
253
253
|
}
|
|
254
254
|
|
|
@@ -31,15 +31,16 @@ const COIN_COLUMNS: HeadlessPaneColumn[] = [
|
|
|
31
31
|
{ key: "rank", header: "#", align: "right" },
|
|
32
32
|
{ key: "code", header: "Coin" },
|
|
33
33
|
{ key: "name", header: "Name" },
|
|
34
|
-
|
|
34
|
+
// Every coin is priced, traded and valued in US dollars: the text headers say so; an export keeps its headers.
|
|
35
|
+
{ key: "price", header: "Price", textHeader: "Price USD", align: "right", format: (_value, row) => String(row.priceText ?? "—") },
|
|
35
36
|
// Since 00:00 UTC, the day `quote` gives a coin (CRYPTO_CHANGE_BASIS_LABEL): the text says so, as the pane's
|
|
36
37
|
// CHG% UTC does, and an export keeps the header it has always had.
|
|
37
38
|
{ key: "changePercent", header: "Chg %", textHeader: "Chg % UTC", align: "right", format: percent },
|
|
38
39
|
{ key: "return7d", header: "7D %", align: "right", format: percent },
|
|
39
40
|
{ key: "return30d", header: "30D %", align: "right", format: percent },
|
|
40
41
|
{ key: "return1y", header: "1Y %", align: "right", format: percent },
|
|
41
|
-
{ key: "volume24h", header: "Vol 24h", align: "right", format: compact },
|
|
42
|
-
{ key: "marketCap", header: "Market cap", align: "right", format: compact },
|
|
42
|
+
{ key: "volume24h", header: "Vol 24h", textHeader: "Vol 24h USD", align: "right", format: compact },
|
|
43
|
+
{ key: "marketCap", header: "Market cap", textHeader: "Market cap USD", align: "right", format: compact },
|
|
43
44
|
];
|
|
44
45
|
|
|
45
46
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { PluginModule } from "../plugin-module";
|
|
2
2
|
import { LIVE_STREAMING_QUICK_SETTING, withLiveStreamingSetting } from "../../../state/hooks/live-streaming";
|
|
3
3
|
import { cryptoMarketsCache, DEFI_TVL_CACHES, STABLECOIN_SUPPLY_CACHES } from "./client";
|
|
4
|
+
import { BITCOIN_NETWORK_CACHES } from "./bitcoin-network";
|
|
4
5
|
import { cryptoBoardHeadless } from "./headless";
|
|
5
|
-
import { CryptoBoardPane } from "./pane";
|
|
6
6
|
import { CRYPTO_SERIES } from "./series";
|
|
7
7
|
|
|
8
8
|
export const cryptoBoardModule: PluginModule = {
|
|
@@ -12,7 +12,7 @@ export const cryptoBoardModule: PluginModule = {
|
|
|
12
12
|
id: "crypto-board",
|
|
13
13
|
name: "Crypto",
|
|
14
14
|
icon: "C",
|
|
15
|
-
component: CryptoBoardPane,
|
|
15
|
+
get component(): typeof import("./pane").CryptoBoardPane { const { CryptoBoardPane } = require("./pane"); return CryptoBoardPane; },
|
|
16
16
|
defaultPosition: "right",
|
|
17
17
|
defaultMode: "floating",
|
|
18
18
|
defaultFloatingSize: { width: 120, height: 32 },
|
|
@@ -40,10 +40,12 @@ export const cryptoBoardModule: PluginModule = {
|
|
|
40
40
|
cryptoMarketsCache.attach(ctx.persistence);
|
|
41
41
|
for (const cache of STABLECOIN_SUPPLY_CACHES) cache.attach(ctx.persistence);
|
|
42
42
|
for (const cache of DEFI_TVL_CACHES) cache.attach(ctx.persistence);
|
|
43
|
+
for (const cache of BITCOIN_NETWORK_CACHES) cache.attach(ctx.persistence);
|
|
43
44
|
},
|
|
44
45
|
dispose() {
|
|
45
46
|
cryptoMarketsCache.reset();
|
|
46
47
|
for (const cache of STABLECOIN_SUPPLY_CACHES) cache.reset();
|
|
47
48
|
for (const cache of DEFI_TVL_CACHES) cache.reset();
|
|
49
|
+
for (const cache of BITCOIN_NETWORK_CACHES) cache.reset();
|
|
48
50
|
},
|
|
49
51
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SeriesDefinition, SeriesPoint } from "../../../series/types";
|
|
2
|
-
import {
|
|
2
|
+
import { cachedSeriesNote } from "../shared/series-warning";
|
|
3
3
|
import { loadDefiTvl, loadStablecoinSupply, loadStablecoinSupplyHistory } from "./client";
|
|
4
|
+
import { BITCOIN_NETWORK_SERIES } from "./bitcoin-network";
|
|
4
5
|
|
|
5
6
|
const DAY_MS = 86_400_000;
|
|
6
7
|
|
|
@@ -29,7 +30,7 @@ const STABLECOIN_SUPPLY_SERIES: readonly SeriesDefinition[] = [
|
|
|
29
30
|
const resource = await loadStablecoinSupply();
|
|
30
31
|
const history = resource.payload.history;
|
|
31
32
|
if (!history) throw new Error("Stablecoin supply is not available yet.");
|
|
32
|
-
return { points: dailyPoints(history.start, history.total),
|
|
33
|
+
return { points: dailyPoints(history.start, history.total), ...cachedSeriesNote(resource) };
|
|
33
34
|
},
|
|
34
35
|
},
|
|
35
36
|
{
|
|
@@ -45,7 +46,7 @@ const STABLECOIN_SUPPLY_SERIES: readonly SeriesDefinition[] = [
|
|
|
45
46
|
const resource = await loadStablecoinSupplyHistory(stablecoinSymbol(ticker!));
|
|
46
47
|
const history = resource.payload.history;
|
|
47
48
|
if (!history) throw new Error(`${resource.payload.symbol} supply history is not available yet.`);
|
|
48
|
-
return { points: dailyPoints(history.start, history.values),
|
|
49
|
+
return { points: dailyPoints(history.start, history.values), ...cachedSeriesNote(resource) };
|
|
49
50
|
},
|
|
50
51
|
},
|
|
51
52
|
];
|
|
@@ -63,7 +64,7 @@ const DEFI_TVL_SERIES: SeriesDefinition = {
|
|
|
63
64
|
const resource = await loadDefiTvl();
|
|
64
65
|
const history = resource.payload.history;
|
|
65
66
|
if (!history) throw new Error("DeFi TVL is not available yet.");
|
|
66
|
-
return { points: dailyPoints(history.start, history.total),
|
|
67
|
+
return { points: dailyPoints(history.start, history.total), ...cachedSeriesNote(resource) };
|
|
67
68
|
},
|
|
68
69
|
};
|
|
69
70
|
|
|
@@ -114,4 +115,5 @@ export const CRYPTO_SERIES: readonly SeriesDefinition[] = [
|
|
|
114
115
|
},
|
|
115
116
|
...STABLECOIN_SUPPLY_SERIES,
|
|
116
117
|
DEFI_TVL_SERIES,
|
|
118
|
+
...BITCOIN_NETWORK_SERIES,
|
|
117
119
|
];
|
|
@@ -18,7 +18,9 @@ const textOrNull = (value: unknown) => value === null || text(value);
|
|
|
18
18
|
/**
|
|
19
19
|
* The flows as served, or an error: the asset asked for, its funds once each,
|
|
20
20
|
* every day newest first with a figure or none for each fund, and where they
|
|
21
|
-
* are from. A payload that breaks any of it is not drawn.
|
|
21
|
+
* are from. A payload that breaks any of it is not drawn. A day's price move
|
|
22
|
+
* that is not a number or null is dropped from that day alone: the flows
|
|
23
|
+
* stand without it.
|
|
22
24
|
*/
|
|
23
25
|
export function validateCryptoEtfFlows(payload: CryptoEtfFlowsPayload, asset: CryptoEtfFlowAsset): CryptoEtfFlowsPayload {
|
|
24
26
|
const invalid = (): never => { throw new Error("The server returned invalid crypto ETF flows"); };
|
|
@@ -35,7 +37,12 @@ export function validateCryptoEtfFlows(payload: CryptoEtfFlowsPayload, asset: Cr
|
|
|
35
37
|
|| !Array.isArray(day.pending) || day.pending.some((ticker) => !known.has(ticker))) return invalid();
|
|
36
38
|
}
|
|
37
39
|
if (payload.days[0] && payload.days[0].date !== payload.asOf) return invalid();
|
|
38
|
-
return payload;
|
|
40
|
+
if (payload.days.every((day) => day.priceChangePct === undefined || flow(day.priceChangePct))) return payload;
|
|
41
|
+
return { ...payload, days: payload.days.map((day) => {
|
|
42
|
+
if (day.priceChangePct === undefined || flow(day.priceChangePct)) return day;
|
|
43
|
+
const { priceChangePct: _dropped, ...rest } = day;
|
|
44
|
+
return rest;
|
|
45
|
+
}) };
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
export async function fetchCryptoEtfFlows(
|
|
@@ -2,6 +2,7 @@ import type { CryptoEtfFlowsPayload } from "../../../api-client/crypto-etf-flows
|
|
|
2
2
|
import type { HeadlessPaneColumn } from "../../../types/headless";
|
|
3
3
|
import type { HeadlessPaneDefinition } from "../../../types/plugin";
|
|
4
4
|
import { REPORT_ROWS } from "../shared/headless-options";
|
|
5
|
+
import { readPriceWindow, type PriceWindow } from "../../../utils/price-window";
|
|
5
6
|
import { fetchCryptoEtfFlows } from "./client";
|
|
6
7
|
import {
|
|
7
8
|
cryptoEtfFlowAsset,
|
|
@@ -9,16 +10,22 @@ import {
|
|
|
9
10
|
cryptoEtfFlowSummary,
|
|
10
11
|
formatFlow,
|
|
11
12
|
formatFlowAmount,
|
|
13
|
+
formatPriceChange,
|
|
14
|
+
hasFundPriceChanges,
|
|
12
15
|
pendingText,
|
|
13
16
|
} from "./model";
|
|
14
17
|
|
|
15
18
|
const flowCell = (value: unknown) => formatFlow(typeof value === "number" ? value : null);
|
|
19
|
+
const optionText = (value: unknown) => typeof value === "string" && value.trim() ? value : undefined;
|
|
16
20
|
|
|
17
|
-
/** Date, the day's total and each fund, in US$ millions as published. */
|
|
21
|
+
/** Date, the day's total, the funds' price move when served, and each fund, in US$ millions as published. */
|
|
18
22
|
function flowColumns(payload: CryptoEtfFlowsPayload): HeadlessPaneColumn[] {
|
|
19
23
|
return [
|
|
20
24
|
{ key: "date", header: "Date", shrink: false },
|
|
21
25
|
{ key: "total", header: "Total", align: "right", format: flowCell },
|
|
26
|
+
...hasFundPriceChanges(payload)
|
|
27
|
+
? [{ key: "priceChangePct", header: "Price %", literalHeader: true, align: "right" as const, format: (value: unknown) => formatPriceChange(typeof value === "number" ? value : null) }]
|
|
28
|
+
: [],
|
|
22
29
|
// A fund the day still waits on reads "pending", as in the pane; none at all is a dash.
|
|
23
30
|
...payload.funds.map((fund): HeadlessPaneColumn => ({
|
|
24
31
|
key: fund.ticker, header: fund.ticker, literalHeader: true, align: "right",
|
|
@@ -28,6 +35,13 @@ function flowColumns(payload: CryptoEtfFlowsPayload): HeadlessPaneColumn[] {
|
|
|
28
35
|
];
|
|
29
36
|
}
|
|
30
37
|
|
|
38
|
+
/** "Net flows of the US spot bitcoin ETFs in US$ millions. 2026-10-19 to 2026-10-23: +$301.20M over 5 trading days." */
|
|
39
|
+
function windowSummaryLine(payload: CryptoEtfFlowsPayload, window: PriceWindow, days: CryptoEtfFlowsPayload["days"]): string {
|
|
40
|
+
const total = days.some((day) => day.total != null) ? days.reduce((sum, day) => sum + (day.total ?? 0), 0) : null;
|
|
41
|
+
return `Net flows of the US spot ${cryptoEtfFlowAssetLabel(payload.asset).toLowerCase()} ETFs in US$ millions. ${window.from} to ${window.to}: `
|
|
42
|
+
+ `${formatFlowAmount(total)} over ${days.length} trading ${days.length === 1 ? "day" : "days"}.`;
|
|
43
|
+
}
|
|
44
|
+
|
|
31
45
|
/** "Net flows of the US spot bitcoin ETFs in US$ millions. 2026-10-20: -$6.70M (IBIT pending); 5 days ..., since 2024-01-11 +$57.47B." */
|
|
32
46
|
function summaryLine(payload: CryptoEtfFlowsPayload): string {
|
|
33
47
|
const summary = cryptoEtfFlowSummary(payload);
|
|
@@ -43,6 +57,15 @@ function summaryLine(payload: CryptoEtfFlowsPayload): string {
|
|
|
43
57
|
].join(" ");
|
|
44
58
|
}
|
|
45
59
|
|
|
60
|
+
/**
|
|
61
|
+
* "Funds: IBIT iShares Bitcoin Trust ETF, ..., BTC Grayscale Bitcoin Mini Trust ETF.": a column is headed by
|
|
62
|
+
* the fund's ticker, and BTC or ETH there is Grayscale's mini fund, not the coin. Null when no fund is named.
|
|
63
|
+
*/
|
|
64
|
+
function fundsLine(payload: CryptoEtfFlowsPayload): string | null {
|
|
65
|
+
const named = payload.funds.flatMap((fund) => fund.name ? [`${fund.ticker} ${fund.name}`] : []);
|
|
66
|
+
return named.length ? `Funds: ${named.join(", ")}.` : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
46
69
|
export const cryptoEtfFlowsHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
47
70
|
shape: "rows",
|
|
48
71
|
argument: { kind: "none" },
|
|
@@ -69,6 +92,22 @@ export const cryptoEtfFlowsHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
69
92
|
minimum: 1,
|
|
70
93
|
maximum: 2000,
|
|
71
94
|
},
|
|
95
|
+
{
|
|
96
|
+
key: "from",
|
|
97
|
+
type: "string",
|
|
98
|
+
placeholder: "year|date",
|
|
99
|
+
description: "First US trading day listed, as a year (2024) or a date (2026-10-19); lists every day of the window in place of --limit.",
|
|
100
|
+
example: "--from 2026-10-19 --to 2026-10-23",
|
|
101
|
+
shot: { reportOnly: "lists the report's days for those dates" },
|
|
102
|
+
replaces: ["limit"],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
key: "to",
|
|
106
|
+
type: "string",
|
|
107
|
+
placeholder: "year|date",
|
|
108
|
+
description: "Last US trading day listed, with --from (default the newest).",
|
|
109
|
+
shot: { reportOnly: "lists the report's days for those dates" },
|
|
110
|
+
},
|
|
72
111
|
],
|
|
73
112
|
columns: [
|
|
74
113
|
{ key: "date", header: "Date" },
|
|
@@ -81,24 +120,36 @@ export const cryptoEtfFlowsHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
81
120
|
limitations: [
|
|
82
121
|
"US spot bitcoin and ether ETFs only, net creations and redemptions in US$ millions as Farside Investors publishes them.",
|
|
83
122
|
"The newest day fills in fund by fund; a fund that has not reported shows as pending.",
|
|
123
|
+
"0.0 is the publisher's zero for the fund that day; a dash is a day it published no figure for.",
|
|
124
|
+
"Price % is the funds' price move that day: the median of their close-to-close changes.",
|
|
84
125
|
],
|
|
85
126
|
},
|
|
86
|
-
|
|
127
|
+
// Stale once a trading day's flows are missing (the load's `cadence`), except in a window that ended before the newest day.
|
|
128
|
+
freshness: { source: "Farside Investors", publisher: "Farside Investors", status: "not-a-feed", basis: "daily fund flows", tradingDayMarket: "US" },
|
|
87
129
|
async load(args, ctx) {
|
|
88
130
|
const asset = cryptoEtfFlowAsset(args.options.asset);
|
|
131
|
+
// --from 2026-10-19 --to 2026-10-23 lists those days, every one of them, in place of the newest --limit.
|
|
132
|
+
const window = readPriceWindow(optionText(args.options.from), optionText(args.options.to));
|
|
89
133
|
const payload = await fetchCryptoEtfFlows(asset, ctx.apiClient);
|
|
90
134
|
const limit = Math.max(1, Number(args.options.limit) || 20);
|
|
91
|
-
const
|
|
135
|
+
const days = window ? payload.days.filter((day) => day.date >= window.from && day.date <= window.to) : payload.days.slice(0, limit);
|
|
136
|
+
const priced = hasFundPriceChanges(payload);
|
|
137
|
+
const rows = days.map((day) => ({
|
|
92
138
|
date: day.date,
|
|
93
139
|
total: day.total,
|
|
140
|
+
...(priced ? { priceChangePct: day.priceChangePct ?? null } : {}),
|
|
94
141
|
...Object.fromEntries(payload.funds.map((fund) => [fund.ticker, day.flows[fund.ticker] ?? null])),
|
|
95
142
|
pending: day.pending,
|
|
96
143
|
}));
|
|
97
144
|
const summary = cryptoEtfFlowSummary(payload);
|
|
145
|
+
const funds = fundsLine(payload);
|
|
146
|
+
// A window that ends before the newest day is dated by its own last day.
|
|
147
|
+
const asOf = window ? days[0]?.date ?? null : payload.asOf;
|
|
98
148
|
return {
|
|
99
149
|
rows,
|
|
100
150
|
columns: flowColumns(payload),
|
|
101
|
-
|
|
151
|
+
...(window && !days.length ? { errors: [`No US trading day with flows from ${window.from} to ${window.to}.`] } : {}),
|
|
152
|
+
notes: [window ? windowSummaryLine(payload, window, days) : summaryLine(payload), ...funds ? [funds] : [], `Source: ${payload.source.publisher}, ${payload.source.url}`],
|
|
102
153
|
complete: !payload.stale,
|
|
103
154
|
...(payload.stale ? { warnings: [`The flows were last read ${payload.fetchedAt.slice(0, 10)}; newer days may be missing.`] } : {}),
|
|
104
155
|
freshness: {
|
|
@@ -106,10 +157,12 @@ export const cryptoEtfFlowsHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
106
157
|
publisher: payload.source.publisher,
|
|
107
158
|
status: "not-a-feed",
|
|
108
159
|
basis: "daily fund flows",
|
|
109
|
-
cadence: "daily",
|
|
160
|
+
...(window && asOf !== payload.asOf ? {} : { cadence: "daily" as const }),
|
|
110
161
|
tradingDayMarket: "US",
|
|
111
|
-
asOf:
|
|
162
|
+
...(asOf ? { asOf } : {}),
|
|
112
163
|
oldest: null,
|
|
164
|
+
// When the server last read the publisher's table, which is what this report retrieved.
|
|
165
|
+
retrievedAt: payload.fetchedAt,
|
|
113
166
|
},
|
|
114
167
|
metadata: {
|
|
115
168
|
asset,
|
|
@@ -117,6 +170,8 @@ export const cryptoEtfFlowsHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
117
170
|
asOf: payload.asOf,
|
|
118
171
|
fetchedAt: payload.fetchedAt,
|
|
119
172
|
stale: payload.stale,
|
|
173
|
+
...(window ? { requestedWindow: window } : {}),
|
|
174
|
+
...(priced ? { priceChangeBasis: "median of the funds' close-to-close price changes, percent" } : {}),
|
|
120
175
|
source: payload.source,
|
|
121
176
|
funds: payload.funds,
|
|
122
177
|
summary: {
|
|
@@ -2,7 +2,6 @@ import type { PluginModule } from "../plugin-module";
|
|
|
2
2
|
import { cryptoEtfFlowsCache } from "./client";
|
|
3
3
|
import { cryptoEtfFlowsHeadless } from "./headless";
|
|
4
4
|
import { CRYPTO_ETF_FLOWS_PANE_ID } from "./model";
|
|
5
|
-
import { CryptoEtfFlowsPane } from "./pane";
|
|
6
5
|
|
|
7
6
|
/** ETFF: the daily net flows of the US spot bitcoin and ether ETFs, per fund. */
|
|
8
7
|
export const cryptoEtfFlowsModule: PluginModule = {
|
|
@@ -11,7 +10,7 @@ export const cryptoEtfFlowsModule: PluginModule = {
|
|
|
11
10
|
id: CRYPTO_ETF_FLOWS_PANE_ID,
|
|
12
11
|
name: "Crypto ETF Flows",
|
|
13
12
|
icon: "F",
|
|
14
|
-
component: CryptoEtfFlowsPane,
|
|
13
|
+
get component(): typeof import("./pane").CryptoEtfFlowsPane { const { CryptoEtfFlowsPane } = require("./pane"); return CryptoEtfFlowsPane; },
|
|
15
14
|
defaultPosition: "right",
|
|
16
15
|
defaultMode: "floating",
|
|
17
16
|
defaultFloatingSize: { width: 120, height: 32 },
|
|
@@ -103,11 +103,24 @@ export function pendingText(day: CryptoEtfFlowDay | null | undefined): string |
|
|
|
103
103
|
|
|
104
104
|
export type CryptoEtfFlowColumn = Omit<DataTableColumn, "id"> & { id: string };
|
|
105
105
|
|
|
106
|
-
/**
|
|
106
|
+
/** Whether the server sent the funds' daily price move beside the flows. */
|
|
107
|
+
export const hasFundPriceChanges = (payload: CryptoEtfFlowsPayload | null | undefined) =>
|
|
108
|
+
!!payload?.days.some((day) => day.priceChangePct !== undefined);
|
|
109
|
+
|
|
110
|
+
/** A day's price move as the table shows it: `+2.15%`, `-0.40%`, `—` for none. */
|
|
111
|
+
export function formatPriceChange(value: number | null | undefined): string {
|
|
112
|
+
if (value == null || !Number.isFinite(value)) return "—";
|
|
113
|
+
return `${value > 0 ? "+" : ""}${value.toFixed(2)}%`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Date, the day's total and the funds' price move that day, then each fund in the publisher's order. */
|
|
107
117
|
export function cryptoEtfFlowColumns(payload: CryptoEtfFlowsPayload | null | undefined): CryptoEtfFlowColumn[] {
|
|
108
118
|
return [
|
|
109
119
|
{ id: "date", label: "Date", width: 12, align: "left" },
|
|
110
120
|
{ id: "total", label: "Total", width: 9, align: "right", title: "The day's net flow across the funds, US$ millions" },
|
|
121
|
+
...hasFundPriceChanges(payload)
|
|
122
|
+
? [{ id: "price", label: "Price %", width: 9, align: "right" as const, title: "The funds' price move that day: the median of their close-to-close changes" }]
|
|
123
|
+
: [],
|
|
111
124
|
...(payload?.funds ?? []).map((fund): CryptoEtfFlowColumn => ({
|
|
112
125
|
id: `fund:${fund.ticker}`,
|
|
113
126
|
label: fund.ticker,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type DataTableCell,
|
|
13
13
|
type StatItem,
|
|
14
14
|
} from "../../../components";
|
|
15
|
+
import { ageFooterSegment } from "../../../components/layout/pane/footer";
|
|
15
16
|
import { formatCompactAxis } from "../../../components/chart-table";
|
|
16
17
|
import { useChartViewReset } from "../../../components/chart/core/view-reset";
|
|
17
18
|
import { scalarPoint, staticSeries } from "../../../components/chart/static/series";
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
flowColor,
|
|
37
38
|
formatFlow,
|
|
38
39
|
formatFlowAmount,
|
|
40
|
+
formatPriceChange,
|
|
39
41
|
fundTickerOfColumn,
|
|
40
42
|
pendingText,
|
|
41
43
|
type CryptoEtfFlowColumn,
|
|
@@ -76,13 +78,15 @@ export function CryptoEtfFlowsPane({ focused, width, height }: PaneProps) {
|
|
|
76
78
|
}, [asset, setSelectedByAsset]);
|
|
77
79
|
|
|
78
80
|
const label = cryptoEtfFlowAssetLabel(asset);
|
|
79
|
-
// The running total since the first fund launched, drawn over the range the table lists.
|
|
81
|
+
// The running total since the first fund launched, drawn over the range the table lists. Its legend is the one
|
|
82
|
+
// place the total and the day it starts from are said; the figures above carry only the table's windows.
|
|
80
83
|
const series = useMemo(() => {
|
|
81
84
|
if (!data) return [];
|
|
82
85
|
const first = days.at(-1)?.date ?? "";
|
|
83
86
|
const points = cumulativeFlows(data).filter((point) => point.date >= first)
|
|
84
87
|
.map((point) => scalarPoint(new Date(`${point.date}T00:00:00Z`), point.value * 1_000_000));
|
|
85
|
-
|
|
88
|
+
const launch = data.days.at(-1)?.date;
|
|
89
|
+
return [staticSeries(points, { id: `etf-flows:${asset}`, label: `${label} ETF net flows since ${launch ?? "launch"}`, color: colors.positive, calendarSpaced: true })];
|
|
86
90
|
}, [asset, data, days, label]);
|
|
87
91
|
const link = useChartTableSelection({ rows: days, getId: rowId, getDate: rowDate, selectedId, onSelect: selectDay, focused });
|
|
88
92
|
|
|
@@ -93,14 +97,12 @@ export function CryptoEtfFlowsPane({ focused, width, height }: PaneProps) {
|
|
|
93
97
|
detail: [latest?.date, pending].filter(Boolean).join(" · ") || undefined },
|
|
94
98
|
{ id: "week", label: "5 days", value: formatFlowAmount(summary.week), color: flowColor(summary.week, colors) },
|
|
95
99
|
{ id: "month", label: "20 days", value: formatFlowAmount(summary.month), color: flowColor(summary.month, colors) },
|
|
96
|
-
{ id: "launch", label: "Since launch", value: formatFlowAmount(summary.sinceLaunch), color: flowColor(summary.sinceLaunch, colors),
|
|
97
|
-
detail: summary.firstDate ? `since ${summary.firstDate}` : undefined },
|
|
98
100
|
] : [];
|
|
99
101
|
|
|
100
102
|
const updatedAgo = useUpdatedAgo(resource.updatedAt);
|
|
101
103
|
const footerInfo = useMemo(() => data ? [
|
|
102
104
|
{ id: "as-of", parts: [{ text: `as of ${data.asOf}`, tone: "muted" as const }] },
|
|
103
|
-
...(updatedAgo ? [
|
|
105
|
+
...(updatedAgo ? [ageFooterSegment(updatedAgo)] : []),
|
|
104
106
|
] : [], [data, updatedAgo]);
|
|
105
107
|
useAutoRefresh(resource.updatedAt, resource.load);
|
|
106
108
|
const refresh = useCallback(() => void resource.reload(), [resource.reload]);
|
|
@@ -135,6 +137,7 @@ export function CryptoEtfFlowsPane({ focused, width, height }: PaneProps) {
|
|
|
135
137
|
const renderCell = useCallback((day: CryptoEtfFlowDay, column: CryptoEtfFlowColumn): DataTableCell => {
|
|
136
138
|
if (column.id === "date") return { text: day.date, value: day.date, color: colors.text };
|
|
137
139
|
if (column.id === "total") return { text: formatFlow(day.total), value: day.total, color: flowColor(day.total, colors) };
|
|
140
|
+
if (column.id === "price") return { text: formatPriceChange(day.priceChangePct), value: day.priceChangePct ?? null, color: flowColor(day.priceChangePct, colors) };
|
|
138
141
|
const ticker = fundTickerOfColumn(column.id) ?? "";
|
|
139
142
|
const value = day.flows[ticker] ?? null;
|
|
140
143
|
// A fund that reported the day before and not yet today: its figure is to come, not none.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { HeadlessPaneDefinition } from "../../../types/plugin";
|
|
2
2
|
import { fetchDebtMaturities } from "./client";
|
|
3
|
-
import { bucketShare, debtAmount, debtNewestAsOf, debtNotices, debtPercent, newerQuarter } from "./model";
|
|
3
|
+
import { bucketShare, debtAmount, debtNewestAsOf, debtNewestForm, debtNotices, debtPercent, newerQuarter } from "./model";
|
|
4
4
|
import { SEC_FILINGS } from "../shared/report-freshness";
|
|
5
5
|
import { resolveHeadlessIssuerListing } from "../shared/headless-market-data";
|
|
6
6
|
|
|
@@ -118,8 +118,9 @@ export const debtMaturitiesHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
118
118
|
},
|
|
119
119
|
],
|
|
120
120
|
metadata: { ...data },
|
|
121
|
-
// The latest filing dates the report (a newer 10-Q's balance date)
|
|
122
|
-
|
|
121
|
+
// The latest filing dates the report (a newer 10-Q's balance date), named by its form so the date is
|
|
122
|
+
// not read as the schedule's; the history rows are past years, not stale data.
|
|
123
|
+
freshness: { asOf: debtNewestAsOf(data), asOfLabel: debtNewestForm(data) ?? undefined },
|
|
123
124
|
};
|
|
124
125
|
},
|
|
125
126
|
};
|
|
@@ -219,9 +219,25 @@ export function newerQuarter(data: DebtMaturitiesPayload): DebtQuarter | null {
|
|
|
219
219
|
const quarter = data.latestQuarter ?? null;
|
|
220
220
|
return quarter && data.latest && quarter.asOf > data.latest.asOf ? quarter : null;
|
|
221
221
|
}
|
|
222
|
+
/** The newest filing the payload reports: the 10-Q when it is newer, else the annual schedule's. */
|
|
223
|
+
function newestDebtFiling(data: DebtMaturitiesPayload): Pick<DebtQuarter, "form" | "asOf" | "filed"> | null {
|
|
224
|
+
return newerQuarter(data) ?? data.latest ?? null;
|
|
225
|
+
}
|
|
222
226
|
/** The newest balance date the payload reports: the 10-Q's when it is newer, else the schedule's. */
|
|
223
227
|
export function debtNewestAsOf(data: DebtMaturitiesPayload): string | null {
|
|
224
|
-
return
|
|
228
|
+
return newestDebtFiling(data)?.asOf ?? null;
|
|
229
|
+
}
|
|
230
|
+
/** The form of the filing `debtNewestAsOf` dates, which names that date: `10-Q`, `10-K`. */
|
|
231
|
+
export function debtNewestForm(data: DebtMaturitiesPayload): string | null {
|
|
232
|
+
return newestDebtFiling(data)?.form ?? null;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Which filing the pane's date is, as its footer says it, longest first:
|
|
236
|
+
* `10-Q 2026-09-30, filed 2026-10-27`, then `10-Q 2026-09-30`.
|
|
237
|
+
*/
|
|
238
|
+
export function debtNewestFilingText(data: DebtMaturitiesPayload): string[] {
|
|
239
|
+
const filing = newestDebtFiling(data);
|
|
240
|
+
return filing ? [`${filing.form} ${filing.asOf}, filed ${filing.filed}`, `${filing.form} ${filing.asOf}`] : [];
|
|
225
241
|
}
|
|
226
242
|
/** The 10-Q's debt in one line, `Current 212.00M · Non-current 6.84B USD`. */
|
|
227
243
|
export function quarterDebtText(quarter: DebtQuarter): string {
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type DataTableCell,
|
|
25
25
|
type StatItem,
|
|
26
26
|
} from "../../../components";
|
|
27
|
+
import { ageFooterSegment } from "../../../components/layout/pane/footer";
|
|
27
28
|
import { colors } from "../../../theme/colors";
|
|
28
29
|
import type {
|
|
29
30
|
DebtFact,
|
|
@@ -53,6 +54,7 @@ import {
|
|
|
53
54
|
debtFilingUrl,
|
|
54
55
|
debtMetricCaption,
|
|
55
56
|
debtMetricValue,
|
|
57
|
+
debtNewestFilingText,
|
|
56
58
|
debtNotices,
|
|
57
59
|
debtPercent,
|
|
58
60
|
historyAxis,
|
|
@@ -431,10 +433,13 @@ export function DebtMaturitiesPane({ width, height, focused }: PaneProps) {
|
|
|
431
433
|
metric("next12", "Due next 12 months", latest.next12MonthsShare),
|
|
432
434
|
metric("next3", "Due next 3 years", latest.next3YearsShare),
|
|
433
435
|
{ id: "as-of", label: "As of", value: latest.asOf },
|
|
434
|
-
// A 10-Q filed since: its own row,
|
|
436
|
+
// A 10-Q filed since: its own row, named by its period, so it never reads
|
|
437
|
+
// as the schedule's. The filed date goes beside the figures where it fits
|
|
438
|
+
// and gives way to them where it does not: the footer says it
|
|
439
|
+
// ("10-Q 2026-09-30, filed 2026-10-27").
|
|
435
440
|
...(quarter ? [{
|
|
436
|
-
id: "latest-10q", label:
|
|
437
|
-
detail:
|
|
441
|
+
id: "latest-10q", label: `10-Q ${quarter.asOf}`, value: quarterDebtText(quarter),
|
|
442
|
+
detail: `filed ${quarter.filed}`, detailVariants: [""], wide: true,
|
|
438
443
|
}] : []),
|
|
439
444
|
];
|
|
440
445
|
}, [latest, quarter]);
|
|
@@ -445,6 +450,15 @@ export function DebtMaturitiesPane({ width, height, focused }: PaneProps) {
|
|
|
445
450
|
label: "Principal total", values, value: debtAmount(last), color: colors.warning,
|
|
446
451
|
};
|
|
447
452
|
}, [bars]);
|
|
453
|
+
// Which filing the pane's date is, then how long ago it loaded: the age goes first in a narrow footer.
|
|
454
|
+
const filingFooter = useMemo(() => {
|
|
455
|
+
const [filing, ...shorter] = data ? debtNewestFilingText(data) : [];
|
|
456
|
+
return [
|
|
457
|
+
...(filing ? [{ id: "filing", asOf: true, parts: [{ text: filing, tone: "muted" as const }],
|
|
458
|
+
variants: shorter.map((text) => [{ text, tone: "muted" as const }]) }] : []),
|
|
459
|
+
...(data && updatedAgo ? [ageFooterSegment(updatedAgo)] : []),
|
|
460
|
+
];
|
|
461
|
+
}, [data, updatedAgo]);
|
|
448
462
|
useAutoRefresh(resource.updatedAt, resource.load);
|
|
449
463
|
usePaneRefreshKey(() => void resource.reload(), { focused });
|
|
450
464
|
usePaneNoticeFooter({
|
|
@@ -465,7 +479,7 @@ export function DebtMaturitiesPane({ width, height, focused }: PaneProps) {
|
|
|
465
479
|
? debtFilingUrl(data.cik, selectedHistoryRow.accession)
|
|
466
480
|
: (latest?.filingUrl ?? data?.source.url ?? null),
|
|
467
481
|
showOpenHint: true,
|
|
468
|
-
info:
|
|
482
|
+
info: filingFooter,
|
|
469
483
|
stale: !!data && resource.data?.stale,
|
|
470
484
|
});
|
|
471
485
|
const renderBucket = (
|