gloomberb 0.18.4 → 0.19.0
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +169 -5
- package/src/account/auth-model.ts +7 -3
- package/src/api-client/auth.ts +9 -4
- package/src/api-client/catalysts.ts +2 -1
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +31 -6
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/index.ts +171 -11
- package/src/api-client/live-scan.ts +91 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +13 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/request.ts +222 -41
- package/src/api-client/research-activity.ts +6 -12
- package/src/api-client/types.ts +74 -0
- package/src/app/keybindings/index.ts +3 -0
- package/src/app/keybindings/labels.ts +18 -1
- package/src/app/keybindings/resolve.ts +14 -0
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +42 -27
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +1 -1
- package/src/architecture/pane-tabs-allowlist.json +3 -0
- package/src/architecture/ttl-caches-allowlist.json +19 -0
- package/src/brokers/sync-broker-instance.ts +2 -8
- package/src/capabilities/chart-series.ts +1 -1
- package/src/cli/commands/crypto-hints.ts +40 -24
- package/src/cli/commands/fred-series.ts +5 -0
- package/src/cli/commands/keybindings.ts +23 -0
- package/src/cli/commands/market.ts +138 -46
- package/src/cli/commands/options-chain.ts +107 -10
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +176 -39
- package/src/cli/commands/search.ts +39 -3
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +57 -35
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +10 -2
- package/src/cli/export-source.ts +29 -1
- package/src/cli/glossary.ts +73 -1
- package/src/cli/history-rows.ts +87 -16
- package/src/cli/index.ts +2 -0
- package/src/cli/known-gaps.ts +5 -2
- package/src/cli/listing-arg.ts +195 -56
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +3 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +111 -16
- package/src/cli/pane-functions/function-help.ts +23 -13
- package/src/cli/pane-functions/headless.ts +56 -12
- package/src/cli/pane-functions/index.ts +58 -16
- package/src/cli/pane-functions/market-session.ts +81 -9
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/report.ts +5 -1
- package/src/cli/pane-functions/resolver.ts +35 -8
- package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
- package/src/cli/pane-functions/screenshot-types.ts +5 -0
- package/src/cli/quote-answer.ts +30 -13
- package/src/cli/report-tables.ts +18 -7
- package/src/cli/restore-plugins.ts +13 -1
- package/src/cli/result.ts +57 -5
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +26 -4
- package/src/components/chart/composite/format.ts +27 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/levels.ts +2 -0
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +61 -5
- package/src/components/chart/composite/range-statistics.ts +62 -0
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +46 -7
- package/src/components/chart/composite/series-axis-tags.tsx +16 -9
- package/src/components/chart/composite/series-cues.ts +14 -0
- package/src/components/chart/composite/text-renderer.ts +9 -2
- package/src/components/chart/composite/tool-catalog.ts +36 -0
- package/src/components/chart/composite/tools.ts +203 -44
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
- package/src/components/command-bar/assist/inventory.ts +2 -1
- package/src/components/command-bar/commands/direct/index.ts +28 -13
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/panel/prompt-binding.ts +0 -8
- package/src/components/command-bar/workflow/alert.ts +1 -1
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/header.tsx +26 -40
- package/src/components/layout/pane/footer/index.tsx +1 -0
- package/src/components/layout/pane/footer/model.ts +27 -0
- package/src/components/onboarding/onboarding-frame.tsx +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
- package/src/components/onboarding/wizard-broker-sync.ts +0 -1
- package/src/components/onboarding/wizard-positions.ts +3 -3
- package/src/components/ticker/list-table-view.tsx +4 -1
- package/src/components/ui/choice-dialog.tsx +23 -18
- package/src/components/ui/choice-groups.ts +18 -0
- package/src/components/ui/data-table/opentui/index.tsx +9 -7
- package/src/components/ui/data-table/opentui/model.ts +26 -1
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/display.tsx +5 -1
- package/src/components/ui/list-view.tsx +8 -4
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +1 -0
- package/src/components/ui/select-button.tsx +5 -1
- package/src/components/ui/select-field.tsx +1 -0
- package/src/components/ui/table-layout.ts +8 -5
- package/src/core/app-runtime.ts +2 -0
- package/src/core/app-services.ts +2 -2
- package/src/core/state/app/layout.ts +2 -0
- package/src/core/state/app/state.ts +9 -0
- package/src/core/state/app/types.ts +3 -0
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/saved-copies.ts +55 -16
- package/src/data/ticker-repository.ts +16 -9
- package/src/data/ticker-writes.ts +81 -0
- package/src/data/ttl-cache.ts +165 -0
- package/src/docs/snapshot.json +427 -216
- package/src/i18n/es.ts +19 -3
- package/src/i18n/ja.ts +19 -3
- package/src/i18n/ko.ts +19 -3
- package/src/i18n/zh-cn.ts +19 -3
- package/src/i18n/zh-tw.ts +19 -3
- package/src/market-data/coordinator/financials.ts +10 -2
- package/src/market-data/coordinator/index.ts +3 -1
- package/src/market-data/coordinator/quotes.ts +4 -2
- package/src/market-data/depositary-pair.ts +2 -2
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +48 -7
- package/src/market-data/market/dated-previous-close.ts +203 -0
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/market/futures-week.ts +68 -0
- package/src/market-data/market/status.ts +16 -4
- package/src/market-data/options-alternatives.ts +141 -4
- package/src/market-data/quotes/contributions.ts +20 -2
- package/src/market-data/quotes/resolution.ts +3 -0
- package/src/market-data/selectors.ts +3 -0
- package/src/news/aggregator.ts +113 -9
- package/src/news/cloud-session.ts +21 -0
- package/src/news/hooks.ts +1 -0
- package/src/news/news-model.ts +1 -0
- package/src/news/types.ts +11 -0
- package/src/notifications/notification-log.ts +6 -4
- package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +380 -82
- package/src/plugins/builtin/alerts/command.ts +31 -0
- package/src/plugins/builtin/alerts/constants.ts +1 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- package/src/plugins/builtin/alerts/fired-log.ts +113 -8
- package/src/plugins/builtin/alerts/format.ts +80 -4
- package/src/plugins/builtin/alerts/headless.ts +30 -7
- package/src/plugins/builtin/alerts/history-rows.ts +7 -3
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +135 -22
- package/src/plugins/builtin/alerts/live.ts +3 -3
- package/src/plugins/builtin/alerts/metric-command.ts +134 -0
- package/src/plugins/builtin/alerts/metric-data.ts +111 -0
- package/src/plugins/builtin/alerts/metric-model.ts +324 -0
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
- package/src/plugins/builtin/alerts/pane.tsx +90 -39
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +37 -10
- package/src/plugins/builtin/alerts/types.ts +7 -0
- package/src/plugins/builtin/analytics/index.tsx +2 -1
- package/src/plugins/builtin/analytics/risk-client.ts +16 -5
- package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
- package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
- package/src/plugins/builtin/analytics/risk-model.ts +55 -9
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/attention/index.tsx +2 -1
- package/src/plugins/builtin/attention/listing.ts +14 -0
- package/src/plugins/builtin/backtest/headless.ts +10 -5
- package/src/plugins/builtin/backtest/pane.tsx +9 -4
- package/src/plugins/builtin/backtest/presets.ts +29 -3
- package/src/plugins/builtin/broker-manager/index.tsx +10 -5
- package/src/plugins/builtin/broker-manager/model.ts +6 -1
- package/src/plugins/builtin/broker-trading/controller.ts +22 -12
- package/src/plugins/builtin/broker-trading/index.tsx +13 -1
- package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
- package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +12 -4
- package/src/plugins/builtin/buildout/format.ts +38 -0
- package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
- package/src/plugins/builtin/buildout/table-model.ts +23 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +7 -3
- package/src/plugins/builtin/catalysts/model.ts +2 -1
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
- package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
- package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
- package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
- package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
- package/src/plugins/builtin/chart-composer/formula.ts +71 -18
- package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
- package/src/plugins/builtin/chart-composer/headless.ts +19 -3
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
- package/src/plugins/builtin/chart-composer/presets.ts +50 -15
- package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
- package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
- package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
- package/src/plugins/builtin/chat/content/composer.tsx +13 -6
- package/src/plugins/builtin/chat/content/footer.tsx +6 -3
- package/src/plugins/builtin/chat/content/index.tsx +50 -21
- package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
- package/src/plugins/builtin/chat/index.tsx +23 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
- package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
- package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
- package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
- package/src/plugins/builtin/clinical-trials/model.ts +0 -9
- package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +1 -1
- package/src/plugins/builtin/company-attention/building.tsx +5 -1
- package/src/plugins/builtin/company-attention/client.ts +8 -9
- package/src/plugins/builtin/company-attention/evidence.ts +4 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +9 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/correlation/headless.ts +13 -5
- package/src/plugins/builtin/correlation/history.ts +22 -0
- package/src/plugins/builtin/cot/client.ts +22 -18
- package/src/plugins/builtin/cot/headless.ts +15 -10
- package/src/plugins/builtin/cot/model.ts +14 -1
- package/src/plugins/builtin/cot/pane.tsx +22 -14
- package/src/plugins/builtin/credit-boards/client.ts +6 -10
- package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
- package/src/plugins/builtin/credit-conditions/model.ts +4 -0
- package/src/plugins/builtin/credit-conditions/series.ts +3 -1
- package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
- package/src/plugins/builtin/credit-documents/export.ts +173 -0
- package/src/plugins/builtin/credit-documents/headless.ts +7 -7
- package/src/plugins/builtin/credit-documents/model.ts +104 -14
- package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
- package/src/plugins/builtin/custom-view/sources.ts +2 -0
- package/src/plugins/builtin/dividend-yield/client.ts +131 -23
- package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
- package/src/plugins/builtin/dividend-yield/model.ts +8 -5
- package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
- package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
- package/src/plugins/builtin/dividend-yield/types.ts +20 -2
- package/src/plugins/builtin/dividend-yield/view.ts +30 -2
- package/src/plugins/builtin/docs/targets.ts +5 -1
- package/src/plugins/builtin/earnings/board-model.ts +16 -15
- package/src/plugins/builtin/earnings/board.tsx +62 -71
- package/src/plugins/builtin/earnings/headless.ts +40 -4
- package/src/plugins/builtin/earnings/scope.ts +31 -43
- package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
- package/src/plugins/builtin/econ/calendar-model.ts +14 -9
- package/src/plugins/builtin/econ/figures.ts +72 -0
- package/src/plugins/builtin/econ/index.tsx +33 -3
- package/src/plugins/builtin/econ/types.ts +4 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
- package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
- package/src/plugins/builtin/econ-statistics/view.ts +3 -0
- package/src/plugins/builtin/equity-screener/categories.ts +179 -0
- package/src/plugins/builtin/equity-screener/client.ts +4 -2
- package/src/plugins/builtin/equity-screener/headless.ts +144 -13
- package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
- package/src/plugins/builtin/equity-screener/model.ts +99 -10
- package/src/plugins/builtin/equity-screener/pane.tsx +70 -15
- package/src/plugins/builtin/exposure/charts.tsx +2 -2
- package/src/plugins/builtin/exposure/model.ts +13 -1
- package/src/plugins/builtin/exposure/pane.tsx +4 -4
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/futures/model.ts +2 -1
- package/src/plugins/builtin/futures/table.tsx +2 -2
- package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
- package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
- package/src/plugins/builtin/fx-matrix/client.ts +11 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
- package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
- package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -3
- package/src/plugins/builtin/gpu/headless.ts +8 -2
- package/src/plugins/builtin/help/function-index.ts +4 -1
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +18 -1
- package/src/plugins/builtin/iv-history/client.ts +52 -11
- package/src/plugins/builtin/iv-history/format.ts +36 -23
- package/src/plugins/builtin/iv-history/headless.ts +36 -18
- package/src/plugins/builtin/iv-history/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/model.ts +163 -27
- package/src/plugins/builtin/iv-history/pane.tsx +10 -6
- package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
- package/src/plugins/builtin/iv-history/universe.ts +5 -1
- package/src/plugins/builtin/jobs/client.ts +25 -29
- package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
- package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
- package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
- package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
- package/src/plugins/builtin/macro-day/pane.tsx +7 -5
- package/src/plugins/builtin/macro-day/release-table.ts +2 -2
- package/src/plugins/builtin/market-heatmap/data.ts +8 -33
- package/src/plugins/builtin/market-movers/client.ts +25 -7
- package/src/plugins/builtin/market-movers/footer.ts +2 -2
- package/src/plugins/builtin/market-movers/headless.ts +32 -12
- package/src/plugins/builtin/market-movers/index.tsx +26 -14
- package/src/plugins/builtin/market-movers/min-float.ts +74 -0
- package/src/plugins/builtin/market-movers/model.ts +4 -1
- package/src/plugins/builtin/market-movers/screener.ts +14 -0
- package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
- package/src/plugins/builtin/market-movers/session-table.ts +4 -3
- package/src/plugins/builtin/market-movers/table.ts +17 -5
- package/src/plugins/builtin/members/breadth-pane.tsx +82 -0
- package/src/plugins/builtin/members/client.ts +19 -4
- package/src/plugins/builtin/members/headless.ts +20 -7
- package/src/plugins/builtin/members/index.tsx +9 -9
- package/src/plugins/builtin/members/model.ts +23 -4
- package/src/plugins/builtin/members/pane.tsx +25 -15
- package/src/plugins/builtin/news/index.tsx +1 -0
- package/src/plugins/builtin/news/wire/categories.ts +19 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
- package/src/plugins/builtin/openfda/client.ts +14 -2
- package/src/plugins/builtin/openfda/index.tsx +3 -2
- package/src/plugins/builtin/openfda/model.ts +9 -0
- package/src/plugins/builtin/openfda/pane.tsx +42 -6
- package/src/plugins/builtin/options/analytics.ts +54 -6
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/model-spot.ts +5 -2
- package/src/plugins/builtin/options/strike-window.ts +2 -1
- package/src/plugins/builtin/options/table.ts +6 -9
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +42 -12
- package/src/plugins/builtin/options-positioning/client.ts +30 -0
- package/src/plugins/builtin/options-positioning/headless.ts +74 -21
- package/src/plugins/builtin/options-positioning/index.tsx +4 -4
- package/src/plugins/builtin/options-positioning/model.ts +122 -14
- package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
- package/src/plugins/builtin/options-scenario/client.ts +116 -47
- package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
- package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
- package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
- package/src/plugins/builtin/options-scenario/csp.ts +8 -2
- package/src/plugins/builtin/options-scenario/index.tsx +9 -7
- package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
- package/src/plugins/builtin/pe-band/model.ts +12 -49
- package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
- package/src/plugins/builtin/perps/cascade.ts +236 -0
- package/src/plugins/builtin/perps/client.ts +56 -2
- package/src/plugins/builtin/perps/freshness.ts +12 -10
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/live-rows.ts +10 -3
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +32 -31
- package/src/plugins/builtin/perps/status-line.ts +25 -0
- package/src/plugins/builtin/plugin-module.ts +5 -1
- package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +12 -7
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/rate-path/pane.tsx +2 -2
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/client.ts +18 -4
- package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
- package/src/plugins/builtin/relative-rotation/model.ts +16 -40
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
- package/src/plugins/builtin/research/event-model.ts +10 -8
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
- package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
- package/src/plugins/builtin/scanner/feed.ts +2 -2
- package/src/plugins/builtin/scanner/flow-model.ts +50 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
- package/src/plugins/builtin/scanner/flow-session.ts +146 -0
- package/src/plugins/builtin/scanner/headless.ts +204 -35
- package/src/plugins/builtin/scanner/index.ts +40 -6
- package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
- package/src/plugins/builtin/scanner/live-feed.ts +168 -0
- package/src/plugins/builtin/scanner/live-headless.ts +138 -0
- package/src/plugins/builtin/scanner/live-model.ts +362 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
- package/src/plugins/builtin/scanner/live-track.ts +186 -0
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/headless.ts +10 -1
- package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
- package/src/plugins/builtin/sec/fund-facts.ts +116 -34
- package/src/plugins/builtin/sec/headless.ts +2 -1
- package/src/plugins/builtin/series-inventory.ts +3 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
- package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
- package/src/plugins/builtin/shared/earnings-report.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/macro-releases.ts +65 -0
- package/src/plugins/builtin/shared/members-menu.tsx +3 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
- package/src/plugins/builtin/shared/report-freshness.ts +3 -2
- package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
- package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
- package/src/plugins/builtin/shared/ticker-company.ts +77 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +10 -3
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/headless.ts +6 -4
- package/src/plugins/builtin/themes/model.ts +17 -8
- package/src/plugins/builtin/themes/pane.tsx +7 -7
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
- package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
- package/src/plugins/builtin/time-sales/pane.tsx +31 -19
- package/src/plugins/builtin/treasury-daily/client.ts +41 -5
- package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
- package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
- package/src/plugins/builtin/treasury-daily/model.ts +50 -3
- package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
- package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
- package/src/plugins/builtin/vol-surface/client.ts +9 -17
- package/src/plugins/builtin/vol-surface/headless.ts +63 -14
- package/src/plugins/builtin/vol-surface/index.tsx +2 -2
- package/src/plugins/builtin/vol-surface/model.ts +22 -7
- package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
- package/src/plugins/builtin/vol-surface/tables.ts +31 -3
- package/src/plugins/builtin/volatility/dvol.ts +46 -0
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +4 -4
- package/src/plugins/builtin/volatility/series.ts +26 -0
- package/src/plugins/builtin/world-indices/footer.ts +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-browser.ts +13 -8
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +5 -5
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
- package/src/remote/resources.ts +2 -1
- package/src/renderers/browser/app-services.ts +1 -7
- package/src/renderers/browser/config-host.ts +2 -55
- package/src/renderers/browser/main.tsx +7 -57
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/list-view.tsx +13 -10
- package/src/renderers/dom/desktop/query-bar.tsx +4 -2
- package/src/renderers/dom/desktop/select-field.tsx +4 -2
- package/src/renderers/dom/host/chart-surface.tsx +21 -0
- package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
- package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
- package/src/renderers/electrobun/bun/index.ts +25 -1
- package/src/renderers/electrobun/shared/app-name.ts +30 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +3 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/renderers/opentui/toast-host.tsx +71 -43
- package/src/series/registry.ts +1 -0
- package/src/series/types.ts +6 -0
- package/src/series/validate.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +1 -0
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
- package/src/sources/provider-router/batches.ts +10 -2
- package/src/sources/provider-router/financial-routes.ts +3 -3
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/history.ts +28 -7
- package/src/sources/provider-router/index.ts +149 -10
- package/src/sources/provider-router/search.ts +13 -2
- package/src/state/app/context/index.tsx +3 -0
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/auth-transition.ts +19 -9
- package/src/sync/baseline.ts +10 -4
- package/src/sync/controller.ts +70 -19
- package/src/sync/core-contributors.ts +90 -31
- package/src/sync/react.ts +4 -1
- package/src/sync/types.ts +3 -0
- package/src/telemetry/stall-reports-dom.ts +38 -0
- package/src/telemetry/stall-reports-node.ts +75 -0
- package/src/telemetry/stall-reports.ts +214 -0
- package/src/telemetry/usage-counts.ts +0 -18
- package/src/test-support/preload.ts +14 -0
- package/src/theme/colors.ts +8 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name.ts +103 -3
- package/src/tickers/listing-venues.ts +38 -3
- package/src/tickers/search/bare-symbol.ts +36 -0
- package/src/tickers/search/index.ts +36 -7
- package/src/tickers/search/ranking.ts +10 -1
- package/src/tickers/search/types.ts +5 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/base-series.ts +13 -5
- package/src/time-series/daily-valuation.ts +62 -0
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +2 -4
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/forward-valuation.ts +32 -0
- package/src/time-series/fundamentals.ts +198 -7
- package/src/time-series/hooks.ts +1 -0
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/price-units.ts +72 -2
- package/src/time-series/resolve-context.ts +7 -1
- package/src/time-series/resolve.ts +116 -15
- package/src/time-series/series-presentation.ts +40 -6
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +5 -3
- package/src/time-series/studies.ts +278 -74
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +3 -3
- package/src/time-series/types.ts +27 -1
- package/src/time-series/valuation-releases.ts +39 -0
- package/src/types/data-provider.ts +9 -1
- package/src/types/desktop-window.ts +2 -0
- package/src/types/financials.ts +73 -2
- package/src/types/headless.ts +61 -5
- package/src/types/instrument.ts +3 -0
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +10 -1
- package/src/ui/host.tsx +3 -0
- package/src/updater/command-names.ts +12 -7
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +44 -0
- package/src/utils/financial-statements.ts +6 -1
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +24 -3
- package/src/utils/fx-market-hours.ts +26 -3
- package/src/utils/main-thread-stall.ts +35 -2
- package/src/utils/market-capitalization.ts +24 -1
- package/src/utils/network-failure.ts +9 -6
- package/src/utils/option-expiry.ts +48 -1
- package/src/utils/options.ts +10 -0
- package/src/utils/plugin-proxy-hosts.json +1 -0
- package/src/utils/price-history.ts +12 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
- package/src/renderers/browser/first-screen.ts +0 -193
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { saveConfigImmediately, scheduleConfigSave } from "../state/config-save-scheduler";
|
|
2
|
+
import { persistTickerCleanup } from "../state/ticker-cleanup";
|
|
3
|
+
import { hasPendingTickerWrites } from "../data/ticker-writes";
|
|
2
4
|
import { normalizeStoredBaseCurrency } from "../utils/base-currency";
|
|
3
5
|
import { debugLog } from "../utils/debug-log";
|
|
4
6
|
import { fnv1aHex, stableStringify } from "../utils/hash";
|
|
@@ -1184,6 +1186,23 @@ function holdingIn(positions: readonly TickerPosition[], links: readonly unknown
|
|
|
1184
1186
|
};
|
|
1185
1187
|
}
|
|
1186
1188
|
|
|
1189
|
+
/** Without a shared baseline, keep both devices' manual lots, matching identical copies one for one. */
|
|
1190
|
+
function mergeUntrackedManualPositions(local: TickerPosition[], pulled: TickerPosition[]): TickerPosition[] {
|
|
1191
|
+
const unmatched = new Map<string, number>();
|
|
1192
|
+
for (const position of local) {
|
|
1193
|
+
const key = JSON.stringify(sanitizePosition(position));
|
|
1194
|
+
unmatched.set(key, (unmatched.get(key) ?? 0) + 1);
|
|
1195
|
+
}
|
|
1196
|
+
const merged = [...local];
|
|
1197
|
+
for (const position of pulled) {
|
|
1198
|
+
const key = JSON.stringify(sanitizePosition(position));
|
|
1199
|
+
const count = unmatched.get(key) ?? 0;
|
|
1200
|
+
if (count > 0) unmatched.set(key, count - 1);
|
|
1201
|
+
else merged.push(position);
|
|
1202
|
+
}
|
|
1203
|
+
return merged;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1187
1206
|
interface PulledTickersMerge {
|
|
1188
1207
|
lastSyncedTickers: Map<string, Record<string, unknown>> | null;
|
|
1189
1208
|
/** This device's live broker portfolios, each with the side holding its latest broker sync. */
|
|
@@ -1274,8 +1293,25 @@ function mergePulledTicker(
|
|
|
1274
1293
|
holdingIn(baselinePositions, baselineLinks, portfolioId),
|
|
1275
1294
|
);
|
|
1276
1295
|
const [sidePositions, sideLinks] = takePulled ? [pulledPositions, pulledLinks] : [localPositions, localLinks];
|
|
1277
|
-
|
|
1278
|
-
|
|
1296
|
+
if (brokerSide && !merge.localOnly?.portfolioIds.has(portfolioId)) {
|
|
1297
|
+
// A broker's timestamp governs imported holdings, never hand-entered
|
|
1298
|
+
// lots. Those use the same edit comparison as a manual portfolio.
|
|
1299
|
+
const manualIn = (entries: readonly TickerPosition[]) => entries.filter((position) => (
|
|
1300
|
+
position.portfolio === portfolioId && position.broker === "manual"
|
|
1301
|
+
));
|
|
1302
|
+
const localManual = manualIn(localPositions);
|
|
1303
|
+
const manualEdited = comparable && !sameAsStored(
|
|
1304
|
+
localManual.map(sanitizePosition), manualIn(baselinePositions).map(sanitizePosition),
|
|
1305
|
+
);
|
|
1306
|
+
const pulledManual = manualIn(pulledPositions);
|
|
1307
|
+
const manual = !comparable ? mergeUntrackedManualPositions(localManual, pulledManual)
|
|
1308
|
+
: manualEdited ? localManual : pulledManual;
|
|
1309
|
+
positions.push(...sidePositions.filter((position) => position.portfolio === portfolioId && position.broker !== "manual"), ...manual);
|
|
1310
|
+
if (sideLinks.includes(portfolioId) || manual.length > 0) portfolios.push(portfolioId);
|
|
1311
|
+
} else {
|
|
1312
|
+
positions.push(...sidePositions.filter((position) => position.portfolio === portfolioId));
|
|
1313
|
+
if (sideLinks.includes(portfolioId)) portfolios.push(portfolioId);
|
|
1314
|
+
}
|
|
1279
1315
|
}
|
|
1280
1316
|
|
|
1281
1317
|
const record: TickerRecord = {
|
|
@@ -1297,8 +1333,9 @@ function mergePulledTicker(
|
|
|
1297
1333
|
function withoutUnpulledHoldings(ticker: TickerRecord, merge: PulledTickersMerge): TickerRecord | null {
|
|
1298
1334
|
const sold = (portfolioId: string) => merge.brokerPortfolios.get(portfolioId) === "pulled";
|
|
1299
1335
|
const { metadata } = ticker;
|
|
1300
|
-
const positions = metadata.positions.filter((position) => !sold(position.portfolio));
|
|
1301
|
-
const
|
|
1336
|
+
const positions = metadata.positions.filter((position) => position.broker === "manual" || !sold(position.portfolio));
|
|
1337
|
+
const held = new Set(positions.map((position) => position.portfolio));
|
|
1338
|
+
const portfolios = metadata.portfolios.filter((portfolioId) => held.has(portfolioId) || !sold(portfolioId));
|
|
1302
1339
|
const kept = positions.length === metadata.positions.length && portfolios.length === metadata.portfolios.length
|
|
1303
1340
|
? ticker
|
|
1304
1341
|
: { ...ticker, metadata: { ...metadata, positions, portfolios } };
|
|
@@ -1322,12 +1359,57 @@ export const coreConfigSyncContributor: SyncContributor = {
|
|
|
1322
1359
|
},
|
|
1323
1360
|
apply: async (payload, { snapshot, baselinePayload, baselineState, state, getState, isCurrent, dispatch, tickerRepository, memo }) => {
|
|
1324
1361
|
const pushedAt = snapshot?.contributors[CORE_CONFIG_CONTRIBUTOR_ID]?.updatedAt;
|
|
1325
|
-
const
|
|
1362
|
+
const merge = (config: AppConfig) => mergeConfigPayload(config, payload, baselineState.config, baselinePayload, {
|
|
1326
1363
|
memo: memo?.get(),
|
|
1327
1364
|
pushedAt,
|
|
1328
|
-
}, localOnlyScope(
|
|
1365
|
+
}, localOnlyScope(config));
|
|
1366
|
+
let nextConfig = merge(state.config);
|
|
1329
1367
|
if (!nextConfig) return;
|
|
1330
|
-
const
|
|
1368
|
+
const removed = removedBrokerHoldings(state.config, nextConfig);
|
|
1369
|
+
if (removed) {
|
|
1370
|
+
// Keep the profile until row and config writes succeed, so a failed
|
|
1371
|
+
// cleanup remains visible to the next attempt.
|
|
1372
|
+
const cleanTickers = () => persistTickerCleanup({
|
|
1373
|
+
tickerRepository,
|
|
1374
|
+
getTickers: () => getState().tickers,
|
|
1375
|
+
transform: (ticker) => withoutRemovedBrokerHoldings(ticker, removed),
|
|
1376
|
+
publish: (symbol, ticker) => {
|
|
1377
|
+
const tickers = new Map(getState().tickers);
|
|
1378
|
+
if (ticker) tickers.set(symbol, ticker);
|
|
1379
|
+
else tickers.delete(symbol);
|
|
1380
|
+
dispatch({ type: "SET_TICKERS", tickers });
|
|
1381
|
+
},
|
|
1382
|
+
isCurrent,
|
|
1383
|
+
});
|
|
1384
|
+
let savedRemoval = false;
|
|
1385
|
+
for (;;) {
|
|
1386
|
+
const cleaned = await cleanTickers().catch(async (error: unknown) => {
|
|
1387
|
+
// Restore the profile if a late row fails after config was saved,
|
|
1388
|
+
// so retry can also recover after restarting the app.
|
|
1389
|
+
if (savedRemoval) await saveConfigImmediately(getState().config);
|
|
1390
|
+
throw error;
|
|
1391
|
+
});
|
|
1392
|
+
if (!cleaned || !isCurrent()) return;
|
|
1393
|
+
if (hasPendingTickerWrites(tickerRepository) || getState().tickers !== cleaned) continue;
|
|
1394
|
+
const source = getState().config;
|
|
1395
|
+
const merged = merge(source);
|
|
1396
|
+
if (!merged) return;
|
|
1397
|
+
// Preserve unrelated edits while retaining the removal already applied
|
|
1398
|
+
// to rows, including pulls from builds that merged whole profile lists.
|
|
1399
|
+
nextConfig = {
|
|
1400
|
+
...merged,
|
|
1401
|
+
brokerInstances: merged.brokerInstances.filter((instance) => !removed.instanceIds.has(instance.id)),
|
|
1402
|
+
portfolios: merged.portfolios.filter((portfolio) => !removed.portfolioIds.has(portfolio.id)),
|
|
1403
|
+
};
|
|
1404
|
+
await saveConfigImmediately(nextConfig);
|
|
1405
|
+
savedRemoval = true;
|
|
1406
|
+
if (!isCurrent()) return;
|
|
1407
|
+
if (getState().config !== source || getState().tickers !== cleaned
|
|
1408
|
+
|| hasPendingTickerWrites(tickerRepository)) continue;
|
|
1409
|
+
break;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
const configChanged = !valuesEqual(nextConfig, getState().config);
|
|
1331
1413
|
if (configChanged) {
|
|
1332
1414
|
// Adopting another device's workspace rearranges the screen under the
|
|
1333
1415
|
// user, so it is worth a line in the log when a report says "my panes
|
|
@@ -1339,32 +1421,9 @@ export const coreConfigSyncContributor: SyncContributor = {
|
|
|
1339
1421
|
});
|
|
1340
1422
|
}
|
|
1341
1423
|
dispatch({ type: "SET_CONFIG", config: nextConfig });
|
|
1342
|
-
scheduleConfigSave(nextConfig);
|
|
1424
|
+
if (!removed) scheduleConfigSave(nextConfig);
|
|
1343
1425
|
}
|
|
1344
1426
|
if (memo && isRecord(payload)) rememberCloudLayout(payload, pushedAt, getState().config, memo, nextConfig.layouts !== state.config.layouts);
|
|
1345
|
-
if (!configChanged) return;
|
|
1346
|
-
|
|
1347
|
-
// A profile removed on another device takes its positions with it here
|
|
1348
|
-
// too; kept, this device would push them back and recreate the tickers
|
|
1349
|
-
// that removal deleted.
|
|
1350
|
-
const removed = removedBrokerHoldings(state.config, nextConfig);
|
|
1351
|
-
if (!removed) return;
|
|
1352
|
-
const nextTickers = new Map(getState().tickers);
|
|
1353
|
-
let changed = false;
|
|
1354
|
-
for (const ticker of getState().tickers.values()) {
|
|
1355
|
-
if (!isCurrent()) return;
|
|
1356
|
-
const next = withoutRemovedBrokerHoldings(ticker, removed);
|
|
1357
|
-
if (next === ticker) continue;
|
|
1358
|
-
changed = true;
|
|
1359
|
-
if (next) {
|
|
1360
|
-
await tickerRepository.saveTicker(next);
|
|
1361
|
-
nextTickers.set(next.metadata.ticker, next);
|
|
1362
|
-
} else {
|
|
1363
|
-
await tickerRepository.deleteTicker(ticker.metadata.ticker);
|
|
1364
|
-
nextTickers.delete(ticker.metadata.ticker);
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
if (changed && isCurrent()) dispatch({ type: "SET_TICKERS", tickers: nextTickers });
|
|
1368
1427
|
},
|
|
1369
1428
|
};
|
|
1370
1429
|
|
package/src/sync/react.ts
CHANGED
|
@@ -39,6 +39,8 @@ export function useCloudSyncRuntime({
|
|
|
39
39
|
dispatch,
|
|
40
40
|
tickerRepository,
|
|
41
41
|
baselineStore,
|
|
42
|
+
getSessionIdentity: () => apiClient.getVerifiedSessionIdentity(),
|
|
43
|
+
getAccountId: () => apiClient.getVerifiedSessionIdentity() ? apiClient.getCurrentUser()?.id ?? null : null,
|
|
42
44
|
getContributors: () => pluginRegistry.getEnabledSyncContributors(),
|
|
43
45
|
getTransport: () => pluginRegistry.getActiveSyncTransport(),
|
|
44
46
|
isLocalMarketData: (providerId) => {
|
|
@@ -69,7 +71,8 @@ export function useCloudSyncRuntime({
|
|
|
69
71
|
// Email verification makes the Cloud transport available. Force the first
|
|
70
72
|
// sync so a workspace completed before verification is uploaded promptly.
|
|
71
73
|
void cloudSyncController.requestSync({ reason: "session-verified", force: true });
|
|
72
|
-
}
|
|
74
|
+
}, (accountId) => cloudSyncController.resetAccount(accountId),
|
|
75
|
+
() => cloudSyncController.retireSession()), [initialized]);
|
|
73
76
|
|
|
74
77
|
useEffect(() => {
|
|
75
78
|
if (!initialized) return;
|
package/src/sync/types.ts
CHANGED
|
@@ -121,6 +121,9 @@ export interface SyncSettings {
|
|
|
121
121
|
|
|
122
122
|
/** Durable record of the payloads this device last pushed to the cloud. */
|
|
123
123
|
export interface SyncBaselineStore {
|
|
124
|
+
/** Account that owns this merge history; absent on stores written before account tracking. */
|
|
125
|
+
loadAccountId?(): string | null;
|
|
126
|
+
saveAccountId?(accountId: string): void;
|
|
124
127
|
load(): Record<string, unknown> | null;
|
|
125
128
|
save(payloads: Record<string, unknown>): void;
|
|
126
129
|
/** Contributor memos by contributor id; see `SyncContributorMemo`. */
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { StallEvent } from "../utils/main-thread-stall";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Only the freezes a user sat through. The system slows the timers of a
|
|
5
|
+
* window behind others or out of view, sometimes to one a second, and the
|
|
6
|
+
* stall monitor cannot tell that from a freeze: a timer back a second late
|
|
7
|
+
* reads the same either way. A window that has been in front, with focus,
|
|
8
|
+
* since before the stall began ran at full speed, so its late timers are
|
|
9
|
+
* freezes. Anything else is dropped, including a real freeze the user
|
|
10
|
+
* clicked into the window during: missing one beats reporting a false one.
|
|
11
|
+
*/
|
|
12
|
+
export function forwardAttendedStalls(
|
|
13
|
+
forward: (event: StallEvent) => void,
|
|
14
|
+
target: Window = window,
|
|
15
|
+
now: () => number = () => performance.now(),
|
|
16
|
+
): { sink: (event: StallEvent) => void; dispose: () => void } {
|
|
17
|
+
const doc = target.document;
|
|
18
|
+
const attended = () => doc.visibilityState === "visible" && doc.hasFocus();
|
|
19
|
+
// Since when the window has been attended; never while it is not.
|
|
20
|
+
let attendedSince = attended() ? now() : Number.POSITIVE_INFINITY;
|
|
21
|
+
const update = () => {
|
|
22
|
+
if (!attended()) attendedSince = Number.POSITIVE_INFINITY;
|
|
23
|
+
else if (attendedSince === Number.POSITIVE_INFINITY) attendedSince = now();
|
|
24
|
+
};
|
|
25
|
+
target.addEventListener("focus", update);
|
|
26
|
+
target.addEventListener("blur", update);
|
|
27
|
+
doc.addEventListener("visibilitychange", update);
|
|
28
|
+
return {
|
|
29
|
+
sink: (event) => {
|
|
30
|
+
if (attended() && attendedSince <= now() - event.blockedMs) forward(event);
|
|
31
|
+
},
|
|
32
|
+
dispose: () => {
|
|
33
|
+
target.removeEventListener("focus", update);
|
|
34
|
+
target.removeEventListener("blur", update);
|
|
35
|
+
doc.removeEventListener("visibilitychange", update);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import type { StallReportState, StallReporterHost } from "./stall-reports";
|
|
4
|
+
|
|
5
|
+
/** Next to config.json: one line per stall, newest last. */
|
|
6
|
+
const STALL_LOG_FILE = "stall-log.txt";
|
|
7
|
+
/** Next to config.json: when each kind of stall was last reported, and the counts since. */
|
|
8
|
+
const STATE_FILE = "stall-reports.json";
|
|
9
|
+
/** Past this the log keeps its newer half. */
|
|
10
|
+
const LOG_MAX_BYTES = 256 * 1024;
|
|
11
|
+
|
|
12
|
+
function readCounts(value: unknown): StallReportState["pending"] {
|
|
13
|
+
const pending: StallReportState["pending"] = {};
|
|
14
|
+
if (!value || typeof value !== "object") return pending;
|
|
15
|
+
for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {
|
|
16
|
+
const { count, longestMs } = (entry ?? {}) as Record<string, unknown>;
|
|
17
|
+
if (typeof count === "number" && Number.isFinite(count) && typeof longestMs === "number" && Number.isFinite(longestMs)) {
|
|
18
|
+
pending[key] = { count, longestMs };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return pending;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The saved state, keeping only entries of the right shape. */
|
|
25
|
+
function parseStallReportState(text: string): StallReportState {
|
|
26
|
+
const parsed: unknown = JSON.parse(text);
|
|
27
|
+
const record = parsed && typeof parsed === "object" ? parsed as Record<string, unknown> : {};
|
|
28
|
+
const sent: Record<string, number> = {};
|
|
29
|
+
if (record.sent && typeof record.sent === "object") {
|
|
30
|
+
for (const [key, at] of Object.entries(record.sent as Record<string, unknown>)) {
|
|
31
|
+
if (typeof at === "number" && Number.isFinite(at)) sent[key] = at;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { sent, pending: readCounts(record.pending) };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function trimLog(path: string): void {
|
|
38
|
+
if (statSync(path).size <= LOG_MAX_BYTES) return;
|
|
39
|
+
const text = readFileSync(path, "utf-8");
|
|
40
|
+
const keep = text.slice(text.length - LOG_MAX_BYTES / 2);
|
|
41
|
+
writeFileSync(path, keep.slice(keep.indexOf("\n") + 1), "utf-8");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The stall reporter for a Bun process (the terminal app, the desktop app's main process). */
|
|
45
|
+
export function createNodeStallReporterHost(options: {
|
|
46
|
+
/** The profile's data folder, or null before the config is loaded. */
|
|
47
|
+
getDataDir(): string | null;
|
|
48
|
+
}): StallReporterHost {
|
|
49
|
+
const pathFor = (file: string) => {
|
|
50
|
+
const dir = options.getDataDir();
|
|
51
|
+
return dir ? join(dir, file) : null;
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
platform: process.platform,
|
|
55
|
+
arch: process.arch,
|
|
56
|
+
appendLog: (line) => {
|
|
57
|
+
const path = pathFor(STALL_LOG_FILE);
|
|
58
|
+
if (!path) return;
|
|
59
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
60
|
+
appendFileSync(path, `${line}\n`, "utf-8");
|
|
61
|
+
trimLog(path);
|
|
62
|
+
},
|
|
63
|
+
load: () => {
|
|
64
|
+
const path = pathFor(STATE_FILE);
|
|
65
|
+
if (!path || !existsSync(path)) return { sent: {}, pending: {} };
|
|
66
|
+
return parseStallReportState(readFileSync(path, "utf-8"));
|
|
67
|
+
},
|
|
68
|
+
save: (state) => {
|
|
69
|
+
const path = pathFor(STATE_FILE);
|
|
70
|
+
if (!path) return;
|
|
71
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
72
|
+
writeFileSync(path, JSON.stringify(state), "utf-8");
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { logFailure } from "../utils/log-failure";
|
|
2
|
+
import type { StallEvent } from "../utils/main-thread-stall";
|
|
3
|
+
import { VERSION } from "../version";
|
|
4
|
+
import { reportCrash } from "./crash-reports";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Freezes, made visible outside the app's own Debug Log.
|
|
8
|
+
*
|
|
9
|
+
* The stall monitor (utils/main-thread-stall.ts) notices when a process could
|
|
10
|
+
* not answer the user for a while and names the last measured section, and
|
|
11
|
+
* hands the stall to its sink. The terminal app and the desktop app's Bun
|
|
12
|
+
* process record their own stalls here; the desktop window sends the stalls
|
|
13
|
+
* it had in front of the user (stall-reports-dom.ts) to the Bun process,
|
|
14
|
+
* which records them too. Where a host is installed, each
|
|
15
|
+
* stall:
|
|
16
|
+
*
|
|
17
|
+
* - goes to a plain-text stall log in the profile folder, one line each, so a
|
|
18
|
+
* user or support can read what froze and when;
|
|
19
|
+
* - from a second up, counts toward a report sent through the crash reporter
|
|
20
|
+
* (and so behind the same off switch), the way update failures are: the
|
|
21
|
+
* duration bucket, the section's fixed name, the process, the longest
|
|
22
|
+
* stall and how many there were. No free text: a section name keeps only
|
|
23
|
+
* its fixed leading part, so a ticker in a queue key never leaves.
|
|
24
|
+
*
|
|
25
|
+
* 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.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** Which process stalled. */
|
|
30
|
+
export type StallProcess = "window" | "app" | "terminal";
|
|
31
|
+
|
|
32
|
+
/** What error tracking gets: fixed values only. */
|
|
33
|
+
export interface StallReport {
|
|
34
|
+
process: StallProcess;
|
|
35
|
+
bucket: StallBucket;
|
|
36
|
+
section: string;
|
|
37
|
+
appVersion: string;
|
|
38
|
+
platform: "macos" | "windows" | "linux" | "other";
|
|
39
|
+
arch: "arm64" | "x64" | "other";
|
|
40
|
+
/** Stalls of this kind since the last report, this one included. */
|
|
41
|
+
count: number;
|
|
42
|
+
/** The longest of them, rounded to 100 ms. */
|
|
43
|
+
longestMs: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type StallBucket = "1-2s" | "2-5s" | "5-15s" | "15s+";
|
|
47
|
+
|
|
48
|
+
/** What the host keeps on disk between launches. */
|
|
49
|
+
export interface StallReportState {
|
|
50
|
+
/** When each kind was last reported, in ms. */
|
|
51
|
+
sent: Record<string, number>;
|
|
52
|
+
/** Stalls counted since each kind was last reported. */
|
|
53
|
+
pending: Record<string, { count: number; longestMs: number }>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface StallReporterHost {
|
|
57
|
+
/** The running version; defaults to this build's. */
|
|
58
|
+
appVersion?: string;
|
|
59
|
+
platform: string;
|
|
60
|
+
arch: string;
|
|
61
|
+
/** Appends one line to the stall log. */
|
|
62
|
+
appendLog(line: string): void;
|
|
63
|
+
load(): StallReportState;
|
|
64
|
+
save(state: StallReportState): void;
|
|
65
|
+
now?(): number;
|
|
66
|
+
/** Defaults to the crash reporter. */
|
|
67
|
+
send?(report: StallReport): void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
71
|
+
/** Shorter freezes go to the log only. */
|
|
72
|
+
const STALL_REPORT_MIN_MS = 1_000;
|
|
73
|
+
const SECTION_PATTERN = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*/;
|
|
74
|
+
const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]{1,60})?$/;
|
|
75
|
+
/** Kinds with counts waiting for their next report; the oldest go first past this. */
|
|
76
|
+
const MAX_KINDS_KEPT = 200;
|
|
77
|
+
|
|
78
|
+
let host: StallReporterHost | null = null;
|
|
79
|
+
|
|
80
|
+
/** The fixed part of a section name: lower-case dotted words, cut where anything else starts. */
|
|
81
|
+
function stallSectionName(name: string | undefined): string {
|
|
82
|
+
if (!name) return "unmeasured";
|
|
83
|
+
const match = SECTION_PATTERN.exec(name);
|
|
84
|
+
const section = match?.[0].replace(/\.$/, "");
|
|
85
|
+
return section && section.length <= 80 ? section : "unmeasured";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function stallBucket(blockedMs: number): StallBucket {
|
|
89
|
+
if (blockedMs < 2_000) return "1-2s";
|
|
90
|
+
if (blockedMs < 5_000) return "2-5s";
|
|
91
|
+
if (blockedMs < 15_000) return "5-15s";
|
|
92
|
+
return "15s+";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A stall another process sent over, kept only when it has the right shape. */
|
|
96
|
+
export function parseStallEvent(value: unknown): StallEvent | null {
|
|
97
|
+
if (!value || typeof value !== "object") return null;
|
|
98
|
+
const { blockedMs, lastMeasured, lastMeasuredEndedMsAgo } = value as Record<string, unknown>;
|
|
99
|
+
if (typeof blockedMs !== "number" || !Number.isFinite(blockedMs) || blockedMs <= 0) return null;
|
|
100
|
+
return {
|
|
101
|
+
blockedMs,
|
|
102
|
+
...(typeof lastMeasured === "string" && lastMeasured.length <= 200 ? { lastMeasured } : {}),
|
|
103
|
+
...(typeof lastMeasuredEndedMsAgo === "number" && Number.isFinite(lastMeasuredEndedMsAgo) ? { lastMeasuredEndedMsAgo } : {}),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function formatSeconds(ms: number): string {
|
|
108
|
+
return `${(ms / 1000).toFixed(ms < 10_000 ? 1 : 0)} s`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** One line of the stall log: when, which process, how long, and what ran last. */
|
|
112
|
+
function formatStallLogLine(process: StallProcess, event: StallEvent, at: number): string {
|
|
113
|
+
const time = new Date(at).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
114
|
+
const last = event.lastMeasured
|
|
115
|
+
? `last measured: ${event.lastMeasured}${event.lastMeasuredEndedMsAgo === undefined ? "" : `, ended ${Math.round(event.lastMeasuredEndedMsAgo)} ms before`}`
|
|
116
|
+
: "nothing measured before it";
|
|
117
|
+
return `${time} ${process.padEnd(8)} froze ${formatSeconds(event.blockedMs).padStart(6)} ${last}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function reportPlatform(platform: string): StallReport["platform"] {
|
|
121
|
+
if (platform === "darwin") return "macos";
|
|
122
|
+
if (platform === "win32") return "windows";
|
|
123
|
+
if (platform === "linux") return "linux";
|
|
124
|
+
return "other";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function reportArch(arch: string): StallReport["arch"] {
|
|
128
|
+
return arch === "arm64" || arch === "x64" ? arch : "other";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function sendThroughCrashReporter(report: StallReport): void {
|
|
132
|
+
const details = [
|
|
133
|
+
`process=${report.process}`,
|
|
134
|
+
`count=${report.count}`,
|
|
135
|
+
`longest=${report.longestMs}ms`,
|
|
136
|
+
`platform=${report.platform}-${report.arch}`,
|
|
137
|
+
].join(" ");
|
|
138
|
+
// A plain object, not an Error: it has no stack, so no file path goes along.
|
|
139
|
+
// The message is the bucket and section, so error tracking keeps one issue per kind.
|
|
140
|
+
reportCrash(
|
|
141
|
+
{ name: "MainThreadStall", message: `${report.bucket} after ${report.section}` },
|
|
142
|
+
{ kind: "plugin", plugin: "stall-monitor", pane: details },
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function loadState(current: StallReporterHost): StallReportState {
|
|
147
|
+
try {
|
|
148
|
+
const state = current.load();
|
|
149
|
+
return { sent: { ...state.sent }, pending: { ...state.pending } };
|
|
150
|
+
} catch (error) {
|
|
151
|
+
logFailure("telemetry-stalls", "Could not read stall report state")(error);
|
|
152
|
+
return { sent: {}, pending: {} };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function prune(state: StallReportState, now: number): void {
|
|
157
|
+
for (const [key, at] of Object.entries(state.sent)) {
|
|
158
|
+
if (!(now - at < DAY_MS) || at > now) delete state.sent[key];
|
|
159
|
+
}
|
|
160
|
+
const kinds = Object.keys(state.pending);
|
|
161
|
+
for (const key of kinds.slice(0, Math.max(0, kinds.length - MAX_KINDS_KEPT))) delete state.pending[key];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Records one stall of `process`. Does nothing where no host is installed; never throws. */
|
|
165
|
+
export function recordStall(process: StallProcess, event: StallEvent): void {
|
|
166
|
+
const current = host;
|
|
167
|
+
if (!current || !(event.blockedMs > 0)) return;
|
|
168
|
+
try {
|
|
169
|
+
const now = current.now?.() ?? Date.now();
|
|
170
|
+
current.appendLog(formatStallLogLine(process, event, now));
|
|
171
|
+
if (event.blockedMs < STALL_REPORT_MIN_MS) return;
|
|
172
|
+
const appVersion = (current.appVersion ?? VERSION).trim().replace(/^v/, "");
|
|
173
|
+
const section = stallSectionName(event.lastMeasured);
|
|
174
|
+
const bucket = stallBucket(event.blockedMs);
|
|
175
|
+
const version = VERSION_PATTERN.test(appVersion) ? appVersion : "unknown";
|
|
176
|
+
const key = `${process}|${bucket}|${section}|${version}`;
|
|
177
|
+
const state = loadState(current);
|
|
178
|
+
prune(state, now);
|
|
179
|
+
const pending = state.pending[key] ?? { count: 0, longestMs: 0 };
|
|
180
|
+
pending.count += 1;
|
|
181
|
+
pending.longestMs = Math.max(pending.longestMs, Math.round(event.blockedMs / 100) * 100);
|
|
182
|
+
if (state.sent[key] !== undefined) {
|
|
183
|
+
state.pending[key] = pending;
|
|
184
|
+
} else {
|
|
185
|
+
delete state.pending[key];
|
|
186
|
+
state.sent[key] = now;
|
|
187
|
+
(current.send ?? sendThroughCrashReporter)({
|
|
188
|
+
process,
|
|
189
|
+
bucket,
|
|
190
|
+
section,
|
|
191
|
+
appVersion: version,
|
|
192
|
+
platform: reportPlatform(current.platform),
|
|
193
|
+
arch: reportArch(current.arch),
|
|
194
|
+
count: pending.count,
|
|
195
|
+
longestMs: pending.longestMs,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
current.save(state);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
logFailure("telemetry-stalls", "Could not record a stall")(error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function installStallReporter(next: StallReporterHost): () => void {
|
|
205
|
+
host = next;
|
|
206
|
+
return () => {
|
|
207
|
+
if (host === next) host = null;
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Test seam. */
|
|
212
|
+
export function resetStallReporterForTests(): void {
|
|
213
|
+
host = null;
|
|
214
|
+
}
|
|
@@ -97,7 +97,6 @@ let officialIds: ReadonlySet<string> | null = null;
|
|
|
97
97
|
let officialLookup: Promise<void> | null = null;
|
|
98
98
|
let automationDepth = 0;
|
|
99
99
|
const builtinPlugins = new WeakSet<object>();
|
|
100
|
-
const openListeners = new Set<() => void>();
|
|
101
100
|
|
|
102
101
|
/** Reads the config switch and the environment. Absent means on. */
|
|
103
102
|
export function usageCountsEnabled(
|
|
@@ -197,27 +196,10 @@ export function automationActive(): boolean {
|
|
|
197
196
|
return automationDepth > 0;
|
|
198
197
|
}
|
|
199
198
|
|
|
200
|
-
/**
|
|
201
|
-
* Calls `listener` each time the user opens a function, with nothing about
|
|
202
|
-
* which one. The web terminal's first-screen measure uses it; automation never
|
|
203
|
-
* reaches it.
|
|
204
|
-
*/
|
|
205
|
-
export function subscribeFunctionOpens(listener: () => void): () => void {
|
|
206
|
-
openListeners.add(listener);
|
|
207
|
-
return () => {
|
|
208
|
-
openListeners.delete(listener);
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
199
|
/** Counts one open of a function by the user. Never throws. */
|
|
213
200
|
export function recordFunctionOpen(fn: UsageFunction): void {
|
|
214
201
|
try {
|
|
215
202
|
if (automationDepth > 0) return;
|
|
216
|
-
for (const listener of openListeners) {
|
|
217
|
-
try { listener(); } catch (error) {
|
|
218
|
-
logFailure("telemetry-usage-counts", "Could not notify function-open listener")(error);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
203
|
if (host && !host.isEnabled()) return;
|
|
222
204
|
count(fn, "opened");
|
|
223
205
|
scheduleFlush();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { beforeEach } from "bun:test";
|
|
2
|
+
import { resetCrashReporterForTests } from "../telemetry/crash-reports";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `bun test` runs every file in one process, so module state outlives the file
|
|
6
|
+
* that set it. A crash report queued while no reporter is installed, such as a
|
|
7
|
+
* plugin that fails to register in an app runtime test, would wait in the
|
|
8
|
+
* queue and go out with the next reporter another file installs, and its key
|
|
9
|
+
* would keep a later identical report from being queued at all. Every test
|
|
10
|
+
* starts with no reporter, an empty queue and nothing seen.
|
|
11
|
+
*/
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
resetCrashReporterForTests();
|
|
14
|
+
});
|
package/src/theme/colors.ts
CHANGED
|
@@ -291,6 +291,14 @@ export function priceColor(value: number, palette: ThemeColors = colors): string
|
|
|
291
291
|
return palette.neutral;
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
/**
|
|
295
|
+
* `priceColor` for a figure as it is printed, to `digits` decimals: a move that
|
|
296
|
+
* rounds to zero (0.00%, never -0.00%) is neutral, not red or green.
|
|
297
|
+
*/
|
|
298
|
+
export function shownPriceColor(value: number, digits: number, palette: ThemeColors = colors): string {
|
|
299
|
+
return priceColor(Number(value.toFixed(digits)), palette);
|
|
300
|
+
}
|
|
301
|
+
|
|
294
302
|
const TONE_TINT = 0.18;
|
|
295
303
|
|
|
296
304
|
/**
|
package/src/theme/themes.ts
CHANGED
|
@@ -745,6 +745,16 @@ const THEME_TRAITS: Readonly<Record<string, readonly ThemeTrait[]>> = {
|
|
|
745
745
|
"high-contrast": ["high-contrast", "colorblind-safe"],
|
|
746
746
|
};
|
|
747
747
|
|
|
748
|
+
/**
|
|
749
|
+
* Whether a theme's gains and losses are told apart by something other than
|
|
750
|
+
* the red and green everyone reads: a color-blind safe theme's blue and
|
|
751
|
+
* vermillion, which a chart keys (`▲▼` beside a candle series) so a reader
|
|
752
|
+
* knows which is which.
|
|
753
|
+
*/
|
|
754
|
+
export function themeKeysDirection(id: string): boolean {
|
|
755
|
+
return (THEME_TRAITS[id] ?? []).includes("colorblind-safe");
|
|
756
|
+
}
|
|
757
|
+
|
|
748
758
|
export interface ThemeSummary {
|
|
749
759
|
id: string;
|
|
750
760
|
name: string;
|