gloomberb 0.19.2 → 0.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +108 -1
- package/src/account/upgrade-dialog.tsx +4 -23
- package/src/api-client/coalesce.ts +118 -0
- package/src/api-client/credit-boards.ts +13 -0
- package/src/api-client/crypto-etf-flows.ts +6 -0
- package/src/api-client/crypto-markets.ts +40 -0
- package/src/api-client/data.ts +201 -14
- package/src/api-client/equity-screener.ts +10 -0
- package/src/api-client/errors.ts +20 -0
- package/src/api-client/gpu.ts +29 -0
- package/src/api-client/index.ts +5 -2
- package/src/api-client/plan-access.ts +10 -1
- package/src/api-client/plan-rules.ts +35 -0
- package/src/api-client/research-activity.ts +1 -32
- package/src/api-client/types.ts +21 -1
- package/src/app/attention-notice.tsx +94 -0
- package/src/app.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +2 -1
- package/src/capabilities/factories.ts +1 -0
- package/src/cli/ascii-output.ts +42 -0
- package/src/cli/base-converter.ts +10 -2
- package/src/cli/command-options.ts +16 -1
- package/src/cli/commands/crypto-hints.ts +23 -4
- package/src/cli/commands/market.ts +92 -23
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +9 -4
- package/src/cli/commands/search.ts +71 -12
- package/src/cli/commands/system.ts +22 -30
- package/src/cli/commands/ticker.ts +49 -9
- package/src/cli/commands/version.ts +33 -0
- package/src/cli/context.ts +5 -9
- package/src/cli/data-dir.ts +7 -0
- package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
- package/src/cli/desktop-pane-shot-browser.ts +320 -0
- package/src/cli/desktop-pane-shot-routes.ts +26 -1
- package/src/cli/desktop-pane-shot.ts +196 -256
- package/src/cli/early-command.ts +42 -0
- package/src/cli/earnings-calendar.ts +9 -0
- package/src/cli/entry.ts +19 -13
- package/src/cli/errors.ts +12 -2
- package/src/cli/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +154 -4
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +40 -5
- package/src/cli/listing-arg.ts +46 -9
- package/src/cli/options.ts +1 -1
- package/src/cli/output-file.ts +60 -13
- package/src/cli/pane-functions/catalog.ts +14 -3
- package/src/cli/pane-functions/data.ts +1 -1
- package/src/cli/pane-functions/dom.ts +45 -0
- package/src/cli/pane-functions/freshness.ts +46 -9
- package/src/cli/pane-functions/function-help.ts +17 -9
- package/src/cli/pane-functions/headless.ts +22 -14
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +6 -2
- package/src/cli/pane-functions/resolver.ts +27 -5
- package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
- package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
- package/src/cli/pane-functions/screenshot-http.ts +70 -0
- package/src/cli/pane-functions/screenshot-render.ts +19 -5
- package/src/cli/quote-answer.ts +20 -0
- package/src/cli/registry.ts +41 -13
- package/src/cli/result.ts +12 -6
- package/src/cli/ticker-resolution.ts +2 -1
- package/src/cli/types.ts +2 -0
- package/src/components/chart/composite/format.ts +3 -0
- package/src/components/chart-table/header.tsx +1 -1
- package/src/components/chart-table/layout.ts +28 -11
- package/src/components/command-bar/action-runtime.ts +4 -0
- package/src/components/command-bar/commands/direct/index.ts +18 -33
- package/src/components/command-bar/commands/direct/runtime.ts +5 -0
- package/src/components/command-bar/commands/registry.ts +10 -1
- package/src/components/command-bar/routes/root/command-items.ts +9 -4
- package/src/components/command-bar/routes/root/runtime.ts +24 -1
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/header.tsx +8 -3
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +7 -2
- package/src/components/layout/pane/footer/model.ts +40 -5
- package/src/components/market-board/index.tsx +3 -2
- package/src/components/onboarding/desks-step.tsx +12 -4
- package/src/components/onboarding/first-run-workspace.ts +81 -4
- package/src/components/onboarding/onboarding-frame.tsx +2 -1
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +54 -23
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
- package/src/components/onboarding/run-setup-again.ts +24 -0
- package/src/components/onboarding/use-onboarding-progress.ts +33 -15
- package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
- package/src/components/onboarding/wizard-model.ts +57 -3
- package/src/components/onboarding/wizard-positions.ts +1 -1
- package/src/components/ui/delayed-mark.tsx +56 -0
- package/src/components/ui/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +8 -1
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +34 -31
- package/src/data/sqlite/access.ts +19 -8
- package/src/data/sqlite/checkpointer.ts +95 -8
- package/src/data/sqlite/database.ts +40 -7
- package/src/docs/snapshot.json +191 -78
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -2
- package/src/layout/desk-keys.ts +4 -1
- package/src/layout/desks.ts +38 -5
- package/src/layout-marketplace/gallery-desktop.tsx +45 -16
- package/src/market-data/batched-reads.ts +16 -0
- package/src/market-data/coordinator/auxiliary.ts +4 -2
- package/src/market-data/coordinator/index.ts +11 -2
- package/src/market-data/futures-board.ts +1 -1
- package/src/market-data/futures-contract.ts +3 -1
- package/src/market-data/market/index-level.ts +6 -4
- package/src/market-data/published-apac-sessions.ts +6 -0
- package/src/news/aggregator.ts +7 -4
- package/src/news/news-model.ts +11 -0
- package/src/news/story-id.ts +22 -0
- package/src/news/ticker-symbols.ts +17 -1
- package/src/notifications/app-notifier.ts +35 -14
- package/src/plugins/builtin/account-management/form-components.tsx +1 -1
- package/src/plugins/builtin/account-management/index.tsx +2 -3
- package/src/plugins/builtin/account-management/model.ts +1 -1
- package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
- package/src/plugins/builtin/alerts/events.ts +10 -2
- package/src/plugins/builtin/alerts/index.tsx +5 -5
- package/src/plugins/builtin/alerts/research-builder.ts +5 -1
- package/src/plugins/builtin/alerts/research-rules.ts +18 -0
- package/src/plugins/builtin/alerts/targets.ts +4 -1
- package/src/plugins/builtin/attention/headless.ts +1 -1
- package/src/plugins/builtin/attention/index.tsx +2 -4
- package/src/plugins/builtin/attention/listing.ts +1 -1
- package/src/plugins/builtin/attention/series.ts +1 -1
- package/src/plugins/builtin/awards/index.tsx +1 -2
- package/src/plugins/builtin/backtest/client.ts +1 -0
- package/src/plugins/builtin/backtest/index.tsx +1 -2
- package/src/plugins/builtin/backtest/rules.ts +3 -1
- package/src/plugins/builtin/backtest/window.ts +8 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
- package/src/plugins/builtin/broker-manager/index.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +5 -0
- package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
- package/src/plugins/builtin/buildout/company/id.ts +2 -0
- package/src/plugins/builtin/buildout/company/model.ts +247 -0
- package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
- package/src/plugins/builtin/buildout/facilities.ts +309 -0
- package/src/plugins/builtin/buildout/format.ts +9 -0
- package/src/plugins/builtin/buildout/index.tsx +51 -5
- package/src/plugins/builtin/buildout/model/index.ts +62 -10
- package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
- package/src/plugins/builtin/buildout/model/types.ts +11 -2
- package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
- package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
- package/src/plugins/builtin/buildout/table-model.ts +26 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
- package/src/plugins/builtin/cds/headless.ts +10 -2
- package/src/plugins/builtin/cds/model.ts +7 -3
- package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
- package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
- package/src/plugins/builtin/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -2
- package/src/plugins/builtin/chart-composer/headless.ts +88 -21
- package/src/plugins/builtin/chart-composer/index.tsx +2 -3
- package/src/plugins/builtin/chart-composer/pane.tsx +26 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/controller/index.ts +1 -1
- package/src/plugins/builtin/chat/controller/messages.ts +15 -4
- package/src/plugins/builtin/chat/ghost-user.ts +8 -0
- package/src/plugins/builtin/chat/index.tsx +5 -5
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/index.tsx +1 -2
- package/src/plugins/builtin/cloud/plugin.tsx +6 -4
- package/src/plugins/builtin/cloud/team/module.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
- package/src/plugins/builtin/comment-letters/index.tsx +1 -2
- package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
- package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
- package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
- package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
- package/src/plugins/builtin/congress-trades/headless.ts +2 -0
- package/src/plugins/builtin/congress-trades/model.ts +92 -64
- package/src/plugins/builtin/congress-trades/table.tsx +14 -2
- package/src/plugins/builtin/connections/index.ts +2 -3
- package/src/plugins/builtin/cot/index.tsx +1 -2
- package/src/plugins/builtin/cpi/index.tsx +1 -2
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/client.ts +7 -2
- package/src/plugins/builtin/credit-boards/headless.ts +25 -12
- package/src/plugins/builtin/credit-boards/model.ts +29 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- package/src/plugins/builtin/crypto-board/headless.ts +4 -3
- package/src/plugins/builtin/crypto-board/index.tsx +4 -2
- package/src/plugins/builtin/crypto-board/series.ts +2 -0
- package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
- package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
- package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
- package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
- package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
- package/src/plugins/builtin/debt-maturities/model.ts +17 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
- package/src/plugins/builtin/debug/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/client.ts +32 -2
- package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
- package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
- package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
- package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
- package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
- package/src/plugins/builtin/dividend-yield/view.ts +21 -0
- package/src/plugins/builtin/docs/index.tsx +2 -3
- package/src/plugins/builtin/doe/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
- package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
- package/src/plugins/builtin/econ/calendar-model.ts +29 -6
- package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +1 -0
- package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
- package/src/plugins/builtin/equity-screener/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/model.ts +28 -4
- package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
- package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
- package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
- package/src/plugins/builtin/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/cli.ts +44 -0
- package/src/plugins/builtin/fear-greed/index.tsx +5 -53
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
- package/src/plugins/builtin/fixed-income/index.tsx +1 -2
- package/src/plugins/builtin/futures/board-reads.ts +25 -0
- package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
- package/src/plugins/builtin/futures/index.tsx +15 -0
- package/src/plugins/builtin/futures/model.ts +14 -0
- package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
- package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
- package/src/plugins/builtin/futures-curve/headless.ts +6 -1
- package/src/plugins/builtin/futures-curve/index.tsx +1 -2
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +51 -12
- package/src/plugins/builtin/gpu/client.ts +15 -2
- package/src/plugins/builtin/gpu/equities.tsx +5 -2
- package/src/plugins/builtin/gpu/headless.ts +14 -2
- package/src/plugins/builtin/gpu/index.tsx +1 -2
- package/src/plugins/builtin/gpu/model.ts +20 -2
- package/src/plugins/builtin/gpu/pane.tsx +1 -1
- package/src/plugins/builtin/help/index.tsx +1 -1
- package/src/plugins/builtin/holders/index-holders.ts +24 -12
- package/src/plugins/builtin/holders/index.tsx +2 -3
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
- package/src/plugins/builtin/iv-history/index.tsx +1 -2
- package/src/plugins/builtin/jodi/index.tsx +1 -2
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
- package/src/plugins/builtin/layout-manager/index.tsx +9 -8
- package/src/plugins/builtin/macro-day/pane.tsx +4 -2
- package/src/plugins/builtin/market-halts/index.tsx +1 -2
- package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
- package/src/plugins/builtin/market-movers/index.tsx +7 -1
- package/src/plugins/builtin/market-movers/session-body.tsx +4 -1
- package/src/plugins/builtin/market-valuation/index.tsx +1 -2
- package/src/plugins/builtin/members/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/pane.tsx +2 -1
- package/src/plugins/builtin/news/headless.ts +7 -0
- package/src/plugins/builtin/news/wire/index.tsx +1 -1
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
- package/src/plugins/builtin/notes/index.tsx +3 -6
- package/src/plugins/builtin/openfda/index.tsx +1 -2
- package/src/plugins/builtin/options/view.tsx +6 -5
- package/src/plugins/builtin/options-calculator/index.tsx +1 -2
- package/src/plugins/builtin/options-positioning/index.tsx +1 -2
- package/src/plugins/builtin/options-scenario/index.tsx +2 -4
- package/src/plugins/builtin/perps/index.tsx +1 -2
- package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
- package/src/plugins/builtin/plugin-module.ts +16 -2
- package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
- package/src/plugins/builtin/power/index.tsx +1 -2
- package/src/plugins/builtin/rate-path/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/model.ts +16 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
- package/src/plugins/builtin/research/analyst-headless.ts +33 -6
- package/src/plugins/builtin/research/analyst-model.ts +37 -2
- package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
- package/src/plugins/builtin/research/index.tsx +5 -8
- package/src/plugins/builtin/research-search/index.tsx +1 -2
- package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
- package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
- package/src/plugins/builtin/roadmap/index.tsx +2 -3
- package/src/plugins/builtin/roadmap/pane.tsx +49 -2
- package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
- package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
- package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
- package/src/plugins/builtin/sec/filing-content.ts +38 -6
- package/src/plugins/builtin/sec/headless.ts +5 -1
- package/src/plugins/builtin/sec/index.tsx +4 -2
- package/src/plugins/builtin/sec/model.ts +2 -0
- package/src/plugins/builtin/sectors/index.tsx +11 -5
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +60 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
- package/src/plugins/builtin/shared/forming-bars.ts +29 -0
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
- package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/pane.tsx +2 -1
- package/src/plugins/builtin/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- package/src/plugins/builtin/supply-chain/client.ts +22 -1
- package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
- package/src/plugins/builtin/supply-chain/index.tsx +1 -2
- package/src/plugins/builtin/thirteenf/api.ts +116 -13
- package/src/plugins/builtin/thirteenf/data.ts +132 -30
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +20 -11
- package/src/plugins/builtin/thirteenf/types.ts +12 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +17 -4
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/headless.ts +11 -3
- package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
- package/src/plugins/builtin/time-sales/index.tsx +1 -2
- package/src/plugins/builtin/time-sales/pane.tsx +3 -1
- package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
- package/src/plugins/builtin/trace-bonds/model.ts +44 -10
- package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
- package/src/plugins/builtin/treasury-auctions/pane.tsx +2 -2
- package/src/plugins/builtin/vol-surface/client.ts +18 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -2
- package/src/plugins/builtin/volatility/index.tsx +1 -2
- package/src/plugins/builtin/world-indices/footer.ts +6 -1
- package/src/plugins/builtin/world-venue-map/client.ts +10 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -2
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
- package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
- package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
- package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
- package/src/plugins/builtin/yield-curve/curves.ts +16 -2
- package/src/plugins/builtin/yield-curve/headless.ts +3 -3
- package/src/plugins/bundle.ts +52 -2
- package/src/plugins/catalog-browser.ts +2 -2
- package/src/plugins/catalog-cli.ts +20 -0
- package/src/plugins/host-link.ts +13 -1
- package/src/plugins/installer.ts +15 -4
- package/src/plugins/loader.ts +43 -11
- package/src/plugins/peer-imports.ts +201 -0
- package/src/plugins/peer-retry.ts +26 -29
- package/src/plugins/plugin-process.ts +128 -0
- package/src/plugins/redirection-guard.ts +45 -0
- package/src/remote/endpoint.ts +16 -3
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/http-proxy.ts +7 -123
- package/src/renderers/dom/desktop/icons.tsx +2 -0
- package/src/renderers/dom/host/image-surface.tsx +46 -21
- package/src/renderers/dom/styles.css +7 -0
- package/src/renderers/dom/toast-host.tsx +2 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +3 -2
- package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
- package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
- package/src/renderers/electrobun/bun/window/create.ts +4 -2
- package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
- package/src/renderers/electrobun/view/main.tsx +7 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/renderers/opentui/browser-open.ts +10 -4
- package/src/renderers/opentui/image/surface.tsx +1 -1
- package/src/renderers/opentui/start.tsx +12 -6
- package/src/renderers/opentui/toast-host.tsx +2 -1
- package/src/series/registry.ts +18 -0
- package/src/series/standalone.ts +10 -1
- package/src/series/types.ts +7 -0
- package/src/series/validate.ts +11 -2
- package/src/sources/gloomberb-cloud/index.ts +28 -5
- package/src/sources/gloomberb-cloud/news.ts +24 -3
- package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
- package/src/sources/provider-errors.ts +26 -3
- package/src/sources/provider-router/history.ts +5 -0
- package/src/sources/provider-router/index.ts +13 -0
- package/src/sync/core-contributors.ts +32 -0
- package/src/telemetry/attention-counts.ts +64 -6
- package/src/telemetry/attention-setting.ts +33 -0
- package/src/telemetry/crash-reports-node.ts +3 -1
- package/src/telemetry/stall-clocks.ts +93 -0
- package/src/telemetry/stall-reports-node.ts +12 -7
- package/src/telemetry/stall-reports.ts +34 -5
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name.ts +19 -0
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +13 -2
- package/src/tickers/search/index.ts +4 -1
- package/src/tickers/search/types.ts +6 -1
- package/src/time-series/bar-aggregation.ts +7 -1
- package/src/time-series/base-series.ts +3 -3
- package/src/time-series/chart-bounds.ts +1 -1
- package/src/time-series/chart-data.ts +5 -2
- package/src/time-series/chart-seed.ts +1 -1
- package/src/time-series/coin-session-close.ts +211 -0
- package/src/time-series/live-bars.ts +13 -2
- package/src/time-series/live-quotes.ts +1 -1
- package/src/time-series/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +55 -6
- package/src/time-series/resolve.ts +12 -3
- package/src/time-series/series-presentation.ts +1 -1
- package/src/time-series/studies/formula.ts +205 -0
- package/src/time-series/studies/indicators.ts +299 -0
- package/src/time-series/studies/leverage.ts +75 -0
- package/src/time-series/studies/pair.ts +138 -0
- package/src/time-series/studies/resolve.ts +333 -0
- package/src/time-series/studies/shared.ts +141 -0
- package/src/time-series/studies/units.ts +243 -0
- package/src/time-series/studies/vwap.ts +274 -0
- package/src/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +22 -0
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +11 -0
- package/src/ui/toast.tsx +26 -1
- package/src/utils/ascii-text.ts +140 -0
- package/src/utils/cli-output.ts +91 -18
- package/src/utils/concurrency.ts +24 -0
- package/src/utils/exchanges.ts +6 -0
- package/src/utils/external-url.ts +20 -7
- package/src/utils/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/main-thread-stall.ts +75 -3
- package/src/utils/perf-marks.ts +36 -2
- package/src/utils/private-files.ts +141 -0
- package/src/utils/public-address.ts +99 -0
- package/src/account/upgrade-personalization.ts +0 -57
- package/src/account/upgrade-tickers.ts +0 -124
- package/src/api-client/upgrade-experiment.ts +0 -91
- package/src/plugins/builtin/cloud/browser.tsx +0 -3
- package/src/renderers/electrobun/launcher/preload.ts +0 -13
- package/src/renderers/electrobun/launcher/windows-emulation.ts +0 -50
- package/src/time-series/studies.ts +0 -1628
|
@@ -31,9 +31,12 @@ import type { TickerFinancials, PricePoint, Quote } from "../../../types/financi
|
|
|
31
31
|
import { chartQuoteOverrideKeyForTarget, getLiveChartQuoteTargets } from "../../../time-series/live-quotes";
|
|
32
32
|
import { createChartSeriesResolver } from "../../../capabilities";
|
|
33
33
|
import { loadFuturesContractTerms, loadFuturesPriceUnit } from "../../../market-data/futures-contract";
|
|
34
|
-
import { isUsListingExchange, parsePublicTickerKey, publicTickerKey } from "../../../utils/exchanges";
|
|
34
|
+
import { canonicalExchange, isKnownExchangeCode, isUsListingExchange, parsePublicTickerKey, publicTickerKey } from "../../../utils/exchanges";
|
|
35
35
|
import { zonedDateTimeParts } from "../../../utils/zoned-date-time";
|
|
36
36
|
import { futuresSessionOpenMinute } from "../../../market-data/market/trading-sessions";
|
|
37
|
+
import { isAlwaysOpenExchange, isRegularSessionTime } from "../../../market-data/market/freshness";
|
|
38
|
+
import { isRoundTheClockCoin } from "../../../utils/crypto-pair";
|
|
39
|
+
import { isCurrencyPairListing } from "../../../utils/fx-market-hours";
|
|
37
40
|
import { cleanFloat32Price } from "../../../cli/history-rows";
|
|
38
41
|
import { parseChartSpec } from "./chart-spec";
|
|
39
42
|
import { paneSchemas } from "./headless-schema";
|
|
@@ -46,7 +49,8 @@ import { loadFundFacts } from "../sec/fund-facts";
|
|
|
46
49
|
import { comparedFundTickers, loadDailyObjectives, mayDrawDailyObjective, withLeverageOverlay } from "./leverage-overlay";
|
|
47
50
|
import { classifyInstrumentType } from "../../../tickers/instrument-kind";
|
|
48
51
|
import { priceComparisonEndsIntraday, type PriceComparison } from "../../../time-series/price-comparison";
|
|
49
|
-
import { LATEST_INTRADAY_TEXT } from "../../../market-data/market/period-return";
|
|
52
|
+
import { LATEST_INTRADAY_TEXT, periodDayText } from "../../../market-data/market/period-return";
|
|
53
|
+
import { CLOSING_PRINT_PENDING_TEXT, newestClosedSession, newestSessionClose, withOfficialCloses } from "../../../time-series/official-close";
|
|
50
54
|
|
|
51
55
|
export interface ChartPaneModel extends HeadlessSeriesResult {
|
|
52
56
|
/** The renderer consumes the full model; generic reports project series and metadata. */
|
|
@@ -368,10 +372,19 @@ function chartFreshness(
|
|
|
368
372
|
const coarsest = resolutions.reduce((worst, resolution) => (
|
|
369
373
|
order.indexOf(resolution!) > order.indexOf(worst!) ? resolution : worst
|
|
370
374
|
), resolutions[0]);
|
|
375
|
+
const closedDay = closedPriceBarDay(series);
|
|
376
|
+
// A window that ended before today (`--to 2026-10-23`) is the history asked for, which no later close makes stale:
|
|
377
|
+
// `daily closes through Fri 23 Oct`.
|
|
378
|
+
const windowEnd = spec.viewport.dateWindow?.end;
|
|
379
|
+
if (windowEnd && /^\d{4}-\d{2}-\d{2}$/.test(windowEnd) && windowEnd < new Date().toISOString().slice(0, 10)) {
|
|
380
|
+
const through = closedDay ?? series.flatMap((entry) => entry.points.at(-1)?.date ?? []).map((date) => new Date(date).toISOString().slice(0, 10)).sort().at(-1);
|
|
381
|
+
const basis = coarsest === "1d" ? "daily closes" : coarsest === "1wk" ? "weekly closes" : coarsest === "1mo" ? "monthly closes" : "intraday bars";
|
|
382
|
+
return { status: "not-a-feed", basis: through ? `${basis} through ${periodDayText(through, new Date().getUTCFullYear())}` : basis,
|
|
383
|
+
...(through && closedDay ? { asOf: through } : {}), oldest: null };
|
|
384
|
+
}
|
|
371
385
|
const forming = formingPriceBar(series);
|
|
372
386
|
// A history: its first bars are not stale data, so the line dates it by the bar still forming alone.
|
|
373
387
|
if (forming) return { ...barHistoryFreshness(coarsest), periodToDate: forming, oldest: null };
|
|
374
|
-
const closedDay = closedPriceBarDay(series);
|
|
375
388
|
// A comparison whose newest point is a session still trading is not a day's closes.
|
|
376
389
|
const basis = coarsest === "1d" && priceComparisonEndsIntraday(comparison) ? { basis: LATEST_INTRADAY_TEXT } : {};
|
|
377
390
|
// Daily bars are dated by the time of the newest point: a closed session's day, or when a session still trading was last quoted.
|
|
@@ -600,30 +613,34 @@ export function argumentWithListing(argument: string, symbol: string, key: strin
|
|
|
600
613
|
}
|
|
601
614
|
|
|
602
615
|
/**
|
|
603
|
-
* A
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
616
|
+
* A catalog series typed where a ticker goes, as its chart: a FRED series id
|
|
617
|
+
* (`fn G DFII10` is FRED:DFII10), or a word a series lists as an alias
|
|
618
|
+
* (`fn G hashrate` is CAP:bitcoin:hashrate), when the catalog lists it, no
|
|
619
|
+
* listing answers to it, and the quote source says it is not a ticker. A
|
|
620
|
+
* real ticker of the same letters, read as a listing or quoted, still charts
|
|
621
|
+
* as the ticker; null then, and for any other symbol.
|
|
608
622
|
*/
|
|
609
|
-
export async function
|
|
623
|
+
export async function bareCatalogSeries(
|
|
610
624
|
series: ChartSeriesSpec,
|
|
611
625
|
readListing: () => Promise<unknown>,
|
|
612
|
-
context: Pick<HeadlessPaneContext, "marketData" | "catalogSeriesName">,
|
|
626
|
+
context: Pick<HeadlessPaneContext, "marketData" | "catalogSeriesName" | "catalogSeriesNamed">,
|
|
613
627
|
): Promise<ChartSeriesSpec | null> {
|
|
614
628
|
const { source } = series;
|
|
615
629
|
if (source.kind !== "security" || source.instrument.exchange || source.instrument.instrument || source.fieldId !== CHART_FIELD_IDS.price) return null;
|
|
616
630
|
const id = source.instrument.symbol.trim().toUpperCase();
|
|
617
631
|
const expression = `FRED:${id}`;
|
|
618
|
-
const
|
|
632
|
+
const fredLabel = /^[A-Z0-9]+$/.test(id)
|
|
619
633
|
? context.catalogSeriesName?.(expression) ?? getSharedRegistry()?.series?.findByExpression(expression)?.definition.name
|
|
620
634
|
: undefined;
|
|
635
|
+
const named = fredLabel ? undefined : context.catalogSeriesNamed?.(id);
|
|
636
|
+
const parsed = named ? parseSeriesExpression(named.expression) : null;
|
|
637
|
+
const label = fredLabel ?? (parsed ? named?.name : undefined);
|
|
621
638
|
if (!label || await readListing()) return null;
|
|
622
639
|
const failure = await Promise.resolve().then(() => context.marketData.getQuote(id, ""))
|
|
623
640
|
.then((quote) => quote ? null : `Not a ticker: ${id}.`, (error: unknown) => error instanceof Error ? error.message : String(error));
|
|
624
641
|
if (!failure || !notATickerSymbol(failure)) return null;
|
|
625
|
-
const
|
|
626
|
-
return { ...
|
|
642
|
+
const spec = parsed ? buildSeriesSpec({ ...parsed, label }, 0) : buildSeriesSpec({ kind: "economic", provider: "fred", seriesId: id, label }, 0);
|
|
643
|
+
return { ...spec, id: series.id, transform: series.transform, axis: series.axis, panelId: series.panelId };
|
|
627
644
|
}
|
|
628
645
|
|
|
629
646
|
/**
|
|
@@ -646,14 +663,28 @@ async function possibleFundTickers(spec: ChartSpec, context: HeadlessPaneContext
|
|
|
646
663
|
});
|
|
647
664
|
}
|
|
648
665
|
|
|
666
|
+
/**
|
|
667
|
+
* `--exchange TSX`: every ticker series that names no venue, on that one, as
|
|
668
|
+
* `BDI:TSX` would be. A code the app does not know fails before any request.
|
|
669
|
+
*/
|
|
670
|
+
function onExchange(spec: ChartSpec, exchange: string): ChartSpec {
|
|
671
|
+
const code = exchange.trim().toUpperCase();
|
|
672
|
+
if (!isKnownExchangeCode(code)) throw new Error(`Unknown exchange "${exchange.trim()}". Write a venue code such as TSX, LSE or XETRA, or name it in the series: BDI:TSX.`);
|
|
673
|
+
return { ...spec, series: spec.series.map((series) => series.source.kind !== "security" || series.source.instrument.exchange || series.source.instrument.instrument
|
|
674
|
+
? series
|
|
675
|
+
: { ...series, source: { ...series.source, instrument: { ...series.source.instrument, exchange: canonicalExchange(code) } } }) };
|
|
676
|
+
}
|
|
677
|
+
|
|
649
678
|
export function chartHeadless(template: keyof typeof paneSchemas): HeadlessPaneDefinition<"series"> {
|
|
650
679
|
return {
|
|
651
680
|
...paneSchemas[template],
|
|
652
681
|
shape: "series",
|
|
653
682
|
async load(args, context) {
|
|
654
683
|
const intradayHistories: ChartPaneModel["snapshot"]["intradayHistories"] = [];
|
|
655
|
-
const
|
|
656
|
-
|
|
684
|
+
const closingPrintsPending: string[] = [];
|
|
685
|
+
const stored = parseChartSpec(context.settings?.chartSpec);
|
|
686
|
+
if (!stored) throw new Error("The chart specification is invalid.");
|
|
687
|
+
const saved = typeof args.options.exchange === "string" ? onExchange(stored, args.options.exchange) : stored;
|
|
657
688
|
// A leveraged or inverse fund compared with anything draws its daily objective beside it.
|
|
658
689
|
const parsed = template === "comparison-chart-pane" && mayDrawDailyObjective(saved)
|
|
659
690
|
? withLeverageOverlay(saved, await loadDailyObjectives(
|
|
@@ -680,9 +711,9 @@ export function chartHeadless(template: keyof typeof paneSchemas): HeadlessPaneD
|
|
|
680
711
|
series: await Promise.all(parsed.series.map(async (series) => {
|
|
681
712
|
if (series.source.kind !== "security" || series.source.instrument.exchange) return series;
|
|
682
713
|
const { symbol } = series.source.instrument;
|
|
683
|
-
// A FRED id no ticker answers to
|
|
684
|
-
const
|
|
685
|
-
if (
|
|
714
|
+
// A FRED id (DFII10) or a series alias (hashrate) no ticker answers to is that series; an intraday chart has none.
|
|
715
|
+
const catalog = template === "graph-intraday-price-pane" ? null : await bareCatalogSeries(series, () => readListing(symbol), context);
|
|
716
|
+
if (catalog) return catalog;
|
|
686
717
|
if (!reads.has(symbol)) reads.set(symbol, readSeriesListing(symbol));
|
|
687
718
|
const instrument = await reads.get(symbol)!;
|
|
688
719
|
return instrument ? { ...series, source: { ...series.source, instrument: { ...series.source.instrument, ...instrument } } } : series;
|
|
@@ -709,6 +740,37 @@ export function chartHeadless(template: keyof typeof paneSchemas): HeadlessPaneD
|
|
|
709
740
|
...await loadIntradayWindow({ provider: context.marketData, symbol: target.symbol, exchange: target.exchange ?? "", request, context: { brokerId: target.brokerId, brokerInstanceId: target.brokerInstanceId, instrument: target.instrument } }),
|
|
710
741
|
})));
|
|
711
742
|
context.signal.throwIfAborted();
|
|
743
|
+
// Once a session has closed, its last bar ends on the official close (the closing auction's print), as
|
|
744
|
+
// `history` and CMP give it, not on the last trade before it; until the quote has that close, the footer says so.
|
|
745
|
+
await Promise.all(histories.map(async (history) => {
|
|
746
|
+
// A future settles rather than closing on an auction print, and a coin or a currency pair has no session
|
|
747
|
+
// to close: their bars stay as traded, and no quote is read for them.
|
|
748
|
+
if (!history.points.length || futuresSessionOpenMinute(history.symbol, history.exchange) != null
|
|
749
|
+
|| isRoundTheClockCoin(history.symbol, history.exchange) || isCurrencyPairListing(history.symbol, history.exchange)) return;
|
|
750
|
+
const now = Date.now();
|
|
751
|
+
const known = history.exchange || history.quote?.listingExchangeName || history.quote?.exchangeName || undefined;
|
|
752
|
+
if (known && isAlwaysOpenExchange(known)) return;
|
|
753
|
+
// While the session trades, no session of the chart has closed since its bars were read.
|
|
754
|
+
if (history.quote?.marketState === "REGULAR" || (known && isRegularSessionTime(known, now) === true)) return;
|
|
755
|
+
if (known && !newestClosedSession(history.points, { venue: known, now })) return;
|
|
756
|
+
// A captured shot's source can have no quotes: its bars are drawn as captured.
|
|
757
|
+
const quote = history.quote ?? (typeof context.marketData.getQuote === "function"
|
|
758
|
+
? await Promise.resolve(context.marketData.getQuote(history.symbol, history.exchange,
|
|
759
|
+
{ brokerId: history.target.brokerId, brokerInstanceId: history.target.brokerInstanceId, instrument: history.target.instrument }))
|
|
760
|
+
.catch(logFailure("chart-composer.headless", "Read the official close of an intraday chart failed", null))
|
|
761
|
+
: null);
|
|
762
|
+
const venue = known || quote?.listingExchangeName || quote?.exchangeName || undefined;
|
|
763
|
+
const newest = newestClosedSession(history.points, { venue, now });
|
|
764
|
+
const decided = newest ? newestSessionClose(quote, { ...newest, venue }) : null;
|
|
765
|
+
if (!newest || !decided) return;
|
|
766
|
+
if ("pending" in decided) {
|
|
767
|
+
closingPrintsPending.push(history.symbol);
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
const closes = new Map([[newest.date, decided.close]]);
|
|
771
|
+
history.points = withOfficialCloses(history.points, closes, { venue, now });
|
|
772
|
+
history.bufferedPoints = withOfficialCloses(history.bufferedPoints, closes, { venue, now });
|
|
773
|
+
}));
|
|
712
774
|
intradayHistories.push(...histories);
|
|
713
775
|
const starts = histories.flatMap(({ start }) => start ? [start.getTime()] : []);
|
|
714
776
|
const ends = histories.flatMap(({ end }) => end ? [end.getTime()] : []);
|
|
@@ -725,9 +787,11 @@ export function chartHeadless(template: keyof typeof paneSchemas): HeadlessPaneD
|
|
|
725
787
|
}, context.marketData) };
|
|
726
788
|
}
|
|
727
789
|
const model = await loadChartPaneModel(spec, context);
|
|
728
|
-
//
|
|
729
|
-
|
|
730
|
-
|
|
790
|
+
// What became of the company that had a bare symbol (GOGL), and a bare coin ticker charted as another listing
|
|
791
|
+
// (ETH, the Grayscale fund), are said as `quote` says them.
|
|
792
|
+
const readListings = await Promise.all(listings.values());
|
|
793
|
+
const listingNotes = [...readListings.flatMap((read) => read?.successorNote ?? []), ...readListings.flatMap((read) => read?.coinNote ?? [])];
|
|
794
|
+
if (listingNotes.length) model.warnings = [...model.warnings ?? [], ...listingNotes];
|
|
731
795
|
if (template === "graph-intraday-price-pane" && !intradayHistories.length && model.chart.viewport) {
|
|
732
796
|
const { start, end } = model.chart.viewport;
|
|
733
797
|
for (const { target, data } of [
|
|
@@ -782,6 +846,9 @@ export function chartHeadless(template: keyof typeof paneSchemas): HeadlessPaneD
|
|
|
782
846
|
model.metadata = { ...model.metadata, resolution: dropped.drop };
|
|
783
847
|
}
|
|
784
848
|
model.freshness = chartFreshness(model.series, model.spec, model.chart.priceComparison, chartQuoteTimes(model));
|
|
849
|
+
if (closingPrintsPending.length) {
|
|
850
|
+
model.freshness = { ...model.freshness, note: [model.freshness.note, CLOSING_PRINT_PENDING_TEXT].filter(Boolean).join(", ") };
|
|
851
|
+
}
|
|
785
852
|
const priceDomainFailures = intradayHistories.flatMap(({ symbol, exchange, priceDomainFailure }) =>
|
|
786
853
|
priceDomainFailure ? [{ symbol, exchange, ...priceDomainFailure }] : []);
|
|
787
854
|
if (priceDomainFailures.length) {
|
|
@@ -11,7 +11,6 @@ import { canonicalExchange, parsePublicTickerKey, publicTickerKey } from "../../
|
|
|
11
11
|
import { tickerInstrumentLabel } from "../../../tickers/instrument-label";
|
|
12
12
|
import type { ChartSpec } from "../../../time-series/types";
|
|
13
13
|
import { ChartComposerPane, ChartComposerResearchTab } from "./pane";
|
|
14
|
-
import { DataCatalogPane } from "./data-catalog-pane";
|
|
15
14
|
import { listFieldSeries } from "./field-series";
|
|
16
15
|
import { registeredSeriesName } from "./series-expression";
|
|
17
16
|
import {
|
|
@@ -404,7 +403,7 @@ const chartComposerTemplates: PaneTemplateDef[] = [
|
|
|
404
403
|
"positioning",
|
|
405
404
|
],
|
|
406
405
|
shortcut: { prefix: "CAT", argPlaceholder: "query", argKind: "text", argOptional: true },
|
|
407
|
-
reportFreshness: { status: "not-a-feed", basis: "series catalog" },
|
|
406
|
+
reportFreshness: { status: "not-a-feed", basis: "series catalog", local: true },
|
|
408
407
|
canCreate: () => true,
|
|
409
408
|
createInstance: (_context, options) => {
|
|
410
409
|
const query = options?.arg?.trim() ?? options?.values?.query?.trim() ?? "";
|
|
@@ -525,7 +524,7 @@ export const chartComposerModule: PluginModule = {
|
|
|
525
524
|
id: DATA_CATALOG_PANE_ID,
|
|
526
525
|
name: "Data Catalog",
|
|
527
526
|
icon: "C",
|
|
528
|
-
component: DataCatalogPane,
|
|
527
|
+
get component(): typeof import("./data-catalog-pane").DataCatalogPane { const { DataCatalogPane } = require("./data-catalog-pane"); return DataCatalogPane; },
|
|
529
528
|
defaultPosition: "right",
|
|
530
529
|
defaultMode: "floating",
|
|
531
530
|
defaultFloatingSize: { width: 110, height: 32 },
|
|
@@ -141,7 +141,10 @@ import { alertInstrument, syncAlertQuoteStream } from "../alerts/live";
|
|
|
141
141
|
import { getSharedMarketDataCoordinator } from "../../../market-data/coordinator";
|
|
142
142
|
import { useOptionChartRange } from "./option-range";
|
|
143
143
|
import { getActiveQuoteDisplay } from "../../../market-data/market/status";
|
|
144
|
-
import { resolveEntryData } from "../../../market-data/selectors";
|
|
144
|
+
import { buildQuoteKey, resolveEntryData } from "../../../market-data/selectors";
|
|
145
|
+
import { useQuoteEntries } from "../../../market-data/hooks";
|
|
146
|
+
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
147
|
+
import { useQuoteDelayFooter } from "../shared/cloud-upgrade";
|
|
145
148
|
import { editPriceLevels, parsePriceLevels, PRICE_LEVELS_KEY, priceLevelTickerKey } from "./price-levels";
|
|
146
149
|
import { canonicalExchange } from "../../../utils/exchanges";
|
|
147
150
|
import { resolveInstrumentForPane } from "../../../core/state/app/instrument";
|
|
@@ -218,6 +221,8 @@ interface ChartComposerSurfaceProps {
|
|
|
218
221
|
onCapture?: (capturing: boolean) => void;
|
|
219
222
|
/** The legend names each series' company; a ticker's own research tab already does. */
|
|
220
223
|
nameSeries?: boolean;
|
|
224
|
+
/** The footer's delayed clock; a ticker's research tab says it in its quote footer instead. */
|
|
225
|
+
delayFooter?: boolean;
|
|
221
226
|
}
|
|
222
227
|
|
|
223
228
|
const QUICK_ADD_CAPTURE = "quick-add";
|
|
@@ -307,6 +312,7 @@ function ChartComposerSurface({
|
|
|
307
312
|
footerId,
|
|
308
313
|
onCapture,
|
|
309
314
|
nameSeries = true,
|
|
315
|
+
delayFooter = true,
|
|
310
316
|
}: ChartComposerSurfaceProps) {
|
|
311
317
|
const trade = useResearchTrading();
|
|
312
318
|
const dialog = useDialog();
|
|
@@ -1119,6 +1125,22 @@ function ChartComposerSurface({
|
|
|
1119
1125
|
}, [activeRuntimeViewport, plottedSeries, spec, viewport]);
|
|
1120
1126
|
// A series a failed refresh left on a saved copy says when that copy was saved, as every pane does.
|
|
1121
1127
|
const savedAt = resolution.savedAt;
|
|
1128
|
+
// Signed out or free, a market series' latest bars are built from delayed quotes.
|
|
1129
|
+
const marketInstruments = useMemo(() => (delayFooter ? spec.series.flatMap((entry) => (
|
|
1130
|
+
entry.source.kind === "security" && isMarketFieldId(entry.source.fieldId) ? [entry.source.instrument] : []
|
|
1131
|
+
)) : []), [delayFooter, spec.series]);
|
|
1132
|
+
const marketQuotes = useQuoteEntries(marketInstruments);
|
|
1133
|
+
// Signed out no stream feeds a chart's quotes: without Pro the chart reads them once to know which are late.
|
|
1134
|
+
const { hasProAccess } = usePlanAccess();
|
|
1135
|
+
const marketQuoteKey = marketInstruments.map(buildQuoteKey).join("\u001f");
|
|
1136
|
+
useEffect(() => {
|
|
1137
|
+
const coordinator = getSharedMarketDataCoordinator();
|
|
1138
|
+
if (hasProAccess || !coordinator) return;
|
|
1139
|
+
for (const instrument of marketInstruments) {
|
|
1140
|
+
void coordinator.loadQuote(instrument).catch(logFailure("chart-composer.pane", "Loading a chart quote failed"));
|
|
1141
|
+
}
|
|
1142
|
+
}, [hasProAccess, marketQuoteKey]);
|
|
1143
|
+
const quoteDelay = useQuoteDelayFooter(Array.from(marketQuotes.values(), resolveEntryData), "chart-delay");
|
|
1122
1144
|
usePaneFooter(footerId, () => ({
|
|
1123
1145
|
info: [
|
|
1124
1146
|
...(delistingKey ? [{ id: "delisted", parts: [{ text: delistingKey, tone: "warning" as const }] }] : []),
|
|
@@ -1130,6 +1152,7 @@ function ChartComposerSurface({
|
|
|
1130
1152
|
: newestBeyondView
|
|
1131
1153
|
? [{ id: "newest", asOf: true, parts: [{ text: `newest ${newestBeyondView}`, tone: "muted" as const }] }]
|
|
1132
1154
|
: []),
|
|
1155
|
+
...(quoteDelay ? [quoteDelay] : []),
|
|
1133
1156
|
],
|
|
1134
1157
|
hints: [
|
|
1135
1158
|
// One key opens the ticket, which chooses Buy or Sell; [s]eries keeps its key with trading on.
|
|
@@ -1140,7 +1163,7 @@ function ChartComposerSurface({
|
|
|
1140
1163
|
{ id: "resolution", key: "b", label: "ars", title: "Bar Size", onPress: footerResolution },
|
|
1141
1164
|
...(anchorable ? [{ id: "vwap-anchor", key: "v", label: "wap anchor", title: "Anchor VWAP", onPress: footerAnchor }] : []),
|
|
1142
1165
|
],
|
|
1143
|
-
}), [delistingKey, savedAt, loading, pickingAnchor, newestBeyondView, footerSeries, openIndicators, indicatorsDisabled, openFormulas, formulasDisabled, footerResolution, anchorable, footerAnchor, trade]);
|
|
1166
|
+
}), [delistingKey, savedAt, loading, pickingAnchor, newestBeyondView, quoteDelay, footerSeries, openIndicators, indicatorsDisabled, openFormulas, formulasDisabled, footerResolution, anchorable, footerAnchor, trade]);
|
|
1144
1167
|
|
|
1145
1168
|
// A fixed window (a GIP session) highlights no range, so the bar names it.
|
|
1146
1169
|
const dateWindowLabel = useMemo(() => {
|
|
@@ -1476,6 +1499,7 @@ export function ChartComposerResearchTab({ focused, width, height, onCapture }:
|
|
|
1476
1499
|
footerId="chart-composer:research"
|
|
1477
1500
|
onCapture={onCapture}
|
|
1478
1501
|
nameSeries={false}
|
|
1502
|
+
delayFooter={false}
|
|
1479
1503
|
/>
|
|
1480
1504
|
);
|
|
1481
1505
|
}
|
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
listTimeSeriesFields,
|
|
9
9
|
} from "../../../time-series/field-catalog";
|
|
10
10
|
import {
|
|
11
|
-
CANONICAL_EXCHANGE_ALIASES,
|
|
12
11
|
canonicalExchange,
|
|
12
|
+
isKnownExchangeCode,
|
|
13
13
|
publicTickerKey,
|
|
14
14
|
} from "../../../utils/exchanges";
|
|
15
15
|
import { MAX_CHART_COMPOSER_SERIES } from "./chart-spec";
|
|
@@ -73,7 +73,8 @@ export function normalizeInstrument(
|
|
|
73
73
|
if (parts.length !== 2) return null;
|
|
74
74
|
const symbol = normalizeBaseSymbol(parts[0]!);
|
|
75
75
|
const exchangeToken = parts[1]!.trim().toUpperCase();
|
|
76
|
-
|
|
76
|
+
// Every venue the app names, as a listing's key writes it (BDI:NEO, Cboe Canada), not only the spellings it reads as another.
|
|
77
|
+
const knownExchange = isKnownExchangeCode(exchangeToken);
|
|
77
78
|
if (!symbol || !/^[A-Z0-9._-]{1,24}$/.test(exchangeToken) || (!knownExchange && !allowUnknownExchange)) {
|
|
78
79
|
return null;
|
|
79
80
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ChartSpec, ChartStudyKind, ChartStudySpec, ResolvedSeries } from "../../../time-series/types";
|
|
2
2
|
import { observationDate } from "../../../time-series/price-comparison";
|
|
3
3
|
import { zonedWallClockToUtcMs } from "../../../utils/zoned-date-time";
|
|
4
|
-
import { DEFAULT_PROFILE_ROWS, vwapAnchors, withVwapAnchors } from "../../../time-series/studies";
|
|
4
|
+
import { DEFAULT_PROFILE_ROWS, vwapAnchors, withVwapAnchors } from "../../../time-series/studies/vwap";
|
|
5
5
|
import { reconcilePanels } from "./chart-spec-edit";
|
|
6
6
|
import { CHART_FIELD_IDS } from "./series-expression";
|
|
7
7
|
|
|
@@ -916,7 +916,7 @@ export class ChatController {
|
|
|
916
916
|
const channel = this.ensureChannelState(channelId);
|
|
917
917
|
mergeChatMessages({
|
|
918
918
|
channel,
|
|
919
|
-
|
|
919
|
+
currentUser: this.session.user,
|
|
920
920
|
messages,
|
|
921
921
|
options,
|
|
922
922
|
viewActive: this.appActive && channel.focusedViewCount > 0,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ChatAttachment, ChatMessage } from "../../../../api-client";
|
|
2
2
|
import { toTimestampMillis } from "../../../../utils/timestamp";
|
|
3
|
+
import { isGloomWire } from "../ghost-user";
|
|
3
4
|
import type { ChannelRuntimeState, MergeMessagesOptions } from "./state";
|
|
4
5
|
import { PENDING_RECONCILE_WINDOW_MS } from "./state";
|
|
5
6
|
import { chatMessageMentionsUsername, compareMessages, normalizeChatUsername } from "./utils";
|
|
@@ -186,16 +187,26 @@ function getMentionMessages(messages: ChatMessage[], user: CurrentChatUser): Cha
|
|
|
186
187
|
|
|
187
188
|
interface MergeChatMessagesOptions {
|
|
188
189
|
channel: ChannelRuntimeState;
|
|
189
|
-
|
|
190
|
+
currentUser?: CurrentChatUser;
|
|
190
191
|
messages: ChatMessage[];
|
|
191
192
|
options?: MergeMessagesOptions;
|
|
192
193
|
viewActive: boolean;
|
|
193
194
|
markViewed: (persist?: boolean) => void;
|
|
194
195
|
}
|
|
195
196
|
|
|
197
|
+
/**
|
|
198
|
+
* New messages from others count as unread, as the server counts them: a
|
|
199
|
+
* Gloom Wire post only when it mentions you.
|
|
200
|
+
*/
|
|
201
|
+
function countUnread(messages: ChatMessage[], user: CurrentChatUser): number {
|
|
202
|
+
const fromPeople = messages.filter((message) => !isGloomWire(message.user));
|
|
203
|
+
const wireMentions = getMentionMessages(messages.filter((message) => isGloomWire(message.user)), user);
|
|
204
|
+
return fromPeople.length + wireMentions.length;
|
|
205
|
+
}
|
|
206
|
+
|
|
196
207
|
export function mergeChatMessages({
|
|
197
208
|
channel,
|
|
198
|
-
|
|
209
|
+
currentUser,
|
|
199
210
|
messages,
|
|
200
211
|
options,
|
|
201
212
|
viewActive,
|
|
@@ -203,11 +214,11 @@ export function mergeChatMessages({
|
|
|
203
214
|
}: MergeChatMessagesOptions): void {
|
|
204
215
|
reconcilePendingMessages(channel, messages);
|
|
205
216
|
const freshIncoming = mergeStoredMessages(channel, messages)
|
|
206
|
-
.filter((message) => message.user.id !==
|
|
217
|
+
.filter((message) => message.user.id !== currentUser?.id);
|
|
207
218
|
if (viewActive) {
|
|
208
219
|
markViewed(false);
|
|
209
220
|
} else if (freshIncoming.length > 0 && options?.countUnread !== false) {
|
|
210
|
-
channel.unreadCount += freshIncoming
|
|
221
|
+
channel.unreadCount += countUnread(freshIncoming, currentUser);
|
|
211
222
|
}
|
|
212
223
|
}
|
|
213
224
|
|
|
@@ -16,6 +16,14 @@ export function isManagedBot(user: Pick<ChatUserSummary, "accountType"> | null |
|
|
|
16
16
|
return user?.accountType === "managed_llm";
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/** Gloom Wire: the account that posts headlines, the day's calendar and pull requests into the topic channels. */
|
|
20
|
+
const GLOOM_WIRE_USER_ID = "managed-llm-gloomwire";
|
|
21
|
+
|
|
22
|
+
/** A Gloom Wire post: there to read, so unread only when it mentions you. */
|
|
23
|
+
export function isGloomWire(user: Pick<ChatUserSummary, "id"> | null | undefined): boolean {
|
|
24
|
+
return user?.id === GLOOM_WIRE_USER_ID;
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
/**
|
|
20
28
|
* A reply quotes its author by name only, so whether that author is a managed
|
|
21
29
|
* assistant comes from the users the pane has seen. A quoted announcement is
|
|
@@ -16,11 +16,9 @@ import { CHAT_KEEP_CONVERSATION_VALUE, createChatSearchProvider } from "./comman
|
|
|
16
16
|
import { chatController } from "./controller";
|
|
17
17
|
import { buildChatConversations, selectChatConversations } from "./conversations";
|
|
18
18
|
import { pickChatPaneToReuse } from "./pane-state";
|
|
19
|
-
import { ChatPane } from "./pane";
|
|
20
19
|
import { chatSidebarStore } from "./sidebar-store";
|
|
21
20
|
import { chatHandlePromptStore } from "./handle-prompt";
|
|
22
21
|
import { ChatStatusWidget } from "./status-widget";
|
|
23
|
-
import { UnreadInboxPane } from "./unread-inbox-pane";
|
|
24
22
|
import { installPaneChatSharing } from "./share/flow";
|
|
25
23
|
import { attachChatPaneLinkPanes } from "./pane-links/previews";
|
|
26
24
|
import { attachChatCommands, cachedChatFunctions, chatFunctionsChanged } from "./pane-links/commands";
|
|
@@ -62,9 +60,10 @@ let disposeUnreadBadge: (() => void) | undefined;
|
|
|
62
60
|
export const chatModule: PluginModule = {
|
|
63
61
|
panes: [{
|
|
64
62
|
id: "chat",
|
|
63
|
+
reportFreshness: { local: true },
|
|
65
64
|
name: "Chat",
|
|
66
65
|
icon: "C",
|
|
67
|
-
component: ChatPane,
|
|
66
|
+
get component(): typeof import("./pane").ChatPane { const { ChatPane } = require("./pane"); return ChatPane; },
|
|
68
67
|
defaultPosition: "right",
|
|
69
68
|
defaultMode: "floating",
|
|
70
69
|
defaultFloatingSize: { width: 80, height: 30 },
|
|
@@ -79,12 +78,12 @@ export const chatModule: PluginModule = {
|
|
|
79
78
|
},
|
|
80
79
|
}, {
|
|
81
80
|
id: UNREAD_INBOX_PANE_ID,
|
|
82
|
-
reportFreshness: { status: "not-a-feed", basis: "chat messages and missed alerts" },
|
|
81
|
+
reportFreshness: { status: "not-a-feed", basis: "chat messages and missed alerts", local: true },
|
|
83
82
|
name: "Unread",
|
|
84
83
|
// Mentions and replies from every conversation, DMs included.
|
|
85
84
|
personalData: "messages",
|
|
86
85
|
icon: "U",
|
|
87
|
-
component: UnreadInboxPane,
|
|
86
|
+
get component(): typeof import("./unread-inbox-pane").UnreadInboxPane { const { UnreadInboxPane } = require("./unread-inbox-pane"); return UnreadInboxPane; },
|
|
88
87
|
defaultPosition: "right",
|
|
89
88
|
defaultMode: "floating",
|
|
90
89
|
defaultFloatingSize: { width: 88, height: 16 },
|
|
@@ -96,6 +95,7 @@ export const chatModule: PluginModule = {
|
|
|
96
95
|
description: "Open the floating chat window for a channel",
|
|
97
96
|
keywords: ["new", "chat", "pane", "message"],
|
|
98
97
|
shortcut: { prefix: "CHAT", argPlaceholder: "channel", argKind: "text" },
|
|
98
|
+
catalogSettings: {},
|
|
99
99
|
createInstance: async (context, options) => {
|
|
100
100
|
const rawArg = options?.arg?.trim() ?? "";
|
|
101
101
|
// The command bar's New DM keeps the open pane on its conversation.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { GloomPlugin, PaneTemplateCreateOptions } from "../../../types/plugin";
|
|
2
2
|
import { normalizeTrialFilters, TRIAL_REPORT_OPTIONS, ALL } from "./model";
|
|
3
|
-
import { TrialsPane } from "./pane";
|
|
4
3
|
import { CLINICAL_TRIALS_PANE_ID, CLINICAL_TRIALS_PLUGIN_ID } from "./types";
|
|
5
4
|
|
|
6
5
|
function createTrialsPaneInstance(options?: PaneTemplateCreateOptions) {
|
|
@@ -40,7 +39,7 @@ export const clinicalTrialsPlugin: GloomPlugin = {
|
|
|
40
39
|
reportOptions: TRIAL_REPORT_OPTIONS,
|
|
41
40
|
name: "Trials",
|
|
42
41
|
icon: "C",
|
|
43
|
-
component: TrialsPane,
|
|
42
|
+
get component(): typeof import("./pane").TrialsPane { const { TrialsPane } = require("./pane"); return TrialsPane; },
|
|
44
43
|
defaultPosition: "right",
|
|
45
44
|
defaultMode: "floating",
|
|
46
45
|
defaultFloatingSize: { width: 100, height: 30 },
|
|
@@ -189,7 +189,7 @@ function LogoTile({ symbol, assetCategory, size }: { symbol: string; assetCatego
|
|
|
189
189
|
return <Box alignItems="center" justifyContent="center" style={tile}>{letter}</Box>;
|
|
190
190
|
}
|
|
191
191
|
return (
|
|
192
|
-
<ImageSurface src={src} alt={symbol} objectFit="contain" alignItems="center" justifyContent="center" style={tile}>
|
|
192
|
+
<ImageSurface src={src} alt={symbol} objectFit="contain" fallbackWhileLoading alignItems="center" justifyContent="center" style={tile}>
|
|
193
193
|
{letter}
|
|
194
194
|
</ImageSurface>
|
|
195
195
|
);
|
|
@@ -2,6 +2,7 @@ import type { GloomPlugin } from "../../../types/plugin";
|
|
|
2
2
|
import { apiClient } from "../../../api-client";
|
|
3
3
|
import { createGloomberbCloudCapabilities, createGloomberbCloudProvider } from "../../../sources/gloomberb-cloud";
|
|
4
4
|
import { accountManagementModule } from "../account-management";
|
|
5
|
+
import { buildoutModule } from "../buildout";
|
|
5
6
|
import { chatModule } from "../chat";
|
|
6
7
|
import {
|
|
7
8
|
CONGRESS_TRADES_PANE_ID,
|
|
@@ -88,8 +89,10 @@ const askgModule: PluginModule = {
|
|
|
88
89
|
},
|
|
89
90
|
panes: [{
|
|
90
91
|
id: ASKG_PANE_ID,
|
|
91
|
-
reportFreshness: { status: "not-a-feed", basis: "generated answer" },
|
|
92
|
+
reportFreshness: { status: "not-a-feed", basis: "generated answer", local: true },
|
|
92
93
|
reportLockNote: askgLockNote,
|
|
94
|
+
// Without a session the wall is all there is, so `fn ASKG` prints it at once.
|
|
95
|
+
signInWall: true,
|
|
93
96
|
name: "Ask Gloom",
|
|
94
97
|
personalData: "messages",
|
|
95
98
|
icon: "K",
|
|
@@ -177,8 +180,7 @@ const congressTradesModule: PluginModule = {
|
|
|
177
180
|
}],
|
|
178
181
|
};
|
|
179
182
|
|
|
180
|
-
|
|
181
|
-
export function createGloomberbCloudPlugin(extraModules: readonly PluginModule[] = []): GloomPlugin {
|
|
183
|
+
export function createGloomberbCloudPlugin(): GloomPlugin {
|
|
182
184
|
return composeBuiltinPlugin({
|
|
183
185
|
id: "gloomberb-cloud",
|
|
184
186
|
name: "Gloom Cloud",
|
|
@@ -195,7 +197,7 @@ export function createGloomberbCloudPlugin(extraModules: readonly PluginModule[]
|
|
|
195
197
|
cloudAccountModule,
|
|
196
198
|
askgModule,
|
|
197
199
|
terminalRelayModule,
|
|
198
|
-
|
|
200
|
+
buildoutModule,
|
|
199
201
|
congressTradesModule,
|
|
200
202
|
cloudTweetsModule,
|
|
201
203
|
],
|
|
@@ -24,7 +24,7 @@ export const teamModule: PluginModule = {
|
|
|
24
24
|
capabilities: [createCloudTeamCapability(), createCloudViewsCapability()],
|
|
25
25
|
panes: [{
|
|
26
26
|
id: TEAM_PANE_ID,
|
|
27
|
-
reportFreshness: { status: "not-a-feed", basis: "account data" },
|
|
27
|
+
reportFreshness: { status: "not-a-feed", basis: "account data", local: true },
|
|
28
28
|
name: "Team",
|
|
29
29
|
icon: "T",
|
|
30
30
|
personalData: "account",
|
|
@@ -12,7 +12,7 @@ import { ThesisTickerTab } from "./ticker-tab";
|
|
|
12
12
|
export const thesisModule: PluginModule = {
|
|
13
13
|
panes: [{
|
|
14
14
|
id: THESIS_PANE_ID,
|
|
15
|
-
reportFreshness: { source: "Your theses", status: "not-a-feed", basis: "your theses" },
|
|
15
|
+
reportFreshness: { source: "Your theses", status: "not-a-feed", basis: "your theses", local: true },
|
|
16
16
|
name: "Theses",
|
|
17
17
|
personalData: "holdings",
|
|
18
18
|
icon: "Θ",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Where the status bar has no room for it, as on a phone, it never shows and
|
|
15
15
|
* neither arm is counted.
|
|
16
16
|
*/
|
|
17
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
17
|
+
import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
18
18
|
import { apiClient } from "../../../api-client";
|
|
19
19
|
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
20
20
|
import { exposeWebExperiment } from "../../../api-client/research-activity";
|
|
@@ -88,6 +88,27 @@ interface TrialOfferStatusWidgetProps {
|
|
|
88
88
|
delayMs?: number;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
let offerShowing = false;
|
|
92
|
+
const offerListeners = new Set<() => void>();
|
|
93
|
+
|
|
94
|
+
function setOfferShowing(next: boolean): void {
|
|
95
|
+
if (offerShowing === next) return;
|
|
96
|
+
offerShowing = next;
|
|
97
|
+
for (const listener of offerListeners) listener();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function subscribeOfferShowing(listener: () => void): () => void {
|
|
101
|
+
offerListeners.add(listener);
|
|
102
|
+
return () => {
|
|
103
|
+
offerListeners.delete(listener);
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Whether the offer line is in the status bar, so the upgrade chip beside it does not pitch Pro twice. */
|
|
108
|
+
export function useTrialOfferShowing(): boolean {
|
|
109
|
+
return useSyncExternalStore(subscribeOfferShowing, () => offerShowing, () => offerShowing);
|
|
110
|
+
}
|
|
111
|
+
|
|
91
112
|
export function TrialOfferStatusWidget(props: TrialOfferStatusWidgetProps) {
|
|
92
113
|
if (getCurrentPluginTarget() !== "web") return null;
|
|
93
114
|
return <WebTrialOffer {...props} />;
|
|
@@ -150,8 +171,10 @@ function WebTrialOffer({ expose = exposeWebExperiment, delayMs = TRIAL_OFFER_DEL
|
|
|
150
171
|
useEffect(() => {
|
|
151
172
|
if (!showing) return;
|
|
152
173
|
hideShowingOffer = hide;
|
|
174
|
+
setOfferShowing(true);
|
|
153
175
|
return () => {
|
|
154
176
|
if (hideShowingOffer === hide) hideShowingOffer = null;
|
|
177
|
+
setOfferShowing(false);
|
|
155
178
|
};
|
|
156
179
|
}, [hide, showing]);
|
|
157
180
|
|