@gloom-sh/terminal 0.19.2 → 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 +1 -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 +20 -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.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +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 +92 -23
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +9 -4
- package/src/cli/commands/search.ts +71 -12
- 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 +5 -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/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +154 -4
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +40 -5
- package/src/cli/listing-arg.ts +46 -9
- 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 +20 -0
- 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 +24 -1
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/header.tsx +8 -3
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +7 -2
- package/src/components/layout/pane/footer/model.ts +40 -5
- 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 +2 -1
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +54 -23
- 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/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +8 -1
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +34 -31
- package/src/data/sqlite/access.ts +19 -8
- package/src/data/sqlite/checkpointer.ts +95 -8
- package/src/data/sqlite/database.ts +40 -7
- 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/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/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/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -2
- 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 +26 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -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/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/index.tsx +1 -2
- 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/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- 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 +2 -0
- 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/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/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- 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-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/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- 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/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 +15 -2
- 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/help/index.tsx +1 -1
- 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/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/index.tsx +7 -1
- package/src/plugins/builtin/market-movers/session-body.tsx +4 -1
- 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/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/index.tsx +1 -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/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/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 +60 -0
- 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/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- 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/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- 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 +116 -13
- package/src/plugins/builtin/thirteenf/data.ts +132 -30
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +20 -11
- package/src/plugins/builtin/thirteenf/types.ts +12 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +17 -4
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/headless.ts +11 -3
- 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/pane.tsx +2 -2
- 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/index.tsx +1 -2
- 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/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 +15 -4
- 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 +2 -0
- 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/initialization.ts +3 -2
- 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/create.ts +4 -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/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/standalone.ts +10 -1
- package/src/series/types.ts +7 -0
- package/src/series/validate.ts +11 -2
- 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 +12 -7
- package/src/telemetry/stall-reports.ts +34 -5
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name.ts +19 -0
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +13 -2
- package/src/tickers/search/index.ts +4 -1
- 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/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +55 -6
- package/src/time-series/resolve.ts +12 -3
- 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/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +22 -0
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +11 -0
- 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/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/main-thread-stall.ts +75 -3
- package/src/utils/perf-marks.ts +36 -2
- 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/renderers/electrobun/launcher/preload.ts +0 -13
- package/src/renderers/electrobun/launcher/windows-emulation.ts +0 -50
- package/src/time-series/studies.ts +0 -1628
|
@@ -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
|
+
}
|
|
@@ -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
|
}
|
|
@@ -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)}
|
|
@@ -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} />;
|
|
@@ -4,7 +4,6 @@ import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
|
|
|
4
4
|
import { socialMentionPostsCache, socialMentionsCache } from "./client";
|
|
5
5
|
import { socialMentionsHeadless } from "./headless";
|
|
6
6
|
import { socialMentionsLockNote } from "./lock-note";
|
|
7
|
-
import { SocialMentionsPane } from "./pane";
|
|
8
7
|
|
|
9
8
|
const socialMentionsSettings = [
|
|
10
9
|
{ key: "socialRange", label: "History", type: "select" as const,
|
|
@@ -12,7 +11,7 @@ const socialMentionsSettings = [
|
|
|
12
11
|
];
|
|
13
12
|
export const socialMentionsModule: PluginModule = {
|
|
14
13
|
series: SOCIAL_MENTION_SERIES,
|
|
15
|
-
panes: [{ id: "social-mentions", reportLockNote: socialMentionsLockNote, name: "Social Mentions", icon: "B", component: SocialMentionsPane,
|
|
14
|
+
panes: [{ id: "social-mentions", reportLockNote: socialMentionsLockNote, name: "Social Mentions", icon: "B", get component(): typeof import("./pane").SocialMentionsPane { const { SocialMentionsPane } = require("./pane"); return SocialMentionsPane; },
|
|
16
15
|
defaultPosition: "right", tickerFollower: true, defaultMode: "floating", defaultFloatingSize: { width: 96, height: 30 },
|
|
17
16
|
tableExport: true, headless: socialMentionsHeadless, settings: { title: "Social Mentions", fields: socialMentionsSettings } }],
|
|
18
17
|
paneTemplates: [{ ...createTickerSurfacePaneTemplate({ id: "social-mentions-pane", paneId: "social-mentions", label: "Social Mentions",
|
|
@@ -3,6 +3,7 @@ import { listingIdentity } from "../shared/ticker-request";
|
|
|
3
3
|
import { Box, ScrollBox, Text, useUiCapabilities } from "../../../ui";
|
|
4
4
|
import { useAsyncResource, useAutoRefresh, usePaneSettingValue, usePluginPaneState, useShortcut, useUpdatedAgo } from "../../../public/react";
|
|
5
5
|
import { CompositeChart, DataTableStackView, EmptyState, PaneStatusBody, usePaneNoticeFooter, usePaneStatusLinkFooter, type DataTableCell, StatGrid } from "../../../components";
|
|
6
|
+
import { ageFooterSegment } from "../../../components/layout/pane/footer";
|
|
6
7
|
import { colors } from "../../../theme/colors";
|
|
7
8
|
import type { SocialMentionPost, SocialMentionsRange } from "../../../api-client/social-mentions";
|
|
8
9
|
import { staticSeries } from "../../../components/chart/static/series";
|
|
@@ -117,7 +118,7 @@ export function SocialMentionsPane({ width, height, focused }: Pick<PaneProps, "
|
|
|
117
118
|
usePaneStatusLinkFooter({ registrationId: "social-mentions", focused, loading: resource.loading || pending, error: signInWall ? null : resource.error,
|
|
118
119
|
url: selected?.topPost?.url ?? (symbol ? `https://x.com/search?q=%24${symbol}` : null), showOpenHint: true,
|
|
119
120
|
info: data ? [
|
|
120
|
-
...(updatedAgo ? [
|
|
121
|
+
...(updatedAgo ? [ageFooterSegment(updatedAgo)] : []),
|
|
121
122
|
...(resource.data?.stale ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []),
|
|
122
123
|
] : [],
|
|
123
124
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { apiClient } from "../../../api-client";
|
|
2
2
|
import type { SupplyChainPayload, SupplyEntity, SupplyEvidenceItem, SupplyOptions, SupplyRow } from "../../../api-client/supply-chain";
|
|
3
3
|
import { createPluginCache } from "../../../data/plugin-cache";
|
|
4
|
-
import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
4
|
+
import { batchReplyItems, cachedCloudResource, createCloudBatchAhead, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
5
5
|
import { activeRelationship, canShowQuote, isUnconfirmed, publicEvidence, supplyOptions, TIER_OPTIONS, trustTier } from "./trust";
|
|
6
6
|
|
|
7
7
|
export const SUPPLY_UNAVAILABLE = "Supply chain disclosures are not available yet.";
|
|
@@ -111,3 +111,24 @@ export async function fetchSupplyChain(symbol: string, client: Pick<typeof apiCl
|
|
|
111
111
|
const cacheKey = (symbol: string, access: string, options: SupplyOptions) => `${symbol.toUpperCase()}:${access}:${JSON.stringify(supplyOptions(options.tiers))}:${!!options.includeLeads}`;
|
|
112
112
|
export const cachedSupplyChain = (symbol: string, access: string, options: SupplyOptions = {}) => cachedCloudResource(supplyChainCache, cacheKey(symbol, access, options), validateSupplyChain);
|
|
113
113
|
export const loadSupplyChain = (symbol: string, access: string, force = false, options: SupplyOptions = {}) => loadCloudResource(supplyChainCache, cacheKey(symbol, access, options), () => fetchSupplyChain(symbol, apiClient, options), { force, validate: validateSupplyChain });
|
|
114
|
+
/** Symbols per disclosures batch request: a ripple's sixty holdings are two, read sixteen at a time on the server. */
|
|
115
|
+
const SUPPLY_BATCH_MAX = 30;
|
|
116
|
+
const supplyChainsAheadByOptions = new Map<string, ReturnType<typeof createCloudBatchAhead<SupplyChainPayload>>>();
|
|
117
|
+
/**
|
|
118
|
+
* Reads many symbols' disclosures ahead in a request per 30 (EARN ripple's
|
|
119
|
+
* holdings), into the cache loadSupplyChain reads. Answers the symbols stored.
|
|
120
|
+
*/
|
|
121
|
+
export function loadSupplyChainsAhead(symbols: readonly string[], access: string, force = false, options: SupplyOptions = {}): Promise<Set<string>> {
|
|
122
|
+
const optionsKey = JSON.stringify([supplyOptions(options.tiers), !!options.includeLeads]);
|
|
123
|
+
let ahead = supplyChainsAheadByOptions.get(optionsKey);
|
|
124
|
+
if (!ahead) {
|
|
125
|
+
ahead = createCloudBatchAhead(supplyChainCache, {
|
|
126
|
+
max: SUPPLY_BATCH_MAX,
|
|
127
|
+
validate: validateSupplyChain,
|
|
128
|
+
read: async (ids) => batchReplyItems(await apiClient.getCloudSupplyChains(ids, options))
|
|
129
|
+
.flatMap((item) => item.chain ? [{ id: item.symbol.toUpperCase(), payload: item.chain }] : []),
|
|
130
|
+
});
|
|
131
|
+
supplyChainsAheadByOptions.set(optionsKey, ahead);
|
|
132
|
+
}
|
|
133
|
+
return ahead(symbols.map((symbol) => ({ id: symbol.toUpperCase(), key: cacheKey(symbol, access, options) })), force);
|
|
134
|
+
}
|
|
@@ -2,7 +2,7 @@ import { apiClient } from "../../../api-client";
|
|
|
2
2
|
import { DEFAULT_GRAPH_OPTIONS, supplyGraphQuery, type GraphOptions, type GraphPayload, type GraphPath } from "../../../api-client/supply-chain-graph";
|
|
3
3
|
import { createPluginCache } from "../../../data/plugin-cache";
|
|
4
4
|
import { dateOptionError, readCalendarDate } from "../../../utils/calendar-date";
|
|
5
|
-
import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
5
|
+
import { batchReplyItems, cachedCloudResource, createCloudBatchAhead, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
6
6
|
import { normalizeSupplyRow, validPeriodMonths, validSupplyEntity, validSupplyRow } from "./client";
|
|
7
7
|
import { graphEvidenceRow } from "./graph-evidence";
|
|
8
8
|
import { activeRelationship, isUnconfirmed } from "./trust";
|
|
@@ -84,3 +84,24 @@ export function graphOptions(input: Record<string, unknown>): GraphOptions {
|
|
|
84
84
|
|| options.tiers.some(value => !["structured", "primary", "secondary", "imported"].includes(value))) throw new Error("Invalid supply chain graph filters");
|
|
85
85
|
return options;
|
|
86
86
|
}
|
|
87
|
+
/** Symbols per graphs batch request: a ripple's sixty holdings are two, read sixteen at a time on the server. */
|
|
88
|
+
const GRAPH_BATCH_MAX = 30;
|
|
89
|
+
const graphsAheadByOptions = new Map<string, ReturnType<typeof createCloudBatchAhead<GraphPayload>>>();
|
|
90
|
+
/**
|
|
91
|
+
* Reads many symbols' graphs on one query ahead in a request per 30 (EARN
|
|
92
|
+
* ripple's two hops), into the cache loadGraph reads. Answers the symbols stored.
|
|
93
|
+
*/
|
|
94
|
+
export function loadGraphsAhead(symbols: readonly string[], options: GraphOptions, access: string, force = false): Promise<Set<string>> {
|
|
95
|
+
const query = supplyGraphQuery(options);
|
|
96
|
+
let ahead = graphsAheadByOptions.get(query);
|
|
97
|
+
if (!ahead) {
|
|
98
|
+
ahead = createCloudBatchAhead(graphCache, {
|
|
99
|
+
max: GRAPH_BATCH_MAX,
|
|
100
|
+
validate: validateGraph,
|
|
101
|
+
read: async (ids) => batchReplyItems(await apiClient.getCloudSupplyGraphs(ids, options))
|
|
102
|
+
.flatMap((item) => item.graph ? [{ id: item.symbol.toUpperCase(), payload: item.graph }] : []),
|
|
103
|
+
});
|
|
104
|
+
graphsAheadByOptions.set(query, ahead);
|
|
105
|
+
}
|
|
106
|
+
return ahead(symbols.map((symbol) => ({ id: symbol.toUpperCase(), key: key(symbol, "", options, access) })), force);
|
|
107
|
+
}
|
|
@@ -3,7 +3,6 @@ import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
|
|
|
3
3
|
import { graphCache } from "./graph-client";
|
|
4
4
|
import { supplyChainCache } from "./client";
|
|
5
5
|
import { supplyChainHeadless } from "./headless";
|
|
6
|
-
import { SupplyChainPane } from "./pane";
|
|
7
6
|
|
|
8
7
|
/** What another pane can open SPLC on: a Graph or Path view, its target and its query, as `graphOptions` reads them. */
|
|
9
8
|
const OPENING_SETTINGS = ["tab", "to", "depth", "direction", "roles", "tiers"];
|
|
@@ -13,7 +12,7 @@ const openingSettings = (values: Record<string, string> = {}) =>
|
|
|
13
12
|
export const supplyChainModule: PluginModule = {
|
|
14
13
|
setup(ctx) { supplyChainCache.attach(ctx.persistence); graphCache.attach(ctx.persistence); },
|
|
15
14
|
dispose() { supplyChainCache.reset(); graphCache.reset(); },
|
|
16
|
-
panes: [{ id: "supply-chain", name: "Supply Chain", icon: "S", component: SupplyChainPane,
|
|
15
|
+
panes: [{ id: "supply-chain", name: "Supply Chain", icon: "S", get component(): typeof import("./pane").SupplyChainPane { const { SupplyChainPane } = require("./pane"); return SupplyChainPane; },
|
|
17
16
|
defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 130, height: 30 },
|
|
18
17
|
tickerFollower: true, instruments: ["equity"], tableExport: true, headless: supplyChainHeadless }],
|
|
19
18
|
paneTemplates: [{ ...createTickerSurfacePaneTemplate({ id: "supply-chain-pane", paneId: "supply-chain", label: "Supply Chain",
|
|
@@ -12,15 +12,23 @@ import type { PluginPersistence } from "../../../types/plugin";
|
|
|
12
12
|
import { apiClient } from "../../../api-client";
|
|
13
13
|
import { ApiRequestError, isPermanentClientError, isTransientFailure } from "../../../api-client/errors";
|
|
14
14
|
import { httpFetch } from "../../../utils/http-transport";
|
|
15
|
+
import { isNetworkFailure } from "../../../utils/network-failure";
|
|
15
16
|
import { asRecord, finiteOrNull } from "../../../utils/guards";
|
|
16
17
|
import { logFailure } from "../../../utils/log-failure";
|
|
17
18
|
|
|
18
19
|
const FORMS_13F_BASE_URL = "https://forms13f.com/api/v1";
|
|
19
20
|
const FORM_PAGE_LIMIT = 100;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
21
|
+
/** The most rows the 13F source gives in one page. */
|
|
22
|
+
const FILING_PAGE_ROWS = 250;
|
|
23
|
+
/** Pages of one filing read at once where the service cannot send it whole. */
|
|
24
|
+
const FILING_PAGE_CONCURRENCY = 4;
|
|
25
|
+
// Large funds file tens of thousands of positions (BlackRock about 50,000), and
|
|
26
|
+
// a silently clipped list makes every weight, total, and buy/sell action wrong.
|
|
27
|
+
const MAX_FORM_ROWS = 100_000;
|
|
28
|
+
/** Rows asked first of a large filing the service is still reading. */
|
|
29
|
+
const FIRST_ROWS = 2_000;
|
|
30
|
+
/** How long a large filing is waited for whole before its first rows are asked. */
|
|
31
|
+
const FIRST_ROWS_AFTER_MS = 300;
|
|
24
32
|
const CACHE_KIND = "forms13f-api";
|
|
25
33
|
const CACHE_SOURCE = "forms13f";
|
|
26
34
|
const CACHE_SCHEMA_VERSION = 1;
|
|
@@ -52,6 +60,8 @@ let forms13FPersistence: PluginPersistence | null = null;
|
|
|
52
60
|
const failedRefreshes = new Set<string>();
|
|
53
61
|
// The promise is the generation. A newer download replaces it, and the replaced one must not write.
|
|
54
62
|
const activeRequests = new Map<string, Promise<unknown>>();
|
|
63
|
+
/** The service answered a whole filing with 404: it is an older one, so filings are read a page at a time. */
|
|
64
|
+
let wholeFilingsMissing = false;
|
|
55
65
|
|
|
56
66
|
export function attachThirteenFApiPersistence(persistence: PluginPersistence) {
|
|
57
67
|
if (forms13FPersistence !== persistence) resetThirteenFApiPersistence();
|
|
@@ -62,6 +72,7 @@ export function resetThirteenFApiPersistence() {
|
|
|
62
72
|
forms13FPersistence = null;
|
|
63
73
|
failedRefreshes.clear();
|
|
64
74
|
activeRequests.clear();
|
|
75
|
+
wholeFilingsMissing = false;
|
|
65
76
|
}
|
|
66
77
|
|
|
67
78
|
function padCik(value: string): string {
|
|
@@ -122,7 +133,15 @@ async function fetchForms13F<T>(
|
|
|
122
133
|
|
|
123
134
|
if (!options.forceRefresh) {
|
|
124
135
|
const pending = activeRequests.get(key) as Promise<T> | undefined;
|
|
125
|
-
if (pending)
|
|
136
|
+
if (pending) {
|
|
137
|
+
if (cached != null) return cached.value;
|
|
138
|
+
// Another reader's request, which that reader may stop (a fund read ahead
|
|
139
|
+
// while the cursor rested on it): this reader then asks again on its own.
|
|
140
|
+
return pending.catch((error: unknown) => {
|
|
141
|
+
if (options.signal?.aborted || !(error instanceof Error && error.name === "AbortError")) throw error;
|
|
142
|
+
return fetchForms13F<T>(path, params, options);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
126
145
|
}
|
|
127
146
|
|
|
128
147
|
const slot: { promise?: Promise<T> } = {};
|
|
@@ -343,24 +362,108 @@ export async function listThirteenFFormHoldingsPage(
|
|
|
343
362
|
return { rows, hasMore: rows.length >= limit && offset + rows.length < MAX_FORM_ROWS };
|
|
344
363
|
}
|
|
345
364
|
|
|
365
|
+
function mapHoldings(raw: unknown): ThirteenFHoldingRecord[] {
|
|
366
|
+
return arrayResponse(raw).map(mapHolding).filter((holding): holding is ThirteenFHoldingRecord => !!holding);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** A filing's whole information table as Gloom Cloud sends it in one answer. */
|
|
370
|
+
function filingRows(raw: unknown): ThirteenFHoldingRecord[] {
|
|
371
|
+
const rows = asRecord(raw).rows;
|
|
372
|
+
if (!Array.isArray(rows)) throw new Error("13F filing answer has no rows");
|
|
373
|
+
return mapHoldings(rows);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export interface FilingHoldingsOptions extends Forms13FReadOptions {
|
|
377
|
+
/** The rows the filing's summary counts, when known: a large filing's first rows come first. */
|
|
378
|
+
entries?: number | null;
|
|
379
|
+
/** The filing's first rows, while a large filing the service has not read yet is still loading. */
|
|
380
|
+
onFirstRows?: (rows: ThirteenFHoldingRecord[]) => void;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* A filing's whole information table: one request to Gloom Cloud, which
|
|
385
|
+
* reads the source's pages itself and keeps the filing. An older service
|
|
386
|
+
* without that answer (a 404, remembered for the session) or one that cannot
|
|
387
|
+
* be reached is read a page at a time, a few pages at once, as before; a page
|
|
388
|
+
* Gloom Cloud cannot send comes from the public source. A busy or failing
|
|
389
|
+
* service (429, 5xx) is the error: paging it would only ask it more.
|
|
390
|
+
*/
|
|
346
391
|
export async function listThirteenFFormHoldings(
|
|
347
392
|
cik: string,
|
|
348
393
|
accessionNumber: string,
|
|
349
394
|
signal?: AbortSignal,
|
|
350
|
-
options:
|
|
395
|
+
options: FilingHoldingsOptions = {},
|
|
396
|
+
): Promise<ThirteenFHoldingRecord[]> {
|
|
397
|
+
const { entries, onFirstRows, ...readOptions } = options;
|
|
398
|
+
if (wholeFilingsMissing) return listPagedHoldings(cik, accessionNumber, signal, readOptions);
|
|
399
|
+
// The count lets a filing the service has not stored yet be read in fewer requests.
|
|
400
|
+
const params = {
|
|
401
|
+
cik: normalizeCik(cik),
|
|
402
|
+
accession_number: accessionNumber,
|
|
403
|
+
...(entries != null && entries > 0 ? { entries } : {}),
|
|
404
|
+
};
|
|
405
|
+
const whole = fetchForms13F<unknown>("/holdings", params, { ...readOptions, signal, cloudOnly: true });
|
|
406
|
+
let answered = false;
|
|
407
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
408
|
+
if (onFirstRows && entries != null && entries > FIRST_ROWS) {
|
|
409
|
+
timer = setTimeout(() => {
|
|
410
|
+
if (answered) return;
|
|
411
|
+
void fetchForms13F<unknown>("/holdings", { ...params, limit: FIRST_ROWS }, { signal, cloudOnly: true, cache: false })
|
|
412
|
+
.then((raw) => { if (!answered) onFirstRows(filingRows(raw)); })
|
|
413
|
+
.catch(() => {
|
|
414
|
+
// Intentional ignore: the first rows only come ahead of the whole filing, which reports its own failure.
|
|
415
|
+
});
|
|
416
|
+
}, FIRST_ROWS_AFTER_MS);
|
|
417
|
+
}
|
|
418
|
+
try {
|
|
419
|
+
return filingRows(await whole);
|
|
420
|
+
} catch (error) {
|
|
421
|
+
signal?.throwIfAborted();
|
|
422
|
+
const missing = error instanceof ApiRequestError && error.status === 404;
|
|
423
|
+
if (!missing && !isNetworkFailure(error)) throw error;
|
|
424
|
+
if (missing) wholeFilingsMissing = true;
|
|
425
|
+
return listPagedHoldings(cik, accessionNumber, signal, readOptions);
|
|
426
|
+
} finally {
|
|
427
|
+
answered = true;
|
|
428
|
+
clearTimeout(timer);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** A filing read a page at a time, a few pages at once, until a page comes back short. */
|
|
433
|
+
async function listPagedHoldings(
|
|
434
|
+
cik: string,
|
|
435
|
+
accessionNumber: string,
|
|
436
|
+
signal: AbortSignal | undefined,
|
|
437
|
+
options: Forms13FReadOptions,
|
|
351
438
|
): Promise<ThirteenFHoldingRecord[]> {
|
|
352
|
-
const
|
|
353
|
-
|
|
439
|
+
const pages: ThirteenFHoldingRecord[][] = [];
|
|
440
|
+
const maxPages = Math.ceil(MAX_FORM_ROWS / FILING_PAGE_ROWS);
|
|
441
|
+
let next = 0;
|
|
442
|
+
let last: number | null = null;
|
|
443
|
+
let failed = false;
|
|
444
|
+
async function readPage() {
|
|
354
445
|
signal?.throwIfAborted();
|
|
446
|
+
const index = next++;
|
|
355
447
|
const page = await listThirteenFFormHoldingsPage(cik, accessionNumber, signal, {
|
|
356
448
|
...options,
|
|
357
|
-
offset,
|
|
358
|
-
limit:
|
|
449
|
+
offset: index * FILING_PAGE_ROWS,
|
|
450
|
+
limit: FILING_PAGE_ROWS,
|
|
359
451
|
});
|
|
360
|
-
|
|
361
|
-
if (
|
|
452
|
+
pages[index] = page.rows;
|
|
453
|
+
if (page.rows.length < FILING_PAGE_ROWS && (last === null || index < last)) last = index;
|
|
454
|
+
}
|
|
455
|
+
async function worker() {
|
|
456
|
+
try {
|
|
457
|
+
while (!failed && next < maxPages && (last === null || next <= last)) await readPage();
|
|
458
|
+
} catch (error) {
|
|
459
|
+
failed = true;
|
|
460
|
+
throw error;
|
|
461
|
+
}
|
|
362
462
|
}
|
|
363
|
-
|
|
463
|
+
// The first page alone says whether there is more: most filings fit in it.
|
|
464
|
+
await readPage();
|
|
465
|
+
await Promise.all(Array.from({ length: FILING_PAGE_CONCURRENCY }, worker));
|
|
466
|
+
return pages.slice(0, (last ?? maxPages - 1) + 1).flat();
|
|
364
467
|
}
|
|
365
468
|
|
|
366
469
|
export async function lookupThirteenFTickers(
|