@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
|
@@ -235,7 +235,7 @@ export function ratingTargetDelta(row: AnalystResearchData["ratings"][number]):
|
|
|
235
235
|
return row.currentPriceTarget - prior;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
export type RatingColumnId = "date" | "firm" | "action" | "current" | "target" | "prior";
|
|
238
|
+
export type RatingColumnId = "date" | "firm" | "action" | "current" | "target" | "prior" | "reason";
|
|
239
239
|
export type RatingColumn = DataTableColumn & { id: RatingColumnId } & Partial<RatingTargetColumnSizing>;
|
|
240
240
|
|
|
241
241
|
export interface RatingSortPreference {
|
|
@@ -255,6 +255,7 @@ const DEFAULT_RATING_SORT_DIRECTIONS: Record<RatingColumnId, SortDirection> = {
|
|
|
255
255
|
current: "asc",
|
|
256
256
|
target: "desc",
|
|
257
257
|
prior: "asc",
|
|
258
|
+
reason: "asc",
|
|
258
259
|
};
|
|
259
260
|
|
|
260
261
|
const BASE_RATING_COLUMNS: RatingColumn[] = [
|
|
@@ -266,6 +267,36 @@ const BASE_RATING_COLUMNS: RatingColumn[] = [
|
|
|
266
267
|
{ id: "prior", label: "PRIOR", width: 13, align: "left" },
|
|
267
268
|
];
|
|
268
269
|
|
|
270
|
+
/** The reason coverage gives for an action: last, taking the room the pane has left. */
|
|
271
|
+
const REASON_COLUMN: RatingColumn = { id: "reason", label: "REASON", width: 28, align: "left", flexGrow: 1 };
|
|
272
|
+
|
|
273
|
+
/** The line coverage of the action quotes, or null when none gives one. */
|
|
274
|
+
export function ratingReason(row: AnalystRatingRecord): AnalystRatingRecord["reason"] | null {
|
|
275
|
+
const reason = row.reason;
|
|
276
|
+
return reason?.text?.trim() && reason.url?.trim() ? reason : null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* The status bar room the selected action's article takes: its `source`
|
|
281
|
+
* link, the `o` hint that opens it and their separators. The consensus
|
|
282
|
+
* context gives way to it rather than cut it.
|
|
283
|
+
*/
|
|
284
|
+
export function ratingReasonFooterWidth(reason: AnalystRatingRecord["reason"] | null): number {
|
|
285
|
+
if (!reason) return 0;
|
|
286
|
+
return displayWidth(`source ${ratingReasonSource(reason)}`) + displayWidth(" · ") + displayWidth("[o]pen") + 2;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Who published the reason: the publisher's name, else the article's site. */
|
|
290
|
+
export function ratingReasonSource(reason: NonNullable<AnalystRatingRecord["reason"]>): string {
|
|
291
|
+
const named = reason.source?.trim();
|
|
292
|
+
if (named) return named;
|
|
293
|
+
try {
|
|
294
|
+
return new URL(reason.url).hostname.replace(/^www\./, "");
|
|
295
|
+
} catch {
|
|
296
|
+
return "source";
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
269
300
|
export function buildRatingColumns(
|
|
270
301
|
rows: readonly AnalystRatingRecord[],
|
|
271
302
|
currency: string | undefined,
|
|
@@ -288,9 +319,11 @@ export function buildRatingColumns(
|
|
|
288
319
|
BASE_RATING_COLUMNS.find((column) => column.id === "target")?.width ?? 13,
|
|
289
320
|
);
|
|
290
321
|
|
|
291
|
-
|
|
322
|
+
const columns = BASE_RATING_COLUMNS.map((column) => {
|
|
292
323
|
return column.id === "target" ? { ...column, ...targetSizing, width: targetWidth } : column;
|
|
293
324
|
});
|
|
325
|
+
// A history with no reason anywhere keeps the table it always had.
|
|
326
|
+
return rows.some((row) => ratingReason(row)) ? [...columns, REASON_COLUMN] : columns;
|
|
294
327
|
}
|
|
295
328
|
|
|
296
329
|
function normalizedText(value: string | undefined): string | null {
|
|
@@ -316,6 +349,8 @@ function ratingSortValue(row: AnalystRatingRecord, columnId: RatingColumnId): st
|
|
|
316
349
|
return ratingTargetSortValue(row);
|
|
317
350
|
case "prior":
|
|
318
351
|
return normalizedText(row.prior);
|
|
352
|
+
case "reason":
|
|
353
|
+
return normalizedText(ratingReason(row)?.text);
|
|
319
354
|
}
|
|
320
355
|
}
|
|
321
356
|
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
spanAxisFormatter,
|
|
9
9
|
staticSeries,
|
|
10
10
|
useChartTableSelection,
|
|
11
|
-
|
|
11
|
+
useExternalLinkFooter,
|
|
12
12
|
type ChartTableSelection,
|
|
13
13
|
type DataTableCell,
|
|
14
14
|
type DataTableKeyEvent,
|
|
@@ -39,6 +39,9 @@ import {
|
|
|
39
39
|
buildRatingColumns,
|
|
40
40
|
firstRatingSortDirection,
|
|
41
41
|
formatRatingTarget,
|
|
42
|
+
ratingReason,
|
|
43
|
+
ratingReasonFooterWidth,
|
|
44
|
+
ratingReasonSource,
|
|
42
45
|
ratingSplit,
|
|
43
46
|
ratingTargetDelta,
|
|
44
47
|
sortRatingRows,
|
|
@@ -142,13 +145,16 @@ function AnalystHeader({ data, history, series, currency, width, height, tableRo
|
|
|
142
145
|
/**
|
|
143
146
|
* Streams the bound symbol and keeps the status bar's reference price on it.
|
|
144
147
|
* Rendered beside the table so a tick redraws the footer, not the ratings.
|
|
148
|
+
* The selected action's reason, when coverage gave one, is a link there.
|
|
145
149
|
*/
|
|
146
|
-
function AnalystFooter({ data, loading, error, width, binding }: {
|
|
150
|
+
function AnalystFooter({ data, loading, error, width, binding, focused, reason }: {
|
|
147
151
|
data: AnalystResearchData | null;
|
|
148
152
|
loading: boolean;
|
|
149
153
|
error: string | null;
|
|
150
154
|
width: number;
|
|
151
155
|
binding: AnalystQuoteBinding;
|
|
156
|
+
focused: boolean;
|
|
157
|
+
reason: ReturnType<typeof ratingReason>;
|
|
152
158
|
}) {
|
|
153
159
|
// The upside is a percentage; about one update a second is enough for it.
|
|
154
160
|
const financials = useLiveTickerFinancials(binding.ticker ? binding.symbol : null, binding.ticker, {
|
|
@@ -157,9 +163,17 @@ function AnalystFooter({ data, loading, error, width, binding }: {
|
|
|
157
163
|
weight: 40,
|
|
158
164
|
});
|
|
159
165
|
const reference = analystReferencePrice(data, financials?.quote);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
166
|
+
const info = useMemo(
|
|
167
|
+
() => buildAnalystFooterInfo(data, { width: width - ratingReasonFooterWidth(reason), loading, error, reference }),
|
|
168
|
+
[data, error, loading, reason, reference.freshness, reference.live, reference.price, width],
|
|
169
|
+
);
|
|
170
|
+
useExternalLinkFooter({
|
|
171
|
+
registrationId: "analyst-research",
|
|
172
|
+
focused,
|
|
173
|
+
url: reason?.url,
|
|
174
|
+
source: reason ? ratingReasonSource(reason) : null,
|
|
175
|
+
info,
|
|
176
|
+
});
|
|
163
177
|
return null;
|
|
164
178
|
}
|
|
165
179
|
|
|
@@ -246,6 +260,9 @@ export function AnalystResearchView({ focused, width, height }: { focused: boole
|
|
|
246
260
|
}
|
|
247
261
|
case "prior":
|
|
248
262
|
return { text: row.prior ?? "-", color: colors.textDim };
|
|
263
|
+
case "reason":
|
|
264
|
+
// Blank, not a dash: no coverage gave a reason.
|
|
265
|
+
return { text: ratingReason(row)?.text ?? "", color: colors.textDim };
|
|
249
266
|
}
|
|
250
267
|
}, [ratingCurrency]);
|
|
251
268
|
|
|
@@ -258,6 +275,7 @@ export function AnalystResearchView({ focused, width, height }: { focused: boole
|
|
|
258
275
|
}));
|
|
259
276
|
}, []);
|
|
260
277
|
|
|
278
|
+
const selectedRow = rows.length > 0 ? rows[selectedIdx] : undefined;
|
|
261
279
|
const footer = (
|
|
262
280
|
<AnalystFooter
|
|
263
281
|
data={authWall ? null : data}
|
|
@@ -265,6 +283,8 @@ export function AnalystResearchView({ focused, width, height }: { focused: boole
|
|
|
265
283
|
error={authWall ? null : error}
|
|
266
284
|
width={width}
|
|
267
285
|
binding={binding}
|
|
286
|
+
focused={focused}
|
|
287
|
+
reason={authWall || !selectedRow ? null : ratingReason(selectedRow)}
|
|
268
288
|
/>
|
|
269
289
|
);
|
|
270
290
|
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import type { PluginModule } from "../plugin-module";
|
|
2
2
|
import { parseTickerListInput, formatTickerListInput } from "../../../tickers/list";
|
|
3
3
|
import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
|
|
4
|
-
import { AnalystResearchView } from "./analyst-pane";
|
|
5
4
|
import { CorporateActionsView } from "./corporate-actions-pane";
|
|
6
|
-
import { EquityDiagnosticView } from "./equity-diagnostic-pane";
|
|
7
|
-
import { RelativeValuationPane } from "./relative-valuation-pane";
|
|
8
5
|
import { relativeValuationHeadless } from "./relative-valuation-headless";
|
|
9
6
|
import { AUTO_PEERS_SETTING, relativeValuationTitle } from "./relative-valuation-peers";
|
|
10
7
|
import { buildRelativeValuationSettingsDef, MAX_RELATIVE_VALUATION_TICKERS } from "./relative-valuation-settings";
|
|
@@ -29,14 +26,14 @@ export const researchModule: PluginModule = {
|
|
|
29
26
|
id: "analyst-research",
|
|
30
27
|
name: "Analyst",
|
|
31
28
|
order: 32,
|
|
32
|
-
component: AnalystResearchView,
|
|
29
|
+
get component(): typeof import("./analyst-pane").AnalystResearchView { const { AnalystResearchView } = require("./analyst-pane"); return AnalystResearchView; },
|
|
33
30
|
instruments: ["equity"],
|
|
34
31
|
});
|
|
35
32
|
ctx.registerTickerResearchTab({
|
|
36
33
|
id: "equity-diagnostic",
|
|
37
34
|
name: "Diagnostic",
|
|
38
35
|
order: 33,
|
|
39
|
-
component: EquityDiagnosticView,
|
|
36
|
+
get component(): typeof import("./equity-diagnostic-pane").EquityDiagnosticView { const { EquityDiagnosticView } = require("./equity-diagnostic-pane"); return EquityDiagnosticView; },
|
|
40
37
|
instruments: ["equity"],
|
|
41
38
|
});
|
|
42
39
|
ctx.registerTickerResearchTab({
|
|
@@ -53,7 +50,7 @@ export const researchModule: PluginModule = {
|
|
|
53
50
|
id: "analyst-research",
|
|
54
51
|
name: "Analyst Research",
|
|
55
52
|
icon: "A",
|
|
56
|
-
component: AnalystResearchView,
|
|
53
|
+
get component(): typeof import("./analyst-pane").AnalystResearchView { const { AnalystResearchView } = require("./analyst-pane"); return AnalystResearchView; },
|
|
57
54
|
defaultPosition: "right",
|
|
58
55
|
tickerFollower: true,
|
|
59
56
|
instruments: ["equity"],
|
|
@@ -68,7 +65,7 @@ export const researchModule: PluginModule = {
|
|
|
68
65
|
reportRequirements: ["Free Gloom sign-in"],
|
|
69
66
|
name: "Equity Diagnostic",
|
|
70
67
|
icon: "D",
|
|
71
|
-
component: EquityDiagnosticView,
|
|
68
|
+
get component(): typeof import("./equity-diagnostic-pane").EquityDiagnosticView { const { EquityDiagnosticView } = require("./equity-diagnostic-pane"); return EquityDiagnosticView; },
|
|
72
69
|
defaultPosition: "right",
|
|
73
70
|
tickerFollower: true,
|
|
74
71
|
instruments: ["equity"],
|
|
@@ -91,7 +88,7 @@ export const researchModule: PluginModule = {
|
|
|
91
88
|
id: "relative-valuation",
|
|
92
89
|
name: "Relative Valuation",
|
|
93
90
|
icon: "R",
|
|
94
|
-
component: RelativeValuationPane,
|
|
91
|
+
get component(): typeof import("./relative-valuation-pane").RelativeValuationPane { const { RelativeValuationPane } = require("./relative-valuation-pane"); return RelativeValuationPane; },
|
|
95
92
|
headless: relativeValuationHeadless,
|
|
96
93
|
defaultPosition: "right",
|
|
97
94
|
defaultMode: "floating",
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
RESEARCH_SEARCH_PANE_ID,
|
|
8
8
|
RESEARCH_SEARCH_TEMPLATE_ID,
|
|
9
9
|
} from "./model";
|
|
10
|
-
import { ResearchSearchPane } from "./pane";
|
|
11
10
|
|
|
12
11
|
const description =
|
|
13
12
|
"Full-text search across earnings call transcripts, news wires, and SEC filings, with saved searches and keyword alerts.";
|
|
@@ -40,7 +39,7 @@ export const researchSearchPlugin: GloomPlugin = {
|
|
|
40
39
|
reportFreshness: { status: "not-a-feed", basis: "search results" },
|
|
41
40
|
name: "Research Search",
|
|
42
41
|
icon: "RS",
|
|
43
|
-
component: ResearchSearchPane,
|
|
42
|
+
get component(): typeof import("./pane").ResearchSearchPane { const { ResearchSearchPane } = require("./pane"); return ResearchSearchPane; },
|
|
44
43
|
defaultPosition: "right",
|
|
45
44
|
defaultMode: "floating",
|
|
46
45
|
defaultFloatingSize: { width: 118, height: 32 },
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import type { RevenueBreakdownView } from "../../../api-client/revenue-breakdown";
|
|
2
2
|
import type { HeadlessPaneColumn, HeadlessPaneDefinition } from "../../../types/plugin";
|
|
3
3
|
import { fetchRevenueBreakdown } from "./client";
|
|
4
|
-
import { growthPercent, quarterLabel, reportedSpan,
|
|
4
|
+
import { growthPercent, quarterLabel, reportedSpan, revenueScale, sharePercent } from "./model";
|
|
5
5
|
import { SEC_FILINGS } from "../shared/report-freshness";
|
|
6
6
|
import { resolveHeadlessIssuerListing } from "../shared/headless-market-data";
|
|
7
7
|
|
|
8
|
-
/**
|
|
8
|
+
/** Shares and growth in percent, as the pane prints them (83.7%, +10.8%), amounts on the table's scale; exports keep the whole number. */
|
|
9
9
|
const number = (format: (value: number | null) => string) => (value: unknown) => format(typeof value === "number" ? value : null);
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A table's amounts on its one scale (`revenueScale`), named once in the text
|
|
13
|
+
* report's heading ("Total revenue, USD millions"). Exports keep the section
|
|
14
|
+
* name and the whole number.
|
|
15
|
+
*/
|
|
16
|
+
function amountScale(values: ReadonlyArray<number | null>, currency: string | null) {
|
|
17
|
+
const scale = revenueScale(values, currency);
|
|
18
|
+
return {
|
|
19
|
+
section: (title: string) => ({ title, ...scale.unit ? { textTitle: `${title}, ${scale.unit}` } : {} }),
|
|
20
|
+
column: (key: string, header: string): HeadlessPaneColumn => ({ key, header, align: "right", textFormat: number(scale.format) }),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
11
23
|
|
|
12
24
|
export const revenueBreakdownHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
13
25
|
shape: "bundle",
|
|
@@ -46,16 +58,18 @@ export const revenueBreakdownHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
46
58
|
ctx.apiClient,
|
|
47
59
|
await resolveHeadlessIssuerListing(ctx, symbol),
|
|
48
60
|
));
|
|
61
|
+
const rowScale = amountScale(data.rows.flatMap((row) => [...row.values, row.ttm]), data.currency);
|
|
62
|
+
const totalScale = amountScale(data.total, data.currency);
|
|
49
63
|
return {
|
|
50
64
|
complete: data.access === "full",
|
|
51
65
|
errors: data.lockedRows > 0 ? [`${data.lockedRows} more rows need Gloom Pro`] : [],
|
|
52
66
|
sections: [
|
|
53
67
|
{
|
|
54
|
-
|
|
68
|
+
...rowScale.section(`Quarterly revenue by ${data.view}`),
|
|
55
69
|
columns: [
|
|
56
70
|
{ key: data.view, header: data.view[0]!.toUpperCase() + data.view.slice(1) },
|
|
57
|
-
...data.periods.map((period) =>
|
|
58
|
-
|
|
71
|
+
...data.periods.map((period) => rowScale.column(quarterLabel(period), quarterLabel(period))),
|
|
72
|
+
rowScale.column("ttm", "TTM"),
|
|
59
73
|
{ key: "share", header: "Share", align: "right", textFormat: number(sharePercent) },
|
|
60
74
|
{ key: "yoy", header: "YoY", align: "right", textFormat: number(growthPercent) },
|
|
61
75
|
],
|
|
@@ -68,8 +82,8 @@ export const revenueBreakdownHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
68
82
|
})),
|
|
69
83
|
},
|
|
70
84
|
{
|
|
71
|
-
|
|
72
|
-
columns: [{ key: "quarter", header: "Quarter" }, { key: "end", header: "End" },
|
|
85
|
+
...totalScale.section("Total revenue"),
|
|
86
|
+
columns: [{ key: "quarter", header: "Quarter" }, { key: "end", header: "End" }, totalScale.column("revenue", "Revenue")],
|
|
73
87
|
rows: data.periods.map((period, index) => ({
|
|
74
88
|
quarter: quarterLabel(period),
|
|
75
89
|
end: period.end,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DataTableColumn } from "../../../components";
|
|
2
|
+
import { pickUnit } from "../../../utils/format";
|
|
2
3
|
import type {
|
|
3
4
|
RevenueBreakdownPayload,
|
|
4
5
|
RevenueBreakdownPeriod,
|
|
@@ -83,6 +84,28 @@ export function revenueAmount(value: number | null): string {
|
|
|
83
84
|
return `${value < 0 ? "-" : ""}${scaled.toFixed(digits)}${suffix}`;
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
const SCALE_WORDS: Record<string, string> = { K: "thousands", M: "millions", B: "billions", T: "trillions" };
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* One scale for a report table's amounts, its largest one's, so a 0.0 never sits beside a 5.52M: `unit`
|
|
91
|
+
* names it once ("USD millions") and `format` prints each amount in it, with one count of decimals down the
|
|
92
|
+
* table (179.3 and 0.0, or 54.32 and 7.88). An amount too small for that many decimals keeps two significant
|
|
93
|
+
* digits (0.030), so only a true zero reads 0.0. The pane keeps each amount's own suffix (`revenueAmount`).
|
|
94
|
+
*/
|
|
95
|
+
export function revenueScale(values: ReadonlyArray<number | null>, currency: string | null): { unit: string; format: (value: number | null) => string } {
|
|
96
|
+
const amounts = values.filter((value): value is number => value !== null && Number.isFinite(value));
|
|
97
|
+
const { suffix, divisor } = pickUnit(amounts);
|
|
98
|
+
const digits = Math.max(0, ...amounts.map((value) => Math.abs(value) / divisor)) >= 100 ? 1 : 2;
|
|
99
|
+
const format = (value: number | null) => {
|
|
100
|
+
if (value === null || !Number.isFinite(value)) return revenueAmount(null);
|
|
101
|
+
const scaled = value / divisor;
|
|
102
|
+
if (scaled === 0 || Number(scaled.toFixed(digits)) !== 0) return scaled.toFixed(digits);
|
|
103
|
+
// Two significant digits: as many decimals as the first one's place, and one more.
|
|
104
|
+
return scaled.toFixed(Math.min(20, 1 - Math.floor(Math.log10(Math.abs(scaled)))));
|
|
105
|
+
};
|
|
106
|
+
return { unit: [currency?.trim(), SCALE_WORDS[suffix]].filter(Boolean).join(" "), format };
|
|
107
|
+
}
|
|
108
|
+
|
|
86
109
|
export const sharePercent = (value: number | null) =>
|
|
87
110
|
value === null ? "--" : `${(value * 100).toFixed(1)}%`;
|
|
88
111
|
|
|
@@ -110,7 +110,9 @@ export function ReverseDcfPane({ width, height, focused }: PaneProps) {
|
|
|
110
110
|
}, [model]);
|
|
111
111
|
|
|
112
112
|
const selectedId = String(discountRate);
|
|
113
|
-
|
|
113
|
+
// A figure wraps only into rows the table's least leaves, so the pane never spills.
|
|
114
|
+
const statMaxLines = height - 1 - 4;
|
|
115
|
+
const contentHeight = Math.max(4, height - 1 - statGridRows(stats, width, undefined, { maxLines: statMaxLines }));
|
|
114
116
|
|
|
115
117
|
return <Box width={width} height={height} flexDirection="column" overflow="hidden">
|
|
116
118
|
<QueryBar width={width} filters={[
|
|
@@ -120,7 +122,7 @@ export function ReverseDcfPane({ width, height, focused }: PaneProps) {
|
|
|
120
122
|
{!symbol ? <EmptyState title="Select a ticker." /> : <PaneStatusBody subject="reverse DCF" loading={inputs.loading && !model}
|
|
121
123
|
error={!model ? inputs.error ?? identityError ?? null : model.error} empty={false}>
|
|
122
124
|
{model && !model.error ? <>
|
|
123
|
-
<StatGrid items={stats} width={width} />
|
|
125
|
+
<StatGrid items={stats} width={width} maxLines={statMaxLines} />
|
|
124
126
|
<DataTableView<Row> focused={focused} columns={COLUMNS} items={model.sensitivity} rootWidth={width} rootHeight={contentHeight}
|
|
125
127
|
getItemKey={(row) => String(row.discountRate)} emptyStateTitle="No implied growth." sortColumnId={null} sortDirection="desc"
|
|
126
128
|
selection={{ kind: "id", selectedId, getId: (row) => String(row.discountRate),
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
type CloudRiskReportListPayload,
|
|
4
4
|
type CloudRiskReportPayload,
|
|
5
5
|
} from "../../../api-client";
|
|
6
|
-
import { ApiRequestError, isPermanentClientError } from "../../../api-client/errors";
|
|
6
|
+
import { ApiRequestError, isPermanentClientError, isTransientFailure } from "../../../api-client/errors";
|
|
7
7
|
import { listingAbroad, type IssuerListingParams } from "../../../api-client/paths";
|
|
8
8
|
import { servedFromSavedCopy } from "../../../data/saved-copies";
|
|
9
9
|
import type { HeadlessPaneApiClient, PluginPersistence } from "../../../types/plugin";
|
|
@@ -21,6 +21,8 @@ type RiskApiClient = Pick<HeadlessPaneApiClient, "getRiskReports" | "getRiskRepo
|
|
|
21
21
|
interface RiskFreshness {
|
|
22
22
|
fetchedAt: number;
|
|
23
23
|
stale: boolean;
|
|
24
|
+
/** Its refresh failed and the copy saved at this time (ms) answered instead. */
|
|
25
|
+
savedAt?: number;
|
|
24
26
|
refreshError?: string;
|
|
25
27
|
errorStatus?: number;
|
|
26
28
|
}
|
|
@@ -72,9 +74,10 @@ function loadCached<T extends object>(
|
|
|
72
74
|
// A failed forced refresh must be retried when this pane is reopened, even
|
|
73
75
|
// when the previously cached value has not reached its ordinary TTL yet.
|
|
74
76
|
if (current()) failedRefreshes.set(failedKey, store);
|
|
75
|
-
// A
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
// A saved copy answers only for a failure asking again may get past: a refusal or a reply that
|
|
78
|
+
// fails validation is the error, and a missing or denied resource also drops its copy.
|
|
79
|
+
if (!isTransientFailure(error)) {
|
|
80
|
+
if (isPermanentClientError(error) && current()) store?.deleteResource(kind, key, { sourceKey: CACHE_SOURCE });
|
|
78
81
|
throw error;
|
|
79
82
|
}
|
|
80
83
|
const fallback = store?.getResource<T>(kind, key, { ...options, allowExpired: true });
|
|
@@ -84,6 +87,7 @@ function loadCached<T extends object>(
|
|
|
84
87
|
...fallback.value,
|
|
85
88
|
fetchedAt: fallback.fetchedAt,
|
|
86
89
|
stale: true,
|
|
90
|
+
savedAt: fallback.fetchedAt,
|
|
87
91
|
refreshError: (error instanceof Error ? error.message : String(error)).trim() || "Request failed",
|
|
88
92
|
errorStatus: error instanceof ApiRequestError ? error.status : undefined,
|
|
89
93
|
}, fallback.fetchedAt);
|
|
@@ -21,7 +21,8 @@ const description =
|
|
|
21
21
|
* filing list, which needs no account: `filings` lists them with a link to
|
|
22
22
|
* each, where Item 1A is the risk factors as written, without RISK's grouping
|
|
23
23
|
* or its changes since the year before. A foreign issuer files a 20-F instead
|
|
24
|
-
* (ASML), whose Item 3.D holds them.
|
|
24
|
+
* (ASML), whose Item 3.D holds them. A company that files neither (IAG on
|
|
25
|
+
* LSE) has nothing in RISK either, and the note says so.
|
|
25
26
|
*/
|
|
26
27
|
function riskFactorsLockNote(lock: PaneLock): Promise<string> {
|
|
27
28
|
return proLockNote(lock, {
|
|
@@ -35,6 +36,7 @@ function riskFactorsLockNote(lock: PaneLock): Promise<string> {
|
|
|
35
36
|
text: (ticker, company) => `gloom filings ${ticker} --form 20-F lists ${company}'s annual reports, each with its filing date and a link to the filing, whose Item 3.D holds the risk factors as written.`,
|
|
36
37
|
answers: (symbol, context) => filesForm(symbol, "20-F", context),
|
|
37
38
|
}],
|
|
39
|
+
noneFor: (ticker) => `${ticker}'s company files no 10-K or 20-F with the SEC, so RISK has no risk factors for it.`,
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
42
|
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type PaneFooterSegment,
|
|
9
9
|
type PaneHint,
|
|
10
10
|
} from "../../../components";
|
|
11
|
+
import { savedCopyFooterInfo } from "../../../components/layout/pane/footer";
|
|
11
12
|
import { useAsyncResource } from "../../../react/async-resource";
|
|
12
13
|
import { colors } from "../../../theme/colors";
|
|
13
14
|
import {
|
|
@@ -119,8 +120,9 @@ function RiskFactorsReader({
|
|
|
119
120
|
const reportLoader = useCallback((force: boolean) => guard(loadRiskReport(ticker!, year!, { force, listing })), [guard, listing, ticker, year]);
|
|
120
121
|
const detail = useAsyncResource(ticker && year !== null ? reportLoader : null, { clearOnError: isPermanentClientError });
|
|
121
122
|
const report = detail.data;
|
|
122
|
-
|
|
123
|
-
const
|
|
123
|
+
// A copy saved earlier answering a failed refresh says when it was saved, in the footer every pane uses, not as an error.
|
|
124
|
+
const listError = list.error ?? (list.data?.savedAt == null ? list.data?.refreshError : null) ?? null;
|
|
125
|
+
const reportError = detail.error ?? (report?.savedAt == null ? report?.refreshError : null) ?? null;
|
|
124
126
|
const refresh = useCallback(() => {
|
|
125
127
|
void list.reload();
|
|
126
128
|
if (year !== null) void detail.reload();
|
|
@@ -151,12 +153,7 @@ function RiskFactorsReader({
|
|
|
151
153
|
info.push({ id: "loading", parts: [{ text: "loading", tone: "muted" }] });
|
|
152
154
|
if (listError) info.push({ id: "list-error", parts: [{ text: `Report list: ${listError}`, tone: "warning" }] });
|
|
153
155
|
if (reportError) info.push({ id: "report-error", parts: [{ text: `${year} report: ${reportError}`, tone: "warning" }] });
|
|
154
|
-
|
|
155
|
-
info.push({ id: "list-stale", parts: [{ text: `List cached ${new Date(list.data.fetchedAt).toISOString()}`, tone: "muted" }] });
|
|
156
|
-
}
|
|
157
|
-
if ((reportError || report?.stale) && report) {
|
|
158
|
-
info.push({ id: "report-stale", parts: [{ text: `Report cached ${new Date(report.fetchedAt).toISOString()}`, tone: "muted" }] });
|
|
159
|
-
}
|
|
156
|
+
info.push(...savedCopyFooterInfo([list.data?.savedAt, report?.savedAt]));
|
|
160
157
|
const hints: PaneHint[] = report?.docUrl
|
|
161
158
|
? [{ id: "open", key: "o", label: "pen filing", onPress: openFiling }]
|
|
162
159
|
: [];
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { PluginModule } from "../plugin-module";
|
|
2
|
-
import { RoadmapPane } from "./pane";
|
|
3
2
|
|
|
4
3
|
export const roadmapModule: PluginModule = {
|
|
5
4
|
panes: [
|
|
@@ -7,8 +6,8 @@ export const roadmapModule: PluginModule = {
|
|
|
7
6
|
id: "roadmap",
|
|
8
7
|
name: "Roadmap",
|
|
9
8
|
icon: "R",
|
|
10
|
-
component: RoadmapPane,
|
|
11
|
-
reportFreshness: { status: "not-a-feed", basis: "curated list" },
|
|
9
|
+
get component(): typeof import("./pane").RoadmapPane { const { RoadmapPane } = require("./pane"); return RoadmapPane; },
|
|
10
|
+
reportFreshness: { status: "not-a-feed", basis: "curated list", local: true },
|
|
12
11
|
defaultPosition: "right",
|
|
13
12
|
defaultMode: "floating",
|
|
14
13
|
defaultFloatingSize: { width: 100, height: 30 },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type RefObject } from "react";
|
|
1
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ReactNode, type RefObject } from "react";
|
|
2
2
|
import { apiClient } from "../../../api-client";
|
|
3
3
|
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
4
4
|
import type { RoadmapItem, RoadmapVote } from "../../../api-client/roadmap";
|
|
@@ -7,7 +7,9 @@ import {
|
|
|
7
7
|
DataTableStackView,
|
|
8
8
|
EMPTY_TABLE_CELL,
|
|
9
9
|
isSectionedItemRow,
|
|
10
|
+
PaneFooterScope,
|
|
10
11
|
renderSectionedRowHeader,
|
|
12
|
+
usePaneTabs,
|
|
11
13
|
usePaneStatusFooter,
|
|
12
14
|
useRowNotes,
|
|
13
15
|
type DataTableCell,
|
|
@@ -23,6 +25,7 @@ import { useThemeColors } from "../../../theme/theme-context";
|
|
|
23
25
|
import type { PaneProps } from "../../../types/plugin";
|
|
24
26
|
import { Box, TextAttributes, type ScrollBoxRenderable } from "../../../ui";
|
|
25
27
|
import { formatShortDate } from "../../../utils/datetime-format";
|
|
28
|
+
import { ChangelogPane } from "../changelog";
|
|
26
29
|
import {
|
|
27
30
|
isVotable,
|
|
28
31
|
optimisticVote,
|
|
@@ -97,7 +100,7 @@ function RoadmapDetail({ item, failure, width, scrollRef }: {
|
|
|
97
100
|
);
|
|
98
101
|
}
|
|
99
102
|
|
|
100
|
-
|
|
103
|
+
function RoadmapView({ focused, width, height }: PaneProps) {
|
|
101
104
|
const colors = useThemeColors();
|
|
102
105
|
const client = useContext(RoadmapClientContext) ?? apiClient;
|
|
103
106
|
const access = usePlanAccess();
|
|
@@ -242,3 +245,47 @@ export function RoadmapPane({ focused, width, height }: PaneProps) {
|
|
|
242
245
|
/>
|
|
243
246
|
);
|
|
244
247
|
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* What is coming and what came: the roadmap's features, and the Changelog as
|
|
251
|
+
* CHG shows it. The first tab is not "Roadmap", which the title already says.
|
|
252
|
+
*/
|
|
253
|
+
const VIEWS = [
|
|
254
|
+
{ value: "roadmap", label: "Features" },
|
|
255
|
+
{ value: "changelog", label: "Changelog" },
|
|
256
|
+
] as const;
|
|
257
|
+
type ViewId = (typeof VIEWS)[number]["value"];
|
|
258
|
+
const VIEW_PANES: Record<ViewId, (props: PaneProps) => ReactNode> = {
|
|
259
|
+
roadmap: RoadmapView,
|
|
260
|
+
changelog: ChangelogPane,
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
export function RoadmapPane(props: PaneProps) {
|
|
264
|
+
const [storedView, setView] = usePluginPaneState<string>("view", "roadmap");
|
|
265
|
+
const view: ViewId = storedView === "changelog" ? "changelog" : "roadmap";
|
|
266
|
+
// A view stays mounted once shown, so coming back to it keeps its place.
|
|
267
|
+
const [mounted, setMounted] = useState<ReadonlySet<ViewId>>(() => new Set([view]));
|
|
268
|
+
useEffect(() => {
|
|
269
|
+
setMounted((current) => (current.has(view) ? current : new Set([...current, view])));
|
|
270
|
+
}, [view]);
|
|
271
|
+
const { strip, rows } = usePaneTabs({ tabs: [...VIEWS], activeValue: view, onSelect: setView, focused: props.focused, dense: true });
|
|
272
|
+
const bodyHeight = Math.max(1, props.height - rows);
|
|
273
|
+
|
|
274
|
+
return (
|
|
275
|
+
<Box width={props.width} height={props.height} flexDirection="column" overflow="hidden">
|
|
276
|
+
{strip}
|
|
277
|
+
{VIEWS.map(({ value }) => {
|
|
278
|
+
if (!mounted.has(value) && value !== view) return null;
|
|
279
|
+
const View = VIEW_PANES[value];
|
|
280
|
+
const active = value === view;
|
|
281
|
+
return (
|
|
282
|
+
<Box key={value} visible={active} height={bodyHeight} flexGrow={1} flexBasis={0} overflow="hidden" data-gloom-role="nested-pane-content">
|
|
283
|
+
<PaneFooterScope active={active}>
|
|
284
|
+
<View {...props} height={bodyHeight} focused={props.focused && active} />
|
|
285
|
+
</PaneFooterScope>
|
|
286
|
+
</Box>
|
|
287
|
+
);
|
|
288
|
+
})}
|
|
289
|
+
</Box>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ScannerHiloExtreme, ScannerHiloPayload } from "../../../api-client";
|
|
1
|
+
import type { ScannerHiloExtreme, ScannerHiloPayload, ScannerStatus } from "../../../api-client";
|
|
2
|
+
import { venueMarketLine } from "../../../cli/pane-functions/freshness";
|
|
2
3
|
|
|
3
4
|
type HiloWindowKey = keyof ScannerHiloPayload["windows"];
|
|
4
5
|
export type HiloMinPrice = "off" | "1" | "5";
|
|
@@ -94,3 +95,19 @@ export function filterHiloRows(
|
|
|
94
95
|
? [...filtered].sort((left, right) => right.count - left.count || right.at - left.at)
|
|
95
96
|
: [...filtered].sort((left, right) => right.at - left.at);
|
|
96
97
|
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* What an empty table says. The price filter when rows came in and it hid them all, or when the market is open and
|
|
101
|
+
* nothing has printed a new extreme yet. A closed market says so and when it reopens, in the status line's words
|
|
102
|
+
* (`Markets closed until Mon.`), as SCAN says its own close: its session has no new highs or lows to count.
|
|
103
|
+
*/
|
|
104
|
+
export function hiloEmptyState(
|
|
105
|
+
{ filtered, status, now }: { filtered: boolean; status: ScannerStatus | undefined; now: number },
|
|
106
|
+
): { title: string; hint?: string } {
|
|
107
|
+
if (filtered || status !== "closed") return { title: "Nothing above the price filter yet." };
|
|
108
|
+
const line = venueMarketLine("NYSE", now);
|
|
109
|
+
return {
|
|
110
|
+
title: line?.state === "closed" ? `${line.text[0]!.toUpperCase()}${line.text.slice(1)}.` : "Market closed.",
|
|
111
|
+
hint: "New highs and lows resume at the open.",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -18,7 +18,7 @@ import { usePluginTickerActions } from "../../runtime";
|
|
|
18
18
|
import { ScannerDeniedState } from "./denied";
|
|
19
19
|
import { useHiloFeed, useScannerStatusFooter } from "./feed";
|
|
20
20
|
import { HiloBars } from "./hilo-bars";
|
|
21
|
-
import { filterHiloRows, formatHiloPrice, type HiloMinPrice, type HiloSort } from "./hilo-model";
|
|
21
|
+
import { filterHiloRows, formatHiloPrice, hiloEmptyState, type HiloMinPrice, type HiloSort } from "./hilo-model";
|
|
22
22
|
|
|
23
23
|
type Side = "lows" | "highs";
|
|
24
24
|
|
|
@@ -118,6 +118,15 @@ function HiloPane({ focused, width, height }: PaneProps) {
|
|
|
118
118
|
);
|
|
119
119
|
|
|
120
120
|
useScannerStatusFooter("hilo", feed, focused);
|
|
121
|
+
// Read when the feed moves, not each render: the clock only matters once a closed session's text is shown.
|
|
122
|
+
const status = feed.payload?.status;
|
|
123
|
+
const emptyStates = useMemo<Record<Side, { title: string; hint?: string }>>(() => {
|
|
124
|
+
const now = Date.now();
|
|
125
|
+
return {
|
|
126
|
+
lows: hiloEmptyState({ filtered: !!feed.payload?.lows.length, status, now }),
|
|
127
|
+
highs: hiloEmptyState({ filtered: !!feed.payload?.highs.length, status, now }),
|
|
128
|
+
};
|
|
129
|
+
}, [feed.payload?.lows.length, feed.payload?.highs.length, status]);
|
|
121
130
|
|
|
122
131
|
const split = width >= SPLIT_MIN_WIDTH;
|
|
123
132
|
// Zero counts before the first payload would read as a dead market, not a loading one.
|
|
@@ -168,7 +177,8 @@ function HiloPane({ focused, width, height }: PaneProps) {
|
|
|
168
177
|
renderCell={RENDER_ROW[side]}
|
|
169
178
|
selectedTextOverridesCellColor
|
|
170
179
|
emptyContent={feed.payload ? undefined : <PaneStatusBody loading loadingLabel="Waiting for the scanner..." />}
|
|
171
|
-
emptyStateTitle=
|
|
180
|
+
emptyStateTitle={emptyStates[side].title}
|
|
181
|
+
emptyStateHint={emptyStates[side].hint}
|
|
172
182
|
/>
|
|
173
183
|
);
|
|
174
184
|
|
|
@@ -100,9 +100,11 @@ const DISPLAY_ORDER = ["symbol", "name", "price", "change", "conditions", "rvol"
|
|
|
100
100
|
/**
|
|
101
101
|
* Symbol, name, price and change, then one column per condition with the
|
|
102
102
|
* figure it matched on, then context as the width allows: a column is added
|
|
103
|
-
* only when it fits whole, the row's status first
|
|
103
|
+
* only when it fits whole, the row's status first, and only when a row has
|
|
104
|
+
* one (after the close none does, and an empty column would take the room of
|
|
105
|
+
* the figures).
|
|
104
106
|
*/
|
|
105
|
-
function liveScanColumns(width: number, conditions: readonly LiveScanCondition[]): DataTableColumn[] {
|
|
107
|
+
export function liveScanColumns(width: number, conditions: readonly LiveScanCondition[], withStatus: boolean): DataTableColumn[] {
|
|
106
108
|
const own = conditions.map((condition, index): DataTableColumn => {
|
|
107
109
|
const label = conditionColumnLabel(condition);
|
|
108
110
|
return { id: `c:${index}`, label, width: Math.max(8, label.length), align: "right" };
|
|
@@ -121,7 +123,7 @@ function liveScanColumns(width: number, conditions: readonly LiveScanCondition[]
|
|
|
121
123
|
));
|
|
122
124
|
let columns = order([...base, ...own]);
|
|
123
125
|
for (const column of OPTIONAL_COLUMNS) {
|
|
124
|
-
if (repeats.has(column.id)) continue;
|
|
126
|
+
if (repeats.has(column.id) || (column.id === "status" && !withStatus)) continue;
|
|
125
127
|
const next = order([...columns, column]);
|
|
126
128
|
if (getTableWidth(next) > width) break;
|
|
127
129
|
columns = next;
|
|
@@ -349,7 +351,8 @@ export default function LiveScanPane({ focused, width, height }: PaneProps) {
|
|
|
349
351
|
[payload, track],
|
|
350
352
|
);
|
|
351
353
|
const matches = useMemo(() => sortMatches(payload?.matches ?? [], sort, noteOf), [noteOf, payload?.matches, sort]);
|
|
352
|
-
const
|
|
354
|
+
const withStatus = useMemo(() => matches.some((match) => noteOf(match) != null), [matches, noteOf]);
|
|
355
|
+
const columns = useMemo(() => liveScanColumns(width, scan.conditions, withStatus), [scan.conditions, width, withStatus]);
|
|
353
356
|
|
|
354
357
|
const apply = (next: LiveScan) => {
|
|
355
358
|
setStoredScan(next);
|