@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
|
@@ -15,6 +15,7 @@ import { isoDateDaysAgo, isoDateToday, isoDateYearsAgo } from "../../../utils/ca
|
|
|
15
15
|
import { abortable, withDeadline } from "../../../utils/async-deadline";
|
|
16
16
|
import { logFailure } from "../../../utils/log-failure";
|
|
17
17
|
import {
|
|
18
|
+
amendmentKind,
|
|
18
19
|
buildBrowserRows,
|
|
19
20
|
filingGapFor,
|
|
20
21
|
estimateFilingQuarterReturn,
|
|
@@ -33,6 +34,7 @@ import type {
|
|
|
33
34
|
FundBrowserRow,
|
|
34
35
|
FundDetailData,
|
|
35
36
|
ThirteenFBrowserTab,
|
|
37
|
+
ThirteenFDetailLoading,
|
|
36
38
|
ThirteenFFilingGap,
|
|
37
39
|
ThirteenFFund,
|
|
38
40
|
ThirteenFHoldingRecord,
|
|
@@ -106,6 +108,8 @@ export interface BrowserLoadResult {
|
|
|
106
108
|
period?: string;
|
|
107
109
|
quarter?: string;
|
|
108
110
|
warning?: string;
|
|
111
|
+
/** A refresh failed and the oldest copy that answered instead was saved at this time (ms). */
|
|
112
|
+
savedAt?: number;
|
|
109
113
|
hasMore?: boolean;
|
|
110
114
|
nextOffset?: number;
|
|
111
115
|
reportsLoaded?: Promise<void>;
|
|
@@ -118,11 +122,31 @@ export async function loadBrowserRows(
|
|
|
118
122
|
options: Pick<Forms13FReadOptions, "forceRefresh" | "onSavedCopy"> & { offset?: number; limit?: number } = {},
|
|
119
123
|
): Promise<BrowserLoadResult> {
|
|
120
124
|
const warnings: string[] = [];
|
|
121
|
-
const result
|
|
125
|
+
const page: { result?: BrowserLoadResult; savedAt?: number } = {};
|
|
126
|
+
const onSavedCopy = (savedAt: number) => {
|
|
127
|
+
page.savedAt = Math.min(page.savedAt ?? savedAt, savedAt);
|
|
128
|
+
// The rows' reports answer after the page does, and their copies date it too.
|
|
129
|
+
if (page.result) page.result.savedAt = page.savedAt;
|
|
130
|
+
options.onSavedCopy?.(savedAt);
|
|
131
|
+
};
|
|
132
|
+
const result = await loadBrowserPage(tab, query, signal, { ...options, onSavedCopy, onWarning: warning => warnings.push(warning) });
|
|
133
|
+
page.result = result;
|
|
134
|
+
if (page.savedAt != null) result.savedAt = page.savedAt;
|
|
122
135
|
if (warnings.length) result.warning = [...new Set([result.warning, ...warnings].filter(Boolean))].join(" ") || undefined;
|
|
123
136
|
return result;
|
|
124
137
|
}
|
|
125
138
|
|
|
139
|
+
/** The oldest saved copy a load answered from (`savedAt`), each also passed on to `onSavedCopy`. */
|
|
140
|
+
function savedCopies(onSavedCopy?: (savedAt: number) => void) {
|
|
141
|
+
const copies: { savedAt?: number; note: (savedAt: number) => void } = {
|
|
142
|
+
note: (savedAt) => {
|
|
143
|
+
copies.savedAt = Math.min(copies.savedAt ?? savedAt, savedAt);
|
|
144
|
+
onSavedCopy?.(savedAt);
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
return copies;
|
|
148
|
+
}
|
|
149
|
+
|
|
126
150
|
async function loadBrowserPage(
|
|
127
151
|
tab: ThirteenFBrowserTab,
|
|
128
152
|
query: string,
|
|
@@ -140,9 +164,12 @@ async function loadBrowserPage(
|
|
|
140
164
|
const pageApiOptions = { ...apiOptions, offset };
|
|
141
165
|
|
|
142
166
|
if (tab === "performance") {
|
|
143
|
-
const topFunds = await listTopThirteenFFunds(quarter, browserLimit, signal, pageApiOptions);
|
|
144
167
|
const quarters = priorQuarters(quarter);
|
|
145
|
-
|
|
168
|
+
// The prior quarters' rankings do not depend on this page, so they are read beside it.
|
|
169
|
+
const [topFunds, history] = await Promise.all([
|
|
170
|
+
listTopThirteenFFunds(quarter, browserLimit, signal, pageApiOptions),
|
|
171
|
+
loadRankingHistory(quarters, signal, apiOptions),
|
|
172
|
+
]);
|
|
146
173
|
const funds: ThirteenFFund[] = topFunds.map((fund) => ({ cik: fund.cik, name: fund.name }));
|
|
147
174
|
const rows = buildBrowserRows({ topFunds, source: "performance" }).map(row => ({ ...row, priorReturns: quarters.map((previous, index) => ({ quarter: previous, value: history[index]?.get(row.cik) ?? null })) }));
|
|
148
175
|
const result: BrowserLoadResult = {
|
|
@@ -161,7 +188,10 @@ async function loadBrowserPage(
|
|
|
161
188
|
|
|
162
189
|
if (tab === "latest") {
|
|
163
190
|
const filings = await listThirteenFFilings(isoDateDaysAgo(21, now), to, latestLimit, signal, pageApiOptions);
|
|
164
|
-
|
|
191
|
+
// An original filing or a restatement is its period's whole report, and an
|
|
192
|
+
// amendment of unknown kind shows no totals either way: only one that adds
|
|
193
|
+
// NEW HOLDINGS needs the fund's earlier filing for its quarter's totals.
|
|
194
|
+
const selected = dedupeLatestForms(filings).filter((form) => amendmentKind(form) === "new-holdings");
|
|
165
195
|
const rows = buildBrowserRows({ latestFilings: filings, source: "latest" });
|
|
166
196
|
const result: BrowserLoadResult = {
|
|
167
197
|
rows,
|
|
@@ -440,7 +470,8 @@ export async function loadFundPerformance(
|
|
|
440
470
|
options: Pick<Forms13FReadOptions, "forceRefresh" | "onSavedCopy"> & { splitFactor?: SplitFactorLookup } = {},
|
|
441
471
|
): Promise<FundPerformanceResult> {
|
|
442
472
|
const warnings: string[] = [];
|
|
443
|
-
const
|
|
473
|
+
const saved = savedCopies(options.onSavedCopy);
|
|
474
|
+
const apiOptions = { forceRefresh: options.forceRefresh, onSavedCopy: saved.note, onWarning: (warning: string) => warnings.push(warning) };
|
|
444
475
|
const detail = await loadFundDetail(cik, fallbackName, signal, options);
|
|
445
476
|
const period = detail.latestForm?.periodOfReport;
|
|
446
477
|
const quarter = period ? periodToQuarter(period) : null;
|
|
@@ -482,7 +513,9 @@ export async function loadFundPerformance(
|
|
|
482
513
|
: filed?.skipped.length ? `No share position held through the quarter to ${period} that could be priced across it.`
|
|
483
514
|
: `No share position held through the quarter to ${period}.`;
|
|
484
515
|
const warning = [...new Set([...(detail.warnings ?? []), ...warnings])].join(" ") || undefined;
|
|
516
|
+
const savedAt = Math.min(detail.savedAt ?? Infinity, saved.savedAt ?? Infinity);
|
|
485
517
|
return {
|
|
518
|
+
...(Number.isFinite(savedAt) ? { savedAt } : {}),
|
|
486
519
|
rows: [row], ...(quarter ? { quarter } : {}), ...(period ? { period } : {}), hasMore: false, basis, ranking,
|
|
487
520
|
...(filed?.splits.length ? { splits: filed.splits } : {}),
|
|
488
521
|
...(filed?.skipped.length ? { skipped: filed.skipped } : {}),
|
|
@@ -491,16 +524,71 @@ export async function loadFundPerformance(
|
|
|
491
524
|
};
|
|
492
525
|
}
|
|
493
526
|
|
|
527
|
+
/** Rows a fund's two newest reports may hold together for their holdings to be read ahead. */
|
|
528
|
+
const PREFETCH_HOLDINGS_MAX = 2_000;
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Reads ahead what opening a fund asks first, while the cursor rests on its
|
|
532
|
+
* row: its filing list, which every other request waits on, and the holdings
|
|
533
|
+
* of its two newest reports when they hold PREFETCH_HOLDINGS_MAX rows or fewer
|
|
534
|
+
* together. A large filer's holdings wait for Enter. These are loadFundDetail's
|
|
535
|
+
* own requests, so opening the fund reads them from cache or joins them.
|
|
536
|
+
*/
|
|
537
|
+
export async function prefetchFundDetail(cik: string, signal: AbortSignal): Promise<void> {
|
|
538
|
+
const now = new Date();
|
|
539
|
+
const forms = await listThirteenFForms(cik, isoDateYearsAgo(4, now), isoDateToday(now), 100, signal);
|
|
540
|
+
const filings = buildPeriodReports(forms).slice(0, 2).flatMap((report) => report.filings);
|
|
541
|
+
const rows = filings.reduce((sum, form) => sum + (form.tableEntryTotal ?? Infinity), 0);
|
|
542
|
+
if (!filings.length || rows > PREFETCH_HOLDINGS_MAX) return;
|
|
543
|
+
await Promise.all(filings.map((form) => listThirteenFFormHoldings(cik, form.accessionNumber, signal, { entries: form.tableEntryTotal })));
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* One fund read ahead at a time (prefetchFundDetail): resting on another row
|
|
548
|
+
* stops the last one's requests, and `cancel` stops it when the list goes.
|
|
549
|
+
*/
|
|
550
|
+
export function createFundPrefetcher(prefetch: (cik: string, signal: AbortSignal) => Promise<void> = prefetchFundDetail) {
|
|
551
|
+
let current: { cik: string; controller: AbortController } | null = null;
|
|
552
|
+
return {
|
|
553
|
+
prefetch(cik: string) {
|
|
554
|
+
if (current?.cik === cik) return;
|
|
555
|
+
current?.controller.abort();
|
|
556
|
+
const entry = { cik, controller: new AbortController() };
|
|
557
|
+
current = entry;
|
|
558
|
+
void prefetch(cik, entry.controller.signal).catch(() => {
|
|
559
|
+
// Intentional ignore: a fund read ahead that fails is read again, and the failure shown, when it opens.
|
|
560
|
+
}).finally(() => {
|
|
561
|
+
if (current === entry) current = null;
|
|
562
|
+
});
|
|
563
|
+
},
|
|
564
|
+
cancel() {
|
|
565
|
+
current?.controller.abort();
|
|
566
|
+
current = null;
|
|
567
|
+
},
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* A fund's latest report and the one before it, with their holdings. With
|
|
573
|
+
* `onProgress`, the fund as it fills in: the latest report's holdings as
|
|
574
|
+
* soon as they are read (a large filing's first rows before the rest), then
|
|
575
|
+
* the prior quarter they are compared with and EDGAR's filing list, each
|
|
576
|
+
* part still loading named in `loading`. The answer is the whole fund.
|
|
577
|
+
*/
|
|
494
578
|
export async function loadFundDetail(
|
|
495
579
|
cik: string,
|
|
496
580
|
fallbackName: string,
|
|
497
581
|
signal?: AbortSignal,
|
|
498
582
|
/** `filingGap: false` skips EDGAR's filing list, for a load that never shows it (an overlap). */
|
|
499
|
-
options: Pick<Forms13FReadOptions, "forceRefresh" | "onSavedCopy"> & {
|
|
583
|
+
options: Pick<Forms13FReadOptions, "forceRefresh" | "onSavedCopy"> & {
|
|
584
|
+
filingGap?: boolean;
|
|
585
|
+
onProgress?: (detail: FundDetailData) => void;
|
|
586
|
+
} = {},
|
|
500
587
|
): Promise<FundDetailData> {
|
|
501
588
|
const now = new Date();
|
|
502
589
|
const warnings: string[] = [];
|
|
503
|
-
const
|
|
590
|
+
const saved = savedCopies(options.onSavedCopy);
|
|
591
|
+
const apiOptions = { forceRefresh: options.forceRefresh, onSavedCopy: saved.note, onWarning: (warning: string) => warnings.push(warning) };
|
|
504
592
|
|
|
505
593
|
const forms = await listThirteenFForms(
|
|
506
594
|
cik,
|
|
@@ -515,46 +603,87 @@ export async function loadFundDetail(
|
|
|
515
603
|
const previousReport = reports[1];
|
|
516
604
|
const latestForm = latestReport?.filings.at(-1) ?? null;
|
|
517
605
|
const previousForm = previousReport?.filings.at(-1) ?? null;
|
|
518
|
-
|
|
606
|
+
const checkGap = options.filingGap !== false;
|
|
607
|
+
const parts: Pick<FundDetailData, "latestHoldings" | "previousHoldings" | "filingGap"> & { loading: ThirteenFDetailLoading } = {
|
|
608
|
+
latestHoldings: [],
|
|
609
|
+
previousHoldings: [],
|
|
610
|
+
filingGap: null,
|
|
611
|
+
loading: { positions: true, ...(previousReport ? { comparison: true } : {}), ...(checkGap ? { filingGap: true } : {}) },
|
|
612
|
+
};
|
|
613
|
+
const detail = (): FundDetailData => {
|
|
614
|
+
const loading = Object.fromEntries(Object.entries(parts.loading).filter(([, value]) => value)) as ThirteenFDetailLoading;
|
|
615
|
+
return {
|
|
616
|
+
cik: normalizeCik(cik),
|
|
617
|
+
name: latestForm?.companyName || fallbackName,
|
|
618
|
+
forms,
|
|
619
|
+
latestForm,
|
|
620
|
+
previousForm,
|
|
621
|
+
latestHoldings: parts.latestHoldings,
|
|
622
|
+
previousHoldings: parts.previousHoldings,
|
|
623
|
+
latestReport,
|
|
624
|
+
previousReport,
|
|
625
|
+
warnings: [...warnings],
|
|
626
|
+
filingGap: parts.filingGap,
|
|
627
|
+
...(saved.savedAt != null ? { savedAt: saved.savedAt } : {}),
|
|
628
|
+
...(Object.keys(loading).length ? { loading } : {}),
|
|
629
|
+
};
|
|
630
|
+
};
|
|
631
|
+
let failed = false;
|
|
632
|
+
// The fund is shown once its latest holdings, or a large filing's first rows, are in.
|
|
633
|
+
const progress = () => {
|
|
634
|
+
if (!options.onProgress || failed || signal?.aborted || (parts.loading.positions && !parts.latestHoldings.length)) return;
|
|
635
|
+
options.onProgress(detail());
|
|
636
|
+
};
|
|
637
|
+
async function loadReport(report: ThirteenFPeriodReport | undefined, onFirstRows?: (rows: ThirteenFHoldingRecord[]) => void): Promise<ThirteenFHoldingRecord[]> {
|
|
519
638
|
if (!report) return [];
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
639
|
+
// An amended period's filings are read at once: the original and each of its additions.
|
|
640
|
+
const filings = await Promise.all(report.filings.map((form, index) => listThirteenFFormHoldings(cik, form.accessionNumber, signal, {
|
|
641
|
+
...apiOptions,
|
|
642
|
+
entries: form.tableEntryTotal,
|
|
643
|
+
...(index === 0 && onFirstRows ? { onFirstRows } : {}),
|
|
644
|
+
})));
|
|
645
|
+
report.filings.forEach((form, index) => {
|
|
646
|
+
const rows = filings[index]!;
|
|
524
647
|
if (form.tableEntryTotal != null && rows.length !== form.tableEntryTotal) {
|
|
525
648
|
report.complete = false;
|
|
526
649
|
warnings.push(`${report.periodOfReport}: loaded ${rows.length} of ${form.tableEntryTotal} disclosed entries for ${form.accessionNumber}.`);
|
|
527
650
|
}
|
|
528
|
-
}
|
|
651
|
+
});
|
|
529
652
|
if (!report.complete && !warnings.some((warning) => warning.startsWith(report.periodOfReport))) {
|
|
530
653
|
warnings.push(`${report.periodOfReport}: amendment type or original report unavailable; disclosed entries cannot be reconciled into a full report.`);
|
|
531
654
|
}
|
|
532
|
-
return
|
|
655
|
+
return filings.flat();
|
|
533
656
|
}
|
|
534
|
-
|
|
535
|
-
loadReport(latestReport)
|
|
657
|
+
await Promise.all([
|
|
658
|
+
loadReport(latestReport, options.onProgress ? (rows) => {
|
|
659
|
+
if (!parts.loading.positions) return;
|
|
660
|
+
parts.latestHoldings = rows;
|
|
661
|
+
progress();
|
|
662
|
+
} : undefined).then((rows) => {
|
|
663
|
+
parts.latestHoldings = rows;
|
|
664
|
+
parts.loading.positions = false;
|
|
665
|
+
progress();
|
|
666
|
+
}),
|
|
536
667
|
loadReport(previousReport).catch((error) => {
|
|
537
668
|
if (signal?.aborted) throw error;
|
|
538
669
|
if (previousReport) previousReport.complete = false;
|
|
539
670
|
warnings.push(`${previousReport?.periodOfReport ?? "Prior quarter"}: holdings unavailable; comparison unavailable.`);
|
|
540
671
|
return [];
|
|
672
|
+
}).then((rows) => {
|
|
673
|
+
parts.previousHoldings = rows;
|
|
674
|
+
parts.loading.comparison = false;
|
|
675
|
+
progress();
|
|
541
676
|
}),
|
|
542
|
-
options.
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
previousHoldings,
|
|
553
|
-
latestReport,
|
|
554
|
-
previousReport,
|
|
555
|
-
warnings,
|
|
556
|
-
filingGap,
|
|
557
|
-
};
|
|
677
|
+
(checkGap ? loadFilingGap(cik, latestForm?.periodOfReport, signal, now, { forceRefresh: options.forceRefresh }) : Promise.resolve(null)).then((gap) => {
|
|
678
|
+
parts.filingGap = gap;
|
|
679
|
+
parts.loading.filingGap = false;
|
|
680
|
+
progress();
|
|
681
|
+
}),
|
|
682
|
+
]).catch((error: unknown) => {
|
|
683
|
+
failed = true;
|
|
684
|
+
throw error;
|
|
685
|
+
});
|
|
686
|
+
return detail();
|
|
558
687
|
}
|
|
559
688
|
|
|
560
689
|
export async function loadFilingPositions(
|
|
@@ -562,12 +691,13 @@ export async function loadFilingPositions(
|
|
|
562
691
|
accessionNumber: string,
|
|
563
692
|
signal?: AbortSignal,
|
|
564
693
|
options: { forceRefresh?: boolean; offset?: number; limit?: number } = {},
|
|
565
|
-
): Promise<{ rows: ThirteenFHoldingRecord[]; hasMore: boolean; warnings: string[] }> {
|
|
694
|
+
): Promise<{ rows: ThirteenFHoldingRecord[]; hasMore: boolean; warnings: string[]; savedAt?: number }> {
|
|
566
695
|
const warnings: string[] = [];
|
|
696
|
+
const saved = savedCopies();
|
|
567
697
|
const result = await listThirteenFFormHoldingsPage(cik, accessionNumber, signal, {
|
|
568
|
-
...options, onWarning: warning => warnings.push(warning),
|
|
698
|
+
...options, onSavedCopy: saved.note, onWarning: warning => warnings.push(warning),
|
|
569
699
|
});
|
|
570
|
-
return { ...result, warnings };
|
|
700
|
+
return { ...result, warnings, ...(saved.savedAt != null ? { savedAt: saved.savedAt } : {}) };
|
|
571
701
|
}
|
|
572
702
|
|
|
573
703
|
/** `2026Q2` for a quarter end such as 2026-06-30. */
|
|
@@ -6,13 +6,11 @@ import {
|
|
|
6
6
|
inferBrowserTabFromQuery,
|
|
7
7
|
isCikQuery,
|
|
8
8
|
} from "./model";
|
|
9
|
-
import { ThirteenFPane } from "./pane";
|
|
10
9
|
import {
|
|
11
10
|
attachThirteenFApiPersistence,
|
|
12
11
|
resetThirteenFApiPersistence,
|
|
13
12
|
} from "./api";
|
|
14
13
|
import { thirteenFHeadless } from "./headless";
|
|
15
|
-
import { ThirteenFTickerPane } from "./signals-pane";
|
|
16
14
|
import { isKnownNonUsListing } from "../../../utils/sec";
|
|
17
15
|
import { isKnownMutualFund } from "../../../tickers/instrument-kind";
|
|
18
16
|
|
|
@@ -32,7 +30,7 @@ export const thirteenFModule: PluginModule = {
|
|
|
32
30
|
id: "thirteenf",
|
|
33
31
|
name: "13F",
|
|
34
32
|
order: 39,
|
|
35
|
-
component: ThirteenFTickerPane,
|
|
33
|
+
get component(): typeof import("./signals-pane").ThirteenFTickerPane { const { ThirteenFTickerPane } = require("./signals-pane"); return ThirteenFTickerPane; },
|
|
36
34
|
instruments: ["equity", "fund"],
|
|
37
35
|
isVisible: ({ ticker, financials }) => !isKnownNonUsListing(ticker) && !isKnownMutualFund(ticker, financials),
|
|
38
36
|
});
|
|
@@ -47,7 +45,7 @@ export const thirteenFModule: PluginModule = {
|
|
|
47
45
|
id: THIRTEENF_PANE_ID,
|
|
48
46
|
name: "13F Funds",
|
|
49
47
|
icon: "13F",
|
|
50
|
-
component: ThirteenFPane,
|
|
48
|
+
get component(): typeof import("./pane").ThirteenFPane { const { ThirteenFPane } = require("./pane"); return ThirteenFPane; },
|
|
51
49
|
defaultPosition: "right",
|
|
52
50
|
defaultMode: "floating",
|
|
53
51
|
defaultFloatingSize: { width: 112, height: 36 },
|
|
@@ -547,6 +547,8 @@ export function estimateFilingQuarterReturn(data: FundDetailData, splits: SplitF
|
|
|
547
547
|
|
|
548
548
|
export function hasComparable13FQuarter(data: FundDetailData): boolean {
|
|
549
549
|
if (data.latestReport?.complete === false || data.previousReport?.complete === false) return false;
|
|
550
|
+
// Until both quarters are in whole, a line one of them lacks is not a buy or a sale.
|
|
551
|
+
if (data.loading?.positions || data.loading?.comparison) return false;
|
|
550
552
|
const current = data.latestForm?.periodOfReport;
|
|
551
553
|
const previous = data.previousForm?.periodOfReport;
|
|
552
554
|
return adjacentQuarterPeriods(current, previous);
|
|
@@ -10,6 +10,7 @@ import { usePluginPaneState, usePluginTickerActions } from "../../runtime";
|
|
|
10
10
|
import { useMineTickers } from "../shared/mine-tickers";
|
|
11
11
|
import { usePaneStatusFooter } from "../../../components/layout/pane/status-footer";
|
|
12
12
|
import { handleRefreshKey } from "../../../components/data-table/table-pane";
|
|
13
|
+
import { savedCopyFooterInfo } from "../../../components/layout/pane/footer";
|
|
13
14
|
import { useAsyncResource } from "../../../react/async-resource";
|
|
14
15
|
import { cachedFunds, cachedMembers, loadFunds, loadMembers } from "../members/client";
|
|
15
16
|
import { coveredFund } from "../members/model";
|
|
@@ -115,7 +116,10 @@ export function FundOverlapView({ data, focused, width }: { data: FundDetailData
|
|
|
115
116
|
const period = peer ? overlapPeriod(data, peer) : null;
|
|
116
117
|
const mismatch = peer && !period;
|
|
117
118
|
const listError = etf ? etfFile.error : error ?? paged.error?.message ?? paged.moreError?.message ?? null;
|
|
118
|
-
|
|
119
|
+
// Either side answered from a copy a failed refresh left says when it was saved.
|
|
120
|
+
const etfSavedAt = etf ? etfFile.data?.savedAt : null;
|
|
121
|
+
const savedInfo = useMemo(() => savedCopyFooterInfo([data.savedAt, peer?.savedAt, etfSavedAt]), [data.savedAt, peer?.savedAt, etfSavedAt]);
|
|
122
|
+
usePaneStatusFooter({ registrationId: "13f-overlap", loading: loading || loadingMore || paged.loading || (!!etf && etfFile.loading), error: listError, info: savedInfo, hints: open ? [{ id: "mine", key: "m", label: mineOnly ? "all tickers" : "mine", title: mineOnly ? "All Tickers" : "Mine Only", onPress: () => setMineOnly(value => !value) }] : [{ id: "search", key: "/", label: "search", onPress: focusSearch }] });
|
|
119
123
|
usePaneNoticeFooter({ registrationId: "13f-overlap-notice", focused, notices: [
|
|
120
124
|
...(peer?.warnings ?? []),
|
|
121
125
|
...(mismatch ? ["No reporting quarter is loaded for both funds; overlap is unavailable."] : []),
|
|
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
2
2
|
import {
|
|
3
3
|
DataTableStackView,
|
|
4
4
|
DataTableView,
|
|
5
|
-
EmptyState, PaneStatusBody, QueryBar, StatGrid, usePagedRows, usePaneNoticeFooter, usePaneTabs, useQueryBarSearch, useTableLoadMore, type DataTableKeyEvent,
|
|
5
|
+
EmptyState, PaneStatusBody, QueryBar, StatGrid, usePagedRows, usePaneFooter, usePaneNoticeFooter, usePaneTabs, useQueryBarSearch, useTableLoadMore, type DataTableKeyEvent,
|
|
6
6
|
type DataTableRootKeyContext, type PageRequest, type PaneFooterSegment, type PaneHint, type StatItem
|
|
7
7
|
} from "../../../components";
|
|
8
8
|
import { useShortcut } from "../../../react/input";
|
|
@@ -23,10 +23,10 @@ import { nextHeaderSort } from "../../../utils/sort-values";
|
|
|
23
23
|
import { useAssetData, usePluginPaneState, usePluginTickerActions } from "../../runtime";
|
|
24
24
|
import { useMineTickers } from "../shared/mine-tickers";
|
|
25
25
|
import { usePaneStatusFooter, usePaneStatusLinkFooter } from "../../../components/layout/pane/status-footer";
|
|
26
|
-
import { corporateSplitFactor, loadBrowserRows, loadFilingPositions, loadFundDetail, loadSplitFactors } from "./data";
|
|
26
|
+
import { corporateSplitFactor, createFundPrefetcher, loadBrowserRows, loadFilingPositions, loadFundDetail, loadSplitFactors } from "./data";
|
|
27
27
|
import { FundOverlapView } from "./overlap-pane";
|
|
28
28
|
import { ThirteenFCrowdingPane, ThirteenFTickerHoldingsView } from "./signals-pane";
|
|
29
|
-
import { PaneFooterScope } from "../../../components/layout/pane/footer";
|
|
29
|
+
import { PaneFooterScope, savedCopyFooterInfo } from "../../../components/layout/pane/footer";
|
|
30
30
|
import { handleRefreshKey } from "../../../components/data-table/table-pane";
|
|
31
31
|
import { useClaimResearchTabKeys } from "../ticker-detail/research-tab-keys";
|
|
32
32
|
import {
|
|
@@ -129,6 +129,9 @@ function ThirteenFBrowserPane({ focused, width, height, onDetailChange }: PanePr
|
|
|
129
129
|
const notes = [...(pagesSettled ? browser.pages.map((page) => page.warning) : []), browser.moreError?.message];
|
|
130
130
|
return [...new Set(notes.filter(Boolean))].join(" ") || null;
|
|
131
131
|
}, [browser.moreError, browser.pages, pagesSettled, reportPaint]);
|
|
132
|
+
// A page a failed refresh answered from a saved copy says when it was saved, as every pane does.
|
|
133
|
+
const savedInfo = useMemo(() => savedCopyFooterInfo(pagesSettled ? browser.pages.map((page) => page.savedAt) : []),
|
|
134
|
+
[browser.pages, pagesSettled, reportPaint]);
|
|
132
135
|
const [detailSeed, setDetailSeed] = useState<FundSeed | null>(() => (
|
|
133
136
|
initialCik ? { cik: String(initialCik), name: normalizedQuery || String(initialCik) } : null
|
|
134
137
|
));
|
|
@@ -166,7 +169,9 @@ function ThirteenFBrowserPane({ focused, width, height, onDetailChange }: PanePr
|
|
|
166
169
|
}, { allowEditable: true });
|
|
167
170
|
|
|
168
171
|
const browserSort = useMemo(() => browserSortFor(sortPreference, browserMode), [sortPreference, browserMode]);
|
|
169
|
-
|
|
172
|
+
// The rows' reports fill the rows in place after their page answers (from the network or a saved copy);
|
|
173
|
+
// a row the table drew before then is drawn again only as a new object.
|
|
174
|
+
const sortedRows = useMemo(() => sortBrowserRows(rows.map((row) => ({ ...row })), browserSort), [rows, browserSort, reportPaint]);
|
|
170
175
|
const columns = useMemo(() => {
|
|
171
176
|
if (browserMode !== "performance") return buildBrowserColumns(false);
|
|
172
177
|
const history = rows[0]?.priorReturns ?? [];
|
|
@@ -191,18 +196,17 @@ function ThirteenFBrowserPane({ focused, width, height, onDetailChange }: PanePr
|
|
|
191
196
|
blurSearch();
|
|
192
197
|
setDetailSeed({ cik: row.cik, name: row.name });
|
|
193
198
|
}, [blurSearch]);
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
});
|
|
200
|
-
}, []);
|
|
199
|
+
// The fund's first requests are read ahead while the cursor rests on its
|
|
200
|
+
// row, one fund at a time, and stopped when the cursor moves on.
|
|
201
|
+
const prefetcher = useMemo(() => createFundPrefetcher(), []);
|
|
202
|
+
useEffect(() => () => prefetcher.cancel(), [prefetcher]);
|
|
203
|
+
const prefetchDetail = useCallback((row: FundBrowserRow) => prefetcher.prefetch(row.cik), [prefetcher]);
|
|
201
204
|
|
|
202
205
|
const browserStatusInfo = useMemo<PaneFooterSegment[]>(() => [
|
|
203
206
|
...(loadingMore ? [{ id: "loading-more", parts: [{ text: "loading more", tone: "muted" as const }] }] : []),
|
|
204
207
|
...(warning ? [{ id: "warning", parts: [{ text: warning, tone: "warning" as const }] }] : []),
|
|
205
|
-
|
|
208
|
+
...savedInfo,
|
|
209
|
+
], [loadingMore, warning, savedInfo]);
|
|
206
210
|
const searchHints = useMemo(() => [{ id: "search", key: "/", label: "search", onPress: focusSearch }], [focusSearch]);
|
|
207
211
|
usePaneStatusFooter({
|
|
208
212
|
registrationId: THIRTEENF_PANE_ID,
|
|
@@ -366,7 +370,11 @@ export function FundDetailView({
|
|
|
366
370
|
abortRef.current = controller;
|
|
367
371
|
setStatus("loading");
|
|
368
372
|
setError(null);
|
|
369
|
-
void loadFundDetail(seed.cik, seed.name, controller.signal, {
|
|
373
|
+
void loadFundDetail(seed.cik, seed.name, controller.signal, {
|
|
374
|
+
forceRefresh: refresh,
|
|
375
|
+
// A first load shows the fund as it fills in; a refresh keeps what is on screen until it answers whole.
|
|
376
|
+
...(refresh ? {} : { onProgress: (partial: FundDetailData) => { if (abortRef.current === controller) setData(partial); } }),
|
|
377
|
+
})
|
|
370
378
|
.then((nextData) => {
|
|
371
379
|
if (abortRef.current !== controller) return;
|
|
372
380
|
setData(nextData);
|
|
@@ -386,6 +394,8 @@ export function FundDetailView({
|
|
|
386
394
|
.catch((loadError) => {
|
|
387
395
|
if (abortRef.current !== controller) return;
|
|
388
396
|
if (loadError instanceof Error && loadError.name === "AbortError") return;
|
|
397
|
+
// A first load that failed after its first rows shows the error, not a part of the filing as the fund.
|
|
398
|
+
setData((current) => current?.loading ? null : current);
|
|
389
399
|
setError(loadError instanceof Error ? loadError.message : String(loadError));
|
|
390
400
|
setStatus("error");
|
|
391
401
|
});
|
|
@@ -516,7 +526,10 @@ export function FundDetailView({
|
|
|
516
526
|
...(staleFiling ? { tone: "warning" as const } : {}) },
|
|
517
527
|
data && hasComparable13FQuarter(data)
|
|
518
528
|
? { id: "compared", label: "Compared with", value: data.previousForm!.periodOfReport }
|
|
519
|
-
|
|
529
|
+
// The quarter the changes will compare with, dim until both are in.
|
|
530
|
+
: data?.loading && hasComparable13FQuarter({ ...data, loading: undefined })
|
|
531
|
+
? { id: "compared", label: "Compared with", value: data.previousForm!.periodOfReport, tone: "muted" }
|
|
532
|
+
: { id: "compared", label: "Compared with", value: "No prior quarter", tone: "muted" },
|
|
520
533
|
...(data?.latestReport && data.latestReport.filings.length > 1
|
|
521
534
|
? [{ id: "combined", label: "Combined", value: `${data.latestReport.filings.length} filings` }]
|
|
522
535
|
: []),
|
|
@@ -544,6 +557,9 @@ export function FundDetailView({
|
|
|
544
557
|
const detailHints = useMemo<PaneHint[]>(() => hasHoldingFiling
|
|
545
558
|
? [{ id: "filing", key: "f", label: "iling", title: "Open Filing", onPress: openSelectedFilingInPane }]
|
|
546
559
|
: [], [hasHoldingFiling, openSelectedFilingInPane]);
|
|
560
|
+
// An open filing and the overlap tab say when their own saved copies are from.
|
|
561
|
+
const detailSavedInfo = useMemo(() => savedCopyFooterInfo(openFiling || activeTab === "overlap" ? [] : [data?.savedAt]),
|
|
562
|
+
[activeTab, data?.savedAt, openFiling]);
|
|
547
563
|
usePaneStatusLinkFooter({
|
|
548
564
|
registrationId: "thirteenf-detail",
|
|
549
565
|
focused,
|
|
@@ -551,6 +567,7 @@ export function FundDetailView({
|
|
|
551
567
|
source: openFiling ? "SEC 13F" : null,
|
|
552
568
|
loading: activeTab !== "overlap" && status === "loading",
|
|
553
569
|
error: activeTab === "overlap" ? null : error,
|
|
570
|
+
info: detailSavedInfo,
|
|
554
571
|
showOpenHint: true,
|
|
555
572
|
hints: detailHints,
|
|
556
573
|
});
|
|
@@ -583,14 +600,16 @@ export function FundDetailView({
|
|
|
583
600
|
]}
|
|
584
601
|
meta={data?.name && data.name !== seed.name ? data.name : undefined}
|
|
585
602
|
/>
|
|
586
|
-
{activeTab === "overlap" && data ?
|
|
603
|
+
{activeTab === "overlap" && data ? data.loading?.positions || data.loading?.comparison ? (
|
|
604
|
+
<PaneStatusBody loading align="center" loadingLabel="Loading 13F filing..." />
|
|
605
|
+
) : <FundOverlapView data={data} focused={focused} width={width} /> : activeTab === "filings" ? (
|
|
587
606
|
<DataTableStackView<FundTimelineRow, FundTimelineColumn>
|
|
588
607
|
focused={focused}
|
|
589
608
|
detailOpen={!!openFiling}
|
|
590
609
|
onBack={closeOpenFiling}
|
|
591
610
|
detailTitle={openFiling ? `${openFiling.periodOfReport} filing` : "13F filing"}
|
|
592
611
|
detailContent={openFiling ? (
|
|
593
|
-
<FilingDetailView focused={focused} filing={openFiling} width={width} sourceWarnings={data?.warnings ?? []} />
|
|
612
|
+
<FilingDetailView focused={focused} filing={openFiling} width={width} sourceWarnings={data?.warnings ?? []} sourceSavedAt={data?.savedAt} />
|
|
594
613
|
) : (
|
|
595
614
|
<Box flexGrow={1} />
|
|
596
615
|
)}
|
|
@@ -659,11 +678,14 @@ function FilingDetailView({
|
|
|
659
678
|
filing,
|
|
660
679
|
width,
|
|
661
680
|
sourceWarnings,
|
|
681
|
+
sourceSavedAt,
|
|
662
682
|
}: {
|
|
663
683
|
focused: boolean;
|
|
664
684
|
filing: FundTimelineRow;
|
|
665
685
|
width: number;
|
|
666
686
|
sourceWarnings: string[];
|
|
687
|
+
/** When the fund's saved copy was saved, when a failed refresh left it on screen. */
|
|
688
|
+
sourceSavedAt?: number;
|
|
667
689
|
}) {
|
|
668
690
|
const { pinTicker } = usePluginTickerActions();
|
|
669
691
|
const [sortPreference, setSortPreference] = usePluginPaneState<FundSortPreference<FilingPositionColumnId>>(
|
|
@@ -711,6 +733,9 @@ function FilingDetailView({
|
|
|
711
733
|
);
|
|
712
734
|
|
|
713
735
|
usePaneNoticeFooter({ registrationId: "thirteenf-filing-notices", notices: [...new Set([...sourceWarnings, ...warnings])], focused });
|
|
736
|
+
const savedInfo = useMemo(() => savedCopyFooterInfo([sourceSavedAt, ...(pagesSettled ? positions.pages.map((page) => page.savedAt) : [])]),
|
|
737
|
+
[pagesSettled, positions.pages, sourceSavedAt]);
|
|
738
|
+
usePaneFooter("thirteenf-filing-saved", () => ({ info: savedInfo }), [savedInfo]);
|
|
714
739
|
|
|
715
740
|
// An untyped amendment may replace or add to the report, so only it is a caveat.
|
|
716
741
|
const kind = amendmentKind(filing);
|
|
@@ -99,6 +99,20 @@ export interface FundDetailData {
|
|
|
99
99
|
warnings?: string[];
|
|
100
100
|
/** Set when the latest report predates the newest quarter past its deadline. */
|
|
101
101
|
filingGap?: ThirteenFFilingGap | null;
|
|
102
|
+
/** A refresh failed and the oldest copy that answered instead was saved at this time (ms). */
|
|
103
|
+
savedAt?: number;
|
|
104
|
+
/** The parts still loading, while the fund fills in (loadFundDetail's `onProgress`); absent once whole. */
|
|
105
|
+
loading?: ThirteenFDetailLoading;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Parts of a fund's detail still loading. */
|
|
109
|
+
export interface ThirteenFDetailLoading {
|
|
110
|
+
/** The latest report's holdings are a large filing's first rows. */
|
|
111
|
+
positions?: boolean;
|
|
112
|
+
/** The prior quarter's holdings, which the latest are compared with. */
|
|
113
|
+
comparison?: boolean;
|
|
114
|
+
/** EDGAR's filing list, for a fund behind on its filings. */
|
|
115
|
+
filingGap?: boolean;
|
|
102
116
|
}
|
|
103
117
|
|
|
104
118
|
/** A manager a 13F-NT names as reporting its holdings. */
|
|
@@ -24,7 +24,8 @@ import {
|
|
|
24
24
|
barPriceDecimals, formatBarPrice, historyAllStart, historyBarsAfterHole, historyChangeBaseNote, historyMissingPeriodsNote, quotedPriceDivisor,
|
|
25
25
|
} from "../../../../cli/history-rows";
|
|
26
26
|
import {
|
|
27
|
-
MISSING_VOLUME_TEXT, missingVolumeCount, missingVolumeNotice, pricePointValues, priceHistoryIntegrityNotice,
|
|
27
|
+
MISSING_VOLUME_TEXT, missingVolumeCount, missingVolumeNotice, pricePointValues, pricePointWickFlags, priceHistoryIntegrityNotice,
|
|
28
|
+
priceHistoryWickNotice,
|
|
28
29
|
} from "../../../../utils/price-history-integrity";
|
|
29
30
|
import {
|
|
30
31
|
useAssetData,
|
|
@@ -130,7 +131,9 @@ function buildHistoricalPriceRows(points: PricePoint[], venue: string | undefine
|
|
|
130
131
|
const sorted = points
|
|
131
132
|
.flatMap((point, sourceIndex) => {
|
|
132
133
|
const date = pricePointDate(point);
|
|
133
|
-
return date ? [{
|
|
134
|
+
return date ? [{
|
|
135
|
+
point: pricePointValues(point), date, sourceIndex, partial: point.partial === true, filled: point.filled === true, contract: point.contract,
|
|
136
|
+
}] : [];
|
|
134
137
|
})
|
|
135
138
|
.sort((left, right) => left.date.getTime() - right.date.getTime());
|
|
136
139
|
const size = historyBarSize(sorted.map((entry) => entry.date.getTime()));
|
|
@@ -141,10 +144,21 @@ function buildHistoricalPriceRows(points: PricePoint[], venue: string | undefine
|
|
|
141
144
|
const times = sorted.map((entry) => entry.date.getTime());
|
|
142
145
|
const interval = size === "intraday" ? null : size;
|
|
143
146
|
const afterGap = historyBarsAfterHole(times, interval);
|
|
144
|
-
|
|
145
|
-
|
|
147
|
+
// A continuous future's bar on another contract than the bar before has no change: two months' prices are no move.
|
|
148
|
+
let held: string | undefined;
|
|
149
|
+
const rolled = sorted.map((entry) => {
|
|
150
|
+
const roll = !!entry.contract && !!held && held !== entry.contract;
|
|
151
|
+
held = entry.contract ?? held;
|
|
152
|
+
return roll;
|
|
153
|
+
});
|
|
154
|
+
const rolls = sorted.flatMap((entry, index) => rolled[index] ? [`${entry.date.toISOString().slice(0, 10)} (${entry.contract})`] : []);
|
|
155
|
+
const gapNotices = [
|
|
156
|
+
historyMissingPeriodsNote(times, interval),
|
|
157
|
+
historyChangeBaseNote(times, interval),
|
|
158
|
+
rolls.length > 0 ? `No CHG at ${rolls.length === 1 ? "the roll" : "each roll"} to a new contract: ${rolls.join(", ")}` : null,
|
|
159
|
+
].filter((notice): notice is string => notice != null);
|
|
146
160
|
const rows = sorted.map((entry, index) => {
|
|
147
|
-
const previous = sorted[index - 1]?.point;
|
|
161
|
+
const previous = rolled[index] ? undefined : sorted[index - 1]?.point;
|
|
148
162
|
const { point, date, sourceIndex, filled } = entry;
|
|
149
163
|
const newest = index === sorted.length - 1;
|
|
150
164
|
const partial = newest && (!!forming || dayToDate);
|
|
@@ -257,6 +271,7 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
|
|
|
257
271
|
[points, venue],
|
|
258
272
|
);
|
|
259
273
|
const integrityNotice = priceHistoryIntegrityNotice(rows.filter((row) => row.point.integrity).length);
|
|
274
|
+
const wickNotice = useMemo(() => priceHistoryWickNotice(points.filter((point) => pricePointWickFlags(point).length > 0).length), [points]);
|
|
260
275
|
const volumeMissing = useMemo(() => missingVolumeCount(rows.map((row) => ({
|
|
261
276
|
...row.point, date: row.time, partial: row.partial, filled: row.filled,
|
|
262
277
|
}))), [rows]);
|
|
@@ -356,7 +371,7 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
|
|
|
356
371
|
|
|
357
372
|
usePaneNoticeFooter({
|
|
358
373
|
registrationId: "historical-prices:integrity",
|
|
359
|
-
notices: [integrityNotice, volumeNotice, ...gapNotices].filter((notice): notice is string => !!notice),
|
|
374
|
+
notices: [integrityNotice, wickNotice, volumeNotice, ...gapNotices].filter((notice): notice is string => !!notice),
|
|
360
375
|
focused,
|
|
361
376
|
title: "Price history data",
|
|
362
377
|
});
|
|
@@ -404,6 +419,7 @@ export function HistoricalPricesPane({ focused, width, height }: PaneProps) {
|
|
|
404
419
|
: loading ? (rows.length ? "Refreshing" : "Loading") : rows.length ? "Available" : "Empty"],
|
|
405
420
|
...(allStart ? [["Source starts", allStart]] : []),
|
|
406
421
|
...(integrityNotice ? [["Warning", integrityNotice]] : []),
|
|
422
|
+
...(wickNotice ? [["Warning", wickNotice]] : []),
|
|
407
423
|
...gapNotices.map((notice) => ["Gap note", notice]),
|
|
408
424
|
...(volumeNotice ? [["Volume note", volumeNotice]] : []),
|
|
409
425
|
...(error ? [["Error", error]] : []),
|