@gloom-sh/terminal 0.19.1 → 0.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/package.json +2 -1
- package/patches/electrobun@1.18.1.patch +108 -1
- package/src/account/upgrade-dialog.tsx +4 -23
- package/src/api-client/coalesce.ts +118 -0
- package/src/api-client/credit-boards.ts +13 -0
- package/src/api-client/crypto-etf-flows.ts +6 -0
- package/src/api-client/crypto-markets.ts +40 -0
- package/src/api-client/data.ts +201 -14
- package/src/api-client/equity-screener.ts +10 -0
- package/src/api-client/errors.ts +58 -0
- package/src/api-client/gpu.ts +29 -0
- package/src/api-client/index.ts +5 -2
- package/src/api-client/plan-access.ts +10 -1
- package/src/api-client/plan-rules.ts +35 -0
- package/src/api-client/research-activity.ts +1 -32
- package/src/api-client/types.ts +21 -1
- package/src/app/attention-notice.tsx +94 -0
- package/src/app/runtime/startup.ts +3 -1
- package/src/app.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +2 -1
- package/src/capabilities/chart-series.ts +2 -1
- package/src/capabilities/factories.ts +1 -0
- package/src/cli/ascii-output.ts +42 -0
- package/src/cli/base-converter.ts +10 -2
- package/src/cli/command-options.ts +16 -1
- package/src/cli/commands/crypto-hints.ts +23 -4
- package/src/cli/commands/market.ts +101 -27
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +14 -10
- package/src/cli/commands/search.ts +84 -25
- package/src/cli/commands/system.ts +22 -30
- package/src/cli/commands/ticker.ts +49 -9
- package/src/cli/commands/version.ts +33 -0
- package/src/cli/context.ts +6 -9
- package/src/cli/data-dir.ts +7 -0
- package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
- package/src/cli/desktop-pane-shot-browser.ts +320 -0
- package/src/cli/desktop-pane-shot-routes.ts +26 -1
- package/src/cli/desktop-pane-shot.ts +196 -256
- package/src/cli/early-command.ts +42 -0
- package/src/cli/earnings-calendar.ts +9 -0
- package/src/cli/entry.ts +19 -13
- package/src/cli/errors.ts +12 -2
- package/src/cli/glossary.ts +2 -1
- package/src/cli/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +189 -11
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +51 -5
- package/src/cli/listing-arg.ts +63 -10
- package/src/cli/options.ts +1 -1
- package/src/cli/output-file.ts +60 -13
- package/src/cli/pane-functions/catalog.ts +14 -3
- package/src/cli/pane-functions/data.ts +1 -1
- package/src/cli/pane-functions/dom.ts +45 -0
- package/src/cli/pane-functions/freshness.ts +46 -9
- package/src/cli/pane-functions/function-help.ts +17 -9
- package/src/cli/pane-functions/headless.ts +22 -14
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +6 -2
- package/src/cli/pane-functions/resolver.ts +27 -5
- package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
- package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
- package/src/cli/pane-functions/screenshot-http.ts +70 -0
- package/src/cli/pane-functions/screenshot-render.ts +19 -5
- package/src/cli/quote-answer.ts +49 -6
- package/src/cli/registry.ts +41 -13
- package/src/cli/result.ts +12 -6
- package/src/cli/ticker-resolution.ts +2 -1
- package/src/cli/types.ts +2 -0
- package/src/components/chart/composite/format.ts +3 -0
- package/src/components/chart-table/header.tsx +1 -1
- package/src/components/chart-table/layout.ts +28 -11
- package/src/components/command-bar/action-runtime.ts +4 -0
- package/src/components/command-bar/commands/direct/index.ts +18 -33
- package/src/components/command-bar/commands/direct/runtime.ts +5 -0
- package/src/components/command-bar/commands/registry.ts +10 -1
- package/src/components/command-bar/routes/root/command-items.ts +9 -4
- package/src/components/command-bar/routes/root/runtime.ts +51 -6
- package/src/components/command-bar/selection-runtime.ts +12 -29
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/header.tsx +10 -5
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +8 -2
- package/src/components/layout/pane/footer/model.ts +73 -6
- package/src/components/layout/pane/header.tsx +2 -2
- package/src/components/layout/pane/status-footer.ts +2 -1
- package/src/components/market-board/index.tsx +3 -2
- package/src/components/onboarding/desks-step.tsx +12 -4
- package/src/components/onboarding/first-run-workspace.ts +81 -4
- package/src/components/onboarding/onboarding-frame.tsx +4 -2
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +60 -26
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
- package/src/components/onboarding/run-setup-again.ts +24 -0
- package/src/components/onboarding/use-onboarding-progress.ts +33 -15
- package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
- package/src/components/onboarding/wizard-model.ts +57 -3
- package/src/components/onboarding/wizard-positions.ts +1 -1
- package/src/components/ui/delayed-mark.tsx +56 -0
- package/src/components/ui/display.tsx +16 -5
- package/src/components/ui/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +16 -3
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/core/app-services.ts +11 -2
- package/src/data/app-persistence.ts +9 -7
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/plugin-cache.ts +15 -0
- package/src/data/plugin-state-store.ts +81 -24
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +165 -90
- package/src/data/session-store.ts +31 -8
- package/src/data/sqlite/access.ts +324 -0
- package/src/data/sqlite/checkpointer.ts +285 -0
- package/src/data/sqlite/database.ts +80 -4
- package/src/data/sqlite/retry.ts +50 -9
- package/src/data/ticker-store.ts +57 -23
- package/src/docs/snapshot.json +191 -78
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -2
- package/src/layout/desk-keys.ts +4 -1
- package/src/layout/desks.ts +38 -5
- package/src/layout-marketplace/gallery-desktop.tsx +45 -16
- package/src/market-data/batched-reads.ts +16 -0
- package/src/market-data/coordinator/auxiliary.ts +4 -2
- package/src/market-data/coordinator/index.ts +11 -2
- package/src/market-data/futures-board.ts +1 -1
- package/src/market-data/futures-contract.ts +3 -1
- package/src/market-data/market/index-level.ts +6 -4
- package/src/market-data/published-apac-sessions.ts +6 -0
- package/src/market-data/realized-volatility.ts +4 -2
- package/src/news/aggregator.ts +7 -4
- package/src/news/news-model.ts +11 -0
- package/src/news/story-id.ts +22 -0
- package/src/news/ticker-symbols.ts +17 -1
- package/src/notifications/app-notifier.ts +35 -14
- package/src/plugins/auto-update-node.ts +10 -6
- package/src/plugins/builtin/account-management/form-components.tsx +1 -1
- package/src/plugins/builtin/account-management/index.tsx +2 -3
- package/src/plugins/builtin/account-management/model.ts +1 -1
- package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
- package/src/plugins/builtin/alerts/events.ts +10 -2
- package/src/plugins/builtin/alerts/index.tsx +5 -5
- package/src/plugins/builtin/alerts/research-builder.ts +5 -1
- package/src/plugins/builtin/alerts/research-rules.ts +18 -0
- package/src/plugins/builtin/alerts/targets.ts +4 -1
- package/src/plugins/builtin/attention/headless.ts +1 -1
- package/src/plugins/builtin/attention/index.tsx +2 -4
- package/src/plugins/builtin/attention/listing.ts +1 -1
- package/src/plugins/builtin/attention/series.ts +1 -1
- package/src/plugins/builtin/awards/index.tsx +1 -2
- package/src/plugins/builtin/backtest/client.ts +1 -0
- package/src/plugins/builtin/backtest/index.tsx +1 -2
- package/src/plugins/builtin/backtest/rules.ts +3 -1
- package/src/plugins/builtin/backtest/window.ts +8 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
- package/src/plugins/builtin/broker-manager/index.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +5 -0
- package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
- package/src/plugins/builtin/buildout/company/id.ts +2 -0
- package/src/plugins/builtin/buildout/company/model.ts +247 -0
- package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
- package/src/plugins/builtin/buildout/facilities.ts +309 -0
- package/src/plugins/builtin/buildout/format.ts +9 -0
- package/src/plugins/builtin/buildout/index.tsx +51 -5
- package/src/plugins/builtin/buildout/model/index.ts +62 -10
- package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
- package/src/plugins/builtin/buildout/model/types.ts +11 -2
- package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
- package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
- package/src/plugins/builtin/buildout/table-model.ts +26 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
- package/src/plugins/builtin/cds/headless.ts +10 -2
- package/src/plugins/builtin/cds/model.ts +7 -3
- package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
- package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
- package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
- package/src/plugins/builtin/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
- package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
- package/src/plugins/builtin/chart-composer/headless.ts +88 -21
- package/src/plugins/builtin/chart-composer/index.tsx +2 -3
- package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
- package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/controller/index.ts +1 -1
- package/src/plugins/builtin/chat/controller/messages.ts +15 -4
- package/src/plugins/builtin/chat/ghost-user.ts +8 -0
- package/src/plugins/builtin/chat/index.tsx +5 -5
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/index.tsx +1 -2
- package/src/plugins/builtin/cloud/plugin.tsx +6 -4
- package/src/plugins/builtin/cloud/team/module.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
- package/src/plugins/builtin/comment-letters/index.tsx +1 -2
- package/src/plugins/builtin/commodity-balances/client.ts +4 -1
- package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
- package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
- package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
- package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
- package/src/plugins/builtin/congress-trades/headless.ts +2 -0
- package/src/plugins/builtin/congress-trades/model.ts +92 -64
- package/src/plugins/builtin/congress-trades/table.tsx +14 -2
- package/src/plugins/builtin/connections/index.ts +2 -3
- package/src/plugins/builtin/cot/client.ts +28 -2
- package/src/plugins/builtin/cot/headless.ts +4 -1
- package/src/plugins/builtin/cot/index.tsx +5 -4
- package/src/plugins/builtin/cot/model.ts +58 -7
- package/src/plugins/builtin/cot/pane.tsx +6 -2
- package/src/plugins/builtin/cpi/client.ts +6 -5
- package/src/plugins/builtin/cpi/index.tsx +1 -2
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/client.ts +7 -2
- package/src/plugins/builtin/credit-boards/headless.ts +25 -12
- package/src/plugins/builtin/credit-boards/model.ts +29 -0
- package/src/plugins/builtin/credit-boards/series.ts +3 -4
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/credit-conditions/client.ts +22 -14
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- package/src/plugins/builtin/crypto-board/client.ts +5 -5
- package/src/plugins/builtin/crypto-board/headless.ts +4 -3
- package/src/plugins/builtin/crypto-board/index.tsx +4 -2
- package/src/plugins/builtin/crypto-board/series.ts +6 -4
- package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
- package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
- package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
- package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
- package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
- package/src/plugins/builtin/debt-maturities/model.ts +17 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
- package/src/plugins/builtin/debt-maturities/series.ts +3 -3
- package/src/plugins/builtin/debug/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/client.ts +32 -2
- package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
- package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
- package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
- package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
- package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
- package/src/plugins/builtin/dividend-yield/view.ts +21 -0
- package/src/plugins/builtin/docs/index.tsx +2 -3
- package/src/plugins/builtin/doe/client.ts +6 -5
- package/src/plugins/builtin/doe/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/doe/prices.ts +2 -3
- package/src/plugins/builtin/doe/series.ts +4 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
- package/src/plugins/builtin/earnings-calls/data.ts +19 -11
- package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
- package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
- package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
- package/src/plugins/builtin/econ/calendar-model.ts +29 -6
- package/src/plugins/builtin/econ/detail-view.tsx +16 -2
- package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +1 -0
- package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
- package/src/plugins/builtin/equity-screener/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/model.ts +28 -4
- package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
- package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
- package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
- package/src/plugins/builtin/executives/data.ts +6 -1
- package/src/plugins/builtin/executives/pane.tsx +9 -21
- package/src/plugins/builtin/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/cache.ts +4 -0
- package/src/plugins/builtin/fear-greed/cli.ts +44 -0
- package/src/plugins/builtin/fear-greed/index.tsx +5 -53
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
- package/src/plugins/builtin/fear-greed/series.ts +6 -4
- package/src/plugins/builtin/fixed-income/index.tsx +1 -2
- package/src/plugins/builtin/futures/board-reads.ts +25 -0
- package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
- package/src/plugins/builtin/futures/index.tsx +15 -0
- package/src/plugins/builtin/futures/model.ts +14 -0
- package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
- package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
- package/src/plugins/builtin/futures-curve/headless.ts +6 -1
- package/src/plugins/builtin/futures-curve/index.tsx +1 -2
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +51 -12
- package/src/plugins/builtin/gpu/client.ts +19 -3
- package/src/plugins/builtin/gpu/equities.tsx +5 -2
- package/src/plugins/builtin/gpu/headless.ts +14 -2
- package/src/plugins/builtin/gpu/index.tsx +1 -2
- package/src/plugins/builtin/gpu/model.ts +20 -2
- package/src/plugins/builtin/gpu/pane.tsx +1 -1
- package/src/plugins/builtin/gpu/series.ts +2 -3
- package/src/plugins/builtin/help/index.tsx +1 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
- package/src/plugins/builtin/holders/index-holders.ts +24 -12
- package/src/plugins/builtin/holders/index.tsx +2 -3
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
- package/src/plugins/builtin/iv-history/index.tsx +1 -2
- package/src/plugins/builtin/jodi/client.ts +4 -1
- package/src/plugins/builtin/jodi/index.tsx +1 -2
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
- package/src/plugins/builtin/layout-manager/index.tsx +9 -8
- package/src/plugins/builtin/macro-day/pane.tsx +4 -2
- package/src/plugins/builtin/market-halts/index.tsx +1 -2
- package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
- package/src/plugins/builtin/market-movers/client.ts +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +16 -5
- package/src/plugins/builtin/market-movers/screener.ts +23 -6
- package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
- package/src/plugins/builtin/market-valuation/index.tsx +1 -2
- package/src/plugins/builtin/members/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/pane.tsx +2 -1
- package/src/plugins/builtin/money-markets/series.ts +3 -5
- package/src/plugins/builtin/news/headless.ts +7 -0
- package/src/plugins/builtin/news/wire/index.tsx +1 -1
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
- package/src/plugins/builtin/notes/index.tsx +3 -6
- package/src/plugins/builtin/openfda/index.tsx +1 -2
- package/src/plugins/builtin/options/view.tsx +6 -5
- package/src/plugins/builtin/options-calculator/index.tsx +1 -2
- package/src/plugins/builtin/options-positioning/index.tsx +1 -2
- package/src/plugins/builtin/options-scenario/index.tsx +2 -4
- package/src/plugins/builtin/perps/index.tsx +1 -2
- package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
- package/src/plugins/builtin/plugin-module.ts +16 -2
- package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
- package/src/plugins/builtin/power/gas-client.ts +2 -1
- package/src/plugins/builtin/power/index.tsx +1 -2
- package/src/plugins/builtin/power/prices-client.ts +3 -2
- package/src/plugins/builtin/rate-path/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/model.ts +16 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
- package/src/plugins/builtin/research/analyst-headless.ts +33 -6
- package/src/plugins/builtin/research/analyst-model.ts +37 -2
- package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
- package/src/plugins/builtin/research/index.tsx +5 -8
- package/src/plugins/builtin/research-search/index.tsx +1 -2
- package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
- package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
- package/src/plugins/builtin/risk-factors/data.ts +8 -4
- package/src/plugins/builtin/risk-factors/index.tsx +3 -1
- package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
- package/src/plugins/builtin/roadmap/index.tsx +2 -3
- package/src/plugins/builtin/roadmap/pane.tsx +49 -2
- package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
- package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
- package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
- package/src/plugins/builtin/scanner/lock-note.ts +26 -3
- package/src/plugins/builtin/sec/filing-content.ts +38 -6
- package/src/plugins/builtin/sec/headless.ts +5 -1
- package/src/plugins/builtin/sec/index.tsx +4 -2
- package/src/plugins/builtin/sec/model.ts +2 -0
- package/src/plugins/builtin/sectors/index.tsx +11 -5
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
- package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
- package/src/plugins/builtin/shared/forming-bars.ts +29 -0
- package/src/plugins/builtin/shared/fred-points.ts +6 -1
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- package/src/plugins/builtin/shared/series-warning.ts +13 -4
- package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
- package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/series.ts +2 -3
- package/src/plugins/builtin/social-mentions/client.ts +15 -10
- package/src/plugins/builtin/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- package/src/plugins/builtin/social-mentions/series.ts +2 -3
- package/src/plugins/builtin/supply-chain/client.ts +22 -1
- package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
- package/src/plugins/builtin/supply-chain/index.tsx +1 -2
- package/src/plugins/builtin/thirteenf/api.ts +127 -29
- package/src/plugins/builtin/thirteenf/data.ts +165 -35
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
- package/src/plugins/builtin/thirteenf/types.ts +14 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
- package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
- package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
- package/src/plugins/builtin/time-sales/index.tsx +1 -2
- package/src/plugins/builtin/time-sales/pane.tsx +3 -1
- package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
- package/src/plugins/builtin/trace-bonds/model.ts +44 -10
- package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
- package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
- package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
- package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
- package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
- package/src/plugins/builtin/vol-surface/client.ts +18 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -2
- package/src/plugins/builtin/volatility/client.ts +43 -16
- package/src/plugins/builtin/volatility/headless.ts +5 -2
- package/src/plugins/builtin/volatility/index.tsx +1 -2
- package/src/plugins/builtin/volatility/model.ts +17 -6
- package/src/plugins/builtin/volatility/pane.tsx +6 -2
- package/src/plugins/builtin/volatility/series.ts +2 -1
- package/src/plugins/builtin/world-indices/footer.ts +6 -1
- package/src/plugins/builtin/world-venue-map/client.ts +10 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -2
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
- package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
- package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
- package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
- package/src/plugins/builtin/yield-curve/client.ts +4 -6
- package/src/plugins/builtin/yield-curve/curves.ts +16 -2
- package/src/plugins/builtin/yield-curve/headless.ts +3 -3
- package/src/plugins/bundle.ts +52 -2
- package/src/plugins/catalog-browser.ts +2 -2
- package/src/plugins/catalog-cli.ts +20 -0
- package/src/plugins/host-link.ts +13 -1
- package/src/plugins/installer.ts +64 -42
- package/src/plugins/loader.ts +43 -11
- package/src/plugins/peer-imports.ts +201 -0
- package/src/plugins/peer-retry.ts +26 -29
- package/src/plugins/plugin-process.ts +128 -0
- package/src/plugins/redirection-guard.ts +45 -0
- package/src/remote/endpoint.ts +16 -3
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/http-proxy.ts +7 -123
- package/src/renderers/dom/desktop/icons.tsx +5 -2
- package/src/renderers/dom/host/image-surface.tsx +46 -21
- package/src/renderers/dom/styles.css +7 -0
- package/src/renderers/dom/toast-host.tsx +2 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
- package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
- package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
- package/src/renderers/electrobun/bun/window/attention.ts +54 -0
- package/src/renderers/electrobun/bun/window/create.ts +6 -2
- package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
- package/src/renderers/electrobun/view/main.tsx +7 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/renderers/electrobun/view/ui-host.tsx +9 -2
- package/src/renderers/opentui/browser-open.ts +10 -4
- package/src/renderers/opentui/image/surface.tsx +1 -1
- package/src/renderers/opentui/start.tsx +12 -6
- package/src/renderers/opentui/toast-host.tsx +2 -1
- package/src/series/registry.ts +18 -0
- package/src/series/resolve.ts +2 -1
- package/src/series/standalone.ts +10 -1
- package/src/series/types.ts +12 -0
- package/src/series/validate.ts +11 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
- package/src/sources/gloomberb-cloud/index.ts +28 -5
- package/src/sources/gloomberb-cloud/news.ts +24 -3
- package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
- package/src/sources/provider-errors.ts +26 -3
- package/src/sources/provider-router/history.ts +5 -0
- package/src/sources/provider-router/index.ts +13 -0
- package/src/sync/core-contributors.ts +32 -0
- package/src/telemetry/attention-counts.ts +64 -6
- package/src/telemetry/attention-setting.ts +33 -0
- package/src/telemetry/crash-reports-node.ts +3 -1
- package/src/telemetry/stall-clocks.ts +93 -0
- package/src/telemetry/stall-reports-node.ts +25 -9
- package/src/telemetry/stall-reports.ts +218 -13
- package/src/test-support/outage.ts +8 -0
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name-listing.ts +59 -29
- package/src/tickers/company-name.ts +84 -1
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +104 -8
- package/src/tickers/search/index.ts +5 -2
- package/src/tickers/search/types.ts +6 -1
- package/src/time-series/bar-aggregation.ts +7 -1
- package/src/time-series/base-series.ts +3 -3
- package/src/time-series/chart-bounds.ts +1 -1
- package/src/time-series/chart-data.ts +5 -2
- package/src/time-series/chart-seed.ts +1 -1
- package/src/time-series/coin-session-close.ts +211 -0
- package/src/time-series/live-bars.ts +13 -2
- package/src/time-series/live-quotes.ts +1 -1
- package/src/time-series/market.ts +5 -3
- package/src/time-series/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +75 -8
- package/src/time-series/resolve.ts +32 -4
- package/src/time-series/series-presentation.ts +1 -1
- package/src/time-series/studies/formula.ts +205 -0
- package/src/time-series/studies/indicators.ts +299 -0
- package/src/time-series/studies/leverage.ts +75 -0
- package/src/time-series/studies/pair.ts +138 -0
- package/src/time-series/studies/resolve.ts +333 -0
- package/src/time-series/studies/shared.ts +141 -0
- package/src/time-series/studies/units.ts +243 -0
- package/src/time-series/studies/vwap.ts +274 -0
- package/src/time-series/types.ts +7 -0
- package/src/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +40 -3
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +17 -1
- package/src/ui/toast.tsx +26 -1
- package/src/utils/ascii-text.ts +140 -0
- package/src/utils/cli-output.ts +91 -18
- package/src/utils/concurrency.ts +24 -0
- package/src/utils/exchanges.ts +6 -0
- package/src/utils/external-url.ts +20 -7
- package/src/utils/financial-statements.ts +73 -16
- package/src/utils/fundamentals.ts +17 -5
- package/src/utils/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/lease-liabilities.ts +54 -0
- package/src/utils/main-thread-stall.ts +122 -5
- package/src/utils/perf-marks.ts +58 -12
- package/src/utils/price-history-integrity.ts +47 -1
- package/src/utils/private-files.ts +141 -0
- package/src/utils/public-address.ts +99 -0
- package/src/account/upgrade-personalization.ts +0 -57
- package/src/account/upgrade-tickers.ts +0 -124
- package/src/api-client/upgrade-experiment.ts +0 -91
- package/src/plugins/builtin/cloud/browser.tsx +0 -3
- package/src/time-series/studies.ts +0 -1628
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "fs";
|
|
2
2
|
import { join } from "path";
|
|
3
|
+
import { makePrivateDirSync, writePrivateFileSync } from "../utils/private-files";
|
|
3
4
|
import type { StallReportState, StallReporterHost } from "./stall-reports";
|
|
4
5
|
|
|
5
6
|
/** Next to config.json: one line per stall, newest last. */
|
|
@@ -31,17 +32,32 @@ function parseStallReportState(text: string): StallReportState {
|
|
|
31
32
|
if (typeof at === "number" && Number.isFinite(at)) sent[key] = at;
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
|
-
return { sent, pending: readCounts(record.pending) };
|
|
35
|
+
return { sent, pending: readCounts(record.pending), recent: readTimes(record.recent) };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readTimes(value: unknown): NonNullable<StallReportState["recent"]> {
|
|
39
|
+
const recent: NonNullable<StallReportState["recent"]> = {};
|
|
40
|
+
if (!value || typeof value !== "object") return recent;
|
|
41
|
+
for (const [process, times] of Object.entries(value as Record<string, unknown>)) {
|
|
42
|
+
if (!Array.isArray(times)) continue;
|
|
43
|
+
const kept = times.filter((at): at is number => typeof at === "number" && Number.isFinite(at));
|
|
44
|
+
if (kept.length > 0) recent[process] = kept;
|
|
45
|
+
}
|
|
46
|
+
return recent;
|
|
35
47
|
}
|
|
36
48
|
|
|
37
49
|
function trimLog(path: string): void {
|
|
38
50
|
if (statSync(path).size <= LOG_MAX_BYTES) return;
|
|
39
51
|
const text = readFileSync(path, "utf-8");
|
|
40
52
|
const keep = text.slice(text.length - LOG_MAX_BYTES / 2);
|
|
41
|
-
|
|
53
|
+
writePrivateFileSync(path, keep.slice(keep.indexOf("\n") + 1));
|
|
42
54
|
}
|
|
43
55
|
|
|
44
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* The stall reporter for a Bun process (the terminal app, the desktop app's
|
|
58
|
+
* main process). Its log and state are readable by the user alone: the log
|
|
59
|
+
* says what the user was doing when the app froze.
|
|
60
|
+
*/
|
|
45
61
|
export function createNodeStallReporterHost(options: {
|
|
46
62
|
/** The profile's data folder, or null before the config is loaded. */
|
|
47
63
|
getDataDir(): string | null;
|
|
@@ -56,20 +72,20 @@ export function createNodeStallReporterHost(options: {
|
|
|
56
72
|
appendLog: (line) => {
|
|
57
73
|
const path = pathFor(STALL_LOG_FILE);
|
|
58
74
|
if (!path) return;
|
|
59
|
-
|
|
60
|
-
|
|
75
|
+
makePrivateDirSync(join(path, ".."));
|
|
76
|
+
writePrivateFileSync(path, `${line}\n`, { append: true });
|
|
61
77
|
trimLog(path);
|
|
62
78
|
},
|
|
63
79
|
load: () => {
|
|
64
80
|
const path = pathFor(STATE_FILE);
|
|
65
|
-
if (!path || !existsSync(path)) return { sent: {}, pending: {} };
|
|
81
|
+
if (!path || !existsSync(path)) return { sent: {}, pending: {}, recent: {} };
|
|
66
82
|
return parseStallReportState(readFileSync(path, "utf-8"));
|
|
67
83
|
},
|
|
68
84
|
save: (state) => {
|
|
69
85
|
const path = pathFor(STATE_FILE);
|
|
70
86
|
if (!path) return;
|
|
71
|
-
|
|
72
|
-
|
|
87
|
+
makePrivateDirSync(join(path, ".."));
|
|
88
|
+
writePrivateFileSync(path, JSON.stringify(state));
|
|
73
89
|
},
|
|
74
90
|
};
|
|
75
91
|
}
|
|
@@ -22,8 +22,22 @@ import { reportCrash } from "./crash-reports";
|
|
|
22
22
|
* stall and how many there were. No free text: a section name keeps only
|
|
23
23
|
* its fixed leading part, so a ticker in a queue key never leaves.
|
|
24
24
|
*
|
|
25
|
+
* A report also says when in the process's life the stall came (still
|
|
26
|
+
* starting or up, and an uptime bucket), how long before it the named section
|
|
27
|
+
* ended (or that it ended inside it, which makes it the likely cause), and,
|
|
28
|
+
* for the desktop app's own process, whether the app was in front and had a
|
|
29
|
+
* window on screen: a hidden app's slowed timers read like a freeze. A stall
|
|
30
|
+
* after a SQLite statement names the statement (a fixed name), its tries and
|
|
31
|
+
* whether it waited on another program's lock. Where the platform's clocks
|
|
32
|
+
* can be read, it says how much of the stall the thread spent on the CPU
|
|
33
|
+
* and how busy the process's other threads were, which tells script or a
|
|
34
|
+
* collection (busy) from a wait on the disk, a lock or another thread
|
|
35
|
+
* (idle); the time the computer slept never counts as a stall (see
|
|
36
|
+
* utils/main-thread-stall.ts). All of it is buckets and flags.
|
|
37
|
+
*
|
|
25
38
|
* One report per process, bucket, section and app version a day; stalls of
|
|
26
|
-
* the same kind after that are counted and carried in the next report
|
|
39
|
+
* the same kind after that are counted and carried in the next report, and
|
|
40
|
+
* every report says how many stalls the process had in the last day.
|
|
27
41
|
*/
|
|
28
42
|
|
|
29
43
|
/** Which process stalled. */
|
|
@@ -41,9 +55,38 @@ export interface StallReport {
|
|
|
41
55
|
count: number;
|
|
42
56
|
/** The longest of them, rounded to 100 ms. */
|
|
43
57
|
longestMs: number;
|
|
58
|
+
/** This process's stalls of a second or more in the last 24 hours, this one included. */
|
|
59
|
+
day: number;
|
|
60
|
+
/** Whether the process had finished starting. */
|
|
61
|
+
phase: "startup" | "steady" | "unknown";
|
|
62
|
+
uptime: UptimeBucket | "unknown";
|
|
63
|
+
/** How long before the stall began the section ended; "in" when it ended inside it. */
|
|
64
|
+
gap: GapBucket;
|
|
65
|
+
/** The desktop app's own process: whether the app was in front (null: not known yet). */
|
|
66
|
+
active?: boolean | null;
|
|
67
|
+
/** The desktop app's own process: whether a window was open and not minimized. */
|
|
68
|
+
visible?: boolean | null;
|
|
69
|
+
/** After a SQLite statement: how many tries it took. */
|
|
70
|
+
attempts?: number;
|
|
71
|
+
/** After a SQLite statement: whether it waited on another connection's lock. */
|
|
72
|
+
lockWait?: boolean;
|
|
73
|
+
/** Where the platform says: the share of the stall the thread was on the CPU, in percent, by tens. */
|
|
74
|
+
cpuPercent?: number;
|
|
75
|
+
/** Where the platform says: the CPU the process's other threads used meanwhile, in percent of one core, by tens. */
|
|
76
|
+
otherCpuPercent?: number;
|
|
44
77
|
}
|
|
45
78
|
|
|
46
79
|
type StallBucket = "1-2s" | "2-5s" | "5-15s" | "15s+";
|
|
80
|
+
type UptimeBucket = "<30s" | "30s-2m" | "2-10m" | "10-60m" | "1-6h" | "6-24h" | "1d+";
|
|
81
|
+
type GapBucket = "none" | "in" | "<1s" | "1-10s" | "10-60s" | "1-10m" | "10m+";
|
|
82
|
+
|
|
83
|
+
/** What the desktop app's Bun process can tell about whether anyone was looking. */
|
|
84
|
+
export interface StallAttention {
|
|
85
|
+
/** One of the app's windows had the keyboard; null before the first focus change. */
|
|
86
|
+
active: boolean | null;
|
|
87
|
+
/** One of the app's windows was open and not minimized; null when it could not be read. */
|
|
88
|
+
visible: boolean | null;
|
|
89
|
+
}
|
|
47
90
|
|
|
48
91
|
/** What the host keeps on disk between launches. */
|
|
49
92
|
export interface StallReportState {
|
|
@@ -51,6 +94,8 @@ export interface StallReportState {
|
|
|
51
94
|
sent: Record<string, number>;
|
|
52
95
|
/** Stalls counted since each kind was last reported. */
|
|
53
96
|
pending: Record<string, { count: number; longestMs: number }>;
|
|
97
|
+
/** When each process's stalls of a second or more in the last day happened, oldest first. */
|
|
98
|
+
recent?: Record<string, number[]>;
|
|
54
99
|
}
|
|
55
100
|
|
|
56
101
|
export interface StallReporterHost {
|
|
@@ -74,8 +119,15 @@ const SECTION_PATTERN = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*/;
|
|
|
74
119
|
const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]{1,60})?$/;
|
|
75
120
|
/** Kinds with counts waiting for their next report; the oldest go first past this. */
|
|
76
121
|
const MAX_KINDS_KEPT = 200;
|
|
122
|
+
/** Stall times kept per process for the day count. */
|
|
123
|
+
const MAX_RECENT_KEPT = 500;
|
|
124
|
+
/** What error tracking keeps of the details line; fields that do not fit are left out. */
|
|
125
|
+
const MAX_DETAILS_LENGTH = 120;
|
|
126
|
+
/** Other threads' CPU past this many percent of a core reads as this. */
|
|
127
|
+
const MAX_OTHER_CPU_PERCENT = 990;
|
|
77
128
|
|
|
78
129
|
let host: StallReporterHost | null = null;
|
|
130
|
+
let attentionProbe: (() => StallAttention) | null = null;
|
|
79
131
|
|
|
80
132
|
/** The fixed part of a section name: lower-case dotted words, cut where anything else starts. */
|
|
81
133
|
function stallSectionName(name: string | undefined): string {
|
|
@@ -85,6 +137,15 @@ function stallSectionName(name: string | undefined): string {
|
|
|
85
137
|
return section && section.length <= 80 ? section : "unmeasured";
|
|
86
138
|
}
|
|
87
139
|
|
|
140
|
+
/** The section a report names; a SQLite statement's adds the statement's fixed name. */
|
|
141
|
+
function reportSection(event: StallEvent): string {
|
|
142
|
+
const section = stallSectionName(event.lastMeasured);
|
|
143
|
+
if (section !== "sqlite.blocked" || !event.sqlite) return section;
|
|
144
|
+
const operation = event.sqlite.operation.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
145
|
+
const named = `${section}.${operation}`.replace(/-+$/, "");
|
|
146
|
+
return SECTION_PATTERN.exec(named)?.[0] === named ? named : section;
|
|
147
|
+
}
|
|
148
|
+
|
|
88
149
|
function stallBucket(blockedMs: number): StallBucket {
|
|
89
150
|
if (blockedMs < 2_000) return "1-2s";
|
|
90
151
|
if (blockedMs < 5_000) return "2-5s";
|
|
@@ -92,15 +153,50 @@ function stallBucket(blockedMs: number): StallBucket {
|
|
|
92
153
|
return "15s+";
|
|
93
154
|
}
|
|
94
155
|
|
|
156
|
+
function uptimeBucket(uptimeMs: number | undefined): UptimeBucket | "unknown" {
|
|
157
|
+
if (uptimeMs === undefined) return "unknown";
|
|
158
|
+
const minutes = uptimeMs / 60_000;
|
|
159
|
+
if (minutes < 0.5) return "<30s";
|
|
160
|
+
if (minutes < 2) return "30s-2m";
|
|
161
|
+
if (minutes < 10) return "2-10m";
|
|
162
|
+
if (minutes < 60) return "10-60m";
|
|
163
|
+
if (minutes < 360) return "1-6h";
|
|
164
|
+
if (minutes < 1_440) return "6-24h";
|
|
165
|
+
return "1d+";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* How long before the stall began the last measured section ended. A
|
|
170
|
+
* section that ended inside the stall ran during it ("in"): the likely
|
|
171
|
+
* cause. Anything after that is time nothing measured ran in.
|
|
172
|
+
*/
|
|
173
|
+
function gapBucket(event: StallEvent): GapBucket {
|
|
174
|
+
if (!event.lastMeasured || event.lastMeasuredEndedMsAgo === undefined) return "none";
|
|
175
|
+
const gapMs = event.lastMeasuredEndedMsAgo - event.blockedMs;
|
|
176
|
+
if (gapMs <= 0) return "in";
|
|
177
|
+
if (gapMs < 1_000) return "<1s";
|
|
178
|
+
if (gapMs < 10_000) return "1-10s";
|
|
179
|
+
if (gapMs < 60_000) return "10-60s";
|
|
180
|
+
if (gapMs < 600_000) return "1-10m";
|
|
181
|
+
return "10m+";
|
|
182
|
+
}
|
|
183
|
+
|
|
95
184
|
/** A stall another process sent over, kept only when it has the right shape. */
|
|
96
185
|
export function parseStallEvent(value: unknown): StallEvent | null {
|
|
97
186
|
if (!value || typeof value !== "object") return null;
|
|
98
|
-
const { blockedMs, lastMeasured, lastMeasuredEndedMsAgo } = value as Record<string, unknown>;
|
|
187
|
+
const { blockedMs, lastMeasured, lastMeasuredEndedMsAgo, sqlite, uptimeMs, phase } = value as Record<string, unknown>;
|
|
99
188
|
if (typeof blockedMs !== "number" || !Number.isFinite(blockedMs) || blockedMs <= 0) return null;
|
|
189
|
+
const detail = sqlite && typeof sqlite === "object" ? sqlite as Record<string, unknown> : null;
|
|
100
190
|
return {
|
|
101
191
|
blockedMs,
|
|
102
192
|
...(typeof lastMeasured === "string" && lastMeasured.length <= 200 ? { lastMeasured } : {}),
|
|
103
193
|
...(typeof lastMeasuredEndedMsAgo === "number" && Number.isFinite(lastMeasuredEndedMsAgo) ? { lastMeasuredEndedMsAgo } : {}),
|
|
194
|
+
...(detail && typeof detail.operation === "string" && detail.operation.length <= 80
|
|
195
|
+
&& typeof detail.attempts === "number" && Number.isFinite(detail.attempts)
|
|
196
|
+
? { sqlite: { operation: detail.operation, attempts: detail.attempts, lockWait: detail.lockWait === true } }
|
|
197
|
+
: {}),
|
|
198
|
+
...(typeof uptimeMs === "number" && Number.isFinite(uptimeMs) && uptimeMs >= 0 ? { uptimeMs } : {}),
|
|
199
|
+
...(phase === "startup" || phase === "steady" ? { phase } : {}),
|
|
104
200
|
};
|
|
105
201
|
}
|
|
106
202
|
|
|
@@ -108,13 +204,57 @@ function formatSeconds(ms: number): string {
|
|
|
108
204
|
return `${(ms / 1000).toFixed(ms < 10_000 ? 1 : 0)} s`;
|
|
109
205
|
}
|
|
110
206
|
|
|
111
|
-
|
|
112
|
-
|
|
207
|
+
function flag(value: boolean | null | undefined): string {
|
|
208
|
+
return value == null ? "?" : value ? "1" : "0";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** A share or a core count in percent, by tens. */
|
|
212
|
+
function tens(value: number | undefined, max: number): number | undefined {
|
|
213
|
+
if (value === undefined || !Number.isFinite(value)) return undefined;
|
|
214
|
+
return Math.min(max, Math.max(0, Math.round(value * 10) * 10));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** The context a report carries, also written at the end of the log line. */
|
|
218
|
+
interface StallContext {
|
|
219
|
+
phase: StallReport["phase"];
|
|
220
|
+
uptime: StallReport["uptime"];
|
|
221
|
+
gap: GapBucket;
|
|
222
|
+
attention: StallAttention | null;
|
|
223
|
+
cpuPercent?: number;
|
|
224
|
+
otherCpuPercent?: number;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function stallContext(event: StallEvent, attention: StallAttention | null): StallContext {
|
|
228
|
+
const cpuPercent = tens(event.threadCpu, 100);
|
|
229
|
+
const otherCpuPercent = tens(event.otherCpu, MAX_OTHER_CPU_PERCENT);
|
|
230
|
+
return {
|
|
231
|
+
phase: event.phase ?? "unknown",
|
|
232
|
+
uptime: uptimeBucket(event.uptimeMs),
|
|
233
|
+
gap: gapBucket(event),
|
|
234
|
+
attention,
|
|
235
|
+
...(cpuPercent === undefined ? {} : { cpuPercent }),
|
|
236
|
+
...(otherCpuPercent === undefined ? {} : { otherCpuPercent }),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** One line of the stall log: when, which process, how long, what ran last, and when in the process's life. */
|
|
241
|
+
function formatStallLogLine(process: StallProcess, event: StallEvent, context: StallContext, at: number): string {
|
|
113
242
|
const time = new Date(at).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
114
243
|
const last = event.lastMeasured
|
|
115
244
|
? `last measured: ${event.lastMeasured}${event.lastMeasuredEndedMsAgo === undefined ? "" : `, ended ${Math.round(event.lastMeasuredEndedMsAgo)} ms before`}`
|
|
116
245
|
: "nothing measured before it";
|
|
117
|
-
|
|
246
|
+
const sqlite = event.sqlite
|
|
247
|
+
? ` (${event.sqlite.operation}, ${event.sqlite.attempts} ${event.sqlite.attempts === 1 ? "try" : "tries"}${event.sqlite.lockWait ? ", waited on a lock" : ""})`
|
|
248
|
+
: "";
|
|
249
|
+
const facts = [
|
|
250
|
+
`phase=${context.phase}`,
|
|
251
|
+
`up=${context.uptime}`,
|
|
252
|
+
`gap=${context.gap}`,
|
|
253
|
+
...(context.attention ? [`fg=${flag(context.attention.active)}`, `vis=${flag(context.attention.visible)}`] : []),
|
|
254
|
+
...(context.cpuPercent === undefined ? [] : [`cpu=${context.cpuPercent}`]),
|
|
255
|
+
...(context.otherCpuPercent === undefined ? [] : [`oth=${context.otherCpuPercent}`]),
|
|
256
|
+
].join(" ");
|
|
257
|
+
return `${time} ${process.padEnd(8)} froze ${formatSeconds(event.blockedMs).padStart(6)} ${last}${sqlite} [${facts}]`;
|
|
118
258
|
}
|
|
119
259
|
|
|
120
260
|
function reportPlatform(platform: string): StallReport["platform"] {
|
|
@@ -128,13 +268,38 @@ function reportArch(arch: string): StallReport["arch"] {
|
|
|
128
268
|
return arch === "arm64" || arch === "x64" ? arch : "other";
|
|
129
269
|
}
|
|
130
270
|
|
|
131
|
-
|
|
132
|
-
|
|
271
|
+
/**
|
|
272
|
+
* The details line error tracking keeps, most telling first. The platform
|
|
273
|
+
* comes last: error tracking has the system and its architecture already.
|
|
274
|
+
* One that would not fit is left out.
|
|
275
|
+
*/
|
|
276
|
+
export function formatStallReportDetails(report: StallReport): string {
|
|
277
|
+
const fields = [
|
|
133
278
|
`process=${report.process}`,
|
|
134
279
|
`count=${report.count}`,
|
|
135
280
|
`longest=${report.longestMs}ms`,
|
|
281
|
+
`phase=${report.phase}`,
|
|
282
|
+
`up=${report.uptime}`,
|
|
283
|
+
`gap=${report.gap}`,
|
|
284
|
+
...(report.active === undefined ? [] : [`fg=${flag(report.active)}`]),
|
|
285
|
+
...(report.visible === undefined ? [] : [`vis=${flag(report.visible)}`]),
|
|
286
|
+
...(report.cpuPercent === undefined ? [] : [`cpu=${report.cpuPercent}`]),
|
|
287
|
+
...(report.otherCpuPercent === undefined ? [] : [`oth=${report.otherCpuPercent}`]),
|
|
288
|
+
...(report.lockWait === undefined ? [] : [`lock=${flag(report.lockWait)}`]),
|
|
289
|
+
...(report.attempts === undefined ? [] : [`try=${report.attempts}`]),
|
|
290
|
+
`day=${report.day}`,
|
|
136
291
|
`platform=${report.platform}-${report.arch}`,
|
|
137
|
-
]
|
|
292
|
+
];
|
|
293
|
+
let details = "";
|
|
294
|
+
for (const field of fields) {
|
|
295
|
+
const next = details ? `${details} ${field}` : field;
|
|
296
|
+
if (next.length <= MAX_DETAILS_LENGTH) details = next;
|
|
297
|
+
}
|
|
298
|
+
return details;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function sendThroughCrashReporter(report: StallReport): void {
|
|
302
|
+
const details = formatStallReportDetails(report);
|
|
138
303
|
// A plain object, not an Error: it has no stack, so no file path goes along.
|
|
139
304
|
// The message is the bucket and section, so error tracking keeps one issue per kind.
|
|
140
305
|
reportCrash(
|
|
@@ -146,10 +311,10 @@ function sendThroughCrashReporter(report: StallReport): void {
|
|
|
146
311
|
function loadState(current: StallReporterHost): StallReportState {
|
|
147
312
|
try {
|
|
148
313
|
const state = current.load();
|
|
149
|
-
return { sent: { ...state.sent }, pending: { ...state.pending } };
|
|
314
|
+
return { sent: { ...state.sent }, pending: { ...state.pending }, recent: { ...state.recent } };
|
|
150
315
|
} catch (error) {
|
|
151
316
|
logFailure("telemetry-stalls", "Could not read stall report state")(error);
|
|
152
|
-
return { sent: {}, pending: {} };
|
|
317
|
+
return { sent: {}, pending: {}, recent: {} };
|
|
153
318
|
}
|
|
154
319
|
}
|
|
155
320
|
|
|
@@ -159,6 +324,22 @@ function prune(state: StallReportState, now: number): void {
|
|
|
159
324
|
}
|
|
160
325
|
const kinds = Object.keys(state.pending);
|
|
161
326
|
for (const key of kinds.slice(0, Math.max(0, kinds.length - MAX_KINDS_KEPT))) delete state.pending[key];
|
|
327
|
+
for (const [process, times] of Object.entries(state.recent ?? {})) {
|
|
328
|
+
const kept = times.filter((at) => now - at < DAY_MS && at <= now);
|
|
329
|
+
if (kept.length > 0) state.recent![process] = kept;
|
|
330
|
+
else delete state.recent![process];
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Never throws: a probe that fails reads as not known. */
|
|
335
|
+
function readAttention(): StallAttention | null {
|
|
336
|
+
if (!attentionProbe) return null;
|
|
337
|
+
try {
|
|
338
|
+
return attentionProbe();
|
|
339
|
+
} catch {
|
|
340
|
+
// Intentional ignore: a window that cannot be read leaves the report saying it is not known.
|
|
341
|
+
return { active: null, visible: null };
|
|
342
|
+
}
|
|
162
343
|
}
|
|
163
344
|
|
|
164
345
|
/** Records one stall of `process`. Does nothing where no host is installed; never throws. */
|
|
@@ -167,15 +348,20 @@ export function recordStall(process: StallProcess, event: StallEvent): void {
|
|
|
167
348
|
if (!current || !(event.blockedMs > 0)) return;
|
|
168
349
|
try {
|
|
169
350
|
const now = current.now?.() ?? Date.now();
|
|
170
|
-
|
|
171
|
-
|
|
351
|
+
const reportable = event.blockedMs >= STALL_REPORT_MIN_MS;
|
|
352
|
+
// Asked only of a stall that will be reported: the probe reads the windows.
|
|
353
|
+
const context = stallContext(event, reportable && process === "app" ? readAttention() : null);
|
|
354
|
+
current.appendLog(formatStallLogLine(process, event, context, now));
|
|
355
|
+
if (!reportable) return;
|
|
172
356
|
const appVersion = (current.appVersion ?? VERSION).trim().replace(/^v/, "");
|
|
173
|
-
const section =
|
|
357
|
+
const section = reportSection(event);
|
|
174
358
|
const bucket = stallBucket(event.blockedMs);
|
|
175
359
|
const version = VERSION_PATTERN.test(appVersion) ? appVersion : "unknown";
|
|
176
360
|
const key = `${process}|${bucket}|${section}|${version}`;
|
|
177
361
|
const state = loadState(current);
|
|
178
362
|
prune(state, now);
|
|
363
|
+
const recent = [...(state.recent?.[process] ?? []), now].slice(-MAX_RECENT_KEPT);
|
|
364
|
+
state.recent = { ...state.recent, [process]: recent };
|
|
179
365
|
const pending = state.pending[key] ?? { count: 0, longestMs: 0 };
|
|
180
366
|
pending.count += 1;
|
|
181
367
|
pending.longestMs = Math.max(pending.longestMs, Math.round(event.blockedMs / 100) * 100);
|
|
@@ -193,6 +379,16 @@ export function recordStall(process: StallProcess, event: StallEvent): void {
|
|
|
193
379
|
arch: reportArch(current.arch),
|
|
194
380
|
count: pending.count,
|
|
195
381
|
longestMs: pending.longestMs,
|
|
382
|
+
day: recent.length,
|
|
383
|
+
phase: context.phase,
|
|
384
|
+
uptime: context.uptime,
|
|
385
|
+
gap: context.gap,
|
|
386
|
+
...(context.attention ? { active: context.attention.active, visible: context.attention.visible } : {}),
|
|
387
|
+
...(context.cpuPercent === undefined ? {} : { cpuPercent: context.cpuPercent }),
|
|
388
|
+
...(context.otherCpuPercent === undefined ? {} : { otherCpuPercent: context.otherCpuPercent }),
|
|
389
|
+
...(event.sqlite && section.startsWith("sqlite.blocked")
|
|
390
|
+
? { attempts: Math.max(1, Math.min(99, Math.round(event.sqlite.attempts))), lockWait: event.sqlite.lockWait }
|
|
391
|
+
: {}),
|
|
196
392
|
});
|
|
197
393
|
}
|
|
198
394
|
current.save(state);
|
|
@@ -208,7 +404,16 @@ export function installStallReporter(next: StallReporterHost): () => void {
|
|
|
208
404
|
};
|
|
209
405
|
}
|
|
210
406
|
|
|
407
|
+
/** How the desktop app's Bun process tells whether anyone was looking when it froze (bun/window/attention.ts). */
|
|
408
|
+
export function setStallAttentionProbe(next: (() => StallAttention) | null): () => void {
|
|
409
|
+
attentionProbe = next;
|
|
410
|
+
return () => {
|
|
411
|
+
if (attentionProbe === next) attentionProbe = null;
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
211
415
|
/** Test seam. */
|
|
212
416
|
export function resetStallReporterForTests(): void {
|
|
213
417
|
host = null;
|
|
418
|
+
attentionProbe = null;
|
|
214
419
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A request that never reached its server, with `message`: the failure a
|
|
3
|
+
* saved copy answers for (`isTransientFailure`), unlike a plain `Error`, which
|
|
4
|
+
* reads as a reply that failed validation.
|
|
5
|
+
*/
|
|
6
|
+
export function outage(message = "Unable to connect. Is the computer able to access the url?"): Error {
|
|
7
|
+
return Object.assign(new Error(message), { code: "ConnectionRefused" });
|
|
8
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { beforeEach } from "bun:test";
|
|
2
2
|
import { resetCrashReporterForTests } from "../telemetry/crash-reports";
|
|
3
|
+
import { setCliAsciiLocaleOverride, setCliColorEnabledOverride, setCliPlainOverride, setCliWidthOverride } from "../utils/cli-output";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* `bun test` runs every file in one process, so module state outlives the file
|
|
@@ -8,7 +9,15 @@ import { resetCrashReporterForTests } from "../telemetry/crash-reports";
|
|
|
8
9
|
* queue and go out with the next reporter another file installs, and its key
|
|
9
10
|
* would keep a later identical report from being queued at all. Every test
|
|
10
11
|
* starts with no reporter, an empty queue and nothing seen.
|
|
12
|
+
*
|
|
13
|
+
* Commands also keep output overrides for the process: a parity test that
|
|
14
|
+
* runs --plain would otherwise make a later serializer test keep empty
|
|
15
|
+
* columns. Start each test with the default output modes and terminal width.
|
|
11
16
|
*/
|
|
12
17
|
beforeEach(() => {
|
|
13
18
|
resetCrashReporterForTests();
|
|
19
|
+
setCliAsciiLocaleOverride(null);
|
|
20
|
+
setCliColorEnabledOverride(null);
|
|
21
|
+
setCliPlainOverride(null);
|
|
22
|
+
setCliWidthOverride(null);
|
|
14
23
|
});
|
|
@@ -3,9 +3,9 @@ import type { DataProvider } from "../types/data-provider";
|
|
|
3
3
|
import type { Quote } from "../types/financials";
|
|
4
4
|
import type { InstrumentSearchResult } from "../types/instrument";
|
|
5
5
|
import { canonicalExchange, isUsListingExchange, parsePublicTickerKey } from "../utils/exchanges";
|
|
6
|
-
import { companyNameMatch,
|
|
6
|
+
import { companyLines, companyNameExtraWords, companyNameMatch, namedByService, runTogether, shortCompanyName } from "./company-name";
|
|
7
7
|
import { LISTING_SEARCH_LIMIT } from "./search";
|
|
8
|
-
import { lineDistance } from "./search/home-listing";
|
|
8
|
+
import { lineDistance, primaryCompanyLine } from "./search/home-listing";
|
|
9
9
|
import { classifyInstrumentKind, isCryptoInstrumentType } from "./search/ranking";
|
|
10
10
|
import { isProviderBusy, type ProviderBusyError } from "../sources/provider-errors";
|
|
11
11
|
import { readTerminalListingCode, type TerminalListing, type TerminalListingCode } from "./terminal-codes";
|
|
@@ -17,6 +17,8 @@ export interface CompanyNameListing {
|
|
|
17
17
|
exchange: string;
|
|
18
18
|
/** The listing's name as search gives it. */
|
|
19
19
|
name: string;
|
|
20
|
+
/** The service marked the listing with the name as typed (`searchAliases`): the company it means by it. */
|
|
21
|
+
named?: true;
|
|
20
22
|
/**
|
|
21
23
|
* When the listing is a fund or an index carrying the name ("JP Morgan" read
|
|
22
24
|
* as ^JTEK, JP MORGAN ETF TRUS), the company's common stock the name says
|
|
@@ -67,7 +69,10 @@ export function isCompanyNameInput(input: string): boolean {
|
|
|
67
69
|
* depositary receipt (SAP's NYSE line for "SAP SE"), so a receipt is the
|
|
68
70
|
* listing only of a company listed by receipts alone (PDD); then the row the
|
|
69
71
|
* service marks with the name, a US line, the shortest symbol (WHR before
|
|
70
|
-
* WHR-A) and search's order. The
|
|
72
|
+
* WHR-A) and search's order. The first then gives way to the line `search`
|
|
73
|
+
* calls the company's primary listing (`primaryCompanyLine`), though its name
|
|
74
|
+
* says more: "lufthansa" is LHA on XETRA, not LUFTHANSA on Budapest, a
|
|
75
|
+
* cross-listing with no search score. The row the service marks with the name as typed
|
|
71
76
|
* (`searchAliases`: JPM for "JP Morgan", SIE for "SIEMENS AG", though its name
|
|
72
77
|
* is Siemens Aktiengesellschaft) is named by it exactly. Contracts, preferred
|
|
73
78
|
* shares and notes are never the listing. With `quoted`, a listing with no
|
|
@@ -95,44 +100,62 @@ export async function findListingOfCompanyName(
|
|
|
95
100
|
const answers = await Promise.all(queries.map((query) => searchListings(dataProvider, query)));
|
|
96
101
|
const busy = answers.find(isProviderBusy);
|
|
97
102
|
const results = answers.flatMap((answer) => Array.isArray(answer) ? answer : []);
|
|
98
|
-
const
|
|
103
|
+
const listed = results.flatMap((result, order) => {
|
|
99
104
|
const exchange = listedExchange(result);
|
|
100
105
|
const type = listedType(result);
|
|
101
106
|
const named = namedByService(name, result);
|
|
102
107
|
const match = named ? { extra: 0, abbreviated: 0 } : companyNameMatch(name, result.name);
|
|
103
108
|
if (!exchange || match == null || classifyInstrumentKind(type) === "derivative" || NOT_COMMON.test(type)
|
|
104
109
|
|| parsePublicTickerKey(result.symbol).exchange) return [];
|
|
105
|
-
|
|
110
|
+
// A word alone names a company by these lines only; the company's other lines are still its lines.
|
|
111
|
+
const namesCompany = !(options.oneWord && (match.abbreviated > 0 || isCryptoInstrumentType(type) || !(order === 0 || named || match.extra === 0)));
|
|
106
112
|
// A receipt is a company's shares too, however search types it ("Depositary Receipt" or "American Depositary Receipt").
|
|
107
113
|
const stock = classifyInstrumentKind(type) === "equity" || RECEIPT.test(type) ? 0 : 1;
|
|
108
114
|
const home = result.homeListing === true;
|
|
109
115
|
const line = home ? 0 : 1 + lineDistance(type, exchange, result.symbol);
|
|
110
116
|
return [{
|
|
111
|
-
result, exchange, stock, home,
|
|
117
|
+
result, exchange, stock, home, namesCompany,
|
|
112
118
|
rank: [match.extra, match.abbreviated, stock, line, named ? 0 : 1, isUsListingExchange(exchange) ? 0 : 1, result.symbol.length, order],
|
|
113
119
|
}];
|
|
114
120
|
}).sort((left, right) => {
|
|
115
121
|
const at = left.rank.findIndex((value, index) => value !== right.rank[index]);
|
|
116
122
|
return at < 0 ? 0 : left.rank[at]! - right.rank[at]!;
|
|
117
123
|
});
|
|
118
|
-
const first =
|
|
124
|
+
const first = listed.find((listing) => listing.namesCompany);
|
|
119
125
|
// Listings are named in Latin letters: a name in another script ("任天堂 ソニー") names none, answered or not.
|
|
120
126
|
if (!first && busy && !hasNonLatinLetters(name)) throw busy;
|
|
121
127
|
if (!first) return null;
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
// Another line the service marks as a home is another company's (Siemens Ltd. on NSE, for "SIEMENS AG"), and
|
|
129
|
+
// so is a line named as that one is (Siemens Ltd. on BSE).
|
|
130
|
+
const otherHomesThan = (lead: typeof first) => {
|
|
131
|
+
const homes = new Set(listed.filter((listing) => listing.home && listing !== lead).map(({ result }) => plainName(result.name)));
|
|
132
|
+
homes.delete(plainName(lead.result.name));
|
|
133
|
+
return homes;
|
|
134
|
+
};
|
|
135
|
+
// The listing `search` calls the company's primary leads, though its name says more than the one typed: the
|
|
136
|
+
// name typed opens what a search for it lists first (LHA on XETRA for "lufthansa", not LUFTHANSA on Budapest).
|
|
137
|
+
const named = namedByService(name, first.result);
|
|
138
|
+
const firstHomes = otherHomesThan(first);
|
|
139
|
+
// Never a line of another business its name is in (`companyLines`): Siemens Energy AG is not Siemens AG's.
|
|
140
|
+
const lead = first.home ? first : primaryCompanyLine(first, companyLines(first, listed, lineFacts).filter((listing) => listing === first
|
|
141
|
+
|| (!listing.home && !firstHomes.has(plainName(listing.result.name)) && listing.stock === first.stock
|
|
142
|
+
&& (!named || companyNameExtraWords(listing.result.name, first.result.name) != null))),
|
|
143
|
+
({ result, exchange }) => ({ distance: lineDistance(listedType(result), exchange, result.symbol), score: result.popularity ?? null }),
|
|
144
|
+
{ named });
|
|
145
|
+
// The lead may give way to its company's other lines: as near the name and of its kind.
|
|
146
|
+
const otherHomes = otherHomesThan(lead);
|
|
147
|
+
const lines = [lead, ...companyLines(lead, listed, lineFacts).filter((listing) => listing !== lead && !listing.home
|
|
148
|
+
&& !otherHomes.has(plainName(listing.result.name))
|
|
149
|
+
&& listing.rank.slice(0, NAME_RANKS).every((value, index) => value === lead.rank[index]))];
|
|
150
|
+
const quoted = options.quoted ? await firstQuoted(dataProvider, lines, options.movedLine) : { line: lead, moved: null };
|
|
131
151
|
const best = quoted.line;
|
|
132
152
|
const found: CompanyNameListing = quoted.moved
|
|
133
153
|
// The line it moved to is a US symbol, read bare as `quote` reads one (GETYQ, which trades over the counter).
|
|
134
154
|
? { symbol: quoted.moved.symbol, exchange: "", name: best.result.name, moved: quoted.moved.note }
|
|
135
|
-
: {
|
|
155
|
+
: {
|
|
156
|
+
symbol: best.result.symbol.toUpperCase(), exchange: best.exchange, name: best.result.name,
|
|
157
|
+
...namedByService(name, best.result) ? { named: true as const } : {},
|
|
158
|
+
};
|
|
136
159
|
const meant = best.stock && options.meant ? await stockNamedRunTogether(dataProvider, name, results) : null;
|
|
137
160
|
return meant ? { ...found, meant } : found;
|
|
138
161
|
}
|
|
@@ -199,6 +222,11 @@ async function firstQuoted<T extends RankedListing>(
|
|
|
199
222
|
}
|
|
200
223
|
|
|
201
224
|
/** A name as two venues may spell it alike: "SIEMENS LTD" for Siemens Ltd. */
|
|
225
|
+
/** A listing as `companyLines` weighs it. */
|
|
226
|
+
function lineFacts({ result, exchange, home }: { result: InstrumentSearchResult; exchange: string; home: boolean }) {
|
|
227
|
+
return { name: result.name, exchange, receipt: RECEIPT.test(listedType(result)), home };
|
|
228
|
+
}
|
|
229
|
+
|
|
202
230
|
function plainName(name: string | null | undefined): string {
|
|
203
231
|
return (name ?? "").normalize("NFKD").replace(/\p{M}/gu, "").toUpperCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
|
|
204
232
|
}
|
|
@@ -211,6 +239,20 @@ function hasPrice(quote: { price?: number } | null | undefined): boolean {
|
|
|
211
239
|
return !!quote && Number.isFinite(quote.price) && quote.price !== 0;
|
|
212
240
|
}
|
|
213
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Whether search marks a listing under another symbol with the word as typed
|
|
244
|
+
* (`searchAliases`): "siemens" marks SIE on XETRA, whose symbol is not the
|
|
245
|
+
* word. One search, the one `findListingOfCompanyName` asks first, so asking
|
|
246
|
+
* this before it costs nothing more.
|
|
247
|
+
*/
|
|
248
|
+
export async function marksAnotherListing(dataProvider: Partial<Pick<DataProvider, "search">>, word: string): Promise<boolean> {
|
|
249
|
+
// A source that cannot search marks nothing.
|
|
250
|
+
if (typeof dataProvider.search !== "function") return false;
|
|
251
|
+
const answer = await searchListings(dataProvider as Pick<DataProvider, "search">, word.trim());
|
|
252
|
+
return Array.isArray(answer) && answer.some((result) =>
|
|
253
|
+
result.symbol.toUpperCase() !== word.trim().toUpperCase() && namedByService(word, result));
|
|
254
|
+
}
|
|
255
|
+
|
|
214
256
|
function searchListings(dataProvider: Pick<DataProvider, "search">, query: string): Promise<InstrumentSearchResult[] | ProviderBusyError> {
|
|
215
257
|
return dataProvider.search(query, { limit: LISTING_SEARCH_LIMIT })
|
|
216
258
|
.catch((error: unknown): InstrumentSearchResult[] | ProviderBusyError => {
|
|
@@ -228,18 +270,6 @@ function listedType(result: InstrumentSearchResult): string {
|
|
|
228
270
|
return result.brokerContract?.secType || result.type || "";
|
|
229
271
|
}
|
|
230
272
|
|
|
231
|
-
/** Letters and digits only, in capitals: "JP Morgan" and "JPMorgan" are JPMORGAN. */
|
|
232
|
-
function runTogether(text: string): string {
|
|
233
|
-
return text.normalize("NFKD").replace(/\p{M}/gu, "").toUpperCase().replace(/[^\p{L}\p{N}]/gu, "");
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/** Whether the service marked the row with the name as typed: the company it means by it. */
|
|
237
|
-
export function namedByService(typed: string, result: InstrumentSearchResult): boolean {
|
|
238
|
-
const wanted = runTogether(typed);
|
|
239
|
-
return !!wanted && Array.isArray(result.searchAliases)
|
|
240
|
-
&& result.searchAliases.some((alias) => typeof alias === "string" && runTogether(alias) === wanted);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
273
|
/**
|
|
244
274
|
* The company's common stock on a US exchange whose name starts with the
|
|
245
275
|
* typed words run together ("JPMorgan Chase & Co." for "JP Morgan"), first in
|