@gloom-sh/terminal 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
|
@@ -30,6 +30,12 @@ export interface PaneFooterSegment {
|
|
|
30
30
|
* data a source delivers late: build it with {@link delayedFooterSegment}.
|
|
31
31
|
*/
|
|
32
32
|
icon?: "warning" | "clock";
|
|
33
|
+
/**
|
|
34
|
+
* A few characters the desktop draws after `icon`, in the footer's muted
|
|
35
|
+
* tone, so the segment reads without hovering: a delay's `15m`. The
|
|
36
|
+
* terminal's `parts` already carry it (`◷15m`).
|
|
37
|
+
*/
|
|
38
|
+
iconText?: string;
|
|
33
39
|
/**
|
|
34
40
|
* The segment only dates the pane's data (`as of 2026-10-05`). A `gloomberb
|
|
35
41
|
* shot` whose status line already dates that data leaves it out, so the
|
|
@@ -51,6 +57,14 @@ export interface PaneFooterSegment {
|
|
|
51
57
|
* warning.
|
|
52
58
|
*/
|
|
53
59
|
capture?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* The segment only says how long ago the pane's data loaded (`5m ago`,
|
|
62
|
+
* `fetched just now`), relative to the moment it is read: build it with
|
|
63
|
+
* {@link ageFooterSegment}. A row too narrow for every segment leaves it out
|
|
64
|
+
* first, whole, before anything else shortens or is cut, so the source and
|
|
65
|
+
* the date stay.
|
|
66
|
+
*/
|
|
67
|
+
age?: boolean;
|
|
54
68
|
/**
|
|
55
69
|
* When the copy the pane shows was saved, on the saved-copy segment
|
|
56
70
|
* ({@link savedCopyFooterSegment}). The footer draws that segment first,
|
|
@@ -75,10 +89,11 @@ export interface PaneFooterSegment {
|
|
|
75
89
|
const DELAYED_FOOTER_GLYPH = "◷";
|
|
76
90
|
|
|
77
91
|
/**
|
|
78
|
-
* The footer's one form for data a source delivers late: a yellow clock
|
|
79
|
-
* `
|
|
80
|
-
* (`◷15m`) in the terminal, which has no tooltip, so it
|
|
81
|
-
* rather than a phrase.
|
|
92
|
+
* The footer's one form for data a source delivers late: a yellow clock and
|
|
93
|
+
* the delay (`15m`) on the desktop, with `detail` in its tooltip, and the
|
|
94
|
+
* clock with the delay (`◷15m`) in the terminal, which has no tooltip, so it
|
|
95
|
+
* takes a few cells rather than a phrase. Without a known delay the desktop
|
|
96
|
+
* draws the clock alone. `onPress` makes it a button, such as an upgrade.
|
|
82
97
|
*/
|
|
83
98
|
export function delayedFooterSegment({
|
|
84
99
|
id,
|
|
@@ -101,6 +116,7 @@ export function delayedFooterSegment({
|
|
|
101
116
|
label: detail,
|
|
102
117
|
title: detail,
|
|
103
118
|
parts: [{ text: `${DELAYED_FOOTER_GLYPH}${delay ?? ""}`, tone: "warning" }],
|
|
119
|
+
...(delay ? { iconText: delay } : {}),
|
|
104
120
|
...(onPress ? { onPress } : {}),
|
|
105
121
|
...(quoteStatus ? { quoteStatus } : {}),
|
|
106
122
|
};
|
|
@@ -127,6 +143,19 @@ export function savedCopyFooterSegment(id: string, savedAt: number, now = Date.n
|
|
|
127
143
|
};
|
|
128
144
|
}
|
|
129
145
|
|
|
146
|
+
/**
|
|
147
|
+
* The footer's one form for how long ago the pane's data loaded: `5m ago`,
|
|
148
|
+
* or `fetched 5m ago` with a `verb`. A narrow row leaves it out first (`age`).
|
|
149
|
+
*/
|
|
150
|
+
export function ageFooterSegment(ago: string, { id = "updated", verb, tone = "muted" }: {
|
|
151
|
+
id?: string;
|
|
152
|
+
/** Said before the age: `fetched`, `checked`, `updated`. */
|
|
153
|
+
verb?: string;
|
|
154
|
+
tone?: PaneFooterPart["tone"];
|
|
155
|
+
} = {}): PaneFooterSegment {
|
|
156
|
+
return { id, age: true, parts: [{ text: verb ? `${verb} ${ago}` : ago, tone }] };
|
|
157
|
+
}
|
|
158
|
+
|
|
130
159
|
/**
|
|
131
160
|
* The saved-copy segment of a pane showing several answers (a list and the
|
|
132
161
|
* report it opens): the oldest of the copies among `savedAts` (ms), or no
|
|
@@ -317,7 +346,8 @@ function trimTrailing(info: PaneFooterSegment[], width: number, keep: number): P
|
|
|
317
346
|
}
|
|
318
347
|
|
|
319
348
|
/**
|
|
320
|
-
* The info segments as they fit `width`:
|
|
349
|
+
* The info segments as they fit `width`: an `age` (`5m ago`) is left out
|
|
350
|
+
* first, so the source and the date outlast it; then segments with `variants` step down
|
|
321
351
|
* together, one variant at a time, until the row fits or none is left; beside
|
|
322
352
|
* the hints, the trailing status that still does not fit is then cut or left
|
|
323
353
|
* out (trimTrailing). A status after every segment with shorter forms gives
|
|
@@ -328,6 +358,10 @@ function trimTrailing(info: PaneFooterSegment[], width: number, keep: number): P
|
|
|
328
358
|
* so a frame that fits its own status line sees it overflow.
|
|
329
359
|
*/
|
|
330
360
|
export function fitPaneFooterInfo(info: PaneFooterSegment[], width: number, { besideHints = true }: { besideHints?: boolean } = {}): PaneFooterSegment[] {
|
|
361
|
+
// How long ago the data loaded goes first, whole: a cut age (`fetched just`) says nothing.
|
|
362
|
+
if (infoTextWidth(info) > width && info.some((segment) => segment.age)) {
|
|
363
|
+
return fitPaneFooterInfo(info.filter((segment) => !segment.age), width, { besideHints });
|
|
364
|
+
}
|
|
331
365
|
if (!besideHints) return stepDownTogether(info, width);
|
|
332
366
|
const lastWithVariants = info.findLastIndex((segment) => (segment.variants?.length ?? 0) > 0);
|
|
333
367
|
if (lastWithVariants >= 0 && lastWithVariants < info.length - 1 && infoTextWidth(info) > width) {
|
|
@@ -539,6 +573,7 @@ export function samePaneFooterRegistration(
|
|
|
539
573
|
&& !!segment.onPress === !!other.onPress
|
|
540
574
|
&& segment.disabled === other.disabled
|
|
541
575
|
&& segment.savedCopyAt === other.savedCopyAt
|
|
576
|
+
&& segment.age === other.age
|
|
542
577
|
&& sameFooterParts(segment.parts, other.parts)
|
|
543
578
|
&& (segment.variants?.length ?? 0) === (other.variants?.length ?? 0)
|
|
544
579
|
&& (segment.variants ?? []).every((variant, variantIndex) => sameFooterParts(variant, other.variants![variantIndex]!));
|
|
@@ -215,9 +215,10 @@ export interface StatChartDetailProps {
|
|
|
215
215
|
export function StatChartDetail({ items, width, height, empty, emptySubject, emptyTitle, renderChart }: StatChartDetailProps) {
|
|
216
216
|
return (
|
|
217
217
|
<Box flexDirection="column" width={width} height={height}>
|
|
218
|
-
|
|
218
|
+
{/* A figure wraps only into rows the chart's least leaves, so the detail never spills. */}
|
|
219
|
+
<StatGrid items={items} width={width} maxLines={height - 3} />
|
|
219
220
|
<PaneStatusBody empty={empty} subject={emptySubject} emptyTitle={emptyTitle}>
|
|
220
|
-
{renderChart(Math.max(3, height - statGridRows(items, width)))}
|
|
221
|
+
{renderChart(Math.max(3, height - statGridRows(items, width, undefined, { maxLines: height - 3 })))}
|
|
221
222
|
</PaneStatusBody>
|
|
222
223
|
</Box>
|
|
223
224
|
);
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { Box, useUiCapabilities } from "../../ui";
|
|
2
2
|
import { t } from "../../i18n";
|
|
3
3
|
import { DESKS, deskFunctions, type DeskKey } from "../../layout/desks";
|
|
4
|
+
import { MAX_DESK_PICKS } from "../../layout/desk-keys";
|
|
4
5
|
import { ToggleList } from "../toggle-list";
|
|
5
6
|
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Ticks or unticks a desk; the ticked ones keep the order they were ticked
|
|
9
|
+
* in. A fourth tick is ignored: untick one first.
|
|
10
|
+
*/
|
|
11
|
+
export function toggleDeskChoice(chosen: DeskKey[], key: DeskKey): DeskKey[] {
|
|
12
|
+
if (chosen.includes(key)) return chosen.filter((entry) => entry !== key);
|
|
13
|
+
return chosen.length < MAX_DESK_PICKS ? [...chosen, key] : chosen;
|
|
9
14
|
}
|
|
10
15
|
|
|
11
|
-
/** "What do you trade?": one checkbox per desk, with the functions it opens. */
|
|
16
|
+
/** "What do you trade most?": one checkbox per desk, with the functions it opens. */
|
|
12
17
|
export function DesksStep({
|
|
13
18
|
chosen,
|
|
14
19
|
cursor,
|
|
@@ -21,6 +26,7 @@ export function DesksStep({
|
|
|
21
26
|
onToggle: (key: DeskKey) => void;
|
|
22
27
|
}) {
|
|
23
28
|
const desktop = useUiCapabilities().nativePaneChrome === true;
|
|
29
|
+
const full = chosen.length >= MAX_DESK_PICKS;
|
|
24
30
|
return (
|
|
25
31
|
<Box flexDirection="column" flexShrink={0} paddingX={desktop ? 0 : 2} style={desktop ? { marginTop: 14 } : undefined}>
|
|
26
32
|
<ToggleList
|
|
@@ -28,6 +34,8 @@ export function DesksStep({
|
|
|
28
34
|
id: desk.key,
|
|
29
35
|
label: t(desk.label),
|
|
30
36
|
enabled: chosen.includes(desk.key),
|
|
37
|
+
// Once three are ticked, the rest wait until one is unticked.
|
|
38
|
+
disabled: full && !chosen.includes(desk.key),
|
|
31
39
|
detail: deskFunctions(desk).join(" "),
|
|
32
40
|
}))}
|
|
33
41
|
selectedIdx={cursor}
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
type SavedLayout,
|
|
9
9
|
} from "../../types/config";
|
|
10
10
|
import type { TickerMetadata, TickerRecord } from "../../types/ticker";
|
|
11
|
+
import { MAX_DESK_PICKS, type DeskKey } from "../../layout/desk-keys";
|
|
12
|
+
import { DESKS, isDeskKey } from "../../layout/desks";
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* The watchlist a first run lands with: the two index ETFs and the names most
|
|
@@ -211,13 +213,88 @@ export function buildFirstRunLayout({
|
|
|
211
213
|
};
|
|
212
214
|
}
|
|
213
215
|
|
|
214
|
-
/**
|
|
215
|
-
|
|
216
|
-
|
|
216
|
+
/**
|
|
217
|
+
* The ids of the layouts onboarding makes. Running it again finds its own
|
|
218
|
+
* layouts by these and replaces them, so it never adds a copy; a layout
|
|
219
|
+
* without one is the user's.
|
|
220
|
+
*/
|
|
221
|
+
const ONBOARDING_HOME_LAYOUT_ID = "onboarding:home";
|
|
222
|
+
const ONBOARDING_LAYOUT_ID_PREFIX = "onboarding:";
|
|
223
|
+
const ONBOARDING_DESK_LAYOUT_ID_PREFIX = `${ONBOARDING_LAYOUT_ID_PREFIX}desk:`;
|
|
224
|
+
|
|
225
|
+
export function onboardingDeskLayoutId(desk: DeskKey): string {
|
|
226
|
+
return `${ONBOARDING_DESK_LAYOUT_ID_PREFIX}${desk}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function isOnboardingLayout(layout: SavedLayout): boolean {
|
|
230
|
+
return layout.id?.startsWith(ONBOARDING_LAYOUT_ID_PREFIX) === true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Takes back the layouts setup made before it tagged them, so a run from
|
|
235
|
+
* Settings rebuilds them in place instead of copying them. Such a profile
|
|
236
|
+
* has setup's Home first and its desks right after it, under the desks'
|
|
237
|
+
* names. A first layout not called Home is the user's, and so is
|
|
238
|
+
* everything after the desks.
|
|
239
|
+
*/
|
|
240
|
+
function adoptUntaggedSetupLayouts(config: AppConfig): AppConfig {
|
|
241
|
+
const [first, ...rest] = config.layouts;
|
|
242
|
+
if (!first || first.name !== "Home" || config.layouts.some(isOnboardingLayout)) return config;
|
|
243
|
+
const desks: SavedLayout[] = [];
|
|
244
|
+
for (const layout of rest) {
|
|
245
|
+
const key = DESKS.find((desk) => desk.name === layout.name)?.key;
|
|
246
|
+
if (!key || desks.length >= MAX_DESK_PICKS || desks.some((desk) => desk.id === onboardingDeskLayoutId(key))) break;
|
|
247
|
+
desks.push({ ...layout, id: onboardingDeskLayoutId(key) });
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
...config,
|
|
251
|
+
layouts: [{ ...first, id: ONBOARDING_HOME_LAYOUT_ID }, ...desks, ...rest.slice(desks.length)],
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** The desks setup made that are still tabs, in tab order: what a run from Settings starts with ticked. */
|
|
256
|
+
export function setupDeskPicks(config: AppConfig): DeskKey[] {
|
|
257
|
+
const picks: DeskKey[] = [];
|
|
258
|
+
for (const layout of adoptUntaggedSetupLayouts(config).layouts) {
|
|
259
|
+
const key = layout.id?.startsWith(ONBOARDING_DESK_LAYOUT_ID_PREFIX)
|
|
260
|
+
? layout.id.slice(ONBOARDING_DESK_LAYOUT_ID_PREFIX.length)
|
|
261
|
+
: null;
|
|
262
|
+
if (isDeskKey(key) && !picks.includes(key)) picks.push(key);
|
|
263
|
+
}
|
|
264
|
+
return picks.slice(0, MAX_DESK_PICKS);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Puts the first-run workspace in place and makes Home active: Home, then a
|
|
269
|
+
* tab per picked desk. The layouts an earlier run made are replaced where
|
|
270
|
+
* they stood, and a desk not picked this time goes with them; a first run
|
|
271
|
+
* replaces the stock Home it starts with. Every other layout is the user's
|
|
272
|
+
* and keeps its place and content. A run from Settings (`rerun`) also
|
|
273
|
+
* rebuilds what setup made before it tagged its layouts, and with none of
|
|
274
|
+
* setup's left it puts Home first, replacing nothing.
|
|
275
|
+
*/
|
|
276
|
+
export function applyFirstRunLayout(
|
|
277
|
+
config: AppConfig,
|
|
278
|
+
home: SavedLayout,
|
|
279
|
+
desks: ReadonlyArray<SavedLayout & { id: string }> = [],
|
|
280
|
+
{ rerun = false }: { rerun?: boolean } = {},
|
|
281
|
+
): AppConfig {
|
|
282
|
+
const { layouts: current } = rerun ? adoptUntaggedSetupLayouts(config) : config;
|
|
283
|
+
const earlier = current.findIndex(isOnboardingLayout);
|
|
284
|
+
const users = current.filter((layout) => !isOnboardingLayout(layout));
|
|
285
|
+
// Everything before the first layout onboarding made is the user's, so its index holds.
|
|
286
|
+
const at = earlier >= 0 ? earlier : 0;
|
|
287
|
+
const kept = earlier >= 0 || rerun ? users : users.slice(1);
|
|
288
|
+
const layouts = [
|
|
289
|
+
...kept.slice(0, at),
|
|
290
|
+
{ ...home, id: ONBOARDING_HOME_LAYOUT_ID },
|
|
291
|
+
...desks,
|
|
292
|
+
...kept.slice(at),
|
|
293
|
+
];
|
|
217
294
|
return {
|
|
218
295
|
...config,
|
|
219
296
|
layouts,
|
|
220
297
|
layout: home.layout,
|
|
221
|
-
activeLayoutIndex:
|
|
298
|
+
activeLayoutIndex: at,
|
|
222
299
|
};
|
|
223
300
|
}
|
|
@@ -359,7 +359,8 @@ export function OnboardingHeader({
|
|
|
359
359
|
}
|
|
360
360
|
|
|
361
361
|
// Progress, not navigation: three segments, the current one lit, the ones
|
|
362
|
-
// behind it filled. A filled segment is
|
|
362
|
+
// behind it filled. A filled segment is a way back unless the wizard marks
|
|
363
|
+
// it unavailable: a step done for good stays filled and is not offered again.
|
|
363
364
|
const activeIndex = sections.findIndex((section) => section.id === active);
|
|
364
365
|
return (
|
|
365
366
|
<Box flexDirection="row" alignItems="flex-start" justifyContent="space-between" minWidth={0}>
|
|
@@ -58,7 +58,7 @@ export function useOnboardingKeyboard({
|
|
|
58
58
|
submitAccountField, backPortfolio, continuePortfolio, setPortfolioOptionIdx, activeBrokerFields,
|
|
59
59
|
brokerFieldIdx, setBrokerSelectIdx, brokerChoices, openBrokerGuide, chosenDesks, finishDesks,
|
|
60
60
|
setDeskCursor, deskCursor, setChosenDesks, continueAccount, skipSetup, primaryUpgradeAction,
|
|
61
|
-
setBillingInterval, keybindings, dialogOpen, commandBarOpen, saveProgressInBackground,
|
|
61
|
+
setBillingInterval, keybindings, dialogOpen, commandBarOpen, saveProgressInBackground, goBack,
|
|
62
62
|
}: {
|
|
63
63
|
stage: OnboardingStage;
|
|
64
64
|
progress: OnboardingProgress;
|
|
@@ -102,6 +102,8 @@ export function useOnboardingKeyboard({
|
|
|
102
102
|
dialogOpen: boolean;
|
|
103
103
|
commandBarOpen: boolean;
|
|
104
104
|
saveProgressInBackground: (patch: Partial<OnboardingProgress> & Pick<OnboardingProgress, "stage">, baseConfig?: AppConfig | undefined) => void;
|
|
105
|
+
/** Back on the card: to the step the user came from. */
|
|
106
|
+
goBack: () => void;
|
|
105
107
|
}) {
|
|
106
108
|
const activeSection: OnboardingSectionId = stage === "portfolio" || stage === "desks"
|
|
107
109
|
? "portfolio"
|
|
@@ -128,7 +130,8 @@ export function useOnboardingKeyboard({
|
|
|
128
130
|
|
|
129
131
|
const handled = ((): boolean => {
|
|
130
132
|
if (isPlainKey(event, "f10")) {
|
|
131
|
-
|
|
133
|
+
// The first run needs a holding; a run from Settings can be left as it was.
|
|
134
|
+
if (isBrokerCommitting || ((stage === "portfolio" || stage === "desks") && !progress.rerun)) return true;
|
|
132
135
|
if (stage === "upgrade" && !planAccess.hasProAccess) continueFree();
|
|
133
136
|
else void finish(true);
|
|
134
137
|
return true;
|
|
@@ -234,7 +237,7 @@ export function useOnboardingKeyboard({
|
|
|
234
237
|
continueAccount();
|
|
235
238
|
} else if (escape) {
|
|
236
239
|
if (account.accountSub === "qr" || account.accountSub === "login") account.returnToAccountForm();
|
|
237
|
-
else
|
|
240
|
+
else goBack();
|
|
238
241
|
} else if (letter(BROWSER_SIGN_IN_KEY) && accountForm) {
|
|
239
242
|
account.beginQrSignIn();
|
|
240
243
|
} else if (letter(SKIP_SETUP_KEY)) {
|
|
@@ -251,7 +254,7 @@ export function useOnboardingKeyboard({
|
|
|
251
254
|
if (enter) {
|
|
252
255
|
primaryUpgradeAction();
|
|
253
256
|
} else if (escape) {
|
|
254
|
-
|
|
257
|
+
goBack();
|
|
255
258
|
} else if (!planAccess.hasProAccess && letter(KEEP_FREE_KEY)) {
|
|
256
259
|
continueFree();
|
|
257
260
|
} else if (!planAccess.hasProAccess && (isPlainKey(event, "left") || letter("h"))) {
|
|
@@ -266,7 +269,7 @@ export function useOnboardingKeyboard({
|
|
|
266
269
|
|
|
267
270
|
if (stage === "ready") {
|
|
268
271
|
if (enter) void finish();
|
|
269
|
-
else if (escape)
|
|
272
|
+
else if (escape) goBack();
|
|
270
273
|
else return false;
|
|
271
274
|
return true;
|
|
272
275
|
}
|
|
@@ -20,7 +20,7 @@ import { t, tf } from "../../i18n";
|
|
|
20
20
|
import { useAppLanguage } from "../../i18n/react";
|
|
21
21
|
import type { PluginRegistry } from "../../plugins/registry";
|
|
22
22
|
import { formatCloudPrice, monthsFreeYearly } from "../../account/billing";
|
|
23
|
-
import { proStepCopy
|
|
23
|
+
import { proStepCopy } from "../../account/upgrade-dialog";
|
|
24
24
|
import { Button, SegmentedControl, terminalButtonColumns } from "../ui";
|
|
25
25
|
import { AccountStep, PortfolioStep } from "./onboarding-steps";
|
|
26
26
|
import {
|
|
@@ -41,7 +41,7 @@ import { useOnboardingAccount } from "./wizard-account";
|
|
|
41
41
|
import { useOnboardingBrokerSync } from "./wizard-broker-sync";
|
|
42
42
|
import { useOnboardingPositions } from "./wizard-positions";
|
|
43
43
|
import { DesksStep, toggleDeskChoice } from "./desks-step";
|
|
44
|
-
import { getOnboardingProgress } from "./wizard-model";
|
|
44
|
+
import { getOnboardingProgress, isOnboardingWorkspaceBuilt, previousOnboardingStage } from "./wizard-model";
|
|
45
45
|
import {
|
|
46
46
|
useOnboardingKeyboard,
|
|
47
47
|
SKIP_SETUP_KEY,
|
|
@@ -86,7 +86,22 @@ function useOnboardingWizard({ pluginRegistry, importBrokerPositions, onComplete
|
|
|
86
86
|
const config = useAppSelector((state) => state.config);
|
|
87
87
|
const progress = getOnboardingProgress(config);
|
|
88
88
|
const stage = progress.stage;
|
|
89
|
+
const workspaceBuilt = isOnboardingWorkspaceBuilt(progress);
|
|
90
|
+
/** Opened again from Settings: any step can be left, and the first-run funnel is not told. */
|
|
91
|
+
const rerun = progress.rerun === true;
|
|
89
92
|
const researchOpenedRef = useRef<string | null>(null);
|
|
93
|
+
/** The stages the user came through, oldest first, for Back to retrace. */
|
|
94
|
+
const visitedRef = useRef<OnboardingStage[]>([]);
|
|
95
|
+
const shownStageRef = useRef(stage);
|
|
96
|
+
const backTargetRef = useRef<OnboardingStage | null>(null);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
const left = shownStageRef.current;
|
|
99
|
+
if (left === stage) return;
|
|
100
|
+
shownStageRef.current = stage;
|
|
101
|
+
const back = backTargetRef.current === stage;
|
|
102
|
+
backTargetRef.current = null;
|
|
103
|
+
if (!back) visitedRef.current = [...visitedRef.current, left];
|
|
104
|
+
}, [stage]);
|
|
90
105
|
const [editingField, setEditingField] = useState(false);
|
|
91
106
|
/** The added position the keyboard acts on once no field is being typed in. */
|
|
92
107
|
const [positionCursorSymbol, setPositionCursorSymbol] = useState<string | null>(null);
|
|
@@ -109,7 +124,7 @@ function useOnboardingWizard({ pluginRegistry, importBrokerPositions, onComplete
|
|
|
109
124
|
// bar, so an unread inbox never stalls the first session.
|
|
110
125
|
const account = useOnboardingAccount({
|
|
111
126
|
nextStep: () => {
|
|
112
|
-
recordResearchActivity("onboarding_signed_in");
|
|
127
|
+
if (!rerun) recordResearchActivity("onboarding_signed_in");
|
|
113
128
|
saveProgressInBackground({ stage: "upgrade", accountStatus: "signed-in" });
|
|
114
129
|
},
|
|
115
130
|
setEditingField,
|
|
@@ -127,12 +142,12 @@ function useOnboardingWizard({ pluginRegistry, importBrokerPositions, onComplete
|
|
|
127
142
|
|
|
128
143
|
useEffect(() => {
|
|
129
144
|
const activity = STAGE_ACTIVITY[stage];
|
|
130
|
-
if (activity) recordResearchActivity(activity);
|
|
131
|
-
}, [stage]);
|
|
145
|
+
if (activity && !rerun) recordResearchActivity(activity);
|
|
146
|
+
}, [rerun, stage]);
|
|
132
147
|
|
|
133
148
|
useEffect(() => {
|
|
134
|
-
if (stage === "portfolio" && positionCount > 0) recordResearchActivity("onboarding_position_added");
|
|
135
|
-
}, [positionCount, stage]);
|
|
149
|
+
if (stage === "portfolio" && positionCount > 0 && !rerun) recordResearchActivity("onboarding_position_added");
|
|
150
|
+
}, [positionCount, rerun, stage]);
|
|
136
151
|
|
|
137
152
|
// The portfolio step opens straight into the ticker field.
|
|
138
153
|
useEffect(() => {
|
|
@@ -197,8 +212,8 @@ function useOnboardingWizard({ pluginRegistry, importBrokerPositions, onComplete
|
|
|
197
212
|
useEffect(() => {
|
|
198
213
|
if (stage !== "research" || !progress.tickerSymbol || researchOpenedRef.current === progress.tickerSymbol) return;
|
|
199
214
|
researchOpenedRef.current = progress.tickerSymbol;
|
|
200
|
-
recordResearchActivity("ticker_saved");
|
|
201
|
-
}, [stage, progress.tickerSymbol]);
|
|
215
|
+
if (!rerun) recordResearchActivity("ticker_saved");
|
|
216
|
+
}, [rerun, stage, progress.tickerSymbol]);
|
|
202
217
|
const movePositionCursor = useCallback((delta: number) => {
|
|
203
218
|
if (positionCursorIndex < 0) return;
|
|
204
219
|
const next = Math.max(0, Math.min(positionCount - 1, positionCursorIndex + delta));
|
|
@@ -239,16 +254,17 @@ function useOnboardingWizard({ pluginRegistry, importBrokerPositions, onComplete
|
|
|
239
254
|
: null;
|
|
240
255
|
const helpFocused = focusedInstance?.paneId === "help";
|
|
241
256
|
const {
|
|
242
|
-
offer, pricing, billingInterval, setBillingInterval, planAccess, primaryUpgradeAction, continueFree,
|
|
257
|
+
offer, pricing, billingInterval, setBillingInterval, planAccess, primaryUpgradeAction, continueFree,
|
|
243
258
|
} = useOnboardingUpgrade({
|
|
244
259
|
stage, progress, saveProgressInBackground, persistProgress,
|
|
245
|
-
shown: !helpFocused && !dialogOpen && !commandBarOpen,
|
|
246
260
|
});
|
|
247
261
|
|
|
248
262
|
const goToSection = useCallback((section: OnboardingSectionId) => {
|
|
249
263
|
if (finishingRef.current || isBrokerCommitting) return;
|
|
250
264
|
setEditingField(false);
|
|
251
265
|
if (section === "portfolio") {
|
|
266
|
+
// Done for good once the workspace is built: picking it again would build it twice.
|
|
267
|
+
if (workspaceBuilt) return;
|
|
252
268
|
setPortfolioSub("positions");
|
|
253
269
|
saveProgressInBackground({ stage: "portfolio" });
|
|
254
270
|
return;
|
|
@@ -260,10 +276,22 @@ function useOnboardingWizard({ pluginRegistry, importBrokerPositions, onComplete
|
|
|
260
276
|
if (planAccess.signedIn) {
|
|
261
277
|
saveProgressInBackground({ stage: "upgrade", accountStatus: "signed-in" });
|
|
262
278
|
}
|
|
263
|
-
}, [isBrokerCommitting, planAccess.signedIn, saveProgressInBackground]);
|
|
279
|
+
}, [isBrokerCommitting, planAccess.signedIn, saveProgressInBackground, workspaceBuilt]);
|
|
280
|
+
|
|
281
|
+
/** Back and Esc: to the step the user came from, never one that is done for good. */
|
|
282
|
+
const goBack = useCallback(() => {
|
|
283
|
+
if (finishingRef.current || isBrokerCommitting) return;
|
|
284
|
+
const back = previousOnboardingStage(progress, visitedRef.current, { pro: planAccess.hasProAccess });
|
|
285
|
+
if (!back) return;
|
|
286
|
+
visitedRef.current = back.visited;
|
|
287
|
+
backTargetRef.current = back.stage;
|
|
288
|
+
setEditingField(false);
|
|
289
|
+
if (back.stage === "portfolio") setPortfolioSub("positions");
|
|
290
|
+
saveProgressInBackground({ stage: back.stage });
|
|
291
|
+
}, [isBrokerCommitting, planAccess.hasProAccess, progress, saveProgressInBackground]);
|
|
264
292
|
|
|
265
293
|
const sectionAvailability: Partial<Record<OnboardingSectionId, boolean>> = {
|
|
266
|
-
portfolio: !isBrokerCommitting,
|
|
294
|
+
portfolio: !isBrokerCommitting && !workspaceBuilt,
|
|
267
295
|
// The workspace is built when the desks are picked, so that step comes first.
|
|
268
296
|
cloud: !isBrokerCommitting && stage !== "desks" && (stage === "account" || stage === "upgrade" || stage === "ready" || !!progress.tickerSymbol),
|
|
269
297
|
pro: !isBrokerCommitting && planAccess.signedIn && (
|
|
@@ -282,7 +310,7 @@ function useOnboardingWizard({ pluginRegistry, importBrokerPositions, onComplete
|
|
|
282
310
|
submitAccountField, backPortfolio, continuePortfolio, setPortfolioOptionIdx, activeBrokerFields,
|
|
283
311
|
brokerFieldIdx, setBrokerSelectIdx, brokerChoices, openBrokerGuide, chosenDesks, finishDesks,
|
|
284
312
|
setDeskCursor, deskCursor, setChosenDesks, continueAccount, skipSetup, primaryUpgradeAction,
|
|
285
|
-
setBillingInterval, keybindings, dialogOpen, commandBarOpen, saveProgressInBackground,
|
|
313
|
+
setBillingInterval, keybindings, dialogOpen, commandBarOpen, saveProgressInBackground, goBack,
|
|
286
314
|
});
|
|
287
315
|
|
|
288
316
|
return {
|
|
@@ -295,7 +323,7 @@ function useOnboardingWizard({ pluginRegistry, importBrokerPositions, onComplete
|
|
|
295
323
|
openBrokerConnect, continueFromPositions, backPortfolio, continuePortfolio, chosenDesks, deskCursor,
|
|
296
324
|
setDeskCursor, setChosenDesks, buildingDesks, finishDesks, account, viewportWidth, viewportHeight, continueAccount,
|
|
297
325
|
offer, planAccess, pricing, billingInterval, setBillingInterval, continueFree, primaryUpgradeAction,
|
|
298
|
-
|
|
326
|
+
goBack, rerun,
|
|
299
327
|
};
|
|
300
328
|
}
|
|
301
329
|
|
|
@@ -310,7 +338,7 @@ export function OnboardingWizard(props: OnboardingWizardProps) {
|
|
|
310
338
|
openBrokerConnect, continueFromPositions, backPortfolio, continuePortfolio, chosenDesks, deskCursor,
|
|
311
339
|
setDeskCursor, setChosenDesks, buildingDesks, finishDesks, account, viewportWidth, viewportHeight, continueAccount,
|
|
312
340
|
offer, planAccess, pricing, billingInterval, setBillingInterval, continueFree, primaryUpgradeAction,
|
|
313
|
-
|
|
341
|
+
goBack, rerun,
|
|
314
342
|
} = useOnboardingWizard(props);
|
|
315
343
|
|
|
316
344
|
if (helpFocused) {
|
|
@@ -387,7 +415,8 @@ export function OnboardingWizard(props: OnboardingWizardProps) {
|
|
|
387
415
|
onDismiss={skipSetup}
|
|
388
416
|
dismissing={isFinishing}
|
|
389
417
|
dismissDisabled={isBrokerCommitting}
|
|
390
|
-
|
|
418
|
+
// The first run needs a holding; a run from Settings can be left as it was.
|
|
419
|
+
showDismiss={rerun}
|
|
391
420
|
/>
|
|
392
421
|
<OnboardingTitle
|
|
393
422
|
step={desktop ? undefined : t("PORTFOLIO")}
|
|
@@ -467,12 +496,14 @@ export function OnboardingWizard(props: OnboardingWizardProps) {
|
|
|
467
496
|
onNavigate={goToSection}
|
|
468
497
|
onDismiss={skipSetup}
|
|
469
498
|
dismissing={isFinishing}
|
|
470
|
-
|
|
499
|
+
dismissDisabled={buildingDesks}
|
|
500
|
+
showDismiss={rerun}
|
|
471
501
|
/>
|
|
472
502
|
<OnboardingTitle
|
|
473
503
|
step={desktop ? undefined : t("DESKS")}
|
|
474
|
-
title={t("What do you trade?")}
|
|
475
|
-
|
|
504
|
+
title={t("What do you trade most?")}
|
|
505
|
+
// A line of its own: the picks guide the workspace, they do not limit it.
|
|
506
|
+
description={`${t("Pick up to 3; each adds a ready-made tab.")}\n${t("Everything else is one search away.")}`}
|
|
476
507
|
/>
|
|
477
508
|
{/* Not allowed to shrink: on a short window the card scrolls instead of the list running under the buttons. */}
|
|
478
509
|
<DesksStep
|
|
@@ -594,7 +625,7 @@ export function OnboardingWizard(props: OnboardingWizardProps) {
|
|
|
594
625
|
<OnboardingButton
|
|
595
626
|
label="Back"
|
|
596
627
|
variant="ghost"
|
|
597
|
-
onPress={account.accountSub === "qr" || account.accountSub === "login" ? account.returnToAccountForm :
|
|
628
|
+
onPress={account.accountSub === "qr" || account.accountSub === "login" ? account.returnToAccountForm : goBack}
|
|
598
629
|
/>
|
|
599
630
|
{account.accountSub !== "qr" ? (
|
|
600
631
|
<OnboardingButton
|
|
@@ -621,7 +652,7 @@ export function OnboardingWizard(props: OnboardingWizardProps) {
|
|
|
621
652
|
: proCopy.note;
|
|
622
653
|
// Ranked: the data itself first, then what reads it.
|
|
623
654
|
const features = [
|
|
624
|
-
{ title:
|
|
655
|
+
{ title: t("Real-time market data"), description: t("Free is 15 minutes behind on quotes and 12 hours on news.") },
|
|
625
656
|
{ title: t("MCP server"), description: t("Claude Code, Codex or Cursor call Gloom's research tools.") },
|
|
626
657
|
{ title: t("Ask Gloom"), description: t("Answers cite filings, calls and news.") },
|
|
627
658
|
{ title: t("Earnings calls"), description: t("Transcripts, summaries, guidance and scores.") },
|
|
@@ -734,7 +765,7 @@ export function OnboardingWizard(props: OnboardingWizardProps) {
|
|
|
734
765
|
<OnboardingButton
|
|
735
766
|
label="Back"
|
|
736
767
|
variant="ghost"
|
|
737
|
-
onPress={
|
|
768
|
+
onPress={goBack}
|
|
738
769
|
/>
|
|
739
770
|
<OnboardingButton
|
|
740
771
|
label={isFinishing ? "Opening workspace..." : "Start exploring"}
|
|
@@ -7,6 +7,8 @@ import { formatMarketPrice } from "../../../market-data/market/format";
|
|
|
7
7
|
import { getRegularSessionDisplay } from "../../../market-data/market/status";
|
|
8
8
|
import { formatPercentRaw } from "../../../utils/format";
|
|
9
9
|
import { Button, NumberField, TextField } from "../../ui";
|
|
10
|
+
import { DelayedMark } from "../../ui/delayed-mark";
|
|
11
|
+
import { useFreeQuoteDelay } from "../../../api-client/plan-access";
|
|
10
12
|
import { ONBOARDING_DESKTOP } from "../onboarding-frame";
|
|
11
13
|
import {
|
|
12
14
|
POSITION_FIELDS,
|
|
@@ -31,7 +33,7 @@ const FIELD_LABELS: Record<PositionFieldId, string> = {
|
|
|
31
33
|
const FIELD_PLACEHOLDERS: Record<Exclude<PositionFieldId, "currency">, string> = {
|
|
32
34
|
ticker: "e.g. AAPL",
|
|
33
35
|
shares: "optional",
|
|
34
|
-
avgCost: "
|
|
36
|
+
avgCost: "last price",
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
/** A field's label; the size counts contracts once the symbol is a future. */
|
|
@@ -47,6 +49,8 @@ function fieldPlaceholder(field: PositionFieldId, state: OnboardingPositionsStat
|
|
|
47
49
|
/** Company name and last price for the symbol being typed, or why it will not resolve. */
|
|
48
50
|
function PreviewLine({ preview, error }: Pick<OnboardingPositionsState, "preview" | "error">) {
|
|
49
51
|
const colors = useThemeColors();
|
|
52
|
+
// Signed out or free, the price is the delayed one, and says so.
|
|
53
|
+
const delay = useFreeQuoteDelay(preview.status === "ready" ? preview.quote : null);
|
|
50
54
|
if (error) return <Text fg={colors.negative} wrapText>{error}</Text>;
|
|
51
55
|
if (preview.status === "idle") return null;
|
|
52
56
|
if (preview.status === "checking") {
|
|
@@ -58,11 +62,12 @@ function PreviewLine({ preview, error }: Pick<OnboardingPositionsState, "preview
|
|
|
58
62
|
const price = headline?.price;
|
|
59
63
|
const change = headline?.changePercent;
|
|
60
64
|
return (
|
|
61
|
-
<Box flexDirection="row" minWidth={0} overflow="hidden">
|
|
65
|
+
<Box flexDirection="row" alignItems="center" minWidth={0} overflow="hidden">
|
|
62
66
|
<Text fg={colors.text} attributes={TextAttributes.BOLD}>{preview.symbol}</Text>
|
|
63
67
|
{preview.name ? <Text fg={colors.textDim}>{` ${preview.name}`}</Text> : null}
|
|
64
68
|
{price != null ? <Text fg={colors.text}>{` ${formatMarketPrice(price, { maxWidth: 12 })}`}</Text> : null}
|
|
65
69
|
{change != null ? <Text fg={shownPriceColor(change, 2, colors)}>{` ${formatPercentRaw(change)}`}</Text> : null}
|
|
70
|
+
{price != null && delay != null ? <Box flexShrink={0} paddingLeft={1}><DelayedMark minutes={delay} /></Box> : null}
|
|
66
71
|
{preview.duplicate ? <Text fg={colors.textMuted}>{t(" already added")}</Text> : null}
|
|
67
72
|
</Box>
|
|
68
73
|
);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { useAppDispatch, useAppStateRef } from "../../state/app/context";
|
|
3
|
+
import { saveConfigImmediately } from "../../state/config-save-scheduler";
|
|
4
|
+
import type { OnboardingProgress } from "../../types/config";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Settings' Run Setup Again: setup from its first step, on what the user has.
|
|
8
|
+
* The holdings are the portfolio's own, and the desks setup made start
|
|
9
|
+
* ticked; finishing rebuilds setup's layouts in place and leaves the user's
|
|
10
|
+
* alone. `open` shows the wizard once the progress is saved; without it
|
|
11
|
+
* (a host that cannot run setup) there is nothing to run.
|
|
12
|
+
*/
|
|
13
|
+
export function useRunSetupAgain(open: (() => void) | undefined): (() => Promise<void>) | undefined {
|
|
14
|
+
const dispatch = useAppDispatch();
|
|
15
|
+
const stateRef = useAppStateRef();
|
|
16
|
+
const run = useCallback(async () => {
|
|
17
|
+
if (!open) return;
|
|
18
|
+
const progress: OnboardingProgress = { version: 1, stage: "portfolio", rerun: true };
|
|
19
|
+
await saveConfigImmediately({ ...stateRef.current.config, onboardingComplete: false, onboardingProgress: progress });
|
|
20
|
+
dispatch({ type: "SET_ONBOARDING_STATE", complete: false, progress });
|
|
21
|
+
open();
|
|
22
|
+
}, [dispatch, open, stateRef]);
|
|
23
|
+
return open ? run : undefined;
|
|
24
|
+
}
|