@gloom-sh/terminal 0.19.1 → 0.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/package.json +2 -1
- package/patches/electrobun@1.18.1.patch +108 -1
- package/src/account/upgrade-dialog.tsx +4 -23
- package/src/api-client/coalesce.ts +118 -0
- package/src/api-client/credit-boards.ts +13 -0
- package/src/api-client/crypto-etf-flows.ts +6 -0
- package/src/api-client/crypto-markets.ts +40 -0
- package/src/api-client/data.ts +201 -14
- package/src/api-client/equity-screener.ts +10 -0
- package/src/api-client/errors.ts +58 -0
- package/src/api-client/gpu.ts +29 -0
- package/src/api-client/index.ts +5 -2
- package/src/api-client/plan-access.ts +10 -1
- package/src/api-client/plan-rules.ts +35 -0
- package/src/api-client/research-activity.ts +1 -32
- package/src/api-client/types.ts +21 -1
- package/src/app/attention-notice.tsx +94 -0
- package/src/app/runtime/startup.ts +3 -1
- package/src/app.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +2 -1
- package/src/capabilities/chart-series.ts +2 -1
- package/src/capabilities/factories.ts +1 -0
- package/src/cli/ascii-output.ts +42 -0
- package/src/cli/base-converter.ts +10 -2
- package/src/cli/command-options.ts +16 -1
- package/src/cli/commands/crypto-hints.ts +23 -4
- package/src/cli/commands/market.ts +101 -27
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +14 -10
- package/src/cli/commands/search.ts +84 -25
- package/src/cli/commands/system.ts +22 -30
- package/src/cli/commands/ticker.ts +49 -9
- package/src/cli/commands/version.ts +33 -0
- package/src/cli/context.ts +6 -9
- package/src/cli/data-dir.ts +7 -0
- package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
- package/src/cli/desktop-pane-shot-browser.ts +320 -0
- package/src/cli/desktop-pane-shot-routes.ts +26 -1
- package/src/cli/desktop-pane-shot.ts +196 -256
- package/src/cli/early-command.ts +42 -0
- package/src/cli/earnings-calendar.ts +9 -0
- package/src/cli/entry.ts +19 -13
- package/src/cli/errors.ts +12 -2
- package/src/cli/glossary.ts +2 -1
- package/src/cli/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +189 -11
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +51 -5
- package/src/cli/listing-arg.ts +63 -10
- package/src/cli/options.ts +1 -1
- package/src/cli/output-file.ts +60 -13
- package/src/cli/pane-functions/catalog.ts +14 -3
- package/src/cli/pane-functions/data.ts +1 -1
- package/src/cli/pane-functions/dom.ts +45 -0
- package/src/cli/pane-functions/freshness.ts +46 -9
- package/src/cli/pane-functions/function-help.ts +17 -9
- package/src/cli/pane-functions/headless.ts +22 -14
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +6 -2
- package/src/cli/pane-functions/resolver.ts +27 -5
- package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
- package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
- package/src/cli/pane-functions/screenshot-http.ts +70 -0
- package/src/cli/pane-functions/screenshot-render.ts +19 -5
- package/src/cli/quote-answer.ts +49 -6
- package/src/cli/registry.ts +41 -13
- package/src/cli/result.ts +12 -6
- package/src/cli/ticker-resolution.ts +2 -1
- package/src/cli/types.ts +2 -0
- package/src/components/chart/composite/format.ts +3 -0
- package/src/components/chart-table/header.tsx +1 -1
- package/src/components/chart-table/layout.ts +28 -11
- package/src/components/command-bar/action-runtime.ts +4 -0
- package/src/components/command-bar/commands/direct/index.ts +18 -33
- package/src/components/command-bar/commands/direct/runtime.ts +5 -0
- package/src/components/command-bar/commands/registry.ts +10 -1
- package/src/components/command-bar/routes/root/command-items.ts +9 -4
- package/src/components/command-bar/routes/root/runtime.ts +51 -6
- package/src/components/command-bar/selection-runtime.ts +12 -29
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/header.tsx +10 -5
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +8 -2
- package/src/components/layout/pane/footer/model.ts +73 -6
- package/src/components/layout/pane/header.tsx +2 -2
- package/src/components/layout/pane/status-footer.ts +2 -1
- package/src/components/market-board/index.tsx +3 -2
- package/src/components/onboarding/desks-step.tsx +12 -4
- package/src/components/onboarding/first-run-workspace.ts +81 -4
- package/src/components/onboarding/onboarding-frame.tsx +4 -2
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +60 -26
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
- package/src/components/onboarding/run-setup-again.ts +24 -0
- package/src/components/onboarding/use-onboarding-progress.ts +33 -15
- package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
- package/src/components/onboarding/wizard-model.ts +57 -3
- package/src/components/onboarding/wizard-positions.ts +1 -1
- package/src/components/ui/delayed-mark.tsx +56 -0
- package/src/components/ui/display.tsx +16 -5
- package/src/components/ui/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +16 -3
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/core/app-services.ts +11 -2
- package/src/data/app-persistence.ts +9 -7
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/plugin-cache.ts +15 -0
- package/src/data/plugin-state-store.ts +81 -24
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +165 -90
- package/src/data/session-store.ts +31 -8
- package/src/data/sqlite/access.ts +324 -0
- package/src/data/sqlite/checkpointer.ts +285 -0
- package/src/data/sqlite/database.ts +80 -4
- package/src/data/sqlite/retry.ts +50 -9
- package/src/data/ticker-store.ts +57 -23
- package/src/docs/snapshot.json +191 -78
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -2
- package/src/layout/desk-keys.ts +4 -1
- package/src/layout/desks.ts +38 -5
- package/src/layout-marketplace/gallery-desktop.tsx +45 -16
- package/src/market-data/batched-reads.ts +16 -0
- package/src/market-data/coordinator/auxiliary.ts +4 -2
- package/src/market-data/coordinator/index.ts +11 -2
- package/src/market-data/futures-board.ts +1 -1
- package/src/market-data/futures-contract.ts +3 -1
- package/src/market-data/market/index-level.ts +6 -4
- package/src/market-data/published-apac-sessions.ts +6 -0
- package/src/market-data/realized-volatility.ts +4 -2
- package/src/news/aggregator.ts +7 -4
- package/src/news/news-model.ts +11 -0
- package/src/news/story-id.ts +22 -0
- package/src/news/ticker-symbols.ts +17 -1
- package/src/notifications/app-notifier.ts +35 -14
- package/src/plugins/auto-update-node.ts +10 -6
- package/src/plugins/builtin/account-management/form-components.tsx +1 -1
- package/src/plugins/builtin/account-management/index.tsx +2 -3
- package/src/plugins/builtin/account-management/model.ts +1 -1
- package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
- package/src/plugins/builtin/alerts/events.ts +10 -2
- package/src/plugins/builtin/alerts/index.tsx +5 -5
- package/src/plugins/builtin/alerts/research-builder.ts +5 -1
- package/src/plugins/builtin/alerts/research-rules.ts +18 -0
- package/src/plugins/builtin/alerts/targets.ts +4 -1
- package/src/plugins/builtin/attention/headless.ts +1 -1
- package/src/plugins/builtin/attention/index.tsx +2 -4
- package/src/plugins/builtin/attention/listing.ts +1 -1
- package/src/plugins/builtin/attention/series.ts +1 -1
- package/src/plugins/builtin/awards/index.tsx +1 -2
- package/src/plugins/builtin/backtest/client.ts +1 -0
- package/src/plugins/builtin/backtest/index.tsx +1 -2
- package/src/plugins/builtin/backtest/rules.ts +3 -1
- package/src/plugins/builtin/backtest/window.ts +8 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
- package/src/plugins/builtin/broker-manager/index.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +5 -0
- package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
- package/src/plugins/builtin/buildout/company/id.ts +2 -0
- package/src/plugins/builtin/buildout/company/model.ts +247 -0
- package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
- package/src/plugins/builtin/buildout/facilities.ts +309 -0
- package/src/plugins/builtin/buildout/format.ts +9 -0
- package/src/plugins/builtin/buildout/index.tsx +51 -5
- package/src/plugins/builtin/buildout/model/index.ts +62 -10
- package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
- package/src/plugins/builtin/buildout/model/types.ts +11 -2
- package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
- package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
- package/src/plugins/builtin/buildout/table-model.ts +26 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
- package/src/plugins/builtin/cds/headless.ts +10 -2
- package/src/plugins/builtin/cds/model.ts +7 -3
- package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
- package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
- package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
- package/src/plugins/builtin/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
- package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
- package/src/plugins/builtin/chart-composer/headless.ts +88 -21
- package/src/plugins/builtin/chart-composer/index.tsx +2 -3
- package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
- package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/controller/index.ts +1 -1
- package/src/plugins/builtin/chat/controller/messages.ts +15 -4
- package/src/plugins/builtin/chat/ghost-user.ts +8 -0
- package/src/plugins/builtin/chat/index.tsx +5 -5
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/index.tsx +1 -2
- package/src/plugins/builtin/cloud/plugin.tsx +6 -4
- package/src/plugins/builtin/cloud/team/module.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
- package/src/plugins/builtin/comment-letters/index.tsx +1 -2
- package/src/plugins/builtin/commodity-balances/client.ts +4 -1
- package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
- package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
- package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
- package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
- package/src/plugins/builtin/congress-trades/headless.ts +2 -0
- package/src/plugins/builtin/congress-trades/model.ts +92 -64
- package/src/plugins/builtin/congress-trades/table.tsx +14 -2
- package/src/plugins/builtin/connections/index.ts +2 -3
- package/src/plugins/builtin/cot/client.ts +28 -2
- package/src/plugins/builtin/cot/headless.ts +4 -1
- package/src/plugins/builtin/cot/index.tsx +5 -4
- package/src/plugins/builtin/cot/model.ts +58 -7
- package/src/plugins/builtin/cot/pane.tsx +6 -2
- package/src/plugins/builtin/cpi/client.ts +6 -5
- package/src/plugins/builtin/cpi/index.tsx +1 -2
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/client.ts +7 -2
- package/src/plugins/builtin/credit-boards/headless.ts +25 -12
- package/src/plugins/builtin/credit-boards/model.ts +29 -0
- package/src/plugins/builtin/credit-boards/series.ts +3 -4
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/credit-conditions/client.ts +22 -14
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- package/src/plugins/builtin/crypto-board/client.ts +5 -5
- package/src/plugins/builtin/crypto-board/headless.ts +4 -3
- package/src/plugins/builtin/crypto-board/index.tsx +4 -2
- package/src/plugins/builtin/crypto-board/series.ts +6 -4
- package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
- package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
- package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
- package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
- package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
- package/src/plugins/builtin/debt-maturities/model.ts +17 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
- package/src/plugins/builtin/debt-maturities/series.ts +3 -3
- package/src/plugins/builtin/debug/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/client.ts +32 -2
- package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
- package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
- package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
- package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
- package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
- package/src/plugins/builtin/dividend-yield/view.ts +21 -0
- package/src/plugins/builtin/docs/index.tsx +2 -3
- package/src/plugins/builtin/doe/client.ts +6 -5
- package/src/plugins/builtin/doe/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/doe/prices.ts +2 -3
- package/src/plugins/builtin/doe/series.ts +4 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
- package/src/plugins/builtin/earnings-calls/data.ts +19 -11
- package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
- package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
- package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
- package/src/plugins/builtin/econ/calendar-model.ts +29 -6
- package/src/plugins/builtin/econ/detail-view.tsx +16 -2
- package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +1 -0
- package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
- package/src/plugins/builtin/equity-screener/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/model.ts +28 -4
- package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
- package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
- package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
- package/src/plugins/builtin/executives/data.ts +6 -1
- package/src/plugins/builtin/executives/pane.tsx +9 -21
- package/src/plugins/builtin/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/cache.ts +4 -0
- package/src/plugins/builtin/fear-greed/cli.ts +44 -0
- package/src/plugins/builtin/fear-greed/index.tsx +5 -53
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
- package/src/plugins/builtin/fear-greed/series.ts +6 -4
- package/src/plugins/builtin/fixed-income/index.tsx +1 -2
- package/src/plugins/builtin/futures/board-reads.ts +25 -0
- package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
- package/src/plugins/builtin/futures/index.tsx +15 -0
- package/src/plugins/builtin/futures/model.ts +14 -0
- package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
- package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
- package/src/plugins/builtin/futures-curve/headless.ts +6 -1
- package/src/plugins/builtin/futures-curve/index.tsx +1 -2
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +51 -12
- package/src/plugins/builtin/gpu/client.ts +19 -3
- package/src/plugins/builtin/gpu/equities.tsx +5 -2
- package/src/plugins/builtin/gpu/headless.ts +14 -2
- package/src/plugins/builtin/gpu/index.tsx +1 -2
- package/src/plugins/builtin/gpu/model.ts +20 -2
- package/src/plugins/builtin/gpu/pane.tsx +1 -1
- package/src/plugins/builtin/gpu/series.ts +2 -3
- package/src/plugins/builtin/help/index.tsx +1 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
- package/src/plugins/builtin/holders/index-holders.ts +24 -12
- package/src/plugins/builtin/holders/index.tsx +2 -3
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
- package/src/plugins/builtin/iv-history/index.tsx +1 -2
- package/src/plugins/builtin/jodi/client.ts +4 -1
- package/src/plugins/builtin/jodi/index.tsx +1 -2
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
- package/src/plugins/builtin/layout-manager/index.tsx +9 -8
- package/src/plugins/builtin/macro-day/pane.tsx +4 -2
- package/src/plugins/builtin/market-halts/index.tsx +1 -2
- package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
- package/src/plugins/builtin/market-movers/client.ts +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +16 -5
- package/src/plugins/builtin/market-movers/screener.ts +23 -6
- package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
- package/src/plugins/builtin/market-valuation/index.tsx +1 -2
- package/src/plugins/builtin/members/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/pane.tsx +2 -1
- package/src/plugins/builtin/money-markets/series.ts +3 -5
- package/src/plugins/builtin/news/headless.ts +7 -0
- package/src/plugins/builtin/news/wire/index.tsx +1 -1
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
- package/src/plugins/builtin/notes/index.tsx +3 -6
- package/src/plugins/builtin/openfda/index.tsx +1 -2
- package/src/plugins/builtin/options/view.tsx +6 -5
- package/src/plugins/builtin/options-calculator/index.tsx +1 -2
- package/src/plugins/builtin/options-positioning/index.tsx +1 -2
- package/src/plugins/builtin/options-scenario/index.tsx +2 -4
- package/src/plugins/builtin/perps/index.tsx +1 -2
- package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
- package/src/plugins/builtin/plugin-module.ts +16 -2
- package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
- package/src/plugins/builtin/power/gas-client.ts +2 -1
- package/src/plugins/builtin/power/index.tsx +1 -2
- package/src/plugins/builtin/power/prices-client.ts +3 -2
- package/src/plugins/builtin/rate-path/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/model.ts +16 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
- package/src/plugins/builtin/research/analyst-headless.ts +33 -6
- package/src/plugins/builtin/research/analyst-model.ts +37 -2
- package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
- package/src/plugins/builtin/research/index.tsx +5 -8
- package/src/plugins/builtin/research-search/index.tsx +1 -2
- package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
- package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
- package/src/plugins/builtin/risk-factors/data.ts +8 -4
- package/src/plugins/builtin/risk-factors/index.tsx +3 -1
- package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
- package/src/plugins/builtin/roadmap/index.tsx +2 -3
- package/src/plugins/builtin/roadmap/pane.tsx +49 -2
- package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
- package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
- package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
- package/src/plugins/builtin/scanner/lock-note.ts +26 -3
- package/src/plugins/builtin/sec/filing-content.ts +38 -6
- package/src/plugins/builtin/sec/headless.ts +5 -1
- package/src/plugins/builtin/sec/index.tsx +4 -2
- package/src/plugins/builtin/sec/model.ts +2 -0
- package/src/plugins/builtin/sectors/index.tsx +11 -5
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
- package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
- package/src/plugins/builtin/shared/forming-bars.ts +29 -0
- package/src/plugins/builtin/shared/fred-points.ts +6 -1
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- package/src/plugins/builtin/shared/series-warning.ts +13 -4
- package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
- package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/series.ts +2 -3
- package/src/plugins/builtin/social-mentions/client.ts +15 -10
- package/src/plugins/builtin/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- package/src/plugins/builtin/social-mentions/series.ts +2 -3
- package/src/plugins/builtin/supply-chain/client.ts +22 -1
- package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
- package/src/plugins/builtin/supply-chain/index.tsx +1 -2
- package/src/plugins/builtin/thirteenf/api.ts +127 -29
- package/src/plugins/builtin/thirteenf/data.ts +165 -35
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
- package/src/plugins/builtin/thirteenf/types.ts +14 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
- package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
- package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
- package/src/plugins/builtin/time-sales/index.tsx +1 -2
- package/src/plugins/builtin/time-sales/pane.tsx +3 -1
- package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
- package/src/plugins/builtin/trace-bonds/model.ts +44 -10
- package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
- package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
- package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
- package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
- package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
- package/src/plugins/builtin/vol-surface/client.ts +18 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -2
- package/src/plugins/builtin/volatility/client.ts +43 -16
- package/src/plugins/builtin/volatility/headless.ts +5 -2
- package/src/plugins/builtin/volatility/index.tsx +1 -2
- package/src/plugins/builtin/volatility/model.ts +17 -6
- package/src/plugins/builtin/volatility/pane.tsx +6 -2
- package/src/plugins/builtin/volatility/series.ts +2 -1
- package/src/plugins/builtin/world-indices/footer.ts +6 -1
- package/src/plugins/builtin/world-venue-map/client.ts +10 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -2
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
- package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
- package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
- package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
- package/src/plugins/builtin/yield-curve/client.ts +4 -6
- package/src/plugins/builtin/yield-curve/curves.ts +16 -2
- package/src/plugins/builtin/yield-curve/headless.ts +3 -3
- package/src/plugins/bundle.ts +52 -2
- package/src/plugins/catalog-browser.ts +2 -2
- package/src/plugins/catalog-cli.ts +20 -0
- package/src/plugins/host-link.ts +13 -1
- package/src/plugins/installer.ts +64 -42
- package/src/plugins/loader.ts +43 -11
- package/src/plugins/peer-imports.ts +201 -0
- package/src/plugins/peer-retry.ts +26 -29
- package/src/plugins/plugin-process.ts +128 -0
- package/src/plugins/redirection-guard.ts +45 -0
- package/src/remote/endpoint.ts +16 -3
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/http-proxy.ts +7 -123
- package/src/renderers/dom/desktop/icons.tsx +5 -2
- package/src/renderers/dom/host/image-surface.tsx +46 -21
- package/src/renderers/dom/styles.css +7 -0
- package/src/renderers/dom/toast-host.tsx +2 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
- package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
- package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
- package/src/renderers/electrobun/bun/window/attention.ts +54 -0
- package/src/renderers/electrobun/bun/window/create.ts +6 -2
- package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
- package/src/renderers/electrobun/view/main.tsx +7 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/renderers/electrobun/view/ui-host.tsx +9 -2
- package/src/renderers/opentui/browser-open.ts +10 -4
- package/src/renderers/opentui/image/surface.tsx +1 -1
- package/src/renderers/opentui/start.tsx +12 -6
- package/src/renderers/opentui/toast-host.tsx +2 -1
- package/src/series/registry.ts +18 -0
- package/src/series/resolve.ts +2 -1
- package/src/series/standalone.ts +10 -1
- package/src/series/types.ts +12 -0
- package/src/series/validate.ts +11 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
- package/src/sources/gloomberb-cloud/index.ts +28 -5
- package/src/sources/gloomberb-cloud/news.ts +24 -3
- package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
- package/src/sources/provider-errors.ts +26 -3
- package/src/sources/provider-router/history.ts +5 -0
- package/src/sources/provider-router/index.ts +13 -0
- package/src/sync/core-contributors.ts +32 -0
- package/src/telemetry/attention-counts.ts +64 -6
- package/src/telemetry/attention-setting.ts +33 -0
- package/src/telemetry/crash-reports-node.ts +3 -1
- package/src/telemetry/stall-clocks.ts +93 -0
- package/src/telemetry/stall-reports-node.ts +25 -9
- package/src/telemetry/stall-reports.ts +218 -13
- package/src/test-support/outage.ts +8 -0
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name-listing.ts +59 -29
- package/src/tickers/company-name.ts +84 -1
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +104 -8
- package/src/tickers/search/index.ts +5 -2
- package/src/tickers/search/types.ts +6 -1
- package/src/time-series/bar-aggregation.ts +7 -1
- package/src/time-series/base-series.ts +3 -3
- package/src/time-series/chart-bounds.ts +1 -1
- package/src/time-series/chart-data.ts +5 -2
- package/src/time-series/chart-seed.ts +1 -1
- package/src/time-series/coin-session-close.ts +211 -0
- package/src/time-series/live-bars.ts +13 -2
- package/src/time-series/live-quotes.ts +1 -1
- package/src/time-series/market.ts +5 -3
- package/src/time-series/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +75 -8
- package/src/time-series/resolve.ts +32 -4
- package/src/time-series/series-presentation.ts +1 -1
- package/src/time-series/studies/formula.ts +205 -0
- package/src/time-series/studies/indicators.ts +299 -0
- package/src/time-series/studies/leverage.ts +75 -0
- package/src/time-series/studies/pair.ts +138 -0
- package/src/time-series/studies/resolve.ts +333 -0
- package/src/time-series/studies/shared.ts +141 -0
- package/src/time-series/studies/units.ts +243 -0
- package/src/time-series/studies/vwap.ts +274 -0
- package/src/time-series/types.ts +7 -0
- package/src/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +40 -3
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +17 -1
- package/src/ui/toast.tsx +26 -1
- package/src/utils/ascii-text.ts +140 -0
- package/src/utils/cli-output.ts +91 -18
- package/src/utils/concurrency.ts +24 -0
- package/src/utils/exchanges.ts +6 -0
- package/src/utils/external-url.ts +20 -7
- package/src/utils/financial-statements.ts +73 -16
- package/src/utils/fundamentals.ts +17 -5
- package/src/utils/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/lease-liabilities.ts +54 -0
- package/src/utils/main-thread-stall.ts +122 -5
- package/src/utils/perf-marks.ts +58 -12
- package/src/utils/price-history-integrity.ts +47 -1
- package/src/utils/private-files.ts +141 -0
- package/src/utils/public-address.ts +99 -0
- package/src/account/upgrade-personalization.ts +0 -57
- package/src/account/upgrade-tickers.ts +0 -124
- package/src/api-client/upgrade-experiment.ts +0 -91
- package/src/plugins/builtin/cloud/browser.tsx +0 -3
- package/src/time-series/studies.ts +0 -1628
|
@@ -19,9 +19,63 @@ const LEGACY_STAGES: Partial<Record<OnboardingStage, OnboardingStage>> = {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export function getOnboardingProgress(config: AppConfig): OnboardingProgress {
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
22
|
+
const saved = config.onboardingProgress ?? { version: 1, stage: "portfolio" };
|
|
23
|
+
const legacy = LEGACY_STAGES[saved.stage];
|
|
24
|
+
const progress = legacy ? { ...saved, stage: legacy } : saved;
|
|
25
|
+
// Older builds sent Back from Gloom Cloud to the portfolio after the
|
|
26
|
+
// workspace was built; such a profile resumes on the workspace.
|
|
27
|
+
return isOnboardingStageFinished(progress, progress.stage) ? { ...progress, stage: "research" } : progress;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Stages the wizard reaches only once the workspace is built. */
|
|
31
|
+
const WORKSPACE_STAGES: ReadonlySet<OnboardingStage> = new Set(["research", "account", "upgrade", "ready"]);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Whether the workspace is built around a holding: the portfolio and desk
|
|
35
|
+
* steps are finished. Older builds saved no desks, so a later stage with a
|
|
36
|
+
* holding counts too.
|
|
37
|
+
*/
|
|
38
|
+
export function isOnboardingWorkspaceBuilt(progress: OnboardingProgress): boolean {
|
|
39
|
+
return !!progress.tickerSymbol && (Array.isArray(progress.desks) || WORKSPACE_STAGES.has(progress.stage));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A step that is done for good: the portfolio and desk steps once the
|
|
44
|
+
* workspace is built (building twice would add every desk twice), and Pro
|
|
45
|
+
* for an account that has it. The wizard shows them as done and never asks
|
|
46
|
+
* them again.
|
|
47
|
+
*/
|
|
48
|
+
function isOnboardingStageFinished(progress: OnboardingProgress, stage: OnboardingStage, pro = false): boolean {
|
|
49
|
+
if (stage === "upgrade") return pro;
|
|
50
|
+
return (stage === "portfolio" || stage === "desks") && isOnboardingWorkspaceBuilt(progress);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Where Back (and Esc) goes: the step the user came from, passing over any
|
|
55
|
+
* that is finished for good (`pro`: the account has Pro). With no way back
|
|
56
|
+
* recorded (the app restarted on this step), the step before it in the
|
|
57
|
+
* flow. `visited` is the stages the user came through, oldest first; what
|
|
58
|
+
* comes back is what is left of it. Null when the step has no way back.
|
|
59
|
+
*/
|
|
60
|
+
export function previousOnboardingStage(
|
|
61
|
+
progress: OnboardingProgress,
|
|
62
|
+
visited: readonly OnboardingStage[],
|
|
63
|
+
{ pro = false }: { pro?: boolean } = {},
|
|
64
|
+
): { stage: OnboardingStage; visited: OnboardingStage[] } | null {
|
|
65
|
+
for (let index = visited.length - 1; index >= 0; index -= 1) {
|
|
66
|
+
const stage = visited[index]!;
|
|
67
|
+
if (stage !== progress.stage && !isOnboardingStageFinished(progress, stage, pro)) {
|
|
68
|
+
return { stage, visited: visited.slice(0, index) };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const stage: OnboardingStage | null = progress.stage === "account"
|
|
72
|
+
? isOnboardingWorkspaceBuilt(progress) ? "research" : "portfolio"
|
|
73
|
+
: progress.stage === "upgrade"
|
|
74
|
+
? "account"
|
|
75
|
+
: progress.stage === "ready"
|
|
76
|
+
? progress.accountStatus === "signed-in" && !pro ? "upgrade" : "account"
|
|
77
|
+
: null;
|
|
78
|
+
return stage ? { stage, visited: [] } : null;
|
|
25
79
|
}
|
|
26
80
|
|
|
27
81
|
export function withOnboardingProgress(
|
|
@@ -302,7 +302,7 @@ export function useOnboardingPositions({
|
|
|
302
302
|
const costBasis = avgCost ?? result.quote?.price;
|
|
303
303
|
if (costBasis === undefined || costBasis === null || !Number.isFinite(costBasis)) {
|
|
304
304
|
setFieldIdx(2);
|
|
305
|
-
setError(t("No
|
|
305
|
+
setError(t("No price yet. Enter the average cost."));
|
|
306
306
|
return false;
|
|
307
307
|
}
|
|
308
308
|
// A future is entered in contracts, sized by its terms; a cost typed as corn is quoted counts cents.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Box, Text, useUiCapabilities } from "../../ui";
|
|
2
|
+
import { useThemeColors } from "../../theme/theme-context";
|
|
3
|
+
import { tf } from "../../i18n";
|
|
4
|
+
import { displayWidth } from "../../utils/format";
|
|
5
|
+
import { ICON_GLYPHS, Icon } from "./icon";
|
|
6
|
+
|
|
7
|
+
/** The terminal's form of the mark: the clock and the delay, as the pane footer draws it (`◷15m`). */
|
|
8
|
+
function delayedMarkText(minutes: number): string {
|
|
9
|
+
return `${ICON_GLYPHS.clock}${minutes}m`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** The delay the mark spells out after its clock: `15m`. */
|
|
13
|
+
function delayedMarkDelay(minutes: number): string {
|
|
14
|
+
return `${minutes}m`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Cells the mark takes: `◷15m` in the terminal; on the desktop and the web the
|
|
19
|
+
* clock icon and its gap (12px and 4px, three cells at the narrowest cell) and `15m`.
|
|
20
|
+
*/
|
|
21
|
+
export function delayedMarkWidth(minutes: number, nativePaneChrome: boolean): number {
|
|
22
|
+
return nativePaneChrome ? 3 + displayWidth(delayedMarkDelay(minutes)) : displayWidth(delayedMarkText(minutes));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The one mark for a price shown late, beside the price: what the pane
|
|
27
|
+
* footer's clock is for a pane's data (`delayedFooterSegment`), for a price
|
|
28
|
+
* outside a footer (the header's SPY, the status bar, a quote's headline).
|
|
29
|
+
* It reads without hovering: a yellow clock and the delay (`15m`) in the dim
|
|
30
|
+
* tone of the meta text around it on the desktop and the web, `◷15m` in the
|
|
31
|
+
* terminal, with `15m delayed` in its tooltip. Callers decide when a price is
|
|
32
|
+
* late with `freeQuoteDelayMinutes` (or `useFreeQuoteDelay`); it draws nothing else.
|
|
33
|
+
*/
|
|
34
|
+
export function DelayedMark({ minutes }: { minutes: number }) {
|
|
35
|
+
const colors = useThemeColors();
|
|
36
|
+
const { nativePaneChrome } = useUiCapabilities();
|
|
37
|
+
const detail = tf("{delay} delayed", { delay: `${minutes}m` });
|
|
38
|
+
if (!nativePaneChrome) {
|
|
39
|
+
return <Text fg={colors.warning} flexShrink={0} selectable={false} aria-label={detail}>{delayedMarkText(minutes)}</Text>;
|
|
40
|
+
}
|
|
41
|
+
return (
|
|
42
|
+
<Box
|
|
43
|
+
role="img"
|
|
44
|
+
aria-label={detail}
|
|
45
|
+
title={detail}
|
|
46
|
+
flexShrink={0}
|
|
47
|
+
flexDirection="row"
|
|
48
|
+
alignItems="center"
|
|
49
|
+
data-gloom-role="delayed-mark"
|
|
50
|
+
style={{ gap: "var(--gloom-space-xs)" }}
|
|
51
|
+
>
|
|
52
|
+
<Icon name="clock" color={colors.warning} />
|
|
53
|
+
<Text fg={colors.textDim} selectable={false}>{delayedMarkDelay(minutes)}</Text>
|
|
54
|
+
</Box>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -214,8 +214,18 @@ export interface BadgeProps {
|
|
|
214
214
|
export const NATIVE_BADGE_STYLE = { height: "calc(100% - 2px)", borderRadius: "var(--gloom-radius-sm)" };
|
|
215
215
|
/** How far a subtle chip's fill is tinted toward its tone. */
|
|
216
216
|
export const BADGE_TINT = 0.28;
|
|
217
|
-
const NATIVE_BADGE_TEXT_STYLE = { fontWeight: "var(--gloom-weight-regular)" };
|
|
218
217
|
const NATIVE_ICON_BADGE_STYLE = { ...NATIVE_BADGE_STYLE, gap: "var(--gloom-space-xs)" };
|
|
218
|
+
const NATIVE_BADGE_TEXT_STYLE = { fontWeight: "var(--gloom-weight-regular)" };
|
|
219
|
+
/**
|
|
220
|
+
* A label with lowercase letters has them in the middle of the chip,
|
|
221
|
+
* wherever the font puts them in its line: the line is cut to x-height and
|
|
222
|
+
* baseline, and that is what the chip centres. Centred whole, SF Mono's
|
|
223
|
+
* lowercase sit a pixel and a half low. An icon centres in the chip, so it
|
|
224
|
+
* reads on the letters' line. A label in capitals and digits keeps the
|
|
225
|
+
* whole line, as ticker chips do; a browser without `text-box` keeps it too.
|
|
226
|
+
*/
|
|
227
|
+
const NATIVE_LOWERCASE_BADGE_TEXT_STYLE = { ...NATIVE_BADGE_TEXT_STYLE, textBox: "trim-both ex alphabetic" };
|
|
228
|
+
const LOWERCASE = /\p{Ll}/u;
|
|
219
229
|
|
|
220
230
|
export function Badge({ label, tone = "neutral", color, variant = "subtle", icon, translate = true }: BadgeProps) {
|
|
221
231
|
const colors = useThemeColors();
|
|
@@ -233,11 +243,12 @@ export function Badge({ label, tone = "neutral", color, variant = "subtle", icon
|
|
|
233
243
|
const chip = (
|
|
234
244
|
<Box height={nativePaneChrome ? undefined : 1} paddingX={1} backgroundColor={background}
|
|
235
245
|
data-gloom-ui="badge" {...(nativeIcon ? { flexDirection: "row", alignItems: "center" } as const : {})}
|
|
236
|
-
justifyContent={nativePaneChrome && !
|
|
237
|
-
style={nativePaneChrome ?
|
|
238
|
-
{nativeIcon ? <Icon name={nativeIcon} color={foreground} /> : null}
|
|
246
|
+
justifyContent={nativePaneChrome && !nativeIcon ? "center" : undefined}
|
|
247
|
+
style={nativePaneChrome ? nativeIcon ? NATIVE_ICON_BADGE_STYLE : NATIVE_BADGE_STYLE : undefined}>
|
|
248
|
+
{nativeIcon ? <Icon name={nativeIcon} color={foreground} fullHeight /> : null}
|
|
239
249
|
{shown || !nativeIcon ? (
|
|
240
|
-
<Text fg={foreground} attributes={TextAttributes.BOLD}
|
|
250
|
+
<Text fg={foreground} attributes={TextAttributes.BOLD}
|
|
251
|
+
style={nativePaneChrome ? LOWERCASE.test(text) ? NATIVE_LOWERCASE_BADGE_TEXT_STYLE : NATIVE_BADGE_TEXT_STYLE : undefined}>{shown}</Text>
|
|
241
252
|
) : null}
|
|
242
253
|
</Box>
|
|
243
254
|
);
|
|
@@ -3,7 +3,7 @@ import { Box, Text } from "../../ui";
|
|
|
3
3
|
import { TextAttributes } from "../../ui";
|
|
4
4
|
import { tf } from "../../i18n";
|
|
5
5
|
import { useThemeColors } from "../../theme/theme-context";
|
|
6
|
-
import { safeExternalUrl } from "../../utils/external-url";
|
|
6
|
+
import { safeExternalUrl, systemOpenCommand } from "../../utils/external-url";
|
|
7
7
|
import { linkContextMenuItems, useContextMenu, useRendererHost, useUiCapabilities } from "../../ui";
|
|
8
8
|
import type { ContextMenuItem } from "../../types/context-menu";
|
|
9
9
|
import { usePaneMenuItems } from "../layout/pane/footer/registration";
|
|
@@ -20,11 +20,8 @@ export function openUrl(rawUrl: string) {
|
|
|
20
20
|
|
|
21
21
|
if (typeof Bun !== "undefined" && typeof Bun.spawn === "function") {
|
|
22
22
|
const platform = typeof process !== "undefined" ? process.platform : "linux";
|
|
23
|
-
const command = platform
|
|
24
|
-
|
|
25
|
-
: platform === "win32"
|
|
26
|
-
? ["cmd", "/c", "start", "", url]
|
|
27
|
-
: ["xdg-open", url];
|
|
23
|
+
const command = systemOpenCommand(url, platform);
|
|
24
|
+
if (!command) return;
|
|
28
25
|
// Spawning throws when the opener is not installed (no xdg-open on
|
|
29
26
|
// minimal Linux and WSL); a link that does nothing beats a crash.
|
|
30
27
|
try {
|
|
@@ -94,13 +94,19 @@ export interface IconProps {
|
|
|
94
94
|
/** Pixel size on the desktop; the terminal draws one glyph. */
|
|
95
95
|
size?: number;
|
|
96
96
|
color?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Desktop: takes its row's full height and draws in the middle of it. A
|
|
99
|
+
* box centred in a row an odd number of pixels taller lands on a whole
|
|
100
|
+
* pixel, half a pixel off; the drawing inside the box does not.
|
|
101
|
+
*/
|
|
102
|
+
fullHeight?: boolean;
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
/** One glyph from the app's icon set. */
|
|
100
|
-
export function Icon({ name, size = 12, color }: IconProps) {
|
|
106
|
+
export function Icon({ name, size = 12, color, fullHeight }: IconProps) {
|
|
101
107
|
const colors = useThemeColors();
|
|
102
108
|
const HostIcon = useUiHost().Icon as ComponentType<IconProps> | undefined;
|
|
103
|
-
if (HostIcon) return <HostIcon name={name} size={size} color={color} />;
|
|
109
|
+
if (HostIcon) return <HostIcon name={name} size={size} color={color} fullHeight={fullHeight} />;
|
|
104
110
|
return <Text fg={color ?? colors.textDim} selectable={false}>{ICON_GLYPHS[name]}</Text>;
|
|
105
111
|
}
|
|
106
112
|
|
|
@@ -132,6 +138,11 @@ export interface IconButtonProps {
|
|
|
132
138
|
size?: number;
|
|
133
139
|
/** Keep the press from also reaching a row or header behind the button. */
|
|
134
140
|
stopPropagation?: boolean;
|
|
141
|
+
/**
|
|
142
|
+
* A few characters after the icon, in a muted tone, for a status that must
|
|
143
|
+
* read without hovering (a delay's `15m`). The label stays the accessible name.
|
|
144
|
+
*/
|
|
145
|
+
text?: string;
|
|
135
146
|
}
|
|
136
147
|
|
|
137
148
|
/**
|
|
@@ -151,6 +162,7 @@ export function IconButton({
|
|
|
151
162
|
disabled = false,
|
|
152
163
|
size = 12,
|
|
153
164
|
stopPropagation = true,
|
|
165
|
+
text,
|
|
154
166
|
}: IconButtonProps) {
|
|
155
167
|
const colors = useThemeColors();
|
|
156
168
|
const label = t(rawLabel);
|
|
@@ -176,6 +188,7 @@ export function IconButton({
|
|
|
176
188
|
disabled={disabled}
|
|
177
189
|
size={size}
|
|
178
190
|
stopPropagation={stopPropagation}
|
|
191
|
+
text={text}
|
|
179
192
|
/>
|
|
180
193
|
);
|
|
181
194
|
}
|
|
@@ -195,7 +208,7 @@ export function IconButton({
|
|
|
195
208
|
>
|
|
196
209
|
{/* Pressed takes the colour the desktop and the terminal pane header give an active toggle. */}
|
|
197
210
|
<Text fg={disabled ? colors.textMuted : color ?? (pressed ? colors.warning : colors.textDim)} selectable={false}>
|
|
198
|
-
{` ${ICON_GLYPHS[icon]} `}
|
|
211
|
+
{` ${ICON_GLYPHS[icon]}${text ?? ""} `}
|
|
199
212
|
</Text>
|
|
200
213
|
</Box>
|
|
201
214
|
);
|
|
@@ -23,15 +23,16 @@ const LETTER_STYLE: Record<LogoMarkSize, CSSProperties> = {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
export interface LogoMarkProps {
|
|
26
|
-
/** The logo, an image URL; without one,
|
|
26
|
+
/** The logo, an image URL; without one, while it loads and when it fails to load, the tile shows `name`'s initial. */
|
|
27
27
|
src?: string | null;
|
|
28
28
|
/** What the logo stands for, such as a broker. Only its initial is drawn; the label beside it names it. */
|
|
29
29
|
name: string;
|
|
30
30
|
size?: LogoMarkSize;
|
|
31
31
|
/**
|
|
32
|
-
* Draws the logo on a white plate
|
|
33
|
-
*
|
|
34
|
-
* theme, while a logo that
|
|
32
|
+
* Draws the logo on a white plate once it has loaded, never behind the
|
|
33
|
+
* initial. Company and coin logos are made for a light page: a black mark
|
|
34
|
+
* on a clear field (XRP's) would vanish on a dark theme, while a logo that
|
|
35
|
+
* fills its square covers the plate.
|
|
35
36
|
*/
|
|
36
37
|
plate?: boolean;
|
|
37
38
|
}
|
|
@@ -59,9 +60,11 @@ export function LogoMark({ src, name, size = "row", plate = false }: LogoMarkPro
|
|
|
59
60
|
src={src ?? undefined}
|
|
60
61
|
alt=""
|
|
61
62
|
objectFit="cover"
|
|
63
|
+
fallbackWhileLoading
|
|
64
|
+
imageBackground={plate ? LOGO_PLATE : undefined}
|
|
62
65
|
aria-hidden="true"
|
|
63
66
|
data-gloom-role="logo-mark"
|
|
64
|
-
style={{ width: tile.side, height: tile.side, flexShrink: 0, borderRadius: tile.radius
|
|
67
|
+
style={{ width: tile.side, height: tile.side, flexShrink: 0, borderRadius: tile.radius }}
|
|
65
68
|
>
|
|
66
69
|
<Box width="100%" height="100%" alignItems="center" justifyContent="center" backgroundColor={colors.border}>
|
|
67
70
|
<Text fg={colors.textBright} attributes={TextAttributes.BOLD} style={LETTER_STYLE[size]}>
|
|
@@ -2,7 +2,7 @@ import { t } from "../../i18n";
|
|
|
2
2
|
import type { ThemeColors } from "../../theme/colors";
|
|
3
3
|
import { useThemeColors } from "../../theme/theme-context";
|
|
4
4
|
import { Box, Text, TextAttributes, useUiCapabilities } from "../../ui";
|
|
5
|
-
import { displayWidth, truncateToDisplayWidth } from "../../utils/format";
|
|
5
|
+
import { clipToDisplayWidth, displayWidth, padTo, truncateToDisplayWidth } from "../../utils/format";
|
|
6
6
|
import { SplitBar, type SplitBarPart } from "./split-bar";
|
|
7
7
|
|
|
8
8
|
export type StatTone = "neutral" | "positive" | "negative" | "warning" | "accent" | "muted";
|
|
@@ -49,6 +49,13 @@ export interface StatGridProps {
|
|
|
49
49
|
width: number;
|
|
50
50
|
/** Fixed column count; by default as many as the widest item allows, up to 4, balanced over the rows. */
|
|
51
51
|
columns?: number;
|
|
52
|
+
/**
|
|
53
|
+
* The most lines the grid takes. A whole-row cell too narrow for its value
|
|
54
|
+
* and detail wraps onto a second line, the detail under the value; with
|
|
55
|
+
* `maxLines` only the leading such cells the lines leave room for do, and
|
|
56
|
+
* the rest keep one line, cut (`statGridRows` counts the same).
|
|
57
|
+
*/
|
|
58
|
+
maxLines?: number;
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
const MAX_COLUMNS = 4;
|
|
@@ -102,9 +109,207 @@ function layoutRows(items: StatItem[], columns: number): StatItem[][] {
|
|
|
102
109
|
return rows;
|
|
103
110
|
}
|
|
104
111
|
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
|
|
112
|
+
/** What a grid's cells share: the label column, the inner width and one cell's width, in terminal cells. */
|
|
113
|
+
function gridGeometry(items: StatItem[], width: number, columns: number, nativePaneChrome: boolean) {
|
|
114
|
+
const labels = labelChars(items);
|
|
115
|
+
const innerWidth = Math.max(1, width - (nativePaneChrome ? 0 : 2));
|
|
116
|
+
const cellWidth = Math.max(8, Math.floor((innerWidth - CELL_GAP * (columns - 1)) / columns));
|
|
117
|
+
const widestValue = Math.max(0, ...items.filter((item) => !item.wide).map((item) => displayWidth(item.value)));
|
|
118
|
+
return { labels, innerWidth, cellWidth, widestValue };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The label's column in a cell `width` wide: it grows into what the grid's
|
|
123
|
+
* widest value leaves, and never gets less than half the cell, so one long
|
|
124
|
+
* value cannot cut every label in a grid with a fixed column count.
|
|
125
|
+
*/
|
|
126
|
+
function cellLabelWidth(width: number, labels: number, widestValue: number): number {
|
|
127
|
+
return Math.min(labels + 1, Math.max(4, Math.floor(width * 0.5), width - widestValue - 1));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Cells a split bar takes after a value that leaves `room` in its column; none when too few to read. */
|
|
131
|
+
function splitBarCells(room: number): number {
|
|
132
|
+
return room >= 4 ? Math.min(SPLIT_BAR_CELLS, room) : 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const MONTH = "(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|June?|July?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\\.?";
|
|
136
|
+
/** A date, time or day count in a cell: `2026-10-29`, `18:00`, `Dec 9`, `30 Sep`, `Dec '26`, `2026`, `in 40 days`, `3d ago`. */
|
|
137
|
+
const DATED = new RegExp([
|
|
138
|
+
"\\d{4}-\\d{2}",
|
|
139
|
+
"\\b\\d{1,2}:\\d{2}\\b",
|
|
140
|
+
`\\b${MONTH} (?:'\\d{2}|\\d{1,4})\\b`,
|
|
141
|
+
`\\b\\d{1,2} ${MONTH}(?![a-z])`,
|
|
142
|
+
"\\b(?:19|20)\\d{2}\\b",
|
|
143
|
+
"\\bin \\d+ ?(?:d|days?|wks?|weeks?|mo|months?)\\b",
|
|
144
|
+
"\\b\\d+ ?(?:d|h|m|min|days?|hours?) ago\\b",
|
|
145
|
+
].join("|"));
|
|
146
|
+
|
|
147
|
+
/** `text` cut to `room` after its last whole word that fits, behind a `…`; mid-word only when its first word does not fit. */
|
|
148
|
+
function clipWords(text: string, room: number): string {
|
|
149
|
+
if (displayWidth(text) <= room) return text;
|
|
150
|
+
const words = text.split(" ");
|
|
151
|
+
for (let count = words.length - 1; count > 0; count--) {
|
|
152
|
+
const kept = `${words.slice(0, count).join(" ")}…`;
|
|
153
|
+
if (displayWidth(kept) <= room) return kept;
|
|
154
|
+
}
|
|
155
|
+
return clipToDisplayWidth(text, room);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* `text` cut to `width` without cutting a date: its ` · ` parts that have none
|
|
160
|
+
* give way first, from the last, cut short at a word and then to a `…` that
|
|
161
|
+
* says something is left out; then the words before a part's date
|
|
162
|
+
* (`1,561,837 shares… 16:02:15.123`). Only dates too wide for the width are
|
|
163
|
+
* cut at their end.
|
|
164
|
+
*/
|
|
165
|
+
function cutKeepingDates(text: string, width: number): string {
|
|
166
|
+
const parts = text.split(" · ");
|
|
167
|
+
const joined = () => parts.join(" · ");
|
|
168
|
+
const roomFor = (index: number) => width - (displayWidth(joined()) - displayWidth(parts[index]!));
|
|
169
|
+
for (let index = parts.length - 1; index >= 0 && displayWidth(joined()) > width; index--) {
|
|
170
|
+
const part = parts[index]!;
|
|
171
|
+
if (DATED.test(part)) continue;
|
|
172
|
+
const room = roomFor(index);
|
|
173
|
+
if (room >= 4) parts[index] = clipWords(part, room);
|
|
174
|
+
else if (parts[index + 1] !== "…" && part !== "…") parts[index] = "…";
|
|
175
|
+
else parts.splice(index, 1);
|
|
176
|
+
}
|
|
177
|
+
for (let index = parts.length - 1; index >= 0 && displayWidth(joined()) > width; index--) {
|
|
178
|
+
const part = parts[index]!;
|
|
179
|
+
const at = part.search(DATED);
|
|
180
|
+
if (at <= 0) continue;
|
|
181
|
+
const date = part.slice(at);
|
|
182
|
+
const room = roomFor(index) - displayWidth(date) - 1;
|
|
183
|
+
parts[index] = room >= 4 ? `${clipWords(part.slice(0, at).trimEnd(), room)} ${date}` : date;
|
|
184
|
+
}
|
|
185
|
+
return truncateToDisplayWidth(joined(), width);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* `text` on at most two lines no wider than `width`: broken after the ` · `
|
|
190
|
+
* parts that fit the first, else at its last space that does; the second line
|
|
191
|
+
* is cut where it has no date (cutKeepingDates).
|
|
192
|
+
*/
|
|
193
|
+
function twoLines(text: string, width: number): string[] {
|
|
194
|
+
const parts = text.split(" · ");
|
|
195
|
+
let first = "";
|
|
196
|
+
let taken = 0;
|
|
197
|
+
for (; taken < parts.length; taken++) {
|
|
198
|
+
const next = first ? `${first} · ${parts[taken]}` : parts[taken]!;
|
|
199
|
+
if (displayWidth(next) > width) break;
|
|
200
|
+
first = next;
|
|
201
|
+
}
|
|
202
|
+
let rest = parts.slice(taken).join(" · ");
|
|
203
|
+
if (!first) {
|
|
204
|
+
// The first part alone is wider than a line: it breaks at a space, or is cut.
|
|
205
|
+
const words = parts[0]!.split(" ");
|
|
206
|
+
let count = 0;
|
|
207
|
+
while (count < words.length && displayWidth(words.slice(0, count + 1).join(" ")) <= width) count++;
|
|
208
|
+
first = count ? words.slice(0, count).join(" ") : truncateToDisplayWidth(words[0]!, width);
|
|
209
|
+
rest = [words.slice(Math.max(1, count)).join(" "), ...parts.slice(1)].filter(Boolean).join(" · ");
|
|
210
|
+
}
|
|
211
|
+
return rest ? [first, cutKeepingDates(rest, width)] : [first];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** A whole-row cell on its two lines (wrapStatCell). */
|
|
215
|
+
interface WrappedStatCell {
|
|
216
|
+
/** The value: one line, or both when it is wider than its column and the detail gives way. */
|
|
217
|
+
value: string[];
|
|
218
|
+
/** Cells of the split bar after the value on the first line; 0 for none. */
|
|
219
|
+
bar: number;
|
|
220
|
+
/** The detail on the second line, or null. */
|
|
221
|
+
detail: string | null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Lines a wrapped cell takes: its value, then its detail. */
|
|
225
|
+
const WRAPPED_CELL_LINES = 2;
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* A whole-row cell (a `wide` item, or any cell of a one-column grid) that does
|
|
229
|
+
* not fit one line, on two: the value and its split bar on the first, and the
|
|
230
|
+
* detail on the second, in the longest form that fits, else cut where it has
|
|
231
|
+
* no date. A value wider than its column takes both lines, its bar after the
|
|
232
|
+
* first where it has room, when the detail can go (there is none, the pane
|
|
233
|
+
* offers an empty form, or it has no date); with a dated detail the value is
|
|
234
|
+
* cut instead, as a date is never what gives way.
|
|
235
|
+
* Null when the cell fits one line as it is, with the detail form that fits
|
|
236
|
+
* beside the value (an empty form counts: the pane chose to show none). Worked
|
|
237
|
+
* out in terminal cells for both renderers, so the grid's rows are the same
|
|
238
|
+
* count on each.
|
|
239
|
+
*/
|
|
240
|
+
function wrapStatCell(item: StatItem, width: number, labelWidth: number): WrappedStatCell | null {
|
|
241
|
+
const valueWidth = Math.max(1, width - labelWidth);
|
|
242
|
+
const valueCells = displayWidth(item.value);
|
|
243
|
+
const forms = item.detail ? [item.detail, ...(item.detailVariants ?? [])] : [];
|
|
244
|
+
const shown = forms.filter(Boolean);
|
|
245
|
+
const detailLine = () => shown.find((form) => displayWidth(form) <= valueWidth) ?? cutKeepingDates(shown.at(-1)!, valueWidth);
|
|
246
|
+
if (valueCells <= valueWidth) {
|
|
247
|
+
const bar = item.split ? splitBarCells(valueWidth - valueCells - 1) : 0;
|
|
248
|
+
const detailRoom = valueWidth - valueCells - (bar ? bar + 1 : 0) - 2;
|
|
249
|
+
const shortest = sizingDetail(item);
|
|
250
|
+
if (!shortest || (detailRoom > 1 && displayWidth(shortest) <= detailRoom)) return null;
|
|
251
|
+
return { value: [item.value], bar, detail: detailLine() };
|
|
252
|
+
}
|
|
253
|
+
const detailCanGo = shown.length === 0 || shown.length < forms.length || !shown.some((form) => DATED.test(form));
|
|
254
|
+
if (detailCanGo) {
|
|
255
|
+
const value = twoLines(item.value, valueWidth);
|
|
256
|
+
return { value, bar: item.split ? splitBarCells(valueWidth - displayWidth(value[0]!) - 1) : 0, detail: null };
|
|
257
|
+
}
|
|
258
|
+
return { value: [cutKeepingDates(item.value, valueWidth)], bar: 0, detail: detailLine() };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** A laid-out row's one cell, wrapped, when it spans the row and does not fit one line (wrapStatCell). */
|
|
262
|
+
function wrappedRow(row: StatItem[], columns: number, geometry: ReturnType<typeof gridGeometry>): WrappedStatCell | null {
|
|
263
|
+
const item = row[0];
|
|
264
|
+
if (!item || row.length > 1 || (!item.wide && columns > 1)) return null;
|
|
265
|
+
const width = item.wide ? geometry.innerWidth : geometry.cellWidth;
|
|
266
|
+
return wrapStatCell(item, width, cellLabelWidth(width, geometry.labels, geometry.widestValue));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* The grid's rows and which of them wrap: every whole-row cell too narrow for
|
|
271
|
+
* one line, in order, while the grid stays within `maxLines`; past that a cell
|
|
272
|
+
* keeps its one line, cut. Decided in terminal cells on both renderers, so a
|
|
273
|
+
* pane sizes the same rows on each.
|
|
274
|
+
*/
|
|
275
|
+
function planStatGrid(items: StatItem[], width: number, columns: number, maxLines = Number.POSITIVE_INFINITY) {
|
|
276
|
+
const rows = layoutRows(items, columns);
|
|
277
|
+
const geometry = gridGeometry(items, width, columns, false);
|
|
278
|
+
let spare = maxLines - rows.length;
|
|
279
|
+
const wrapped = rows.map((row) => {
|
|
280
|
+
if (spare < WRAPPED_CELL_LINES - 1) return null;
|
|
281
|
+
const cell = wrappedRow(row, columns, geometry);
|
|
282
|
+
if (cell) spare -= WRAPPED_CELL_LINES - 1;
|
|
283
|
+
return cell;
|
|
284
|
+
});
|
|
285
|
+
const lines = rows.length + wrapped.filter(Boolean).length * (WRAPPED_CELL_LINES - 1);
|
|
286
|
+
return { rows, wrapped, lines };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Rows a StatGrid takes, so panes can size what sits below it: a whole-row
|
|
291
|
+
* cell that wraps counts both its lines. With `maxLines`, the grid drawn with
|
|
292
|
+
* the same `maxLines`.
|
|
293
|
+
*/
|
|
294
|
+
export function statGridRows(items: StatItem[], width: number, columns?: number, { maxLines }: { maxLines?: number } = {}): number {
|
|
295
|
+
return planStatGrid(items, width, Math.max(1, columns ?? statGridColumns(items, width)), maxLines).lines;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* The leading items that fit a band of `maxLines`, in whole rows: as many rows
|
|
300
|
+
* as there are lines, and the rows they leave for a whole-row cell to wrap
|
|
301
|
+
* into (`maxLines` on the grid, which `lines` counts).
|
|
302
|
+
*/
|
|
303
|
+
export function statGridWithin(items: StatItem[], width: number, maxLines: number, columns?: number): { items: StatItem[]; lines: number } {
|
|
304
|
+
if (maxLines < 1 || items.length === 0) return { items: [], lines: 0 };
|
|
305
|
+
const resolved = Math.max(1, columns ?? statGridColumns(items, width));
|
|
306
|
+
const shown = layoutRows(items, resolved).slice(0, maxLines).flat();
|
|
307
|
+
return { items: shown, lines: statGridRows(shown, width, resolved, { maxLines }) };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** A label in its column, cut and padded by terminal cells, so a wide character keeps the values aligned. */
|
|
311
|
+
function labelText(label: string, labelWidth: number): string {
|
|
312
|
+
return padTo(truncateToDisplayWidth(label, labelWidth - 1), labelWidth);
|
|
108
313
|
}
|
|
109
314
|
|
|
110
315
|
/**
|
|
@@ -114,16 +319,13 @@ export function statGridRows(items: StatItem[], width: number, columns?: number)
|
|
|
114
319
|
* hairline-split cells, so the top of the pane reads as one piece of chrome;
|
|
115
320
|
* the terminal draws aligned text columns.
|
|
116
321
|
*/
|
|
117
|
-
export function StatGrid({ items, width, columns: columnsProp }: StatGridProps) {
|
|
322
|
+
export function StatGrid({ items, width, columns: columnsProp, maxLines }: StatGridProps) {
|
|
118
323
|
const colors = useThemeColors();
|
|
119
324
|
const { nativePaneChrome } = useUiCapabilities();
|
|
120
325
|
if (items.length === 0) return null;
|
|
121
326
|
const columns = Math.max(1, columnsProp ?? statGridColumns(items, width));
|
|
122
|
-
const rows =
|
|
123
|
-
const labels =
|
|
124
|
-
const innerWidth = Math.max(1, width - (nativePaneChrome ? 0 : 2));
|
|
125
|
-
const cellWidth = Math.max(8, Math.floor((innerWidth - CELL_GAP * (columns - 1)) / columns));
|
|
126
|
-
const widestValue = Math.max(0, ...items.filter((item) => !item.wide).map((item) => displayWidth(item.value)));
|
|
327
|
+
const { rows, wrapped: wrapping, lines: lineCount } = planStatGrid(items, width, columns, maxLines);
|
|
328
|
+
const { labels, innerWidth, cellWidth, widestValue } = gridGeometry(items, width, columns, !!nativePaneChrome);
|
|
127
329
|
|
|
128
330
|
const toneColor = (item: StatItem): string =>
|
|
129
331
|
item.color || (statToneColor(item.tone, colors) ?? (nativePaneChrome ? colors.textBright : colors.text));
|
|
@@ -132,25 +334,66 @@ export function StatGrid({ items, width, columns: columnsProp }: StatGridProps)
|
|
|
132
334
|
<Box
|
|
133
335
|
flexDirection="column"
|
|
134
336
|
paddingX={nativePaneChrome ? 0 : 1}
|
|
135
|
-
height={
|
|
337
|
+
height={lineCount}
|
|
136
338
|
flexShrink={0}
|
|
137
339
|
data-gloom-role="stat-grid"
|
|
138
340
|
style={nativePaneChrome ? { "--stat-label-w": `${labels}ch`, "--stat-columns": String(columns) } : undefined}
|
|
139
341
|
>
|
|
140
|
-
{rows.map((row, rowIndex) =>
|
|
342
|
+
{rows.map((row, rowIndex) => {
|
|
343
|
+
const wrapped = wrapping[rowIndex];
|
|
344
|
+
if (wrapped) {
|
|
345
|
+
const item = row[0]!;
|
|
346
|
+
const label = t(item.label);
|
|
347
|
+
const width = item.wide ? innerWidth : cellWidth;
|
|
348
|
+
const labelWidth = cellLabelWidth(width, labels, widestValue);
|
|
349
|
+
const [firstValue, ...moreValue] = wrapped.value;
|
|
350
|
+
return (
|
|
351
|
+
<Box key={rowIndex} height={WRAPPED_CELL_LINES} flexDirection="row" data-gloom-role="stat-grid-row">
|
|
352
|
+
<Box
|
|
353
|
+
key={item.id ?? `${rowIndex}:0`}
|
|
354
|
+
width={nativePaneChrome ? undefined : width}
|
|
355
|
+
height={WRAPPED_CELL_LINES}
|
|
356
|
+
flexDirection="row"
|
|
357
|
+
overflow="hidden"
|
|
358
|
+
data-gloom-role="stat-grid-cell"
|
|
359
|
+
data-wide={item.wide ? "true" : undefined}
|
|
360
|
+
data-wrapped="true"
|
|
361
|
+
data-gloom-stat-id={item.id}
|
|
362
|
+
>
|
|
363
|
+
<Text fg={colors.textDim} data-gloom-role="stat-grid-label">
|
|
364
|
+
{nativePaneChrome ? label : labelText(label, labelWidth)}
|
|
365
|
+
</Text>
|
|
366
|
+
<Box flexDirection="column" flexGrow={1} minWidth={0}>
|
|
367
|
+
<Box height={1} flexDirection="row">
|
|
368
|
+
<Text fg={toneColor(item)} attributes={TextAttributes.BOLD} data-gloom-role="stat-grid-value">{firstValue}</Text>
|
|
369
|
+
{item.split && wrapped.bar ? (
|
|
370
|
+
<Box marginLeft={nativePaneChrome ? 0 : 1} flexShrink={nativePaneChrome ? 1 : 0} style={nativePaneChrome ? { minWidth: 0 } : undefined}>
|
|
371
|
+
<SplitBar parts={item.split} width={nativePaneChrome ? SPLIT_BAR_CELLS : wrapped.bar} />
|
|
372
|
+
</Box>
|
|
373
|
+
) : null}
|
|
374
|
+
</Box>
|
|
375
|
+
{moreValue.map((line, index) => (
|
|
376
|
+
<Text key={`value:${index}`} fg={toneColor(item)} attributes={TextAttributes.BOLD} data-gloom-role="stat-grid-value">{line}</Text>
|
|
377
|
+
))}
|
|
378
|
+
{wrapped.detail != null ? (
|
|
379
|
+
<Text fg={colors.textDim} data-gloom-role="stat-grid-detail">{wrapped.detail}</Text>
|
|
380
|
+
) : null}
|
|
381
|
+
</Box>
|
|
382
|
+
</Box>
|
|
383
|
+
</Box>
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
return (
|
|
141
387
|
<Box key={rowIndex} height={1} flexDirection="row" gap={nativePaneChrome ? 0 : CELL_GAP} data-gloom-role="stat-grid-row">
|
|
142
388
|
{row.map((item, index) => {
|
|
143
389
|
const label = t(item.label);
|
|
144
390
|
const width = item.wide ? innerWidth : cellWidth;
|
|
145
|
-
|
|
146
|
-
// never gets less than half the cell, so one long value cannot cut
|
|
147
|
-
// every label in a grid with a fixed column count.
|
|
148
|
-
const labelWidth = Math.min(labels + 1, Math.max(4, Math.floor(width * 0.5), width - widestValue - 1));
|
|
391
|
+
const labelWidth = cellLabelWidth(width, labels, widestValue);
|
|
149
392
|
const valueWidth = Math.max(1, width - labelWidth);
|
|
150
393
|
const value = nativePaneChrome ? item.value : truncateToDisplayWidth(item.value, valueWidth);
|
|
151
394
|
// The bar shows only whole enough to read; the value never gives way to it.
|
|
152
395
|
const barRoom = valueWidth - displayWidth(value) - 1;
|
|
153
|
-
const barCells = !item.split ? 0 : nativePaneChrome ? SPLIT_BAR_CELLS :
|
|
396
|
+
const barCells = !item.split ? 0 : nativePaneChrome ? SPLIT_BAR_CELLS : splitBarCells(barRoom);
|
|
154
397
|
const detailWidth = valueWidth - displayWidth(value) - (barCells ? barCells + 1 : 0) - 2;
|
|
155
398
|
// The desktop cell has no gap between cells: its label column, inset and gaps take the room
|
|
156
399
|
// (measured short, as a cell is wider than a character, so a form that fits is never clipped).
|
|
@@ -177,7 +420,7 @@ export function StatGrid({ items, width, columns: columnsProp }: StatGridProps)
|
|
|
177
420
|
: undefined}
|
|
178
421
|
>
|
|
179
422
|
<Text fg={colors.textDim} data-gloom-role="stat-grid-label">
|
|
180
|
-
{nativePaneChrome ? label :
|
|
423
|
+
{nativePaneChrome ? label : labelText(label, labelWidth)}
|
|
181
424
|
</Text>
|
|
182
425
|
<Text fg={toneColor(item)} attributes={TextAttributes.BOLD} data-gloom-role="stat-grid-value">{value}</Text>
|
|
183
426
|
{item.split && barCells ? (
|
|
@@ -192,7 +435,8 @@ export function StatGrid({ items, width, columns: columnsProp }: StatGridProps)
|
|
|
192
435
|
);
|
|
193
436
|
})}
|
|
194
437
|
</Box>
|
|
195
|
-
|
|
438
|
+
);
|
|
439
|
+
})}
|
|
196
440
|
</Box>
|
|
197
441
|
);
|
|
198
442
|
}
|
package/src/core/app-services.ts
CHANGED
|
@@ -10,8 +10,17 @@ import { createAppRuntime } from "./app-runtime";
|
|
|
10
10
|
|
|
11
11
|
export type AppServices = ReturnType<typeof createAppServices>;
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
interface AppServicesOptions extends AppServicesFactoryOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Runs where the user waits on it (the terminal, the desktop app), so no
|
|
16
|
+
* database statement waits on another program's lock (data/sqlite/access.ts).
|
|
17
|
+
* A command sets it to false and waits instead.
|
|
18
|
+
*/
|
|
19
|
+
interactive?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createAppServices({ config, plugins, externalPlugins, interactive = true }: AppServicesOptions) {
|
|
23
|
+
const persistence = new AppPersistence(join(config.dataDir, ".gloomberb-cache.db"), { interactive });
|
|
15
24
|
const tickerRepository = new TickerRepository(persistence.tickers);
|
|
16
25
|
const providerRouter = new AssetDataRouter(null, [], persistence.resources, connectionHealth);
|
|
17
26
|
const runtime = createAppRuntime({
|